Jump to content

[TOPIC: topicViewTemplate]
[GLOBAL: userSmallPhoto]
Photo

clearing display.currentStage - 2 questions
Started by dan56 Dec 23 2018 06:46 AM

14 replies to this topic
display stage composer
[TOPIC CONTROLS]
[/TOPIC CONTROLS]
[modOptionsDropdown]
[/modOptionsDropdown]
[reputationFilter]
[TOPIC: post.html]
#1

dan56

[GLOBAL: userInfoPane.html]
dan56
  • Enthusiast

  • 43 posts
  • Corona SDK

I’ve been working on my first game built on Corona for several months, and I’ve got it in Alpha testing at the Google Play store now. All seems good, except…

 

A couple testers report an intermittent bug that I’ve seen only once (and can’t duplicate). Unfortunately they see it a little more frequently than I do. The bug happens after a game is completed and the user tries to “play again.” Occasionally, some display objects from the last game aren’t cleared from the display.

 

I’m using composer, inserting all my display objects into groups and then inserting them into the sceneGrooup. At the end of every game/scene I call composer.removeScene() on scene:hide() intending to remove everything before the next scene. As I say usually his works.

 

When I went hunting through my code to find what I had missed, I did discover an error where I had set one of my display objects to nil, without first removing it from the display. My first question is…

 

1) Could that mistake have caused composer.removeScene() to get confused when it tried to remove all objects and leave some _other_ objects on the screen? I speculating that if the garbage collector came along and (intermittently) removed the display object before composer tried to remove it that might cause a problem.

 

So, I fixed that mistake but I thought what if there are other mistakes like that. I admit I have made more than one mistake in my life. So, I started thinking, “Maybe I can explicitly remove all objects from the main stage before I fire up the new scene and that will correct for any other lurking mistakes.” Assuming that’s a good approach, the second question is…

 

2) How can I clear out the display stage before I create a new scene?

 

I couldn’t find anything like display.currentStage.removeAll() and eventually I tried the code below. It sort of works, in that it doesn’t seem to cause any harm, but I’m hoping someone else has a better idea.

function clearTheStage()
    local stage = display.currentStage
    
    if (stage.numChildren > 3) then

        for i=4,stage.numChildren do
            local child = stage[i]
            display.remove(child)
            print( "stageChild["..i.."] was removed " )
        end
    
    else
    
        print ("left the first 3 objects on the stage")

    end


end

Actually, in my first attempt, I tried to clear all the children, starting with stage[1] but that didn’t go so well. I invoked clearTheStage() in the composer scene:ceate() function and then all I  got from scene:show() was a blank screen. I’m thinking composer creates some child objects in the display stage that need to be around for it to work, like maybe composer.stage

 

So, as you can tell I’m experimenting here. Maybe I should re-phrase the second question to, “How can I guarantee that the stage is clear before I create a new scene?”

 

 



[TOPIC: post.html]
#2

roaminggamer

[GLOBAL: userInfoPane.html]
roaminggamer
  • Corona Geek

  • 7,562 posts
  • Corona SDK

1. Generally you should not use current stage as your only group, nor should you directly manipulate it.  This is dangerous.

 

Instead make one or more groups and insert your content into them.

 

Then, later when you need to destroy all that content, just destroy the group(s).

 

 

 

2. If you insist on deleting child by child, then you must do it in reverse, not forward as you seem to be:

--
-- Better way to handle deletions
--

-- Make a group
local group = display.newGroup()

-- Fill it up for example
for i = 1, 100 do
   local rect = display.newRect( group, math.random( -300, 300 ), math.random( -300, 300 ), 10, 10 )
end

... later destroy all 
display.remove(group)

--
-- Poor way to handle deletions
--

-- Make a group
local group = display.newGroup()

-- Fill it up for example
for i = 1, 100 do
   local rect = display.newRect( group, math.random( -300, 300 ), math.random( -300, 300 ), 10, 10 )
end

-- Do one of the following or the other, but not both

-- 1. for-loop deletion
for i = group.numChildren, 1, -1 do
   display.remove(group[i])
end

-- 2. while-loop deletion
while( group.numChildren > 0 ) do
   display.remove(group[1])
end



