Programming
Setting Up Development Environment
UnigineScript
High-Level Systems
C++
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
Rendering-Related Classes

Memory Management

This article dwells on objects management and communication between the Unigine scripts, external C++ classes and C++ part of Unigine engine (internal C++ classes and the Engine Editor).

See Also

Unigine Ownership System

Unigine has its own optimized memory allocator for faster and more efficient memory management. It works within a particular preallocated memory pool, which is shared by all of the Unigine modules. Custom C++ modules, which extend Unigine functionality with external C++ classes, use an underlying system-provided allocator, which has a separate memory pool.

Allocating memory, keeping track of those allocations and freeing memory blocks when they are no longer needed is mainly the task of managing objects. Unigine consists of several modules (including C++ modules mentioned above) each of which can establish the ownership of an object, meaning that it would be responsible for deallocating the memory when the object is no longer needed. By that, the object is accessible by all these modules. Ownership can also be released and transferred for another module to take responsibility and delete the unnecessary object at the right time. If failing to set only one owner that deallocates the object, memory leaks or even application crashes are imminent.

Here, objects are instances of internal C++ classes. The following scheme shows Unigine ownership system by the example of Node. The Engine Editor block is marked with * in order to show that at the current moment this module owns the node (the instance of the internal C++ class). Note that this scheme is done as an example, and any of the modules can become an owner of the internal instance and store a pointer to it.

Unigine Ownership System by Example of Node

Internal C++ classes. Instances of these classes are low-level built-in objects, that are actually created on the C++ side when a script or a C++ module declares them, such as: node (as in the example), mesh, body, image and so on. Such internal instances can be owned by one of the following modules:

  • Scripts. These are:
    • World script that contains world logic.
    • Editor script that wraps editor functionality and editor GUI.
    • System script that does system housekeeping and controls the system menu GUI.
    Scripts are written in the UnigineScript language and allow creating user classes.
    Notice
    An internal instance can be handled by any of the scripts (world, system or editor one), no matter whom it belongs to. The ownership matters when deletingit.
  • Engine Editor. It is a C++ part of Unigine engine. When the world is initialized, Engine Editor loads nodes from the world file and creates an array of pointers to their internal instances. Engine Editor nodes also appear on the Nodes panel as hierarchal list and can be adjusted in the virtual world in real-time. By default, Engine Editor owns all these nodes. Also ownership of a node can be transferred to Engine Editor from one of the scripts or from a C++ module.
  • API C++ classes are wrapper classes that hide implementation of internal C++ classes and store pointers to instances of these internal classes (internal instances, in other words). API C++ classes can be owners of the internal instances in the following cases:
    • These instances are created on the C++ side of the user application via the create() function.
    • Ownership of such instances is set manually via the grab() function of API C++ classes.
    To release ownership, the release() function is used.

    As instances of C++ API classes only store pointers to instances of internal C++ classes, they cannot be created and deleted via the standard new / deleteoperators. So they should be declared assmart pointers (Unigine::Ptr) that allow you to automatically manage their lifetime. If you delete a smart pointer, the instance of an API C++ class will be automatically deleted. In addition, this instance will delete the internal C++ object if the corresponding ownership is set via the grab() function.

There is also Engine World, which is also a C++ part of Unigine engine (do not confuse with the world script!). All the instances created via the new operator or the create() function are automatically added to Engine World. Engine World is responsible for loading a world with all its nodes, for managing a spatial tree and handling collisions and intersections of nodes. However, it doesn't take ownership of internal instances.

Managing Ownership

The basic principles of managing ownership of internal instances are the following:

  • Whoever creates an instance of an internal C++ class will automatically become the owner of this instance. Afterwards, the module that owns the internal instance can delete it. For example:
    • If you create an instance on the script side of the application via the newoperator, the script will become the owner and you will be able to delete the instance by using thedeleteoperator.
    • If you create an instance on the C++ side via the create() function, the C++ module will take ownership of the instance and you will be able to delete it via the destroy() function.
  • One instance of an internal C++ class should be owned only by one module to avoid double-freeing.
  • When transferring ownership of an instance, it must be released from the previous owner and assigned to a new one. The order of these operations is of no importance.
  • Never leave orphan or released instances without an assigned owner.

