[A5] Threading a game
J-Gamer

I have been trying to wrap my head around threads and I have come up with two questions:
1. Is there a way to send events from one thread to another? In particular user events, so that I can send costum information(name of button pressed, upgrade used, etc.). I was thinking of having an event queue in the shared data class which would listen to such user events.
2. Is it possible to use functions of an object as the main function of a thread? I could than have a AI::Think function which would try to find the other player's cannons etc.

Also, according to this thread, threading can make modularising your application way easier if you got the mutexes right. Did I understand that correctly?

Any help?

weapon_S

1. Send them all to a 'global' queue. "Listening" is a big word.
2. With Boost maybe? No, otherwise.

I don't know what the link is about. Just fiddle around until you understand it ???

J-Gamer
weapon_S said:

Send them all to a 'global' queue.

How do you mean? What I thought of doing was having an event queue in a data class(where you can put a mutex on) and sending costum events to it so that the parent thread can get informed about what happened.

Example of usage:
1. Game initialisation
2. Main thread starts AI thread
3. AI thinks while main thread is handling user input etc.
4. AI wants to make a move and sends an event with all necessary data in it to the event queue in the data class
5. main thread listens to the event queue(how should this happen? there is always the possibility of a mutex being on the queue at the time) in the data class and reacts accordingly by making the move.
6. Go back to step 3

Thomas Fjellstrom

Yes, I believe event queues were explicitly designed to be thread safe. So you can send events from one thread and receive events in another.

J-Gamer

I just came up with this to let this happen:
in the data class, I should have an ALLEGRO_USER_EVENT_SOURCE* member, which is set when the thread first starts. Then, in the main thread, I register this event source in the event queue of the main thread, and whenever the AI makes its move, it uses al_emit_user_event to notify the main thread it made a move.

Is my logic sound/should this work?

Thomas Fjellstrom

Yup, though you may also have the option of emitting fake input events. Which ever works best for your case.

J-Gamer

I think the AI will want to just say what he wants to do, instead of faking a mouse click or something like that. I'm also thinking of putting user input in another thread with the same principle: as soon as the player really chooses to send a unit(scout,bomb,other upgrade) to the field, the main thread will be noticed trough a user event.

Thomas Fjellstrom

Some people prefer to simulate input events for AI, so it can be recorded in the same way user input is recorded, for replays, save games, and cheat detection. But I think if you're emitting user events for the player as well, you can use those to do all that instead of raw input.

J-Gamer

Yeah... I think it is easier to let the user input thread figure out what the user wants to do and then send the corresponding event to the main thread. Otherwise, I would have to put extra code in at the AI output part to calculate all the coordinates of mouse clicks etc.

One technical question that still hasn't been answered: How do I make a function pointer to a member function? Because that's what I'll need if I want to use the AI's Think() function as main function of the thread.

Edgar Reynaldo
J-Gamer said:

2. Is it possible to use functions of an object as the main function of a thread? I could than have a AI::Think function which would try to find the other player's cannons etc.

Yes, you can use class methods, they just have to be static, and you have to bind the parameters somehow.

