Skip to main content
Understanding Multisynq’s time system is crucial for building applications with animations, timers, and time-dependent behaviors. Unlike real-world time, Multisynq uses simulation time to ensure perfect synchronization across all users.

Simulation Time vs Real Time

  • 🕒 Simulation Time
  • ⏰ Real Time
Synchronized, deterministic time used by models
  • Source: Multisynq reflector heartbeat ticks
  • Synchronization: Identical across all users
  • Deterministic: Same time, same results
  • Access: this.now() in models
class GameModel extends Multisynq.Model {
    init() {
        console.log("Session started at:", this.now()); // Simulation time
        this.tick();
    }
    
    tick() {
        const currentTime = this.now();
        console.log("Simulation time:", currentTime);
        
        // All users see identical time values
        this.future(1000).tick(); // Schedule next tick
    }
}
Simulation time only advances when heartbeat ticks are received from the reflector.

Core Time Concepts

📐 Simulation Time

Time since session began
  • Measured in milliseconds
  • Starts at 0 when session begins
  • Identical for all users
  • Only advances with reflector heartbeats
// All users get the same value
const time = this.now(); // e.g., 45723 ms

💓 Heartbeat Ticks

Synchronization mechanism
  • Sent by reflector to all users
  • Advances simulation time
  • Ensures synchronized execution
  • Typically ~60-120Hz
// Multiple calls return same value
console.log(this.now()); // 1000
console.log(this.now()); // 1000 (same!)
console.log(this.now()); // 1000 (until next tick)

The future() Method

future() is Multisynq’s scheduling mechanism for time-based behaviors.
  • Basic Syntax
  • Multiple Schedules
  • Fractional Delays
Schedule a method to run in the future
this.future(delay).methodName(args);
  • delay: Milliseconds to wait (can be fractional)
  • methodName: Method to call when delay expires
  • args: Optional arguments to pass
class AnimationModel extends Multisynq.Model {
    init() {
        this.x = 0;
        this.startAnimation();
    }
    
    startAnimation() {
        this.x += 1;
        
        // Schedule next frame in 16.67ms (~60fps)
        this.future(1000/60).startAnimation();
    }
    
    explode() {
        // Destroy after 2 seconds
        this.future(2000).destroy();
    }
}

Common Patterns

Classic game update pattern
class GameModel extends Multisynq.Model {
    init() {
        this.gameState = "playing";
        this.entities = new Map();
        this.lastUpdate = this.now();
        
        // Start the main game loop
        this.gameLoop();
    }
    
    gameLoop() {
        const currentTime = this.now();
        const deltaTime = currentTime - this.lastUpdate;
        
        // Update all game systems
        this.updatePhysics(deltaTime);
        this.updateAI(deltaTime);
        this.updateAnimations(deltaTime);
        this.checkWinConditions();
        
        this.lastUpdate = currentTime;
        
        // Schedule next frame (60fps)
        this.future(1000/60).gameLoop();
    }
    
    updatePhysics(dt) {
        for (const entity of this.entities.values()) {
            entity.update(dt);
        }
    }
    
    updateAI(dt) {
        // AI logic runs at game loop frequency
    }
    
    updateAnimations(dt) {
        // Smooth animation updates
    }
}
Countdown timers and delayed actions
class TimerModel extends Multisynq.Model {
    init() {
        this.timers = new Map();
        this.nextTimerId = 1;
    }
    
    createTimer(duration, callback, repeat = false) {
        const timerId = this.nextTimerId++;
        const timer = {
            id: timerId,
            duration,
            callback,
            repeat,
            startTime: this.now()
        };
        
        this.timers.set(timerId, timer);
        this.scheduleTimer(timer);
        
        return timerId;
    }
    
    scheduleTimer(timer) {
        this.future(timer.duration)[timer.callback]();
        
        if (timer.repeat) {
            // Reschedule for repeated timers
            this.future(timer.duration).rescheduleTimer(timer.id);
        }
    }
    
    rescheduleTimer(timerId) {
        const timer = this.timers.get(timerId);
        if (timer && timer.repeat) {
            this.scheduleTimer(timer);
        }
    }
    
    cancelTimer(timerId) {
        this.timers.delete(timerId);
        // Note: Cannot cancel already scheduled future calls
    }
    
    // Example usage
    startGame() {
        // Game ends in 5 minutes
        this.createTimer(5 * 60 * 1000, "endGame", false);
        
        // Spawn enemies every 10 seconds
        this.createTimer(10000, "spawnEnemy", true);
    }
    
    endGame() {
        this.gameState = "finished";
        this.publish("game", "ended", { time: this.now() });
    }
    
    spawnEnemy() {
        const enemy = Enemy.create();
        this.entities.set(enemy.id, enemy);
    }
}
Smooth object animations
class AnimatedObject extends Multisynq.Model {
    init() {
        this.position = { x: 0, y: 0 };
        this.target = { x: 100, y: 100 };
        this.animationDuration = 2000; // 2 seconds
        this.animationStart = null;
        
        this.startAnimation();
    }
    