Orphan (unowned) instances of internal C++ classes are the result of calling the following functions:

  • All clone() functions in the library return new cloned instances that are not owned by any of the modules.
  • engine.world.loadNode()loads an orphan node.

Notice
Ownership is automatically reassigned in the following cases. That is why it is required to use class_remove()for created bodies, shapes or joints to release the script ownership.

Script Ownership

By default, any instance of an internal C++ class (a node, a mesh, an image and so on) created in a script is owned by this script:

Source code (UnigineScript)
NodeDummy node_1 = new NodeDummy(); // the script owns node_1
NodeDummy node_2 = new NodeDummy(); // the script owns node_2

// after adding node_2 as a child to node_1
// node_1 WON'T become an owner of node_2
node_1.addChild(node_2);

// only node_1 will be deleted and node_2 hierarchy will be updated
delete node_1;
However, if you assign a body to an object and call class_remove() for this body (as described above), such body will be owned by the object. So, when deleting the object, the body will be deleted too. The same goes for bodies and shapes and for bodies and joints.
Source code (UnigineScript)
ObjectDummy object = new ObjectDummy(); // the script owns the object
BodyRigid body = new BodyRigid(object); // both the script and the object own the body

// remove script ownership for the body
class_remove(body);

// both the object and its body will be deleted
delete object;

Handling Ownership of Internal Instances

Scripts can handle ownership of an instance of an internal C++ class using the following system functions:

  • class_append() assigns ownership of the internal instance to the current script module. All appended instances will be automatically deleted on the script shutdown; or they can be deleted using the deleteoperator. For example:
    Source code (UnigineScript)
    // clone an existing node. The cloned node will be orphaned
    Node clone = node.clone();
    // set script ownership for the node
    class_append(clone);
    // delete the cloned node later if necessary
    delete clone;
    Here, clone() returns a new node, which is orphaned. To prevent the memory leak, a script takes ownership and safely deletes it.
  • class_manage() indicates that reference counting should be performed for the internal instance. When the number of references that point to the instance reaches 0, the memory previously allocated for it is automatically deleted, thus freeing the developer from carefully managing the lifetime of pointed-to instances. Before calling this function, the internal instance should be appended to the script.
    Source code (UnigineScript)
    // create an image that will be automatically owned by the script
    Image image = new Image();
    // enable reference counting for the image
    class_manage(image);
    // the image will be deleted when there are no references left to it
    image = 0;
    Here, the image is automatically owned by the script, because it was created using a new operator. It will be deleted, because there are no references left to it.
  • class_release() removes all references to the instance of the internal C++ class. It removes even the smallest memory leaks.
    Source code (UnigineScript)
    // create a new node that will be automatically owned by the script
    NodeDummy node = new NodeDummy();
    // delete the reference to the node
    node = class_release(node);
  • class_remove() releases the ownership of the instance. It needs to be reassigned to any module (before or after it has been released) not to become orphaned. For example, it can be passed the Engine Editor to appear in the Nodes hierarchy to be adjusted in real-time.
    Source code (UnigineScript)
    Body body = class_remove(new BodyRigid(object)); // bodies are automatically managed by objects they are assigned to
    ShapeSphere shape = class_remove(new ShapeSphere(body,radius)); // shapes are automatically managed by bodies they are assigned to
    JointFixed joint = class_remove(new JointFixed(body,body0)); // joints are automatically managed by bodies they are assigned to
  • class_cast() converts the pointer to an internal instance of a given type into another type.
    Notice
    Conversions of the pointer are unsafe and allow specifying any type, because neither the pointer nor its type is checked.
    When converting into the other type a new internal instance isn't constructed, so you cannot delete it via the deleteoperator.
    Source code (UnigineScript)
    // get a node
    Node node = engine.world.getNode(id);
    // cast the node to the ObjectMeshStatic type
    ObjectMeshStatic mesh = class_cast("ObjectMeshStatic",node);
    // call a member function of the class to which the node has been casted
    string name = mesh.getMeshName();
    If you try to delete the mesh declared in the example above via the deleteoperator, you will get an error. However, it you delete the node, the mesh will also be deleted.

