04 Synchronizables - Alteruna/au-multiplayer-guide GitHub Wiki

What is a Synchronizable?

Synchronizables are the core concept of exchanging application state between users in applications built with Alteruna Multiplayer. Synchronizables are Unity components that are attached to GameObjects and are collected and synchronized by the Alteruna Multiplayer package. For example, the TransformSynchronizable ensures that the GameObject’s transform component is synchronized between the users in a room and the SpawnerSynchronizable can instantiate objects on all clients in the Room simultaneously.

What is a UniqueID?

Synchronizables are automatically assigned a unique global identifier (UniqueID) when they are first created. This ensures that all application instances can always identify the correct object to synchronize and prevent collisions. You can inspect the assigned UniqueID in the inspector window for all objects that have Synchronizable components attached to them (Figure 4.1).

image
Figure 4.1

How do I build my own Synchronizable?

Alteruna Multiplayer supports synchronization of virtually any data type or user-defined class as long as they can be serialized as a stream of bytes. Alteruna Multiplayer provides a framework for reading and writing the most common primitive data types in C#. Sending data in a series of bytes, opens up for more complex data types to be synchronized as well.

Example of a Synchronizable

Here is a detailed example of a Synchronizable.

public class ExampleSynchronizable : Synchronizable
{
    // Data to be synchronized with other players in our playroom.
    public float SynchronizedFloat = 3.0f;

    // Used to store the previous version of our data so that we know when it has changed.
    private float _oldSynchronizedFloat;

    public override void DisassembleData(Reader reader, byte LOD)
    {
        // Set our data to the updated value we have recieved from another player.
        SynchronizedFloat = reader.ReadFloat();

        // Save the new data as our old data, otherwise we will immediatly think it changed again.
        _oldSynchronizedFloat = SynchronizedFloat;
    }

    public override void AssembleData(Writer writer, byte LOD)
    {
        // Write our data so that it can be sent to the other players in our playroom.
        writer.Write(SynchronizedFloat);
    }

    private void Update()
    {
        // If the value of our float has changed, sync it with the other players in our playroom.
        if (SynchronizedFloat != _oldSynchronizedFloat)
        {
            // Store the updated value
            _oldSynchronizedFloat = SynchronizedFloat;

            // Tell Alteruna Multiplayer that we want to commit our data.
            Commit();
        }

        // Update the Synchronizable
        base.SyncUpdate();
    }
}

Code snippet.1 shows an example of a custom Synchronizable that is included in the Unity package.

Example walkthrough

The ExampleSynchronizable has a floating point variable that it wishes to synchronize between users.

The first step is to inherit from Alteruna.Synchronizable. The Synchronizable base class enables developers to rapidly implement custom synchronization of any data type.

In the code example, the Update() method is used to regularly check for changes of the component’s state. If a change is detected, the Commit() method should be invoked to inform the Alteruna Multiplayer framework to synchronize the data over the network to other users. In the example (Code snippet 1), this occurs when the floating point variable’s value has changed.

Requesting a component to be synchronized using the Commit() method, will notify the framework that the component's data should be sent, in its new state, to the other users.

The Alteruna Multiplayer framework updates all of its synchronizable components in regular intervals. If a component has invoked the Commit() method the framework will request the component to assemble its data by invoking the overridden AssembleData() method. In AssembleData() the component must write all parts of its state to the supplied Writer instance.

In the same fashion, the framework will invoke the overridden DisassembleData() when it receives an updated state for a given component on the network. Differently from AssembleData(), the framework will supply a Reader instance that may be used to read all parts of the component’s state.

NOTE: Both the Reader and the Writer class have methods for reading/writing most common data types from the network stream.

Modifying synchronized attributes

Synchronizing components over the network may result in conflicts when multiple users attempt to either modify and/or access the data belonging to a single object. Alteruna Multiplayer provides an API for synchronizing access and acquiring exclusive ownership to synchronizables. Alteruna Multiplayer ensures that only one user can have ownership at a time, and an ownership can be acquired for any period of time. The concept of acquired ownership is there to support developers with atomic access but does not in itself protect access to any attributes of the synchronizable. The developer needs to acquire, modify and later release the ownership of a synchronizable and ensure that modifications are encapsulated within a check for ownership.

image
Code snippet. 2 shows a basic example of requesting ownership at start, update the position of the object and immediately release the ownership.

Taking ownership can be performed in two ways, either trying to take ownership at once, or waiting for an infinite amount of time until other requesters have given up their ownership and taking it as soon as it becomes available.

Please note that using the ownership API is not mandatory, it is solely up to the application developer to decide if and when it is needed in order to protect access to the synchronizable’s attributes.

⚠️ **GitHub.com Fallback** ⚠️