Programming
Fundamentials
Setting Up Development Environment
UnigineScript
C++
C#
UUSL (Unified UNIGINE Shader Language)
File Formats
Rebuilding the Engine and Tools
GUI
Double Precision Coordinates
API
Bounds-Related Classes
Containers
Controls-Related Classes
Core Library
Engine-Related Classes
GUI-Related Classes
Node-Related Classes
Pathfinding-Related Classes
Physics-Related Classes
Plugins-Related Classes
Rendering-Related Classes
Utility Classes

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.

Notice
If you use projectors to render your application, see the article on Running Syncker with AppProjection.

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.

  1. 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.
    Source code(UnigineScript)
    // 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;
    }
    Notice
    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.
  2. 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.
    Notice
    You can skip this step and load the world on each slave individually.
    Source code(UnigineScript)
    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;
    	}
    }
  3. Then, specify the shutdown logic for the console and Syncker:
    Source code(UnigineScript)
    // 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;
    }
  4. 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.
    Source code(UnigineScript)
    // 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:

Notice
The DEFINE_NAME should be specified on the application start-up.
Source code(UnigineScript)
#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:

  1. 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:

    Shell commands
    main_x64 -data_path "../" -editor_script "core/systems/syncker/syncker_slave.cpp" -extern_define "DEFINE_NAME" -console_command "script_editor_load"
    The script_editor_load console command is required to load the Syncker script for slaves.
    Notice
    Do not forget to add other required start-up arguments.
  2. Load the world for each slave if you didn't implement the loading logic in the script:
    Source code
    world_load unigine_project/unigine_project
    You can also perform this command on the application start-up (see the previous step):
    Shell commands
    -console_command "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):
    Output
    ---- Syncker::Slave ----
    Name: unknown
    UDP: 8890
    TCP: 8891
  3. Run the unigine_project script on the main computer to be used as the master.
    Shell commands
    main_x64 -data_path "../" -console_command "world_load unigine_project/unigine_project"
    After that, in the console of the connected slaves you will see the following:
    Output
    ---- Syncker::Master ----
    Address: 192.168.0.255
    UDP: 8890
    TCP: 8891
  4. Change the broadcasting network address of the master:
    Source code
    master_address xxx.xxx.xxx.xxx
    And reload the world by using the world_reload. After that, in the master console you will see messages that slaves have connected:
    Output
    Connection to "xxx.xxx.xxx.xxx" accepted in 0.00 seconds
Notice
If the world on the slaves is not loaded programmatically, console commands world_reload or world_load with the same world as currently loaded are not run on slaves after the master. Only loading of a new world works.

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.
    Notice
    Make sure that the name of each mesh surface matches the name of the corresponding slave.
    Source code
    master_mesh <mesh_name>
    To set the slave name, run the slave_name console command:
    Source code
    slave_name <name>
Last update: 2017-07-03