Removing all vent particles immediately

In my game I had the following problem: the parent group of a vent was removed before all the emitted particles would die. Since vent:stop() only stops the emission of particles, the living particles were still active and belonged to the parent group, which no longer existed, and that caused an error. Here is a solution I came up with:

  1. A recursive function to dispatch an event to all objects in a particular group:

    local function dispatchToGroup(options) local event = options.event; local parent = options.parent; local function recursion(parent) --if object has children if(parent.numChildren ~= nil and parent.numChildren > 0) then for i = parent.numChildren, 1, -1 do if (parent[i] ~= nil) then recursion(parent[i]); --dispatching event to object’s children end end end parent:dispatchEvent(event) --dispatching event to current child end recursion(parent); --starting recursion end

  2. Creating a new vent and subscribing each emited particle to our own custom event (a command to kill all particles)

    local vent = CBE.newVent { --creating a new vent preset = “embers”, parentGroup = theParent, onCreation = function§ --subscribing each new particle to our own custom event p:addEventListener(“killEmbers”, function() p._kill(); --killing the particle using its internal method end); end }

  3. Adding a vent method that removes all particles

    function vent:removeAllParticles() dispatchToGroup({event = {name=“killEmbers”}, parent = theParent}); end

Now if we want to remove a vent and all its particles immediately, we just do this:

vent:stop(); vent:removeAllParticles(); vent = nil;

Please tell me what you think about this approach. Or maybe there is a much better way to do it, like a predefined method in CBEffects that I don’t know about  :smiley:

Nice to know you’re using CBEffects. Fortunately, you won’t have to worry about this, because it’s built-in in the CBEffects ‘vent.clean()’ function.

[lua]

vent:stop()

vent:clean()

[/lua]

To completely destroy the vent, you’ll need the destroy command. You’ll run into problems if you only nil out the vent, because each vent has a display group attached to it:

[lua]

vent:destroy() – Calls stop(), clean(), and removes all required objects

[/lua]

Sorry about the docs not being clearer :slight_smile:

  • C

Thank you for your response, Caleb. vent:destroy() throws me an error in the latest version of CBEffects. Please try this example:

local CBE = require("CBEffects.Library") local vent = CBE.newVent { preset = "burn" } vent:start() local function onScreenTap() vent:destroy(); end Runtime:addEventListener("tap", onScreenTap)

Whoops! Should be fixed now.

  • C

Awesome :slight_smile:  Thanks, Caleb!

HOLD ON. Just tried it in my app - still gives me the same error…  :unsure:

Are you sure you’re not tapping more than once?

  • C

Never mind, it works great. Back then I tried it in my app and it would crash, must’ve been because _destroy _was called more than once somewhere else in the code. Sorry for misleading, Caleb.

  • Ilya

No problem :slight_smile:

  • C

I downloaded CBEffects today (5 Jul 2014). Since I had some problems with destroying vents so I searched the forum for this problem and found this thread. When tried soin08’s small example above it throws the following error:

File: CBEffects/cbe\_core/vent\_core/core.lua Line: 147 Attempt to index upvalue 'p' (a nil value)

When I immediately touch the screen, everything is fine. But when I wait a little longer (maybe when the first particle has died?) I get the error.

@Raye, I had the same problem in the latest version and thought the bug had not been fixed but than I tried to use a flag to make sure that the onTap event fires only once and it worked, which means there is no bug.

@soin08

Ok, I added a flag. And I still got the same error (most of the time, not always). So I added some print statements, to see whats going on.

-------------------------------------------------------------------------------- -- main.lua -------------------------------------------------------------------------------- display.setStatusBar(display.HiddenStatusBar) -------------------------------------------------------------------------------- -- Create Sample Effect -------------------------------------------------------------------------------- local CBE = require("CBEffects.Library") local isDestroyed = false local vent = CBE.newVent {     preset = "burn" } vent:start() local function onScreenTap()    print("onScreenTap() - isDestroyed -\> " .. tostring(isDestroyed))    if isDestroyed == false then       print("vent = " .. tostring(vent) )       vent:destroy()       isDestroyed = true       print("vent destroyed - isDestroyed -\> " .. tostring(isDestroyed))       print("vent = " .. tostring(vent) )    end end Runtime:addEventListener("tap", onScreenTap)

Sometimes it works, but sometimes I get the error as mentioned above. As far as I can tell from the console output the vent:destroy() is called only once. Or am I wrong?

@Raye, you’re right… I too get that error sometimes. You can use my solution until this gets fixed. Or could it be a Corona bug? Let’s see what Caleb (the creator of the amazing CBEffects) thinks about it.

For me it works fine like 9 times out of 10 on avarage.

@sonoi08 Phew, I’m glad you get the error, too.  :lol:

As far as have looked at the code the p._kill() function tries to kill a p that has a nil value. I put a

if p == nil then return end

at the beginning of p._kill() but I don’t know if this good programming practice in Corona/Lua.

I’m relative new to Corona and Lua. In the Examples/Tuts/Docs I have never seen practices that an object is checked if it is nil before deleting/working on it. But I know this practice from other programming languages.

Ok, let’s see if Caleb can help.

@Raye, such practicies do exist in Lua. For example in this topic @Roaminggamer talks about how you can determine wheter a displayObject has been removed to safely use the _onComplete _function passed to the transition. I use a different method btw.

@soin08 Ah thank you. Your technique with storing the transitions in a table and cancel is even more elegant.

Right now the hardest part for me in Corona developing is to really clean up a complex scene with (OOP objects and inheritance objects) when it comes to transition to a different scene. It is rather frustrating: the ingame works really nice, but such a thing like simple transition to the menu (or an pause overlay) really bothers me.

Thanks for alerting me of this, it should be fixed now.

Explanation? CBEffects uses internally a structure called a VS-T (I can’t remember what it stands for; it had a meaning months ago when I created it :slight_smile: ), which allows you to mark objects for removal from it. Then, whenever you want, you can clear those objects. Kind of like a delayed table.remove.

Anyhow and anyhoo, when a particle is ._kill()-ed, it’s marked for removal in the VS-T particle container. CBEffects clears the particle container every few frames, so there are “dead” particle references in there most of the time. When you call vent:destroy(), CBEffects iterates through all particles in that container and calls ._kill() on them - including the ones that have been marked for removal already. Obviously, if the container hasn’t been cleared at that instant, it tries to ._kill() particles that are already “dead”, which creates that error. I just uploaded a fix that clears the particle container right before iterating through them, so it’s fixed.

  • C

@Caleb Tested and working! Cool! Thank you for this fast fix and explanation and this great module at all.

No problem :slight_smile:

  • C

Thanks, Caleb!

@Raye, I feel you! I had the exact same thoughts when I first started using Corona (not too long ago haha). But after I have used Corona for about 6 months I now have developed a certain style to follow. For example, modular approach servers me as OOP in a way. I also use “custom” events as mentioned here.

Menus

I don’t use any Scene Manager like Storyboard so each of my game menus is a separate module. Each menu creates its own displayGroup and places all the graphics inside of it. Than it shows all the animation. When it’s time to clean up the menu it starts all the fade out animation and when it’s complete, it simply does container:removeSelf() and c_ontainer = nil_, where container is the menu’s displayGroup.

Pause Overlay

Each object in my game has  _timers and _transitions tables to store the respective values. When the game is paused (e. g. back button pressed) I dispatch a “custom” event called “pause”. All the objects are subscribed to this event and they pause all transitions and timers. Resuming the game is a similar process.