Timeloop.js – Running a function repeatedly (The Gameloop)

The result of this article is: timeloop.js
Prerequisites: none

Introduction

In this article I will explore how to create a gameloop in javascript. I will focus on the core idea: How to run a certain function repeatedly. The result of this article is the timeloop.js class. So at the end of the article we will have created a reusable class that does just that.

So what is a gameloop? There are a lot of good articles on gameloops already so I will not describe myself. This is a good one:

http://www.koonsolo.com/news/dewitters-gameloop/

While the loops in that article works very well with Java or C++ they won’t work in js so we will have to write ours differently. But more on that issue later.

Are gameloops really suitable for js-games?

Javascript works different compared to for example Java or C++. Javascript is very suitable for event driven programs and this sometimes allows you to code certain features of a program differently. Could it be that we do not need a gameloop in a javascript game?

Imagine a very turn based game. Memory for example. Such a game could probably be coded without using a game loop. It could be completely event driven. We could use the mouse “on click” events. The game advances only when the player clicks on something.

A realtime shooter with AI controlled enemies will however really need a gameloop. Because the AI enemies must act on their own even if the human player sits still and does nothing. Such a game is “time driven” and not “event driven”. The game state advances as time passes, regardless of user input.

Answer: Yes we really need a gameloop. Though for completely event driven games we do not need one.

The while loop approach won’t work

Did you read that article at koonsolo.com I linked to? In that article a while loop is used. The approach would look like this in js:

var lastRun = Date.now(); // milliseconds elapsed since 1 January 1970 00:00:00 UTC up until now as a number.
var running = true;
 
while (running) {
	var now = Date.now();
	var delta = now - lastRun;
	lastRun = now;
 
	// insert your game logic here
}

But that won’t work. It is important to understand that javascript is singlethreaded. The whileloop would be the only thing running and that would make it impossible to read keyboard input etc. Instead we have to use either window.setInterval or window.setTimeoout.

setInterval and setTimeout

As opposed to the while loop these two methods won’t cause the browser to hang. It is however important to have an indepth understanding on how they work. After reading these articles you should have “indepth understanding” :)

From reading these articles we get these important insights:

  • We should use setTimeout instead of setInterval. Because setInterval will stack the function calls if the game logic takes longer time to execute than the interval.
  • Both setInterval and setTimeout are inexact. We can not be sure that the callback will be executed exactly when we requested.
  • Both setInterval and setTimeout will mess up the “this” variable. We have to remember to handle that somehow.

The core of the idea

This is a minimalistic working example on how we will create our gameloop:

var lastRun = Date.now(); // milliseconds elapsed since 1 January 1970 00:00:00 UTC up until now as a number.
var minDelta = 10; // means maximum fps = 100
var loop = function() {
	// Schedule the next run
	window.setTimeout(loop, minDelta);
 
	// Find the delta and set lastRun to now
	var now = Date.now();
	var delta = now - lastRun;
	lastRun = now;
 
	// Then run the game logic
	console.log(delta);
}
loop(); // This will start the gameloop (and you console will be spammed with deltas)

Note that we calculate a delta. This is the time since the last time we ran the gameloop. As setTimout is inexact the delta will probably be a little different each time but this does not matter. We just need our game logic to take the delta into account.

An acknowledgement

This sucks but is true: Date.now() has bad precision. In the best of worlds it would have a resolution of 1ms but in reality the resolution is around 15ms. At least on windows (xp, vista and 7). I have heard the resolution is much better on Linux and Mac though.

The logic above will work anyways. Some times the delta will be larger than the truth and some times smaller. But it seems that in the long run the differences takes out each other.

Read more about Date.now() resolution in these articles if you feel like it:

Usage mockup for our Timeloop class

Lets call the class “Timeloop” instead of “Gameloop”. Because this class could be used for any form of timeloop, not only gameloops. And yeah we are creating a “class” here. I will make use of simple javascript inheritance by John Resig.

This is how I envision the usage of the Timeloop class:

// Lets say we have a "game" object that contains the function we want to run repetedly:
var game = {
	update: function(delta) {
		// Run the game logic here:
		console.log(delta)
	}
};
 
// We add a timeloop instance. The constructor will take a settings object:
game.loop = new Timeloop({
	targetFps: 60, // targetDelta should also be an alternative
	callback: game.update, // We should run game.update
	thisArg: game, // and the scope (this) should be the game object
});
 
// Start the gameloop
game.loop.start();
 
// Stop aka Pause the gameloop
game.loop.stop();
 
// Check if the gameloop is running
game.loop.isRunning(); // returns a boolean
 
// Target delta or fps can be changed later
game.loop.setTargetDelta(10); // 100 fps
game.loop.setTargetFps(100);  // 100 fps
 
// Those should have getters as well
game.loop.getTargetDelta();
game.loop.getTargetFps();
 
// We should also be able to get som benchmarking info
game.loop.getAvgDelta(); // The average of the recent deltas
game.loop.getAvgFps(); // The average of the recent fps
game.loop.getAvgExecTime(); // The average of the recent execution times of the callback.
game.loop.getBenchmarkHtml(); // Returns some html we could put in a div to see all benchmark info.

The result

View the sourcecode on github

Leave a Reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>