UnigineScript
The Language
Core Library
Engine Library
Node-Related Classes
GUI-Related Classes
Plugins Library
High-Level Systems
Samples
C++ API
API Reference
Integration Samples
Usage Examples
Content Creation
Materials
Unigine Material Library
Tutorials

Networking Sample

The following network sample is based on the version 4.0. Networking examples can be found under data/network/samples folder of Unigine SDK or Evaluation kit.

Step 1. Update (optional)

For the network packets to be received even when the application window is not in focus or minimized, engine.app.setUpdate() function should be called.

Source code (UnigineScript)
engine.app.setUpdate(1);

Step 2. Register handlers for network events

To be notified about any network events, the handlers for them should be registered and implemented. Note that your event handler can be named in any way, but it should take certain arguments.

Let's say we want to receive messages when a client has connected to the server. For that, we need to register an event handler for NETWORK_ON_CLIENT_CONNECTED event using network.registerHandler() function. It has a handler_name argument that specifies what function is going to handle the given event. Our registered handler function should take the client address (NetworkAddress class) as an argument.

Source code (UnigineScript)
// Register the event handler
network.registerHandler(NETWORK_ON_CLIENT_CONNECTED,"NetworkHandler::onClientConnected");

// Implement the handler that takes a mandatory argument
void onClientConnected(NetworkAddress client_address) {		
	log.message("Client connected: network address = %s\n",client_address.toString());
} 

To stop receiving notifications about a network event use network.unregisterHandler() function.

Source code (UnigineScript)
network.unregisterHandler(NETWORK_ON_CLIENT_CONNECTED);

Step 3. Start and stop a network session

Depending on whether a network node is a server or a client, do the following.

3.1. On the Server side

To start a server, use network.startServer(). Here, we will set the maximum number of client connections to 10. When this limit is reached, new connection requests are rejected.
But before that, we need to create an instance of NetworkAddress to store and pass our server address over the network.

Notice
NetworkAddress and SharedData are an extern classes and they are not handled by memory management system by default.
  • If you create them manually (using a constructor in UnigineScript), be careful to avoid memory leaks. Use class_manage() to make the garbage collector responsible for deleting them, or delete created instances explicitly.
  • Those instances that are created internally (received from network event handlers or returned by other functions, for example, like network.getMyNetworkAddress()) will be automatically managed by the garbage collector.
Source code (UnigineScript)
// Create NetworkAddress instance. Assign its ownership to the script (to be handled by a garbage collector).
NetworkAddress server_address = class_manage(new NetworkAddress("127.0.0.1",60016));

// Start a server. No more than 10 clients can connect to it.
network.startServer(server_address,10);

To stop the server, use network.stopServer().

Source code (UnigineScript)
network.stopServer();

3.1. On the Client side

To connect a client to the server, use network.connectToServer() and pass the server address (a NetworkAddress instance).

Source code (UnigineScript)
network.connectToServer(server_address);

To disconnect from the server, use network.disconnectFromServer().

Source code (UnigineScript)
network.disconnectFromServer();

Step 4. Prepare data to be sent over the network

To sent the data over the network, an instance of the SharedData class should be created and filled with corresponding data. All simple UnigineScript data types can be added into SharedData (int, long, float, double, vec3, dvec3, ivec3, vec4, dvec4, ivec4, mat4, quat or string). NetworkAddress also can be added as a data field.

Source code (UnigineScript)
SharedData shared_data = class_manage(new SharedData());
shared_data.addField(MESSAGE_TYPE_CREATE_PROFILE);
shared_data.addField(user_profile.getUserID());
shared_data.addField(user_profile.getDisplayName());
shared_data.addField(user_profile.getNetworkAddress()); 

Step 5. Add code to parse SharedData

Source code (UnigineScript)
int protocol_message_type = shared_data.getNextField();
int user_id = shared_data.getNextField();
string display_name = shared_data.getNextField();
NetworkAddress network_address = shared_data.getNextField(); 

Step 6. Send data over the network

Call one of the following function to send your data over the network.

Source code (UnigineScript)
// Send data to the specified address
network.send(shared_data,server_address);
// Send data to all connected nodes
network.sendAll(shared_data);
// Send data to all connected nodes, except for the specified node
network.sendAll(shared_data,server_address);

Step 7. State synchronization (optional)

If you want to synchronize your data over the network, you can use a SharedState class. Basically, it contains an internal instance of SharedData and synchronization parameters. Besides that, it can store a history of received updates.

7.1. Create a synchronized state

To create a state from the array of data fields, use network.createSharedState() function.

