Game Development Community

Deleting ParticleEffect? bug?

by Nicolas Stohler · in Torque Game Builder · 04/09/2006 (1:27 pm) · 8 replies

I use the following function from the shooter tutorial to attach a particle effect to every shot the player fires:
function attachThruster( %mountObj, %mountPosition, %angle )
{
  %thruster = new t2dParticleEffect() { scenegraph = t2dScene; };
  %thruster.loadEffect("~/data/particles/smallThruster.eff");
  %thruster.setLayer( %mountObj.getLayer()+1 );
  %thruster.mount( %mountObj, %mountPosition, 0, false );
  %thruster.setRotation( %angle );
  %thruster.playEffect();
}

I create the shots currently with %shot.setCollisionResponse( KILL ), so when they collide (with my tilemap-world border, there are no enemies yet!), they get deleted automatically. the newly added particle effects also vanish from the screen when the shots get deleted, but it looks like they somehow stay in memory (I verified this using the debug banner). when I create a lot of shots (3 every 10 ms), the actual framerate drops from the initial 300 to 30 or less in not even 10 seconds!

for testing purposes, I deleted the shots right after I created them (using %shot.safeDelete();) and did get the same results (total objects skyrockets without anything on screen at all!).

I'm really confused now... Am I trying something the wrong way (do I have to delete all those effects manually (I think that's what mounts are here for...) or is this documented terribly wrong in the tutorial (and the spacescroller demo... since the attachThruster function is pretty the same there)?

Or is this just a bug?

#1
04/15/2006 (6:10 am)
It happend in my game too, I guess it is a serious bug. Object count in debug banner keeps increasing in Beta2, which didn't happen in Beta1.
#2
04/15/2006 (8:45 am)
YES! I wrote about this in a previous thread as well.

It can get real ugly if you have a lot of particles. Frame rate just drops like mad.
#3
04/15/2006 (11:46 am)
It would be really helpful, if this is happening consistently, if someone put together a simple code snippet for the latest beta that shows this problem. Any takers?

I can then begin to track down the problem and submit a fix here if it's a simple one.

- Melv.
#4
04/15/2006 (1:15 pm)
Melv, here you go:
function attachThruster( %mountObj, %mountPosition )
{
  %thruster = new t2dParticleEffect() { scenegraph = t2dScene; };
  %thruster.loadEffect("~/data/particles/smallThruster.eff");
  %thruster.setLayer( %mountObj.getLayer()+1 );
  %thruster.mount( %mountObj, %mountPosition, 0, false );
  %thruster.playEffect();
}

function fire()
{
  %shot = new t2dStaticSprite() { scenegraph = t2dScene; };
  %shot.setPosition( "0 0" );
  %shot.setImageMap( playerShotImageMap );
  attachThruster( %shot, "0 0", 0 );
  %shot.safeDelete(); //... simple sample -> just deleting the created shot doesnt kill the mounted particle effect
  $player_fireEventId = schedule( $player_fireRate, 0, "fire" );  // call fire again and again...
}

...just call fire(), and watch the object count go up and the framerate drop.

edit: added the call to %shot.safeDelete(), removed other stuff: the object count does go up when the call to attachThruster is made; comment it out to see that it doesnt increase otherwise
#5
04/16/2006 (2:51 am)
Guys,

If you're interested in why the problem occurred then continue reading else goto the file "t2dSceneGraph.cc" and replace the function "subUpdateScene()" with the code below.

So, after a little digging, I located the issue which is quite a 'sneaky' one. When you "safe-delete" an object, it typically gets put into a list for pending deletions and doesn't get destroyed immediately, hence it being a 'safe" deletion because callbacks can use it.

Anyway, this "safe-delete" function is virtual meaning that object classes can define a termination behaviour themselves. This is essential for particle effects which require a staged destruction meaning that the emission is halted and when all the currently-active particles are destroyed, the deletion is then processed using the standard "safe-delete" mechanism. During the period of when the "safe-delete" requests comes in and the object takes control of the deletion logic and the subsequent deletion by the standard "safe-delete" mechanism, the object typically uses its "integrateObject()" method to determine if the object is ready for final deletion (in this case whilst waiting for particles to end naturally).

The reason why the effects were not being destroyed was that the "integrateObject()" method was not being called when it was in a pending-deletion state. This code is handled as part of the scene-graph sub-update routine. I have therefore changed this routine to take into account this state.

The following code should successfully handle this state:

