Advertisement

Object Reference Count Unusually High

Started by August 15, 2011 06:00 PM
6 comments, last by WitchLord 13 years, 3 months ago
There seems to be a problem with interfaces where multiple interfaces like to shoot the reference count of an object through the roof, causing them to not be properly garbage collected. In the example shown bellow, I am inheriting from 2 interfaces at this level, and from IObject further down the hierarchy at the base level. Note that I wanted the TA_Projectile class to hold a reference to itself then nullify the reference when it needs to be destroyed so that it can manage itself without needing to be managed externally.

Assume that there are no references to this object being stored elsewhere.

/*
Class: TA_Projectile
*/
class TA_Projectile : Actor, ITimerCallback, IObjectDestructionCallback
{
TA_Projectile(TA_Mind@ mind,TA_Weapon@ weapon,CollisionShape@ s,SceneNode@ node,float mass=1.0f,int damage=1,int flight_time=2000)
{
@m_RootNode = @node;
m_Damage = damage;
@m_Mind = @mind;
@m_Timer = Timer(flight_time,this);

// CollisionShape@ s = TA_CreateProjectileShape(this.Type,node);
AddNode(m_RootNode);
@m_ControllingRB = AddRigidBody(m_RootNode,mass,s);
@m_CollisionBody = AddCollisionBody(m_RootNode,s);

@m_Myself = this;
@m_Weapon = @weapon;

SetDestructionCallback(this);
m_Timer.Start();
}


// This callback is part of IObjectDestructionCallback and called when Destroy is called.
void OnObjectDestroyed(Object@ obj)
{
@m_Weapon = null;
@m_Mind = null;
@m_Myself = null;
// We want to be sure that the timer is destroyed since it is an Object
// that is not managed. These objects get destroyed as expected by the garbage collector.
m_Timer.Destroy();
m_RootNode.Remove();

// With the inheritance tree that I have, The interfaces shown plus Actor, which is and Object, which is an IObject, this will print out that there are about 9-10 references to this single object. There are no references being stored anywhere script-side.

// When the TA_Projectile inherits from only IObject, the reference count is normal and the TA_Projectile gets released on garbage collection as expected.
Println("TA_Projectile destroy callback "+RefCount(this));
}

// When the projectile collides with a TA_Body, it will call its OnDamaged callback.
void OnCollide(const Actor@ other)
{
if(other.Is("TA_Body"))
{
//cast<const TA_Body@>(other).OnDamaged(m_Mind,m_Damage);
}
}

// After the flight time has been reached, we want to destroy the Object.
void TimerAction(Timer@ t) { m_Timer.Destroy(); m_RootNode.Remove(); Destroy(); }

Vector GetLinearVelocity() const { return m_ControllingRB.LinearVelocity; }
void SetLinearVelocity(Vector&in v) { m_ControllingRB.SetLinearVelocity(v); }

Rotation GetRotation() { return m_ControllingRB.GetRotation(); }
void SetRotation(const Rotation&in rot) { m_ControllingRB.SetRotation(rot); }


void SetPosition(Vector&in pos) { m_ControllingRB.SetPos(pos); }
Vector get_Position() { return m_ControllingRB.Pos; }

void ApplyTorqueImpulse(Vector&in imp) const { m_ControllingRB.ApplyTorqueImpulse(imp); }
void ApplyTorqueImpulse(float x,float y,float z) const { m_ControllingRB.ApplyTorqueImpulse(x,y,z); }

void ApplyLinearImpulse(const Vector&in imp) const { m_ControllingRB.ApplyLinearImpulse(imp); }
void ApplyLinearImpulse(float x,float y,float z) const { m_ControllingRB.ApplyLinearImpulse(x,y,z); }

SceneNode@ get_RootNode() const { return m_RootNode; }

// The root node.
private SceneNode@ m_RootNode;

// The mind that owns this projectile.
private TA_Mind@ m_Mind;

private int m_Damage;
private Timer@ m_Timer;
private RigidBody@ m_ControllingRB;
private CollisionBody@ m_CollisionBody;

// In order for projectiles to be self-sufficient, we need to have at
// least one reference to it somewhere. This handle gets cleared
// when OnDestroyed is called.
private TA_Projectile@ m_Myself;

private TA_Weapon@ m_Weapon;
};





