Allegro.cc - Online Community

Allegro.cc Forums » Game Design & Concepts » Listeners, threads, synchronization

Credits go to Arthur Kalliokoski, jmasterx, and l j for helping out!
This thread is locked; no one can reply to it. rss feed Print
Listeners, threads, synchronization
Edgar Reynaldo
Major Reynaldo
May 2007
avatar

Hey fellas. So here's the deal. I've got event source and event listener classes. I'm trying to decide if I need a separate thread for each event source, like keyboard, mouse, and so on. Right now I only have a separate thread running the timers, and I'm trying to decide if I should have a separate thread for each input source.

The real problem comes when an event is emitted. If the input is running in a separate thread, and that thread calls the OnEvent method of the listener then it means the users of my listener class will need to synchronize data with mutexes, whereas if it runs on the main thread it would not.

My second problem comes with the use of ALLEGRO_EVENT_QUEUE's. I don't know how to properly emit events when they occur. If I emit events as soon as they are asked for, that works, but defeats the purpose of a listener. If I emit events as soon as they occur, then that means I need a separate thread so it doesn't block processing waiting for events.

So, really, I'm just looking for some advice on how to cleanly design my event system.

Should I stick with listeners and use separate threads? Or accept that some events won't be processed until the user manually checks the event queue?

Arthur Kalliokoski
Second in Command
February 2005
avatar

My question is, why would you want separate threads? Earlier today, I had a subroutine in the timer handler of a loop incrementing an array of 64k floats, and the timer was going 10000 times a second, and it was only using 70% cpu. No extra threads involved. Compared to that, handling a keyboard or mouse event is child's play.

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

Edgar Reynaldo
Major Reynaldo
May 2007
avatar

A separate thread is so that the main thread doesn't get blocked waiting for input.

If I don't use threads, and just register everything as an ALLEGRO_EVENT_SOURCE to my event handler, then listeners don't get called until someone asks the queue for data.

Arthur Kalliokoski
Second in Command
February 2005
avatar

A separate thread is so that the main thread doesn't get blocked waiting for input.

That's the whole point of using events, to allow it to continue. Then you pick off the events off the queue as you're ready to handle them. If they're really time-consuming, set a flag to be checked occasionally. Or am I misunderstanding something here?

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

Edgar Reynaldo
Major Reynaldo
May 2007
avatar

I am not concerned about cpu use here, but rather the order and timing that events get processed in.

Maybe I'm just getting hung up on the idea of a listener. I would like to allow people to register listeners to my event sources, and that is where the whole source of the problem is coming from.

l j
Member #10,584
January 2009
avatar

I don't see why you can't have listeners without separate threads?

Edgar Reynaldo
Major Reynaldo
May 2007
avatar

If there are no separate threads, then any input coming into the event handler queue has to be polled, and cannot be emitted to listeners until it is actually received, as opposed to emitting an event immediately when it occurs, which requires threads. At least I think that is what my problem is.

jmasterx
Member #11,410
October 2009

I'm not sure why timing is an issue at all, it would be nice to know more about why you need it. Even if you have an event that needs to be dispatched immediately, the only reason this would be useful is if you had many threads for many events.

For example. I would have my main event dispatch thread which might give out a mouse click. then I do:

for each listener
{
    listener->event(e);
}

However, unless each listener is on a separate thread, this is completely useless. I can think of very few practical examples where this is useful.

Multithreaded listeners are much less practical than message systems which are multithreaded because listeners, by nature, need to wait until the current listener has returned to give to the next. Thus you are stuck with the polling method:

for each listener
{
    listener->queueEvent(e);
}

Which would be non-blocking on the worker threads, and the worker threads poll and execute tasks.

Since I'm inferring this has to do with GUI in some way, the simple solution I would say is, if you have a task that will make the UI unresponsive, fork it to another thread.

//main thread
for each listener
{
    listener->timerTicked(e);
}

