Quick Links: Download Gideros Studio | Gideros Documentation | Gideros Development Center | Gideros community chat
Creating and dispatching Event more efficiently - Gideros Forum

Creating and dispatching Event more efficiently

totebototebo Member
edited June 2016 in General questions
I've started optimizing my sprite-heavy game, and am learning a lot about what causes CPU slowdown. One such, unexpected, culprit has turned out to be dispatching Event(s). If I dispatch (and catch) events regularly this slows down the game quite a bit more than I expected.

I'm very much in favour of using events, because they help keep the code clean, so I was wondering if there is a more efficient way of creating and dispatching them? The alternative is calling on methods directly, which doesn't feel as neat and would require me to refactor quite a bit of code.

I have a feeling what the answer will be (@sinistersoft!), but thought I'd at least ask the question.

Likes: SinisterSoft

My Gideros games: www.totebo.com
+1 -1 (+1 / -0 ) Share on Facebook
«1

Comments

  • You can define call backs instead of events.
    +1 -1 (+2 / -0 ) Share on Facebook
  • totebototebo Member
    Callbacks, that's the word I was looking for. That's what I meant by "calling on methods directly".
    My Gideros games: www.totebo.com
  • SinisterSoftSinisterSoft Maintainer
    @totebo yes, my suggestion is to stop using all the event - just use a single one for the frame event and a couple for i/o
  • totebototebo Member
    @sinister, cheers. I've started breaking up the code to allow for this. Any hints and tips for defining the callbacks? I am ripping out quite a few events, and feel my OOP is a gonner. :)
    My Gideros games: www.totebo.com
  • antixantix Member
    It would help if we knew how you are using events and would possibly be able to see how/why things are slowing down so much.
    Follow me on FaceBook Check out my DevBlog, my GitHub, and try my games...
    Falling Animals | Breaky Wall | Exetor | Mini Putt Golfing | Ninja Fruit Master
  • totebototebo Member
    One of the biggest slowdowns were my controls; I had a "MOVE" event fire with the radian the character should be moving. This could happen every frame. I've replaced it with a direct call to a function instead, which cleared that issue up.
    My Gideros games: www.totebo.com
  • antixantix Member
    edited June 2016
    Okay so the rule of thumb is that if something is going to happen every frame, an event is not the answer :D

    I have a class called HUD (a global variable). All controls (joypads, buttons, etc) and other overlays (score, health, etc) get added to that and the HUD manages all of those controls each frame, calling each controls update() function.

    On creation, the player creates a pointer to the HUD object. It scans the HUD for things it will need like joypads and buttons and saves those in a table (self.controls) for later. Then in the players update() function it checks each control in self.controls and performs actions based on their individual states.

    That's how I'm doing things anyway and it seems to be pretty fast.

    Likes: rolfpancake

    Follow me on FaceBook Check out my DevBlog, my GitHub, and try my games...
    Falling Animals | Breaky Wall | Exetor | Mini Putt Golfing | Ninja Fruit Master
    +1 -1 (+1 / -0 ) Share on Facebook
  • totebototebo Member
    edited June 2016
    Cheers @antix, sounds like a neat approach.

    Now, I've gone through and replaced most events with callbacks, except the ENTER_FRAME gameloop and TOUCH events. The result? A huge speedup.

    Here is the class I'm using to replace EventDispatcher:
    CallbackBroadcaster = Core.class()
     
    function CallbackBroadcaster:init()
     
    	self.callbacks = {}
     
    end
     
    function CallbackBroadcaster:broadcastCallback( id, event )
     
    	for i=1, #self.callbacks do
     
    		local object_callback = self.callbacks[i]
    		if object_callback then
    			if object_callback.id == id then
    				local callback = object_callback.callback
    				local scope = object_callback.scope
    				callback( scope, event )
    			end
    		end
     
    	end
     
    	for i=1, #self.callbacks do
     
    		if self.callbacks[i] == nil then
    			table.remove( self.callbacks, i )
    		end
     
    	end
     
    end
     
     
    function CallbackBroadcaster:removeCallback( id, callback, scope  )
     
    	for i=1, #self.callbacks do
    		if self.callbacks[i] and self.callbacks[i].id == id and self.callbacks[i].callback == callback and self.callbacks[i].scope == scope then
    			self.callbacks[i] = nil
    			break
    		end
    	end
     
    end
     
     
    function CallbackBroadcaster:addCallback( id, callback, scope )
     
    	local already_registered 
    	for i=1, #self.callbacks do
    		local current_callback = self.callbacks[i]
    		if current_callback and current_callback.id == id and current_callback.callback == callback and current_callback.scope == scope then
    			already_registered = true
    			break
    		end
    	end
     
    	if already_registered ~= true then
     
    		local c = {}
    		c.callback = callback
    		c.scope = scope
    		c.id = id
    		table.insert( self.callbacks, c )
     
    	end
     
    end
    My Gideros games: www.totebo.com
    +1 -1 (+3 / -0 ) Share on Facebook
  • antixantix Member
    edited June 2016 Accepted Answer
    Instead of checking if the callback is nil, and then looping through the list again to remove nil ones inside broadcastCallback(), why not just remove the unrequired in removeCallback() like so..
    function CallbackBroadcaster:broadcastCallback( id, event )
     
    	if #callbacks > 0 then -- Make sure there is something there
    		for i=1, #self.callbacks do
     
    			local object_callback = self.callbacks[i]
    			if object_callback.id == id then
    				local callback = object_callback.callback
    				local scope = object_callback.scope
    				callback( scope, event )
    			end
    		end
    	end
    end
     
    function CallbackBroadcaster:removeCallback( id, callback, scope  )
     	local remove = table.remove
     
    	for i= #self.callbacks, 1, -1 do -- Reverse through table so table.remove doesn't crap out
    		if self.callbacks[i] and self.callbacks[i].id == id and self.callbacks[i].callback == callback and self.callbacks[i].scope == scope then
    			remove(callbacks, i)
    			break
    		end
    	end
     
    end
    Question.. Are "id" and "scope" are used by an object to determine what kind of action to perform on callback?

    Likes: totebo

    Follow me on FaceBook Check out my DevBlog, my GitHub, and try my games...
    Falling Animals | Breaky Wall | Exetor | Mini Putt Golfing | Ninja Fruit Master
    +1 -1 (+1 / -0 ) Share on Facebook
  • totebototebo Member
    Loop backwards to avoid remove issues, what a great idea. Table remove inside of its own loop has always confused me.

    ID is used to identify each callback, very much like events. Scope is used to call the function in the right scope, so the function called can use vars and functions within its own scope. Maybe there is a better way of doing this?

    Seeing that Event.new() is a CPU drain, is there a way of replacing the ENTER_FRAME event with a callback? Then I'd be able to scrap Event alltogether and maybe free up some CPU?
    My Gideros games: www.totebo.com
  • antixantix Member
    Yep looping backwards is very awesome :)

    Do all of your game objects have their own ENTER_FRAME event listener? That doesn't scale at all well. Personally I find that pooling objects and then processing each object in the pool of active objects each frame works very well.

    I really don't see the need for a callback system unless you are going to be performing timed events.

    Likes: SinisterSoft

    Follow me on FaceBook Check out my DevBlog, my GitHub, and try my games...
    Falling Animals | Breaky Wall | Exetor | Mini Putt Golfing | Ninja Fruit Master
    +1 -1 (+1 / -0 ) Share on Facebook
  • SinisterSoftSinisterSoft Maintainer
    Yes, only have one enter frame event - add it to stage.

    Likes: antix

    +1 -1 (+1 / -0 ) Share on Facebook
  • totebototebo Member
    edited June 2016
    Hey, no I just have one ENTER_FRAME loop from stage in the game, that feeds into all other (pooled) objects. But as I'm on an optimisation crusade, I was wondering if THAT event is causing a slowdown? And maybe others, like TOUCHES_MOVE and TIMER that could happen frequently. Is there a way to replace those events with callbacks too?
    My Gideros games: www.totebo.com
  • antixantix Member
    Okay just checking. One ENTER_FRAME event isn't going to cause any slowdown.

    Timers shouldn't cause much slowdown, unless you are creating them on the fly and using them excessively. You can pool timers just like sprites if you like. You can just manage timed events in your ENTER_FRAME function if you like, keeping an internal list of events to fire at specific times. The resolution should be good enough for any timed events you might require.

    Touches, again should not cause issues unless you have too many listeners. It will depend on how many listeners you use. You can even have just one touch manager which does hit tests on an internal list of buttons and other controls. Generally you will have only a directional joypad and a couple of buttons so it shouldn't be an issue.

    Likes: totebo

    Follow me on FaceBook Check out my DevBlog, my GitHub, and try my games...
    Falling Animals | Breaky Wall | Exetor | Mini Putt Golfing | Ninja Fruit Master
    +1 -1 (+1 / -0 ) Share on Facebook
  • totebototebo Member
    Gotcha. This has been an eye opener for me in terms of regained performance, so may be worth adding to a tutorial or maybe a heads up on the Event page in the docs.

    Likes: antix

    My Gideros games: www.totebo.com
    +1 -1 (+1 / -0 ) Share on Facebook
  • antixantix Member
    I agree some mention somewhere or a tutorial would be good. Maybe there could be a tutorial or page on how to gain performance.
    Follow me on FaceBook Check out my DevBlog, my GitHub, and try my games...
    Falling Animals | Breaky Wall | Exetor | Mini Putt Golfing | Ninja Fruit Master
  • SinisterSoftSinisterSoft Maintainer
    Yes, too many of the existing tutorials setup event listeners all over the place. This is good if you have nice fast phones or not a lot of objects - but no good for almost every other case.

    Likes: antix

    +1 -1 (+1 / -0 ) Share on Facebook
  • totebototebo Member
    So far, these three tricks are nothing but magic:

    1. Pooling
    2. Local vars
    3. Callbacks instead of Events
    My Gideros games: www.totebo.com
  • totebototebo Member
    edited June 2016
    ps. @antix, your nifty table.remove code causes a niggle in my case. I sometimes remove callbacks as I receive a callback, which means the table can get modified in the same frame. This means callbacks don't get fired in that scenario. Not sure how to allow for this with your code, so reverted to the old code which always fires everything it can, then removes once it's finished.
    My Gideros games: www.totebo.com
  • antixantix Member
    @totebo, that's really odd. I would think that even if an object creates a new callback (whilst it is actually performing a callback), it should get fired on the next ENTER_FRAME call of broadcastCallback() because it will be added at the end of the table. I'll think about this a bit more but I can't see how any callback would be missed.

    If (in your ENTER_FRAME) you are looping through and updating all of your game objects, then I can't really see why you are adding the overhead of a callback system, since all possible object behaviors can be handled in their update() function.
    Follow me on FaceBook Check out my DevBlog, my GitHub, and try my games...
    Falling Animals | Breaky Wall | Exetor | Mini Putt Golfing | Ninja Fruit Master
  • totebototebo Member
    To question number 1, here is what happens:

    1. I register the same callback in two separate classes
    2. The callback starts firing and both classes remove their respective callback as they receive it

    Because the table is altered when the first class removes their callback, the second class never gets their callback.

    Question 2:

    This is true. But since each class is not entirely self contained and rely on knowing what's going on in other classes, Events were really useful to keep the code readable. Until I realised they slow things down. So now I use callbacks as a replacement for Events.

    One example of the events I use are an enemy is killed, which is picked up by SceneGameworld to add points, and GameWorld to play the killed animation and spawn rewards. I could do this with direct calls from Main (which owns SceneGameworld), and then from SceneGameworld (which owns GameWorld), but then I'd get a load of logic in each update function, and it makes the code messy. Callbacks for the win!
    My Gideros games: www.totebo.com
  • antixantix Member
    1. I mostly see where you are coming from now ;)

    2. A little messy code are a good trade-off for speed I reckon :D
    Follow me on FaceBook Check out my DevBlog, my GitHub, and try my games...
    Falling Animals | Breaky Wall | Exetor | Mini Putt Golfing | Ninja Fruit Master
  • totebototebo Member
    You reckon? :)

    I wonder if it would be faster to call the functions directly, though. I suppose there is a small overhead looping through the callbacks every frame. BUT my code is only moderately messy now, instead of an absolute mess. There are levels of messiness. ;)
    My Gideros games: www.totebo.com
  • antixantix Member
    edited June 2016
    There's an old adage that goes like "Don't put of till tomorrow what you can do today". I think this applies to your situation somewhat.

    Take the example where you are processing an enemy. It takes damage, and determines that it has died. This being the case it inherently knows that the players score needs to be incremented. Why bother creating some callback task that will be performed at some later date when you could do that right there, on the spot?

    I think callbacks are fantastic but just using those for the sake of code tidiness isn't good practice in my opinion. The player will never know whether your code is tidy or not and they most likely don't give a hoot. However, if your tidy code makes the game run slower then they might notice that.

    When I think about things like points that are to be added to the players score. In a very simple model I would have a global var called POINTS_TO_ADD. Whenever an enemy dies it would just adds the appropriate amount to POINTS_TO_ADD. Then once per frame if POINTS_TO_ADD is not 0, update the score and reset it to 0 for the next frame.

    I don't think global vars are as evil as they are made out to be. Used sparingly they are very powerful.
    Follow me on FaceBook Check out my DevBlog, my GitHub, and try my games...
    Falling Animals | Breaky Wall | Exetor | Mini Putt Golfing | Ninja Fruit Master
  • totebototebo Member
    edited June 2016
    Global vars, gah! I use strict.lua to avoid such shenanigans. You're a brave man.

    That said, your global way of dealing with updates is interesting. I wonder if this is more efficient than callbacks, though, because I would still need to look through POINTS_TO_ADD, and all other vars like it, every frame; just like I'm doing with the callbacks. (and there is something that screams "enemies shouldn't know about POINTS_TO_ADD!?" in my mind) :)
    My Gideros games: www.totebo.com
  • antixantix Member
    I don't know about brave hehe, but I really find global vars are an easy (and seemingly efficient) way to share information between classes.

    Who cares what enemies "should" and "shouldn't" know about. Just because they know something they probably shouldn't.. they don't need to do anything with it right?

    I tried option STRICT in VB.NET for a while. While I guess it's really good programming practice, it did also end-up being a whole lot more typing that I had to do hehe.
    Follow me on FaceBook Check out my DevBlog, my GitHub, and try my games...
    Falling Animals | Breaky Wall | Exetor | Mini Putt Golfing | Ninja Fruit Master
  • totebototebo Member
    This is the reason I don't want enemies to know too much.

    image

    image
    My Gideros games: www.totebo.com
  • antixantix Member
    Heheh, I suppose that's a good reason. Umm, who is that in the 2nd picture?
    Follow me on FaceBook Check out my DevBlog, my GitHub, and try my games...
    Falling Animals | Breaky Wall | Exetor | Mini Putt Golfing | Ninja Fruit Master
  • totebototebo Member
    Bryan Cranston from Breaking Bad dropping the mike at Comic Con. I think we've veered off topic here, Cliff. :)

    Likes: antix

    My Gideros games: www.totebo.com
    +1 -1 (+1 / -0 ) Share on Facebook
Sign In or Register to comment.