You use the TA_Projectile like so.


class Gun : Weapon
{
void Fire()
{
// WOOOOOOOOOOOOOOOOW!!!
TA_Projectile proj();
}
};


RefCount is defined as

int RefCount(asIScriptObject* obj)
{
obj->AddRef();
return obj->Release();
}
It is probably correct. Let's try to verify the actual references:

I count the following references:

- The garbage collector
- m_Myself
- Timer
- SetDestructionCallback

So the refCount should be 4 while the object is alive, and no method is called on it.

When you print the actual refCount in the OnObjectDestroyed method you should have the following count:

- Context holds on to the this pointer during the call
- Garbage collector is still holding on to one reference
- The is an extra reference when calling the RefCount function
- The SetDestructionCallback is probably still holding on to a reference
- Is the object parameter pointing to the actual object, if so you have another reference here
- If it is the TimerAction that is invoking the destruction, then you have another reference from the context here
- What does the Destroy method actually do? Assuming it is a script method from the Actor class, you will have one more reference there

I only counted 7 references here, but I'm not sure what your implementation actually looks like, so there other references are probably valid too.

Observe that the object will only be fully destroyed when the Garbage Collector has determined that it is the only remaining referer, i.e. refCount == 1. The OnObjectDestroyed() method removes the circular references, but doesn't actually destroy the object yet. The callstack needs to unwind to free all local references to the object, and then the Garbage Collector needs to have an opportunity to run and see that the object is ready to be destroyed.

I also need to warn you about the use of the m_Myself member. This is not going to work the way you wanted it. This is just creating a circular reference, but it is not a guarantee that the object will stay alive. The GC will eventually detect this circular reference, and since there are no outside references to the object it will assume it is a dead object and will force the destruction of the object (it simply sets all handle members to null).

Instead of a local m_Myself you probably want to keep a global array of handles to the projectiles. The projectile can then add itself to the array when created, and remove itself when it should be destroyed. This will guarantee the lifetime of the projectile.

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

Advertisement
The thing about the destroy function is that it is a way for me to explicitly handle the lifetime of Actprs. Actor objects are objects in the compiled engine that hold a pointer to a script object to pass engine events to the script. When Destroy is called, the actor gets flagged for the engines garbage collector to be deleted at the end of the frame. The actor, when deleted, calls release on the script objrct reference. Perhaps I should just call release until the refence count is 4? Because Destroy is meant to act as the delete or free call does in C++. Or I should make sure that the callback objects are released when destroy is called.
You cannot force an object to be freed in a garbage collected environment. That would require you to know the location of all references to the object so that you could set those to null (and just hope that nothing will break due to this).

Calling Release() just to decrease the refCounter without clearing the actual reference will just cause crashes. You must never do something like 'while( obj->Release() > 4 );'. In fact, you must never make any decisions based on the return value from the AddRef/Release methods, this is just for debugging purpose.

The OnObjectDestroyed() method should do what you are already doing, i.e. release all references held by the object. This will guarantee that the object is not involved in any circular references. The object will still remain until all references to it are released, but it will just be an empty shell that won't do anything if something tries to invoke a method on it. Your internal reference to the object can also be released at this moment, and you don't have to worry about it anymore. The garbage collector will take care of freeing the memory of the object when all other references to it are removed (or the other objects become garbage too).

You could also add a method to your Actor class that would let referring objects query whether the Actor is still valid or not (i.e. if it has been destroyed). If the other object finds that the Actor is no longer valid it can clear its reference to the object at that moment.

This is very similar to what I do in my own game engine.

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

Well, I found the problem was with the interface handles not being released in the object base class and the timer. I was working under the assumption that the handles would be implicitly cleared on the destruction of the timer.
So nothing to correct in AngelScript this time? :)

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

Advertisement
Seems to be the case. Thanks for the tip. Although I would like to ask, why is it this not handled automatically when the object holding the handle is released?
I guess the Timer is garbage collected, so the destruction of the object will actually only happen when the garbage collector runs. At that time it will release the references to the handles it holds.

Currently the GC only runs automatically when a new garbage collected object is created, and even then it only runs a few incremental steps so as not to block the application. If you wish to guarantee the destruction of a garbage collected object at a particular time, you must manually invoke the garbage collector to run a full cycle.

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