//something really long
WikipediaLoader::timerTicked(Event e)
{
    createAndJoinNewWorkerThread(&WikipediaLoader::loadAllOfWiki,this);
}

This would avoid all the mutex stuff since when wikipedia is loaded, it could simply call WikipediaLoader::finishedLoading(Wikipedia* wiki);

You can always use Boost or C++11 to make mutexes less intimidating. But I do hope you have a compelling reason for such a complex design.

Arthur Kalliokoski
Second in Command
February 2005
avatar

any input coming into the event handler queue has to be polled

  al_wait_for_event(queue, &event);  //Sleep until something happens

https://www.allegro.cc/forums/thread/613680/994171#target

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

Edgar Reynaldo
Major Reynaldo
May 2007
avatar

al_wait_for_event(queue, &event); //Sleep until something happens

That's exactly what I mean by polling. You are actively asking the queue not to return until you receive an event. Perhaps I wasn't clear enough earlier.

The alternative is to have another thread monitoring that input (using al_wait_for_event in the backend) which will emit an event to its listeners as soon as an event is received on the queue. That however raises the issue of your listeners callback code executing on multiple threads. This means all my users would have to use mutexes around their code... which would be nice to avoid.

My second alternative is to have queues emit events to listeners when they are queried. It would keep processing on the main thread, but all listeners reactions are delayed until the user processes them. Not my favorite option, but it seems to be what is being suggested.

jmasterx said:

Since I'm inferring this has to do with GUI in some way, the simple solution I would say is, if you have a task that will make the UI unresponsive, fork it to another thread.

Yes, it is for the main system and event handling of my eagle library.

