Allegro.cc - Online Community

Allegro.cc Forums » Programming Questions » Running a function on a triggered event (Thread?)

This thread is locked; no one can reply to it. rss feed Print
Running a function on a triggered event (Thread?)
nshade
Member #4,372
February 2004

Ok, here's a doozy!

I need to have execute a function on an event, but the tricky thing is... the event can not be checked for.

In the game I'm trying to port, there is a "process" (For lack of a better term) that is fired off by a hardware interrupt. When the interrupt fires, the game stops what it's doing, and the interrupt handler updates the game variables. and then returns control back to the game proper. In a perfect world, I want to attach this to the timer, but I'm not really seeing a mechanism that doesn't relay on actively looking at the event queue.

Do you think this will need a thread? I'm quite embarrassed to say, but I don't know much about threads other than they appear to be functions that multitask "in the background". I was looking at the documentation, and I guess it assumes a knowledge of what threads are.

In the game I have when the interrupt fires, it checks some global variables from the game proper and then adjusts some other vars and maybe update the screen. Then it passes control back to the main program which is none the wiser.

In the main program there is a global that can shut off the interrupt handler when it's not needed. I guess I can make the thread sleep and wake up on the timer interrupt, check the global, and do it's thing?

Is there a crash course I read up on how Allegro threads work?

==EDIT==

Ok, looking at this for about an hour. I have wired errors, but let's make sure I have my logic down.

#SelectExpand
1#include <allegro5/allegro.h> 2 3//make a global variable to monkey with 4int global =1; 5 6// First I define a thread object 7ALLEGRO_THREAD mythread; 8 9// Now I define what the thread does 10void *thread_func(ALLEGRO_THREAD *thr) 11{ 12 loop: 13 printf ("The global is now %d\n",global++); 14 goto loop; 15} 16 17//then the main program 18int main(int argc, char **argv) 19{ 20 if (!al_init()) { 21 abort_example("Could not init Allegro.\n"); 22 } 23 24 //initialize the thread 25 mythread = al_create_thread(thread_func); 26 27 // and start the thread 28 al_start_thread(mythread); 29 30 // do things here 31 al_rest(10); 32 printf ("The global is now %d\n",global); 33 34 //kill the thread (Shutting down the systen) 35 al_destroy_thread(mythread); 36}

Is that about how it's supposed to work, right?

Chris Katko
Member #1,881
January 2002
avatar

Perhaps I'm not thinking clearly, but don't you just want a function to run on your hardware interrupt, that then fires off an Allegro event, which then gets run when you next applicable loop comes by? (Just like how a keyboard works.)

I don't think you'd need a separate thread unless you're using polling. The hardware interrupt/callback gets called directly by the OS, and then that callback either directly does something (old style), or, sets an Allegro event to fire off in the queue (basically setting a "it changed" flag) and when the queue is processed next loop it will be processed.

Do you have details/an example of what your hardware event/interrupt is? If so, I could probably give a more specific example of what to do.

-----sig:
“Programs should be written for people to read, and only incidentally for machines to execute.” - Structure and Interpretation of Computer Programs
"Political Correctness is fascism disguised as manners" --George Carlin

nshade
Member #4,372
February 2004

The "program loop" for the main game is all over the place. Depending on what screen you are on, you had a different loop running the show. In fact you can probably argue the external thread is the main loop proper as it's the routine that updates everything and returns. (The thread does not do screen updates or do any input)

In the original game, this routine I'm trying to duplicate was attached to the interrupt timer on INT 1Ch. So basically 18 times a second a hardware interrupt was fired by the timer chip, the "main" game would pause, the program pointer would jump to the interrupt routine, the registers were saved and the routine would do the background stuff. After it was done, the registers were restored and the program counter would go back to the game. The game itself is rather oblivious of the second routine running in "tandem".

The game itself does not have a input/render/flip loop. It's very GUI based and will only update the screen when the user goes to another interface, (and then use a different input dispatch loop for that screen.)

So this external routine is what's actually updating the game's universe. The "Main" game is just the interface too it.

It's kind of clever, and that's why I think the thread idea is the best answer.

Edgar Reynaldo
Major Reynaldo
May 2007
avatar

nshade
Member #4,372
February 2004

It's really about emulation...

