| 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.
**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 **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
}
}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
}
}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);Multisynq.Session.join({
name: "my-game-session",
password: "secret123",
model: GameModel,
view: GameView
});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]
// Snapshots are handled automatically
// New users join by loading the latest snapshot
// then applying recent events to catch up// 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.
**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";
}
}
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!`);
}
}
- Direct Model Mutation: Never modify model state directly from the view
- Async Operations in Models: Models must be deterministic and synchronous
- Heavy View Events: Don't send large data payloads in events
- Circular Dependencies: Avoid event loops between model and view