You must make sure your application is correctly synchronized on all cluster nodes. This means you must make sure its inputs/events (causes) or the results (consequences) of your scripts are correctly synchronized.
Typically this means using MiddleVR's input devices and time API which is automatically synchronized, and using MiddleVR's synchronization API to manually synchronize other data.
Let's take some examples in the order of difficulty:
- My application is using keyboard, joystick, or VR controllers:
Those inputs are automatically synchronized if you use MiddleVR's input devices API. If your interactions don't have any random part, they should be correctly synchronized.
If your interactions use time, be sure to use MiddleVR's time API.
- At the end of a frame, the only modifications are the position/orientation of GameObjects:
- In my application, is is important that certain MonoBehaviours variables are synchronized:
- My user interacts with a GUI using a mouse:
See Synchronize a GUI.
- I want to synchronize arbitrary data from the cluster server to the cluster nodes and back:
We have already talked about the different levels of synchronization, so will focus here on what the Unity developer should know to create a properly synchronized cluster application.
What issues are introduced by using a cluster ?#
In a perfect world#
A cluster system should in the end display the same image as a non-cluster system. The main difficulty is to get everything correctly synchronized.
In a perfect world, Unity would synchronize each and every state of all scripts and scene graph. Unfortunately this is not the case and probably never will be: this would potentially mean thousands or millions of network synchronizations per frame!
So we must try to minimize both the amount of network synchronization and also minimize the work for developers to adapt their applications.
The root issue is that MiddleVR will run a Unity instance (player) per computer that make up the graphics system.
Also note that on each cluster node, all your scripts will still be running.
By default those player don't have any relation to each other, so they have no reason to be synchronized. More specifically they will probably have different inputs, resulting in different results:
user input: keyboard, mouse, joystick, trackers, GUI inputs,
time input: the elapsed time and time to render each frame (delta time) might be different on each cluster node.
even with the same inputs the result can be different: the application is non-deterministic. For example Unity's physics and particles are non-deterministic. If you run the same application twice you will not get the same result.
Cause or consequence ?#
Let's take the example where pressing "Space" on your keyboard opens a door.
There are two ways to think about cluster synchronization:
sharing the input (causes): this should give the same result in your applicaiton is deterministic. In our example, we have to synchronize the key press on all cluster nodes so the script will correctly open the door.
sharing the result (consequences): sometimes it is easier to just synchronize the result of a frame from the server. In our door example, it means that we would synchronize the door rotation.
The theory is the following: if your application is deterministic ("whose resulting behavior is entirely determined by its initial state and inputs, and which is not random"), its state at the end of each frame will be the same on all machines.
This means that:
the initial state must be the same: this is automatic since we run the same application on all cluster nodes (unless your application is doing random things at startup)
at each frame inputs must be the same: you need to make sure that your application uses inputs that are the same on cluster nodes by using MiddleVR's synchronized inputs or synchronization API.
You can also decide that you don't want to synchronize the causes but only want to share the result of the frame from the server. This is fine as long as you don't forget any parameter.
For example if your application is only moving game objects around,
you can safely use MiddleVR's
MVRClusterObject script that will
automatically synchronize its position and orientation.
But if your application can also change the color of a material you also need to make sure the color change is correctly synchronized with a custom synchronization.
Note: MiddleVR automatically shares the position/rotation of physics objects because the physics engine is not always deterministic if the MVRManager "SimpleCluster" parameter is enabled.
How to choose ?#
Typically you choose to share causes or consequences based on two parameters:
ease of development: is it easier to share one or the other ?
resources: it might not be practical/fast to share the position of a million game objects if they are entirely determined by the inputs.
You can also safely mix the two methods:
share causes for deterministic aspects of your application,
share consequences for non-deterministic aspects.
How to synchronize ?#
If the MVRManager parameter Simple Cluster is enabled, the following will happen:
All physics objects will be automatically synchronized with the
Character Controllerswill be automatically synchronized with the
Animatorswill have their culling mode changed to
Always Animateto make sure that each animation runs even if it is not visible on a given cluster node.
Timelineswill have their time synchronized.
There are three ways to synchronize data:
MVRClusterObjectwhich will automatically share a GameObject's position and rotation and MonoBehaviour's variables.
MVR.Cluster.BroadcastMessageAPI to manually synchronize any type of data.
MiddleVR will automatically synchronize the state of all its devices.
So instead of:
Input.GetKeys(...), you need to use:
and other methods to manage the different devices.
See section Input devices.
Note: Make sure that your scripts always execute after the MVRManagerScript, which is the case by default unless you modify the Script Execution Order. See section When to synchronize ?
Time & Delta time#
Instead of using
Time.time you should use
gives the elapsed time since the server started and is correctly
synchronized on the cluster.
Instead of using
Time.deltaTime you should use
The time and delta time are only updated once per frame,
MVRManagerScript is updated.
See section When to synchronize ?
Time.fixedDeltaTime are automatically synchronized.
GameObject position and rotation#
All nodes from the MiddleVR's hierarchy nodes are automatically synchronized.
MVRClusterObject to a GameObject will automatically
synchronize its position, rotation and scale.
MiddleVR ships a script that should help videos be correcly synchronized in
Place this script on a GameObject with a Video Player. If PlayOnAwake is enabled, it will prepare and start the video on all cluster nodes. If not, call "PlayPauseVideo" on this script to play/pause the video. It will wait for all cluster nodes to finish preparing the video then start it. It preloads the video on all nodes and synchonizes the play and pause.
Note: We recommend to play the videos from a local file on each cluter node to avoid network congestion and potential resulting synchronization problems.
MVRClusterObject and enabling
Synchronize Mono Behaviours
will automatically synchronize MonoBehaviours variables.
If the MVRManager
SimpleCluster is enabled,
MiddleVR will automatically add the
MVRClusterObject script to physics objects.
This will automatically synchronize their position/orientation when
See section When to synchronize ?
Note that this will only work if all your objects are already created and not dynamically added later on in the application.
If your objects are created after the first MVRManagerScript update,
you will need to manually add the
Make sure that the Culling Mode of your Animators are set to "Always Animate" and Update Mode is not "Animate Physics", otherwise cluster nodes might not update the animations at the same time.
This is automatically done since MiddleVR 2.1.1 for animators that are enabled on startup. For animations added dynamically, make sure to do it yourself.
At startup MiddleVR automatically gets the seed of Unity for pseudo-random values and synchronizes it across the cluster.
You can safely use the Random functions of Unity after the first
MVRManagerScript. The Random functions will
output the same sequences on all cluster nodes.
If a random value is not automatically synchronized, you can always
Cluster.BroadcastMessage to send the
random value from the server to the clients.
Random objects positions#
If as a result of your scripts, objects have random
positions/orientations, you can apply to them the
which will synchronize their position/orientation at each end of frame.
If the MVRManager parameter
is enabled, the random seed of the particles will be automatically synchronized.
Make sure to disable "Auto Random Seed".
Simple particles should then correctly be synchronized.
More complex particles cannot at this time be correctly synchronized.
Disabling the following parameters helps reduce chances of non-deterministic particles:
Limit Velocity over Lifetime
Rotation By Speed
CinemachineBrain will automatically be removed from all MiddleVR cameras.
It is perfectly possible to use Unity's GUI on the cluster with a few limitations:
- A "Screen Space - Overlay" GUI is not compatible with the Window Mode Compositor.
This means that:
either you interact with a GUI using a VR controler by using either "Screen Space - Camera" or "World Space". This should automatically be synchronized because the Wand position/orientation and buttons are automatically synchronized.
or interact with your mouse and use the "Screen Space - Overlay" GUI only on the server in conjunction with the Server Unity Window option. You then must manually synchronize the GUI events with
I want to synchronize arbitrary data from the cluster server to the cluster nodes and back:
It is possible that some parts of the app won't be correclty synchronized by MiddleVR. This includes:
- Tree foliage,
- Random events,
- Some post-processing effects, see Unity URP & HDRP,
- Videos: See Videos
Contact support for more information.
When to synchronize ?#
MVRClusterObject script can be found in
When added to a GameObject, it can synchronize automatically:
Transform: local position, orientation and scale
Mono Behaviours parameters: Experimental Automatically synchronizes all serializable fields of a MonoBehaviour:
- All public fields
- All private fields tagged [Serializable]
Synchronize Children is enabled, will also synchronize all the children of
the Game Object.
Note: The synchronization will only be effective after the MVRManagerPostFrameScript update. See section When to synchronize ?
Supported types and limits#
Any basic type
Any blittable/unmanaged struct
Structs and Classes marked with the [Serializable] attributes:
- Includes any public field of a type that is serializable,
- Includes non-public fields if they have the [UnityEngine.SerializeField] attribute,
- Public fields with the [NonSerialized] attribute are skipped.
Notably NOT Supported:
- Arrays (please use List
Additionally, the top-level type being serialized can be a derived
Limitations of this implementation:
It does not support arbitrary object graphs. Reference cyles will be ignored.
It uses dynamic code generation thus it is incompatible with any AOT (Ahead Of Time) compilation mode like IL2CPP.
- Prefer avoiding polymorphic references in serialized objects as it is considerably slower to serialize/deserialize.
Try to synchronize the minimum number of objects with MVRClusterObject. The more objects you synchronize manually, or the more number of physic objects that you use (which will be automatically synchronized), the slower the network will be.
It's better to display a small viewport on the master and to disable its VSync if it's not part of the actual display system so it runs faster and doesn't slow down the rest of the cluster.
Also if the master has a different refresh rate than the rest of the cluster, use the Server Unity Window option.
Disable Unity's output logs (in the Player Settings, Use Player Log) otherwise Unity will write its logs through the network and slow the application down.
Use a low LogLevel in MiddleVR Config.
CPU Intensive tasks#
It's better to put all the CPU intensive tasks before the
MVRManagerScript::Update executes: we use a thread at the end of a frame
which will handle the frame synchronization, but Unity can start to work
before the frame sync is actually over, so we get some parallelization.
Just be aware that MiddleVR will update the devices state a bit later, when the MVRManagerScript executes.
MiddleVR supports the call to
SceneManager.LoadScene to change the current scene.
The only thing you have to do is make sure that the
MVRManager is in the first scene
you are running but not the other ones because the original
MVRManager will not be
destroyed when changing scene.
See sample script