Jump to content

  • Log In with Google      Sign In   
  • Create Account

[web] JavaScript game loop


Old topic!
Guest, the last post of this topic is over 60 days old and at this point you may not reply in this topic. If you wish to continue this conversation start a new topic.

  • You cannot reply to this topic
17 replies to this topic

#1 Sean T. McBeth   Crossbones+   -  Reputation: 1633

Like
0Likes
Like

Posted 16 August 2010 - 05:29 AM

I have a number of uses for a "game loop" in javascript, 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.

Sponsor:

#2 jolid   Members   -  Reputation: 143

Like
0Likes
Like

Posted 16 August 2010 - 07:01 AM

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.

#3 smr   GDNet+   -  Reputation: 1681

Like
0Likes
Like

Posted 16 August 2010 - 07:23 AM

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 javascript without setTimeout/setInterval. You could busy wait in your game loop if it were invoked via setTimeout, but that'd kill the battery.

EDIT:
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.

#4 Sean T. McBeth   Crossbones+   -  Reputation: 1633

Like
0Likes
Like

Posted 16 August 2010 - 08:31 AM

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 time
do output
set 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.]


#5 BeanDog   Members   -  Reputation: 1063

Like
0Likes
Like

Posted 16 August 2010 - 09:06 AM

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).

~BenDilts( void );

Lucidchart: Online Flow Chart Software; Lucidpress: Digital Publishing Software


#6 Sik_the_hedgehog   Crossbones+   -  Reputation: 1833

Like
0Likes
Like

