Programming
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

Serialization

Unigine API allows creating of objects whose states can be saved into a binary file and later restored from it.

See also

An example can be found in <UnigineSDK>/source/samples/Api/Scripts/Serialization/ directory.

Serialization Guidelines

Saving and restoring of the object state is done by using a binary serialization mechanism of Unigine. In order to be compatible with this mechanism,a class must implement methods that allow you to store states of the class objects into the Unigine::Stream and restore them from it.

These methods can be grouped as follows:

  • Methods for saving/restoring of states. The states are used for objects created and handled purely in UnigineScript. It means, such object implemented on the C++ side is created in script via new operator and deleted via delete. It is not passed between UnigineScript and any other system.
    • saveState() - saves a state of an object created on the UnigineScript side.
    • restoreState() — restores a state of an object created on the UnigineScript side. This function implies that a class has a default constructor that creates an empty object.
  • Methods for saving/restoring of pointers. The pointers are used for objects that are created in a C++ part of the application and will be deleted there as well. The script receives it, but is not responsible for managing them. For example, this is the case when a C++ function that creates an object is called from the script.
    • savePointer() — a static method which is used to save a state of an object created on C++ side and handled by a script.
    • restorePointer() — a static method which is used to restore a state of an object created on C++ side and handled by a script.

If an object is going to be created in script, as well as created on C++ side, all four functions should be implemented.

Notice
If the above methods are implemented incorrectly, the objects will not be saved, and memory-related errors will appear.

Step 1. Implement Object Saving and Restoring

By default, the following implementation is used (see the include/UnigineInterpreter.h header file):

Source code (C++)
// functor to save a state of an object constructed in scripts
template <class Class>
void ExternClassSaveState(const StreamPtr &stream,Class *object) {
	object->saveState(stream);
}

// functor to restore a state of an object constructed in scripts
template <class Class>
Class *ExternClassRestoreState(const StreamPtr &stream) {
	Class *object = new Class();
	object->restoreState(stream);
	return object;
}

// functor to save a state of an object created on C++ side and handled by a script
template <class Class>
	void ExternClassSavePointer(const StreamPtr &stream,Class *object) {
	Class::savePointer(stream,object);
}

// functor to restore a state of an object created on C++ side and handled by a script
template <class Class>
Class *ExternClassRestorePointer(const StreamPtr &stream) {
	return Class::restorePointer(stream);
}

Step 2. Export Class

You need to export classes whose instances are going to be serialized. One of the following functions can be used for that:

  • MakeExternClass() function. Instances of classes exported by using this function are non-restorable, that is, they should be manually re-created. If you try to restore an instance of such the class, this instance will be restored to null.
  • MakeExternClassSaveRestoreState() function, which allows you to save and restore instances created within UnigineScript.
  • MakeExternClassSaveRestorePointer() function, which allows you to save and restore objects that were created in C++ code and exported into UnigineScript.
  • MakeExternClassSaveRestoreStatePointer() function, which combines two previous possibilities, that is, objects created on both the UnigineScript and C++ sides can be saved and restored.

Changing Serialization Behaviour

After exporting, it is still possible to change serialization behavior in run-time. For that you can use two UnigineScript functions.

Notice
It is completely safe only if both state and pointer saving/restoring is implemented.
  • class_append() attaches the object to the script that will handle it (save, restore and delete it).
  • class_remove() removes the object from the script.
Notice
UnigineScript does not destroy objects that were created on C++ side and were not appended to the script.

Serialization Example

Here is an example of exporting a C++ class that fully supports serialization into UnigineScript.

C++ Side

Source code (C++)
#include <UnigineEngine.h>
#include <UnigineInterpreter.h>
#include <UnigineInterface.h>

#include "AppSystemLogic.h"
#include "AppWorldLogic.h"
#include "AppEditorLogic.h"

using namespace Unigine;
using namespace Math;

/******************************************************************************\
*
* User defined class
*
\******************************************************************************/

class MyObject : public Base {
		
	public:
		
		MyObject() : mass(0.0f) {
			Log::warning("MyObject::MyObject(): called\n");
		}
		MyObject(const vec3 &size,float mass) : size(size), mass(mass) {
			Log::warning("MyObject::MyObject((%g,%g,%g),%g): called\n",size.x,size.y,size.z,mass);
		}
		~MyObject() {
			Log::warning("MyObject::~MyObject(): called\n");
		}
		
		// size
		void setSize(const vec3 &s) {
			Log::warning("MyObject::setSize((%g,%g,%g)): called\n",s.x,s.y,s.z);
			size = s;
		}
		const vec3 &getSize() const {
			return size;
		}
		
		// mass
		void setMass(float m) {
			Log::warning("MyObject::setMass(%g): called\n",m);
			mass = m;
		}
		float getMass() const {
			return mass;
		}
		
