December 25th, 2012

Game timers: Issues and solutions.

When I started game programming I ran into an issue common to most aspiring game developers: The naive infinite loop resulted in variable framerate and variable time difference that made the game nondeterministic. It is a problem that Supreme Commander/Demigod developer Forrest Smith described well in Synchronous Engine Architecture (mirror).

Since I came up with a simple solution in my last engine I wanted to share it here: Maybe it will save a few hours to someone :) !

Problem: Variable framerate and Variable timeslices.

When I started engine development I used a naive game loop that would:

  1. Retrieve the time difference (timeslice) since the last screen update.
  2. Update the world according to inputs and timeslice.
  3. Render the state of the world to the screen.

It looked as follow :



    int lastTime = Timer_Gettime();


    while (1){

      int currentTime = Gettime();
      int timeSlice = currentTime - lastTime ;

      UpdateWorld(timeSlice);
      RenderWorld();

      lastTime = currentTime;
    }




With a fast renderer the game world is updated with tiny time differences (timeslice) of 6-7 milliseconds (60Hz) and everything is fine.
The key concept is that timeslices at which the game is simulated are variables in size :


This architecture does work but is limited: The weakness in the design is that simulating the world accurately is tied to having small timeslices (a.k.a high framerate). Problems arise if the framerate drops too much: Updating at 30Hz instead of 60Hz may move objects so much that collisions are missed (In a game such as SHMUP that was a big problem):



There are algorithms to palliate to this problem but this naive design has many more flaws. The fundamental difficulty is not the length of a timeslice but its variability:

  • Record user inputs and replaying a game session (for debugging or entertainment) becomes difficult since the timeslice are different. This is true even if the replay machine is the machine that originally recorded the session since the operating system system calls will never have the same duration.



This issue can also be solved by recording the simulation time along with inputs but the game engine design is now becoming messy. Besides, there is an other issue:

  • If your game is network based and you need to run several simulations simultaneously on different machines with identical inputs: The time slices will not match and small difference will appear until the simulation on each machines are out of sync !

Solution: Fixed timeslices.

There is an elegant approach to fix everything at the cost of a small latency: Update the game timer at a fixed rate inside a second loop.



    int  gameOn = 1
    int simulationTime = 0;

    while (1){

      int realTime = Gettime();

      while (simulationTime < realTime){
             simulationTime += 16; //Timeslice is ALWAYS 16ms. 
             UpdateWorld(16);
      }

      RenderWorld();
    }


This tiny change dissociates rendition and simulation as follow:

The engine nows runs at with CONSTANT timeslices, regardless of network latency/renderer performances.

It allows simple and elegant code for replay recording/playback, solid collisions detection system... but most importantly it allows simulation running on different machines to be synchronized as long as the same input/random feeds are provided.

Before you patent it...

The joy of "discovering" this approach was short lived: This system is very well known in the game industry and is actually the keystone of games such as Starcraft II or id Software engines !

Further readings

Merry Christmas....

...and Happy Hacking guys ;) !


Add a comment



Name Homepage
E-mail
(Will not appear online)
Comment



Comments (11)


#1 - Tom - 12/26/2012 - 05:53
For the Javascript developers out there the window.requestAnimationFrame API does the equivalent thing.
#2 - Cezar Wagenheimer - 12/26/2012 - 06:25
Hi!

Interesting post, but I think I was not able to understand it very well!

Using Fixed Timeslices how the game speed will be constant on different machines?
#3 - Futjikato - 12/26/2012 - 09:02
Hi there,

am I missing something or is that over simplified ?
To decouple the timeslice size from network or rendering speed you need to divide these two areas in different threads. Otherwise it could still happen that the rendering needs more than the braced timeslice size. The two loops in your short code example are not running in parallel.
#4 - Christian - 12/26/2012 - 09:27
It's so prominent when researching this stuff online so you've probably already read it, but I think its worth linking to Glenn Fiedler's "Fix Your Timestep" (http://gafferongames.com/game-physics/fix-your-timestep/) article (and entire site) if someone's interested in even more timing/integration-related information for game development. Your solution here is basically his second-to-last one, i.e. the final one minus spiral-of-death-prevention and interpolation.

Cheers!
#5 - Den - 12/26/2012 - 17:14
Thank you for this article. Merry Christmas and Happy Year.
#6 - Marek - 12/28/2012 - 22:54
Great article at Random ASCII blog: Don’t Store That in a Float. http://randomascii.wordpress.com/2012/02/13/dont-store-that-in-a-float/
#7 - Nicolas Silvagni - 12/29/2012 - 11:48
This is not so simple, because now, the rendered frame timestamps are not in sync with the physical world. And it creates choppy camera and objects animation. In a perfect world, the rendered positions should be interpolate/extrapolate to keep things smooth.
#8 - Bruce Dawson - 01/01/2013 - 13:44
You say "6-7 milliseconds (60Hz)" but that should be "16-17 milliseconds (60Hz)" -- unless your seconds are shorter than mine. This mistake shows up in a couple of other places in your post.

One consequence of fixed time stepping is that frame-rate is no longer linearly proportional to CPU speed, which means that as you run on slower and slower machines you eventually hit a rendering frame rate of zero, because all of the CPU time is consumed on the UpdateWorld() calls. This makes it more important to have the UpdateWorld() loop be as cheap as possible.
#9 - Anaël - 01/03/2013 - 05:45
Nice, I also use that for some years in my engine Maratis,
it evolved like this :

unsigned int frequency = 60;
unsigned long previousFrame = 0;
unsigned long startTick = window->getSystemTick();

// on events
while(window->isActive())
{
// on events
if(window->onEvents())
{
if(window->getFocus())
{
// compute target tick
unsigned long currentTick = window->getSystemTick();

unsigned long tick = currentTick - startTick;
unsigned long frame = (unsigned long)(tick * (frequency * 0.001f));

// update elapsed time
unsigned int i;
unsigned int steps = (unsigned int)(frame - previousFrame);


// don't wait too much
if(steps >= (frequency/2))
{
update();
draw();
previousFrame += steps;
continue;
}

// update
for(i=0; i 0){
draw();
}
}
else
{
window->sleep(0.1);
}
}
}
#10 - Todd - 01/14/2013 - 14:26
Forrest worked on Supreme Commander 2. I don't think he worked on Star Craft.
#11 - Tigrou - 01/22/2013 - 17:27
Good article, nicely explained as usual (even if there is nothing new for me, but can be useful for others). However, feel its a little bit short and was excepting something a little longer.

 

@2012