Allegro.cc - Online Community

Allegro.cc Forums » Programming Questions » Perfect Game Loop (A5 edition)

This thread is locked; no one can reply to it. rss feed Print
Perfect Game Loop (A5 edition)
SiegeLord
Member #7,827
October 2006
avatar

So, since I recently decided to start playing around with A5, I again decided to tackle the problem of the perfect game loop for fixed timestep timing system (the only deterministic system there is).

I've came up with two approaches, which roughly operate to give the same resulting logic per second and frame per second rates.

This first approach uses A5 events and timers in a very hacky fashion. I don't really like it, although it takes the fewest number of lines, and perhaps is easier to understand. Basically, we use a timer only event queue to control our logic updates, and at the same time rest when possible.

1int refresh_rate = 60;
2float max_logic_time = 0.5f / refresh_rate;
3ALLEGRO_EVENT timer_dummy;
4 
5ALLEGRO_TIMER *timer = al_install_timer(ALLEGRO_BPS_TO_SECS(refresh_rate));
6al_start_timer(timer);
7ALLEGRO_EVENT_QUEUE *timer_queue = al_create_event_queue();
8al_register_event_source(timer_queue, (ALLEGRO_EVENT_SOURCE*)timer);
9 
10bool done = false;
11while (!done)
12{
13 al_wait_for_event(timer_queue, &timer_dummy);//rest at least the fixed time step
14 double start_time = al_current_time();
15 do
16 {
17 DoLogic()
18
19 if(al_current_time() - start_time > max_logic_time)//break if we are taking too long
20 break;
21 } while(al_get_next_event(timer_queue, &timer_dummy));
22 
23 al_flush_event_queue(timer_queue);//remove excess overflow
24
25 al_wait_for_vsync();
26 al_clear(black);
27
28 DrawStuff();
29
30 al_flip_display();
31}

The second approach takes more lines, but it does not use A5 timers or events, making it a little more elegant in that sense. It's operation is a little more complicated, but I am sure you all will be able to figure it out. Another advantage of this method is that it maps a little more closely to the abortive delta timing method, making it a little more tractable.

1int refresh_rate = 60;
2double fixed_dt = 1.0f / refresh_rate;
3double old_time = al_current_time();
4double game_time = al_current_time();
5 
6bool done = false;
7while (!done)
8{
9 double dt = al_current_time() - old_time;
10 al_rest(fixed_dt - dt); //rest at least fixed_dt
11 dt = al_current_time() - old_time;
12 old_time = al_current_time();
13
14 if(old_time - game_time > dt)//eliminate excess overflow
15 {
16 game_time += fixed_dt * floor((old_time - game_time) / fixed_dt);
17 }
18
19 double start_time = al_current_time();
20 while(old_time - game_time >= 0)
21 {
22 game_time += fixed_dt;
23
24 DoLogic();
25
26 if(al_current_time() - start_time > fixed_dt * 0.5)//break if we start taking too long
27 break;
28 }
29
30 al_wait_for_vsync();
31 al_clear(black);
32
33 DrawStuff();
34
35 al_flip_display();
36}

So yeah... comments/suggestions? I think the second one can be improved by hiding some of its functionality behind functions, for one.


EDIT:

Notably, if you promise that your DoLogic() will never ever ever ever take longer than the fixed time step to complete, you can simplify the versions as follows:

A5 Timer/Event:

