Allegro.cc - Online Community

Allegro.cc Forums » Programming Questions » Multiple event queues

This thread is locked; no one can reply to it. rss feed Print
Multiple event queues
RickyLee
Member #11,573
December 2009

I read in the wiki that it's OK to have multiple event queues but is there any down side to having a bunch of these? I'm looking at making a Timer class to abstract timers, and to make them 100% independent from each other I was thinking of giving them their own event queue. Is this going to cause any issues if I have 20 event queues laying around?

Thomas Fjellstrom
Member #476
June 2000
avatar

You have to make sure to check all eventqueues in your event loop, so instead of just calling al_wait_for_event once, you'll have to revert to polling your event queues, which kind of defeats the purpose of an event system.

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

RickyLee - I recently did an abstraction of timers, here it is if you would like to see it :

Timer.zip

EagleTimer.hpp#SelectExpand
1 2 3 4#ifndef EagleTimer_HPP 5#define EagleTimer_HPP 6 7 8class EagleTimer : public EagleEventSource { 9private : 10 int id;// unique id 11protected : 12 double spt;// seconds per tick 13 unsigned long long previous_ticks; 14 unsigned long long current_ticks; 15 16 static int NextId() { 17 static int i = 0; 18 return i++; 19 } 20 21 virtual void RefreshTimer()=0; 22 23public : 24 25 EagleTimer() : id(NextId()) , spt(0.0) , previous_ticks(0UL) , current_ticks(0UL) {} 26 virtual ~EagleTimer() {} 27 28 virtual bool Create(double seconds_per_tick)=0; 29 virtual void Destroy()=0; 30 virtual void Start()=0; 31 virtual void Stop()=0; 32 virtual void WaitForTick()=0; 33 34 int ID() {return id;} 35 double SPT() {return spt;} 36 unsigned long long Count() {return current_ticks;} 37 virtual void* Source()=0; 38 39 // time passed - since when? last check? since last take time? 40 41 // time passed since the last TakeAllTime was called 42 unsigned long long TicksPassed(); 43 double TimePassed(); 44 double TakeAllTime(); 45}; 46 47 48#endif // EagleTimer_HPP

EagleTimer.cpp#SelectExpand
1 2 3#include "Eagle5/backends/Allegro5/EagleTimer.hpp" 4 5 6 7unsigned long long EagleTimer::TicksPassed() { 8 RefreshTimer(); 9 return current_ticks - previous_ticks; 10} 11 12 13 14double EagleTimer::TimePassed() { 15 RefreshTimer(); 16 return (double)(current_ticks - previous_ticks)*spt; 17} 18 19 20 21double EagleTimer::TakeAllTime() { 22 RefreshTimer(); 23 double t = (double)(current_ticks - previous_ticks)*spt; 24 previous_ticks = current_ticks; 25 return t; 26}

Allegro5Timer.hpp#SelectExpand
1 2 3 4#include "Eagle5/Eagle5.hpp" 5 6#include "allegro5/allegro.h" 7//#include "allegro5/allegro 8 9 10 11class Allegro5Timer : public EagleTimer { 12private : 13 14 ALLEGRO_TIMER* timer; 15 ALLEGRO_EVENT_QUEUE* queue; 16 17 void RefreshTimer(); 18 19public : 20 Allegro5Timer() : EagleTimer() , timer(0) , queue(0) {} 21 22 23 virtual bool Create(double seconds_per_tick); 24 virtual void Destroy(); 25 virtual void Start(); 26 virtual void Stop(); 27 28 virtual long long unsigned int Count(); 29 virtual void* Source(); 30 31 32 ALLEGRO_TIMER* AllegroTimer() {return timer;} 33};

Allegro5Timer.cpp#SelectExpand
1 2 3#include "Eagle5/backends/Allegro5/Allegro5Timer.hpp" 4 5 6 7void Allegro5Timer::RefreshTimer() { 8 if (queue && timer) { 9 ALLEGRO_EVENT ev; 10 while (al_get_next_event(queue , &ev) { 11 current_ticks++; 12 } 13 } 14} 15 16 17 18bool Allegro5Timer::Create(double seconds_per_tick) { 19 Destroy(); 20 timer = al_create_timer(seconds_per_tick); 21 queue = al_create_event_queue(); 22 if (queue && timer) { 23 spt = seconds_per_tick; 24 previous_ticks = current_ticks = al_get_timer_count(timer); 25 al_register_event_source(queue , al_get_timer_event_source(timer)); 26 return true; 27 } 28 if (!timer) { 29 OutputLog() << "Could not create an Allegro 5 Timer - Couldn't create an ALLEGRO_TIMER." << endl; 30 } 31 if (!queue) { 32 OutputLog() << "Could not create an Allegro 5 Timer - Couldn't create an ALLEGRO_EVENT_QUEUE." << endl; 33 } 34 // The queue or the timer failed to be created 35 Destroy(); 36 return false; 37} 38 39 40 41void Allegro5Timer::Destroy() { 42 if (queue) { 43 al_destroy_event_queue(queue); 44 queue = 0; 45 } 46 if (timer) { 47 al_destroy_timer(timer); 48 timer = 0; 49 } 50 spt = 0.0; 51} 52 53 54 55void Allegro5Timer::Start() { 56 if (timer) {al_start_timer(timer);} 57 else { 58 OutputLog() << "Tried to start an allegro timer that hasn't been successfully created yet." << endl; 59 } 60} 61 62 63 64void Allegro5Timer::Stop() { 65 if (timer) {al_stop_timer(timer);} 66 else { 67 OutputLog() << "Tried to stop an allegro timer that hasn't been successfully created yet." << endl; 68 } 69} 70 71 72 73void Allegro5Timer::WaitForTick() { 74 if (queue && timer) { 75 RefreshTimer(); 76 ALLEGRO_EVENT e; 77 al_wait_for_event(queue , &e); 78 ++current_ticks; 79 } 80 return; 81} 82 83 84 85void* Allegro5Timer::Source() { 86 return timer; 87}

You can have as many timers as you like because they each have their own event queue, so yeah to answer your question I think it is fine.

BUT I think you need to rethink how many timers you actually need. 20 is a bit many. There should be maybe one or two, at most 3? I would say.

weapon_S
Member #7,859
October 2006
avatar

If you're doing a game, you'll probably want to tie most timing to the game-logic anyway. I.e. if your game has to proceed 0.1 seconds, 2 ticks, or whatever unit you use, it can tell to all objects in the game how much to advance their internal time.
Multiple timers is more for timing real world stuff. (E.g. an internet connection time-out.)

RickyLee
Member #11,573
December 2009

Thanks guys. I got it working and seems to be fine with multiple event queue's. Probably just comes down to how you like to structure your code. I really try to separate out different systems and make them self contained.

If anyone is interested here is what I have. A long time ago, on this site actually, someone posted event code for C++ that acts more like it does in .NET. Easily allowing callbacks to C++ object methods. I make heavy usage of this in most all my code to really keep systems separate and fire events instead of having objects be coupled together.

Event.h (this can be confusing if you aren't good with templates so it can be skipped. usage is simple though.)

#SelectExpand
1#pragma once 2#include <list> 3 4using namespace std; 5 6 7class TFunctor0 8{ 9public: 10 virtual void Call()=0; 11}; 12 13template <class TClass> 14class TSpecificFunctor0 : public TFunctor0 15{ 16private: 17 void (TClass::*fpt)(); 18 TClass* pt2Object; 19public: 20 TSpecificFunctor0(TClass* _pt2Object, void(TClass::*_fpt)()) 21 { 22 pt2Object = _pt2Object; 23 fpt=_fpt; 24 } 25 26 virtual void Call() 27 { (*pt2Object.*fpt)(); } 28}; 29 30class Event0 31{ 32public: 33 list<TFunctor0*> mCaller; 34 35 template<class Target> 36 void Bind(Target* t, void (Target::*fnPtr)()) 37 { 38 mCaller.push_back(new TSpecificFunctor0<Target>(t,fnPtr)); 39 } 40 41 void Clear() 42 { mCaller.clear(); } 43 44 void Raise() 45 { 46 list<TFunctor0*>::reverse_iterator iter; 47 48 for (iter = mCaller.rbegin(); iter!= mCaller.rend(); iter++) 49 { 50 (*iter)->Call(); 51 } 52 } 53}; 54 55//=============================================================================== 56 57template<class P> 58class TFunctor1 59{ 60public: 61 virtual void Call(P var1)=0; 62}; 63 64template <class TClass, class param1> 65class TSpecificFunctor1 : public TFunctor1<param1> 66{ 67private: 68 void (TClass::*fpt)(param1); 69 TClass* pt2Object; 70public: 71 TSpecificFunctor1(TClass* _pt2Object, void(TClass::*_fpt)(param1)) 72 { pt2Object = _pt2Object; fpt=_fpt; } 73 74 virtual void Call(param1 var1) 75 { (*pt2Object.*fpt)(var1); } 76}; 77 78template<class T1> 79class Event1 80{ 81public: 82 list<TFunctor1<T1>* > mCaller; 83 84 template<class Target> 85 void Bind(Target* t, void (Target::*fnPtr)(T1)) 86 { mCaller.push_back(new TSpecificFunctor1<Target, T1>(t,fnPtr)); } 87 88 void Raise(T1 V1) 89 { 90 list<TFunctor1<T1>*>::reverse_iterator iter; 91 92 93 for (iter = mCaller.rbegin(); iter!= mCaller.rend(); iter++) 94 { 95 (*iter)->Call(V1); 96 } 97 } 98 99 void Clear() 100 { 101 mCaller.clear(); 102 } 103}; 104 105//=============================================================================== 106 107template<class P, class Q> 108class TFunctor2 109{ 110public: 111 virtual void Call(P var1, Q var2)=0; 112}; 113 114template <class TClass, class param1, class param2> 115class TSpecificFunctor2 : public TFunctor2<param1, param2> 116{ 117private: 118 void (TClass::*fpt)(param1, param2); 119 TClass* pt2Object; 120public: 121 TSpecificFunctor2(TClass* _pt2Object, void(TClass::*_fpt)(param1, param2)) 122 { pt2Object = _pt2Object; fpt=_fpt; } 123 124 virtual void Call(param1 var1, param2 var2) 125 { (*pt2Object.*fpt)(var1, var2); } 126}; 127 128template<class T1, class T2> 129class Event2 130{ 131public: 132 list<TFunctor2<T1, T2>* > mCaller; 133 134 template<class Target> 135 Event2(Target* t, void (Target::*fnPtr)(T1, T2)) 136 { mCaller.push_back(new TSpecificFunctor2<Target, T1, T2>(t,fnPtr)); } 137 138 Event2(){} 139 140 template<class Target> 141 void Bind(Target* t, void (Target::*fnPtr)(T1, T2)) 142 { mCaller.push_back(new TSpecificFunctor2<Target, T1, T2>(t,fnPtr)); } 143 144 void Raise(T1 V1, T2 V2) 145 { 146 list<TFunctor2<T1, T2>*>::reverse_iterator iter; 147 148 149 for (iter = mCaller.rbegin(); iter!= mCaller.rend(); iter++) 150 { 151 (*iter)->Call(V1, V2); 152 } 153 } 154 155 void Clear() 156 { 157 mCaller.clear(); 158 } 159}; 160 161 162//=============================================================================== 163 164template<class P, class Q, class Y> 165class TFunctor3 166{ 167public: 168 virtual void Call(P var1, Q var2, Y var3)=0; 169}; 170 171template <class TClass, class param1, class param2, class param3> 172class TSpecificFunctor3 : public TFunctor3<param1, param2, param3> 173{ 174private: 175 void (TClass::*fpt)(param1, param2, param3); 176 TClass* pt2Object; 177public: 178 TSpecificFunctor3(TClass* _pt2Object, void(TClass::*_fpt)(param1, param2, param3)) 179 { pt2Object = _pt2Object; fpt=_fpt; } 180 181 virtual void Call(param1 var1, param2 var2, param3 var3) 182 { (*pt2Object.*fpt)(var1, var2, var3); } 183}; 184 185template<class T1, class T2, class T3> 186class Event3 187{ 188public: 189 list<TFunctor3<T1, T2, T3>* > mCaller; 190 191 template<class Target> 192 void Bind(Target* t, void (Target::*fnPtr)(T1, T2, T3)) 193 { mCaller.push_back(new TSpecificFunctor3<Target, T1, T2, T3>(t,fnPtr)); } 194 195 void Raise(T1 V1, T2 V2, T3 V3) 196 { 197 list<TFunctor3<T1, T2, T3>*>::reverse_iterator iter; 198 199 200 for (iter = mCaller.rbegin(); iter!= mCaller.rend(); iter++) 201 { 202 (*iter)->Call(V1, V2, V3); 203 } 204 } 205 206 void Clear() 207 { 208 mCaller.clear(); 209 } 210};

My timer class that uses an Event<> to fire when it's interval is up:

#SelectExpand
1#pragma once 2#include <allegro5/allegro.h> 3#include "Event.h" 4 5class Timer 6{ 7private: 8 ALLEGRO_TIMER *_timer; 9 ALLEGRO_EVENT_QUEUE *_eventQ; 10public: 11 Timer(float interval) 12 { 13 _eventQ = al_create_event_queue(); 14 15 _timer = al_create_timer(ALLEGRO_MSECS_TO_SECS(interval)); 16 al_register_event_source(_eventQ, al_get_timer_event_source(_timer)); 17 } 18 19 ~Timer() 20 { 21 if(_timer) 22 al_destroy_timer(_timer); 23 24 if(_eventQ) 25 al_destroy_event_queue(_eventQ); 26 } 27 28 void Start() 29 { 30 al_start_timer(_timer); 31 } 32 33 void Stop() 34 { 35 al_stop_timer(_timer); 36 } 37 38 void Update() 39 { 40 ALLEGRO_TIMEOUT timeout; 41 ALLEGRO_EVENT e; 42 43 al_init_timeout(&timeout, 0.01); 44 if(al_wait_for_event_until(_eventQ, &e, &timeout)) 45 { 46 switch(e.type) 47 { 48 case ALLEGRO_EVENT_TIMER: 49 if(e.timer.source == _timer) 50 OnTick.Raise(this); 51 } 52 } 53 } 54 55 Event1<Timer*> OnTick; 56};

Peter Wang
Member #23
April 2000

Not sure which is more appropriate: :o

Or: ::)

Abstractions on top of abstractions on top of abstractions on top of abstractions. Abstractions all the way down?

RickyLee
Member #11,573
December 2009

I know, but I find the usage to be so much better with this style of event handling. The event queue seems like a neat idea at first, but having the entire game be tied to 1 event queue just doesn't seem like it promotes decoupling of objects. If I have a player object that collects input I'll have an event queue inside that object as well so it's not dependent on my main games event queue which is just doing the draw/update work.

I hate querying for events and then branching off logic in the main loop. I'd much rather register my events on initialization and then just have them get fired when the event happens, but hey that's just me. I'm a .NET guy.

Thomas Fjellstrom
Member #476
June 2000
avatar

Or you can have your event loop pass the events into your Game class, which then further passes events down to individual objects in the game.

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

Elias
Member #358
May 2000

The way you do it, you have to keep a list of all objects, then call a quite questionable Update method on all of them. (Waiting up to 0.01 seconds inside.) It would indeed be much cleaner if you do like Tomasu says - wait until an event occurs then pass it along to the objects (with the same Update method, but this time it doesn't wait some arbitrary time but gets passed the object to process).

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

RickyLee
Member #11,573
December 2009

That sounds pretty coupled together though. All my objects relying on Allegro events being passed to them? In the event style programming I like, the classes aren't tied together with anything, which makes the usage of them cross-library. All I have to do is change the insides to the library specific stuff and all the glue code doesn't require modification. For example that same Timer interface I've written versions for in a couple other libraries. The exposed interface stays the same, just the insides change. I'm finding that to be very flexible.

I'd prefer not to wait 0.01 seconds but it seemed the event queue function blocks waiting for an event to happen so that was a way to make it non blocking. The examples I've seen do that by making a timer that runs 60 times a second. I would prefer if it could just check if there are any events instead of waiting for an event. Is there a way to do that instead?

Thomas Fjellstrom
Member #476
June 2000
avatar

RickyLee said:

That sounds pretty coupled together though. All my objects relying on Allegro events being passed to them?

You don't have to pass an ALLEGRO_EVENT to your objects. You could, or you could send your own event objects, or even call specific methods per event type.

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

RickyLee
Member #11,573
December 2009

I'm trying to picture how I structure my games and how this event queue would work together. With a main Game class and a StateManager class managing game state objects that switch between each other.

To have 1 event queue it would have to be in the Game class, but, for example, my MainMenu state class could have timers and such in it doing various things. The Game class itself would know nothing about the MainMenu state class (as it shouldn't). It would just know enough to create the class and push it onto the stack of states and switch to it. It doesn't need to know anything about it's insides, so in order to get these game states to know about this event queue I'd have to pass it along to them all so they could register events against it, creating a dependency that otherwise would not need to exist in a game library I use for different engines. Global var is out of the question. I never have those.

I'll have to think about this some, but the event queue system seems like it pigeon holes you some into a certain structure at first glance.

beoran
Member #12,636
March 2011

The thing is, most other game libraries I know ALSO use an event queue. SDL and SFML, for example, seem to have only one event queue, so a multiple queue design will not work for those libraries.

The way to deal with this then, if you don't want to be bound too tightly with Allegro is to have an adapter class in the middle that will poll the event queue and emit Allegro independent method calls to your game objects, as Thomas suggested.

weapon_S
Member #7,859
October 2006
avatar

RickyLee said:

I would prefer if it could just check if there are any events instead of waiting for an event. Is there a way to do that instead?

al_is_event_queue_empty
Everything has already been said. You'd probably be happiest implementing the event listener design pattern, with a simple translation to your generalized events from Allegro events. Your game objects don't need to know about the queue, because all events arrive in order[1] anyway.
Separate timers really aren't that sweet. Maybe you could go with two separate kind of objects (templates). One that listens to an external timer, and one that has a build-in timer. One could easily be derived from the other.
>_> TBH I'm a noob. I only know this stuff in theory.

References

  1. You might want to emit some events circumventing the queue in reaction to events, and those are technically speaking not in order.
RickyLee
Member #11,573
December 2009

Thanks for that function, that should do the trick for what I'm looking for with this stuff.

Elias said:

The way you do it, you have to keep a list of all objects, then call a quite questionable Update method on all of them.

I'm not all that worried about storing objects. I store tons of different objects in a game, this is no different. I changed the update method to do al_is_event_queue_empty() instead of waiting any timeout.

I'm still playing with the ideas of sharing the 1 event queue. Just not sure how I'd implement my event handling code with 1 event queue. I still want my Timer class in some fashion for a nice abstraction around what a timer is. I would somehow need to know what Timer object was fired just by examining the event structure.

Peter Wang
Member #23
April 2000

If you are making your own timers which you just poll, then you really don't need Allegro timers and event queues at all. You just need to know the current time when Update is called (e.g. using al_get_time), the time of the last tick, then count the number of ticks between one and the other.

Or you can use ALLEGRO_TIMERs without event queues: al_get_timer_count

Go to: