Skip to content

Networking

CyanLaser edited this page Jan 29, 2024 · 9 revisions

There are multiple concepts used for networking in CyanTrigger and Udon itself. While this area will cover the basics, it is best to also check VRChat’s official documentation on this subject. Link to VRChat’s networking Docs.

Broadcast types

In CyanTrigger, every Event has a broadcast type. The default broadcast type is Local, which is not networked to other clients. There are two other broadcast types which are networked:

  • Send to All This broadcast type will tell everyone in the instance to execute the actions of this event.
  • Send to Owner This broadcast type will tell only the Owner of the object to execute the actions of this event. No one else will execute the actions. Using these networked broadcast types allows for users to coordinate events with other users in the room.

Ownership

Every GameObject has an owner. By default, the owner of an object will be the master of the instance. For pickups, the owner will be the last person to hold the item. The only other way to change the owner of an object, is to set it in Udon or CyanTrigger using the Networking.SetOwner action. Ownership of a GameObject is only important for networking related items, such as Position sync and Synced Variables. Only the Owner of an object can update the synced position of that object. The owner is also the only player that can modify synced variables and have the value sent to all other players.

Sync Settings

Every UdonBehaviour has a Sync Method. The Sync Method affects how the object is treated for networking purposes. Sync Method mainly controls when variables sync data with other clients. If Sync Method None is chosen, then all networking is disabled for the UdonBehaviour. Note that disabling the GameObject or the UdonBehaviour will prevent any synced variables from syncing and it is not recommended. By default CyanTrigger will try to auto set the sync mode, but you can manually select a sync mode if needed.

SyncSettingsManual

There are 4 different types of Sync Methods that can be used on a CyanTrigger:

SyncOptions

Continuous

Continuous Sync Method will periodically multiple times per second serialize the synced variables on the UdonBehaviour, even if the values have not changed. You cannot control when this happens. Continuous Sync should only be used when you have fast changing values that need to be updated often. If the UdonBehaviour is on a GameObject that also has the VRCObjectSync component, then it is required to use Continuous Sync. The majority of time it is recommended to use Manual Sync instead of Continuous. Continuous Sync Method cannot be used on GameObjects that contain other UdonBehaviours with Manual Sync.

Manual

Manual Sync Method will not do anything until the user calls UdonBehaviour.RequestSerialization. Only then will it serialize variable data and sync the values. Manual Sync Method cannot be used on GameObjects that contain other UdonBehaviours with Continuous Sync or if the GameObject has the VRCObjectSync Component. See VRChat’s documentation for this networking mode.

Manual With Auto Request

Manual with auto request is a special version of Manual in that CyanTrigger will automatically determine if RequestSerialization needs to be called. If an event ever sets a synced variable, that event will also call RequestSerialization. This simplifies things for users as they no longer need to call one action. If you want absolute control over when items are synced, it is recommended to use “Manual” instead to ensure timing. Note that if a CyanTrigger tries to modify a variable on another CyanTrigger using UdonBehaviour.SetProgramVariable, you must call UdonBehaviour.RequestSerialization on that CyanTrigger to properly sync the value.

None

None Sync Method will ensure that the UdonBehaviour does not sync anything. Synced variables and Networked events will be ignored, and everything will be local to the user only. If this is the only UdonBehaviour on the GameObject, then this object will not have ownership either. Sync Method None can be used on GameObjects with other UdonBehaviours with different Sync Methods.

Auto Set Sync Mode

CyanTrigger by default will try to auto determine the Sync Method needed. This will disable the option of selecting the Sync Method and display what it thinks it should be. If the Sync Method selected is not desired, you can uncheck Auto Set Sync Mode and select the one needed. Auto Set Sync Mode will look through all actions and inputs used on the CyanTrigger to help determine the Sync Method. Having synced variables and networked events will ensure that this object is synced. In the case where there are no synced variables or networked events, but ownership is needed, then checking the owner of the GameObject using the “This GameObject” variable will ensure that the CyanTrigger has networking enabled.

SyncSettingsNone

Synced Variables

CyanTrigger can define synced Global variables. These variables will be saved (serialized) and set to all players in the instance. If you are the owner of an object, before the synced variables are serialized, the PreSerialization event will be called. When serialization has finished, PostSerialization will be called with the results on if everything was successful or if anything failed. On all remote clients, OnDeserialization will be called when new variable data is received. You can use this event to check for variable updates. Note that OnVariableChanged will be called automatically even when a synced variable is changed over the network.

General Synced Variable Setup

When working with Synced Variables, there are two areas to consider:

  • Setting the value of a synced variable (Only one player executes)
  • Reacting to a synced variable's value changing (All players will execute)

See below for example. You can also view the Networking Example scene included in the CyanTrigger package for more and different examples of synced variable usage.