[TOPIC: post.html]
#3

roaminggamer

[GLOBAL: userInfoPane.html]
roaminggamer
  • Corona Geek

  • 7,562 posts
  • Corona SDK

@dan,

 

nilling a reference to an object has no effect on whether it can be removed from a group.

 

All that does is affect your ability to use the variable that held the reference to 'refer to' the object.



[TOPIC: post.html]
#4

roaminggamer

[GLOBAL: userInfoPane.html]
roaminggamer
  • Corona Geek

  • 7,562 posts
  • Corona SDK

If an object is not cleared/removed when destroying a scene, you failed to add it to the scene group and/or you have a global reference to it somewhere.

 

In the latter case, it will be destroyed, but not garbage collected.



[TOPIC: post.html]
#5

roaminggamer

[GLOBAL: userInfoPane.html]
roaminggamer
  • Corona Geek

  • 7,562 posts
  • Corona SDK

Also.  Current stage is not the same a sceneGroup (in case there was any confusion there).

 

Current stage is actually a bit of a misnomer.  There is only ever one current stage and it is the top group that holds all other content and groups.  You never destroy it.

 

Tip: It is best to only ask one question per post, because:

 

1. It makes the post easier to write and to read.

 

2. It makes the post easier to answer.

 

3. It makes the post more useful for future readers.

 

4. Posting with multiple questions leads to one or more questions going unanswered.


Edited by roaminggamer, 23 December 2018 - 12:20 PM.


[TOPIC: post.html]
#6

roaminggamer

[GLOBAL: userInfoPane.html]
roaminggamer
  • Corona Geek

  • 7,562 posts
  • Corona SDK

Dan,
 
I have a bunch of composer examples here that may or may not be useful to you:

 

https://github.com/roaminggamer/CoronaGeek/raw/master/Hangouts/composer_scene_manager.zip


I think you said this above, but the simplest way to create a scene on each entry and destroy it on each exit is to use the 

  • show listener + 'will' phase to create
  • hide listener + 'did' phase to destroy

Simply make a local at the top of the file called 'group' or whatever you want.

 

Then, when creating, do this

group = display.newGroup()
sceneGroup:insert( group )

later when destroying do this:

display.remove(group)
group = nil


[TOPIC: post.html]
#7

roaminggamer

[GLOBAL: userInfoPane.html]
roaminggamer
  • Corona Geek

  • 7,562 posts
  • Corona SDK

Do not call sceneRemove() in hide() unless you do it in the 'did' phase and delay it by at least 1ms:

function scene:hide( event )
   if( event.phase == "did" ) then
      timer.performWithDelay( 1, function() <DO THE CALL HERE> end )
   end
end

It is a bad idea to 'destroy' the current scene while executing a script in the current scene.

Therefore a slight delay (as above) causes the removal to be done in the next frame (after the function/listener call completes).


Edited by roaminggamer, 23 December 2018 - 12:35 PM.


[TOPIC: post.html]
#8

dan56

[GLOBAL: userInfoPane.html]
dan56
  • Enthusiast

  • 43 posts
  • Corona SDK

First of all, thanks for the quick response!

If I could pick you brain a little more, I now have 3 questions about the first two questions

 

On my first question (Could my mistake of removing an object before removeScene() could remove it, have caused  removeScene() to _not_ remove all objects?) you mentioned:

 

make one or more groups and insert your content into them. Then, later when you need to destroy all that content, just destroy the group(s).

 

I do insert all my display objects into groups, but normally I don’t explicitly destroy them. Later when I need to destroy that content, I call removeScene() intending that removeScene() will destroy all the groups and the display objects within them

 

1a) That is good practice, correct?

 

1b) If, because of my mistake, one of the objects _inside_ one of the groups had already been deleted, perhaps that would cause the problem I observed, intermittently?

 

So, if the wisdom to be learned here is “don’t ever delete an object that is in a display group. Let removeScene() delete the group and it will delete all the objects within it. I’d be happy with that. I thought that I read that after removing a display object I also had to set the value of a variable pointing to that object to nil, but I’d be glad to _not_ do that.  

 

 

On the second question (How can I clear out the display stage…) I don’t insist on doing it child by child, I’m just looking for a way to guarantee that all the display objects from prior scene(s) are removed. You mentioned

 

