A complete Multisynq application consists of two main components: a Model that contains all shared state and logic, and a View that handles user interface and input. This guide shows you how to structure and launch your applications.

Application Structure

Every Multisynq app requires exactly two classes: one Model and one View.

Essential application template

// 1. Define your Model class
class MyModel extends Multisynq.Model {
    init() {
        // Initialize shared state and logic
        this.gameState = "waiting";
        this.players = new Map();
        this.startTime = this.now();
        
        // Subscribe to user input events
        this.subscribe("input", "player-action", this.handlePlayerAction);
        
        // Start main game loop
        this.future(1000/60).gameLoop();
    }
    
    handlePlayerAction(data) {
        // Process user input and update state
        console.log("Player action:", data);
    }
    
    gameLoop() {
        // Main simulation logic
        if (this.gameState === "playing") {
            this.updateGame();
        }
        
        // Continue the loop
        this.future(1000/60).gameLoop();
    }
    
    updateGame() {
        // Game simulation logic here
    }
}

// 2. REQUIRED: Register your model
MyModel.register("MyModel");

// 3. Define your View class
class MyView extends Multisynq.View {
    init() {
        // Get reference to the model
        this.model = this.wellKnownModel("MyModel");
        
        // Set up user interface
        this.setupUI();
        this.setupInputHandlers();
        this.startRenderLoop();
        
        // Subscribe to model events
        this.subscribe("game", "state-changed", this.updateDisplay);
    }
    
    setupUI() {
        // Create DOM elements
        this.canvas = document.getElementById('gameCanvas');
        this.ctx = this.canvas.getContext('2d');
        
        // Style the interface
        document.body.style.margin = '0';
        document.body.style.backgroundColor = '#222';
    }
    
    setupInputHandlers() {
        // Handle user input
        this.canvas.addEventListener('click', (event) => {
            const rect = this.canvas.getBoundingClientRect();
            const x = event.clientX - rect.left;
            const y = event.clientY - rect.top;
            
            // Send input to model
            this.publish("input", "player-action", { x, y, type: "click" });
        });
        
        document.addEventListener('keydown', (event) => {
            this.publish("input", "player-action", { 
                key: event.key, 
                type: "keydown" 
            });
        });
    }
    
    startRenderLoop() {
        this.render();
    }
    
    render() {
        // Clear canvas
        this.ctx.fillStyle = '#222';
        this.ctx.fillRect(0, 0, this.canvas.width, this.canvas.height);
        
        // Draw current state
        this.drawGame();
        
        // Continue render loop
        requestAnimationFrame(() => this.render());
    }
    
    drawGame() {
        // Read model state and draw UI
        this.ctx.fillStyle = 'white';
        this.ctx.font = '24px Arial';
        this.ctx.fillText(
            `Game State: ${this.model.gameState}`, 
            20, 40
        );
        this.ctx.fillText(
            `Players: ${this.model.players.size}`, 
            20, 70
        );
    }
    
    updateDisplay() {
        // React to model changes (optional)
        console.log("Model state updated");
    }
}

// 4. Register your view
MyView.register("MyView");

Critical: Every Model subclass must call .register("ClassName") with the exact class name.

Launching a Session

You need an API key from multisynq.io/coder to launch sessions.

Standard session setup

// Configure your session
const apiKey = "your_api_key_here";         // Get from multisynq.io/coder
const appId = "com.example.myapp";          // Unique app identifier
const name = Multisynq.App.autoSession();   // Auto-generate session name
const password = Multisynq.App.autoPassword(); // Auto-generate password

// Launch the session
Multisynq.Session.join({ 
    apiKey, 
    appId, 
    name, 
    password, 
    model: MyModel, 
    view: MyView 
});

The autoSession() and autoPassword() helpers parse URL parameters and create random values if needed.

Session Lifecycle

Understanding the session lifecycle helps you build robust applications that handle joining, leaving, and persistence correctly.

What happens when you join a session

// When Session.join() is called, this sequence occurs:

// 1. Connect to synchronizer
console.log("Connecting to Multisynq synchronizer...");

// 2. Check for existing session state
// If session exists: Load from snapshot
// If new session: Initialize fresh

// 3. Instantiate Model
console.log("Creating model instance...");
const model = MyModel.create();

// 4. Initialize or restore model state
if (isNewSession) {
    // First user - run init()
    model.init();
    console.log("Model initialized with fresh state");
} else {
    // Joining existing session - load snapshot
    // model.init() is NOT called
    console.log("Model restored from snapshot");
}

// 5. Instantiate View
console.log("Creating view instance...");
const view = new MyView();
view.setModel(model);

// 6. Start main execution loop
console.log("Starting main loop...");
startMainLoop(model, view);

Critical: model.init() only runs for the first user in a session. Subsequent users get the model from a snapshot.

Main Loop Execution

Multisynq automatically manages the main loop, processing model events first, then view events, then rendering.

Best Practices

🏗️ Application Structure

Organize your code effectively

// ✅ Good structure
class GameModel extends Multisynq.Model {
    init() {
        this.initializeGameState();
        this.setupEventHandlers();
        this.startGameSystems();
    }
    
    initializeGameState() {
        // Clear separation of concerns
    }
}

// ✅ Modular views
class GameView extends Multisynq.View {
    init() {
        this.ui = new UIManager(this);
        this.renderer = new GameRenderer(this);
        this.input = new InputHandler(this);
    }
}

🔄 Session Management

Handle session lifecycle properly

// ✅ Robust session handling
try {
    const session = await Multisynq.Session.join(options);
    
    // Add error handling
    session.on('error', handleSessionError);
    session.on('disconnect', handleDisconnect);
    
    return session;
} catch (error) {
    console.error("Session failed:", error);
    showErrorDialog(error);
}

🎯 Performance

Optimize for smooth gameplay

// ✅ Efficient rendering
render() {
    if (this.needsRedraw) {
        this.drawGame();
        this.needsRedraw = false;
    }
}

// ✅ Batch operations
handleMultipleInputs(inputs) {
    // Process inputs in batches
    this.publish("input", "batch", inputs);
}

🛡️ Error Handling

Build resilient applications

// ✅ Graceful error handling
class RobustModel extends Multisynq.Model {
    init() {
        try {
            this.setupGame();
        } catch (error) {
            this.handleInitError(error);
        }
    }
    
    handleInitError(error) {
        console.error("Init failed:", error);
        this.gameState = "error";
        this.publish("error", "init-failed", error);
    }
}

Common Patterns

Next Steps

Building a complete Multisynq application requires understanding both the technical structure (Model + View + Session) and the patterns for managing user interaction, state changes, and session lifecycle. Start with simple applications and gradually add complexity as you master these fundamentals.