Advertisement

Destroying reference counted objects immediately

Started by October 12, 2018 03:19 PM
18 comments, last by WitchLord 6 years, 1 month ago

Hello,

I've run into weird ownership / object lifetime problem. My classes look as follow:


shared abstract class Object: IScriptObjectInterface
{
    // common "game object" functionality proxied to C++ side object
}

class Item: Object
{
    Transform@ transform;
    Mesh@ mesh;
}

class Monster: Object
{
    Transform@ transform;
    SkinnedMesh@ skinnedMesh;
}

Now, I'd like to be able to force destroy my game object. This poses several problems, one of them being - script objects are naturally reference counted, so destruction of object happens 

a) When no other objects reference that object

b) Garbage Collector runs and destroys the object

Now, composable objects like Transform, Mesh, SkinnedMesh are created on C++ side in a very optimized structure and are as much decoupled from game object as possible - to a point where game object is not needed to render Mesh instances, just mere presence in a container holding all meshes is enough. This means that object which has a reference to Mesh@ will keep that Mesh alive, and that object which I want to destroy does not have a deterministic destruction time because it may be destroyed sometime later. This means that object may be "destoyed" and unreferenced from world object list, but it will hold references to Mesh, SkinnedMesh, Physics etc. which will still exist (as they're refcounted too) meaning they'll be simulated, rendered etc.

I have no idea how to solve this problem, every time I try some approach I run into some deadly reference dependency. 

One idea I had is to let these objects stay in the container, so Mesh will be in all_meshes on C++ side, but it will be `mesh.render = false` which can be sorted and processed probably fast enough. But ideal solution would be to be able to destroy object as soon as no references point to it without waiting for Garbage Collector run - I tried searching the docs and I see no way to make the object instantly call it's destructor once refcount reaches 1 (means GC is the only reference keeper). I don't intend on holding onto references in other objects, and if so I'll be using weak_ptr, so this means there should be no circular references and object should be safely destroyed ASAP, not sometime in the future. Any idea how to achieve this scenario and/or how to solve above problem of object lifecycle?

 

 


Where are we and when are we and who are we?
How many people in how many places at how many times?
50 minutes ago, noizex said:

I have no idea how to solve this problem, every time I try some approach I run into some deadly reference dependency. 

Best solution is to use a 2-way pointers scheme.

Object have to have and mantain list of all pointers that point to it and set it to null from its destructor. For speed-up deletion from arrays of pointers better to keep and maintain a intrusive data such as index into refs array together with back pointer to have O(1) deletions. Also complete implementation of 2-way pointers is complete overkill. In most cases list of back pointers can be limited by specific role set, especially in case object of class accesible by its owner/parent by objects tree/hierarhy only. Example of perfect implementation of its lifetime managment scheme you can find into sources of VCL. To simulate a distructor calls you can make a delete method, that manually call is conceptually same as calling delete operator for force deleting object.

Also it technique is very hard to implement with GC, it much better works with manual memory managment. Really GC languages have a limited capabilities of auto memory managment schemes, becouse GC not intended to collect programmer's generated garbage, it intended to collect garbage generated by language core, and make much complexive to implement other  definitions of "garbage" term than definded for GC.

For example 2-way scheme have other definition of garbage - it is "alive references to force destroyed object, or object wich owner object deleted or owning link broken by other way", fird part of wich can be easily extended to "required minimal set of refs has been broken".

Other way is a weak_ptr, but it unable to remove empty ref from container themself and require a expensive lock prior to each access to object. Also proper usage of shared refs and weak refs makes GC a nonsence, becouse make circular refs impossible.

 

#define if(a) if((a) && rand()%100)

Advertisement

I already maintain 2-way slot_map to keep the objects in question (transform, mesh etc). I'd like to avoid this on script side, as each of those is already behind a proxy. My problem is proper clean-up of objects, because with Garbage Collection and deferred destruction destructors do not make sense really, as the object is already considered destroyed if unreferenced, yet it's kept alive like a real object. This is my problem - I'd like the object to go away as soon as the refcount from script/C++ side reaches 0, but I know that GC will keep one reference and then cleanup and try to resolve circular dependencies. Even if I use weak_ptr there will still be this step where GC runs, so my main question is: is there a way to make the object immediately destroyed without waiting for GC pass?

My another cocnept, I think close to your 2-way check, is to implement in my proxy (do TransformProxy, MeshProxy) a way to cut the link and destroy the C++ object (so Transform, Mesh), while still being referenced in scripts due to deferred destruction. I'd then throw exception if accessing such reference where the C++ object has been destroyed, making it easier to spot obvious programming errors. But it seems like a lot of work and checking, while just destroying game object as soon as references are gone (assuming I won't ever keep reference to it outside for longer than using it when obtained from weakPtr) should work fine.