Handling Ownership of Hierarchical Nodes

There is also a group of functions that allow you to safely handle hierarchical nodestogether with all of their children. They can be found in thedata/core/unigine.h file of Unigine SDK.

  • Node node_append(Node node)registers script ownership of the given node and its children.
    Source code (UnigineScript)
    // load a node. The node will be automatically owned by the script
    Node node = engine.world.loadNode("my.node");
    
    // ...
    // release script ownership of the instance
    
    // add an orphan child node to the loaded node
    node.addChild(class_remove(new NodeDummy()));
    // set script ownership for both the parent and the child nodes
    node_append(node);
    Here, the script takes ownership of both my node and its child node (NodeDummy).
    Notice
    The situation presented in the example is created intentionally in order to show that node_append() sets script ownership for the whole node hierarchy. So, for example, you don't need to call class_remove() before adding a child to a node in your application.
  • Node node_remove(Node node)releases script ownership of the given node and its children and casts them to their types. This function should be used, for example, to set Engine Editor ownership of the node owned by the script.
    Notice
    Do not forget to set another owner for an orphan node (if any).
    Source code (UnigineScript)
    // create a static mesh that will be automatically owned by the script
    ObjectMeshStatic mesh = new ObjectMeshStatic("samples/common/meshes/statue.mesh");
    // release script ownership and set Engine Editor as a new owner
    engine.editor.addNode(node_remove(mesh));
  • void node_delete(Node node)deletes the parent node together with its children. Before calling this function, the node should beappended for the script to take ownership.
    Source code (UnigineScript)
    // load a node. The node will be automatically owned by the script
    Node node = engine.world.loadNode("my.node");
    
    // ...
    // release script ownership of the node
    
    // add an orphan child node to the loaded node
    node.addChild(class_remove(new NodeDummy()));
    // set script ownership for the node and its child
    node_append(node);
    
    // perform something here
    
    // delete the node and its child
    node_delete(node);
    Here, both the node and its child will be deleted.
    Notice
    The situation presented in the example is created intentionally in order to show that node_delete() deletes the whole node hierarchy. So, for example, you don't need to call class_remove() before adding a child to a node in your application.
  • Node node_clone(Node node)clones the node. Herewith, a new instance of the internal C++ class is created. Like for all otherclone() functions, the created instance is orphaned and its ownership is to be passed to some module. For example, this function is useful when the node is an object with a body and joints. A usual clone() function does not recreate connected joints.
    Source code (UnigineScript)
    // create a node that is automatically owned by the script
    ObjectMeshStatic mesh = new ObjectMeshStatic("samples/common/meshes/statue.mesh");
    // set script ownership for the cloned node
    node_append(node_clone(mesh));
    Here, the first mesh object is automatically owned by the script as it is created using new operator, while the copied one should be appended manually.

    Functions for non-hierarchicalnodes:

  • Node node_cast(Node node)allows you to safely convert the given base node to its derived type.
    Notice
    When converting into the other type a new internal instance isn't constructed, so you cannot delete it via the deleteoperator.
    Source code (UnigineScript)
    // get a node that will be automatically owned by the script
    Node node = engine.world.getNode(id);
    // cast the node to ObjectMeshStatic
    ObjectMeshStatic mesh = node_cast(node);
    // call a member function of the ObjectMeshStatic class
    int num_targets = mesh.getNumSurfaceTargets(0);
    After downcasting, the node can call member functions of the ObjectMeshStatic class. If you try to delete the mesh declared in the example above via the deleteoperator, you will get an error as there is no such internal instance. However, if you delete the node, the mesh will also be deleted.

Pass Ownership Between Scripts

Ownership of an internal instance can be passed between the scripts: world, system and editor ones. For the scripts to interact, use the following functions:

  • One of the engine.world.call()to call functions of the world script.
  • One of the engine.system.call() to call functions of the system script.
  • One of the engine.editor.call()to call functions of the editor script.

