| title | Real-time Synchronization |
|---|---|
| description | Learn how Multisynq achieves perfect synchronization through deterministic execution |
Unlike traditional approaches, every user runs the same deterministic application logic locally, ensuring identical state across all participants.
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.
Same inputs always produce same outputs across all clients Reflector servers order all events into a single canonical stream Global heartbeat ensures time-dependent operations stay in sync Reflectors only pass messages - all logic runs on clientsUser 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.
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);
}
}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;
}
}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);
}
}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
}));
}
}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
}
}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);
}
}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");
}
}
}- Use
this.random()instead ofMath.random()in Models - Use
this.now()instead ofDate.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.
- Never write to Model from View - only through events
- Don't use
Math.random()in Model code - Don't use
Date.now()orsetTimeout()in Model code - Don't access DOM from Model code
- Don't make HTTP requests from Model code
Multisynq automatically handles:
- Temporary network interruptions
- Device sleep/wake cycles
- Browser tab switching
- Connection drops and recovery
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";
}
}// 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();
});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