Source code (UnigineScript)
SharedState state = network.createSharedState(owner_id,state_id,(field0,field1,field2));

You can also create an empty state and fill it with data. It can be done in different ways.

Source code (UnigineScript)
// Create an empty state
SharedState state = network.createSharedState(owner_id,state_id);

// Fill the state with data:
// from the array
state.setArray((field0,field1,field2));

// field by field
state.addDataField(field0);
state.addDataField(field1);
state.addDataField(field2);

state.setDataField(0,field0);
state.setDataField(1,field1);
state.setDataField(2,field2);

// if there is only one field
state.setDataField(field);

state.setNextDataField(field0);
state.setNextDataField(field1);
state.setNextDataField(field2);

// from the existing instance of SharedData
state.setData(data);

Set state synchronization parameters:

After that, state updates will be automatically sent to all clients with a set interval.

Source code (UnigineScript)
state.setUpdateInterval(100);
state.setReliability(RT_RELIABLE_ORDERED);
state.setRelevantState(user.getNetworkAddress(),RELEVANT_STATE_SEND_ALL_CHANGES)
state.setMaxHistorySize(20);

You can also disable the automatic sending of updates (via setAutoUpdate()) and send the state updates manually (via network.sendUpdateSharedState()). In this case, it makes no sense to set update interval.

Source code (UnigineScript)
// Set state synchronization parameters
state.setReliability(RT_RELIABLE_ORDERED);
state.setRelevantState(user.getNetworkAddress(),RELEVANT_STATE_SEND_ALL_CHANGES)
state.setMaxHistorySize(20);
// Send state update
network.sendUpdateSharedState(state);

7.2. Delete the state

A state should be deleted by the same network host that called its creation function.

Notice
On other network nodes, a state should not be deleted manually - it will be automatically removed after handling the delete massage.

Source code (UnigineScript)
network.destroySharedState(state);

7.3. Update state

A state can be updated using any of the methods described above.

7.4. Get state data and work with history

To access the last update of the state and get its timestamp, use getSharedData() and getTimestamp() functions.

Source code (UnigineScript)
SharedData server_data = state.getSharedData();
int timestamp = state.getTimestamp();
To get updates for separate fields of the container, use one of the following.
Source code (UnigineScript)
forloop(int i = 0; state.getNumDataFields()) {
    log.message("field %d : %s\n",i,string(state.getDataField(i)));
}

// or
state.resetCurDataField();
forloop(int i = 0; state.getNumDataFields()) {
    log.message("field %d %s\n",i,string(state.getNextDataField()));
} 

Even if there is only one field in the container, you can still use getDataField():

Source code (UnigineScript)
int field = state.getDataField();

A state can automatically store the history of updates received from another network node. (Only if the maximum size for history is set higher than 1!) To work with this update history, do the following.

Source code (UnigineScript)
forloop(int i = 0; state.getHistorySize()) {
    int timestamp = state.getTimestamp(i);
    SharedData data = state.getHistoryEntry(i);
} 

Step 8. Discover servers on the LAN (optional)

8.1. On the Server side

  1. On the server side, you need to set the game_type_info. By this information clients will find necessary servers in the LAN and filter out all the rest. In addition, you can set any custom_info that the server will send back in response (for example, the current number of connected clients).
    Source code (UnigineScript)
    network.setGameTypeInfo("sample");
    network.setCustomInfo("0");
    
  2. Start the server on the specified address.
    Source code (UnigineScript)
    network.startServer(server_address,10);
    

8.2. On the Client side

  1. Set the game_type_info in order to connect to the desired server.
    Source code (UnigineScript)
    network.setGameTypeInfo("sample");
    
  2. Register and implement event handler NETWORK_ON_PONG_RECEIVED. This message indicates that the server has replied to a connection request.
    Source code (UnigineScript)
    network.registerHandler(NETWORK_ON_PONG_RECEIVED,"NetworkHandler::onPongReceived");
    
    void onPongReceived(NetworkAddress server_address,int ping_time,string game_type,string custom_info) {		
    	log.message("New server available: address = %s ping = %d ms game type = %s number of clients = %s\n",
    server_address.toString(),ping_time,game_type,custom_info);
    }
    
  3. Send the ping request if there are game servers in the LAN.
    Source code (UnigineScript)
    // To search for the servers filtered according to game_type_info
    network.pingAvailableLANServers(listen_port,server_port,"sample")
    // To search for all servers
    network.pingAvailableLANServers(listen_port,server_port,"")
    
    
Last update: 2017-07-03