-- Better way to handle deletions

--...

--... later destroy all 
display.remove(group)

 

 

2) OK. I thought I was essentially doing that when I called removeScene(), but perhaps I need to explicitly call display.remove() for each of the groups I created?

 

As an aside, I did try removing all the 3 mystery children in reverse order. The mystery children being the ones that that are there before I execute any other code in scene:create()  

 

I used the for loop style code like this…

       for i = stage.numChildren, 1, -1 do

            local child = stage[i]

            display.remove(child)

            print( "stageChild["..i.."] was removed " )

        end

I got these log messages

 

11:58:36.365  stageChild[3] was removed

11:58:36.365  stageChild[2] was removed

11:58:36.365  stageChild[1] was removed

 

But after that, the rest of my code in scene:create() and scene:show()that normally produces a screen just produced a black screen.




[TOPIC: post.html]
#9

dan56

[GLOBAL: userInfoPane.html]
dan56
  • Enthusiast

  • 43 posts
  • Corona SDK

OK, after clicking submit, I see you posted some more.

 

Let me go back and sort it out



[TOPIC: post.html]
#10

roaminggamer

[GLOBAL: userInfoPane.html]
roaminggamer
  • Corona Geek

  • 7,562 posts
  • Corona SDK

Dan,
 
This will probably be my last response.  You've got way too many questions in the original post, so I'll just re-review your original post and provide terse answers.
 

Occasionally, some display objects from the last game aren’t cleared from the display.

If this happens, the object(s) were not inserted into scene group OR were not in a group that you later destroyed.
 
Go back and check your code for places where you did not insert a object into a group and/or did not destroy that group.
 

 

inserting all my display objects into groups and then inserting them into the sceneGroup.

 
Yes.  Do this.
 

At the end of every game/scene I call composer.removeScene() on scene:hide() intending to remove everything before the next scene. 

Do not do this as stated in your question.  Instead use the method I showed above where it is done in the 'did' phase of hide() and then only inside a call to timer.peformWithDelay() so the remove scene action occurs AFTER the scene  listener executes.



[TOPIC: post.html]
#11

roaminggamer

[GLOBAL: userInfoPane.html]
roaminggamer
  • Corona Geek

  • 7,562 posts
  • Corona SDK

I had to continue this in a second post because to 'quote' all your questions put me beyond the allowed 'quote' limit for a post.

I did discover an error where I had set one of my display objects to nil, without first removing it from the display. My first question is…

This isn't really clear.
 
If you're doing this, then you've got as big problem:
local obj = display.new*( ... )

obj = nil

... then later


display.remove(obj) -- DOES NOTHING
However, if you're clearing deleting the object by destroying the group it is in or by iterating over the group and destroying objects one by one, then you're fine.
 

 

Maybe I can explicitly remove all objects from the main stage 

Don't do that.  The main stage is not a scenegroup.  Leave it alone.
 
 
 

2) How can I clear out the display stage before I create a new scene?

Again, do not clear the (current) stage. Simply destroy any groups you created in the scene.
 
This is how I make my composer scenes to avoid ALL possible confusion about phases:
-- =============================================================
-- Your Copyright Statement Here, YYYY-YYYY
-- =============================================================
-- Scene Description Here
-- =============================================================
local composer       = require( "composer" )
local scene          = composer.newScene()

----------------------------------------------------------------------
--                        LOCALS
----------------------------------------------------------------------
local content

----------------------------------------------------------------------
--                        FORWARD DECLARATIONS
----------------------------------------------------------------------
-- None


----------------------------------------------------------------------
--   Scene Methods
----------------------------------------------------------------------

----------------------------------------------------------------------
----------------------------------------------------------------------
function scene:create( event )
   local sceneGroup = self.view
end

----------------------------------------------------------------------
----------------------------------------------------------------------
function scene:willShow( event )
   local sceneGroup = self.view
   --
   content = display.newGroup()

   --[[
   NOW PUT CONTENT YOU CREATE IN 'content' or a child of 'content' 
   --]]
end
----------------------------------------------------------------------
----------------------------------------------------------------------
function scene:didShow( event )
   local sceneGroup = self.view
