UnigineEditor
Interface Overview
Assets Workflow
Settings and Preferences
Adjusting Node Parameters
Setting Up Materials
Setting Up Properties
Landscape Tool
Using Editor Tools for Specific Tasks
FAQ
Программирование
Fundamentals
Setting Up Development Environment
Usage Examples
UnigineScript
C#
UUSL (Unified UNIGINE Shader Language)
File Formats
Rebuilding the Engine and Tools
GUI
Double Precision Coordinates
API
Containers
Common Functionality
Controls-Related Classes
Engine-Related Classes
Filesystem Functionality
GUI-Related Classes
Math Functionality
Node-Related Classes
Networking Functionality
Pathfinding-Related Classes
Physics-Related Classes
Plugins-Related Classes
CIGI Client Plugin
Rendering-Related Classes

Implementing Syncker Logic for a Custom Project

C++ Implementation

You can create separate master and slave applications, or a single one to run on both master and slave sides. To enable the Syncker in your master or slave application to synchronize nodes, render parameters and players over the network, follow the instructions below.

Initializing Syncker

First of all, you should initialize Syncker. To do so you can simply add the following code to the AppSystemLogic class for both master and slave appplications.

In the AppSystemLogic.h file include SynckerInterface.h library and declare a Syncker manager interface pointer in the private section of the AppSystemLogic

Source code (C++)
// AppSystemLogic.h

/* .. */

// including the Syncker interface library
#include <SynckerInterface.h>

/* .. */

class AppSystemLogic : public Unigine::SystemLogic 
{
public:
	AppSystemLogic() {}
	virtual ~AppSystemLogic() {}
	
	virtual int init() override;

private:
	// declaring a Syncker manager interface pointer
	Unigine::Syncker::ManagerInterface* syncker_manager = nullptr;
	
	// declaring pointers to master and slave interface
	Unigine::Syncker::MasterInterface* syncker_master = nullptr;
	Unigine::Syncker::SlaveInterface* syncker_slave = nullptr;

public:

	/* .. */

};

/* .. */

Insert the following Syncker initialization code to the AppSystemLogic::init() method:

Source code (C++)
// AppSystemLogic.cpp

#include <UnigineApp.h>
using namespace Unigine; 
using namespace Syncker;
/* .. */

int AppSystemLogic::init(
{
	// update even if the window is not focused
	App::get()->setUpdate(1);

	// find Syncker plugin index
	int index = Engine::get()->findPlugin("Syncker");
	if (index == -1)
	{
		Log::error("Syncker isn't loaded!\n");
		return 1;
	}
	
	// get the Syncker manager interface
	syncker_manager = (ManagerInterface*)Engine::get()->getPluginData(index);

	// initialize master or slave interface of the Syncker depending on the specified "-sync_master" start-up command line option (slave by default)
	if (syncker_manager->getArgIsMaster())
	{
		syncker_master = syncker_manager->initMaster(syncker_manager->getArgMasterBroadcast(), syncker_manager->getArgUdpPort(), syncker_manager->getArgTcpPort(), syncker_manager->getArgTcpPingPort());
	}
	else
	{
		syncker_slave = syncker_manager->initSlave(syncker_manager->getArgSlaveName(), syncker_manager->getArgUdpPort(), syncker_manager->getArgTcpPort(), syncker_manager->getArgTcpPingPort());
	}
	// enabling display of debug information for master or slave
	syncker_manager->getSyncker()->setDebug(1);
	
	return 1;
}

You should also perform a part of initialization in the AppWorldLogic class. For this purpose include the SynckerInterface.h library and declare a pointer to the master interface in the AppWorldLogic.h file:

Source code (C++)
// AppWorldLogic.h

/* .. */

// including the Syncker interface library
#include <SynckerInterface.h>

/* .. */

class AppWorldLogic : public Unigine::WorldLogic 
{
public:

/* .. */

private:
	// declaring a pointer to the master interface
	Unigine::Syncker::MasterInterface* syncker_master = nullptr;
};

/* .. */

In the AppWorldLogic.h file add the code to get the pointer to the master interface for further use, create the main camera (player) and do some framerate adjustment in the AppWorldLogic::init() method:

Source code (C++)
// AppWorldLogic.cpp
#include <UnigineEditor.h>
#include <UnigineGame.h>
#include <UnigineApp.h>

/* .. */

using namespace Unigine;
using namespace Syncker;
using namespace Math;

int AppWorldLogic::init() 
{
	// finding Syncker plugin index
	int index = Engine::get()->findPlugin("Syncker");
	if (index == -1)
	{
		Log::error("AppWorldLogic::init(): Plugin \"Syncker\" is not loaded!\n");
		return 0;
	}

	// get the Syncker manager interface
	ManagerInterface* syncker_manager = (ManagerInterface*)Engine::get()->getPluginData(index);

	// getting a pointer to Master interface (if it was initialized in AppSystemLogic.cpp) to be used later
	if (syncker_manager->isMasterInitialized())
	{
		syncker_master = syncker_manager->getMaster();
	}

	// creating player (on Master and Slaves)
	player = PlayerSpectator::create();
	player->setPosition(Vec3(0.0f, -40.0f, 12.0f));
	player->setDirection(vec3(0.0f, 1.0f, 0.0f), vec3::UP);
	player->setMinVelocity(10);
	player->setMaxVelocity(player->getMinVelocity() * 2);
	Game::get()->setPlayer(player->getPlayer());

	// clamping framerate to 60 if VSync is off
	if ((App::get()->getFlags() & App::VSYNC) == 0 ||
		(App::get()->getFlags() & App::FULLSCREEN) == 0)
	{
		Game::get()->setFTime(1 / 60.0f);
	}

	return 1;
}

Synchronizing Nodes/Materials

Objects of the following types are synchronized automatically: ObjectWaterGlobal, ObjectCloudLayer, WorldLight if they present in the *.world file on all computers.

The list of the nodes, available for synchronization, includes the following:

To start synchronizing other nodes or materials (the logic is the same for both), that exist in the world, you just need to tell the Syncker which of them you want. To do so you can add the following code to the AppWorldLogic::init() method of your master application:

Source code (C++)
int AppWorldLogic::init() 
{
	/* ... */

	// checking if we have a Master interface
	if (syncker_master)
	{
		// getting a pointer for the node named "material_ball"
		NodePtr my_node = Editor::get()->getNodeByName("material_ball");

		// add the existing node to synchronization
		syncker_master->addSyncNode(my_node);
	}

	return 1;
}

Now, you can implement your logic for synchronized nodes in the AppWorldLogic::update() method for the master:

Source code (C++)
int AppWorldLogic::update() 
{
	/* ... */

	// checking if we have a Master interface
	if (syncker_master)
	{
		// getting a pointer for the node named "material_ball"
		NodePtr my_node = Editor::get()->getNodeByName("material_ball");
		
		// changing node's rotation
		my_node->setRotation(my_node->getRotation() * Math::quat(0, 0, Game::get()->getIFps()));

	}

	return 1;
}

Creating/Deleting Nodes at Runtime

You can create and delete nodes at runtime. When creating a node you can decide whether it is dynamic and is going to be synchronized, or it is static and just going to be added to the world. To synchronize creation of a node on all connected slaves, use the createNode() function.

Source code (C++)
/* ... */
int cubes_created = 0;

int AppWorldLogic::update() 
{
	/* ... */

	// checking if we have a Master interface and cubes are not yet created
	if (syncker_master && !cubes_created)
	{
		// creating a dynamic cube and set up its material and transformation
		ObjectMeshStaticPtr dynamic_cube = ObjectMeshStatic::create("box.mesh");
		dynamic_cube->setMaterial("mesh_base", 0);
		dynamic_cube->setPosition(Math::Vec3(game->getRandomFloat(-50, 50), game->getRandomFloat(-50, 50), game->getRandomFloat(0, 50)));
		dynamic_cube->setRotation(Math::quat(game->getRandomFloat(0, 360), game->getRandomFloat(0, 360), game->getRandomFloat(0, 360)));
			
		// create a static cube and set up its material and transformation
		ObjectMeshStaticPtr static_cube = ObjectMeshStatic::create("box.mesh");
		static_cube->setMaterial("mesh_base", 0);
		static_cube->setPosition(Math::Vec3(game->getRandomFloat(-50, 50), game->getRandomFloat(-50, 50), game->getRandomFloat(0, 50)));
		static_cube->setRotation(Math::quat(game->getRandomFloat(0, 360), game->getRandomFloat(0, 360), game->getRandomFloat(0, 360)));
			
		// commanding slaves to create a dynamic node and adding it to synchronization by setting add_to_sync = 1
		syncker_master->createNode(dynamic_cube->getNode(), 1, 1);
		
		// commanding slaves to create a static node, that won't be synchronized
		syncker_master->createNode(static_cube->getNode(), 0);

		cubes_created = 1;
	}

	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 <your_unigine_project>/data/<your_unigine_project>/meshes folder.

Customizing Synchronization via User Messages

Let us consider the following simple example: we can set rotation for a static user object, by simply sending a quaternion via a user message instead of synchronizing the whole node.

  1. First step is to create an object on the master and on all slaves as a static one (without adding it to synchronization).
  2. Next, we implement two callback functions:
    • udp_user_send - to be called on sending a user message (for the master). Here we collect all necessary parameters (object's rotation quaternion in our case) and create a message to be sent.
    • udp_user_receive - to be called on receiving it (for the slave). Here we extract the parameters from the received message and use them (in this case to rotate our object).
  3. And the last step is to set these callbacks using the setUDPUserReceiveCallback() and setUDPUserSendCallback() in the AppWorldLogic::init() method
    Source code (C++)
    /* ... */
    // declaring a user object, the we are going to rotate via user messages
    ObjectMeshStaticPtr user_object;
    
    /// callback function to be fired on sending a user message to slaves
    void AppWorldLogic::udp_user_send(BlobPtr message)
    {
    	// getting the rotation of the object as a quaternion
    	Math::quat rotation = user_object->getRotation();
    
    	// writing the quaternion to the blob
    	message->writeQuat(rotation);
    }
    /// callback function to be fired on receiving a user message from the master
    void AppWorldLogic::udp_user_receive(BlobPtr message)
    {
    	// reading the quaternion from the blob
    	Math::quat rotation = message->readQuat();
    	
    	// setting the rotation of the object using the quaternion
    	user_object->setRotation(rotation);
    }
    
    int AppWorldLogic::init() 
    {
    	/* ... */
    
    	// checking if we have a Master interface
    	if (syncker_master)
    	{
    		/* ... */
    		// creating our user object and commanding slaves to create a static node, that won't be synchronized
    		user_object = ObjectMeshStatic::create("box.mesh");
    		syncker_master->createNode(user_object->getNode(), 0);
    		
    		// setting a callback on sending user message
    		syncker_master->setUDPUserSendCallback(MakeCallback(udp_user_send));
    
    	}
    	else
    	{
    		/* ... */
    		
    		// setting a callback on receiving user message
    		syncker_slave->setUDPUserReceiveCallback(MakeCallback(udp_user_receive));
    	}
    
    	return 1;
    }
    
    /* ... */

Changing Views and Cameras

You can change the viewport on the master or slave side. For this purpose you can use the following code (example):

Source code (C++)
Unigine::Syncker::ManagerInterface* syncker_manager;
/* ... 

perform checks if the Syncker plugin is loaded 
and get the Syncker manager interface (ManagerInterface)

 ... */

// if syncker is initialized, assigning the config mesh surface named "surface_0" to the 2nd monitor
if (syncker_manager->isSynckerIntialized()) 
	syncker_manager->getSyncker()->setView(1, "surface_0");
	
/* ... */

You can also tell a slave to use another camera. To do so, you can use the following code (example):

Source code (C++)
/* ... */

PlayerSpectatorPtr aux_player = nullptr;

/* ... */

int AppWorldLogic::update() 
{
	/* ... */
	
	// checking if we have a Master interface
	if (syncker_master && !aux_player)
	{
		// creating a new auxiliary player (on Master and Slaves)
		aux_player = PlayerSpectator::create();
		aux_player->setPosition(Vec3(0.0f, -40.0f, 200.0f));
		aux_player->setDirection(vec3(0.0f, 1.0f, 0.0f), vec3::UP);
		aux_player->setMinVelocity(10);
		aux_player->setMaxVelocity(aux_player->getMinVelocity() * 2);
	
		// setting a bitmask for the third slave
		int slave_mask = 0;
		slave_mask = 1 << 2;
		
		// commanding slaves to create a new player and adding it to synchronization
		createNode(aux_player->getNode(), 1);
		
		// setting a new created player for the third slave 
		syncker_master->setSlavePlayer(slave_mask, aux_player->getPlayer());

	}
	
/* ... */
}
Last update: 04.06.2018