touch / tap event endless loop bug?

I’m was trying to implement some kind of a state machine, and found myself with the following strange behavior:

-- create a large button  
local rect = display.newRect( 0, 0, display.contentWidth, display.contentHeight )  
rect:setFillColor( 128, 64, 64 )  
  
-- state1  
function state1Cb( evt )  
  
 print("state1")  
 rect:removeEventListener( "tap", state1Cb )  
 rect:addEventListener( "tap", state2Cb )  
  
end  
  
-- state2  
function state2Cb( evt )  
  
 print("state2")  
 rect:removeEventListener( "tap", state2Cb )  
 rect:addEventListener( "tap", state1Cb )  
  
end  
  
-- start  
rect:addEventListener( "tap", state1Cb )  

This code results in an endless loop
Apparently, the event gets triggered again and again…

I’ve been creating games in Flash/actionscript for quite some time now, and I don’t recall such a behavior.
I’m a newbie in Lua, though…
Is it normal, or is it a bug?

If it isn’t a bug, how can I implement such a state machine (I’m trying to port some of my actionscript infra to Lua/Corona)?

Appreciate any help,
EZ [import]uid: 9536 topic_id: 2762 reply_id: 302762[/import]

Just throwing out the first idea off the top of my head, cant say its the best idea.

You may have to change to a “touch” event type, instead of tap. “tap” fires off when, basically, the touch phase == “ended”.

Something like this might work:

-- create a large button  
local rect = display.newRect( 0, 0, display.contentWidth, display.contentHeight )  
rect:setFillColor( 128, 64, 64 )  
   
-- state1  
function state1Cb( evt )  
  
 print("state1")  
 if (evt.phase == "ended") then  
 rect:removeEventListener( "touch", state1Cb )  
 rect:addEventListener( "touch", state2Cb )  
 end  
end  
   
-- state2  
function state2Cb( evt )  
  
 print("state2")  
 if (evt.phase == "ended") then  
 rect:removeEventListener( "touch", state2Cb )  
 rect:addEventListener( "touch", state1Cb )  
 end  
end  
   
-- start  
rect:addEventListener( "touch", state1Cb )  
  

Not sure though. Perhaps some set of “began” and “ended” might work… would have to play around with it. [import]uid: 8541 topic_id: 2762 reply_id: 8389[/import]

Unfortunately, it acts the same with the Touch event.
Of course this can be solved using a different logic (listening to “began” phase and then to “ended”) but I’m still puzzled about this strange result… [import]uid: 9536 topic_id: 2762 reply_id: 8410[/import]

Probably has something to do with the event stack.

I guess technically, since the first tap event is called, within that event call you are adding to the event stack of the same object. Maybe it looks like this:

rect:event:tap -> state1Cb (state1Cb adds the next event to the stack
rect:event:tap ->state2Cb (Corona clears the prev event, but notices this one too, so calls it)
rect:event:tap ->state1Cb (Corona clears the prev event, but notices this one too, so calls it)
rect:event:tap ->state2Cb (Corona clears the prev event, but notices this one too, so calls it)
rect:event:tap ->state1Cb (Corona clears the prev event, but notices this one too, so calls it)

Looks like an endless cycle.

You might just change the one function to point to alternate to another function:

local function\_stack = {}  
function\_stack[1] = state1Cb   
function\_stack[2] = state2Cb  
  
-- create a large button  
local rect = display.newRect( 0, 0, display.contentWidth, display.contentHeight )  
rect:setFillColor( 128, 64, 64 )  
   
-- state1  
function state1Cb( evt )  
  
 print("state1")  
  
  
end  
   
-- state2  
function state2Cb( evt )  
  
 print("state2")  
   
  
end  
   
  
function rect\_tap\_handler(event)  
 function\_stack[event.target.state](event)  
 if (rect.state == 1) then  
 event.target.state= 2  
 else  
 event.target.state= 1  
 end  
  
end  
  
-- start  
rect.state = 1  
rect:addEventListener( "tap", rect\_tap\_handler)  

Essentially you have one addEventListener call, and within that one, it calls the appropriate function based on the current state.

I am with you though, it seems odd that Corona would not know that the event added does not correspond with the current event it is firing.

One last thought that might work, dunno though, is to add return true to the touch event handlers that you already have:

-- create a large button  
local rect = display.newRect( 0, 0, display.contentWidth, display.contentHeight )  
rect:setFillColor( 128, 64, 64 )  
   
-- state1  
function state1Cb( evt )  
  
 print("state1")  
 rect:removeEventListener( "tap", state1Cb )  
 rect:addEventListener( "tap", state2Cb )  
 return true   
end  
   
-- state2  
function state2Cb( evt )  
  
 print("state2")  
 rect:removeEventListener( "tap", state2Cb )  
 rect:addEventListener( "tap", state1Cb )  
 return true   
end  
   
-- start  
rect:addEventListener( "tap", state1Cb )  

return true tells Corona to stop going down the objects that are visible looking for another object that might have a touch event… it stops its search right there at that object. [import]uid: 8541 topic_id: 2762 reply_id: 8418[/import]

Its a bug definately. On my machine it runs in a loop and you can’t stop the simulator. [import]uid: 5712 topic_id: 2762 reply_id: 8432[/import]

Perhaps because the events won’t remove until the screen redraws again, or until the next “frame”.

I’d go with sanchan91’s variable-setting approach. [import]uid: 4454 topic_id: 2762 reply_id: 8502[/import]

Sure a good work around till this bug is fixed. [import]uid: 5712 topic_id: 2762 reply_id: 8572[/import]

Yeap, I’ve implemented a workaround.
It will be interesting to hear from Ansca team and know if this is a bug or not.

Thanks for your help, guys [import]uid: 9536 topic_id: 2762 reply_id: 8577[/import]

I posted an issue about this:

http://developer.anscamobile.com/issues/2981 [import]uid: 5712 topic_id: 2762 reply_id: 8579[/import]

Thanks Mike, looking forward for that bug fix…

Until then, here is a clean workaround for this problem:

local rect1 = display.newRect( 0, 0, display.contentWidth, display.contentHeight )  
rect1:setFillColor( 128, 64, 64 )  
  
local rect2 = display.newRect( 0, 0, display.contentWidth, display.contentHeight )  
rect2:setFillColor( 64, 1287, 64 )  
  
-- state  
function state1Cb( evt )  
  
 print("state1")  
  
 rect1:removeEventListener( "tap", state1Cb )  
  
 rect1.isVisible = false  
 rect2.isVisible = true  
  
 rect2:addEventListener( "tap", state2Cb )  
  
end  
  
-- state2  
function state2Cb( evt )  
  
 print("state2")  
  
 rect2:removeEventListener( "tap", state2Cb )  
  
 rect1.isVisible = true  
 rect2.isVisible = false  
  
 rect1:addEventListener( "tap", state1Cb )  
  
end  
  
-- start  
rect2.isVisible = false  
rect1:addEventListener( "tap", state1Cb )  

For whoever needs it (I do for my game state machine)… [import]uid: 9536 topic_id: 2762 reply_id: 9370[/import]

I’m having this same issue. Has there been any fixes for this yet? What’s the best work around?
What I’m trying to do is have an image with 2 different states.(i have 4 different images. ) When pressed the first time-move to center of the stage. (then removeEventListener to the other 3 images) When pressed again, go back to original state (and addEventListeners to all of them). Shouldn’t be this hard. I’m sure I’m missing something.

Thanks.
[import]uid: 11144 topic_id: 2762 reply_id: 21957[/import]

Instead of using two different functions (and thus two different event listeners) for each state, have one function that addresses both states. Or rather a single function that passes code execution along to the correct other function. Most of the times I’ve seen people talk about state machines the code is implemented with a switch/case statement. Taking into account what’s discussed in this thread:
http://developer.anscamobile.com/forum/2011/02/10/there-equivalent-lua-javascript-switch-case-n

then the code would look like:

local rect = display.newRect(0, 0, display.contentWidth, display.contentHeight)  
local state = 1  
  
local function state1()  
 print("state1")  
 state = 2  
end  
  
local function state2()  
 print("state2")  
 state = 1  
end  
  
local function onTap(event)  
 if state == 1 then state1()  
 elseif state == 2 then state2()  
 end  
end  
  
rect:addEventListener("tap", onTap)  

EDIT: Oh wait this was already suggested by sanchan. In which case I don’t know why you aren’t doing that, it’s the most robust approach. Or why people are still considering this a bug, and that approach is a workaround; it seems a lot more hackish to me to mess around with turning events on and off instead of one clean handler to direct traffic. [import]uid: 12108 topic_id: 2762 reply_id: 21958[/import]

This looks related to this: http://developer.anscamobile.com/forum/2011/08/01/possible-addeventlistener-bugs

I’ve reimplemented the event dispatching code which solves the infinite loop issue among other things. [import]uid: 27183 topic_id: 2762 reply_id: 60852[/import]