Self-removing animator

You are an experienced programmer and have a problem with the engine, shaders, or advanced effects? Here you'll get answers.
No questions about C++ programming or topics which are answered in the tutorials!
Post Reply
binford
Posts: 28
Joined: Tue Nov 14, 2006 10:32 am
Location: Munich

Self-removing animator

Post by binford »

Hi guys,

I wrote a custom animator which should remove itself after it is done. For this purpose I store a reference to the scene node to which the animator is attached in the animator.

My animateNode() looks like this

Code: Select all

 
if( m_finished )
{
    mp_sceneNode->removeAnimator( this );
    delete this;
}
But in the next render loop, the app crashes. Even without the delete statement the app crashes.

It looks like irrlicht doesent like animators which try to remove themselves (although I dont realy understand why). The forum search revealed that some guys had similar problems. They proposed to add a flag ("isUsed",for instance) to the ISceneNodeAnimator class, and to check this flag in the next Animators-loop of a IAnimatedSceneNode.

Questions:
1. Is there a way to remove an animator from the animator itself?
2. If there is no way, is changing the engine the way to go? (will this be considered in one of the next releases?)
vitek
Bug Slayer
Posts: 3919
Joined: Mon Jan 16, 2006 10:52 am
Location: Corvallis, OR

Post by vitek »

I think there are several problems with this code.
  1. The first problem is taht you are deleting the animator directly. The animator is reference counted, so you should use drop().
  2. You should only call drop() if you have called grab() or were given a pointer via the create method.
  3. You should have already called drop() in the code that added the animator in the first place, so you don't need to drop() it again.
  4. All you really need to do is remove the animator from the scene node. The scene node will drop the animator then.
Also, there is code in ISceneNode::OnPostRender() that iterates through a list of animators and calls animate() on them. If you remove one of the list entries while the list is being iterated on, you might screw up the code that is running in that loop. You could cause it to skip an animator, or worse.

You might consider doing something like this to safely remove the animators...

Code: Select all

// in one of your .h files
void ISceneNodeAnimator_addToDeletionQueue(ISceneNodeAnimator* anim, ISceneNode* node);
void ISceneNodeAnimator_flushDeletionQueue();

// in one of your .cpp files
struct SEntry
{
  scene::ISceneNode* Node;
  scene::ISceneNodeAnimator* Animator;
};

core::array<SEntry> Cleanup;

void ISceneNodeAnimator_addToDeletionQueue(ISceneNodeAnimator* anim, ISceneNode* node)
{
  SEntry e;
  e.Node = node;
  e.Animator = anim;

  Cleanup.push_back(e);

  // grab both so that they don't get dropped while in our list
  node->grab();
  anim->grab();
}

void ISceneNodeAnimator_flushDeletionQueue()
{
  if (Cleanup.size() != 0)
  {
    u32 e;
    for (e = 0; e < Cleanup.size(); ++e)
    {
      Cleanup[e].Node->removeAnimator(Cleanup[e].Animator);
      Cleanup[e].Node->drop();
      Cleanup[e].Animator->drop();
    }

    Cleanup.clear();
  }
}

// in your animator...
if (m_finished)
  ISceneNodeAnimator_addToDeletionQueue(mp_sceneNode, this);

// in main, after drawAll()
ISceneNodeAnimator_flushDeletionQueue();
Travis
binford
Posts: 28
Joined: Tue Nov 14, 2006 10:32 am
Location: Munich

Post by binford »

Ok after looking at IUnknown, I think I do understand a bit more. But...
You should only call drop() if you have called grab() or were given a pointer via the create method.
I think I should also call drop if I created the object myself via the new operator. True?

And what exactly is the benefit of coding a custom SceneNodeAnimatorFactory? I read a bit about the factory pattern but I still dont really get it. Is there a easy-to-understand case, in which this pattern proves to be useful?

btw thanks for your numerous responses vitek, they're always valuable.
vitek
Bug Slayer
Posts: 3919
Joined: Mon Jan 16, 2006 10:52 am
Location: Corvallis, OR

Post by vitek »

As the documentation says, you should call drop() if you grab() or if you called a create...() function. It doesn't say what to do if you allocate an object with new, but what you should do is pretty easy to figure out.

In the normal usage you would see code like this...

Code: Select all

ISceneNodeAnimator* anim = smgr->createFlyCircleAnimator(...);
  node->addAnimator(anim);
anim->drop();
The first like creates an animator on the heap with a call to new and returns a pointer to it. You can see the source for this here. If you inspect anim with a debugger, you should see that the reference count is 1 just before the addAnimator() call.

The addAnimator() call adds the animator to the scene nodes animator list and calls grab() on the animator. Now the reference count is 2.

Finally we call drop() on our pointer. The reference count will fall to 1. At this point, the scene node is the owner of the animator. If the animator is removed from the scene node with removeAnimator(), it will call drop() [it must because it called grab() in addAnimator()]. This will drop the reference count to 0 and the animator will delete itself.

