Running Syncker for a Custom Project
To use Syncker most efficiently from the point of view of performance, selective synchronization of nodes and materials is needed. This is possible if Syncker is run from a script (and it does not matter if it will be a world, editor or a custom system one) for full control over synchronization.
Script Implementation
To run the Syncker master from the world script of a custom project, to synchronize nodes, render parameters and the player over the network, implement your script according to instructions below. Notice that similar logic is used to synchronize materials.
- First of all, initialize Syncker in the script that is run on the master computer. Also you should initialize a console to enable the ability of running master command-line options.
To synchronize creation of a node on all connected slaves, use the Unigine::Syncker::Master::createNode() function.
// unigine_project.cpp on a master computer #include <core/unigine.h> #include <core/systems/syncker/syncker_master.h> #include <core/systems/syncker/syncker_master_console.h> // It is more efficient to serialize nodes stored in an array and pass them as // one packet instead of sending them one by one ObjectMeshStatic meshes[0]; int init() { // Set a fixed FPS value to make simulations consistent across all computers engine.game.setFTime(1.0f / 60.0f); // Initialize master console Unigine::Syncker::Master::Console::init(); // Initialize Syncker using a given broadcast address Unigine::Syncker::Master::init(master_address); PlayerSpectator camera = new PlayerSpectator(); camera.setPosition(Vec3(2.0f,0.0f,1.5f)); camera.setDirection(Vec3(-1.0f,0.0f,-0.5f)); engine.game.setPlayer(camera); // Fill the world with meshes that will be synchronized for(int j = 0; j < 2; j++) { for(int i = 0; i < 2 - j; i++) { // you can specify the path to any other mesh ObjectMeshStatic mesh = node_remove(new ObjectMeshStatic("unigine_project/meshes/box.mesh")); mesh.setMaterial("mesh_base","*"); mesh.setProperty("surface_base","*"); mesh.setPosition(Vec3(i,j,j)); // Synchronize creation of the mesh on all slaves Unigine::Syncker::Master::createNode(mesh); meshes.append(mesh); } } return 1; }
For this code to be compiled, copy the box.mesh file from the <Unigine SDK>/data/samples/common/meshes folder to the unigine_project/data/unigine_project/meshes folder. - Implement logic, which allows loading the world for all of the slaves on the master side, and call it in the init() function after the Syncker initialization.
You can skip this step and load the world on each slave individually.
void sync_world() { // load slave world Unigine::Syncker::Master::systemCall(~0,"engine.console.run",("world_load " + engine.world.getName())); // waiting for the slave world int ret[0]; float begin = clock(); while(clock() - begin < 2.0f) { int is_loaded = 1; Unigine::Syncker::Master::systemCall(~0,"engine.world.isLoaded",(),ret); foreach(int i = 0; ret) is_loaded &= i; if(is_loaded) break; } }
- Then, specify the shutdown logic for the console and Syncker:
// unigine_project.cpp on a master computer int shutdown() { // Run a console command on all slave applications, if necessary. // The command being called is run from a system script of slaves. Unigine::Syncker::Master::systemCall(~0,"engine.console.run",("world_quit")); // Shut down the console Unigine::Syncker::Master::Console::shutdown(); // Shut down Syncker. Unigine::Syncker::Master::shutdown(); return 1; }
- And synchronization logic in the script render() function. Such logic allows you to specify all nodes (and materials, if required), which have to be synchronized.
Detailed function descriptions can be found here.
// unigine_project.cpp on a master computer int render() { // Synchronize frame information Unigine::Syncker::Master::syncFrame(); // Synchronize player position Unigine::Syncker::Master::syncPlayer(); // Synchronize render parameters Unigine::Syncker::Master::syncRender(); // Synchronize nodes Unigine::Syncker::Master::syncNodes(meshes); // End of the frame Unigine::Syncker::Master::syncEnd(); return 1; }
If you run the same world script on the master and slave sides, you need to wrap your code around with #ifndef DEFINE_NAME ... #else ... #endif as follows:
#include <core/unigine.h>
// logic to run on the master
#ifndef DEFINE_NAME
#include <core/systems/syncker/syncker_master.h>
#include <core/systems/syncker/syncker_master_console.h>
ObjectMeshStatic meshes[0];
// init() function defined above
// shutdown() functions defined above
// render() function defined above
#else
// logic to run on a slave
int init() {
PlayerSpectator camera = new PlayerSpectator();
camera.setPosition(Vec3(2.0f,0.0f,1.5f));
camera.setDirection(Vec3(-1.0f,0.0f,-0.5f));
engine.game.setPlayer(camera);
return 1;
}
#endif
Running Syncker
As the Syncker master initialization and shutdown logic and also synchronization of nodes and materials is implemented in the world script, there is no need to use the Syncker script for the master. However, you still need to use the Syncker script for slaves as you didn't implement a custom logic for it.
After the master script is implemented, you need to run it as follows:
- Run all slave applications that will be synchronized with the master.
For that, specify the following options on the slave application start-up to override the default editor script with the Syncker script for slaves. In case you use the same world script on the master and slave sides, specify also the required definition:
The editor_reload console command is required to load the Syncker script for slaves.main_x64 -data_path "../" -editor_script "core/systems/syncker/syncker_slave.cpp" -extern_define "DEFINE_NAME" -console_command "editor_reload"
Do not forget to add other required start-up arguments. -
Load the world for each slave if you didn't implement the loading logic in the script:
You can also perform this command on the application start-up (see the previous step):
world_load unigine_project/unigine_project
If you open the console, you will see that a slave application listens on two ports for packets from a master (this default values can be left unchanged):-console_command "world_load unigine_project/unigine_project"
---- Syncker::Slave ---- Name: unknown UDP: 8890 TCP: 8891
- Run the unigine_project script on the main computer to be used as the master.
After that, in the console of the connected slaves you will see the following:
main_x64 -data_path "../" -console_command "world_load unigine_project/unigine_project"
---- Syncker::Master ---- Address: 192.168.0.255 UDP: 8890 TCP: 8891
- Change the broadcasting network address of the master:
And reload the world by using the world_reload. After that, in the master console you will see messages that slaves have connected:
master_address xxx.xxx.xxx.xxx
Connection to "xxx.xxx.xxx.xxx" accepted in 0.00 seconds
Configuring Displays
After launching your application on the master and slave computers, you need to configure a viewport of each slave depending on the screen configuration:
- If your screen configuration is represented by a grid of monitors, you can simply follow the instructions specified in the Changing Screen Configuration chapter of the main article on Syncker.
- In case your screen configuration is represented as a mesh or CAVE, set it for all of the connected slaves via the master_mesh
console command. Make sure that the name of each mesh surface matches the name of the corresponding slave.To set the slave name, run the slave_name console command:
master_mesh <mesh_name>
slave_name <name>