This document describes the Synchronization GE real-time sync protocol API

Introduction

To maintain low coupling and eg. allow the scene model to be reused without networking, the JavaScript client API is divided into classes with different responsibilities:

Connection management

Initiating a WebSocket connection to a server happens through the WebSocketClient object, which exposes the following functions:

        webSocketClient.connect(hostname, port, loginData)
        webSocketClient.disconnect()
      

The loginData is optional JSON data that specifies user name, authentication information etc.

The WebSocketClient provides signals for the connect succeeding or failing, and provides access to sending and receiving raw binary messages which is utilized by the SyncManager. After a successful connection to the server the client is assigned and sent back an integer user ID and a reply data which is also JSON, and these can be read via the WebSocketClient's "userID" and "loginReplyData" properties.

Scene model

The Scene, Entity, Component and Attribute classes implement access to the Entity-Component-Attribute -based scene and are what the SyncManager manipulates to replicate scene modifications coming from the server.

These classes provide also signals for scene modification operations that happen on them (such as "entityCreated", "entityRemoved") and the SyncManager hooks into these to be able to send the client's scene modifications to the server.

All scene modification operations (creation, modification and removal of entities, components and attributes) are accompanied by a "change type" or signaling mode. It is possible to make changes to the scene without sending them to the network, or even without signaling them internally at all. The default change mode should be used in most cases, and is also used if the change type parameter is omitted.

The entities in the scene and the components in an entity are primarily identified with their integer ID's. The ID's are split into different ranges based on their purpose:

Next follows a brief description of the most important functions in the Scene, Entity and Component classes needed by the network synchronization.

Scene

          scene.createEntity(id, changeType)
        

Create an empty entity into the scene. ID 0 will assign the next available. The change type decides whether to create a replicated (Replicate) or local (LocalOnly) entity.

          scene.removeEntity(id, changeType)
        

Remove an entity by ID.

          scene.entityById(id)
        

Return an entity by ID, or no entity if not found.

          scene.entityByName(name)
        

Return an entity by string name. The name is contained in its Name component.

Entity

          entity.createComponent(id, typeId, name, changeType)
        

Create a component to the entity. ID 0 will assign the next available. The typeId can be a string (such as "Mesh" or "Placeable") or an integer type value that is used internally in the network protocol for optimization. Components can be optionally given a string name.

          entity.removeComponent(id, changeType)
        

Remove a component from the entity by ID.

          entity.removeAllComponents(changeType)
        

Remove all components from the entity.

          entity.triggerAction(name, params, execType)
        

Signal an RPC-like Entity Action. The action is identified with its string name, and the parameters are an array of strings. The execution type is a bit combination of the following constants: cExecTypeLocal, cExecTypeServer, cExecTypePeers, which means to either execute the action only locally, send it to the server, or to send it to all connected clients via the server. The network synchronization of the action is also implemented through a signal, "actionTriggered", that the SyncManager hooks into.

          entity.componentById(id)
        

Return a component from the entity by ID, or no component if not found.

          entity.componentByType(typeId, name)
        

Return a component from the entity by its type, which can be a string or an integer type number. Optionally limit the query to components with the specified name.

Component

          component.createAttribute(index, typeId, name, value, changeType)
        

Create a dynamic attribute to the component. Index starts from zero and should be allocated sequentially. Currently only the DynamicComponent supports dynamic attributes. Type id is a string name identifying the type (such as "string", "float3" or "bool"), or an integer type value that is used internally in the network protocol for optimization.

          component.removeAttribute(index, changeType)
        

Remove a dynamic attribute by its zero-based index.

          component.attributeById(id)
        

Returns an attribute by its string ID. The ID is a short identifier starting with a lowercase letter which is also the same as the property name of the attribute for shorthand access (ie. component.attributeName)

          component.attributeByName(name)
        

Returns an attribute by its string name. This is separate from the ID and should be a longer descriptive name that is shown in eg. editor tools. Note that for dynamic attributes the ID and the name can not be separately specified, but are set to the same string value.

          registerCustomComponent(typeName, blueprintComponent, changeType)
        

Registers a custom static-structured component. Its attribute structure (described by a "blueprint" component that should have been created beforehand) will be sent to the server the next time syncManager.sendChanges() is called. After that components of the new type can be created both on the client and the server.

Scene synchronization

To begin synchronizing scene content with the server, the Scene and SyncManager objects need to be created. On construction, the SyncManager needs to be given a WebSocketClient object that is connected to a server and a Scene, preferably empty of entities.

        var syncManager = new SyncManager(webSocketClient, scene);
      

After this, the SyncManager automatically receives scene modification messages from the server and applies the changes to the Scene. To send pending changes made on the client side back to the server, the following function on the SyncManager needs to be called:

        syncManager.sendChanges()
      

Binary protocol description

The binary messages sent between the client and server are described in detail in following document: Tundra protocol

In a WebSocket implementation, each message is sent as one binary WebSocket frame, with the message ID encoded as an unsigned little-endian 16-bit value in the beginning.

Model of operation in pseudocode

Implementing a synchronization client is easier than a server, as it does not need to handle several connections. A client needs only to listen to the binary protocol messages as they arrive from the server, and perform them on its local scene. It also needs to listen to the change signals from the local scene, and send those that have the change type Replicated back to the server.

The server, on the other hand, should for efficiency operate on network "ticks" or "frames" that happen for example 20 or 30 times per second. It collects all the scene changes up to the next tick, then processes them in a batched manner. If for example the position in an entity's Transform component changes several times before the next tick, only the latest data should be sent to conserve bandwidth.

The server needs to maintain a structure, called here the SyncState, for each client connections that contains the list of "dirty" (contains changes that need to be sent) entities, components and attributes. When it is time to perform the next network tick, the default (naive) operation mode is to go through this structure for all client connections, and send all pending changes. More intelligent per-client operations such as interest management (throttling the update rate or excluding updates completely for far away entities) or overall bandwidth throttling can be performed. In pseudocode, the algorithm for going through the SyncState of a client is the following:

        Go through any newly registered component types. Send them to the client
        Go through all dirty entities in the SyncState
          If entity was removed, send RemoveEntity message
          If entity is new for the client, send CreateEntity message with full component and attribute data
          Otherwise go through its dirty components
           If component was removed, collect it into a RemoveComponents message
           If component is new for the client, collect it into a CreateComponents message with full attribute data
           Otherwise go through its dirty attributes
            If attribute is dynamic and was removed, collect it into a RemoveAttributes message
            If attribute is dynamic and is new for the client, collect it into a CreateAttributes message
            Otherwise (the attribute is modified) collect it into an EditAttributes message
          Send RemoveComponents, CreateComponents, RemoveAttributes, CreateAttributes, EditAttributes messages that have data in them
          Clear the dirty status for the entity and all its components & attributes