Allegro.cc - Online Community

Allegro.cc Forums » Allegro Development » event loop best practice

This thread is locked; no one can reply to it. rss feed Print
event loop best practice
Peter Hull
Member #1,136
March 2001

On topic for once...

I have found that writing the main event loop is not trivial and there are several event-getting functions that Allegro has.

In the demos and examples they use different ways.
Speed
Skater
Cosmic Protector

I think this might be confusing for people starting out.

Is there a 'best' way to do it (for programs that don't have unusual requirements) and if so, should we emphasize it in the docs/examples/demos?

Edgar Reynaldo
Member #8,592
May 2007
avatar

Let's distill down the essence of each loop, shall we?

From your first link for Speed : https://github.com/liballeg/allegro5/blob/6017bed18585146d2499aa0a13709f975a9bb047/demos/speed/main.c#L67-L129

We have this :

#SelectExpand
1 while (!gameover) { 2 3 /* move everyone */ 4 while ((al_get_timer_count(inc_counter) > 0) && (!gameover)) { 5 update_view(); 6 update_bullets(); 7 update_explode(); 8 update_message(); 9 10 if (update_badguys()) { 11 if (advance_view()) { 12 cyclenum++; 13 al_set_timer_count(inc_counter, 0); 14 lay_attack_wave(TRUE); 15 advance_player(TRUE); 16 } 17 else { 18 lay_attack_wave(FALSE); 19 advance_player(FALSE); 20 } 21 } 22 23 gameover = update_player(); 24 25 al_set_timer_count(inc_counter, al_get_timer_count(inc_counter)-1); 26 redraw = 1; 27 } 28 29 /* take a screenshot? */ 30 if (key[ALLEGRO_KEY_PRINTSCREEN]) { 31 static int ss_count = 0; 32 33 char fname[80]; 34 35 sprintf(fname, "speed%03d.tga", ++ss_count); 36 37 al_save_bitmap(fname, al_get_backbuffer(screen)); 38 39 while (key[ALLEGRO_KEY_PRINTSCREEN]) 40 poll_input_wait(); 41 42 al_set_timer_count(inc_counter, 0); 43 } 44 45 /* toggle fullscreen window */ 46 if (key[ALLEGRO_KEY_F]) { 47 int flags = al_get_display_flags(screen); 48 al_set_display_flag(screen, ALLEGRO_FULLSCREEN_WINDOW, 49 !(flags & ALLEGRO_FULLSCREEN_WINDOW)); 50 51 while (key[ALLEGRO_KEY_F]) 52 poll_input_wait(); 53 } 54 55 /* draw everyone */ 56 if (redraw) { 57 draw_view(); 58 redraw = 0; 59 } 60 else { 61 rest(1); 62 } 63}

Let's analyze that. The basic form is this :

   while (!gameover) {
      while ((al_get_timer_count(inc_counter) > 0) && (!gameover)) {
         /* LOGIC */
      }
      if (redraw) {
         draw_view();
         redraw = 0;
      }
      else {
         rest(1);
      }
   }

How is that any different than the old A4 style game loop?

while (keepgoing) {
   while (ticks--) {
      Logic();
   }
   if (redraw) {
      Redraw();
      redraw = false;
   }
   rest(1);

That's a horrible way to code a game loop.

Let's move on.

Example 2, Skater demo : https://github.com/liballeg/allegro5/blob/6017bed18585146d2499aa0a13709f975a9bb047/demos/skater/src/framework.c#L256-L464

Basically, it boils down to this :

#SelectExpand
1/* Do the main loop; until we're not done. */ 2while (!done) { 3 4 ALLEGRO_EVENT event; 5 6 al_wait_for_event(event_queue, &event); 7 8 HandleEvent(event); 9 10 if (!al_is_event_queue_empty(event_queue)) continue; 11 12 /* Check if the timer has ticked. */ 13 while (timer > 0) { 14 --timer; 15 16 CheckScreenshot(); 17 18 HandleState(); 19 20 /* We just did one logic frame so we assume we will need to update 21 the visuals to reflect the changes this logic frame made. */ 22 need_to_redraw = 1; 23 24 /* Let the framerate counter know that one logic frame was run. */ 25 fps_tick(fps); 26 27 keyboard_tick(); 28 mouse_tick(); 29 } 30 31 /* In case a frame of logic has just been run or the user wants 32 unlimited framerate, we must redraw the screen. */ 33 if (need_to_redraw == 1 && !background_mode) { 34 draw_framework(); 35 need_to_redraw = 0; 36 } 37 38 /* Check if the user pressed the close icon. */ 39 done = done || closed; 40 41}

That's ugly, and it depends on a bunch of globals.

Basically, it's this :

#SelectExpand
1/* Do the main loop; until we're not done. */ 2while (!done) { 3 4 do { 5 ALLEGRO_EVENT event; 6 al_wait_for_event(event_queue, &event); 7 8 HandleEvent(event); 9 } while (!al_is_event_queue_empty(event_queue)); 10 11 /* Check if the timer has ticked. */ 12 while (timer > 0) { 13 --timer; 14 Update(); 15 redraw = true; 16 } 17 if (redraw) { 18 draw_framework(); 19 redraw = false; 20 } 21}

Which is much cleaner and easier to understand. It's your basic A5 game loop.

Third, we have Cosmic Protector : https://github.com/liballeg/allegro5/blob/6017bed18585146d2499aa0a13709f975a9bb047/demos/cosmic_protector/src/logic.cpp#L52-L67

And, I can't follow that or even find main, so I'm not gonna try.

Skater wins.

Niunio
Member #1,975
March 2002
avatar

Actually, the SPEED loop is an Allegro 4 loop. The original was written for a Speed Hack by Shawn himself. To include it as an example to the newer Allegro they wrote a sort of "wrap layer" to simulate Allegro 4, defined in file a4_aux.h and implemented in file a4_aux.c. It is an example about how to implement an old-school game system.

Also, you have more ways to work with the game loop. Since my game engine is component based, my game loop is quite simpler than those, but of course it needs a lot of "behind the scene" work.

SourceForge doesn't have a fancy way to show code, so I just copypaste it here from mnggame.pas:

#SelectExpand
1164 { Game loop. } 1165 SELF.Log (etDebug, GAME_LOOP_START); 1166 fTimer.Start; 1167 REPEAT 1168 TRY 1169 fEventManager.Poll 1170 EXCEPT 1171 ON E: Exception DO HandleException (E) 1172 END 1173 UNTIL fTerminated; 1174 fTimer.Stop; 1175 SELF.Log (etDebug, GAME_LOOP_END);

Simple explanation, each component that needs Allegro events just registers itself into the fEventManager and the Poll method just manages it. The fTimer object, for example, is one of these components, and it not just updates the clock but also calls the appropriate methods to update the game and render the stuff on screen. But it is quite more complex actually.

Anyway, I think there's no one best way to do it.

-----------------
Current projects: Allegro.pas | MinGRo

Peter Hull
Member #1,136
March 2001

I don't suppose there's a single best way that can be copied and pasted into every project. But if you look at what Edgar did, he was able to distill out the essence of each alternative.

In Cosmic Protector, the main loop looks like

 repeat {
  delta = time_since_last_frame
  logic(delta) 
  render(delta) 
  pause for 1/60 second
}

and the 'logic' is

while (event queue not empty) {
 get event
 process event
}

and the only events it's interested in are display close and switch out.

That timer-less approach is quite common in code I have seen.

Most code seems to use variations on the Skater loop, which uses a timer.

repeat {
  repeat {
   get event
   if timer event then update = true
  } until (queue is empty)
  if update {
   logic
   redraw
  }
}

(with the possible refinement of handling > 1 timer event per batch of events)

There are also al_wait_for_event_timed and al_wait_for_event_until which I don't think I have seen used anywhere, but they ought to be usable to control timing in an event loop.

Finally there are the bad ones

repeat {
 get event
 process event
 logic
 redraw
}

and

repeat {
 wait for event
 process event
 logic 
 redraw
}

which need no further discussion!

EDIT:
Coincidentally this appeared on the front page of Hacker News this morning but I haven't been bothered to read it yet.
http://gameprogrammingpatterns.com/game-loop.html

Edgar Reynaldo
Member #8,592
May 2007
avatar

What about frame skipping and different logic and rendering rates? We should discuss those too.

Logical frame skipping is simple. Only run one update, even if there are multiple timer events. Ignoring them will make your game slow down gracefully and you will see every update on the screen. However, sometimes you don't want your game to slow down. You can run multiple logic updates per render call, and then your game will run the same speed on every computer if you're using a timer. However, this may lead to teleportation if it takes longer to process logic than you have time available after setting a rendering rate.

My personal preference is to drop all timer events after the first. I'd rather the game slow down if the computer can't handle it. Then I can see whether or not my hardware is good enough to run it.

Peter Hull
Member #1,136
March 2001

What about frame skipping and different logic and rendering rates? We should discuss those too.

Yes; some games (maybe the more 'vector-ish' ones, like flight sims, might suit that)

Also, in the code above

   do {
      ALLEGRO_EVENT event;
      al_wait_for_event(event_queue, &event);

      HandleEvent(event);
   } while (!al_is_event_queue_empty(event_queue));

is there a chance that the queue will never be empty (say if you whizzed the mouse about continually) and so the loop will never run?

l j
Member #10,584
January 2009
avatar

A mouse has a refresh rate up to 1000hz, I don't think a mouse event handler couldn't run under 0.001 seconds. I suppose if some of the event handlers do take a lot of time the loop could potentially not finish.

Edgar Reynaldo
Member #8,592
May 2007
avatar

I've never been able to whizz the mouse around fast enough to make the rendering pause or stutter. Don't think it's likely under normal circumstances.

If you were feeling paranoid, you could set a limit on the max number of events to handle at once, but then you get events piling up sometimes.

I know if you try to render once for every mouse event you're gonna feel the pain, because most screens don't update as fast as the mouse.

Go to: