Decision note: the shared-memory and sync model
Every Core type that replicates state, such as TsvrcMemory
and Process, uses the same two pieces: the VRChat manual sync mode
(BehaviourSyncMode.Manual) and an explicit RequestSerialization() call after each change. This
note explains why.
Manual sync, sent on demand
VRChat describes the two sync
modes this way: manual
sync "requires calling RequestSerialization() to send data updates" and suits values that don't
change often, while continuous sync "updates automatically" and suits "frequently updated values."
State in the Core types changes in discrete steps, such as a process starting, a timer pausing or a
player joining a tracked set, not continuously like a transform position. Each change has a clear
moment to send it, so there's no reason to pay for automatic periodic replication.
The resync heartbeat
Process adds a second mechanism: while a process runs, its owner sends its full synced state
again every 5 seconds, whether or not anything changed. The lifecycle broadcasts of the Core process
types are discrete [NetworkCallable] events with no delivery confirmation, and a client that misses
one has no other way to catch up. The VRChat docs document one case where events are lost: when
players in one instance run different world versions, the server "may silently drop events and not
deliver
them".
The heartbeat doesn't depend on knowing which case happened: a client that missed a broadcast gets
the current synced state within a few seconds, with no acknowledgement or retry logic. See
Process for the loop that sends it.
Working around what can't be synced directly
The synced tier of TsvrcMemory exists because DataDictionary, the natural shape for a key-value
store, isn't a syncable type. The types synced variables
support are primitives, the small
Unity value types (Vector2, Vector3, Vector4, Quaternion, Color), strings and VRCUrl.
TsvrcMemory serializes the dictionary to a string with TsJson before
each synced write and parses it back in OnDeserialization, and that string field follows the same
manual sync pattern as any other synced value. See TsvrcMemory
for what the synced tier guarantees about ordering and delivery.
Ownership
Core uses the VRChat ownership model as it is: only "the owner of a networked object can modify its synchronized Udon variables," so only the owner sends state to other players. There's no second ownership concept on top of it.
The types differ in how a write from another player reaches the owner:
TsvrcMemorytakes ownership for the local player before it writes a synced key.Processnever takes ownership by itself. A lifecycle call from another player is sent to the owner over the network, and the owner applies it. A subclass that should run on the player who starts it takes ownership first.