I guess I should provide some context. You see I'm porting a commercial game from the late 80s to modern systems. (I was able to procure the source code) The game has a graphics library that I'm replacing with an Allegro wrapper. It's going swimmingly. Believe it or not, the "core" game is pretty cross platform. However the original graphics library assumes there are hardware interrupts. In the case of the Atari and Amiga, this is tied to vsync. On the PC, a real mode int 1Ch interrupt handler is installed. I'm super SUPER trying to avoid going into the game proper and altering it's program flow for now.

Also, this legacy graphics library is used for other games from the same company.

I know it seems I'm overthinking it, the only function I can think in the game proper that executed in any regularity is the user input code, but if the game isn't collecting user input, (which sometimes it doesn't) the background process will stop. This interrupt does things like alter and prep sprites to be different when they are next displayed, it updates the universe on where the bad guys are doing, updates the status of your character for all the "over time" stuff and then cleans up.

So a thread would probably be the best bet as that's what the game expects. There are no facilities in the original code to service an event queue other then keyboard and mouse, and that is done asynchronously when it gets around to it.

I simply don't know if altering the user input is going to have ramifications down the line as I'm still rebuilding the old source code.

I know it weird, but games like this were written weirdly back then. Looking at it ever more, a thread is the best answer. In a 80s/90s kind of way it's what the original game had. It was this one function running as it's own thing independent of the actual game. Is it strange that the game has for all intents and purposes two program loops at once. Well, yea, one for the computer and one for you. It almost seems like a client/server architecture of you look at it right.

I decided to make a little allegro thread lab to check if my psudocode works above. I doesn't. (I didn't think it would) I was just trying to pin down the logic of setting up such a beast.

EDIT

Muhahah!!!

#SelectExpand
1#include <stdio.h> 2#include <allegro5\allegro.h> 3 4//make a global variable to monkey with 5int global =1; 6ALLEGRO_THREAD *mythread = NULL; 7 8int *arg; 9int dienow =0 ; 10int imdead =0 ; 11 12static void *Func_Thread(ALLEGRO_THREAD *thr, void *arg); 13 14//then the main program 15int main(int argc, char **argv) 16{ 17 if (!al_init()) { 18 printf("Could not init Allegro.\n"); 19 } 20 mythread = al_create_thread(Func_Thread, arg); 21 22 //initialize the thread 23 al_start_thread(mythread); 24 25 // do things here 26 while (!_kbhit()) { 27 printf("Inside loop global is %d\n", global); 28 al_rest(5); 29 } 30 dienow = 1; 31 if (imdead == 1) { exit; } 32 } 33 34// Now I define what the thread does 35static void *Func_Thread(ALLEGRO_THREAD *thr, void *arg) 36{ 37 38 (void)arg; 39 loop: 40 printf("The global is now %d\n", global++); 41 al_rest(1); 42 if (dienow == 1) { imdead = 1; return 0; } 43 44 goto loop; 45 46}

Yay! IPC via global variables. This is what I'm looking for. It doesn't seem that complicated at all!

bamccaig
Member #7,536
July 2006
avatar