end

----------------------------------------------------------------------
----------------------------------------------------------------------
function scene:willHide( event )
   local sceneGroup = self.view
end
----------------------------------------------------------------------
----------------------------------------------------------------------
function scene:didHide( event )
   local sceneGroup = self.view
   --
   -- Destroy all scene content in one fell-swoop.
   -- 
   display.remove( content )
   content = nil
end

----------------------------------------------------------------------
----------------------------------------------------------------------
function scene:destroy( event )
   local sceneGroup = self.view
end

----------------------------------------------------------------------
--            FUNCTION/CALLBACK DEFINITIONS
----------------------------------------------------------------------


---------------------------------------------------------------------------------
-- Scene Dispatch Events, Etc. - Generally Do Not Touch Below This Line
---------------------------------------------------------------------------------
function scene:show( event )
   local sceneGroup    = self.view
   local willDid    = event.phase
   if( willDid == "will" ) then
      self:willShow( event )
   elseif( willDid == "did" ) then
      self:didShow( event )
   end
end
function scene:hide( event )
   local sceneGroup    = self.view
   local willDid    = event.phase
   if( willDid == "will" ) then
      self:willHide( event )
   elseif( willDid == "did" ) then
      self:didHide( event )
   end
end
scene:addEventListener( "create", scene )
scene:addEventListener( "show", scene )
scene:addEventListener( "hide", scene )
scene:addEventListener( "destroy", scene )
---------------------------------------------------------------------------------
return scene


Edited by roaminggamer, 23 December 2018 - 01:20 PM.


[TOPIC: post.html]
#12

dan56

[GLOBAL: userInfoPane.html]
dan56
  • Enthusiast

  • 43 posts
  • Corona SDK

OK in sorting out the previous posts, I think I’m basically doing creating/deleting the way you recommend, except for the point about …

 

Do not call sceneRemove() in hide() unless you do it in the 'did' phase and delay it by at least 1ms:

 

I do call it in the “did” phase but I’m not using any delay now, and it seems like not doing that could causer a race / intermittent problem like I see.

 

So, I think my bottom line is:

 

  • Add the delay
  • Forget about the 3 mystery children. I don’t need any more mysteries right now.

 

Oh, and maybe one other bottom line task for me. I’m pretty sure I found that technique of calling removeScene() in hide() in one of the tutorials. I'll go back and hunt for that (for a little while at least). If I do find it, I’ll use that documentation flag to point out that a delay is required.

 

Thanks!


  • roaminggamer likes this

[TOPIC: post.html]
#13

dan56

[GLOBAL: userInfoPane.html]
dan56
  • Enthusiast

  • 43 posts
  • Corona SDK

Found it! The tutorial with scene:hide() calling composer.removeScene() is here: 

 

https://docs.coronalabs.com/guide/programming/05/index.html 

 

That tutorial says:  

 

With this simplified approach, let's modify our scene:hide() function:


-- hide()
function scene:hide( event )
 
    local sceneGroup = self.view
    local phase = event.phase
 
    if ( phase == "will" ) then
        -- Code here runs when the scene is on screen (but is about to go off screen)
        timer.cancel( gameLoopTimer )
 
    elseif ( phase == "did" ) then
        -- Code here runs immediately after the scene goes entirely off screen
        Runtime:removeEventListener( "collision", onCollision )
        physics.pause()
        composer.removeScene( "game" )
    end
end

This addition should be clear — we simply call composer.removeScene( "game" ) within the "did" phase of scene:hide(), effectively destroying the scene after it transitions fully off screen.

 

I flagged it.

 

Gotta go wrap Christmas presents!

 

Happy holiday to all.



[TOPIC: post.html]
#14

thomas6

[GLOBAL: userInfoPane.html]
thomas6
  • Contributor

  • 956 posts
  • Corona SDK

Making sure all (display) objects are properly removed is a big part of debugging and betatesting for me. All my displayGroups get a .name property that allows me to do some quick object-hunting. After playing a couple of levels I go to a special debugmodule that goes over all displayObjects on the currentStage. If there is a .name propery I output the name so I know who the culprit is. It there is no .name property I know it's not a group but instead 'visible' displayObject - I then set alpha to 1 and center it on the screen.

 

