Skip to content

Latest commit

 

History

History
386 lines (307 loc) · 10.4 KB

File metadata and controls

386 lines (307 loc) · 10.4 KB
title Model-View-Synchronizer
description The core architecture pattern that powers Multisynq applications

Understanding the Model-View-Synchronizer (MVS) architecture is fundamental to building successful Multisynq applications. This pattern ensures perfect synchronization across all users while maintaining clean separation of concerns.

Multisynq Overview

Core Components

**Handles user input and output** - Processes keyboard, mouse, and touch events - Determines what's displayed on screen - Interacts with the DOM - **State is NOT synchronized** across users **Handles calculation and simulation** - Contains all application logic - Manages shared state - **State is ALWAYS identical** for all users - Runs deterministic computations **Stateless cloud service** - Routes events between users - Mirrors events to all session participants - Handles snapshot storage and retrieval - Manages session membership

Key Principles

**The model state is guaranteed to always be identical for all users.**

This is the fundamental promise of Multisynq. No matter how many users are in a session, their model state will be perfectly synchronized. This eliminates the complexity of managing distributed state.

// This state is identical across all users
class Game extends Multisynq.Model {
    init() {
        this.score = 0;        // Same for everyone
        this.players = [];     // Same for everyone
        this.gameState = "active"; // Same for everyone
    }
}
**View state is NOT synchronized and can differ between users.**

This allows for personalized experiences while maintaining shared core functionality. Users can have different UI preferences, screen sizes, or device capabilities.

class GameView extends Multisynq.View {
    init() {
        this.darkMode = localStorage.getItem('darkMode'); // User-specific
        this.screenSize = window.innerWidth;              // Device-specific
        this.notifications = [];                          // Local only
    }
}
**All communication happens through events.**

Events provide a clean, decoupled way for components to communicate. The routing is handled automatically by Multisynq.

// View publishes events to model
this.publish("user-input", { action: "move", direction: "left" });

// Model subscribes to events
this.subscribe("user-input", this.handleUserInput);

Session Architecture

When a Multisynq application starts, it joins a **session**. Users with the same session ID will share the same synchronized state.
Multisynq.Session.join({
    name: "my-game-session",
    password: "secret123",
    model: GameModel,
    view: GameView
});
The **synchronizer** ensures all users receive the same events in the same order, maintaining deterministic execution.
graph TD
    A[User 1 View] --> B[Synchronizer]
    C[User 2 View] --> B
    D[User 3 View] --> B
    B --> E[User 1 Model]
    B --> F[User 2 Model]
    B --> G[User 3 Model]
Loading
**Snapshots** are periodically saved to the cloud. New users can join by loading a recent snapshot instead of replaying all events from the beginning.
// Snapshots are handled automatically
// New users join by loading the latest snapshot
// then applying recent events to catch up

Event Routing Rules

Understanding these routing rules is crucial for proper Multisynq development: **Events from view to model are reflected to all users**
// View publishes
this.publish("player-action", { type: "jump" });

// Model receives on ALL devices
this.subscribe("player-action", this.handlePlayerAction);

This ensures all users see the same game state changes.

**Events from model to view are executed locally only**
// Model publishes locally
this.publish("game-event", { type: "explosion" });

// View receives only on local device
this.subscribe("game-event", this.showExplosion);

This allows for local feedback without network overhead.

**Events between views are local only**
// Useful for UI state management
this.publish("ui-toggle", { panel: "settings" });

Perfect for managing local UI state.

**Events between models are local only**
// Internal model communication
this.publish("internal-update", { data: calculations });

Used for internal model organization.

Data Flow Constraints

**Critical Rule**: The view can read from the model, but can't write to it directly. ```js class GameView extends Multisynq.View { update() { // ✅ Reading from model is allowed const score = this.model.score; const players = this.model.players;
    // ✅ Publishing events to model is allowed
    this.publish("user-input", { action: "move" });
    
    // ✅ Updating local view state is allowed
    this.localState.lastUpdate = Date.now();
}

}

</Tab>

<Tab title="❌ Incorrect">
```js
class GameView extends Multisynq.View {
    update() {
        // ❌ Never directly modify model state
        this.model.score = 100;
        this.model.players.push(newPlayer);
        
        // ❌ This breaks synchronization!
        this.model.gameState = "paused";
    }
}

Practical Example

Here's how the MVS pattern works in practice:

```js class GameModel extends Multisynq.Model { init() { this.players = new Map(); this.gameState = "waiting";
    // Subscribe to view events
    this.subscribe("player-join", this.addPlayer);
    this.subscribe("player-move", this.movePlayer);
}

addPlayer(playerData) {
    // This runs on ALL devices
    const player = Player.create(playerData);
    this.players.set(player.id, player);
    
    // Notify views locally
    this.publish("player-added", player);
}

movePlayer({ playerId, direction }) {
    // This runs on ALL devices
    const player = this.players.get(playerId);
    if (player) {
        player.move(direction);
    }
}

}

</Tab>

<Tab title="View (Local)">
```js
class GameView extends Multisynq.View {
    init() {
        this.canvas = document.getElementById('game-canvas');
        this.ctx = this.canvas.getContext('2d');
        
        // Subscribe to model events
        this.subscribe("player-added", this.onPlayerAdded);
        
        // Handle user input
        document.addEventListener('keydown', this.handleKeyDown);
    }
    
    handleKeyDown(event) {
        // Send to model via event
        this.publish("player-move", {
            playerId: this.viewId,
            direction: event.key
        });
    }
    
    update() {
        // Read from model (allowed)
        const players = this.model.players;
        
        // Render locally
        this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
        for (const player of players.values()) {
            this.drawPlayer(player);
        }
    }
    
    onPlayerAdded(player) {
        // Local feedback only
        this.showNotification(`${player.name} joined!`);
    }
}

Benefits of MVS Architecture

All users see exactly the same state at all times, eliminating sync bugs and conflicts. Clear separation between business logic (Model) and presentation (View) improves maintainability. Deterministic execution means no server-side state management or complex conflict resolution. Local computation with synchronized results provides excellent performance and responsiveness.

Best Practices

- Keep models deterministic and side-effect free - Use `future()` for time-based behaviors - Avoid external dependencies in models - Make model state easily serializable - Handle all user input in the view - Use local state for UI-specific data - Avoid blocking operations in the view - Keep views responsive and interactive - Use clear, descriptive event names - Keep event payloads small and simple - Avoid high-frequency events when possible - Use scoped events for user-specific actions

Common Pitfalls

**Avoid these common mistakes:**
  1. Direct Model Mutation: Never modify model state directly from the view
  2. Async Operations in Models: Models must be deterministic and synchronous
  3. Heavy View Events: Don't send large data payloads in events
  4. Circular Dependencies: Avoid event loops between model and view

Next Steps

Learn how to build robust, synchronized models Master view development and user interaction Deep dive into event-driven communication Understand state persistence and recovery The Model-View-Synchronizer pattern is the foundation of all Multisynq applications. Understanding this architecture deeply will help you build more robust, maintainable, and scalable multiplayer experiences.