跳转至

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 ITimelineTarget interface for consistent API
  • Composition pattern: delegates to AnimationState and TimeCalculator
  • Façade pattern: hides internal complexity
  • Observer pattern: event system for state/time changes
  • Compatibility layer: provides startAnimation, pauseAnimation methods

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:

    STOPPED
       │ play()
    PLAYING ◄─────────┐
       │              │
       │ pause()      │ resume()
       ▼              │
    PAUSED            │
       │              │
       └── stop() ────┘

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 (frameTime property)
  • 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) – Returns TimeCalculationResult
  • 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 mode
  • isValidMode(mode) – Type guard for mode validation
  • fromString(modeString) – Converts string to enum
  • getDefaultFixedDeltaTime() – Returns default fixed delta (~60 FPS)
  • getDefaultMaxDeltaTime() – Returns default max delta (50ms)
  • getModeDescription(mode) – Returns human-readable description

Update Strategies:

Fixed Delta:

  • Uses fixedDeltaTime value directly
  • Deterministic and frame-rate independent
  • Best for: Physics simulations, deterministic animations

Variable Delta:

  • Calculates from actual frame time difference
  • Clamped to maxDeltaTime to prevent extreme values
  • Best for: Smooth playback, frame-rate dependent animations

Data Flow

Time Update Flow

  1. Client calls timeline.update(rafNow) (optional rafNow, defaults to performance.now())
  2. Controller delegates to TimeCalculator.calculateTime(rafNow, isPlaying, isPaused)
  3. Calculator:
  4. Checks if playing and not paused
  5. Calls TimeUpdateModeHelper.calculateDeltaTime() based on mode
  6. Applies timeScale * animationSpeed to delta
  7. Accumulates frameTime
  8. Computes adjustedTime (with offset and scale)
  9. Controller:
  10. Increments frameCount if shouldUpdate
  11. Returns adjustedTime
  12. Event System: (if time changed significantly)
  13. Emits timeChange event
  14. Notifies all listeners

State Change Flow

  1. Client calls start(), pause(), resume(), or stop()
  2. Controller forwards to AnimationState method
  3. AnimationState:
  4. Validates state transition (e.g., can't pause if not playing)
  5. Updates internal state
  6. Emits state change event (play, pause, resume, stop)
  7. Controller:
  8. Relays event to its own listeners
  9. Event system notifies all registered listeners

Speed Change Flow

  1. Client calls setSpeed(speed)
  2. Controller:
  3. Updates AnimationState.setSpeed(speed)
  4. Updates TimeCalculator.setAnimationSpeed(speed)
  5. AnimationState:
  6. Clamps speed (minimum 0.1x)
  7. Emits speedChange event if speed actually changed
  8. 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:

timeline.addEventListener((event) => {
  // React to timeline 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:

if (timeline.isFallbackPreviewMode()) {
  // Handle preview mode
}

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 WeakMap for 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.