Suppose that I've written my own animator, MySceneNodeAnimator. When I create an instance of my animator on the heap, it is exactly the same as calling create....() for one of the other animator types. The reference count will initially be set to 1.

So, when you create an object that inherits from IUnknown [which should actually be CRefCounted], the initial reference count is 1. When you are done with the object, you should drop() it. If you pass a pointer to your object to another class, that class should grab() the pointer when it takes control of the object, and it should drop() that pointer when it is done with the object.

Your code is a special case. You are trying to remove/drop yourself.

As mentioned, you only drop() if you grab() or you created the object with a call to a create...() function. We also know we should drop() if we called new to allocate an object instance ourselves.

If you look at the code for your animator, I seriously doubt that you are calling grab(). That would be a really weird thing to do, and it could cause your object to remain in memory forever [it holds a reference to itself]. I know that your animator didn't allocate itself with new or create...() either because that is physically impossible. So you should not call drop().

There is one other thing that you should be careful about. If you do something that could cause the object to be deleted, you should not access any of the member data of that object after that point. The following would be an example of this...

Code: Select all

void MySceneNodeAnimator::animateNode(ISceneNode* node, u32 timeMs)
{
  //...

  if (m_finished)
  {
    node->removeAnimator(this);
    m_finished = false; // very bad!!!
  }
}
If the call to removeAnimator() resulted in the deletion of the scene node, you would be writing to memory that you no longer own. That would be roughly equivalent to this...

Code: Select all

int* ip = new int(5);
delete ip;
*ip = 10; // boom
Now the factory question. The reason you would want to create a factory would be so that you can register the factory with the scene manager. The scene manager would be able to create your derived animator type without knowing anything about it other than having a pointer to a factory. This will make it possible for you to load up a .irr scene file that has scene nodes with your animator already attached to them.

Unfortunately, this isn't incredibly useful at the moment. IrrEdit doesn't support add-ins, so there is no way to be able to use IrrEdit to add your animators to scene nodes. You can open up an existing .irr file with a text editor and add the appropriate entries.

Travis
binford
Posts: 28
Joined: Tue Nov 14, 2006 10:32 am
Location: Munich

Post by binford »

Ok thanks vitek, things are clear now.

Conclusion:
My fault was to use the delete operator on an Object which is derived from IUnkown. Those Objects are deleted automatically when there refenrece counter is set to 0. This is true as soon as you have one drop() call more than grab() calls.
vitek
Bug Slayer
Posts: 3919
Joined: Mon Jan 16, 2006 10:52 am
Location: Corvallis, OR

Post by vitek »

Exactly.
binford
Posts: 28
Joined: Tue Nov 14, 2006 10:32 am
Location: Munich

Post by binford »

Ok I have to add sth here, in case someone tries sth simmilar. vitek pointed out that...

Code: Select all

void MySceneNodeAnimator::animateNode(ISceneNode* node, u32 timeMs)
{
  //...

  if (m_finished)
  {
    node->removeAnimator(this);
    m_finished = false; // very bad!!!
  }
} 
but to be really precise it should be

Code: Select all

void MySceneNodeAnimator::animateNode(ISceneNode* node, u32 timeMs)
{
  //...

  if (m_finished)
  {
    node->removeAnimator(this); // very bad, too !!!
  }
} 
Why? Because this animator will delete itself, which is not a bad thing. BUT after that it tries to continue to execute after the removeAnimator() function which IS a bad thing. With my compiler, this code crashed reliable every time.

A solution to that can be found here http://irrlicht.sourceforge.net/phpBB2/ ... p?p=110907
binford
Posts: 28
Joined: Tue Nov 14, 2006 10:32 am
Location: Munich

Post by binford »

hmm I just realized that in IUnknown.h you'll find this:

Code: Select all

if (!ReferenceCounter)
			{
				delete this;
				return true;
			}
Im my theory, as stated above, this should be dangerous. It seems my theory is wrong.
vitek
Bug Slayer
Posts: 3919
Joined: Mon Jan 16, 2006 10:52 am
Location: Corvallis, OR

Post by vitek »

That is not dangerous. The code you've pasted doesn't use the this object after it is deleted, it simply returns a constant.

The key is that nobody can use anything that they have dropped. After you drop() a pointer, you should consider the pointer to be invalid, unless you _know_ that someone else has has a reference to that same object [they called grab() but haven't yet called drop()].

Travis
binford
Posts: 28
Joined: Tue Nov 14, 2006 10:32 am
Location: Munich

Post by binford »

Okay I think I got it now. Thx!
markanderson
Posts: 30
Joined: Thu Mar 16, 2006 6:21 pm

Post by markanderson »

In case anyone else uses the functions supplied by vitek above.

Code: Select all

// in your animator...
if (m_finished)
  ISceneNodeAnimator_addToDeletionQueue(mp_sceneNode, this); 
But it should be:

Code: Select all

// in your animator...
if (m_finished)
  ISceneNodeAnimator_addToDeletionQueue(this, mp_sceneNode); 
The functions work great through. Thanks vitek.
Post Reply