composer bug: function show (did) running twice

Just submitted this bug to Corona (although I didn’t receive the bug submission email confirmation…), but I like to share here on the forum so other developers can be aware of the bug.

Bug: the show function (phase ‘did’) of a scene is being called twice 

How to reproduce:

Create 2 composer scene files ( let’s say scene A & scene B ).  On the Scene A  show function, inside the “will” phase,  call composer.gotoScene(“sceneB”).  

The “show” function (phase ‘did’) of scene B will be called twice.

Why it is happening and how to overcome:

That bug happens when you call the gotoScene()  from the ‘will’ phase.  So, a way to overcome this bug is to move the gotoScene() do the ‘did’ phase.

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

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

I was thinking that in the will phase, set a flag using setVariable() that you’re in the will phase. When it’s the show phase, set the flag to be “did”.

function scene:show( event ) &nbsp; &nbsp; &nbsp; scene.setVariable( "phase", event.phase) &nbsp; &nbsp; &nbsp; if event.phase == "will" then &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; ... &nbsp; &nbsp; &nbsp; else &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; ... &nbsp; &nbsp; &nbsp; end end

Then your module can know what phase you’re in.

Rob

Do you know a way to intercept all “show” functions?

For example, today I intercept all .gotoScene() functions by doing:

local composer = require("composer") local gotoScene = composer.gotoScene composer.gotoScene = function(a,b) print("I am intercepting gotoScene. Doing something here...") gotoScene(a,b) end

Since the ‘show’ is a listener function specific for each scene, I am not finding a simple way to do it…

it won’t be easy, because what you’d need to do is monkey-patch dispatchEvent() (as you did with gotoScene).

the problem is, you need to do that on each *scene*, not on composer.

and the problem is compounded because your scene may not “exist” prior to your call to gotoScene() unless you explicity pre-load it…

basically it would look something like this  (consider this pseudocode, just to illustrate concepts):

-- in scene1, trying to goto scene2 local otherScene = composer.loadScene("scene2", ...any other needed params -- now you have the preloaded scene, you can monkey with it -- (you could write a helper function to apply this monkeying, i'll just do it inline) otherScene.\_dispatchEvent = otherScene.dispatchEvent -- save original otherScene.dispatchEvent = function(self, event) print("I intercepted this event:", event.name) if (some condition that says i still want original behavior) then self:\_dispatchEvent(event) -- call original else -- do something OTHER than default behavior end end composer:gotoScene("scene2", ...

Thanks for the help @davebollinger. Having to add a code on each scene is something that I was trying to avoid.

I was able to find a simple fix for the bug with a single code/require.

This is my solution:

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 == "show" and currScene.\_currEventPhase == "will" then --print("current Scene is on '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(10, 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

and on my main.lua I just require that file:

require "rb-composer"

That file (rb-composer) basically adds to every scene its last event info and also overwrites the composer.gotoScene forcing it to check if the current scene is not on a show/will phase. If it is, it delays the gotoScene until it is not.

That solution allows me to avoid the bug and not require me to make any other change on my project.

Thanks for all the help guys.

UPDATE: Simplified and fixed a bug.

I would take a slightly different route and only wrap the show event.
 
Modify composer.newScene() to wrap scene:addEventListener() to add custom code for the “show” listener.

-- not tested for typos local composer = require("composer") composer.\_newScene = composer.newScene function composer.newScene( ... ) local scene = composer.\_newScene(unpack(arg)) scene.\_addEventListener = scene.addEventListener function scene.addEventListener( self, name ) -- listener arg dropped since it is same as scene if( name == "show" ) then scene.\_show = scene.show local lastDid = 0 function scene.show( self, event ) curTime = system.getTimer() if( event.phase == "did" ) then if( curTime - lastTime \< 10 ) then return end lastTime = curTime self.\_show( self, event ) else self.\_show( self, event ) end end end self:\_addEventListener( name ) end end