Allegro.cc - Online Community

Allegro.cc Forums » Programming Questions » Some assistance with event queues please?

This thread is locked; no one can reply to it. rss feed Print
 1   2 
Some assistance with event queues please?
grammerangel
Member #13,001
July 2011

I'm new to this event system, and nobody around me knows what I'm talking about. I'm about two weeks into Allegro, and this has been bugging me.

Here's a snatch of code I'm working on, everything is initialized;

#SelectExpand
1 2void input (ALLEGRO_DISPLAY *display, ALLEGRO_EVENT_QUEUE *eventqueue) 3{ 4 5 ALLEGRO_EVENT ev; 6 ALLEGRO_TIMEOUT timeout; 7 al_init_timeout(&timeout, .06); 8 bool getevent = al_peek_next_event (eventqueue, &ev); 9 10 while (1) 11 { 12 cout << "Enter '1' to exit or '2' to draw a line." << endl; 13 cin >> number; 14 if (number==1) 15 { 16 al_destroy_display(display); 17 18 } 19 if (number==2) 20 { 21 al_draw_line(250,0,250,500, al_map_rgb(255,0,0), 10); 22 al_flip_display(); 23 24 } 25 26 27 if (getevent==true && ev.type == ALLEGRO_EVENT_DISPLAY_CLOSE) 28 { 29 cout << "Event" << endl; 30 al_destroy_display(display); 31 break; 32 33 } 34 35 } 36}

Suppose I press the X button before I put in a number. I'm trying to get the third if, the getevent line, to understand that there is an event in the queue and act accordingly. This code serves no actual purpose, but I want to understand exactly how queues store data. I thought the queue would store the input, and then getevent would recognize that there's something in it and save the event in ev, which the next piece would pick up. But in practice, the line doesn't return true.

If somebody could explain this, rather than correct this, I'd appreciate it.

Trent Gamblin
Member #261
April 2000
avatar

You should have a single "message pump" loop something like this:

