This document is about: FUSION 2
SWITCH TO

Data Sync Helpers


Available in the Industries Circle
Circle
Fusion XR prototyping module

Data Sync Helpers Principle

The Data Sync Helpers addon provides some helpers classes that simplify synchronizing data for use cases with special needs.

Typically, the target use cases are, for instance:

  • Sending very regularly small chunk of data, whose full list is also required for late joiners: drawing points with a 3D pen and allowing late joiners to have the full drawing too
  • Sending very regularly small chunk of data with no need to keep the full list but that can be sent several time before the next data reception: for instance, it can be used for a wand drawing a temporary line in air, or for a gun, to be sure that all shots are fired
  • Synchronizing a large one-shot data: sending a photo shot took in an application to other users

RingBufferLossLessSyncBehaviour class

Overview

As seen in Projectile essential - Projectile data buffer, a ring buffer is a good approach to store a succession of small data, even if several ones could be sent before the first one is received: the ring buffer logic will keep several of them, without losing data.

However, for information that needs to be accessible for late joiners too (drawing points, etc. ), a ring buffer alone will progressively lose the data that passed through it.

The RingBufferLossLessSyncBehaviour<TEntry> class solves this problem:

  • In addition to a classical ring buffer, the underlying storage keeps track of the full count of data that were shared,
  • When a late joiner arrives, they can thus know they are missing data,
  • If that occurs, this class will send a message through a RPC to other users (the class will handle finding which user can send the data, even if the original writer has since left room),
  • Other users having the missing data will share it through the Runner.SendReliableDataToPlayer method.

Under the hood, this class relies on a NetworkArray<byte> to store both the ring buffer data (start index, next write the need), the total amount of data count, and the actual data:

fusion industries addon data sync helpers networkarray

The missing data can occurs either when the ring buffer is filled too fast, or for late joiners, and that's when messages requesting the missing data ranges are sent:

fusion industries addon data sync helpers networkarray 2

RingBufferLosslessSyncBehaviour<TEntry> is a generic NetworkBehaviour subclass that needs to be subclassed to specify TEntry. TEntry should be a struct representing the data to share, implementing the RingBuffer.IRingBufferEntry interface to define how it converts to a byte array (AsByteArray) and how to load its content from a byte array (FillFromBytes). To help implement those methods, the SerializationTools contains static methods to help.

usage

Add data

As the state authority, use AddEntry(TEntry) to add an object in the synched data.

Overriding callbacks methods

Some callbacks can be overridden to react upon live data reception, or complete recovery of missed data, for instance.

C#

    // newEntries will contain the new TEntry structs detected in the last data update.The received data at start might not be long enough to form an entry (for late joiners first ring reception, ...), those additional bytes are stored in newPaddingStartBytes
    public virtual void OnNewEntries(byte[] newPaddingStartBytes, TEntry[] newEntries) { }

    // OnDataLoss will warn of data loss: no need to handle the loss, the request are sent automatically
    public virtual void OnDataloss(RingBuffer.LossRange lossRange) { }

    // Called when one loss is restored
    protected virtual void OnLossRestored(LossRequest request, byte[] receivedData) { }

    // When all loss in the total data remains. SplitCompleteData() can be called to retrieve all the TEntry 
    protected virtual void OnNoLossRemaining() { }

    // When a loss is permanent (no one having the data is still in the room 
    public virtual void OnNoAnswerForALossRequest(LossRequest request) { }

Implementing IRingBufferEntry

The SerializationTools class simplifies implementing the IRingBufferEntry expected methods.

To represent a struct as a byte array (AsByteArray), simply pass all its basic supported types (float, Vector3, byte, Quaternion, int) to SerializationTools.AsByteArray, and it will return a byte array. For instance, for the following struct:

C#

    [System.Serializable]
    public struct DrawPoint : RingBuffer.IRingBufferEntry
    {
        public Vector3 localPosition;
        public float drawPressure; 
        /* ... */
    }

This implementation AsByteArray will be optimal:

C#

    public byte[] AsByteArray => SerializationTools.AsByteArray(localPosition, drawPressure);