//-----------------------------------------------------------------------------
// Sub-Update Scene.
//-----------------------------------------------------------------------------
void t2dSceneGraph::subUpdateScene( const F32 elapsedTime, CDebugStats* pDebugStats )
{
#ifdef T2D_DEBUG_PROFILING
        PROFILE_START(T2D_t2dSceneGraph_subUpdateScene);
#endif
    // Update Graph Time.
    mSceneTime += elapsedTime;

    // Only Process if we've got objects in the scene!
    if ( getSceneObjectCount() > 0 )
    {
        // Increment Update Sequence.
        nextUpdateSequence();

        // Fetch First Object.
        t2dSceneObject* pSceneObject2D = getProcessHead()->getProcessNext();

        // ****************************************************
        // Pre-Integrate Stage.
        // ****************************************************
#ifdef T2D_DEBUG_PROFILING
        PROFILE_START(T2D_t2dSceneGraph_preIntegrate);
#endif

       // Pre integration is a physics processing step, so editor scenes need not perform it.
       if ( !getIsEditorScene() )
       {
           while ( pSceneObject2D != getProcessHead() )
           {
               // Is the object enabled/not-paused/not being deleted?
               // NOTE:-    We also need to check if we're disabled but owned by a mount that is now enabled.
               //           We also need to check if we're paused but owned by a mount that is now not-paused.
               if ( !pSceneObject2D->getInitialUpdate() ||
                   ( pSceneObject2D->isBeingDeleted() && !pSceneObject2D->getSafeDelete() ) ||
                   ( !pSceneObject2D->isBeingDeleted() &&
                   (pSceneObject2D->getEnabled() || (pSceneObject2D->getIsMounted() && pSceneObject2D->mMountOwned && pSceneObject2D->getProcessMount()->getEnabled()) ) &&
                   (!pSceneObject2D->getPaused() || (pSceneObject2D->getIsMounted() && pSceneObject2D->mMountOwned && !pSceneObject2D->getProcessMount()->getPaused()) ) ) )
               {
                   // Pre-Inegrate.
                   pSceneObject2D->preIntegrate( mSceneTime, elapsedTime, pDebugStats );
               }

               // Fetch Next Object.
               pSceneObject2D = pSceneObject2D->getProcessNext();
           }

           // Reset to first object.
           pSceneObject2D = pSceneObject2D->getProcessNext();
       }

#ifdef T2D_DEBUG_PROFILING
        PROFILE_END();   // T2D_t2dSceneGraph_preIntegrate
#endif


        // ****************************************************
        // Integrate Object Stage.
        // ****************************************************
#ifdef T2D_DEBUG_PROFILING
        PROFILE_START(T2D_t2dSceneGraph_integrateObject);
#endif

        while ( pSceneObject2D != getProcessHead() )
        {
           // Is the object enabled/not-paused/not being deleted?
           // NOTE:-    We also need to check if we're disabled but owned by a mount that is now enabled.
           //           We also need to check if we're paused but owned by a mount that is now not-paused.
           if ( !pSceneObject2D->getInitialUpdate() ||
               ( pSceneObject2D->isBeingDeleted() && !pSceneObject2D->getSafeDelete() ) ||
               ( !pSceneObject2D->isBeingDeleted() &&
               (pSceneObject2D->getEnabled() || (pSceneObject2D->getIsMounted() && pSceneObject2D->mMountOwned && pSceneObject2D->getProcessMount()->getEnabled()) ) &&
               (!pSceneObject2D->getPaused() || (pSceneObject2D->getIsMounted() && pSceneObject2D->mMountOwned && !pSceneObject2D->getProcessMount()->getPaused()) ) ) )
            {
                // Post-Update.
                pSceneObject2D->integrateObject( mSceneTime, elapsedTime, pDebugStats );
            }   

            // Fetch Next Object.
            pSceneObject2D = pSceneObject2D->getProcessNext();
        }

#ifdef T2D_DEBUG_PROFILING
        PROFILE_END();   // T2D_t2dSceneGraph_integrateObject
#endif

        // ****************************************************
        // Post-Integrate Stage.
        // ****************************************************
#ifdef T2D_DEBUG_PROFILING
        PROFILE_START(T2D_t2dSceneGraph_postIntegrate);
#endif

        // Fetch Next Object.
        pSceneObject2D = pSceneObject2D->getProcessNext();

        if (!getIsEditorScene())
        {
           while ( pSceneObject2D != getProcessHead() )
           {
               // Is the object enabled/not-paused/not being deleted?
               // NOTE:-    We also need to check if we're disabled but owned by a mount that is now enabled.
               //           We also need to check if we're paused but owned by a mount that is now not-paused.
               if ( !pSceneObject2D->getInitialUpdate() ||
                   ( pSceneObject2D->isBeingDeleted() && !pSceneObject2D->getSafeDelete() ) ||
                   ( !pSceneObject2D->isBeingDeleted() &&
                   (pSceneObject2D->getEnabled() || (pSceneObject2D->getIsMounted() && pSceneObject2D->mMountOwned && pSceneObject2D->getProcessMount()->getEnabled()) ) &&
                   (!pSceneObject2D->getPaused() || (pSceneObject2D->getIsMounted() && pSceneObject2D->mMountOwned && !pSceneObject2D->getProcessMount()->getPaused()) ) ) )
               {
                   // Pre-Inegrate.
                   pSceneObject2D->postIntegrate( mSceneTime, elapsedTime, pDebugStats );
               }

               // Fetch Next Object.
               pSceneObject2D = pSceneObject2D->getProcessNext();
           }
        }

#ifdef T2D_DEBUG_PROFILING
        PROFILE_END();   // T2D_t2dSceneGraph_postIntegrate
#endif

    }
#ifdef T2D_DEBUG_PROFILING
        PROFILE_END();   // T2D_t2dSceneGraph_subUpdateScene
#endif
}

- Melv.
#6
04/16/2006 (3:37 am)
Melv

I added the fix and recompiled, the object count now really is stable!

thanks a lot for this easter present ;-) ... which reminds me, I need go play Marble Blast Ultra on the Xbox360 and find some more of those easter eggs today.

- Nicolas
#7
04/16/2006 (4:10 am)
No problem ... enjoy. :)

- Melv.
#8
04/16/2006 (11:02 am)
How else can I put this other than, you rule!