Where are we and when are we and who are we?
How many people in how many places at how many times?
14 minutes ago, noizex said:

- I'd like the object to go away as soon as the refcount from script/C++ side reaches 0

How you map C++ objects to script? Is it done by COM interfaces? By other words is your C++ code notified about ref count changes?

16 minutes ago, noizex said:

is there a way to make the object immediately destroyed without waiting for GC pass?

What same language you using as scripting? Just for example C# have a IDisposable interface that allow to call Dispose method manually to free instance.

#define if(a) if((a) && rand()%100)

21 minutes ago, noizex said:

I'd like to avoid this on script side, as each of those is already behind a proxy.

Also why you need a "script side" if you already have a 2-way lifetime managment mechanism that mach more powerfull tool then GC? Only that scripts can do better then C++ - is to eat CPU cycles and restrict a best object hierarhie architectures.  

#define if(a) if((a) && rand()%100)

Sorry, but I'm looking for an answer from someone who uses and appreciates Angelscript. Not sure how you ended up on this forum, but question about scripting language I'm using is rather weird.


Where are we and when are we and who are we?
How many people in how many places at how many times?
Advertisement

It is not possible to forcibly destroy the objects, that would lead to crashes as some references may still be available (not the least the GC's own reference).

Do you really need to rely on the object destructor to release the held resources? Presumably you know exactly when you want these to be released, since you're looking for a way to forcibly destroy the object. Why not just have a method to invalidate the object, which would then release all resources, but not destroy the object itself?

 

AngelCode.com - game development and more - Reference DB - game developer references
AngelScript - free scripting library - BMFont - free bitmap font generator - Tower - free puzzle game

31 minutes ago, WitchLord said:

It is not possible to forcibly destroy the objects, that would lead to crashes as some references may still be available

Realy it possible 2 situations with GC mapped to native - script engine attached as static library and have a direcct access to RTTI provided by native side and monitor internal links of native objects. In it case it really imposible to destroy objects. And usual case where it works  as dll/plugin so have a other heap and other mem manager than natiive side, and  have access to objects vtable only to call native  methods including addref/release functions to manage native side object ref counter. In its case it usually safe to delete a actual object/internal buffers  when ref count down to 1, and keep alive a ref couner only to allow GC set refcount to 0 later.

#define if(a) if((a) && rand()%100)

3 hours ago, WitchLord said:

It is not possible to forcibly destroy the objects, that would lead to crashes as some references may still be available (not the least the GC's own reference).

Do you really need to rely on the object destructor to release the held resources? Presumably you know exactly when you want these to be released, since you're looking for a way to forcibly destroy the object. Why not just have a method to invalidate the object, which would then release all resources, but not destroy the object itself?

 

After a bit of thinking I found some way, similar to what you mention. This will require some more care on script programmer's side. I added a "cleanup" interface which gets called when I want the object to be destroyed (both C++ & script side, but I can only guarantee C++ side goes away, and script side may be kept alive a little longer). This means I can release all references there instead of counting on destructor or scoped release. So I just do:


void cleanup()
{
   @transform = null;
   @mesh = null;
}

and because both Transform and Mesh are C++ registered classes, they're released as soon as their refcount reaches 0 and no GC is involved. This is more demanding than just relying on destructor but as you pointed out, there is no way to force destruction of object without risking dangerous side effects. So this is probably the only way I can do this, when the destruction of used resources matters (and it does, I want them gone when I say "this object is destroyed from world").


Where are we and when are we and who are we?
How many people in how many places at how many times?

This problem you've faced is common too all languages with automatic garbage collection, e.g. Java, C#, etc. We who are used to C++ ways of thinking just have to learn to think differently than we are used to in these cases :) There are pro's and con's with both automatic GC and manual GC.

Maybe you could at avoid this extra effort for the script writers by having it done automatically by your engine. Instead of the script providing the cleanup function, the engine could just enumerate all members of the script object and set all handles to null (or perhaps only the ones that are critical C++ objects).

 

 

AngelCode.com - game development and more - Reference DB - game developer references
AngelScript - free scripting library - BMFont - free bitmap font generator - Tower - free puzzle game

This topic is closed to new replies.

Advertisement