So either I see the objects visually, or I see the name of the group. That gives me an idea of where to look for bad cleanup routines.

 

That being said, my code structuring has becoming so much of an automatic routing that I rarely make errors of this kind.

 

Also, my whole game always runs in a top-level displayGroup. I can effectively run multiple copies of my game side by side if I want to, on a single device. Doing everything in a proprietary top-level displayGroups allows you to do quick-and-dirty object removal as well - although that's more of a fix than good coding habits.

 

On a final note: I don't use composer. Don't see the appeal for it either, but that's just my personal opinion.



[TOPIC: post.html]
#15

Rob Miracle

[GLOBAL: userInfoPane.html]
Rob Miracle
  • Moderator

  • 25,646 posts
  • Enterprise

@dan56 filled out a documentation request on this, and I thought I should share my response here:

 

Generally speaking, you should not remove the scene that's currently on the screen. After some testing, we found it's safe to remove the current scene in the scene:hide()'s "did" phase. This is what the Getting Started guide says. The Getting Started guide is meant to not overwhelm the new developer with more obscure use cases. It's likely safer to drop the composer.removeScene() inside a short timer to make sure all other things like pausing physics, timers being removed etc. actually happen before you remove the scene. In the case of the game you're building, it should be safe to do it without a timer, but other users may find a timer to be more practical:
 

function scene:hide( event )
    if ( event.phase == "did" ) then
        timer.performWithDelay( 100, function() composer.removeScene( "game" ); end )
    end
end

assuming of course that your scene is named "game". A 1-second timer will execute in the next frame which is either 1/30th of a second or 1/60th of a second later, not really in 1 millisecond. If your frame rate is 60 fps, the next frame will happen in 17 milliseconds, so any
time between 1 and 17 will be the same frame. It's also not going to hurt to wait even longer. Your user isn't going to react in a 10th of a
second to go back to your scene.

 

Now for some additional commentary...

 

Now, all that said, I am not a fan of removing scenes even in scene:hide() anyway. There is no harm leaving a scene in memory, unless you need to free the memory. It's more work, but the **right way** to reset a scene and be more computer efficient is to reset objects during scene:show()'s "will" phase. Move your objects back to their starting position, reset rotations, set physics starting values, etc. If you want to take advantage of scene caching, this is what you should do. Yes, it's more work on you. It takes planning and effort to get ti right.

 

Programmers are always looking for efficiency. That, of course, could be more efficient ways to do things like faster sorting methods, but frequently it's ways to write less code. Removing a scene is a brute force way of resetting a scene, but it's just that a brute force method. Yes, you write one line of code vs. potentially dozens or hundreds, but you're causing the system to have to recreate things, reload images from storage, etc. While one developer might call it programmer efficiency, others could call it a lazy hack.

 

It's one I'm quite guilty of in my own apps and games. I will remove the scene to reset it. However, I almost never reset it in the scene:hide() "did" phase. Instead, I remove the scene before I go to it. It's pretty easy to do:

composer.removeScene("game")
composer.gotoScene("game")

You're not messing with timers. You're not messing if the scene is still on the screen or not. You're not messing with deleting yourself.

 

And if you're thinking about saving lines of code, it doesn't really save that much. If you're already doing:

 

local player = display.newImageRect("player.png", 100, 1000)

sceneGroup:insert(player)

player.x = 50

player.y = display.contentCenterY

physics.addBody( player, ...)

player:addEventListener( "touch", playerTouchHandler )

 

 

in scene:create(), it's not that much more work to:

local player -- at the top of the module outside of any functions for scope

in scene:create()

player = display.newImageRect("player.png", 100, 1000)
sceneGroup:insert(player)
 

 

in scene:show()

if event.phase == "will" then
    player.x = 50
    player.y = display.contentCenterY
    physics.addBody( player, ...)
    player:addEventListener( "touch", playerTouchHandler )end
 
That's one more line of code. But you would want to remove the physics body in scene:hide()'s "did" phase. You may need to set the initial rotation, and physics properties too, which is where the extra code comes into play.

 
Rob


  • Quitalizner likes this


[topic_controls]
[/topic_controls]