StateTree synchronization relies on @Sync + SyncPolicy, with SyncEngine generating snapshots/diffs.
- Sync rules can be declared on types (
@Sync), decoupled from handler logic - Support per-player filtering to avoid unnecessary data leakage or bandwidth waste
- Use snapshot/diff merge mode to reduce sync costs
.serverOnly: Not synced to client.broadcast: All clients receive same data.perPlayerSlice(): Dictionary-specific convenience method, automatically slices[PlayerID: Element]to sync only that player's slice (high frequency use, no filter needed).perPlayer((Value, PlayerID) -> Value?): Requires manual filter function, filter by player (applicable to any type, low frequency use, for scenarios requiring custom filter logic).masked((Value) -> Value): Same-type masking (all players see same masked value).custom((PlayerID, Value) -> Value?): Fully custom
- snapshot: Complete state (including per-player filtering)
- diff: Only send changes (path-based patches)
SyncEngine maintains:
- broadcast cache: Shared by all players
- per-player cache: Independent for each player
StateUpdate.firstSync is sent once after player cache is first established,
avoiding race conditions between join snapshot and first diff.
@Sync marks dirty on write, used to reduce diff costs.
TransportAdapter can switch dirty tracking at runtime:
- Enabled: Only serialize dirty fields
- Disabled: Full snapshot on every sync
In handlers, you can request a deterministic sync through ctx.requestSyncNow() (or ctx.requestSyncBroadcastOnly()).