For example, you can create an internal instance in the world script and then pass its ownership to the system script:

  1. Create an instance in the world script and release world script ownership of this object, so that the system script can grab its ownership. Then pass the object to the system script function via the engine.system.call() function.
    Source code (UnigineScript)
    int init() {
    	
    	// 1. Create a new dummy object. The internal instance to which the dummy object points will be created automatically
    	ObjectDummy dummy = new ObjectDummy();
    	// 2. Release world script ownership of this object
    	class_remove(dummy);
    	// 3. Pass the object to the system script function
    	engine.system.call("receive_dummy_ownership",dummy);
    	
    	return 1;
    }
  2. In the system script function, set the system script ownership for the received orphan instance. Now this instance can be deleted using the deleteoperator; otherwise, the instance will be automatically deleted on the system script shut down.
    Source code (UnigineScript)
    void receive_dummy_ownership(ObjectDummy dummy) {
    	// 1. Set system script ownership of the dummy object constructed in the world script
    	class_append(dummy);
    	
    	// 2. The system script can delete the internal instance to which the dummy object points. Or it will be deleted on the system script shut down 
    	delete dummy;
    }

Engine Editor Ownership

As it was said, Engine Editor owns the nodes loaded from the world file. When the world is unloaded, the nodes will be automatically deleted, thus freeing allocated memory.

  • engine.editor.addNode()establishes Engine Editor ownership of the node. Before that, the instance should be released of other owners.
    Source code (UnigineScript)
    // create a static mesh that will be automatically owned by the script
    ObjectMeshStatic mesh = new ObjectMeshStatic("samples/common/meshes/statue.mesh");
    // release script ownership and set Engine Editor as a new owner
    engine.editor.addNode(node_remove(mesh));
    After the script releases ownership of the mesh established via the new operator, it can be handled by Engine Editor. If you add a node created via the new operator to Engine Editor via engine.editor.addNode()withoutreleasing this node of script ownership, the engine will crash on its shut down:
    Source code (UnigineScript)
    engine.editor.addNode(new ObjectDummy()); // crash will occur as both the script and Engine Editor own the object
    engine.editor.addNode(node_remove(new ObjectDummy())); // such code will work properly
  • engine.editor.removeNode()deletes the node that is owned by the Engine Editor.
    Source code (UnigineScript)
    // create a static mesh that will be automatically owned by the script
    ObjectMeshStatic mesh = new ObjectMeshStatic("samples/common/meshes/statue.mesh");
    // release script ownership and set Engine Editor as a new owner
    engine.editor.addNode(node_remove(mesh));
    
    // perform something here
    
    // delete the node
    engine.editor.removeNode(mesh);
    Here, a new mesh, which is created via the new operator and owned by the script, is released of script ownership. After that, it becomes orphaned and ownership is transfered to the Engine Editor, which safely deletes it.
  • engine.editor.releaseNode()allows releasing ownership of the node held by the Engine Editor and transfer it to some other module (for example, the script).
    Source code (UnigineScript)
    // create a static mesh that will be automatically owned by the script
    ObjectMeshStatic mesh = new ObjectMeshStatic("samples/common/meshes/statue.mesh");
    // release script ownership and set Engine Editor as a new owner
    engine.editor.addNode(node_remove(mesh));
    
    // perform something here
    
    // release Engine Editor ownership
    engine.editor.releaseNode(mesh);
    // set script ownership of the object
    node_append(mesh);
    Here, ownership of the node is passed from the script to the Engine Editor, which in turn releases it. After that, the node is free to be owned by the script again.

C++ Ownership

There are different variants to create and handle ownership of the internal instance:

  1. Create an instance of an internal C++ class by the script and pass it to be received by the function defined on the C++ side:
  2. Create a smart pointer to an instance of the C++ API class and pass it to the script:

Receive Instance as Smart Pointer

