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: rolfpancake, turgutcs
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.
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:
Likes: SinisterSoft, antix, pie
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?
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.
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.
2. Local vars
3. Callbacks instead of Events
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.
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.
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!
2. A little messy code are a good trade-off for speed I 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.
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.
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)
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.