Setting Synced Variables

First, make sure you have a variable in the variable list at the top of the CyanTrigger and have it set to "Synced". Note that not every variable type can be synced. Be sure to set the initial value.

Ensure Ownership

When setting the value of a synced variable, you must be the owner of the GameObject. Use the Networking.SetOwner action to set the owner of the GameObject to the local player. If you are not the owner, setting the value of the variable will work for that frame for the local player only, but the value will be reset back to the owner's value a few frames later.

Set variable Value

When the local player is the owner of the GameObject, then synced variables can be set in through any action that has an output variable. A small set of examples could be:

  • bool.set
  • bool.UnaryNegation
  • int.set
  • int.Addition
  • float.set
  • VRCPlayerApi.get DisplayName

Setting a synced variable depends on your use case and variable type. Every type has a Set action which allows you to set a variable to a specific value. Some cases may depend on math operations or getting a value from another thing first.

Request Serialization

After the variable's value has been set, Udon needs to know to sync that variable to all other clients. The CyanTrigger Sync method will determine if you need to do anything here. If the sync mode is continuous, then this will be handled for you. If the sync mode is Manual, you will need to call UdonBehaviour.RequestSerialization. If the sync mode is Manual with Auto Request, CyanTrigger will generate this for you. Note, it will only generate it when the variable is set in its own actions. If a CyanTrigger tries to modify a variable on another CyanTrigger using UdonBehaviour.SetProgramVariable, you must call UdonBehaviour.RequestSerialization on that CyanTrigger to properly sync the value.

Notes

It is important that only one player executes the actions to set the value. Usually this means having the Event in CyanTrigger set to Local. There are methods to gate who performs the actions, but this is more advanced and you will need to understand the basics to ensure these are setup correctly. Send to Owner is one way of gating. It is recommended to never use Send To All when attempting to set a synced variable, especially never use Networking.SetOwner in a Send to All Event. In the case where an Event is set to local, but due to other reasons the Event is still synced, you should use add a gate to only allow one player to execute the actions. An example of this would be using a local OnTriggerEnter event to detect an ObjectSynced object. While the event may be local, everyone will still try to execute it due to everyone seeing the ObjectSynced object entering the trigger for everyone. In this case, an easy gating method would be to check if the owner of the synced object is the local player, and only let them perform setting the synced variable.

Reacting to Synced Variables

When a synced variable has changed, everyone will need to know about this and handle it appropriately, including the one who set the value. There are multiple ways to react to a synced variable:

  • OnDeserialization
  • OnVariableChanged

These methods are only for knowing when a synced variable has changed. It depends on your use case for what to do with that information. For both of these methods, treat them as synced events. If you plan on changing another synced variable from these events, you must gate it so that only the owner or one specific player executes the actions.

OnDeserialization

In Udon, the OnDeserialization event is called after new networking data on that Udon has been set. This means that variable changes can be handled here. You do not know which have changed though. One option is to just handle as if every variable could have changed, without checking which. This is redundant and does not always work in every case. Another option is to have a local variable and check the difference with a synced variable to know when the value has changed specifically. This can be automated with the OnVariableChanged event, although there are some trade offs when using that.

Note that OnDeserialization is called only by non owning clients. If you are using OnDeserialization, the player setting the values of the synced variables will still need a way to handle the values changing. You can manually call OnDeserialization using UdonBehaviour.SendCustomEvent after any synced variable has been set. Another option could be to use the OnPostSerialization event with a single action of UdonBehaviour.SendCustomEvent to OnDeserialization, but this will not happen immediately as it waits for all synced variable data to be sent rather than right after any value has been changed.

OnVariableChanged

The OnVariableChanged event is an event that will be called whenever a variable's value has changed. This is not limited to synced variables as it works for all variable types.

Note that when performing math with multiple actions, it is best to use multiple variables for the operations and only set the variable with an OnVariableChanged event in the final action. If you use this variable in the other operations, it will still call the event for each time, potentially giving weird results and sometimes errors.

Note that one difference between OnDeserialization and OnVariableChanged is that OnDeserialization guarantees that all synced variable's have had their value's updated. With OnVariableChanged, you are only guaranteed that variable's value has been updated. Other variables may change, but may not have been updated yet. Udon still needs to set the values of variables before calling OnDeserialization, meaning that synced variables can be changed in any arbitrary order. When working with multiple synced variables each with their own OnVariableChanged, be sure to only check that one variable in its own OnVariableChanged event and not other synced variables. If you need to check multiple synced variables together for certain logic, it is recommended to instead use OnDeserialization event instead.

Synced Toggle Example

This guide is also available as a video tutorial.

Synced Toggle

