PlayMesh/Docs
HomeGitHubnpm

Instance State

Synchronized runtime key-value state scoped to each instance.

Overview

Every Instance has three key-value stores, all scoped to that instance. In single-node mode they live in memory; with Redis they are stored in Redis hashes and consistent across all nodes.
Method / PropertyDescriptionReturns
instance.stateServer-only runtime state. Never leaves the server.ScopedState
instance.publicStateSynced live to every client in the instance - a snapshot on join, then every change.ScopedState
instance.userState(userId)Private per-user state, synced only to that user's own clients. Other members never receive it.ScopedState
Instance state is for runtime data: active while the instance exists, discarded when it is destroyed. It is not a database replacement.

Synced state

All synced state is server-writable only - clients hold live read replicas via client.stateOf(path), client.userStateOf(path), and client.onStateChange(...).
// Server
await match.publicState.set('round', 3);           // everyone sees this
await match.userState('alice').set('hand', cards); // only Alice sees this
await match.state.set('spawn-seed', seed);         // nobody but the server

// Client
client.stateOf('ranked/match-42');      // public state replica
client.userStateOf('ranked/match-42');  // your private state
client.onStateChange(change => {
  // change.scope === 'user' marks your private state
});

API

All three stores share the same ScopedState API:
Method / PropertyDescriptionReturns
instance.state.get(key)Get a value. Returns undefined if not set.Promise<unknown>
instance.state.set(key, value)Set a value (JSON-serialized).Promise<void>
instance.state.delete(key)Remove a key.Promise<void>
instance.state.keys()List all keys in this namespace.Promise<string[]>
instance.state.clear()Remove all keys in this namespace.Promise<void>

Examples

// Boss health
await dungeon.state.set('boss-health', 5000);

dungeon.on('attack-boss', async (session, payload) => {
  const { damage } = payload as { damage: number };
  const health = (await dungeon.state.get('boss-health') as number) ?? 0;
  const newHealth = Math.max(0, health - damage);
  await dungeon.state.set('boss-health', newHealth);
  dungeon.broadcast('boss-health-updated', { health: newHealth });

  if (newHealth === 0) {
    dungeon.broadcast('boss-defeated', { killer: session.userId });
  }
});

// Match timer
match.onJoin(async () => {
  if (match.sessions.length === 2) {
    await match.state.set('started-at', Date.now());
    match.broadcast('match-started', { timeLimitMs: 5 * 60 * 1000 });
  }
});

// Kill counter
dungeon.on('monster-killed', async (session) => {
  const kills = ((await dungeon.state.get('kills') as number) ?? 0) + 1;
  await dungeon.state.set('kills', kills);
  dungeon.broadcast('kill-count', { kills });
});

Cleanup

domain.destroyInstance(id) automatically callsinstance.state.clear(). You can also clear manually.
// Automatic
await world.destroyInstance('dungeon-1');

// Manual reset
await dungeon.state.clear();

// Delete individual key
await dungeon.state.delete('boss-health');

Auto-destroy instances

Temporary instances (matches, dungeon runs) can clean themselves up once everyone leaves, while permanent instances never do:
world.createInstance('city');                              // permanent
world.createInstance('match-42', { autoDestroy: 60_000 }); // gone 60s after emptying
autoDestroy: true uses a 30-second grace period. The timer arms when the last member leaves (a never-visited instance is not reaped), cancels if anyone joins during the grace period, and checks cluster-wide emptiness through presence before destroying. instance.temporary tells the two kinds apart.

Use cases

  • Match timers and countdowns
  • Boss / objective health and progress
  • Runtime counters (kills, score)
  • Active objectives or flags
  • Game phase / round state
  • Per-instance configuration
Instance state is not persistent. With Redis, it survives node restarts but is cleared when the instance is destroyed. Use your own database for data that must outlast instances.