The first option is the following: a node that is created and handled by the world script is passed to the C++ function that receives a smart pointer.

  1. Create a custom function on the C++ side, which will receive a NodePtr smart pointer. Then a function needs to be registered with the Unigine interpreter, so that the script could call it in its runtime.
    Source code (C++)
    //main.cpp
    #include <UnigineEngine.h>
    #include <UnigineInterpreter.h>
    #include <UnigineInterface.h>
    
    /*
     */
    using namespace Unigine;
    
    /*
     */
    
    // 1. Create an external function that receives a smart pointer NodePtr
    void my_node_set(NodePtr node) {
    	// 1.1. Call the node member function exposed through the C++ API
    	node->setTransform(translate(vec3(1.0f,2.0f,3.0f)));
    }
    
    /*
     */
    int main(int argc,char **argv) {
    	
    	// 2. Register the function for export into Unigine
    	Interpreter::addExternFunction("my_node_set",MakeExternFunction(&my_node_set));
    	
    	// 3. Initialize the engine. It will be shut down automatically
    	EnginePtr engine(UNIGINE_VERSION,argc,argv);
    	
    	// 4. Enter the engine main loop
    	engine->main();
    	
    	return 0;
    }
  2. Create a node in the world script and call the registered C++ function. The ownership of the node will belong to the script, so only it could call the deleteoperator to destroy the node.
    Source code (UnigineScript)
    // world script (myworld.cpp)
    
    int init() {
    	// 1. Create a new dummy node, so it could be passed to the external C++ function
    	Node node = new NodeDummy();
    	// 2. Call the registered C++ function
    	my_node_set(node);
    	
    	// 3. Delete the node if no longer needed; otherwise, the node will be automatically deleted by the script shutdown
    	delete node;
    
    	return 1;
    }

Receive Instance as Smart Pointer and Grab Its Ownership

In this case, a node is passed to the C++ function that receives it as a smart pointer and this function grabs ownership of the node, so that it becomes responsible for its deleting.

  1. Create a custom function on the C++ side, which will receive a NodePtr smart pointer and grab its ownership. Then a function needs to be registered with Unigine interpreter, so that the script could call it in its runtime.
    Source code (C++)
    #include <UnigineEngine.h>
    #include <UnigineInterpreter.h>
    #include <UnigineInterface.h>
    
    /*
     */
    using namespace Unigine;
    
    /*
     */
    
    // 1. Create an external function that receives a script node as a smart pointer NodePtr
    void my_node_set(NodePtr node) {
    	
    	// 1.1. Grab ownership of the received pointer
    	node->grab();
    	
    	// 1.2. Call the node member function exposed through the C++ API
    	node->setTransform(translate(dvec3(1.0f,2.0f,3.0f)));
    	
    	// 1.3. Delete the node if necessary
    	node->destroy();
    }
    
    /*
     */
    int main(int argc,char **argv) {
    	
    	// 2. Register the function for export into Unigine
    	Interpreter::addExternFunction("my_node_set",MakeExternFunction(&my_node_set));
    	
    	// 3. Initialize the engine. It will be shut down automatically
    	EnginePtr engine(UNIGINE_VERSION,argc,argv);
    	
    	// 4. Enter the engine main loop
    	engine->main();
    	
    	return 0;
    }
  2. Create a node in the world script, release script ownership and call the registered C++ function. The ownership of the node will belong to the external function, so you don't need to call delete operator to destroy the node.
    Notice
    Script ownership must be released either on the script or C++ side. Otherwise, there will be 2 owners of the node and it will cause engine crash.
    Source code (UnigineScript)
    int init() {
    		
    	// 1. Create a new dummy node, so it could be passed to the C++ function
    	Node node = new NodeDummy();
    	// 2. Release script ownership so that it can be grabbed by the C++ function
    	class_remove(node);
    	// 3. Call the registered C++ function
    	my_node_set(node);
    	// 4. Check the result of function execution
    	log.message("%s\n",typeinfo(node.getTransform()));
    	
    	return 1;
    }

Receive Instance of Specific Type