The only reason I am having this dilemma I belive is because when I did things the way everyone seems to be suggesting, (using ALLEGRO_EVENT_QUEUE's) and registering all the input, but then that means none of the listener callbacks get called until you poll the queue. See what I am saying?

Arthur Kalliokoski
Second in Command
February 2005
avatar

none of the listener callbacks get called until you poll the queue.

The loop that "polls" events doesn't run continuously, it hangs at the al_wait_for_event() line (sleeps) until an event is received. Then it runs through the switch statement full speed.

Try this little console program, which doesn't have a timer. It'll print a line with event count every event it receives, so swirl the mouse around in the window and/or hit keys when it has focus, and watch the printf's in the command prompt window you started it from. Hit ESC to quit.

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

jmasterx
Member #11,410
October 2009

Exactly what Arthur said. The way any GUI application functions is that it yields the cpu until there is an event. Then, the event is processed. This is not polling. Polling is more like:

void logic()
{
     foreach queued event
      do event;
     render();
}

al_wait_for_event blocks and sleeps until the OS interrupts it with an event. So effectively, the first listener will process the event immediately when it happens. It does not have to wait until the next while iteration as you might be thinking.

Arthur Kalliokoski
Second in Command
February 2005
avatar

jmasterx said:

It does not have to wait until the next while iteration as you might be thinking.

I think it does, since there's a switch statement and all the cases have break statements, but it doesn't take but a few milliseconds at worst (probably doing the call to al_wait_for_event())

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

Edgar Reynaldo
Major Reynaldo
May 2007
avatar

I know I was abusing the use of the word poll there, but you do you understand what I'm saying? Calling al_wait_for_event blocks the thread. The question is, should I have a separate thread for handling all incoming events and emit them when they actually occur, or do I have the user acknowledge they need to emit the event or do I simply have my event handler intercept each message and emit them to listeners when the user ahem polls the queue by calling EventHandler::WaitForEvent()?

And what if I had a separate event source? If my event handler is sleeping waiting for an event from al_wait_for_event then I have to wake it up by emitting an event to its own ALLEGRO_EVENT_QUEUE and then I have to ignore it and return the next real event.

I'm just saying, that if I want to have listeners for input sources, then someone needs to be polling for them, whether that is the user or a separate thread is the issue.

Should I show code? Maybe I'm just not explaining well, but this is all for the main system of my library, so I want to design it right before I go and put coding time into it.

jmasterx
Member #11,410
October 2009

I think I get what you mean. You don't want your GUI to force a blocking function like al_wait_for_event to be called. But, I have to wonder, if someone is using A5 but not using al_wait_for_event for their game timer and everything else, I have to wonder what it is they are trying to do with your GUI. I really do not see why any sane programmer would want a straight-up infinite while loop that would not yield the CPU. Even the logic / update function should only be called every 1/60th of a second.

Agui, for example, requires you to supply the Allegro event:

#SelectExpand
1void SceneManager::run() 2 { 3 m_devices->setSceneMessenger(this); 4 5 al_start_timer(m_gameTimer); 6 7 //is the event handled? 8 bool handled = false; 9 ALLEGRO_EVENT next; 10 int numTicks = 0; 11 12 //main loop 13 while(m_gameIsRunning) 14 { 15 16 handled = false; 17 al_wait_for_event(queue,&evt); 18 19 bool hasNext = al_peek_next_event(queue,&next); 20 if(hasNext && next.type == ALLEGRO_EVENT_TIMER) 21 { 22 al_drop_next_event(queue); 23 } 24 //render the scene 25 if(m_needsRedraw && al_is_event_queue_empty(queue)) 26 { 27 m_g.begin(); 28 m_currentScene->render(); 29 m_g.end(); 30 31 m_needsRedraw = false; 32 } 33 34 defaultBeginEventHandler(&evt); 35 m_currentScene->processEvent(&evt,handled); 36 37 //do default behavior if event was not handled by the scene 38 if (!handled) 39 { 40 defaultEndEventHandler(&evt); 41 } 42 43 processMessages(); 44 } 45 }

Which has:

#SelectExpand
1 void SceneManager::defaultBeginEventHandler( ALLEGRO_EVENT*evt ) 2 { 3 4 m_currentScene->processGuiInputEvent(evt); 5 6 if(evt->type == ALLEGRO_EVENT_TIMER && evt->timer.source == m_gameTimer) 7 { 8 m_needsRedraw = true; 9 m_currentScene->processGuiLogic(); 10 m_currentScene->logic(); 11 m_devices->getNetClient()->tick(); 12 } 13 else if(evt->type == ALLEGRO_EVENT_DISPLAY_RESIZE) 14 { 15 m_needsResize = true; 16 m_newScreenWidth = evt->display.width; 17 m_newScreenHeight = evt->display.height; 18 sendResizeMessage(m_newScreenWidth,m_newScreenHeight); 19 } 20 else if(m_needsResize) 21 { 22 m_needsResize = false; 23 24 } 25 }

Which calls the Agui input handler:

void Scene::processGuiInputEvent( ALLEGRO_EVENT* evt )
  {
    m_a5GuiInput.processEvent(*evt);
  }

Everything in A5 is event driven and I do not see why anyone would not be okay with the blocking al_wait_for_event.

Edgar Reynaldo
Major Reynaldo
May 2007
avatar

Really I am saying that I want listeners on my input sources but then they need to run in their own thread. That's all I'm really saying, but that makes it ugly and then you need mutexes, which aren't hard, but most newbs don't understand mutexes.

For instance to get an event from a keyboard input source I would need to have a separate allegro event queue listening to the keyboard running in a different thread. This then means you need to synchronize with the main thread to share data, which sucks.

The crux of the problem is not being able to wait for an allegro event and receive an outside event from an EagleEventSource at the same time without using another thread to wait for allegro events.

jmasterx
Member #11,410
October 2009

I'm wondering though, in what instance would you receive an eagle event that is not triggered by an allegro event? I think Allegro event queues are thread-safe so, from another thread, you could emit an Allegro user event that would be handled by your Eagle API and dispatched on the main thread to listeners.

Edgar Reynaldo
Major Reynaldo
May 2007
avatar

Here, I'll show you the problem I'm having currently. My timers are an event source, or at least I would like them to be, but if I

1) al_register_event_source them to the event handler's allegro event queue then who do I have emit events to listeners? My event handler class? When? When the user waits for an event? Or when the event happens? Garrh.

void Allegro5Timer::RegisterTimerInput(EagleEventHandler* event_handler) {
/*
   EAGLE_ASSERT(event_handler);
   Allegro5EventHandler* a5_event_handler = dynamic_cast<Allegro5EventHandler*>(event_handler);
   EAGLE_ASSERT(allegro_handler);

   ALLEGRO_EVENT_QUEUE* allegro_queue = a5_event_handler->AllegroQueue();
   EAGLE_ASSERT(allegro_queue);
   al_register_event_source(allegro_queue , al_get_timer_event_source(timer));
*/
   // so we don't have timers registered to queues and eagle timers registered as event sources at the same time
   SubscribeListener(event_handler);/// TODO Convert to an event source
}

Timers work fine with al_wait_for_event (commented code here) but then no one emits the event, which can be done every time al_wait_for_event returns, but do you have someone constantly monitoring this (another thread)? What if no one ever asks the queue for an event? Then they just pile up, without any of their listener callbacks being called. :P See my problem?

I think I'm just gonna have separate threads and make listeners use mutexes. IDK. What else can I do?

jmasterx
Member #11,410
October 2009

I might not be understanding correctly again but,

In agui, the user HAS to do the following:

al_wait_for_event(queue,evt);
m_a5GuiInputHandler->processEvent(evt));