It doesn't really sound like a thread is the correct solution here at all. An interrupt implies that you don't have two programs running side-by-side, but rather that you temporarily stop executing the main program to execute some special code. For all intents and purposes, that's just a subroutine call. The only difference is that on the original platforms that subroutine would have been invoked by the operating system via a hardware interrupt. Depending on how you decide to emulate this interrupt all you really need is to invoke some subroutine when the timing is right. A timer event sounds appropriate. Without knowing the details of how you've ported the game (since it sounds like you're trying to avoid modifying the guts of the existing source) it's kind of difficult to imagine what's wrong with this advice. Append: Threads sound like a really neat thing, but they're notoriously difficult to get right, and notoriously difficult to debug. If at all possible, you should avoid them.

nshade
Member #4,372
February 2004

I feel you, and trust me, I'm considering all the advice, but in the original code there is simply no place I can check a queue every 55 milliseconds. The keyboard and mouse ran on an interrupt too. The mouse routine would invoke INT 33h and take data that has been queued by the MOUSE.SYS driver. Likewise the keyboard was polled by invoking INT 16h and the BIOS returned what was waiting in it's queue. These were perfect the to attach to an Allegro event handler. (Well, I actually created two queues with Allegro and registered a single event to each. The original game didn't do a real hot job validating that was in the queue, so I just fed it two with the data each expects.

With INT 1Ch, it's simply not evoked by the game at all. It uses a function to start and stop the timer interrupt (and the routine it runs) arbitrarily when it want to "pause" the universe. There is no queue and there is nothing it checks. From the game's perspective, it's a function running outside and independent of the actual program. The game is pretty diligent about controlling the interrupt via some write-only global variables. The interrupt function, in turn, treats them as read only. Outside of the interrupt control variables, there is no way to prevent something from going out of control. I like to think of it like this though. In the game now, if I was to lose control of the thread, I'll just kill the process and fix it. Back when this game was being developed, if you lost control of the INT 1Ch interrupt, you were rebooting your computer.

Edgar Reynaldo
Major Reynaldo
May 2007
avatar

Okay, I'm really trying to feel ya here.

If you create a thread with an event queue on it, you can listen to your 'interrupt' timer, and set variables accordingly. You can even run your global 'subroutine' from there as well.

Your problem however, will be "pausing" the main game sequence and loop.

What is the structure of the main program? It is a sequence of subroutines? Does each subroutine have its own program loop? You're making me more and more curious about what your program looks like.

Those subroutine loops must, I repeat must, yield or wait while the global interrupt process is running. Otherwise you get all kinds of problems like data races. Ideally when using shared data among different threads you use a synchronization mechanism like a mutex.

Chris Katko
Member #1,881
January 2002
avatar

I definitely concur with what Edgar said. Unless I'm missing something, extra threads seem unnecessary and can cause tons of concurrency-related problems if they're allowed to get out of sync. This doesn't seem like a real multi-threaded application.

-----sig:
“Programs should be written for people to read, and only incidentally for machines to execute.” - Structure and Interpretation of Computer Programs
"Political Correctness is fascism disguised as manners" --George Carlin

Edgar Reynaldo
Major Reynaldo
May 2007
avatar

Well, also, I see it from your view. Having to modify a whole ton of subroutine program loops would suck.

EDIT
How archaic are we talking here? Written in C, but how old?

EDIT2
My new sig should be "always carry a mutex in your wallet"

nshade
Member #4,372
February 2004

Looks like code was started around 1987-88ish. Yes, every "screen" in the game has it's own program loop. I have a development blog, though I'm a weary of posting it here. Not because it's secret or anything, but because I don't want to lead on how awful a programmer I actually am :)

The game has mechanisms in place to keep race conditions to a minimum. It's pretty good about keeping variables read and write only, and can pause the interrupt when it's not needed. Right now the thread is working as intended. However, I'm using a very sub optimal loop.

void *newtimer(ALLEGRO_THREAD *thread, void *dummyarg)
{
   while (1)
   {
      inint = TRUE;
      if (killthread == TRUE)
      {
         printf("1Ch Thread killed\n");
         return 0;
      }
      /* Do all the things! */
      al_rest(0.055);
   }
}

I know... I know... I know.

This is NOT how I should be doing it. It eats up CPU time needlessly and I have no control structures short if a variable that is checked. The killthread bool comes from input routine. There are several debug routines in there from the previous devs. I added my own escape hatch... (You press the "escape" key).

I'm going to be making three passes in my quest to rebuild this game..

Pass 1: Bring back the dead - Get the game running using chicken-wire and good expectations. This puts the code into a functional state and allows me to go though the source, document, and learn.

Pass 2: Refactor - When I build the solution now, the compiler generates so many warnings I literally have them turned off. Between unused variables, unresolved externs, incorrect return types, and DOS functions that operate wildly from the POSIX / Win32 counterparts, it's a wonder that I even have a display. But I know it's broken and my second pass with be with /WX enabled and poisoned #pragmas (Or whatever MSVC uses) to squash all those things that make the compiler sad.

Pass 3: Update - Once the engine is in a good state, I will be giving a much-needed resolution boost and graphical face lift.

I'll be fixing all the hacks and broken bits armed with my better knowledge of the code. Maybe there is a control scheme that can be rewritten.. I don't know yet as my main priority is to just get it running first. It will be changed to a proper event queue when I unify mouse, keyboard, and display queues. I know it's hacky, and trust me, I know when I do something yucky. It's going to cause me grief when I refactor on pass 2. However, I'll be going to that phase with better knowledge of the system as a whole and armed with a bunch of TODO: triggers all over my code.

I am, by all means, not done :)
That said, hopefully you won't give my dev blog too much of the evil-eye.
https://codecontinuum.blogspot.com/

