Building Aukera Part 3: Designing an Update Loop
I can’t dissect whole components in one post. It’s ridiculous. I tried writing about one and the article was huge and confusing. I tried doing a screencast, but that was 20 minutes of boring. It just doesn’t work. Therefore, this post will not be about the Aukera library core. It won’t be about any one component. Instead, I’m going to go into detail about one function that caused a lot of trouble: The update function.
Updating all Over the Place
I say “the update function” like there’s only ever been - and always will be - just one. But that’s not the case. “The update function” I’m referring to is actually a combination of the Game prototype’s update method, the update method on most actors, and the Room prototype’s update method. These methods all play off each other, and I had some challenges finding the best way to make that work.
Haste makes waste
The highest-level update function needs to call itself. It’s the only way the
game will get a good loop going. The easiest way to do this is with
setTimeout
, since it doesn’t involve any recursion, and gives the Javascript
engine an chance to do something that’s not figuring out the next frame of the
game. So the shell of the first version of the main update function looked
something like this:
auk.Game.update = function () {
var that = this;
// Do the loop stuff, not important right now.
setTimeout(function() { that.update(); }, 0);
}
It stores a reference to this
so it can close around it, does the update
stuff, and calls itself again with a zero timeout. The zero timeout means it
should run as fast as it possibly can. That’s always the best idea, right? That
won’t strain the processor and actually make it run slower at all. That would be
ridiculous. Anyway, I didn’t notice any problems with this at first, so lets
continue on.
Keeping the actors in the loop
The whole point of the update loop is to let actors do their thing, so something needs to be in place to make that happen. First things first though: a definition. What is an actor? For our purposes, an actor is any object that does things in the game. As far as the game object is concerned any object can be an actor. As long as the object is in the actors array, the game object will call certain methods on it at certain times, if they’re available. So it’s the update function’s job to make sure that if an actor has an update method, it gets called. Here’s one of my first approaches:
auk.Game.update = function () {
var that = this,
actorCount = this.actors.length,
i;
for (i = 0; i < actorCount; i += 1) {
setTimeOut(function () { this.actors[i].update(); }, 0);
}
setTimeout(function() { that.update(); }, 0);
}
Beautiful isn’t it? Aside from the fact that it completely forgets to check if there is an update function, this function tries to be too clever for its own good. The idea is that by doing timeouts for everything, the update function doesn’t block! That’s also good right? Do it all as fast as possible? Don’t block? That certainly won’t stress the system at all. Yeah… I think I was looking too much into Node for my own good. I was trying to sort out the whole pseudo-event system this library uses at this point too, so I tried some other wacky things. At one point I ditched the games’s update method entirely and just had each actor’s update loop calling itself. I also alternated between timeouts of 0 and 30 (something close to 30fps. Kinda).
Competing with the past
I decided to do some simple benchmarking too see just how good all these fancy update loops were working. With the game running on my 4-year-old laptop, I opened chrome’s task manager and checked the CPU usage. Consistently 50% - 70% regardless of which non-blocking method I was using. That’s some great performance there. “Oh well,” I thought to myself, “At least it’s probably better than Generic Platformer.” For some background: I built Generic Platformer over the course of about 5 days while still learning how to use objects in javascript. It’s not very well written to be honest. I decided to comfort myself by seeing just how much faster the new update loop was vs whatever GP was doing. So I checked the CPU column for GP and saw…
12%
12%
This poorly-written, synchronous, blocking, old, hasty bit of code was performing far better than my super fancy non-blocking update loop. How was this possible? What was it doing? Something with these two out-of-context, edited- down snippets:
function doUpdate(){
//Call the game's update function
game.update();
// Do this all again in 30 milliseconds.
t = setTimeout("doUpdate()", 30);
}
this.update = function(){
for (var i=0; i<this.actors.length; i++){
this.actors[i].update();
}
}
You don’t want to see the mess I pulled those out of. So this blocks, but overall the script ran a lot more efficiently. Occasionally the game would lag a bit, but the processor usage never went above 15%. I promptly adjusted the Aukera update loop to work this way as well, and then eventually it got split between games and room.
So what did I learn?
Non-blocking isn’t always the best way to do something. When each actor’s update method is called synchronously, it forces the game to wait for them all to finish before it starts the loop over again. This is a good thing, because in the cases where the loop takes longer than 30mills to run, you get frame dropping instead of the loop running twice simultaneously. Sure, frame dropping sucks, but not as much as a browser game hogging all the system resources.
Of course, I threw all that performance out the window when I switched to full 3D, but that’s a temporary setback. Next time, I’ll talk more about actors.