#SelectExpand
1ALLEGRO_EVENT event; 2redraw = true; 3while (true) { 4 while (!al_event_queue_is_empty(queue)) { 5 al_get_next_event(&event); 6 if (event.type == ALLEGRO_EVENT_TIMER) { 7 input(&event); 8 logic(); 9 redraw = true; 10 } 11 } 12 if (redraw) { 13 draw(); 14 } 15 al_rest(0.001); // rest 1 millisecond to avoid frying the cpu 16}

grammerangel
Member #13,001
July 2011

Alright, I was wondering if you could explain some things:

#SelectExpand
1ALLEGRO_EVENT event; //Creates event 2redraw = true; //Unsure of what the purpose of redraw is. I saw it in the 3 //tutorial as well 4 5while (true) { 6 while (!al_event_queue_is_empty(queue)) { //While the event queue is 7 //not empty... 8 9 al_get_next_event(&event); //So what does this line do differently? 10 //It gets the next event, but how is that 11 //different from what I did? 12 13 if (event.type == ALLEGRO_EVENT_TIMER) {//If the event is AET... 14 input(&event); //It goes to input and passes the event's location... 15 logic(); //What is this for? 16 redraw = true; //and again redraw. But redraw was always true. 17 } 18 } 19 if (redraw) { 20 draw(); //Then what's this? If this is where the line is drawn, 21 //what does logic or input do? 22 } 23 al_rest(0.001); // rest 1 millisecond to avoid frying the cpu 24}

Trent Gamblin
Member #261
April 2000
avatar

#SelectExpand
1ALLEGRO_EVENT event; //Creates event 2redraw = true; //Unsure of what the purpose of redraw is. I saw it in the 3 //tutorial as well 4/* when redraw is true (which happens to be any time any logic is done), the scene is redrawn */ 5 6while (true) { 7 while (!al_event_queue_is_empty(queue)) { //While the event queue is 8 //empty... 9 10/* NOT empty */ 11 12 al_get_next_event(&event); //So what does this line do differently? 13 //It gets the next event, but how is that 14 //different from what I did? 15 16/* You peeked at the event which does not remove it from the queue. Peek event should rarely be used. The major difference between my code and yours is that events are popped off the queue in only one place, which will make your code much more maintainable in the long run */ 17 18 if (event.type == ALLEGRO_EVENT_TIMER) {//If the event is AET... 19 input(&event); //It goes to input and passes the event's location... 20 21/* input() should handle your events. For example it could check if the escape key is pressed and quit the game if so. In a complicated game, you could make input(), logic(), and draw() all function pointers (or put them in a class) and then you could have different components that all use the same loop. For example, a menu loop and a game loop */ 22 23 logic(); //What is this for? 24 25/* This updates all of your game objects. It would usually check what happened in input() and move the player etc according to input. You could get away with combining logic and input in many games. You would also update anything that moves or reacts to /time/ in logic(). No drawing should be done in logic(). */ 26 27 redraw = true; //and again redraw. But redraw was always true. 28 29/* redraw should be set to false just before or after draw() in the if block. That was a mistake on my part */ 30 31 } 32 } 33 if (redraw) { 34 draw(); //Then what's this? If this is where the line is drawn, 35 //what does logic or input do? 36 37/* This does nothing but draw the current state of the game. You should not update things relevant to time or input in here */ 38 39 } 40 al_rest(0.001); // rest 1 millisecond to avoid frying the cpu 41}

grammerangel
Member #13,001
July 2011

Also, I'm confused how al_get_next_event can work with only one argument. I need to give the queue, right?

Trent Gamblin
Member #261
April 2000
avatar

grammerangel
Member #13,001
July 2011

Yes, that's where I got this.

bool al_get_next_event(ALLEGRO_EVENT_QUEUE *queue, ALLEGRO_EVENT *ret_event)

Take the next event out of the event queue specified, and copy the contents into ret_event, returning
true. The original event will be removed from the queue. If the event queue is empty, return false and
the contents of ret_event are unspecified.

It still looks like it needs two parameters. Unless I'm misunderstanding something.

Matthew Leverton
Supreme Loser
January 1999
avatar

Yes, it needs both arguments.

grammerangel
Member #13,001
July 2011

Okay, that makes sense.

Let me see if I get the gist here; the event queue almost always has something in it; a timer. Every tick kicks the previous event out of the queue, so that's why the program wouldn't just "remember" the close button. But then why do we keep checking if the event is ALLEGRO_EVENT_TIMER and passing the location based off of that? The timer is always ticking, so all the program would do is act off of the autonomous timer and wouldn't react to user input like DISPLAY_CLOSE, right? I know that's not how it works, but then where am I misunderstanding this?

Matthew Leverton
Supreme Loser
January 1999
avatar

Queues can hold an infinite number of events. Nothing is removed from the queue until you take something out of it. Functions like al_get_next_event() and al_wait_for_event() removes the oldest element off the queue. You process that event. Then you wait for the next event, etc.

grammerangel
Member #13,001
July 2011

...Dammit, now I'm confused again.

Let me run this one by you; The al_get_next_event sees the timer's tick, then gets rid of it and copies it into event. Have I got it so far?

But the rest of the code only works if the event type is ALLEGRO_EVENT_TIMER. The event holder can only hold one event, right? So what is the point of repeatedly going through input if all you're passing is the location of event, which is holding a timer tick? Or is there an difference between an event queue and ALLEGRO_EVENT event?

Matthew Leverton
Supreme Loser
January 1999
avatar

Normally the queue would hold a variety of event types: a timer tick, a mouse movement, a key press, a close window, etc. You can register one or many different sources to the same queue.

So you copy (move) the event out of the queue, check what type it is, and do something. Repeat.

grammerangel
Member #13,001
July 2011

Is it just implied that there's more than one source? Here's what I'm seeing:

if (event.type == ALLEGRO_EVENT_TIMER) {
input(&event);

The first line checks if the event holder is a timer tick, and the second line uses the event, that tick in input. All that is being used in this example is the ticks. Nothing but. There is no check if the event is a mouse input, or a display close. Why would the input be concerned with the timer? What purpose is there to carrying the timer tick to input? Or am I supposed to just use this as a template and add my own checks for events? Something like:

#SelectExpand
1al_get_next_event(&event); 2 3//This gets the tick from the timer. If the user gave an event beforehand, //then that event is stored just before the tick. 4 5if (event.type == ALLEGRO_EVENT_TIMER) 6 7//This checks if the event was a tick. Which means it's going to be checking //and triggering almost constantly. 8 9{ 10al_get_next_event(&event); 11 12//this gets the event next in the queue, which is the user's event because the //newest event was copied then destroyed by the last al_get_next_event. 13 14input(&event); 15 16//And this handles the user's event that was obtained in the last function. 17}

Does this even make sense? Are my thoughts coming through at all?

Matthew Leverton
Supreme Loser
January 1999
avatar

I don't understand Trent's example either. Perhaps he assumes you aren't going to use events to process input, and instead are just going to poll keyboard/mouse states and look at the event's timestamp.

For games it's easiest to process everything all at once per timer tick. While using events for input guarantees you won't lose any data, it means you have to somehow work that into your timer logic.

So some people take the lazy approach because it usually works without any problems: just check for timer ticks, and then use al_get_keyboard_state() to check if a certain key is pressed at that moment in time.

grammerangel
Member #13,001
July 2011

...and then react to it, right? The poling method just checks over and over if a key is pressed, then performs some action based on the poll results?

That kind of makes sense... I'm away from my Visual Studio at the moment, let me get back to you tomorrow.

Matthew Leverton
Supreme Loser
January 1999
avatar

If you use input polling, you would still wait for a timer tick. Once you get a tick, you poll the keyboard state once and then check to see if whatever keys you are interested in were pressed.

The whole point of the timer tick is to make sure your game runs at a constant speed. If you know you are always checking 60 times per second, then you can simplify the rest of the logic. e.g., If the LEFT key is pressed during that tick, you might move your character over by exactly four pixels.

Thomas Fjellstrom
Member #476
June 2000
avatar

Maybe it'll help if I explain the event loop from the wiki:

#SelectExpand
1while(1) // loop till we 'break' out. 2 { 3 ALLEGRO_EVENT ev; 4 al_wait_for_event(event_queue, &ev); // wait for an event to come in. Forever if need be. 5 6 if(ev.type == ALLEGRO_EVENT_TIMER) { // We got a timer event, do some logic! 7 if(bouncer_x < 0 || bouncer_x > SCREEN_W - BOUNCER_SIZE) { 8 bouncer_dx = -bouncer_dx; 9 } 10 11 if(bouncer_y < 0 || bouncer_y > SCREEN_H - BOUNCER_SIZE) { 12 bouncer_dy = -bouncer_dy; 13 } 14 15 bouncer_x += bouncer_dx; 16 bouncer_y += bouncer_dy; 17 18 redraw = true; // things changed, so tell the drawing code it should run 19 } 20 else if(ev.type == ALLEGRO_EVENT_DISPLAY_CLOSE) { // user clicked the X button on the window, break out of event loop and exit. 21 break; 22 } 23 24 25 // if something changed and we want to redraw, do that, but only once the event queue has been completely drained. 26 // In case things bogged down for a bit and we have multiple timer events in the queue, 27 // this makes sure we only draw once for ALL timer events in the queue, which will help us catch up. 28 // Also, it makes sure all input in the queue is taken care of before we draw. 29 if(redraw && al_is_event_queue_empty(event_queue)) { 30 redraw = false; // obviously we don't want to draw right away again, so clear redraw flag. 31 32 // drawing! 33 al_clear_to_color(al_map_rgb(0,0,0)); 34 35 al_draw_bitmap(bouncer, bouncer_x, bouncer_y, 0); 36 37 al_flip_display(); // flip backbuffer to screen. 38 } 39 }

Hope this helps.

--
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

AMCerasoli
Member #11,955
May 2010
avatar

Before I take the time to write something, have you read the tutorial on the wiki? you know those which says events and input?

grammerangel
Member #13,001
July 2011

I followed the tutorials, and I get the individual examples, but the tutorials don't cover anything other than that specific example.

Anyway, I tried building a routine that checks whether there's input on the display, and passes the event to another function that deals with the event. Suppose I click on the X button;

#SelectExpand
1 2while (1) 3 { 4 if (!al_is_event_queue_empty(eventqueue)) //If there is something in the event queue... 5 { 6 7 8 al_get_next_event(eventqueue, &ev); //Get the event 9 10 if (ev.type==ALLEGRO_EVENT_TIMER) //This catches the tick right after the X button... 11 { 12 13 al_get_next_event (eventqueue, &ev); //So this one should get the X button, the event just before the tick, right? 14 15 input (display, ev); //And then you send the event that should be the display close. 16 17 18 } 19 } 20 21 }

Of course, this doesn't work. The input doesn't doesn't recognize that the event coming in is the display close.

I know that there are easier ways to pull this off, but I want to makes sure I can use this kind of setup to receive mouse or keyboard input. I still don't think I'm understanding the queue thing, or I'm not thinking properly. Sorry for sticking with this problem, but I'm going to have to learn this if I'm going to get close to designing my own game.

Edgar Reynaldo
Major Reynaldo
May 2007
avatar

I think you're thinking about this the wrong way. Just because there is a timer event in the queue doesn't mean there is another event in the queue, so you may be waiting a while until that event comes.

The event queue will give you the oldest event first, and the rest in ascending order of time received. You could get a key / mouse click event at anytime, and it has nothing to do with a timer event or with a display close event. Don't expect events in any certain order, or you will be disappointed. They will be in the order they happened, but that is all you can expect.

1) When you get a timer event, you should probably set a redraw flag, and you need to update any objects that you have by the amount of time passed.

2) When you get a display close event, then break out of your program loop.

3) When you get other events, send them to each of your objects so they can deal with them as that is the input in your program.

So something like this might work better for you :

#SelectExpand
1 while (!quit) { 2 if (redraw) { 3 redraw = false; 4 al_clear_to_color(al_map_rgb(0,0,0)); 5 for (int i = 0 ; i < (int)objects.size() ; ++i) { 6 objects[i]->Display; 7 } 8 al_flip_display(); 9 } 10 do { 11 ALLEGRO_EVENT ev; 12 al_wait_for_event(event_queue , &ev); 13 if (ev.type == ALLEGRO_EVENT_TIMER) { 14 redraw = true; 15 for (int i = 0 ; i < (int)objects.size() ; ++i) { 16 objects[i]->Update(1.0/FramesPerSecond); 17 } 18 } 19 else if (ev.type == ALLEGRO_EVENT_DISPLAY_CLOSE) { 20 quit = true; 21 break; 22 } 23 else { 24 for (int i = 0 ; i < (int)objects.size() ; ++i) { 25 objects[i]->ProcessEvent(&ev); 26 } 27 } 28 } while (!al_is_event_queue_empty()); 29 }

This kind of event loop only draws when the timer ticks, will wait for the next event before continuing on, and will process all events that are in the queue before redrawing again.

Thomas Fjellstrom
Member #476
June 2000
avatar

So something like this might work better for you :

Which seems to be functionally equivalent to the one in the tutorial.

--
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

Edgar Reynaldo
Major Reynaldo
May 2007
avatar

grammerangel
Member #13,001
July 2011

Okay, I think I get it now... kind of... I see the problem with the code. I have other problems with this, but this is all I have on the subject of event queues.

Thank you so much, everyone. I appreciate it.

Edgar Reynaldo
Major Reynaldo
May 2007
avatar

Actually, the loop I posted should be using al_wait_for_event, not al_get_next_event. I edited it to fix that, and made the drawing code clearer as well.

It's not quite the same as the wiki because of the explicit calls to Update, ProcessEvent, and Display, but the principle is the same.

Thomas Fjellstrom
Member #476
June 2000
avatar

It's not quite the same as the wiki because of the explicit calls to Update, ProcessEvent, and Display, but the principle is the same.

The only difference is that the wiki example is so simple it doesn't use separate functions to handle updates, events, or displays. Otherwise its functionally identical.

--
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

 1   2 


Go to: