Quick Links: Download Gideros Studio | Gideros Documentation | Gideros community chat | DONATE
Huge-seeming problem with event system — Gideros Forum

Huge-seeming problem with event system

kamcknigkamcknig Member
edited October 2012 in Bugs and issues
I am attaching a zip of my test project that I recreated the problem in. I am using a central object that is an event dispatcher that certain objects can register to events for. But it seems that in some circumstances, some event listeners don't get called correctly. If you run the project, I'd expect the traces to be this:

1. MainModel:init()
2. GameModel:init()

Click the stage

3. onMouseDown
4. MainModel.onGameWon
5. GameModel.onGameWon
6. After dispatch

But instead the order is

1. MainModel:init()
2. GameModel:init()

Click the stage

3. onMouseDown
4. MainModel.onGameWon
5. After dispatch

As you can see, GameModel which is also listening for the GAME_WON event never gets fired at all. This seems to be a huge issue unless I'm misunderstanding things. This has caused us to MAJORLY overhaul our game structure and has caused us to have to couple a lot of things together that we'd rather not do. Is this intended behavior? If so can you explain why it is? And if there is a workaround?

Comments

  • zvardinzvardin Member
    edited October 2012
    Hm... the error seems to be with removeEventListener. If you comment out that line in the onGameWon function then the event will continue to propagate as expected. I tried changing the onGameWon function name in one class and it still happened this way, so it seems removeEventListener is removing all listeners for "GAME_WON". This would seem to be a bug. I know at one point @atilim was going to add ways to remove all listeners of a type etc if I recall, so maybe this got introduced recently.
  • kamcknigkamcknig Member
    edited October 2012
    I do believe it is a bug, but did not find myself that it was with teh removeEventListener so thanks for finding that! Hmm. I think I will have to keep my implementation until @atilim can look at it.
  • bowerandybowerandy Guru
    edited October 2012
    I suspect this is a bug, probably caused by that age old problem of removing something from a list while enumerating through it. My guess is that somewhere in the depths of the dispatchEvent() code, @atilim will have to make a copy of the target list first before enumerating through it.

    Until then, I think you'll have to not use removeEventListener() within an event hander for the same event.

    best regards
  • OK great thank you!
  • Hey @atilim, can we get a comment on this? This is a REALLY big bug. I have GameModel that dispatches a GAME_WON event through an event dispatcher called EventStream. My MainModel listens for this event on EventStream and in its event handler, removes the listener for GAME_WON. Then random other objects that were also listening for that event will no longer receive it. We REALLY need a fix for this or at least a comment on it to know whats going on. We are at a point now where finding a workaround is basically impossible at this point.
  • atilimatilim Maintainer
    Sorry for not being around for some time :) Now I'll look at it.
  • @kamcknig, it's a slightly obscure thing that you're doing here ; to remove a listener while it is being processed. If there's no way that you can restructure your code then there's one thing you could try. Instead of actually removing the listener immediately you could set a flag and then check this inside an ENTER_FRAME handler and do the remove in there.

    Best regards
  • @bowerandy, that seems like a good potential workaround. I don't think what we're doing is terribly obscure. Multiple listeners should be able to remove their listeners without affecting the other listeners. We'll do what we can for now but it would be nice to get a fix for this soon as we're approaching our deadline.
  • But I'm removing a different handler. Each handler should be separate shouldn't it?
  • bowerandybowerandy Guru
    edited October 2012
    @kamcknig, yes they *should* appear to be separate but in reality they are probably held inside Gideros as part of the same list.

    In your example in your MainModelClass:onGameWon() handler function you are removing the GAME_WON handler for the MainModelObject. However, that handler is probably stored in a list called "GAME_WON" associated with the EventStream class object.

    At the the point you are removing this handler, Gideros is likely to be traversing that list. This fiddling around with the contents may affect any other GAME_WON handlers that are also on the same list, e.g. the one for the gameModel object.

    Anyway, we probably should wait for @atilim to confirm. In the meantime, try the workaround:
    -- In main.lua add this...
    --
    EventQ={}
     
    stage:addEventListener(Event.ENTER_FRAME, 
    	function()
                    -- Traverse Q executing all queued functions. Then clear it down.
    		for i,func in ipairs(EventQ) do
    			func()
    		end
    		EventQ={}
    	end)
     
    function PostToQ(func)
    	-- Defer the execution of a nialdic function, func, until some
    	-- later point (i.e. during the next frame update)
    	--
    	EventQ[#EventQ+1]=func
    end
     
    -- Then later in MainModel.lua
    --
    function MainModelClass.onGameWon(self, event)
    	print("MainModel.onGameWon");
    	PostToQ(function() EventStream:removeEventListener("GAME_WON",
                 self.onGameWon, self) end)
    end
    Seems to fix it, I think.

    Actually, @atilim or @ar2rsawseen, you might want to consider adding this feature (or something like it) to the system or the EasyGideros init library. I have found it to be very useful in the past (and not just to workaround bugs). In a previous system I worked with it was called postToInputQueue() and took a block of code to be executed next time the UI input thread was serviced.

    Among other things it can be useful for queueing up stuff to happen once an app is fully initialised and the UI loop has started processing events.

    best regards

    Likes: atilim

    +1 -1 (+1 / -0 )Share on Facebook
  • atilimatilim Maintainer
    edited October 2012 Accepted Answer
    exactly as @bowerandy said: "problem of removing something from a list while enumerating through it". Listeners are stored in a linear array and if an element is removed while enumerating through, indices shift.

    Here is my simplified test code:
    dispatcher = EventDispatcher.new()
     
    foo1 = function()
    	print("foo1")
    	dispatcher:removeEventListener("foo", foo1)
    end
     
    foo2 = function()
    	print("foo2")
    end
     
    foo3 = function()
    	print("foo3")
    end
     
    dispatcher:addEventListener("foo", foo1)
    dispatcher:addEventListener("foo", foo2)
    dispatcher:addEventListener("foo", foo3)
     
    dispatcher:dispatchEvent(Event.new("foo"))
    By now, every time I call a listener/callback, I remember that "careful! when you make a callback anything can happen" including adding/removing elements to the callback list while enumerating and then I remember that copying the data before enumerating is one of the inefficient solutions. In this case, I don't know how I forgot this rule.

    Anyway, now I fixed it. You can expect a new release in 3-4 days. Thank you for reporting.
  • Thanks so much both of you! @atilim and @bowerandy
  • Thanks guys! I'm glad the Gideros staff is attentive and quick to make fixes. Looking forward to the update!
  • @atilim Just curious if there has been any update on this?
  • Will something like
    Timer.delayedCall(0, function() dispatcher:removeEventListener("foo", foo1) end)
    be a workaround?

    Maybe if there is a function names nextTick(func) can do it more efficiently.

    Likes: hgvyas123

    +1 -1 (+1 / -0 )Share on Facebook
  • @kamcknig I think the newer version was not published yet, so no fixes yet. Since it is Turkey's national holiday, I think we'll have a delay on that :)
  • @ar2rsawseen, oh yes! That's right, thanks :)
Sign In or Register to comment.