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.
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(...).API
All three stores share the same
ScopedState API:Atomic operations
get() followed by set() loses updates under concurrent handlers and across nodes. The atomic operations run as single Lua scripts on Redis - truly atomic across the cluster, never a get-then-set (memory implementations are equivalent within one process):Successful mutations sync to clients like
set(); failed conditional mutations emit no state event. For compareAndSet, expected: undefined matches only a missing key - a stored null matches only expected: null.Batched writes (patch)
A round reset or a freshly generated floor can touch hundreds of keys.
patch() writes them all as one atomic change: one revision, one sync message, one Redis call. Entries whose value is undefined are deleted. It works on every scope, including userState(id):Clients receive one change carrying
patch (keys set, with their values) and deleted (keys removed) instead of key/value. The replica applies it in one step; client.stateOf() is always current. To wipe a whole scope, clear() is already a single call.Visibility (visibleTo)
By default every public key reaches every member - including a cheater reading the network.
visibleTo restricts a value to a list of user ids. Everyone else sees the key as deleted, in live changes, join snapshots and recovery snapshots alike, so their client never holds the value:Re-
set a key to change who sees it - players who lose sight receive a deletion. Revisions stay gap-free for everyone, so filtering never triggers snapshot recovery. It works across nodes: visibility is stored with the value.⚠
Key names and the fact that a key changed are not hidden - put secrets in values, not names. Restricted keys support
get, set, patch and delete; atomic operations treat them as non-matching. Server-side get() and entries() always return the real values.For events rather than state, send to a subset of members with
instance.broadcastWhere().Revisions and recovery
Every synced state change carries a monotonic revision - one sequence per instance for public state, one per user per instance for private state (Redis-backed counters stay monotonic across nodes). Client replicas apply changes strictly in revision order: duplicates and stale changes are ignored, and if a change is ever lost the client detects the gap and transparently fetches a fresh snapshot over the same connection - no reconnect.
client.stateOf() / client.userStateOf() are always the authoritative view; during a recovery, individual onStateChange callbacks for the skipped changes may not fire, so re-read the replica instead of accumulating changes yourself.Examples
Cleanup
domain.destroyInstance(id) automatically callsinstance.state.clear(). You can also clear manually.Auto-destroy instances
Temporary instances (matches, dungeon runs) can clean themselves up once everyone leaves, while permanent instances never do:
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.