Tom Says: Safe code is boring code!
Can you spot the timing error in this code? It doesn't matter, but the language is D. Though it doesn't affect the bug I found (there are bugs I haven't found), SDL_Delay sleeps the thread for the given number of milliseconds.
void main ()
{
const int desired_framerate = 1; // frames per second
const int milliseconds_per_frame = 1000 / desired_framerate;
// Track the starting time
int last_ticks = SDL_GetTicks ();
main_loop:
while ( running )
{
// Calculate time spent on last frame
int current_ticks = SDL_GetTicks ();
int frame_duration = current_ticks - last_ticks;
last_ticks = current_ticks;
// Update the game
running = update ( frame_duration );
if ( running )
{
// Render current game state to the screen
game_render ();
// Cap the framerate
if ( frame_duration < milliseconds_per_frame )
{
writefln ( "Waiting %d", milliseconds_per_frame - frame_duration );
SDL_Delay ( milliseconds_per_frame - frame_duration );
}
}
}
}
Here is a corrected version which seems to fix the timing error, but does not fix many bugs I don't know about.
void main ()
{
const int desired_framerate = 1; // frames per second
const int milliseconds_per_frame = 1000 / desired_framerate;
// Track the starting time
int last_ticks = SDL_GetTicks ();
main_loop:
while ( running )
{
// Calculate time spent on last frame
int current_ticks = SDL_GetTicks ();
int frame_duration = current_ticks - last_ticks;
// Cap the framerate
if ( frame_duration < milliseconds_per_frame )
{
writefln ( "Waiting %d", milliseconds_per_frame - frame_duration );
SDL_Delay ( milliseconds_per_frame - frame_duration );
current_ticks = SDL_GetTicks ();
frame_duration = current_ticks - last_ticks;
}
last_ticks = current_ticks;
// Update the game
running = update ( frame_duration );
if ( running )
{
// Render current game state to the screen
game_render ();
}
}
}
I found the problem by analyzing the output from two writefln function calls. The first is listed in the "Cap the framerate" block, and another is inside the update function, and prints frame_duration. I noticed a pattern like this:
Waiting 17 Update 31 Render! Waiting 969 Update 984 Render! Waiting 16 Update 32 Render! Waiting 968 Update 980 Render!
Apparently, the time spent by SDL_Delay was being counted toward the the duration of the next frame. So one frame would be throttled properly, but the time spent in SDL_Delay was counted toward the next frame's time. Thus, it would appear to have spent about exactly the right amount of time, and there would be a very short delay. Then the next frame would be seen as taking a very short amount of time, so the delay would be longer, although close to being accurate.
Proper output looks like this:
Waiting 1000 Update 1000 Render! Waiting 987 Update 1000 Render! Waiting 988 Update 1000 Render! Waiting 984
By the way, this is all assuming that the update and game_render functions take much less time than one frame. If they take longer, the SDL_Delay call won't be made at all.
While ensuring that the SDL_Delay call wouldn't be made if the frame rendered fast enough, I noticed that when the delay is added, the time spent per frame (total frame time − time passed to SDL_Delay) averages about 12 milliseconds on my computer. When the delay block is not activated, the frame takes about 8 milliseconds to render. That means that between SDL_Delay, SDL_GetTicks, writefln, and the integer arithmetic, there are a few milliseconds being lost when the game is moving too quickly. So far, for me, this is not a problem worth worrying about, but it's good to know.
This is another cool problem: generating prime numbers. I made my prime number sieve multi-threaded in the best way I knew how, but got zero speed-up on a dual-core system. Why?
Posted May 29, 2007, in the late, late night.