UnigineEditor
Interface Overview
Assets Workflow
Settings and Preferences
Working With Projects
Adjusting Node Parameters
Setting Up Materials
Setting Up Properties
Landscape Tool
Using Editor Tools for Specific Tasks
Extending Editor Functionality
Programming
Setting Up Development Environment
Usage Examples
UnigineScript
C++
C#
UUSL (Unified UNIGINE Shader Language)
File Formats
Rebuilding the Engine 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
Objects-Related Classes
Networking Functionality
Pathfinding-Related Classes
Physics-Related Classes
Plugins-Related Classes
IG Plugin
CIGIConnector Plugin
Rendering-Related Classes

Working with Smart Pointers

Some Basics#

In UNIGINE instances of C++ API classes (such as: Node, Mesh, Body, Image and so on...) only store pointers to instances of internal C++ classes, they cannot be created and deleted via the standard new / delete operators. So they should be declared as smart pointers (Unigine::Ptr) that allow you to automatically manage their lifetime. UNIGINE has its own optimized memory allocator for faster and more efficient memory management. Each smart pointer stores a reference counter, i.e. how many smart pointers are pointing to the managed object. Reference counting is thread-safe, as modifying the counter is an atomic operation.

Not all methods of Engine's internal C++ classes are exposed to the user, some of them are used by the Engine only. These are specific functions that either are used only for some internal purposes, or cannot be given to the user "as is". So, to filter out such methods an intermediate level, called interface, is used. This interface stores a pointer to the instance of the Engine's internal C++ class.

To create an instance of an internal class we should declare a smart pointer for it and call the create() method - class constructor - providing construction parameters if necessary.

Source code (C++)
// instantiating an object of an internal class
<Class>Ptr instance = <Class>::create(<construction_parameters>);

Lifetime#

We can divide all objects into two groups based on the way their lifetime is managed:

  • ownership objects (Image, Texture, Mesh, Tileset, etc.) these objects are managed in accordance with reference counter: when the last smart pointer is destroyed, the counter goes to 0, and the managed object is then automatically deleted. In this case it is assumed that the object is no longer needed (the Engine doesn’t know anything about it, and the user has got no pointer to be able to use it) and, therefore, it is deleted. (e.g. such objects declared within a scope will be automatically deleted when leaving the scope).
    Source code (C++)
    // creating a new image
    ImagePtr img = Image::create();
    
    // now two pointers point to our image (reference counter increment)
    ImagePtr img2 = img;
    
    // removing the image (as both pointers no longer point to it and reference counter is zero)
    img2 = img = nullptr;
  • non-ownership objects (nodes, widgets, materials, properties, etc.) - these objects interact with the Engine and become managed by it since the moment of their creation (they are engaged in the main loop, can be retrieved by names, etc.). The lifetime of these objects is not determined by the reference counter, they provide the mechanism of weak references, so you can check whether an object was deleted or not. To delete such objects you should use deleteLater() or a corresponding manager’s method (e.g.: Materials::removeMaterial()).
    Source code (C++)
    NodePtr node;
    void somefunc1(){
      // creating a new dummy node
      node = NodeDummy::create();
    }
    
    void somefunc2(){
      // checking whether the node exists
      if (node)
        Log::message("The node is alive\n");
    	
      // deleting the node
      node.deleteLater();
    }

Instead of managing references for nodes manually now you can simply choose lifetime management policy for it:

  • World-managed - in this case a node shall be deleted when the world is closed. This policy is used by default for each new node.
  • Engine-managed - in this case the node shall be deleted automatically on Engine shutdown (can be used for nodes that should be kept when changing worlds).
Notice
Lifetime of each node in the hierarchy is defined by its root (either parent or possessor). Setting lifetime management type for a child node different from the one set for the root has no effect.

Upcasting and Downcasting#

Sometimes (e.g. when we use World::getNodeByName(), etc. ) we get a NodePtr value, which is a pointer to the base class, but in order to perform operations with certain object (e.g. ObjectMeshDynamicPtr) we need to perform downcasting (i.e. convert from a pointer-to-base to a pointer-to-derived).the following methods were introduced:

  • static_ptr_cast - static casting without any checks (in accordance with C++ semantics)
  • checked_ptr_cast - downcasting with automatic type checking performed
  • dynamic_ptr_cast - dynamic casting (in accordance with C++ semantics)

Sometimes you may also need to perform upcasting (i.e. convert from a pointer-to-derived to a pointer-to-base) this type of casting is performed automatically.

Notice
Implicit type conversion for Unigine smart pointers is not allowed.

The code samples below demonstrate the points described above.

Example 1

Source code (C++)
#include <UnigineEditor.h>
using namespace Unigine;
/* .. */

// find a pointer to node by a given name
NodePtr baseptr = World::getNodeByName("my_meshdynamic");

// cast a pointer-to-derived from pointer-to-base with automatic type checking
ObjectMeshDynamicPtr derivedptr = checked_ptr_cast<ObjectMeshDynamic>(baseptr);

// static cast: pointer-to-derived (File) from pointer-to-base (Stream)
if(stream->getType() == Stream::FILE)
    FilePtr file = static_ptr_cast<File>(stream);

// upcast to the pointer to the Object class which is a base class for ObjectMeshDynamic
ObjectPtr object = derivedptr;

// upcast to the pointer to the Node class which is a base class for all scene objects
NodePtr node = derivedptr;

Deleting Objects#

A smart pointer has a clear() destructor intended for ownership objects clearing the pointer and deleting the object only in case if the smart pointer calling this method is the last one pointing to the object (interface, in this case). This should be taken into account.

As for non-ownership objects, they can be deleted via one of the following methods:

  • deleteLater() - performs delayed deletion, in this case the object will be deleted during the next swap stage of the main loop (rendering of the object ceases immediately, but it still exists in memory for a while, so you can get it from its parent, for example). This method simplifies object deletion from a secondary thread, so you can call it and forget about the details, letting the Engine take control over the process of deletion, which can be used for future optimizations;
  • deleteForce() - performs immediate deletion, which might be necessary in some cases. Calling this method for main-loop-dependent objects (e.g., nodes) is safe only when performed from the Main thread.
Both these methods can be safely called more than once for a single object (as well as after an object has been deleted by the Engine) without causing a double deletion. So, no worries, call it whenever an object is no longer needed.
Notice
Although these two methods are available for all objects, they were not actually designed for ownership ones, as the major use case for them is counter-based lifetime management.

See Also#

  • For more information on the methods of the Ptr class, see Ptr class page.
Last update: 2020-03-12