This variant is similar to the first one: a node that is created and handled by the world script is passed to a C++ function that receives it as a smart pointer of a specific type. Here, "specific type" means a C++ API class that is wrapped with a smart pointer (for example, ObjectMeshStaticPtr, DecalDefferedMeshPtr and so on).

  1. Create a custom function on the C++ side, which will receive an ObjectMeshDynamicPtr smart pointer. The external function should be registered to be called by the script.
    Source code (C++)
    //main.cpp
    #include <UnigineEngine.h>
    #include <UnigineInterpreter.h>
    #include <UnigineObjectMeshDynamic.h>
    #include <UnigineInterface.h>
    #include <UnigineLog.h>
    
    /*
     */
    using namespace Unigine;
    
    /*
     */
    // 1.0. Create an external function that receives a smart pointer ObjectMeshDynamicPtr
    void my_object_update(ObjectMeshDynamicPtr object,float time) {
    	
    	// 1.1. Call the member function of the ObjectMeshDynamic
    	object->updateSurfaceBegin(0);
    	object->updateSurfaceEnd(0);
    	Log::message("Surface indices was updated\n");
    }
    
    /*
     */
    int main(int argc,char **argv) {
    	
    	// 2. Register the function for export into Unigine
    	Interpreter::addExternFunction("my_object_update",MakeExternFunction(&my_object_update));
    	
    	// 3. Initialize the engine. It will be shut down automatically
    	EnginePtr engine(UNIGINE_VERSION,argc,argv);
    	
    	// 4. Enter the engine main loop
    	engine->main();
    	
    	return 0;
    }
  2. On the script side, create an object and pass it to the external function. After that, the registered external C++ function call receive it, when called by the script. Ownership remains by the script, so it can delete the object.
    Source code (UnigineScript)
    // world script (myworld.cpp)
    
    int init() {
    	// 1. Create a new ObjectMeshDynamic
    	Object object = new ObjectMeshDynamic();
    	// 1.1. Call the member function
    	object.setMaterial("mesh_base","*");
    
    	// 3. Call the registered external C++ function
    	my_object_update(object,engine.game.getTime());
    	
    	// 4. The script can delete the object, as it was the one that allocated it
    	delete object;
    
    	return 1;
    }

Receive Instance as Variable

The third variant is to create an internal instance in the script (for example, the image) and pass it to the C++ function that receives a variable. The external function is called by the script thereafter.

  1. Create a custom function on the C++ side, which will receive a variable. The variable is cast to the ImagePtr smart pointer type using the dedicated function getImage(). After that, the C++ function should be registered to be called by the script.
    Notice
    You should set the script runtime when calling the getImage() function: pass the pointer to the current interpreter via the Unigine::Interpreter::get() function.
    Source code (C++)
    //main.cpp
    #include <UnigineEngine.h>
    #include <UnigineInterpreter.h>
    #include <UnigineImage.h>
    #include <UnigineInterface.h>
    
    /*
     */
    using namespace Unigine;
    
    /*
     */
    // 1. Create an external function that receives a variable
    const char* my_image_get(const Variable &v) {
    
    	// 1.1. Cast the received variable to the ImagePtr type
    	ImagePtr image = v.getImage(Interpreter::get());
    	
    	// 1.2. Call the image member functions exposed through C++ API
    	return image->getFormatName();
    }
    
    /*
     */
    int main(int argc,char **argv) {
    	
    	// 2. Register the function for export into Unigine
    	Interpreter::addExternFunction("my_image_get",MakeExternFunction(&my_image_get));
    	
    	// 3. Initialize the engine. It will be shut down automatically
    	EnginePtr engine(UNIGINE_VERSION,argc,argv);
    	
    	// 4. Enter the engine main loop
    	engine->main();
    	
    	return 0;
    }

    There also exists another way to cast the variable into the image pointer type using the VariableToType() function:

    Source code (C++)
    //main.cpp
    ...
    // 1. The same external function
    const char* my_image_get(const Variable &v) {
    
    	// 1.1. Another way of casting the variable value into ImagePtr:
    	ImagePtr image = VariableToType<ImagePtr>(Interpreter::get(),v).value;
    
    	return image->getFormatName();
    }
    ...
  2. On the script side, the image needs to be created and simply passed to the C++ function. The image will be converted into the ImagePtr smart pointer automatically.
    Source code (UnigineScript)
    //world script (myworld.cpp)
    int init() {
    
    	// 1. Create a new image
    	Image image = new Image();
    	// 1.1. Specify parameters of the image and fill it with black color
    	image.create2D(256,256,IMAGE_FORMAT_R8);
    	
    	// 2. Call the registered external function and simply pass the image to it
    	my_image_get(image);
    	
    	// 3. The script can delete the image explicitly, if necessary
    	delete image;
    	
    	return 1;
    }

Create and Pass Instance as Smart Pointer

Smart pointers allow the C++ function not only to receive instances of internal C++ classes created by the script, but also create such instances.

Notice
You cannot create and pass an instance of a C++ API class as a smart pointer, as instances of C++ API classes cannot be created or deleted via the standard newanddeleteoperators: such instances must be declared using smart pointers only.

Here, a script calls an external function that creates a new image using ImagePtr.

  1. In the external C++ function, declare ImagePtr, call API function create()and pass the pointer to the script. However, if you just pass the pointer, it will be dangling, i.e. it will not point to the valid image object as it isn't visible outside the external function scope. To avoid it, you should pass ownership of the pointer to the script via therelease()function. Then a custom function is registered to be called on the script side.
    Source code (C++)
    //main.cpp
    #include <UnigineEngine.h>
    #include <UnigineInterpreter.h>
    #include <UnigineImage.h>
    #include <UnigineInterface.h>
    
    /*
     */
    using namespace Unigine;
    
    /*
     */
    
    // 1. Create an external function that returns an ImagePtr smart pointer
    ImagePtr my_image_create_0() {
    	// 1.1. Declare an image smart pointer
    	ImagePtr image;
    	
    	// 1.2. Create an image in the Unigine memory pool through the ImagePtr smart pointer
    	image = Image::create();
    	// 1.3. Specify parameters of the image and fill it with black color
    	image->create2D(128,128,Image::FORMAT_RG8);
    	// 1.4. Pass ownership of the pointer to the script
    	image->release();
    	
    	return image;
    }
    
    /*
     */
    int main(int argc,char **argv) {
    	
    	// 2. Register the function for export into Unigine
    	Interpreter::addExternFunction("my_image_create_0",MakeExternFunction(&my_image_create_0));
    	
    	// 3. Initialize the engine. It will be shut down automatically
    	EnginePtr engine(UNIGINE_VERSION,argc,argv);
    	
    	// 4. Enter the engine main loop
    	engine->main();
    	
    	return 0;
    }
  2. Call the registered C++ function. The image it returns can be simply passed to the script, as conversion from the ImagePtr into the Image will be done automatically. As ownership of the image has been passed from the external function to the script, it can be deleted by the script any time (or it will be automatically deleted on engine shut down).
    Source code (UnigineScript)
    //world script (myworld.cpp)
    int init() {
    	
    	// 1. Call the external function in the script
    	Image image = my_image_create_0();
    	// 2. Set the script ownership for the image
    	class_append(image);
    	
    	// 3. The script will automatically handle the ImagePtr returned by the C++ function as a simple image
    	log.message("%s\n",image.getFormatName());
    
    	// 4. Delete the image if necessary
    	delete image;
    	
    	return 1;
    }

Create and Pass Instance as Variable