To load a byte array into the struct, the SerializationTools.Unserialize method handles the unserialization to basic supported types (float, Vector3, byte, Quaternion, int), while keeping track of the position in the array.

So for the same sample struct, this implementation of FillFromBytes will load the data serialized with the AsByteArray previously defined:

C#

    public void FillFromBytes(byte[] entryBytes)
    {
        int unserializePosition = 0;
        SerializationTools.Unserialize(entryBytes, ref unserializePosition, out localPosition);
        SerializationTools.Unserialize(entryBytes, ref unserializePosition, out drawPressure);
    }

RingBufferSyncBehaviour class

RingBufferSyncBehaviour is the parent class of RingBufferLossLessSyncBehaviour, which handles the ring buffer aspect, without the message to recover data not stored in the underlying ring buffer.

It can be used as RingBufferLossLessSyncBehaviour, without anything related to loss recovery (the losses are still detected, but not recoverable here).

This class can be used for classical ring buffer needs (gun projectiles, etc.).

The ring buffer index tracking itself is done in a struct named Ringbuffer, which updates start index and next write index, and can add data/update the actual data storage if provided.

StreamingSyncBehaviour class

Overview

The StreamSynchedBehaviour allows sending arbitrary chunks of data to other players. This helper uses the Fusion SendReliableDataToPlayer API. The first part of the 4-part key used to identify a message with this API is used to store the Object id, so that several objects in the room could send and receive data, without fear of sending them to the wrong object.

The StreamSynchedBehaviour main specific point is to handle the late joiners case: how to send data to users that missed the initial transmission, especially if, in shared mode, the state authority has left the room.

This is done by an RPC message sent by late joiners, requesting the reception of the full cache, from either the state authority or another player if they left the room.

Usage

Sending data

Sending data is done by calling Send(byte[] data) with the data to send. Data are received as stored as chunks, and the subclasses need to merge them if needed.

Overriding callbacks methods

Some callbacks can be overridden to react upon live data reception, or recovery of missed data, for instance.

C#

    // Provides the 0-1 download progress of a currently received chunk of data
    protected virtual void OnDataProgress(float progress) { }
    // Called on complete reception of a chunk of data
    protected virtual void OnNewBytes(byte[] newData) { }
    // Called to know the client is a late joiner, waiting for data
    protected virtual void OnLateJoinersWaitingForData() { }
    // Called if missing data for a late joiner are unavailable anywhere
    protected virtual void OnMissingData() { }

Demo

The helpers have some sample classes showing how to subclass them:

  • TestStreamSynchedBehaviour for StreamingSyncBehaviour
  • TestLosslessRingBufferSync for RingBufferLossLessSyncBehaviour
  • TestRingBufferSync for RingBufferSyncBehaviour

The DemoStreaminghelpers scene contains the TestStreamSynchedBehaviour implementation of StreamingSyncBehaviour. The scene contains 2 of those components to show that the data is sent to the proper StreamSynchedBehaviour object. By pressing the SendRandomData button, a chunk of data is sent to connected users, and will appear in the All data attributes. It will also be available for late joiners.

The DemoRingBuffer contains both a TestLosslessRingBufferSync and a TestRingBufferSync to show both lossless and regular ring buffers. On the ring objects, by pressing either AddSingleEntry (which will add to the data the entry described in the inspector Single entry to add field), AddRandomEntry or AddSeveralRandomEntries, entries will be added to the entries field in the inspector. They will appear on both connected clients, and on late joiner clients. As the log level includes Data loss recovery, it will be possible to see messages about data loss recovery in the console. Late joiners will systematically trigger such messages, and AddSeveralRandomEntries calls fill the ring buffer too fast on purpose, so such messages will appear. In the end, all users will have the same entries.

Download

This addon latest version is included into the addon project

Supported topologies

  • shared mode

Please note that StreamingSyncBehaviour can be used in host topology too.

Changelog

  • Version 2.0.1:
  • Add Vector2 and uint support in serialization tools
  • add flexibility to ring buffer entry size determination
  • Version 2.0.0: First release
Back to top