Allegro.cc - Online Community

Allegro.cc Forums » Programming Questions » Implementing game loop

This thread is locked; no one can reply to it. rss feed Print
Implementing game loop
almbfsek
Member #12,778
April 2011

I've been reading this. It explains couple of game loop types. I'm mostly interested in implementing the last one: "Constant Game Speed independent of Variable FPS"
I'm having hard time to understand it. AFAIU it basically updates the game logic 25 times per second but it there is no limit on rendering. But it also implements an interpolation value for in-between times.

While implementing it I wanted to use allegro 5's timer functions so I came up with this:

#SelectExpand
1 //Start timer 2#include <stdio.h> 3#include <allegro5/allegro.h> 4 5#define WIDTH 960 6#define HEIGHT 640 7#define LOGIC_FPS 5 8 9int main(int argc, char **argv) 10{ 11 ALLEGRO_DISPLAY *display; 12 ALLEGRO_EVENT_QUEUE *eventQueue; 13 ALLEGRO_TIMER *timer; 14 15 bool isGameRunning = true; 16 bool updateGameLogic = true; 17 18 //Various inits 19 if(!al_init()) { 20 fprintf(stderr, "failed to initialize allegro\n"); 21 return EXIT_FAILURE; 22 } 23 24 timer = al_create_timer(1.0 / LOGIC_FPS); 25 if(!timer) { 26 fprintf(stderr, "failed to create timer!\n"); 27 return EXIT_FAILURE; 28 } 29 30 display = al_create_display(WIDTH, HEIGHT); 31 if(!display) { 32 fprintf(stderr, "failed to create display!\n"); 33 al_destroy_timer(timer); 34 return EXIT_FAILURE; 35 } 36 37 eventQueue = al_create_event_queue(); 38 if(!eventQueue) { 39 fprintf(stderr, "failed to create eventQueue!\n"); 40 al_destroy_timer(timer); 41 al_destroy_display(display); 42 return EXIT_FAILURE; 43 } 44 45 //Tie events to queue 46 al_register_event_source(eventQueue, al_get_display_event_source(display)); 47 al_register_event_source(eventQueue, al_get_timer_event_source(timer)); 48 49 //Start timer 50 al_start_timer(timer); 51 52 //Main loop 53 while(isGameRunning) 54 { 55 ALLEGRO_EVENT e; 56 al_wait_for_event(eventQueue, &e); 57 58 //Input 59 if(e.type == ALLEGRO_EVENT_TIMER) 60 updateGameLogic = true; 61 else if(e.type == ALLEGRO_EVENT_DISPLAY_CLOSE) 62 isGameRunning = false; 63 64 //Update game logic 65 if(updateGameLogic && al_is_event_queue_empty(eventQueue)) { 66 updateGameLogic = false; 67 fprintf(stdout, "Logic updated\n"); 68 } 69 70 //Update display 71 //Calculate interpolation here 72 al_clear_to_color(al_map_rgb(50,123,1)); 73 al_flip_display(); 74 fprintf(stdout, "Display updated\n"); 75 } 76 77 //Cleaning 78 al_destroy_timer(timer); 79 al_destroy_display(display); 80 al_destroy_event_queue(eventQueue); 81 82 return EXIT_SUCCESS; 83}

But it doesn't seem to work. For some reason logic update and display update happens at the same time with a rate of 5 times per second (LOGIC_FPS).

I need advice.

Thanks in advance.

Desmond Taylor
Member #11,943
May 2010
avatar

I'd suggest using al_wait_for_event_until.

This is how I would do it.

#SelectExpand
1 ALLEGRO_TIMEOUT timeout; 2 al_init_timeout( &timeout, 0.6 ); 3 4 al_wait_for_event_until( this->queue, &this->event, &timeout ); 5 6 switch ( this->event.type ) 7 { 8 case ALLEGRO_EVENT_TIMER: 9 { 10 this->ticked = true; 11 } 12 break; 13 }

That's taken strait from my code so don't just copy and paste. It's only an example so will not work without the rest of my class.

AMCerasoli
Member #11,955
May 2010
avatar

Here is what I use to separate the logic from the drawing... Obviously isn't complete, I haven't started any project which needs to separate the logic from the drawing.. If you're creating a simple game probably you don't need it, but it's good to understand and work this way from the beginning.