Posted 16 August 2010 - 10:48 AM

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 javascript

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 javascript. In the end that extra code is no-op though, just get rid of that (assuming you know what you're doing) >_> Enjoy!

#7 Sean T. McBeth   Crossbones+   -  Reputation: 1633

Like
0Likes
Like

Posted 16 August 2010 - 11:56 AM

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.

#8 Sik_the_hedgehog   Crossbones+   -  Reputation: 1833

Like
0Likes
Like

Posted 16 August 2010 - 12:35 PM

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.

#9 Sean T. McBeth   Crossbones+   -  Reputation: 1633

Like
0Likes
Like

Posted 18 August 2010 - 02:34 AM

Apparently, setTimeout on Android Browser is bugged in anything prior to Android 2.2.

#10 jolid   Members   -  Reputation: 143

Like
0Likes
Like

Posted 18 August 2010 - 08:34 AM

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!

#11 Sean T. McBeth   Crossbones+   -  Reputation: 1633

Like
0Likes
Like

Posted 18 August 2010 - 09:44 AM

Of course, this is all assuming that the timing method is any good. Who knows how if each browser's implementation of the Date object is at all accurate.

#12 Sik_the_hedgehog   Crossbones+   -  Reputation: 1833

Like
0Likes
Like

Posted 18 August 2010 - 12:22 PM

Quote:
Original post by capn_midnight
Of course, this is all assuming that the timing method is any good. Who knows how if each browser's implementation of the Date object is at all accurate.

Usually browsers just take the timestamp from the operating system, so if anything goes wrong, it's usually the operating system's fault. Of course, then there's embedded systems where the browser is part of the firmware itself, but in those cases either the hardware is too weak to do anything useful or it has a decent implementation.

Bear into mind that the worst that could happen is that you'll get a lower real framerate, but the logic framerate will still remain the same no matter what you try to do (well, frameskipping capping aside). The only way such a thing could break is if the browser was completely unable to retrieve the proper time of the day in the first place.

#13 Sockwave   Members   -  Reputation: 100

Like
0Likes
Like

Posted 08 September 2010 - 11:55 AM

I'm really impressed with Sik's algorithm, it looks like perfect solution to this problem. If anyone wants to see a demo of it implemented in a game engine, I wrote a simple code to test it out. You can find it here. This isn't a game at all, but it shows the frame rate control at work. Feel free to look through the source code if you're curious. In Firefox on my box the graphics start to lag at around 100 objects in the demo, but the motion and interactions remain smooth and on time. Very nice idea!

#14 Olof Larsson   Members   -  Reputation: 99

Like
0Likes
Like

Posted 03 June 2011 - 06:26 PM

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.


No you must either use setTimout or setInterval. I would actually recommend you to use setTimeout because setInterval will stack the function calls if the game logic takes longer time to execute than the interval..
I just wrote a quiet in depth article on gameloops for javascript HTML5 games. Read it all here: http://jsteam.org?p=6
You could use the class in the end of that article. I MIT licensed it.

#15 typedef struct   Members   -  Reputation: 230

Like
1Likes
Like

Posted 03 June 2011 - 10:33 PM

You want to use 'requestAnimFrame". It's supported on most browsers. I use a shim like this to fallback to setTimeout:

window.requestAnimFrame = (function () {
	return window.requestAnimFrame ||
    	window.webkitRequestAnimationFrame ||
    	window.mozRequestAnimationFrame ||
    	window.oRequestAnimationFrame ||
    	window.msRequestAnimationFrame ||
    	function (callback, element) {
        	window.setTimeout(callback, 1000.0 / 60.0);
    	};
})();

Here's a blog post about it. EDIT: the sidebars of that blog are awesome
Anthony Umfer

#16 Sean T. McBeth   Crossbones+   -  Reputation: 1633

Like
0Likes
Like

Posted 12 November 2011 - 01:58 PM

I continue to be amazed that this post consistently ranks #1 for searches of "javascript game loop".

Anyway, typedef struct, thank you for your post. That is very interesting. Unfortunately, on iOS 5's Safari, it shows no performance increase, and in Chrome 15 on Windows XP it actually shows a slower performance, almost to the point of Internet Explorer levels. Maybe in the future this will be better, but for now, setInterval or setTimeout seems to be the fastest way.

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


#17 BUnzaga   Members   -  Reputation: 267

Like
1Likes
Like

Posted 02 December 2011 - 01:06 AM

EDIT: I am now using this game loop:
// setInterval(GameLoop, 1);

var FPS = 50;
var dt = 1/FPS; // synthetic delta time
var last = Date.now()/1000;
var accrued = 0.0;
var ri = 0.0; // render interpolation

GameLoop:function(){
	var now = Date.now()/1000;
	accrued += (now-last);
	CheckInput();
	while(accrued > dt){
		Update(dt);
		accrued -= dt;
	}
	ri = (accrued/dt); // calculate render interpolation
	Render(ri); // pass it to render moving images
	last = now;
}
Let me know if you find any 'issues' with this :) I have been working on it for a couple days weeks now, and like the results.

You can test out the game loop on my html5 test page...

http://www.html5.bunzaga.com

There is an 800x600 canvas with 1000 'targets' bouncing around the screen.

Here is my updated game loop for html5 canvas. It works well for both 'fast' and 'slow' computers...
// setInterval(GameLoop, 1);
var old_time = Date.now();
var FPS = 1/60;
function GameLoop(){
	var new_time = Date.now();
	var dt = ((new_time - old_time)/1000);
	while(dt > FPS){
		Update(FPS);
		dt -= FPS;
	}
	Update(dt);
	Render();
	old_time = new_time;
}


#18 ucm   Members   -  Reputation: 124

Like
1Likes
Like

Posted 02 December 2011 - 01:35 AM

Here is my updated game loop for html5 canvas. It works well for both 'fast' and 'slow' computers...

// setInterval(GameLoop, 1);


var old_time = Date.now();
var FPS = 1/60;
function GameLoop(){
    var new_time = Date.now();
    var dt = ((new_time - old_time)/1000);
    while(dt > FPS){
        Update(FPS);
        dt -= FPS;
    }
    Update(dt);
    Render();
    old_time = new_time;
}

Let me know if you find any 'issues' with this :) I have been working on it for a couple days now, and like the results.




Awesome BUnzaga!

Now lets see more man ;-)




Old topic!
Guest, the last post of this topic is over 60 days old and at this point you may not reply in this topic. If you wish to continue this conversation start a new topic.



PARTNERS