		// save state
		void saveState(StreamPtr &stream) const {
			Log::warning("MyObject::saveState(): called\n");
			stream->writeVec3(size);
			stream->writeFloat(mass);
		}
		
		// restore state
		void restoreState(StreamPtr &stream) {
			Log::warning("MyObject::restoreState(): called\n");
			size = stream->readVec3();
			mass = stream->readFloat();
		}
		
		// save pointer
		static void savePointer(StreamPtr &stream,MyObject *object) {
			Log::warning("MyObject::savePointer(): called\n");
			stream->writeVec3(object->size);
			stream->writeFloat(object->mass);
		}
		
		// restore pointer
		static MyObject *restorePointer(StreamPtr &stream) {
			MyObject *object = new MyObject();
			Log::warning("MyObject::restorePointer(): called\n");
			object->size = stream->readVec3();
			object->mass = stream->readFloat();
			return object;
		}
		
	private:
		
		vec3 size;
		float mass;
};

MyObject *MakeMyObject(const vec3 &size,float mass) {
	return new MyObject(size,mass);
}

void DeleteMyObject(MyObject *object) {
	delete object;
}

/******************************************************************************\
*
* Main
*
\******************************************************************************/

#ifdef _WIN32
	int wmain(int argc,wchar_t *argv[]) {
#else
	int main(int argc,char *argv[]) {
#endif
	
	// export class with serialization
	ExternClass<MyObject> *my_object = MakeExternClassSaveRestoreStatePointer<MyObject>();
	my_object->addConstructor<const vec3&,float>();
	my_object->addFunction("setSize",&MyObject::setSize);
	my_object->addFunction("getSize",&MyObject::getSize);
	my_object->addFunction("setMass",&MyObject::setMass);
	my_object->addFunction("getMass",&MyObject::getMass);
	Interpreter::addExternClass("MyObject",my_object);
	
	// export functions
	Interpreter::addExternFunction("MakeMyObject",MakeExternFunction(&MakeMyObject));
	Interpreter::addExternFunction("DeleteMyObject",MakeExternFunction(&DeleteMyObject));
	
	AppSystemLogic system_logic;
	AppWorldLogic world_logic;
	AppEditorLogic editor_logic;
	
	Unigine::EnginePtr engine(UNIGINE_VERSION,argc,argv);
	
	engine->main(&system_logic,&world_logic,&editor_logic);
	
	return 0;
}

Unigine Script Side

And here is how the exported class can be used in UnigineScript (see the description of the yield control statement for better understanding of the example):

Source code (UnigineScript)
/*
 */
MyObject object_0;
MyObject object_1;

/*
 */
void object_info(MyObject object) {
	
	// object parameters
	vec3 size = object.getSize();
	float mass = object.getMass();
	
	log.message("size is: (%g,%g,%g), mass is: %g\n",size.x,size.y,size.z,mass);
}

/*
 */
int init() {
	
	/////////////////////////////////
	
	log.message("\n");
	
	// make script constructed object
	object_0 = new MyObject(vec3(1.0f,2.0f,3.0f),10.0f);
	
	// make extern constructed object
	object_1 = MakeMyObject(vec3(4.0f,5.0f,6.0f),100.0f);
	
	/////////////////////////////////
	
	// show console
	engine.console.setActivity(1);
	
	return 1;
}

/*
 */
int shutdown() {
	
	// delete external constructed object
	DeleteMyObject(object_1);
	
	return 1;
}

/*
 */
int update() {
	
	/////////////////////////////////
	// first update
	/////////////////////////////////
	
	log.message("\n");
	
	// parameters
	object_info(object_0);
	object_info(object_1);
	
	yield 1;
	
	/////////////////////////////////
	// second update
	/////////////////////////////////
	
	log.message("\n");
	
	// save and restore world state
	engine.console.run("state_save && state_restore");
	
	yield 1;
	
	/////////////////////////////////
	// third update
	/////////////////////////////////
	
	log.message("\n");
	
	// parameters
	object_info(object_0);
	object_info(object_1);
	
	yield 1;
	
	/////////////////////////////////
	
	return 1;
}

Output

The following result will be printed into the console:

Output
MyObject::MyObject((1,2,3),10): called
MyObject::MyObject((4,5,6),100): called

size is: (1,2,3), mass is: 10
size is: (4,5,6), mass is: 100

Unigine~# state_save && state_restore
Saving "data/serialization" world state to "save/quicksave.save" file

MyObject::saveState(): called
MyObject::savePointer(): called
MyObject::~MyObject(): called
MyObject::~MyObject(): called

Restoring "data/serialization" world state from "save/quicksave.save" file

MyObject::MyObject(): called
MyObject::restoreState(): called
MyObject::MyObject(): called
MyObject::restorePointer(): called

size is: (1,2,3), mass is: 10
size is: (4,5,6), mass is: 100

Unigine~# quit

MyObject::~MyObject(): called
MyObject::~MyObject(): called
Last update: 2017-12-21