#SelectExpand
1#include <allegro5/allegro.h> 2#include <allegro5/allegro_native_dialog.h> 3 4int main(int argc, char **argv) 5{ 6 ALLEGRO_DISPLAY *display = NULL; 7 ALLEGRO_EVENT_QUEUE *event_queue = NULL; 8 ALLEGRO_EVENT ev; 9 ALLEGRO_TIMER *FPS = NULL; //Frames 10 ALLEGRO_TIMER *LPS = NULL; //Logic 11 bool redraw = true; 12 13 if(!al_init()) { 14 al_show_native_message_box(display, "Error", "Error", "Failed to initialize allegro!", NULL, ALLEGRO_MESSAGEBOX_ERROR); 15 return -1; 16 } 17 18 FPS = al_create_timer(1.0 / 5); //Frames 19 LPS = al_create_timer(1.0 / 10); //Logic 20 21 display = al_create_display(640, 480); 22 23 event_queue = al_create_event_queue(); 24 25 al_register_event_source(event_queue, al_get_display_event_source(display)); 26 27 al_register_event_source(event_queue, al_get_timer_event_source(FPS)); 28 29 al_register_event_source(event_queue, al_get_timer_event_source(LPS)); 30 31 al_clear_to_color(al_map_rgb(0,0,0)); 32 33 al_flip_display(); 34 35 al_start_timer(FPS); 36 al_start_timer(LPS); 37 38 while(1) 39 { 40 41 al_wait_for_event(event_queue, &ev); 42 43 if(ev.timer.source == LPS) { //LOGIC 44 Beep(500,10); 45 } 46 47 else if(ev.type == ALLEGRO_EVENT_DISPLAY_CLOSE) { 48 break; 49 } 50 51 else if(ev.timer.source == FPS) { //DRAW 52 al_clear_to_color(al_map_rgb(0,0,0)); 53 Beep(100,10); 54 al_flip_display(); 55 } 56 } 57 58 al_destroy_timer(FPS); 59 al_destroy_timer(LPS); 60 al_destroy_display(display); 61 al_destroy_event_queue(event_queue); 62 63 return 0; 64}

You can compile it right away. The Beep() function is just to make a "beep"...

I may be wrong but actually you should separate the input from the logic and the drawing, that is what I'm trying to do. With Allegro 5 this is very easy to do.

But to do this, each object (if you're using C++, which I hope, since is the best for game programming) should have:

- A Draw Function
- A Logic Function

and..

- A Input function (depending if that object is controlled by the user.)

Anyway, I'm not pretty sure about all this, I still learning. :-X

almbfsek
Member #12,778
April 2011

Desmond I guess it doesn't have anything to do with my game loop question but I'm eager to know what advnatage would I have by using a timeout? I originally used al_wait_for_event_until as seen in the wiki tutorials but then I thought the simple the better and got rid off the timeout. Thanks

AMCerasoli, I tested it and it sucessfully separates game logic and FPS but still the FPS is pre determined. As suggested in the link I gave why run at 25 FPS when its possible to run it at 300 FPS :) it's wasting clock cycles. My code basically does the same thing except it doesn't put a limit on the FPS. I still don't see why it doesn't work though. any ideas?

EDIT: Ok here is an hybrid between AMCerasoli's and my code. Someone please tell me why "Limitless update" gets written at the same rate with "Display updated"? Change the FPS value and see it for your self. What am I doing wrong?

#SelectExpand
1#include <stdio.h> 2#include <allegro5/allegro.h> 3 4#define WIDTH 960 5#define HEIGHT 640 6#define LPS 1 7#define FPS 4 8 9int main(int argc, char **argv) 10{ 11 ALLEGRO_DISPLAY *display; 12 ALLEGRO_EVENT_QUEUE *eventQueue; 13 ALLEGRO_TIMER *lpsTimer, *fpsTimer; 14 ALLEGRO_EVENT e; 15 16 bool isGameRunning = true; 17 18 //Various inits 19 if(!al_init()) { 20 fprintf(stderr, "failed to initialize allegro\n"); 21 return EXIT_FAILURE; 22 } 23 24 lpsTimer = al_create_timer(1.0 / LPS); 25 if(!lpsTimer) { 26 fprintf(stderr, "failed to create lpsTimer!\n"); 27 return EXIT_FAILURE; 28 } 29 30 fpsTimer = al_create_timer(1.0 / FPS); 31 if(!fpsTimer) { 32 fprintf(stderr, "failed to create fpsTimer!\n"); 33 return EXIT_FAILURE; 34 } 35 36 display = al_create_display(WIDTH, HEIGHT); 37 if(!display) { 38 fprintf(stderr, "failed to create display!\n"); 39 al_destroy_timer(lpsTimer); 40 al_destroy_timer(fpsTimer); 41 return EXIT_FAILURE; 42 } 43 44 eventQueue = al_create_event_queue(); 45 if(!eventQueue) { 46 fprintf(stderr, "failed to create eventQueue!\n"); 47 al_destroy_timer(lpsTimer); 48 al_destroy_timer(fpsTimer); 49 al_destroy_display(display); 50 return EXIT_FAILURE; 51 } 52 53 //Tie events to queue 54 al_register_event_source(eventQueue, al_get_display_event_source(display)); 55 al_register_event_source(eventQueue, al_get_timer_event_source(lpsTimer)); 56 al_register_event_source(eventQueue, al_get_timer_event_source(fpsTimer)); 57 58 //Start timers 59 al_start_timer(lpsTimer); 60 al_start_timer(fpsTimer); 61 62 //Main loop 63 while(isGameRunning) 64 { 65 al_wait_for_event(eventQueue, &e); 66 67 switch(e.type) { 68 case ALLEGRO_EVENT_DISPLAY_CLOSE: 69 isGameRunning = false; 70 break; 71 72 case ALLEGRO_EVENT_TIMER: 73 if(e.timer.source == lpsTimer) { 74 fprintf(stdout, "Logic updated\n"); 75 } 76 else if(e.timer.source == fpsTimer) { 77 al_clear_to_color(al_map_rgb(50,123,1)); 78 al_flip_display(); 79 fprintf(stdout, "Display updated\n"); 80 } 81 break; 82 } 83 84 fprintf(stdout, "Limitless update\n"); 85 } 86 87 //Cleaning 88 al_destroy_timer(lpsTimer); 89 al_destroy_timer(fpsTimer); 90 al_destroy_display(display); 91 al_destroy_event_queue(eventQueue); 92 93 return EXIT_SUCCESS; 94}

Edgar Reynaldo
Major Reynaldo
May 2007
avatar

else if(ev.timer.source == FPS) { //DRAW

You are checking the timer field of the event without knowing the type of the event. ev.timer and ev.timer.source could have absolutely any value. Stop it. :'(

My take on separating logic and drawing :

#SelectExpand
1const float LOGIC_PER_SECOND = 120.0f; 2const float FRAMES_PER_SECOND = 60.0f; 3 4ALLEGRO_TIMER* logic_timer = al_create_timer(LOGIC_PER_SECOND); 5ALLEGRO_TIMER* display_timer = al_create_timer(FRAMES_PER_SECOND); 6 7// Event loop 8while (!quit) { 9 while (1) { 10 ALLEGRO_EVENT ev; 11 al_wait_for_event(event_queue , &ev); 12 if (ev.type == ALLEGRO_EVENT_TIMER) { 13 if (ev.timer.source == logic_timer) { 14 DoLogic(1.0/LOGIC_PER_SECOND); 15 } else if (ev.timer.source == display_timer) { 16 redraw = true; 17 } 18 } 19 if (al_is_event_queue_empty(event_queue)) {break;} 20 } 21 if (redraw) { 22 Redraw(); 23 } 24 // clean up 25}

With this simple logic you can implement any logic per second, and any frames per second, with frames being dropped if they are taking too long.

almbfsek
Member #12,778
April 2011

Edgar, that's what I came up with too. Can you solve my problem though?
BTW, what kind of effect does al_is_event_queue_empty(event_queue) have? why do I need it? thanks

edit: also, is it still logical to use interpolation with this method?

Edgar Reynaldo
Major Reynaldo
May 2007
avatar

almbfsek said:

As suggested in the link I gave why run at 25 FPS when its possible to run it at 300 FPS

Drawing more frames than the number of Hz that your monitor runs at is pointless, as you will never see more than Hz number of frames anyway. If you're talking about logic per second that's different though.

almbfsek said:

Someone please tell me why "Limitless update" gets written at the same rate with "Display updated"?

Because you output 'Limitless update' each time any event occurs - whether it's a logic event or a display event.

almbfsek said:

Can you solve my problem though?
BTW, what kind of effect does al_is_event_queue_empty(event_queue) have? why do I need it? thanks

You need to exhaust all of the events that are in the queue before you update your display so that all the logic events have been processed.

   if (al_is_event_queue_empty(event_queue)) {break;}

This line will break out of the event loop once there are no events left to process. If there are events left, it will go to the beginning of the loop and check the next event.

If you want to adjust the rate that each timer runs, you can do that with al_set_timer_speed.

Edit for your edit

almbfsek said:

edit: also, is it still logical to use interpolation with this method?

Well, you could do it with ALLEGRO_EVENT.timestamp and al_get_time. I don't see the point of using interpolation at this level though. It just complicates things needlessly. I also don't see the point of running logic at a different rate than the display.

almbfsek
Member #12,778
April 2011

Because you output 'Limitless update' each time any event occurs - whether it's a logic event or a display event.

Aah I see. It's because of al_wait_for_event(eventQueue, &e) right? It waits until an event occurs thus "Limitless update" happens exactly at the same speed of the fastest occurring event.

How can I truly make it separate though? I don't want to limit my FPS. I want to make it run as fast as possible while limiting game logic to 25 lps for example?

NVM I better use a good old get_tick like routine. since timers in allegro are tied to the events what I'm asking is not possible.

Edgar Reynaldo
Major Reynaldo
May 2007
avatar

almbfsek said:

I don't want to limit my FPS.

It is physically impossible to render more frames per second than the refresh rate of the monitor. So set your display timer to the refresh rate of the monitor and forget about it.

almbfsek said:

I want to make it run as fast as possible while limiting game logic to 25 lps for example?

Is your logic really so expensive that you can't run it at the same rate as your display?

almbfsek
Member #12,778
April 2011

Edgar I agree that "FPS dependent on Constant Game Speed" type of game loop is simpler and works most of the time but I want to try new methods and see the difference for my self.

Also there might be scenarios where the refresh rate of the monitor is more than the computer can handle. For example my monitor runs at 75 Hz but 75 fps might be too much for my old GPU. In this case if game logic and fps are not separated game logic will slow down too. It's something that I especially not want in a network game. It's easier to sync players if the game logic has its own rate.

Edgar Reynaldo
Major Reynaldo
May 2007
avatar

almbfsek
Member #12,778
April 2011

that's what I use now. you're right that "fps as fast as possible" is not really needed. for interpolation I use something like this:

#SelectExpand
1float getLogicInterpolation() { 2 return (al_get_timer_count(lpsTimer) % LPS + 1.0) / (float)LPS; 3}

and the main loop now looks like this

#SelectExpand
1while(isGameRunning) 2{ 3 //Event handling 4 while(!al_is_event_queue_empty(eventQueue)){ 5 ALLEGRO_EVENT e; 6 al_wait_for_event(eventQueue, &e); 7 8 switch(e.type) { 9 case ALLEGRO_EVENT_DISPLAY_CLOSE: 10 isGameRunning = false; 11 break; 12 13 case ALLEGRO_EVENT_TIMER: 14 if(e.timer.source == lpsTimer) 15 isLogicUpdateNeeded = true; 16 else if(e.timer.source == fpsTimer) 17 isDisplayUpdateNeeded = true; 18 break; 19 } 20 } 21 22 //Game logic 23 if(isLogicUpdateNeeded) { 24 isLogicUpdateNeeded = false; 25 //updateLogic(); 26 } 27 28 //Rendering 29 if(isDisplayUpdateNeeded) { 30 isDisplayUpdateNeeded = false; 31 //updateDisplay(); 32 } 33}

thanks everyone for your help

AMCerasoli
Member #11,955
May 2010
avatar

You are checking the timer field of the event without knowing the type of the event. ev.timer and ev.timer.source could have absolutely any value. Stop it.

Damn, is true I forgot to changed the last time!.

You should separate the drawing from the logic because:

  • Drawing more frames than the number of Hz that your monitor runs at is pointless.

  • Drawing takes 80 % more processing time than logic, basically you would be wasting GPU processing drawing the same frames, wasting battery if you're using a laptop, burning out your GPU, etc. this is why you Logic should always be faster than your frames per second

  • After 30 FPS, the human eye can't notice the difference between 30 FPS or 3000 FPS.

almbfsek said:

In this case if game logic and fps are not separated game logic will slow down too. It's something that I especially not want in a network game.

Well, I don't know what system are you using to create your network game, may be Peer-to-peer or client-server, but I think synchronizing the game completely is not a good option, I haven't created an only game, but I use to think about it, and in a Client-Server game I think the Server program doesn't need any logic per second, let alone FPS.

I think it should be something like: the client send info to the server the server check if that info is correct and then send it to the other clients. so the Server just need receive as many request it's possible, and you don't need to time out the LPS of the Server... Anyway, read this web page, is poor gold.

Peter Wang
Member #23
April 2000

You are checking the timer field of the event without knowing the type of the event. ev.timer and ev.timer.source could have absolutely any value. Stop it.

Actually in this case it's fine, as all event types have the three common fields: type, source, timestamp. It would be better style to use ev.any.source though.

After 30 FPS, the human eye can't notice the difference between 30 FPS or 3000 FPS.

This old myth must die.

Arthur Kalliokoski
Second in Command
February 2005
avatar

After 30 FPS, the human eye can't notice the difference between 30 FPS or 3000 FPS.

Actually I can quite easily see the successive images of a mountain sticking up out of the terrain as I spin a camera around in place, at least on a CRT. Even at 85 fps. But 30 fps is generally good enough.

I can also wave my finger around between my eye and the screen and see separate silhouettes, one for each refresh.

They all watch too much MSNBC... they get ideas.

Go to: