Events are the primary communication mechanism in Multisynq applications. The publish-subscribe (pub-sub) pattern enables clean, decoupled communication between Models and Views while maintaining perfect synchronization.

Core Event API

These functions are only available to classes that inherit from Multisynq.Model or Multisynq.View.

Sends an event to all subscribers

publish(scope, event, data)
  • scope: Namespace for the event (String)
  • event: Name of the event (String)
  • data: Optional data payload (any serializable type)
// Example: Publishing player input
this.publish("player-123", "move", { direction: "left", speed: 5 });

Handler Requirements

Critical difference: Model and View handlers have different requirements!

Must use method names, not function expressions

// ✅ Correct - use method name
class GameModel extends Multisynq.Model {
    init() {
        this.subscribe("input", "move", this.handleMove);
    }
    
    handleMove(data) {
        // Handle the move
    }
}
// ❌ Incorrect - function expressions don't work
class GameModel extends Multisynq.Model {
    init() {
        this.subscribe("input", "move", (data) => {
            // This won't work in models!
        });
    }
}

This is because functions cannot be serialized, so only the method name is stored.

Event Routing Patterns

Understanding event routing is crucial for proper Multisynq development:

Events are transmitted to the synchronizer and mirrored to all users

// View publishes
this.publish("game", "player-action", { type: "jump" });

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

Use case: User input, game actions, any synchronized state changes

Scopes and Namespacing

Scopes provide namespacing to avoid event name conflicts and enable targeted communication:

Built-in scopes available everywhere

// Session-wide events
this.subscribe(this.sessionId, "game-start", this.handleGameStart);

// User-specific events
this.subscribe(this.viewId, "user-input", this.handleUserInput);

// Public events
this.subscribe("public", "announcement", this.handleAnnouncement);

Practical Examples

Best Practices

🎯 Targeted Communication

Use specific scopes for efficient communication

// ✅ Good - specific scope
this.publish(playerId, "player-event", data);

// ❌ Avoid - broad scope when specific is better
this.publish("global", "player-event", data);

📦 Small Payloads

Keep event data small and simple

// ✅ Good - minimal data
this.publish("input", "move", { direction: "left" });

// ❌ Avoid - large data structures
this.publish("input", "move", { 
    player: entirePlayerObject,
    gameState: entireGameState
});

🔄 Avoid Chains

Don’t create Model → View → Model chains

// ❌ Avoid this pattern
class GameModel extends Multisynq.Model {
    someMethod() {
        // Model triggers view event
        this.publish("ui", "update", data);
    }
}

class GameView extends Multisynq.View {
    init() {
        this.subscribe("ui", "update", (data) => {
            // View triggers model event - BAD!
            this.publish("model", "response", processedData);
        });
    }
}

🧹 Clean Up

Unsubscribe when no longer needed

class TemporaryComponent extends Multisynq.View {
    init() {
        this.subscribe("temp", "event", this.handler);
    }
    
    destroy() {
        this.unsubscribe("temp", "event");
        // Or use unsubscribeAll() for all subscriptions
        super.destroy();
    }
}

Common Patterns

Centralized state management

class GameState extends Multisynq.Model {
    init() {
        this.score = 0;
        this.level = 1;
        
        this.subscribe("game", "score", this.updateScore);
        this.subscribe("game", "level", this.updateLevel);
    }
    
    updateScore(points) {
        this.score += points;
        this.publish("ui", "score-changed", this.score);
    }
    
    updateLevel(newLevel) {
        this.level = newLevel;
        this.publish("ui", "level-changed", this.level);
    }
}

Event Debugging

Track event flow in development

class DebugModel extends Multisynq.Model {
    publish(scope, event, data) {
        console.log(`[MODEL] Publishing: ${scope}:${event}`, data);
        super.publish(scope, event, data);
    }
    
    subscribe(scope, event, handler) {
        console.log(`[MODEL] Subscribing to: ${scope}:${event}`);
        super.subscribe(scope, event, handler);
    }
}

Performance Considerations

Avoid these performance pitfalls:

  1. High-frequency events: Don’t publish events every frame
  2. Large payloads: Keep event data minimal
  3. Circular subscriptions: Avoid event loops
  4. Unused subscriptions: Always clean up when done

Limit event frequency

class ThrottledInput extends Multisynq.View {
    init() {
        this.lastMove = 0;
        document.addEventListener('mousemove', this.handleMouseMove);
    }
    
    handleMouseMove(e) {
        const now = Date.now();
        if (now - this.lastMove < 16) return; // ~60fps max
        
        this.lastMove = now;
        this.publish("input", "mouse-move", {
            x: e.clientX,
            y: e.clientY
        });
    }
}

Next Steps

Events are the nervous system of Multisynq applications. Understanding when and how to use them effectively is crucial for building responsive, maintainable multiplayer experiences.