pkrcel
Member #14,001
February 2012

You're too hard on yourself, your blog is interesting and quite well written and you embarked on a tight adventure, mad props to you!

I can't be of much help, but....don't stress yourself out too much on the interrupt thing.

I came from embedded electronics programming by trade, and there you're tied to hardware and interrupt actually MEAN something.

Just emulate the interrupt in a timer fashion if you're wrapping Allegro around ROE.

I would suggest, wrap as much as possible from the start, I mean all of ROE and not only Nermal2 as a tout-court sub for the GFX engine....I would see this as the easiest route, sort of emulating the Hardware inasmuch MAME did with the original HW of the games (sort of, eh).

Otherwise I see really no end on your quest to update the universe in a totally asynchronous mode...w/o resorting to threads.

The question is...has it really to be asychronous?

I mean the original game did "pause" the main loops while executing the interrupt code (IIRC no multithreading...no?).

If you have to pause the main game anyway...then for now you could manage to go synchronous...or am I missing something obvious?

It is unlikely that Google shares your distaste for capitalism. - Derezo
If one had the eternity of time, one would do things later. - Johan Halmén

Edgar Reynaldo
Major Reynaldo
May 2007
avatar

Audric
Member #907
January 2001

You may already have found it in the docs, but allegro provides a syntax for waiting the other thread to stop: al_join_thread();. This sets a specific flag in the thread, so the thread's function can loop on while (!al_get_thread_should_stop(me)) {}

Having converted some large DOS program, I have the following advice:

About int size, I would do a global search-and-replace of the word "int" to "short", then fix back any nonsensical expressions like "long short" to "long int".

If the code makes use of structs, you may find a problem that newer compilers align the members by default (ex: ints starting at addresses multiple of 4 / 8, for fastest access). If the code makes assumptions about relative member position or struct size, you will get some crazy memory mismatches, or big misunderstanding when reading game files. Fortunately you can use #pragma pack(1) or __attribute__((packed)) to force the same tight packing as the original.

Original code / compiler may assume that chars are unsigned by default. It's normally not a problem for actual strings and standard library, but code like this becomes an endless loop when char is signed:
char i; for(i=0;i<200;i++) {} // 127+1 wraps back to -128
Compiler warnings may hint at this, telling you that a condition is suspiciously always true or false.
If you have this situation, rather than replace each char by the verbose "unsigned char" , there should be a compiler option like -funsigned-char.

Edgar Reynaldo
Major Reynaldo
May 2007
avatar

What are you suggesting he do then? Start and stop the thread running his global update? That doesn't solve synchronization or pausing.

It could work like :

Thread M runs main and listens to timer, calling 'interrupt' every 55MS.

InterruptA:
   if (ev.type == ALLEGRO_EVENT_TIMER) {
      if (interrupt_running) {
         paused = 1;
         while (!pause_acknowledge) {yield();}
         GlobalUpdate();
         paused = 0;
      }
   }

Thread B runs the program loop, but each loop now has to have a yield section.

ThreadB:

while (program_sub_loop) {
   if (paused) {
      pause_acknowledge = 1;
      while (paused) {yield();}
      pause_acknowledge = 0;
   }

   /// Process as normal here
}

I'm sorry but I think you're going to have to get your hands dirty and dig around in the guts of your program a bit.

Audric
Member #907
January 2001

I wasn't replying to the most recent discussion about concurrency, the blog mentions this issue:

Quote:

I will need a mechanism to kill the thread when the game is exited. I've discovered that if I kill my game before the thread, I'm stuck with a zombie process that's super hard to kill.

and I didn't see this part addressed so far.

pkrcel
Member #14,001
February 2012

I'm sorry but I think you're going to have to get your hands dirty and dig around in the guts of your program a bit.

I guess that's a given, even with the thread "workaround" afterall.

It is unlikely that Google shares your distaste for capitalism. - Derezo
If one had the eternity of time, one would do things later. - Johan Halmén

nshade
Member #4,372
February 2004

The thread control is there now. I am implementing the quit routines today and it will flag the thread to return and die.

Go to: