composer bug: function show (did) running twice

Tip - The prior code will ONLY work if you make your scenes like this:

function scene:create( event ) end function scene:hide( event ) end function scene:hide( event ) end function scene:destroy( event ) end scene:addEventListener( "create", scene ) scene:addEventListener( "show", scene ) scene:addEventListener( "hide", scene ) scene:addEventListener( "destroy", scene )

Thanks for the extra suggestion @roaminggamer.

Trying your code I found 2 typos (1st:  your composer.newScene function needs to return scene object;  2nd:  need to change ‘lastDid’ with ‘lastTime’).

Your solution aims in trapping the 2nd call to the show function in a short period of time (in this case, 10 ms). Testing here, it did avoid the 2nd show call, but it does not avoid a 2nd call to first scene hide function (see log below).

Normal composer behavior (bug):

scene A - on create scene A - on show will scene A - on hide will scene B - on create scene B - on show will scene A - on hide did scene B - on show did scene A - on hide did \<\<-- called TWICE! scene B - on show did \<\<-- called TWICE!

You solution:

scene A - on create scene A - on show will scene A - on hide will scene B - on create scene B - on show will scene A - on hide did scene B - on show did scene A - on hide did \<\< -- called TWICE!

I am using the solution that I posted above and it is working fine. I am trapping all events just because I want to, but I could just trap the “show” event.

welcome.  looks like you no longer need it, but…  you could have used your modifed newScene as the “helper function” i mentioned, might’ve spared having to add the four new listeners

I haven’t seen this come into the ticketing system yet. Have you had a chance to email support AT coronalabs.com with the project.zip and a good description of the problem yet?

Thanks

Rob

I just noticed that the bug also occurs if you call the composer.gotoScene()  from the scene:create(). In that case, not only the show/did function is called twice, but also the show/will.

So, an updated fix would be:

file: rb-composer.lua

local composer = require "composer" composer.\_gotoScene = composer.gotoScene composer.gotoScene = function(sceneName, options) local currScene = composer.getScene( composer.getSceneName( "current" ) ) if currScene then if currScene.\_currEventName == "create" or (currScene.\_currEventName == "show" and currScene.\_currEventPhase == "will") then print("current Scene is on 'CREATE' or 'SHOW - WILL'. Let's not execute the gotoScene now due to composer bug. Let's try again in a few so the scene can be on 'SHOW - DID'") return timer.performWithDelay(50, function() composer.gotoScene(sceneName, options) end) end end composer.\_gotoScene(sceneName, options) end composer.\_newScene = composer.newScene composer.newScene = function() local scene = composer.\_newScene() function scene.trap\_event(event) scene.\_currEventName = event.name scene.\_currEventPhase = event.phase end scene:addEventListener( "create", scene.trap\_event ) scene:addEventListener( "show", scene.trap\_event ) scene:addEventListener( "hide", scene.trap\_event ) scene:addEventListener( "destroy", scene.trap\_event ) return scene end

my guess it that CL will “fix” your bug by amending the documentation to read “Calling gotoScene() while a prior scene transition is still in effect is not support and may lead to unexpected behavior.” or something to that effect (in other words, just citing some version of your original problem description as an unsupported operation).

you might try posting the code that’s CAUSING all these unwanted gotoScene’s that you need to cancel after initiated, then maybe someone could suggest a workaround that prevents the problem before it even occurs.  there would seem to be several ways your “outside module” could submit a *request* for a scene change that is “deferred” (or cancelled outright) if/as necessary by some other “scene manager” -type code capable of monitoring those requests versus current scene states.

Can you post a link to your sample code here? i.e. The zipped up project you would have had to attach to the bug report.

I’d like to take a look at this.

PS - What version of Corona?

https://www.dropbox.com/s/i3toht3wplxdo36/code.zip?dl=0

Corona SDK Build: 2017.3079

You can see the console for the prints.

In my specific case, I cannot simply move the gotoScene() from the “will” phase to the  “did” phase (because it is an external module who is calling the gotoScene, so it does not have visibility if the scene is already visible in the screen or not).

@redbeach,

After posting, I took an example of my own and reproduced this with 2017.2077

Tip:

In the short-term, you should be able to avoid this as follows (duration of timer may need adjusting):

timer.performWithDelay( 1, function() local options = { effect = "slideLeft", time = 500 } composer.gotoScene( "ifc.scene2", options ) end )

Looks like we were typing at the same time.

Here is another short-term solution for you if you are blocked and need to progress ( uses my special flavor of composer file layout, but you should be able to see what I’m doing right away ):

local lastDid function scene:didEnter( event ) local sceneGroup = self.view -- This bit of code prevents short-term multi-entry local curTime = system.getTimer() if( lastDid and (curTime - lastDid) \< 10 ) then return end lastDid = curTime --- ... add normal work here 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:willEnter( event ) elseif( willDid == "did" ) then self:didEnter( event ) end end function scene:hide( event ) local sceneGroup = self.view local willDid = event.phase if( willDid == "will" ) then self:willExit( event ) elseif( willDid == "did" ) then self:didExit( event ) end end scene:addEventListener( "create", scene ) scene:addEventListener( "show", scene ) scene:addEventListener( "hide", scene ) scene:addEventListener( "destroy", scene ) --------------------------------------------------------------------------------- return scene

Hummm… but your workaround (adding a timer, even if it is only 1ms), it would basically have the effect of not having the gotoScene() being run during the ‘will’ phase.  Since I have an external module calling the code, the timer could have the opposite effect (e.g:, the code would not run during the ‘will’ phase, but due to the delay, now it will).  Does that make sense?

I believe that the best solution would be to have a way to check on which phase (‘will’ or ‘did’) the current composer scene is in, and then only call the gotoScene when it entered on the ‘did’ phase. Just trying to find a way to do that…

Just saw your other solution.  Interesting wrapper. I think a wrapper would be the way to go, let me take a look on your code more carefully.

The bug submission form is currently not working. We are just about ready to get the new form in place. In the mean time please email all the information to support AT coronalabs.com

Calling composer.gotoScene() before the current scene is on screen seems like an odd behavior. If you’re not going to show the scene, why go to it in the first place? 

Rob

@RedBeach,

FYI.  If you wanted my version of composer file, here is an empty skeleton.  I like to have the will and did phases broken down into methods.  I find this much easier to use and clearer when showing folks ‘how to do XYZ’.

Note: The prior code snippet I shared was from an older copy of the template where I still used the old storyboard.* naming convention for the methods.  Sorry if that was confusing.

-- ============================================================= -- -- ============================================================= local composer = require( "composer" ) local scene = composer.newScene() ---------------------------------------------------------------------- -- Locals ---------------------------------------------------------------------- ---------------------------------------------------------------------- -- Scene Methods ---------------------------------------------------------------------- function scene:create( event ) local sceneGroup = self.view end ---------------------------------------------------------------------- function scene:willShow( event ) local sceneGroup = self.view 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 end ---------------------------------------------------------------------- function scene:destroy( event ) local sceneGroup = self.view end ---------------------------------------------------------------------- -- Custom Scene Functions/Methods ---------------------------------------------------------------------- --------------------------------------------------------------------------------- -- Scene Dispatch Events, Etc. - Generally Do Not Touch Below This Line --------------------------------------------------------------------------------- -- This code splits the "show" event into two separate events: willShow and didShow -- for ease of coding above. 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 -- This code splits the "hide" event into two separate events: willHide and didHide -- for ease of coding above. 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

I have an outside module (i.e, not a composer scene) that keeps monitoring for a specific event and when that event happens, it call the gotoScene(). Since it is an outside module, it does not have any idea of the current scene on screen (and its phase).  So, that bug happens when a scene is being opened and my module calls the gotoScene while that scene was still on its way to be on screen.

You can certainly set flags using the composer.setVariable/.getVariable functions and your non-composer scene module can certainly access those. You just have to require composer in your module.

Secondly, and I do this a lot, I will use something like this:

 local thisSceneName = composer.getSceneName( "current" ) print("thisSceneName ", thisSceneName ) local thisScene = composer.getScene( thisSceneName ) thisScene.view:insert( self.bullet[idx] )

In this case my module is creating a bullet and I want it in the current scene’s view group. You can add functions to your scene that modules could call doing something like this:

-- composer scene function scene:doSomethingSpecial( params ) &nbsp; &nbsp; &nbsp;-- do stuff in the scene &nbsp; &nbsp; &nbsp;-- self is the scene object end

-- external module local thisSceneName = composer.getSceneName( "current" ) print("thisSceneName ", thisSceneName ) local thisScene = composer.getScene( thisSceneName ) thisScene:doSomethingSpecial( x, y, x)

There are all kinds of possibilities. You could in each phase of show, set a variable in the scene that has the current phase so your module can monitor it.

Rob

Hi @Rob

I know I can require the composer library and get the scene object and other information. But from what I read, the current ‘phase’ (will or did) of the scene on screen is not a information that I have access to, unless I pass the “event” from the composer scene to my object. And yes, adding a function call on the show function would allow me to solve the problem. I am now searching if I have a simple and direct way to add a wrapper around all show functions (so I don’t have to add that my function call to each composer scene), but if not, I will end up doing what you said.

Thanks,

Thanks for all the code and help @roaminggamer