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
// 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:
// 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:
// 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:
// 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:
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:
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.
/* ... */
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;
}
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.
- First step is to create an object on the master and on all slaves as a static one (without adding it to synchronization).
- 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).
- And the last step is to set these callbacks using the addCallback() method in the AppWorldLogic::init()
/* ... */ // 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->addCallback(Syncker::MasterInterface::UDP_USER_SEND, MakeCallback(udp_user_send)) } else { /* ... */ // setting a callback on receiving user message syncker_slave->addCallback(Syncker::SlaveInterface::UDP_USER_RECEIVE, 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):
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):
/* ... */
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());
}
/* ... */
}