Unlike traditional approaches, every user runs the same deterministic application logic locally, ensuring identical state across all participants.

How Multisynq Synchronization Works

Deterministic Virtual Machine

Multisynq runs your Model code in a deterministic virtual machine called Teatime. This ensures that given the same initial state and the same sequence of events, every user’s device produces exactly the same result.

Key Principles

Deterministic Execution

Same inputs always produce same outputs across all clients

Event Ordering

Reflector servers order all events into a single canonical stream

Synchronized Time

Global heartbeat ensures time-dependent operations stay in sync

No Server Logic

Reflectors only pass messages - all logic runs on clients

Architecture Overview

User A Device                 Reflector Server              User B Device
┌─────────────┐              ┌─────────────┐              ┌─────────────┐
│    View A   │              │             │              │    View B   │
│     ↕       │              │   Orders    │              │     ↕       │
│   Model A   │ ←── Events ──│   Events    │── Events ──→ │   Model B   │
│ (Teatime VM)│              │             │              │ (Teatime VM)│
└─────────────┘              └─────────────┘              └─────────────┘

All Models run identically on every device, synchronized through ordered events.

Basic Synchronization Example

Model-Driven State Changes

class CounterModel extends Multisynq.Model {
    init() {
        this.count = 0;
        
        // Subscribe to events from any view
        this.subscribe(this.sessionId, "increment", this.handleIncrement);
        this.subscribe(this.sessionId, "reset", this.handleReset);
    }
    
    handleIncrement() {
        // This runs identically on every user's device
        this.count += 1;
        
        // Notify all views of the change
        this.publish(this.sessionId, "countChanged", this.count);
    }
    
    handleReset() {
        this.count = 0;
        this.publish(this.sessionId, "countChanged", this.count);
    }
}

View Interaction

class CounterView extends Multisynq.View {
    constructor(model) {
        super(model);
        
        // Listen for model changes
        this.subscribe(this.sessionId, "countChanged", this.updateDisplay);
        
        // Set up UI event handlers
        this.setupButtons();
        
        // Display initial state
        this.updateDisplay(model.count);
    }
    
    setupButtons() {
        document.getElementById("increment").onclick = () => {
            // Send event to model (will sync to all users)
            this.publish(this.sessionId, "increment");
        };
        
        document.getElementById("reset").onclick = () => {
            this.publish(this.sessionId, "reset");
        };
    }
    
    updateDisplay(count) {
        document.getElementById("counter").textContent = count;
    }
}

Advanced Synchronization Patterns

Chat System with Message History

class ChatModel extends Multisynq.Model {
    init() {
        this.messages = [];
        this.subscribe(this.sessionId, "sendMessage", this.addMessage);
    }
    
    addMessage({ text, username }) {
        // Deterministic ID generation
        const message = {
            id: this.random().toString(),  // Synchronized random
            text: text,
            username: username,
            timestamp: this.now()          // Synchronized time
        };
        
        this.messages.push(message);
        this.publish(this.sessionId, "messagesUpdated", this.messages);
    }
}

Real-time Cursor Tracking

class CursorModel extends Multisynq.Model {
    init() {
        this.cursors = new Map();
        this.subscribe(this.sessionId, "view-join", this.userJoined);
        this.subscribe(this.sessionId, "view-exit", this.userLeft);
        this.subscribe(this.sessionId, "cursorMove", this.updateCursor);
    }
    
    userJoined(viewId) {
        this.cursors.set(viewId, { x: 0, y: 0, color: this.getRandomColor() });
        this.publish(this.sessionId, "cursorsChanged", this.getCursorsArray());
    }
    
    userLeft(viewId) {
        this.cursors.delete(viewId);
        this.publish(this.sessionId, "cursorsChanged", this.getCursorsArray());
    }
    
    updateCursor({ viewId, x, y }) {
        if (this.cursors.has(viewId)) {
            this.cursors.get(viewId).x = x;
            this.cursors.get(viewId).y = y;
            this.publish(this.sessionId, "cursorsChanged", this.getCursorsArray());
        }
    }
    
