[web] JavaScript game loop

Started by
16 comments, last by ucm 12 years, 4 months ago
I have a number of uses for a "game loop" in &#106avascript, either for old Dynamic HTML stuff or new HTML5 stuff, be it games or business apps. I'm not happy with the performance of setInterval (or its retarded brother, setTimeout) and was wondering if y'all knew of a better way. Currently, I have something along the lines of:

function gameLoop(){   // do the per-iteration stuff}var timer;function init(){   if(timer == null)   {      // do the one-time setup stuff      timer = setTimeout(gameLoop, [[1]]);   }}function stop(){   if(timer != null)   {      clearTimeout(timer);      timer = null;   }}


and then usually call init() via the page onload event: <body onload="init()">

setInterval currently accounts for most of my performance problems on the Moto Droid.

[Formerly "capn_midnight". See some of my projects. Find me on twitter tumblr G+ Github.]

Advertisement
You probably want to make the interval larger than 1 millisecond. With that short a space you may as well run it in a while loop -- it's going to destroy your performance. Try to figure out what framerate you would want the game to run and set the interval there (eg: 33 is about 30 fps).

FWIW, you might want to use setInterval instead of setTimeout. Using Timeout will vary your loop times slightly by waiting the interval after each loop iteration, whereas Interval will work from the previous loop execution.
It looks like he's only using the 1ms timeout for the initialization. This way he can load resources in the background, avoiding hanging the browser.

I don't know of another way to throttle your app in &#106avascript without setTimeout/setInterval. You could busy wait in your game loop if it were invoked via setTimeout, but that'd kill the battery.<br><br>EDIT:<br>Actually I'd recommend the opposite of what jolid said about using setInterval. I'd recommend using setTimeout at the end of each game loop iteration to reschedule. This way you can adjust the timeout duration dynamically to achieve your desired frame rate.
Okay, further discussion time.

I've created a little bit of a performance test on this. The output format is "<min of all runs>, <last five runs>, <max of all runs>". The timing is setup so that I'm measuring as close to the "outside of my code" time as possible. So my timing code is essentially
set current_time to NOW()calculate difference from last timedo outputset last_time to NOW()loop

so the time covered between last_time and current_time is pretty much all setTimeout or setInterval's time.

When I run this in Google Chrome 6 beta, I get numbers like "1, 5,5,5,5,5, 6" for both setTimeout and setInterval.

Internet Explorer 8: "15, 15,15,16,16,16, 16", again on both setTimeout and setInterval.

Android Browser on the Motorola Droid: "0, 1,9,10,13,13, 40". Both loops are similar again, though there is a very noticeable lack of stability in the numbers, whereas Chrome and IE had settled into an fairly unvarying stream of numbers.

Anyway, the upshot of this is that HTML5 cannot yet be used for even 30FPS animation on Android Browser yet, as it frequently dips above the 33ms threshold, just to return to the next frame update, say nothing about how much processing can be done during that update.


(NOTE: The comparable order of magnitude between the IE and Android numbers is something I've noticed before. Yes, IE8 on a 2.33GHz Core 2 Duo machine often feels similar to Android Browser on a 550MHz Arm Cortex. These numbers aren't the total story, but I've experienced other situations where this has come up. Shameful, shameful, shameful)

[Formerly "capn_midnight". See some of my projects. Find me on twitter tumblr G+ Github.]

Unfortunately, I think you're out of luck on this one. No browser that I'm aware of has a way to let you force rendering to go through without finishing your current thread of execution, which as far as I know can only be done by using setTimeout or setInterval. If Android isn't getting back to you as fast as you'd like, your only recourse may be to bug Android's (and/or Android's browser's) developers on Google Groups.

Your best bet might actually be to create a benchmark of some kind that's actually meaningful, then get results of it posted to some tech blogs. Nothing gears up browser developers like seeing they're behind on a widely-publicized benchmark (IE's developers are excluded here, as nothing gears them up).
When I saw the title of this topic I was wondering if my article on the Opera developers page would actually fit... and it turns out I wasn't wrong.

setTimeout() is really unreliable for timing purposes (since the browser will take the delay as a minimum wait, not an exact delay), you need to look for some other timer to rely upon. For example, you can use the current date - every time you create a new Date object, it'll contain the current date, down to the millisecond. You can compare its value with the time of the last frame to determine if you need to run a new frame or not. This method works reliable on every browser I tested, even Explorer (which is notorious for bad timing with such low rates). You'll still need setTimeout() to keep the game loop running (without resorting to hanging the browser with a busy loop), but you won't rely on it to do the timing anymore.

You may want to look at my article for this, here I explain in more detail how would this work:
Framerate control system for &#106avascript

And yes, I know, there's some redundant code in there (return this, delete d)... That's what happens when I dare to write an article after three years of inactivity with &#106avascript. In the end that extra code is no-op though, just get rid of that (assuming you know what you're doing) &gt;_&gt; Enjoy!
Don't pay much attention to "the hedgehog" in my nick, it's just because "Sik" was already taken =/ By the way, Sik is pronounced like seek, not like sick.
This solves the consistency problem. The maximum throughput problem is probably as BeanDog suggested.

Question on a curious side note: for this sort of frame rate syncing, is there a case where processing N frames this update will take such a length of time to require M frames to be processed on the next update, where M is some number greater than N? It just came to mind, haven't had the time to suss it out yet, seems like it might be possible given a certain level of processing complexity.

[Formerly "capn_midnight". See some of my projects. Find me on twitter tumblr G+ Github.]

Yes, but at that point there isn't much that can be done. I'd suggest capping the amount of frames it can give (for example, modify the loop so it also stops after totalFrames reaches certain number, e.g. 4 frames). That'll reduce the effective framerate, but at least it'll prevent the game from locking up. How you want to do the capping is up to you, different games had different approaches to this.

Also, don't include graphics updating in this system. Read the value given by getFrames(), process the game logic frames, and only once you're done with them proceed to update the graphics with whatever ends up as the current state. Otherwise frameskipping won't work at all (or work completely badly), really.
Don't pay much attention to "the hedgehog" in my nick, it's just because "Sik" was already taken =/ By the way, Sik is pronounced like seek, not like sick.
Apparently, setTimeout on Android Browser is bugged in anything prior to Android 2.2.

[Formerly "capn_midnight". See some of my projects. Find me on twitter tumblr G+ Github.]

I've got nothing new to add, just wanted to say Sik's method is much better than the hacked-up setIntervals I've used and it's probably the best (most consistent) solution I've seen. [bookmarked]

Thanks for that!

This topic is closed to new replies.

Advertisement