    startAnimation() {
        this.animationStart = this.now();
        this.animate();
    }
    
    animate() {
        const elapsed = this.now() - this.animationStart;
        const progress = Math.min(elapsed / this.animationDuration, 1);
        
        // Smooth easing function
        const easedProgress = this.easeInOutCubic(progress);
        
        // Interpolate position
        this.position.x = this.lerp(0, this.target.x, easedProgress);
        this.position.y = this.lerp(0, this.target.y, easedProgress);
        
        // Notify views of position change
        this.publish("animation", "position", {
            id: this.id,
            position: this.position,
            progress: progress
        });
        
        if (progress < 1) {
            // Continue animation next frame
            this.future(1000/60).animate();
        } else {
            // Animation complete
            this.onAnimationComplete();
        }
    }
    
    easeInOutCubic(t) {
        return t < 0.5 
            ? 4 * t * t * t 
            : 1 - Math.pow(-2 * t + 2, 3) / 2;
    }
    
    lerp(start, end, progress) {
        return start + (end - start) * progress;
    }
    
    onAnimationComplete() {
        this.publish("animation", "complete", { id: this.id });
    }
}

Views and Time

Views use real time, not simulation time!
  • View Future
  • Synchronization
Views can use future() with real time
class GameView extends Multisynq.View {
    init() {
        this.canvas = document.getElementById('canvas');
        this.lastFrame = Date.now();
        
        // Start render loop using real time
        this.renderLoop();
    }
    
    renderLoop() {
        const now = Date.now();
        const deltaTime = now - this.lastFrame;
        
        // Render current frame
        this.render(deltaTime);
        
        this.lastFrame = now;
        
        // Schedule next frame (~60fps)
        this.future(16).renderLoop();
    }
    
    render(deltaTime) {
        // Use simulation time for game state
        const gameTime = this.model.now();
        
        // Use real time for smooth interpolation
        this.drawGameObjects(gameTime, deltaTime);
    }
}

Timing Best Practices

⚡ Performance

Optimize timing frequency
// ✅ Good: Different rates for different systems
this.updatePhysics();    // 60fps - needs precision
this.updateAI();         // 10fps - less critical
this.updateNetworking(); // 20fps - moderate

// ❌ Avoid: Everything at maximum rate
this.future(16).updateEverything(); // Wasteful
// ✅ Good: Batch related operations
this.future(100).batchUpdate();

batchUpdate() {
    this.updateMultipleThings();
    this.processAllChanges();
    this.future(100).batchUpdate();
}

🎯 Precision

Use appropriate timing precision
// ✅ Good: Match timing to need
this.future(1000/60).renderFrame();   // 60fps for smooth animation
this.future(1000/20).checkCollisions();// 20fps for game logic
this.future(5000).saveProgress();      // 5s for persistence

// ❌ Avoid: Inappropriate frequencies
this.future(1).highFrequencySpam();    // Too fast
this.future(10000).criticalGameLogic(); // Too slow

Debugging Time Issues

  • Time Logging
  • Performance Monitoring
Debug timing problems
class DebugModel extends Multisynq.Model {
    init() {
        this.tickCount = 0;
        this.startTime = this.now();
        
        this.debugTick();
    }
    
    debugTick() {
        this.tickCount++;
        const elapsed = this.now() - this.startTime;
        const averageInterval = elapsed / this.tickCount;
        
        console.log(`Tick ${this.tickCount}:`);
        console.log(`  Sim time: ${this.now()}`);
        console.log(`  Elapsed: ${elapsed}ms`);
        console.log(`  Average interval: ${averageInterval.toFixed(2)}ms`);
        
        this.future(1000).debugTick(); // Every second
    }
}

Common Pitfalls

Avoid these timing mistakes:
  • ❌ Real Time in Models
  • ❌ Future Cancellation
  • ❌ Blocking Operations
// ❌ NEVER do this in models
class BadModel extends Multisynq.Model {
    init() {
        this.startTime = Date.now(); // BREAKS SYNCHRONIZATION!
        this.badTick();
    }
    
    badTick() {
        const now = Date.now(); // DIFFERENT ON EACH DEVICE!
        if (now - this.startTime > 5000) {
            this.doSomething(); // Will happen at different times!
        }
        this.future(100).badTick();
    }
}

// ✅ Correct approach
class GoodModel extends Multisynq.Model {
    init() {
        this.startTime = this.now(); // Use simulation time
        this.goodTick();
    }
    
    goodTick() {
        const now = this.now(); // SYNCHRONIZED!
        if (now - this.startTime > 5000) {
            this.doSomething(); // Happens simultaneously!
        }
        this.future(100).goodTick();
    }
}

Next Steps

Mastering simulation time and the future() method is essential for creating responsive, synchronized applications. Always use simulation time in models and real time in views to maintain perfect synchronization across all users.
I