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.
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.
// 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.
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.
- 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.
// 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().
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).
network.connectToServer(server_address);
To disconnect from the server, use network.disconnectFromServer().
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.
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
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.
// 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.
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.
// 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:
- update interval — how often state updates will be sent
- update reliability — how reliable you want packet delivery to be
- state relevance — whether all updates should be sent to the network node, some of them or none at all
- history size — how much updates will be stored for this state
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.
// 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.
network.destroySharedState(state);
7.3. Update state
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.
SharedData server_data = state.getSharedData();
int timestamp = state.getTimestamp();
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():
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.
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
- 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).
network.setGameTypeInfo("sample"); network.setCustomInfo("0");
- Start the server on the specified address.
network.startServer(server_address,10);
8.2. On the Client side
- Set the game_type_info in order to connect to the desired server.
network.setGameTypeInfo("sample");
- Register and implement event handler NETWORK_ON_PONG_RECEIVED. This message indicates that the server has replied to a connection request.
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); }
- Send the ping request if there are game servers in the LAN.
// 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,"")