So Agui gets to see every Allegro event, regardless of whether the user uses the event or not.

It is at this point that you would do something like:

if the event timer is an eagle timer then
dispatch event to this timer's subscribers

Using this method though, if someone registers a 10ms timer but the game loop takes 15ms, then you will end up with timer events piling up. However, the only way I can think of solving that would mean that the user timer callback would be called at the correct time from another thread which could introduce issues depending on what the user is doing. But if you have a mutex that blocks the main thread, it should hopefully not cause an issue. (Some issues it could cause though is, say you're in the middle of rendering when the timer ticks, and that timer event causes you to add a gui element thereby invalidating the iterator and crashing the main thread when it returns). So your users will have to be careful if you do it this way.

Edgar Reynaldo
Major Reynaldo
May 2007
avatar

How does Java do dynamic gui's then? Does their gui run on the EDT? Or do they just use synchronize a lot?

I mean, I could use a delayed execution model, where the listeners are notified as soon as you take the event off the queue. That would mean its your own fault if you don't empty the queue. But that still doesn't tell me what to do with my timer class and trying to make it an event source. It just feels wrong to have a timer class not call its callbacks on an interrupt basis. I can always synchronize the event handler with a mutex, and then I guess I would leave the responsibility of timer listeners to implement their own locking. It's not perfect, but that seems like the best bet so far to me.

jmasterx
Member #11,410
October 2009

I'm not sure how Java does a lot of things TBH. The JVM is like a nice magic box.

Your idea sounds good. However, I would think about exactly what the user will be using these timers for and how much precision they really need.

In Agui, I got around this problem by sacrificing timer precession.

in my logic() loop, I go through each timer and do a timestamp check with al_get_time() which is pretty accurate.

If the time is greater than the interval, then dispatch to the listeners.

This design avoids timers queuing up, avoids mutexes, syncing, and is generally accurate to 1 logic() execution which is 17ms.

I figure if people need to do animation, they'll do it in Widget::logic(double time).

If they need more than 17ms accuracy, they probably are doing something that should use a native OS timer instead.

Something like Java, or QT is a purely event-driven GUI that invalidates windows to redraw, but a game GUI has different goals that can be adapted to a game loop.

Edgar Reynaldo
Major Reynaldo
May 2007
avatar

Edit 2 :
Important fixes
Eagle5_0pt3pt1_bugs_fixed_plus_event_demo.7z

If you downloaded anything be sure to get the latest version. I forgot to start the event thread, so there were never any messages in the queue! There is now a demo program showing all the events that are being received with the messages on the console and displayed on the screen as well. Please try it out!

Original post
Ok, been looking at some things, and here's the next option : Make the event handler run in its own thread, and protect it with a mutex. So users can access it from any thread they want. And then make listeners lock their own mutexes to protect their own data from corruption if necessary. This way the event handler can have its own dedicated thread to listen for events and yet they can access the queue from any thread they want and it will do the locking for them. Then, I don't need to make every input have its own thread, rather just use al_register_event_source and monitor the event queue.

Edit:
Alright, been working on it, and in addition to the event handler having its own thread where it listens to the allegro event queue, I've added the ability to emit events at once, or later on when they are read from the event handler's queue. That way no one ever need touch a mutex if they don't want to.

I guess code speaks better than words, so here is my allegro 5 event handler :

Allegro5EventHandler.hpp & Allegro5EventHandler.cpp

#SelectExpand
1 2#ifndef Allegro5EventHandler_HPP 3#define Allegro5EventHandler_HPP 4 5 6#include "Eagle/Events.hpp" 7#include "Eagle/Threads.hpp" 8 9 10#include "Eagle/backends/Allegro5/Allegro5Conditions.hpp" 11#include "Eagle/backends/Allegro5/Allegro5Threads.hpp" 12 13#include "allegro5/allegro.h" 14 15 16EagleEvent GetEagleEvent(ALLEGRO_EVENT ev); 17 18enum EVENT_THREAD_MESSAGES { 19 CLOSE_EVENT_THREAD = 0 20}; 21 22void* Allegro5EventThreadProcess(EagleThread* thread , void* event_handler); 23 24 25class Allegro5EventHandler : public EagleEventHandler { 26 27private : 28 29 friend void* Allegro5EventThreadProcess(EagleThread* thread , void* data); 30 31 ALLEGRO_EVENT_QUEUE* event_queue;/// main event queue 32 ALLEGRO_EVENT_SOURCE main_source;// for messages to the event thread 33 34 Allegro5Thread* event_thread; 35 36 Allegro5ConditionVar* cond_var; 37 38 bool emitter_delay; 39 40 41 bool Running(); 42 43 virtual EagleEvent PrivateWaitForEvent(); 44 virtual EagleEvent PrivateWaitForEvent(double timeout); 45 46 virtual void PrivateRefreshQueue(); 47 48public : 49 50 Allegro5EventHandler(bool delay_emitted_events); 51 ~Allegro5EventHandler(); 52 53 virtual bool Create(); 54 virtual void Destroy(); 55 virtual bool Valid(); 56 57 virtual void RespondToEvent(EagleEvent e); 58 59 ALLEGRO_EVENT_QUEUE* AllegroQueue() {return event_queue;} 60 61}; 62 63 64 65 66#endif // Allegro5EventHandler_HPP 67 68 69 70 71#include "Eagle/Logging.hpp" 72 73#include "Eagle/backends/Allegro5/Allegro5EventHandler.hpp" 74#include "Eagle/backends/Allegro5/Allegro5InputHandler.hpp" 75#include "Eagle/backends/Allegro5/Allegro5Mutex.hpp" 76#include "Eagle/backends/Allegro5/Allegro5System.hpp" 77 78 79 80 81EagleEvent GetEagleEvent(ALLEGRO_EVENT ev) { 82 EagleEvent ee; 83 84 ee.type = (EAGLE_EVENT_TYPE)(int)ev.type; 85 ee.timestamp = ev.any.timestamp; 86/* 87 EAGLE_EVENT_TYPE type; 88 EagleEventSource* source; 89 double timestamp;// In seconds since program started 90 union { 91 KEYBOARD_EVENT_DATA keyboard;// keycode display unicode modifiers repeat 92 MOUSE_EVENT_DATA mouse;// x y z w dx dy dz dw button display 93 JOYSTICK_EVENT_DATA joystick;// id stick axis button pos 94 TOUCH_EVENT_DATA touch;// display id x y dx dy primary 95 TIMER_EVENT_DATA timer;// source count 96 DISPLAY_EVENT_DATA display;// source x y width height orientation 97 WIDGET_EVENT_DATA widget; 98 USER_EVENT_DATA data; 99 }; 100*/ 101 102 Allegro5System* a5sys = dynamic_cast<Allegro5System*>(eagle_system); 103 EAGLE_ASSERT(a5sys); 104 105 switch (ev.type) { 106 case ALLEGRO_EVENT_JOYSTICK_AXIS : 107 case ALLEGRO_EVENT_JOYSTICK_BUTTON_DOWN : 108 case ALLEGRO_EVENT_JOYSTICK_BUTTON_UP : 109 case ALLEGRO_EVENT_JOYSTICK_CONFIGURATION : 110 ee.joystick.nid = get_joystick_n(ev.joystick.id); 111 if (ee.joystick.nid == -1) { 112 ee.joystick.id = 0; 113 } 114 else { 115 ee.joystick.id = &joysticks[ee.joystick.nid]; 116 } 117 ee.joystick.stick = ev.joystick.stick; 118 ee.joystick.axis = ev.joystick.axis; 119 ee.joystick.button = ev.joystick.button; 120 ee.joystick.pos = ev.joystick.pos; 121 break; 122 case ALLEGRO_EVENT_KEY_DOWN : 123 case ALLEGRO_EVENT_KEY_UP : 124 case ALLEGRO_EVENT_KEY_CHAR : 125 ee.keyboard.keycode = ev.keyboard.keycode; 126 ee.keyboard.unicode = ev.keyboard.unichar; 127 ee.keyboard.modifiers = ev.keyboard.modifiers; 128 ee.keyboard.repeat = ev.keyboard.repeat; 129 ee.keyboard.display = a5sys->GetGraphicsContext(ev.keyboard.display); 130 break; 131 case ALLEGRO_EVENT_MOUSE_AXES : 132 case ALLEGRO_EVENT_MOUSE_BUTTON_DOWN : 133 case ALLEGRO_EVENT_MOUSE_BUTTON_UP : 134 case ALLEGRO_EVENT_MOUSE_WARPED : 135 case ALLEGRO_EVENT_MOUSE_ENTER_DISPLAY : 136 case ALLEGRO_EVENT_MOUSE_LEAVE_DISPLAY : 137 ee.mouse.x = ev.mouse.x; 138 ee.mouse.y = ev.mouse.y; 139 ee.mouse.z = ev.mouse.z; 140 ee.mouse.w = ev.mouse.w; 141 ee.mouse.dx = ev.mouse.dx; 142 ee.mouse.dy = ev.mouse.dy; 143 ee.mouse.dz = ev.mouse.dz; 144 ee.mouse.dw = ev.mouse.dw; 145 ee.mouse.button = ev.mouse.button; 146 ee.mouse.display = a5sys->GetGraphicsContext(ev.mouse.display); 147 break; 148 case ALLEGRO_EVENT_TIMER : 149 ee.timer.eagle_timer_source = 0;// did not come from an Eagletimer 150 ee.timer.raw_source = ev.timer.source; 151 ee.timer.count = ev.timer.count; 152 break; 153 case ALLEGRO_EVENT_DISPLAY_EXPOSE : 154 case ALLEGRO_EVENT_DISPLAY_RESIZE : 155 case ALLEGRO_EVENT_DISPLAY_CLOSE : 156 case ALLEGRO_EVENT_DISPLAY_LOST : 157 case ALLEGRO_EVENT_DISPLAY_FOUND : 158 case ALLEGRO_EVENT_DISPLAY_SWITCH_OUT : 159 case ALLEGRO_EVENT_DISPLAY_SWITCH_IN : 160 case ALLEGRO_EVENT_DISPLAY_ORIENTATION : 161 case ALLEGRO_EVENT_DISPLAY_HALT_DRAWING : 162 case ALLEGRO_EVENT_DISPLAY_RESUME_DRAWING : 163 ee.display.source = a5sys->GetGraphicsContext(ev.display.source); 164 ee.display.x = ev.display.x; 165 ee.display.y = ev.display.y; 166 ee.display.width = ev.display.width; 167 ee.display.height = ev.display.height; 168 ee.display.orientation = ev.display.orientation; 169 break; 170 171 case ALLEGRO_EVENT_TOUCH_BEGIN : 172 case ALLEGRO_EVENT_TOUCH_END : 173 case ALLEGRO_EVENT_TOUCH_MOVE : 174 case ALLEGRO_EVENT_TOUCH_CANCEL : 175 ee.touch.display = a5sys->GetGraphicsContext(ev.touch.display); 176 ee.touch.id = ev.touch.id; 177 ee.touch.x = ev.touch.x; 178 ee.touch.y = ev.touch.y; 179 ee.touch.dx = ev.touch.dx; 180 ee.touch.dy = ev.touch.dy; 181 ee.touch.primary = ev.touch.primary; 182 break; 183 default : break; 184 } 185 return ee; 186} 187 188 189 190 191void* Allegro5EventThreadProcess(EagleThread* thread , void* event_handler) { 192 193 194// Allegro5EventHandler* a5_event_handler = dynamic_cast<Allegro5EventHandler*>(event_handler); 195 Allegro5EventHandler* a5_event_handler = dynamic_cast<Allegro5EventHandler*>((EagleEventHandler*)event_handler); 196 EAGLE_ASSERT(a5_event_handler); 197 EAGLE_ASSERT(a5_event_handler->Valid()); 198 199 ALLEGRO_EVENT_QUEUE* queue = a5_event_handler->event_queue; 200 ALLEGRO_EVENT_SOURCE* main_source = &(a5_event_handler->main_source); 201// ALLEGRO_MUTEX* mutex = a5_event_handler->mutex; 202 EagleConditionVar* cond_var = a5_event_handler->cond_var; 203 204 bool close = false; 205 while (!thread->ShouldStop() && !close) { 206 ALLEGRO_EVENT ev; 207 al_wait_for_event(queue , &ev); 208 209 if (ev.type == EAGLE_EVENT_USER_START) { 210 if (ev.user.source == main_source) {// signalled through queue by another thread 211 switch (ev.user.data1) { 212 case CLOSE_EVENT_THREAD : 213 close = true; 214 break; 215 default : 216 OutputLog() << "Event not recognized by event thread process." << std::endl; 217 break; 218 219 } 220 } 221 } 222 else { 223 a5_event_handler->RespondToEvent(GetEagleEvent(ev)); 224 cond_var->BroadcastCondition();// alert any thread waiting on the condition (an event) 225 } 226 } 227 228 return event_handler; 229} 230 231 232 233 234 235 236bool Allegro5EventHandler::Running() { 237 return (event_thread && event_thread->Running()); 238} 239 240 241 242EagleEvent Allegro5EventHandler::PrivateWaitForEvent() { 243 // we need to wait for the event thread to signal there is a message in the queue 244 // parent has already determined there is no event in the queue, so we wait 245 cond_var->WaitForCondition(); 246 return TakeNextEvent(); 247} 248 249 250 251EagleEvent Allegro5EventHandler::PrivateWaitForEvent(double timeout) { 252 253 int ret = 0; 254 ret = cond_var->WaitForConditionUntil(timeout); 255 EagleEvent e; 256 if (!ret) { 257 // signalled - there is an event now 258 return TakeNextEvent(); 259 } 260 // timed out, return default event 261 return e; 262} 263 264 265 266void Allegro5EventHandler::PrivateRefreshQueue() { 267 // Do nothing, event thread is already waiting on events for us 268} 269 270 271 272Allegro5EventHandler::Allegro5EventHandler(bool delay_emitted_events) : 273 EagleEventHandler(delay_emitted_events), 274 event_queue(0), 275 main_source(), 276 event_thread(0), 277 cond_var(0) 278{ 279 al_init_user_event_source(&main_source); 280} 281 282 283 284Allegro5EventHandler::~Allegro5EventHandler() { 285 Destroy(); 286} 287 288 289 290bool Allegro5EventHandler::Create() { 291 EAGLE_ASSERT(eagle_system);// System must be initialized and running 292 Destroy(); 293 294 event_queue = al_create_event_queue(); 295 al_register_event_source(event_queue , &main_source); 296 297 mutex = new Allegro5Mutex(); 298 299 event_thread = new Allegro5Thread(); 300 301 cond_var = new Allegro5ConditionVar(); 302 303 // don't call create on thread until queue, mutex, and condvar are in place 304 if (!event_queue || 305 !mutex->Create(false) || 306 !cond_var->Create() || 307 !event_thread->Create(Allegro5EventThreadProcess , this) 308 ) 309 { 310 Destroy(); 311 return false; 312 } 313 314 return true; 315} 316 317 318 319 320void Allegro5EventHandler::Destroy() { 321 322 /// MUST STOP PROCESS FIRST 323 if (Running()) { 324 ALLEGRO_EVENT ev; 325 ev.type = EAGLE_EVENT_USER_START; 326 ev.any.source = &main_source; 327 ev.any.timestamp = al_get_time(); 328 ev.user.source = &main_source; 329 ev.user.data1 = CLOSE_EVENT_THREAD; 330 al_emit_user_event(&main_source , &ev , NULL);// tell event thread to close 331 event_thread->Join(); 332 } 333 334 if (event_thread) { 335 delete event_thread; 336 event_thread = 0; 337 } 338 if (mutex) { 339 delete mutex; 340 mutex = 0; 341 } 342 if (cond_var) { 343 delete cond_var; 344 cond_var = 0; 345 } 346 if (event_queue) { 347 al_destroy_event_queue(event_queue); 348 event_queue = 0; 349 } 350 351} 352 353 354 355bool Allegro5EventHandler::Valid() { 356 return (event_queue && 357 mutex && 358 mutex->Valid() && 359 event_thread && 360 event_thread->Valid() && 361 cond_var && 362 cond_var->Valid() 363 ); 364} 365 366 367 368void Allegro5EventHandler::RespondToEvent(EagleEvent ee) { 369 EagleEventHandler::RespondToEvent(ee);// emits and queues message 370 // now wake any threads waiting on us 371 cond_var->BroadcastCondition(); 372}

I'd like to note that this is my first time using a condition variable, and it works well to signal the waiting thread for me.

I'd also like to note there is still that pesky d3d_shutdown crash with my code.

Here : https://www.allegro.cc/forums/thread/613306
and here : https://www.allegro.cc/forums/thread/613372/991308#target

Program received signal SIGSEGV, Segmentation fault.
0x6779fc80 in d3d_shutdown () at C:\downloads\Programming\ProgrammingLibraries\Allegro\5pt1pt8\allegro\src\win\d3d_disp.cpp:2610
2610       _al_d3d->Release();
(gdb) p _al_d3d
$1 = (LPDIRECT3D9) 0xc407e0
(gdb) p _al_d3d->Release
There is no member named Release.
(gdb) p _al_d3d->Release()
Couldn't find method IDirect3D9::Release
(gdb)

And I've committed to SVN as well if you want the full library.
svn checkout "http://svn.code.sf.net/p/eagle5gui/code/trunk eagle5gui-code"

Source package
Binary + Source package (really unimpressive demo that counts to 16 to demonstrate the use of event handlers and timers, but the library has a fully functional debugging build you can use!)
(They are linked with Allegro 5.1.8 built with MinGW 4.5.0).
(And, with some alteration you should be able to get the CB projects to compile the library for you. Just the matter of fixing some search directories I believe)

So I'd love it if any of you would check out my library. Sorry there are no docs yet. Tell me what your favorite kind of docs are and I might use them.

And if anyone has a clue about the crash in d3d_shutdown, I'd love to hear suggestions.

Go to: