PathEngine home previous: Passing Arraysnext: Working with the TestBed
Contents, Programmers Guide, Applying the SDK, Ownership and Lifetimes

Ownership and Lifetimes

Reference counts

PathEngine interface object lifetimes are managed through a reference counting mechanism.

Every time you obtain a pointer to an interface object through the API, a reference count associated with that object is incremented.

When you no longer need to use the object, you directly or indirectly call the object's release() method which decrements this reference count. The object is then only actually deleted when the reference count gets to zero.

When working with the C++ API, interface object pointers are supplied wrapped with std::unique_ptr, and a custom operator delete method calls release() for you. So, in this case, reference counting is automatic, and you should never need to worry about calling release() directly.

(Actually, the release() method is hidden, by renaming, in the C++ headers, since it would be dangerous for this to be called directly by the user.)

Reference relationships between API objects

Interface objects that need to refer to one another also use the same reference counting mechanism, and interface objects will therefore keep other interface objects 'alive' as necessary.

The ownership relationships between PathEngine interface objects form a directed acyclic graph, as follows.


Reference relationships between PathEngine interface objects.

This directed graph of reference relationships guarantee that references can never form cycles, which means that we get all the benefits of automatic lifetime management without the need for garbage collection, and without associated delays.

Note that iCollisionContext and iObstacleSet may each reference multiple 'contained' objects. (iCollisionContext references zero or more iObstacleSets. iObstacleSet references zero or more iAgents.) iContentProcessing3D and iTestBed are 'special' interfaces, but conceptually both reference a root iPathEngine object nevertheless.

Asserting that an object will de destroyed

Sometimes you want to be sure that the object is actually destroyed at a certain point in your code. (Because you want to reuse the memory used by the object, for example, or because you want to ensure that any cleanup costs are only incurred at certain times.)

What you can do here is to assert that nothing else is holding a reference to a pointer, before your call to release, as follows:

// we expect the mesh to actually be destroyed, at this point
// (mesh is a unique_ptr<iMesh>)
    assert(!mesh->hasRefs());
    mesh = nullptr;

The idea is that, if some other part of your code is holding on to an API object pointer that directly or indirectly references the mesh, you want to know about this, as opposed to just ignoring the references and invalidating the API object (potentially leading to a crash later on).

With this approach you effectively get the advantages of deterministic resource management together with the advantages of safe pointer management.

Using C++ smart pointers to API objects

Scoped use of PathEngine API objects is straighforward, and will look something like the following:

{
    int32_t agentRadius = 80;
    int32_t array[] =
    {
		    -agentRadius, -agentRadius,
		    -agentRadius, agentRadius,
		    agentRadius, agentRadius,
		    agentRadius, -agentRadius,
	  };
    unique_ptr<iShape> shape = pathEngine.newShape(array, sizeof(array) / sizeof(*array));
    
    //.... do stuff with the shape
    
    //.... shape will then be deleted, releasing the last reference, on scope exit
}

Holding more than one pointer to an API object

You can also hold more than one pointer to an API object, and let PathEngine's internal reference counting mechanisms take care of cleaning everything up, when appropriate.

Since reference counts are managed internally by PathEngine (with intrusive reference counting), it's not actually necessary to use shared_ptr for this. Just use multiple unique_ptrs.

Use object addExternalRef() to add new references, as desired:

{
    int32_t agentRadius = 80;
    int32_t array[] =
    {
		    -agentRadius, -agentRadius,
		    -agentRadius, agentRadius,
		    agentRadius, agentRadius,
		    agentRadius, -agentRadius,
	  };
    unique_ptr<iShape> shape = pathEngine.newShape(array, sizeof(array) / sizeof(*array));
    
    {
        unique_ptr<iShape> helperShape = shape->addExternalRef();
        // (object internal reference count is set to 2, at this point)
           
        //.. do stuff with helperShape and/or shape here
    }
    // (object internal reference count is back to 1,
    //  and the object will be destroyed on scope exit,
    //  as before)
}

(For const objects, use addConstExternalRef().)

A useful paradigm can be to pass around C++ references to objects, when they are known to be in scope for the lifetime of called code. More persistent references can then be taken, as unique_ptr, from these C++ references, if this turns out to be necessary.

class ReferenceHoldingClass
{
    std::unique_ptr<iAgent> _agent;
public:
    ReferenceHoldingClass(iAgent& agent) :
     _agent(agent.addExternalRef())
    {
    }
    //.. other stuff
}
    
// actorAgent being passed by reference
// implies that this is guaranteed to remain
// alive by calling code
void SomeAILogic(
    iAgent& actorAgent
    //... other stuff
    )
{
    // some AI stuff
    
    {
        ReferenceHoldingClass foo(agent);
    
        // use foo
        
        // (foo can also live longer than calling scope,
        //  if necessary)
    }
    
    // more AI stuff
}

Thread safety

The PathEngine internal reference counting mechanism is thread safe, and it is ok for references to be made or released from multiple threads, for the same interface object.


Documentation for PathEngine release 6.04 - Copyright © 2002-2024 PathEnginenext: Working with the TestBed