    getRandomColor() {
        const colors = ['red', 'blue', 'green', 'purple', 'orange'];
        return colors[Math.floor(this.random() * colors.length)];
    }
    
    getCursorsArray() {
        return Array.from(this.cursors.entries()).map(([viewId, cursor]) => ({
            viewId,
            ...cursor
        }));
    }
}

Synchronized Time and Randomness

Deterministic Time

class GameModel extends Multisynq.Model {
    init() {
        this.startTime = this.now(); // Synchronized start time
        this.gameLoop();
    }
    
    gameLoop() {
        const currentTime = this.now();
        const elapsedTime = currentTime - this.startTime;
        
        // Game logic based on synchronized time
        this.updateGameState(elapsedTime);
        
        // Schedule next update
        this.future(16).gameLoop(); // ~60 FPS
    }
}

Deterministic Random Numbers

class LootModel extends Multisynq.Model {
    init() {
        this.subscribe(this.sessionId, "openChest", this.generateLoot);
    }
    
    generateLoot() {
        // Every user gets the same "random" result
        const lootRoll = this.random();
        
        let loot;
        if (lootRoll < 0.1) {
            loot = "legendary";
        } else if (lootRoll < 0.3) {
            loot = "rare";
        } else {
            loot = "common";
        }
        
        this.publish(this.sessionId, "lootReceived", loot);
    }
}

Future Scheduling

Use future() for time-based events that must stay synchronized:

class TimerModel extends Multisynq.Model {
    init() {
        this.timeLeft = 60; // 60 seconds
        this.startCountdown();
    }
    
    startCountdown() {
        this.publish(this.sessionId, "timerUpdate", this.timeLeft);
        
        if (this.timeLeft > 0) {
            this.timeLeft--;
            // Schedule next tick in 1 second
            this.future(1000).startCountdown();
        } else {
            this.publish(this.sessionId, "timerExpired");
        }
    }
}

Best Practices for Synchronization

Do’s ✅

  • Use this.random() instead of Math.random() in Models
  • Use this.now() instead of Date.now() in Models
  • Use this.future() for time-based operations in Models
  • Communicate via events between Model and View
  • Keep Models deterministic - no external API calls, random DOM access, etc.

Don’ts ❌

  • Never write to Model from View - only through events
  • Don’t use Math.random() in Model code
  • Don’t use Date.now() or setTimeout() in Model code
  • Don’t access DOM from Model code
  • Don’t make HTTP requests from Model code

Connection Resilience

Built-in Reconnection

Multisynq automatically handles:

  • Temporary network interruptions
  • Device sleep/wake cycles
  • Browser tab switching
  • Connection drops and recovery

View-Level Connection Awareness

class NetworkView extends Multisynq.View {
    constructor(model) {
        super(model);
        this.isConnected = true;
    }
    
    update(time) {
        // Check connection status (this is automatic)
        const connected = this.session && this.session.view;
        
        if (connected !== this.isConnected) {
            this.isConnected = connected;
            this.updateConnectionIndicator();
        }
    }
    
    updateConnectionIndicator() {
        const indicator = document.getElementById("connection-status");
        indicator.textContent = this.isConnected ? "Connected" : "Reconnecting...";
        indicator.className = this.isConnected ? "connected" : "disconnected";
    }
}

Testing Synchronization

Multi-User Testing

// Use the Widget Dock for easy multi-device testing
Multisynq.Session.join({
    apiKey: "your-api-key",
    appId: "com.example.test",
    model: TestModel,
    view: TestView
}).then(session => {
    // Show QR code for joining from other devices
    Multisynq.App.makeWidgetDock();
});

Debug Synchronization

Multisynq.Session.join({
    // ... other options
    debug: ["session", "messages", "events", "ticks"]
});

This enables console logging of:

  • Session connection status
  • Messages sent/received
  • Event publishing/subscribing
  • Heartbeat ticks

Next Steps