Timeline模块架构
Implementation notes and component relationships for the timeline system.
Overview
The timeline module is layered so that each piece handles a focused responsibility.
Component Layout
┌─────────────────────────────────────────────────────────┐
│ TimelineController (Facade) │
│ - Implements ITimelineTarget │
│ - Orchestrates all functionality │
├─────────────────────────────────────────────────────────┤
│ ┌──────────────────────┐ ┌──────────────────────┐ │
│ │ AnimationState │ │ TimeCalculator │ │
│ │ (State Machine) │ │ (Time Math) │ │
│ │ │ │ │ │
│ │ - play/pause/ │ │ - Scaling │ │
│ │ resume/stop │ │ - Offsets │ │
│ │ - Speed control │ │ - Delta calc │ │
│ │ - Events │ │ - Stats │ │
│ └──────────────────────┘ └──────────────────────┘ │
│ │ │ │
│ │ │ │
│ └──────────┬─────────────┘ │
│ ▼ │
│ ┌──────────────────────────────────────────────┐ │
│ │ TimeUpdateModeHelper │ │
│ │ (Strategy Pattern) │ │
│ │ │ │
│ │ - Fixed Delta │ │
│ │ - Variable Delta │ │
│ └──────────────────────────────────────────────┘ │
├─────────────────────────────────────────────────────────┤
│ ┌──────────────────────────────────────────────┐ │
│ │ Event System (Observer) │ │
│ │ │ │
│ │ - State change events │ │
│ │ - Time change events │ │
│ │ - Speed change events │ │
│ └──────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────┘
Responsibilities
TimelineController
Responsibilities:
- Provides the public API (implements
ITimelineTarget) - Coordinates
AnimationState,TimeCalculator, and events - Owns listener registration and dispatch
- Provides compatibility methods for legacy code
- Tracks frame count and statistics
- Detects fallback preview mode
Key Features:
- Implements
ITimelineTargetinterface for consistent API - Composition pattern: delegates to
AnimationStateandTimeCalculator - Façade pattern: hides internal complexity
- Observer pattern: event system for state/time changes
- Compatibility layer: provides
startAnimation,pauseAnimationmethods
Methods:
- Playback:
start(),pause(),resume(),stop() - Time:
setTime(),setSpeed(),setTimeScale(),setTimeOffset(),setTimeUpdateMode() - Update:
update(rafNow?)– Returns adjusted time - Queries:
getCurrentTime(),getFrameTime(),isFallbackPreviewMode(),getStats() - State:
isPlaying(),isPaused(),isStopped(),getPlaybackState() - Events:
addEventListener(),removeEventListener(),clearEventListeners()
AnimationState
Responsibilities:
- Stores playback state (playing/paused/stopped)
- Manages animation speed
- Emits state-change events
- Implements a finite-state machine with validation
State Machine:
Key Features:
- State validation (e.g., can't pause if not playing)
- Speed clamping (minimum 0.1x)
- Event emission on state changes
- State info queries
Methods:
- Control:
play(speed?),pause(),resume(),stop(),reset() - Speed:
setSpeed(speed),getSpeed() - Queries:
getState(),isPlaying,isPaused,isStopped,getStateInfo() - Events:
addEventListener(),removeEventListener(),clearEventListeners()
TimeCalculator
Responsibilities:
- Handles time scaling, offsets, and delta updates
- Applies different update strategies via
TimeUpdateModeHelper - Tracks frame time and last update time
- Computes adjusted time (with scaling and offset)
Time Calculation Flow:
raw input (rafNow)
│
▼
calculateDeltaTime() [via TimeUpdateModeHelper]
│
▼
apply timeScale * animationSpeed
│
▼
accumulate frameTime
│
▼
apply timeOffset and timeScale
│
▼
adjustedTime (output)
Key Features:
- Frame time tracking (
frameTimeproperty) - Last update time tracking (for variable delta mode)
- Configurable time scale and offset
- Support for fixed and variable delta modes
- Statistics and cloning support
Methods:
- Time Control:
setTime(time),resetTime(),setTimeScale(scale),setTimeOffset(offset) - Update Mode:
setTimeUpdateMode(mode),getTimeUpdateMode() - Speed:
setAnimationSpeed(speed),getAnimationSpeed() - Calculation:
calculateTime(rafNow, isPlaying, isPaused)– ReturnsTimeCalculationResult - Queries:
getAdjustedTime(),getTimeScale(),getTimeOffset(),getStats() - Utility:
clone(),updateConfig()
TimeUpdateMode
Responsibilities:
- Encapsulates update strategies (
fixed_delta,variable_delta) - Provides static helper methods for delta calculation
- Validates configuration and exposes descriptions
TimeUpdateMode Enum:
enum TimeUpdateMode {
FIXED_DELTA = 'fixed_delta', // Deterministic fixed steps
VARIABLE_DELTA = 'variable_delta' // Frame-rate dependent
}
TimeUpdateModeHelper Methods:
calculateDeltaTime(params)– Calculates delta based on modeisValidMode(mode)– Type guard for mode validationfromString(modeString)– Converts string to enumgetDefaultFixedDeltaTime()– Returns default fixed delta (~60 FPS)getDefaultMaxDeltaTime()– Returns default max delta (50ms)getModeDescription(mode)– Returns human-readable description
Update Strategies:
Fixed Delta:
- Uses
fixedDeltaTimevalue directly - Deterministic and frame-rate independent
- Best for: Physics simulations, deterministic animations
Variable Delta:
- Calculates from actual frame time difference
- Clamped to
maxDeltaTimeto prevent extreme values - Best for: Smooth playback, frame-rate dependent animations
Data Flow
Time Update Flow
- Client calls
timeline.update(rafNow)(optionalrafNow, defaults toperformance.now()) - Controller delegates to
TimeCalculator.calculateTime(rafNow, isPlaying, isPaused) - Calculator:
- Checks if playing and not paused
- Calls
TimeUpdateModeHelper.calculateDeltaTime()based on mode - Applies
timeScale * animationSpeedto delta - Accumulates
frameTime - Computes
adjustedTime(with offset and scale) - Controller:
- Increments
frameCountifshouldUpdate - Returns
adjustedTime - Event System: (if time changed significantly)
- Emits
timeChangeevent - Notifies all listeners
State Change Flow
- Client calls
start(),pause(),resume(), orstop() - Controller forwards to
AnimationStatemethod - AnimationState:
- Validates state transition (e.g., can't pause if not playing)
- Updates internal state
- Emits state change event (
play,pause,resume,stop) - Controller:
- Relays event to its own listeners
- Event system notifies all registered listeners
Speed Change Flow
- Client calls
setSpeed(speed) - Controller:
- Updates
AnimationState.setSpeed(speed) - Updates
TimeCalculator.setAnimationSpeed(speed) - AnimationState:
- Clamps speed (minimum 0.1x)
- Emits
speedChangeevent if speed actually changed - Event System: Notifies listeners of speed change
Design Patterns
Composition Pattern
Controller composes multiple helpers:
class TimelineController {
private animationState = new AnimationState();
private timeCalculator = new TimeCalculator();
start(speed?: number) {
this.animationState.play(speed);
}
update(rafNow?: number): number {
return this.timeCalculator.calculateTime(
rafNow,
this.animationState.isPlaying,
this.animationState.isPaused
).adjustedTime;
}
}
Façade Pattern
TimelineController hides coordination complexity behind a simple API, implementing ITimelineTarget interface.
Observer Pattern
Event listeners subscribe to time/state change events:
State Machine Pattern
AnimationState implements a finite-state machine with validated transitions:
- States: STOPPED, PLAYING, PAUSED
- Transitions validated (e.g., can't pause if not playing)
Strategy Pattern
TimeUpdateModeHelper encapsulates different delta calculation strategies:
- FIXED_DELTA – Deterministic fixed steps
- VARIABLE_DELTA – Frame-rate dependent steps
Adapter Pattern
Compatibility methods adapt new API to legacy code:
startAnimation(speed) → start(speed)
pauseAnimation() → pause()
setAnimationTime(time) → setTime(time)
Extensibility
Plugin Model
class CustomTimelineExtension {
constructor(timeline: TimelineController) {
timeline.addEventListener(event => this.handle(event));
}
private handle(event: AnimationStateChangeEvent) {
switch (event.type) {
case 'play':
this.onPlay();
break;
case 'timeChange':
this.onTimeChange(event.data.time);
break;
}
}
}
Config-Driven Behavior
const config: TimelineConfig = {
timeScale: 1.0, // Time scaling factor
timeOffset: 0.0, // Time offset in seconds
timeUpdateMode: 'fixed_delta', // or 'variable_delta'
animationSpeed: 1.0, // Animation speed multiplier
fixedDeltaTime: 0.016, // Fixed delta time (~60 FPS)
maxDeltaTime: 0.05, // Max delta time (50ms)
};
const timeline = new TimelineController(config);
Interface Abstraction
TimelineController implements ITimelineTarget for consistent API:
interface ITimelineTarget {
// Playback control
startAnimation(speed?: number): void;
pauseAnimation(): void;
resumeAnimation(): void;
stopAnimation(): void;
// Time control
setAnimationTime(time: number): void;
setAnimationSpeed(speed: number): void;
getAnimationSpeed(): number;
// Time scaling
setTimeScale(scale: number): void;
getTimeScale(): number;
setTimeOffset(offset: number): void;
getTimeOffset(): number;
// Update mode
setTimeUpdateMode(mode: 'fixed_delta' | 'variable_delta'): void;
getTimeUpdateMode(): 'fixed_delta' | 'variable_delta';
// Queries
getCurrentTime(): number;
supportsAnimation(): boolean;
}
Fallback Preview Mode
The timeline detects fallback preview scenarios when frameTime < -0.5:
This is useful for export/recording scenarios where time may be set to negative values.
Performance considerations
- Event arrays and batching minimise overhead.
- Caches prevent redundant calculations.
- Clean up listeners promptly (avoid leaks) and prefer
WeakMapfor ephemeral data.
Testing strategy
Unit tests
describe('AnimationState', () => {
it('transitions STOPPED → PLAYING', () => {
const state = new AnimationState();
state.play();
expect(state.getState()).toBe(AnimationPlaybackState.PLAYING);
});
});
Integration tests
describe('TimelineController', () => {
it('coordinates updates correctly', () => {
const timeline = new TimelineController();
timeline.start();
timeline.update(0.016);
expect(timeline.getPlaybackState()).toBe(AnimationPlaybackState.PLAYING);
});
});
Performance tests
describe('TimeCalculator performance', () => {
it('hits 10k updates < 10ms', () => {
const calc = new TimeCalculator(config);
const t0 = performance.now();
for (let i = 0; i < 10_000; i++) calc.calculateTime(0.016);
expect(performance.now() - t0).toBeLessThan(10);
});
});
Summary
The timeline architecture embraces: 1. Single responsibility per component. 2. Open/closed extensibility. 3. Dependency inversion through interfaces. 4. Interface segregation (small, focused APIs). 5. Composition-focused design.
The result is an easily testable, extensible, and maintainable timeline subsystem.