Okay, I spent quite a long time nailing the game loop in the upcoming game and I want to share my approach, which I believe is valuable and worth implementing in your engine.
If you ask around in programming circles for an ideal way to implement a framerate independent physics engine/game logic, you’ll likely be forwarded to Glenn Fiedler’s article Fix Your Timestep. It’s still the gold standard when it comes to implementing a simple game loop that is mostly decoupled from the rendering.
This article serves as a follow up to Fix Your Timestep where I cover some improvements and additional features such as slowing or speeding up time, timers, and an input playback system.
Assuming you have implemented the semi-fixed timestep method, your game/engine loop resembles something like this1:
#define PHYSICS_FPS 240.0
#define DT 1.0 / PHYSICS_FPS
typedef struct Game {
double time;
double previousTime;
double currentTime;
double frameTime;
double frameAccumulator;
} Game;
void game_loop (Game *game) {
double newTime = OS_getTime();
game->frameTime = (newTime - game->currentTime);
game->previousTime = game->currentTime;
game->currentTime = newTime;
game->frameAccumulator += MIN(game->frameTime, 0.25);
processInput();
while (game->frameAccumulator >= DT) {
physics_simulate(DT);
game->time += DT;
game->frameAccumulator -= DT;
}
renderGame();
}And this is already pretty good! The physics and renderer are decoupled from one another, and the simulation runs at a different speed than the renderer.
The first and easiest improvement that can be made here is to to store the time in U64 as nanoseconds. Because of how floats and doubles are stored in the computer memory, their precision decreases as their value increases. So as the game is running for long periods of time, players will start to realize that the game is now stuttering and become unplayable.
On PC platforms you can easily request a high resolution timer from the OS that would either return to you in nanoseconds, or can be converted to nanoseconds. On Windows you can request it like so:
LARGE_INTEGER frequency;
u64 OS_init () {
QueryPerformanceFrequency(&frequency);
}
u64 OS_getTimeNs () {
LARGE_INTEGER counter;
QueryPerformanceCounter(&counter);
return (counter.QuadPart * 1000000000ULL) / frequency.QuadPart;
}I prefer to have the timer function report time elapsed since the game launched.
LARGE_INTEGER initialCounter;
LARGE_INTEGER frequency;
u64 OS_init () {
QueryPerformanceCounter(&initialCounter);
QueryPerformanceFrequency(&frequency);
}
u64 OS_getTimeNs () {
LARGE_INTEGER counter;
QueryPerformanceCounter(&counter);
return ((counter.QuadPart - initialCounter.QuadPart) * 1000000000ULL) / frequency.QuadPart;
}Now the game code can be changed to use the U64 format to store the time. It’s very useful to have macros that convert in between different time formats.
#define SEC_TO_NS(a) (u64) (a * 1000000000ULL)
#define MS_TO_NS(a) a * 1000000ULL
#define NS_TO_SEC(a) (float) a / 1000000000.0f
#define PHYSICS_FPS 240
#define DT SEC_TO_NS(1) / PHYSICS_FPS
typedef struct Game {
u64 time;
u64 previousTime;
u64 currentTime;
u64 frameTime;
u64 frameAccumulator;
} Game;
void game_loop (Game *game) {
u64 newTime = OS_getTimeNs();
game->frameTime = (newTime - game->currentTime);
game->previousTime = game->currentTime;
game->currentTime = newTime;
game->frameAccumulator += MIN(game->frameTime, MS_TO_NS(250));
processInput();
while (game->frameAccumulator >= DT) {
physics_simulate(SEC_TO_NS(DT));
game->time += DT;
game->frameAccumulator -= DT;
}
renderGame();
}In your physics_simulate function you are most likely using deltaTime as a multiplier to control the speed of animations and physics interactions. Since SEC_TO_NS returns a float, it can be passed to the physics_simulate function and no further changes need to be made.
The renderer likely uses a float time value to animate the shaders.
The macro NS_TO_SEC can be used to convert time to seconds represented in floats and now that shaders are working as you’d expect to.
game->shaderTime = NS_TO_SEC(game->time);
shader_setFloat(shader, "time", game->shaderTime);Now there is an issue here that the shaders are still using the float format to store the time for their animations and as the game is running for long periods2, stutters in the shader animations (like the water shader) will become apparent.
Solving this issue is more complicated and involved. I have an idea3 of how to tackle this but I will instead forward you to this article A Shader Trick by Jonathan Blow where he details what they did to solve this problem for their game The Witness.
The gist of this trick is that you would find the looping point in the shaders and reset the time at those intervals. It works and it’s a neat trick!
The next addition is a timescale factor. It is used to speed up or slow down the simulation while keeping the simulation deterministic. Similar to how you can play a youtube video at 2x and it’s still the same video. You could think of it when a ball starts to roll down a slope and comes to a stopping point, regardless of the simulation speed, the ball should always roll the same and stop at the same point.
And good news is that it’s very simple to implement! All you need is to add a timescale value represented as a float to your game struct where timescale 1.0 means that the game is running at the normal speed and values lesser or higher mean that that the game is running slower/faster.
while (game->frameAccumulator >= DT / game->timescale) {
physics_simulate(SEC_TO_NS(DT));
game->time += DT;
game->frameAccumulator -= DT / game->timescale;
}And done!
Any timescale values between 0 to 1 will make the game to run in slowmotion and values above 1 would make the time pass faster.
The best part is that the shaders are also slowing and speeding up correctly.
Okay now let’s add the final touch which are timers. Very often you do want to have a system where time is used to control certain events like a door opening 2 seconds after a button was pressed or a tutorial message is displayed if the player doesn’t move after 10 seconds.
The semi-fixed timestep method can be extended to tick those timers.
typedef struct Timer {
u64 timeElapsed;
u64 duration;
void (* function) (void *args);
void *args;
int expired;
} Timer;
void timer_tickTimers (Game *game, u64 deltaTime) {
for (uint i = 0; i < game->activeTimersCount; i++) {
Timer *timer = game->activeTimers[i];
timer->timeElapsed += deltaTime;
if (timer->timeElapsed > timer->duration) {
timer->expired = 1;
if (timer->function) {
timer->function(timer->args);
}
}
}
}And it would be called from inside the while loop.
while (game->frameAccumulator >= DT / game->timescale) {
timer_tickTimers(game, DT);
physics_simulate(SEC_TO_NS(DT));
game->time += DT;
game->frameAccumulator -= DT / game->timescale;
}You could use the macros from earlier to help with setting the duration of the timers.
Timer timer = {
.duration = SEC_TO_NS(10.0f),
.function = game_displayTutorialMessage,
};One additional thing is to initialize the time values right after the game is finished initializing. Otherwise the first frameTime of game_loop is capped to 250ms and if there is a timer that starts ticking from the first frame, it will end 250ms sooner than the set duration.
void game_init (Game *game) {
// After the game has been initialized
u64 newTime = OS_getTimeNs();
game->frameTime = (newTime - game->currentTime);
game->previousTime = game->currentTime;
game->currentTime = newTime;
}And we’re done! Putting it all together the game loop now looks like so:
void game_loop (Game *game) {
u64 newTime = OS_getTimeNs();
game->frameTime = (newTime - game->currentTime);
game->previousTime = game->currentTime;
game->currentTime = newTime;
game->frameAccumulator += MIN(game->frameTime, MS_TO_NS(250));
processInput();
while (game->frameAccumulator >= DT / game->timescale) {
timer_tickTimers(game, DT);
physics_simulate(SEC_TO_NS(DT));
game->time += DT;
game->frameAccumulator -= DT / game->timescale;
}
renderGame();
}In the next article I will go over how to add an input replay system here to record and playback player input. It’s a very powerful tool that could be used for a range of things like debugging the physics engine, self-playing showcases, and automated QA testing.
For simplicity I have left out the state interpolation.
Since most GPUs don’t support U64 values, the U64 can be divided into two U32s composed from the upper and lower bits and this could be potentially used as a timer in the shader removing the need to use the float format altogether. But there is no implementation and I haven’t tried it. It’s very likely that the final solution would be different.


I prefer doubles in seconds to integers for game time.
* More readable for debugging. 1.16 seconds vs 1160000000 ns
* Your nanoseconds timers probably aren't as accurate as you think(depending on os, settings, hardware) and why do you need that precision to begin with?
* Doubles are more flexible. Since integers are fixed point you'll have to change a bunch of code if you suddenly decide that you'd rather work in microseconds or any other unit.
* More than enough precision for game time and animation. If changes in precision still worry you then just start your clock at 2^32 seconds - this will give your clock constant sub-microsecond precision for over a hundred years.
* You can use float32 for deltas as they have enough precision for any real use case involving game time. With nanosecond integers uint32 deltas will overflow after a few seconds.
That said, the most important thing is getting away from float32. Uint64 is a valid choice but I think doubles have meaningful advantages.