Jump to content

[TOPIC: topicViewTemplate]
[GLOBAL: userSmallPhoto]
Photo

composer bug: function show (did) running twice
Started by RedBeach May 17 2017 02:54 PM

26 replies to this topic
bug composer show did will phase twice running
[TOPIC CONTROLS]
Page 1 of 2 1 2
This topic has been archived. This means that you cannot reply to this topic.
[/TOPIC CONTROLS]
[modOptionsDropdown]
[/modOptionsDropdown]
[reputationFilter]
[TOPIC: post.html]
#1

RedBeach

[GLOBAL: userInfoPane.html]
RedBeach
  • Corona Geek

  • 1,132 posts
  • Corona SDK

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



[TOPIC: post.html]
#2

roaminggamer

[GLOBAL: userInfoPane.html]
roaminggamer
  • Corona Geek

  • 6,373 posts
  • Corona SDK

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?



[TOPIC: post.html]
#3

RedBeach

[GLOBAL: userInfoPane.html]
RedBeach
  • Corona Geek

  • 1,132 posts
  • Corona SDK

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

Corona SDK Build: 2017.3079

 

You can see the console for the prints.



[TOPIC: post.html]
#4

RedBeach

[GLOBAL: userInfoPane.html]
RedBeach
  • Corona Geek

  • 1,132 posts
  • Corona SDK

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).



[TOPIC: post.html]
#5

roaminggamer

[GLOBAL: userInfoPane.html]
roaminggamer
  • Corona Geek

  • 6,373 posts
  • Corona SDK

@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 )



[TOPIC: post.html]
#6

roaminggamer

[GLOBAL: userInfoPane.html]
roaminggamer
  • Corona Geek

  • 6,373 posts
  • Corona SDK

Looks like we were typing at the same time.



[TOPIC: post.html]
#7

roaminggamer

[GLOBAL: userInfoPane.html]
roaminggamer
  • Corona Geek

  • 6,373 posts
  • Corona SDK

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


Edited by roaminggamer, 17 May 2017 - 03:26 PM.


[TOPIC: post.html]
#8

RedBeach

[GLOBAL: userInfoPane.html]
RedBeach
  • Corona Geek

  • 1,132 posts
  • Corona SDK

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...



[TOPIC: post.html]
#9

RedBeach

[GLOBAL: userInfoPane.html]
RedBeach
  • Corona Geek

  • 1,132 posts
  • Corona SDK

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.



[TOPIC: post.html]
#10

Rob Miracle

[GLOBAL: userInfoPane.html]
Rob Miracle
  • Moderator

  • 23,695 posts
  • Corona Staff

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



[TOPIC: post.html]
#11

roaminggamer

[GLOBAL: userInfoPane.html]
roaminggamer
  • Corona Geek

  • 6,373 posts
  • Corona SDK

@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

Edited by roaminggamer, 17 May 2017 - 03:46 PM.


[TOPIC: post.html]
#12

RedBeach

[GLOBAL: userInfoPane.html]
RedBeach
  • Corona Geek

  • 1,132 posts
  • Corona SDK

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

 

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.



[TOPIC: post.html]
#13

Rob Miracle

[GLOBAL: userInfoPane.html]
Rob Miracle
  • Moderator

  • 23,695 posts
  • Corona Staff

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 )
     -- do stuff in the scene
     -- 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



[TOPIC: post.html]
#14

RedBeach

[GLOBAL: userInfoPane.html]
RedBeach
  • Corona Geek

  • 1,132 posts
  • Corona SDK

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,



[TOPIC: post.html]
#15

RedBeach

[GLOBAL: userInfoPane.html]
RedBeach
  • Corona Geek

  • 1,132 posts
  • Corona SDK

Thanks for all the code and help @roaminggamer



[TOPIC: post.html]
#16

Rob Miracle

[GLOBAL: userInfoPane.html]
Rob Miracle
  • Moderator

  • 23,695 posts
  • Corona Staff

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 )
      scene.setVariable( "phase", event.phase)
      if event.phase == "will" then
            ...
      else
            ...
      end
end

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

 

Rob



[TOPIC: post.html]
#17

RedBeach

[GLOBAL: userInfoPane.html]
RedBeach
  • Corona Geek

  • 1,132 posts
  • Corona SDK

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....



[TOPIC: post.html]
#18

davebollinger

[GLOBAL: userInfoPane.html]
davebollinger
  • Corona Geek

  • 1,081 posts
  • Enterprise

Do you know a way to intercept all "show" functions?

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

...cut...

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", ...



[TOPIC: post.html]
#19

RedBeach

[GLOBAL: userInfoPane.html]
RedBeach
  • Corona Geek

  • 1,132 posts
  • Corona SDK

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.



[TOPIC: post.html]
#20

roaminggamer

[GLOBAL: userInfoPane.html]
roaminggamer
  • Corona Geek

  • 6,373 posts
  • Corona SDK

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


Edited by roaminggamer, 18 May 2017 - 09:54 AM.


[TOPIC: post.html]
#21

roaminggamer

[GLOBAL: userInfoPane.html]
roaminggamer
  • Corona Geek

  • 6,373 posts
  • Corona SDK

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 )


[TOPIC: post.html]
#22

RedBeach

[GLOBAL: userInfoPane.html]
RedBeach
  • Corona Geek

  • 1,132 posts
  • Corona SDK

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.



[TOPIC: post.html]
#23

davebollinger

[GLOBAL: userInfoPane.html]
davebollinger
  • Corona Geek

  • 1,081 posts
  • Enterprise

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.

 

 

 

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



[TOPIC: post.html]
#24

Rob Miracle

[GLOBAL: userInfoPane.html]
Rob Miracle
  • Moderator

  • 23,695 posts
  • Corona Staff

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



[TOPIC: post.html]
#25

RedBeach

[GLOBAL: userInfoPane.html]
RedBeach
  • Corona Geek

  • 1,132 posts
  • Corona SDK

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



[topic_controls]
Page 1 of 2 1 2
 
[/topic_controls]