With synced variables, it is easy to create a synced toggled object for your world. Synced variables paired with the OnVariableChanged event lets you easily handle when a variable has been updated on any client.

SyncedToggleExample

In this example, you can see everything needed to sync a toggle for all players, including late joiners. There are three sections to this, Variable definition, Modifying the variable, Handling variable changes.

Variable Definition

In the example, there is only one variable defined for this CyanTrigger. This variable is called “value”. It is of type bool and it is set to sync. An important part of this is that it is set to the default state of the object. In the scene, the toggled object is already active, thus the variable should also start active. If not, the first interaction modifying it will not change for players properly.

Modifying the Variable

In this example, I am using the Interact event to modify the variable state. This can be any event, but Interact is easy to demonstrate. When modifying a variable, you must be the owner of the object inorder to change the value. With this in mind, the first action in the Event is Networking.SetOwner. This will make the local player the owner of the object, allowing us to change the value of the variable. The next action then modifies the variable’s value. For this example, I am flipping the bool value using bool.UnaryNegation and storing it back in the same variable.

Handling Variable Changes

OnVariableChanged is the event you should use to handle values changing for variables. This will check when a variable’s value has changed from a local action, or from a synced variable changing. When this Event executes, you have the updated value in the variable and can use it for additional actions. The OnVariableChanged event also provides a variable containing the old value. In this example, I am taking another object, called “Thing”, and setting it’s active state to the value of the variable.

Event Replay

ReplayOptions

Event Replay is a new method of syncing events for late joiners. On any event with the “Send to All” broadcast, there is an option for what happens with this event when a player joins later. While similar, Event Replay is not the same as SDK2 Buffering. While provided for you to use, Event Replay is less efficient compared to Synced Variables. It is always better to use Synced Variables instead.

Note: Use caution when using Event Replay with events dependent on variables or other data not set in the Event itself. There may be timing issues when that data is set. Always test your worlds with multiple players and with late joiners!

Video Explanation

A video explanation of Event Replay with examples is available on YouTube.

ReplayVideoTutorial

Types of Replay

None

None is the same as no Replay. Nothing will be handled for late joiners and only players in the instance will get the event through the “Send to All” broadcast.

Replay Once

Replay Once will save if the event has fired at all, and replay it one time for users who join after. This can be used for one time events, or used to set the state in a multi state system.

Replay Parity

Replay Parity will save if the event has fired at all, and replay it one time if it was fired an odd number of times, or replay it two times if it was fired an even number of times. Replay Parity is intended for events that Toggle object states.

Replay All

Replay All will save if the event has fired at all, and replay it for every time it was fired. Replay All is intended for events where counting is required.

Clear Replay Action

The Clear Replay action will reset all replay data for a given event, as if it has never fired. Using Clear Replay is necessary when you have multiple Events with Replay that modify the same objects or properties.

ClearReplayExample

In this example, there are two events set to Replay Once. Both events will modify the same GameObjects: “Door/Open” and “Door/Closed”. Due to this, we need to ensure that only one of these events will replay for late joiners to keep everything synced. This is where the Clear Replay action comes in. On calling “_Open”, we know that “_Close” should not be called, and the replay data is cleared. On calling “_Close”, we know that “_Open” should not be called, and the replay data is cleared.

Differences between SDK2 Buffering and CyanTrigger Event Replay

CyanTrigger Event Replay is not the same as SDK2 Buffering! While the UI to enable it is similar, most situations will act differently. There are two main areas where Event Replay differs:

Event Order

Event Replay does not keep the call order for events. In SDK2, order is kept when calling buffered events. With CyanTrigger, Replayed events will always play in the order they were defined in the trigger. When comparing CyanTrigger on different objects, the replay order could change every time.

Order Example in CyanTrigger

OrderExampleCT

Order Example in SDK2 VRC_Trigger

OrderExampleVRCT

In this example, the CyanTrigger and VRC_Trigger appear to do the same thing. On Interact, call a custom to disable an object, then call a custom to enable the object. In SDK2, the GameObject will always be enabled, as that was the last event to be called. In CyanTrigger with Replay, the GameObject will be enabled for those in the room, but will be disabled for late joiners. This is because late joiners will always replay the “Enable” event first, and then replay the “Disable” event second, leaving the object disabled. This is where the ClearReplay action is used to determine exactly what happens for late joiners.

Enabled

For Event Replay, the object’s enabled state matters. SDK2 doesn’t care if the object or VRC Trigger is enabled. Buffered events will always execute for late joiners, even when the object is disabled at start. For Replay to work properly both the GameObject and the UdonBehaviour must be enabled at start. If either are disabled at start, then users will not act on replay data until the object is enabled. This means that if a replay event is fired before the object is enabled, users in the instance will fire the event and also replay it when the object is enabled, over-counting it.