In this variant, the external function creates a new image as a variableand then the script calls this external function.

  1. In the C++ function, declare an ImagePtr, call the API function create()to create a smart pointer, which will allocate an internal Unigine image, and set it to the variable using the dedicated functionsetImage().
    Notice
    You should set the script runtime when calling the setImage() function: pass the pointer to the current interpreter via the Unigine::Interpreter::get() function.
    Source code (C++)
    //main.cpp
    #include <UnigineEngine.h>
    #include <UnigineInterpreter.h>
    #include <UnigineImage.h>
    #include <UnigineInterface.h>
    
    /*
     */
    using namespace Unigine;
    
    /*
     */
    
    // 1. Create an external function that returns a variable
    Variable my_image_create_1() {
    	// 1.1. Declare an image smart pointer
    	ImagePtr image;
    	
    	// 1.2. Create an image in the Unigine memory pool
    	image = Image::create();
    	// 1.3. Specify parameters of the image and fill it with black color
    	image->create2D(128,128,Image::FORMAT_RG8);
    	
    	// 1.4. Define the variable
    	Variable v;
    	// 1.5. Set the image smart pointer to the variable
    	v.setImage(Interpreter::get(),image);
    	
    	return v;
    }
    
    /*
     */
    int main(int argc,char **argv) {
    
    	// 2. Register the function for export into Unigine
    	Interpreter::addExternFunction("my_image_create_1",MakeExternFunction(&my_image_create_1));
    	
    	// 3. Initialize the engine. It will be shut down automatically
    	EnginePtr engine(UNIGINE_VERSION,argc,argv);
    	
    	// 4. Enter the engine main loop
    	engine->main();
    	
    	return 0;
    }

    Another way to set the ImagePtr smart pointer to the variable is to use the TypeToVariable function:

    Source code (C++)
    //main.cpp
    ...
    // 1. The same external function
    Variable my_image_create_1() {
    	
    	image = Image::create();
    	image->create2D(128,128,Image::FORMAT_RG8);
    
    	// 1.3. Another way of setting the image smart pointer to the variable:
    	Variable v = TypeToVariable<ImagePtr>(Interpreter::get(),image).value;
    	
    	return v;
    }
    ...
  2. A script calls the registered C++ function. After that, it handles the returned variable as a simple image, because conversion is done automatically. Remember, the script cannot delete the image, as the ownership belongs to the external function. The smart pointer will be automatically released when the reference count reaches zero.
    Source code (UnigineScript)
    //world script (myworld.cpp)
    int init() {
    	
    	// 1. Call the external function in the script
    	Image image = my_image_create_1();
    	
    	// 2. The script automatically handles the returned ImagePrt as a simple image
    	log.message("%s\n",image.getFormatName());
    	
    	return 1;
    }

Manage Script Ownership from C++ Side

If an instance has been created in the script and then passed as a variable to the C++ function, you can handle script ownership from this function by using the following methods of the Unigine::Variable class:

  • class_append()
  • class_manage()
  • class_remove()
  • class_release()

These functions receive a pointer to the required context, which allows you to correctly set ownership of the required script. To get the right context, use the following functions:

  • Unigine::Engine::getWorldInterpreter() to manage world script ownership of the pointer.
  • Unigine::Engine::getSystemInterpreter() to manage system script ownership of the pointer.
  • Unigine::Engine::getEditorInterpreter() to manage editor script ownership of the pointer.

Setting the context is required as we perform conversion between external class variables and script ones.

Notice
When calling the functions listed above, the corresponding script should already be loaded. Otherwise, NULL will be returned.

For example:

  1. Create a custom function on the C++ side, which will receive a variable. Release script ownership of the received variable.
    Source code (C++)
    #include <UnigineEngine.h>
    #include <UnigineInterpreter.h>
    #include <UnigineImage.h>
    #include <UnigineInterface.h>
    
    /*
     */
    using namespace Unigine;
    
    /*
     */
    // 1. Create an external function that receives a variable
    const char* change_owner(const Variable &v) {
    	
    	// 1.1 Release script ownership of the received variable
    	Engine *engine = Engine::get();
    	v.removeExternClass(engine->getWorldInterpreter()); // the world script context should be set, as removeExternClass() requires a script environment
    
    	// 1.2. Cast the received variable to the ImagePtr type
    	ImagePtr image = v.getImage(Interpreter::get());
    	
    	// 1.3. Set C++ ownership for the image
    	image->grab();
    	
    	// 1.4. Call the image member functions exposed through C++ API
    	return image->getFormatName();
    }
    
    /*
     */
    int main(int argc,char **argv) {
    	
    	// 2. Register the function for export into Unigine
    	Interpreter::addExternFunction("change_owner",MakeExternFunction(&change_owner));
    	
    	// 3. Initialize the engine. It will be shut down automatically
    	EnginePtr engine(UNIGINE_VERSION,argc,argv);
    	
    	// 4. Enter the engine main loop
    	engine->main();
    	
    	return 0;
    }
  2. On the script side, create an image and pass it to the external C++ function. As the script ownership is released in the external function, there is no need to delete the image here.
    Source code (UnigineScript)
    int init() {
    
    	// 1. Create a new image
    	Image image = new Image();
    	// 1.1. Specify parameters of the image and fill it with black color
    	image.create2D(256,256,IMAGE_FORMAT_R8);
    	
    	// 2. Call the registered external function and simply pass the image to it
    	log.message("%s\n",change_owner(image));
    }
Last update: 2017-07-03