1int refresh_rate = 60;
2ALLEGRO_EVENT timer_dummy;
3 
4ALLEGRO_TIMER *timer = al_install_timer(ALLEGRO_BPS_TO_SECS(refresh_rate));
5al_start_timer(timer);
6ALLEGRO_EVENT_QUEUE *timer_queue = al_create_event_queue();
7al_register_event_source(timer_queue, (ALLEGRO_EVENT_SOURCE*)timer);
8 
9bool done = false;
10while (!done)
11{
12 al_wait_for_event(timer_queue, &timer_dummy);//rest at least the fixed time step
13 do
14 {
15 DoLogic()
16 } while(al_get_next_event(timer_queue, &timer_dummy));
17
18 al_wait_for_vsync();
19 al_clear(black);
20
21 DrawStuff();
22
23 al_flip_display();

A5 cur_time:

1int refresh_rate = 60;
2double fixed_dt = 1.0f / refresh_rate;
3double old_time = al_current_time();
4double game_time = al_current_time();
5 
6bool done = false;
7while (!done)
8{
9 double dt = al_current_time() - old_time;
10 al_rest(fixed_dt - dt); //rest at least fixed_dt
11 dt = al_current_time() - old_time;
12 old_time = al_current_time();
13
14 while(old_time - game_time >= 0)
15 {
16 game_time += fixed_dt;
17
18 DoLogic();
19 }
20
21 al_wait_for_vsync();
22 al_clear(black);
23
24 DrawStuff();
25
26 al_flip_display();
27}

"For in much wisdom is much grief: and he that increases knowledge increases sorrow."-Ecclesiastes 1:18
[SiegeLord's Abode][Codes]:[DAllegro5]:[RustAllegro]

Thomas Harte
Member #33
April 2000
avatar

Quote:

... for fixed timestep timing system (the only deterministic system there is).

Don't tell Newton!

My only useful comment: if al_rest can at any point rest a little too short (e.g. if it resnts to within ±2ms) then following:

al_rest(fixed_dt - dt); //rest at least fixed_dt
  dt = al_current_time() - old_time;

dt may be less than fixed_dt. Is that what you want? If al_rest has a stated accuracy then you should round down to th enearest multiple of that and then add one more multiple, if it doesn't then I guess there's little you can do.

Re:

  al_wait_for_vsync();
  al_clear(black);
  
  DrawStuff();
  
  al_flip_display();

Would it not be more sensible to do:

  al_clear(black);
  
  DrawStuff();
  
  al_wait_for_vsync();
  al_flip_display();

Vsyncing before your draw and then flipping after whatever portion of a frame it takes you to draw is likely to lead to tearing.

Other than that, I've yet to fully digest the code. And it's slightly past work o'clock.

SiegeLord
Member #7,827
October 2006
avatar

Aha, thanks for the vsync tip. It doesn't really work at all for me, so I couldn't tell the difference between different locations for it. Your reasoning makes sense though.

Quote:

Is that what you want? If al_rest has a stated accuracy then you should round down to th enearest multiple of that and then add one more multiple, if it doesn't then I guess there's little you can do.

The idea is that we rest until the time passed since we last measured time is at least fixed_dt. dt is usually smaller than fixed_dt, so we usually rest for some period of time.

"For in much wisdom is much grief: and he that increases knowledge increases sorrow."-Ecclesiastes 1:18
[SiegeLord's Abode][Codes]:[DAllegro5]:[RustAllegro]

MiquelFire
Member #3,110
January 2003
avatar

al_clear(black); I believe that should be in DrawStuff();

Maybe this was with AllegroGL (and by extension, OpenLayer) only (and I haven't looked at Allegro5's docs yet), but doesn't calling al_flip_display(); imply al_wait_for_vsync(); on platforms that support vsync?

---
Febreze (and other air fresheners actually) is just below perfumes/colognes, and that's just below dead skunks in terms of smells that offend my nose.
MiquelFire.red
If anyone is of the opinion that there is no systemic racism in America, they're either blind, stupid, or racist too. ~Edgar Reynaldo

Trent Gamblin
Member #261
April 2000
avatar

No, it doesn't.

Bob
Free Market Evangelist
September 2000
avatar

Quote:

doesn't calling al_flip_display(); imply al_wait_for_vsync(); on platforms that support vsync?

It probably should (or at least be a parameter).

It's not clear to me that al_wait_for_vsync() is useful by itself.

--
- Bob
[ -- All my signature links are 404 -- ]

Trent Gamblin
Member #261
April 2000
avatar

How would it make any difference? The user calls al_wait_for_vsync then al_flip_display.

Bob
Free Market Evangelist
September 2000
avatar

What happens if al_flip_display() waits for vsync anyway (eg: an OpenGL backend)? Do we wait for vsync twice? If we ignore al_wait_for_vsync() on those platforms, apps lose the ability to time things using vsync (which is still useful for many reasons).

--
- Bob
[ -- All my signature links are 404 -- ]

Trent Gamblin
Member #261
April 2000
avatar

The opengl backend doesn't wait for vsync AFAIK unless you call al_wait_for_vsync yourself.

Bob
Free Market Evangelist
September 2000
avatar

Yes it does. At the end of the day, it calls wglSwapBuffers(), which is allowed to (and most of the time does) wait for vsync, by default.

--
- Bob
[ -- All my signature links are 404 -- ]

Elias
Member #358
May 2000

We should remove al_wait_for_vsync and make it a display flag.

--
"Either help out or stop whining" - Evert

Thomas Fjellstrom
Member #476
June 2000
avatar

Quote:

We should remove al_wait_for_vsync and make it a display flag.

I was thinking the same thing. And a HINT at that. We can't guarantee its enabled or disabled since the user can force the issue in the driver settings.

--
Thomas Fjellstrom - [website] - [email] - [Allegro Wiki] - [Allegro TODO]
"If you can't think of a better solution, don't try to make a better solution." -- weapon_S
"The less evidence we have for what we believe is certain, the more violently we defend beliefs against those who don't agree" -- https://twitter.com/neiltyson/status/592870205409353730

SiegeLord
Member #7,827
October 2006
avatar

Okay, I looked around some other forums, and Bullet source code, and it seems that this is the optimal solution. I probably will put it up on the wiki eventually...

1int refresh_rate = 60;
2double fixed_dt = 1.0f / refresh_rate;
3double old_time = al_current_time();
4double game_time = al_current_time();
5 
6bool done = false;
7while (!done)
8{
9 /*
10 Standard rester:
11 Forces the drawing to take at least one fixed_dt interval, reducing CPU usage
12 in lighweight programs.
13 */
14 double dt = al_current_time() - old_time;
15 al_rest(fixed_dt - dt);
16 dt = al_current_time() - old_time;
17 old_time = al_current_time();
18
19 /*
20 Time overflow eliminator:
21 Makes sure that when game time gets too far behind,
22 the logic while loop does not enter a period of
23 hyperactivity. Note that this may need to be eliminated
24 for networked games, where game_time should be synchronized
25 across machines.
26 */
27 if(old_time - game_time > dt)
28 {
29 game_time += fixed_dt * floor((old_time - game_time) / fixed_dt);
30 }
31
32 double start_time = al_current_time();
33 while(old_time - game_time >= 0)
34 {
35 game_time += fixed_dt;
36
37 DoLogic();
38
39 /*
40 Logic time limiter:
41 It ensures that the logic does not spiral out of control when it starts
42 taking too long to complete. This measures the time for all of the logic
43 steps, not just one. I find that this leads to a smoother operation than
44 when limiting each step individually. This favours FPS over LPS.
45 */
46 if(al_current_time() - start_time > fixed_dt)
47 break;
48 }
49 
50 /*
51 Various drawing operations go here
52 */
53 al_clear(black);
54
55 DrawStuff();
56 
57 al_wait_for_vsync();
58 al_flip_display();
59}

"For in much wisdom is much grief: and he that increases knowledge increases sorrow."-Ecclesiastes 1:18
[SiegeLord's Abode][Codes]:[DAllegro5]:[RustAllegro]

Thomas Harte
Member #33
April 2000
avatar

Sorry, this is absolutely more my day job editor instincts coming out than my programmer instincts, but maybe chuck an extra comment in above 'double start_time = al_current_time();' to the effect that the main logic call loop follows, so that it's more obvious to newbies where the section of code that "may need to be eliminated for networked games" ends?

My unnecessary, tedious pedantry also makes me comment again on:

SiegeLord said:

fixed timestep timing system (the only deterministic system there is).

To the effect that my argument for disagreeing with your assertion is that real life isn't run on a fixed timestep system and you cannot build a deterministic system from a non-deterministic system.

SiegeLord
Member #7,827
October 2006
avatar

Quote:

Sorry, this is absolutely more my day job editor instincts coming out than my programmer instincts, but maybe chuck an extra comment in above 'double start_time = al_current_time();' to the effect that the main logic call loop follows, so that it's more obvious to newbies where the section of code that "may need to be eliminated for networked games" ends?

Ah, good point. I'll be more blatant about it, and why that actually works in the actual article.

Quote:

To the effect that my argument for disagreeing with your assertion is that real life isn't run on a fixed timestep system and you cannot build a deterministic system from a non-deterministic system.

What I mean by deterministic is that, given identical starting conditions, when you run a game it will run identically in every case. This is impossible (pretty much) to do with a variable time step, especially one dependent on framerate. There are other benefits to using fixed step timing of course, but that's irrelevant in this case.

"For in much wisdom is much grief: and he that increases knowledge increases sorrow."-Ecclesiastes 1:18
[SiegeLord's Abode][Codes]:[DAllegro5]:[RustAllegro]

Go to: