This document describes the Synchronization GE real-time sync protocol API
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:
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.
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.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.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.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.
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()
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.
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