#SelectExpand
1 2class ArgumentBinder { 3private : 4 int p1; 5 Data* d; 6public : 7 static void Process(ALLEGRO_THREAD* thread , void* this) { 8 ((ArgumentBinder*)this)->ClassMethod(); 9 } 10 11 void ClassMethod() { 12 // do stuff with members 13 } 14} 15 16ArgumentBinder bound_data; 17ALLEGRO_THREAD* thread = al_create_thread(ArgumentBinder::Process , &bound_data);

J-Gamer

Thanks! I'll try that.

EDIT:
This is what I've come up with so far:

#SelectExpand
1#ifndef __THREADED_H__ 2#define __THREADED_H__ 3class Threaded; 4 5class ThreadedData 6{ 7 public: 8 ALLEGRO_MUTEX* mutex; 9 ALLEGRO_COND* cond; 10 bool ready; 11 Threaded* thr; //used in Threaded::InitThread 12 ThreadedData() : mutex(al_create_mutex()),cond(al_create_cond()),ready(false); 13}; 14 15class Threaded 16{ 17 private: 18 public: 19 static void InitThread(ALLEGRO_THREAD* thr, void* data) 20 { 21 (ThreadedData*)data->thr->Thread_function((ThreadedData*)data); 22 }; 23 virtual void Thread_function(ALLEGRO_THREAD* thr,ThreadedData* data) {}; 24}; 25#endif

These are classes you should derive from, like this:

#SelectExpand
1class ThreadedUIData : public ThreadedData 2{ 3 public: 4 //other variables you need to be shared between your class and the parent thread 5}; 6 7class ThreadedUI : public ThreadedData 8{ 9 private: 10 ALLEGRO_EVENT_QUEUE* event_queue; 11 12 public: 13 void Thread_function(ALLEGRO_THREAD* thr,ThreadedData* data); 14}; 15 16void ThreadedUI::Thread_function(ALLEGRO_THREAD* thr, ThreadedData* data) 17{ 18 //initialize variables 19 event_queue = al_create_event_queue(); 20 al_register_event_source(event_queue,al_get_mouse_event_source()); 21 al_register_event_source(event_queue,al_get_keyboard_event_source()); 22 23 24 al_lock_mutex(data->mutex); 25 ThreadedUIData* ui_data = (ThreadedUIData*)data; 26 //do something with the fields of ThreadedUIData* ui_data 27 al_signal_cond(data->cond); 28 data->ready = true; 29 al_unlock_mutex(data->mutex); 30 31 //main thread loop 32 while(!al_thread_should_stop(thr)) 33 { 34 //check the event queue for input events and send a costum event if something important has been selected 35 } 36};

Or am I doing something terribly wrong here?

BAF

Some people prefer to simulate input events for AI, so it can be recorded in the same way user input is recorded, for replays, save games, and cheat detection.

Err, what? I've never heard of that before. ???

Even if you did do that, how would you prevent the user input from affecting the AI player?

Arthur Kalliokoski

IIRC that's how they did it in Quake, all actors have the same input interface, some are controlled by AI.

Thomas Fjellstrom
BAF said:

Even if you did do that, how would you prevent the user input from affecting the AI player?

The AI is essentially just another player with access to no more control or information as the player is.

BAF

Okay, but why would you simulate input events? How would you prevent the user from making those same inputs, thereby screwing wth the AI?

Arthur Kalliokoski

The "inputs" to an actor are just software interfaces, not actually hardwired to the mouse or anything.

BAF

Yes, but TF was talking about emitting fake input events. If you're faking input events to control the AI, then it looks the same as normal input events.

Arthur Kalliokoski

The AI actors inputs are sent "events" by the AI. Your protagonist actor is sent "events" from the mouse and keyboard, etc.

Think of a computer controlled car that has servos hooked to the steering wheel and pedals, and you're not in it. You race this car by driving another car normally.

Thomas Fjellstrom
BAF said:

Yes, but TF was talking about emitting fake input events. If you're faking input events to control the AI, then it looks the same as normal input events.

Depends. If you went that low level, you could simulate a second mouse/keyboard. But "Input Events" does not necessarily mean low level input events. But yeah, if Allegro doesn't have a way to distinguish between multiple low level input devices, that would make it difficult to go that low level.

BAF

Yes, I understand. I am questioning TF's solution of handling threading by putting fake input events into the queue from the other thread.

Matthew Leverton

This:

But "Input Events" does not necessarily mean low level input events

It's not a very hard concept to grasp. :P

BAF

The original description was ambiguous at best, and that was what I was referring to. I understand the intended meaning...

Some people prefer to simulate input events for AI, so it can be recorded in the same way user input is recorded, for replays, save games, and cheat detection.

Trent Gamblin

I don't think you would put them on the same queue. For example, you could have an Entity class that has a ALLEGRO_EVENT *GetInput(void) method that could return ALLEGRO_EVENTS like key presses and releases and the "Game" class would handle them. Then you can do things like:

for (i = 0; i < num_entities; i++) {
    ALLEGRO_EVENT *e = entity[i]->GetInput();
    if (e) {
        if (e->type == ALLEGRO_EVENT_KEYBOARD) {
            switch (e->keyboard.keycode) {
                ...
            }
        }
    }
}

And you could have some typedef:

typedef ALLEGRO_EVENT *(input_getter)(void);

Then your player class could have

private:
    input_getter input;

And when you wanted to change input you could just set 'input' to PhysicalInput(), ScriptInput(), NoInput() or whatever.

None of this really relies on using ALLEGRO_EVENTs though. But generally having a standard interface to get input from can be useful.

Thomas Fjellstrom
BAF said:

The original description was ambiguous at best, and that was what I was referring to. I understand the intended meaning...

It was meant to cover both low level injecting, and using higher level events, but both would still be input simulation as far as the engine is concerned.

J-Gamer

Bugfix: The code I posted had 2 errors, which are fixed in this renewed code:

#SelectExpand
1#ifndef __THREADED_H__ 2#define __THREADED_H__ 3class Threaded; 4 5class ThreadedData 6{ 7 public: 8 ALLEGRO_MUTEX* mutex; 9 ALLEGRO_COND* cond; 10 bool ready; 11 Threaded* thr; //used in Threaded::InitThread 12 ThreadedData() : mutex(al_create_mutex()),cond(al_create_cond()),ready(false) {}; 13}; 14 15class Threaded 16{ 17 private: 18 public: 19 static void InitThread(ALLEGRO_THREAD* thr, void* data) 20 { 21 ((ThreadedData*)data)->thr->Thread_function(thr,(ThreadedData*)data); 22 }; 23 virtual void Thread_function(ALLEGRO_THREAD* thr,ThreadedData* data) = 0; 24}; 25 26class ThreadedUIData : public ThreadedData 27{ 28 public: 29 //other variables you need to be shared between your class and the parent thread 30}; 31 32class ThreadedUI : public ThreadedData 33{ 34 private: 35 ALLEGRO_EVENT_QUEUE* event_queue; 36 37 public: 38 void Thread_function(ALLEGRO_THREAD* thr,ThreadedData* data); 39}; 40 41void ThreadedUI::Thread_function(ALLEGRO_THREAD* thr, ThreadedData* data) 42{ 43 //initialize variables 44 event_queue = al_create_event_queue(); 45 al_register_event_source(event_queue,al_get_mouse_event_source()); 46 al_register_event_source(event_queue,al_get_keyboard_event_source()); 47 48 49 ThreadedUIData* ui_data = (ThreadedUIData*)data; 50 al_lock_mutex(ui_data->mutex); 51 //do something with the fields of ThreadedUIData 52 ui_data->ready = true; 53 al_signal_cond(ui_data->cond); 54 al_unlock_mutex(ui_data->mutex); 55 56 //main thread loop 57 while(!al_get_thread_should_stop(thr)) 58 { 59 //do stuff 60 } 61}; 62 63#endif

EDIT: about input, I'm thinking of making costum events for using upgrades(scout,bomb,tunneler,...) so that those can be sent to the main game logic thread instead of raw input. I'll let the UI thread find out which upgrade the player wants to place where.(High-level input FTW ;D)

EDIT2: Are allegro calls thread-safe?(Like al_get_display_width/height,...)

RPG Hacker

I also have a threading related question. Guess I'll just ask it here:

When should I use mutexes? I know you're supposed to use them when, for example, accessing linked lists or something similiar. However, somewhere I read that you have to use them even when you're just using shared variables or even shared functions. Is that correct? I can hardly imagine, because that would surely make a lot of mutexes.

Also can someone here recommend good thread setups? Right now I have one thread for the logic loop and one thread for the screen loop. For a simple 2D platformer like I'm making, I guess, that's alright, although I'm only using up to two cores, but generally speaking: Is there any recommended thread setup for games? How many threads should you use and what for?

Matthew Leverton
J-Gamer said:

Are allegro calls thread-safe?

Yes and no. It's not documented, and I'm not even sure which calls are and aren't. Other than events, you should basically assume the calls are not thread safe.

Guess I'll just ask it here:

Don't hijack other people's threads.

RPG Hacker

Don't hijack other people's threads.

I don't see the problem, as it's perfectly related to the thread's title and can most likely help the thread creator as well. If you still think my post doesn't fit then tell me and I'll create a new one.

J-Gamer

To answer your question:
AFAIK the only time you need to use mutexes is when there's the possibility two threads will try to access the same variable at the same time. That's the whole point of mutexes and conditions.
I didn't know that you need them when accessing lists etc... seems strange.

EDIT: about thread setup, I was thinking of doing it this way:
Main thread where the game logic is executed
User input thread
AI thread
Render thread

The UI and AI threads send costum events to the main thread, which I explained before.

RPG Hacker

Thanks for the response.

J-Gamer said:

AFAIK the only time you need to use mutexes is when there's the possibility two threads will try to access the same variable at the same time. That's the whole point of mutexes and conditions.
I didn't know that you need them when accessing lists etc... seems strange.

Well, if accessing linked lists (talking about shared lists here of course) without a mutex, one thread could delete an element another thread is using, causing it to be deadlocked, so it seems logical to me to use a mutex in this situation. I just don't know if using regular shared variables necessarily requires mutexes. Is it that critical if two threads access a variable at the same time? Sure: When using a variable multiple times in a thread it may be a good idea to make a copy of it, anyways, just to prevent it from being changed mid-thread, but let's assume we have a variable that the logic thread writes to, while the render thread only reads it. Can it have critical effects if both threads access the variable at the same time, anyways?

J-Gamer

If the render thread asks for it while the logic thread is changing it, yes(certainly for larger variables). You could get garbage values because the processor is halfway trough its writing process.

RPG Hacker

I see. I guess it's "rather be safe than sorry" then and I shouldn't hesitate to use mutexes. Thanks!

Dustin Dettmer

You can also go too crazy using mutexes (mutices?[1]). Balance is key.

I'm working on solving a race condition[2] at the moment, which can happen with overuse of mutexes. In my particular case there is design flaw causing the mutex to try to lock itself twice. For instance:

#SelectExpand
1int theOracle(); 2 3int theAdvisersBuddy() 4{ 5 int r = 5; 6 if(notSoSure) 7 r += theOracle(); 8 return r; 9} 10 11int adviser() 12{ 13 int r = 13; 14 r += theAdvisersBuddy(); 15} 16 17int theOracle() 18{ 19 mutex.lock(); 20 int r = rand()%5; 21 r += adviser(); 22 mutex.unlock(); 23 return r; 24} 25 26printf("The oracle has spoken: %d", theOracle());

In this case the oracle must consult his adviser to give his answer. Little does the Oracle know, the adviser has a buddy. The adviser always consults his buddy before telling the oracle what to do. When the buddy is unsure what to give the adviser, he consults the oracle.

Here is the backtrace (flipped for learner's sake):

  1. printf() <- The output statement

  2. theOracle() => the mutex is now locked!

  3. adviser() <- the oracle is consulting his adviser

  4. theAdvisersBuddy() <- the adviser is consulting his buddy

  5. theOracle() => The mutex is locked a second time!

Since you can't lock a mutex twice, the program will freeze on the line 'mutex.lock()' inside theOracle.

RPG Hacker

Can't recursive mutexes do that (assuming all that code is running on one thread)?

Dustin Dettmer

I suppose that's true. My example is not ideal then.

Thread #608082. Printed from Allegro.cc