|
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
|
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. -- |
Edgar Reynaldo
Major Reynaldo
May 2007
|
RickyLee - I recently did an abstraction of timers, here it is if you would like to see it : 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
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}
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};
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. My Website! | EAGLE GUI Library Demos | My Deviant Art Gallery | Spiraloid Preview | A4 FontMaker | Skyline! (Missile Defense) Eagle and Allegro 5 binaries | Older Allegro 4 and 5 binaries | Allegro 5 compile guide |
weapon_S
Member #7,859
October 2006
|
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. |
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.) 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: 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: 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
|
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. -- |
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). -- |
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
|
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. -- |
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
|
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 References
|
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
|
|