Allegro.cc - Online Community

Allegro.cc Forums » Programming Questions » Controlling threads

This thread is locked; no one can reply to it. rss feed Print
Controlling threads
nshade
Member #4,372
February 2004

So I have a thread that runs in a while() loop. I'm trying to control the thread using globals, bit I was wondering if there was a way to suspend/resume the thread externally.

I see there is al_start_thread() and al_set_thread_should_stop() - I'm not keen on destroying the thread, but if I did I'm assuming I'd have to do a al_create_thread() to bring a new instance online?

I know that within the thread, if I return, then it's killed, or is it just suspended? Just wondering how things are going on under the hood.

Edgar Reynaldo
Major Reynaldo
May 2007
avatar

nshade
Member #4,372
February 2004

I'm running into bigger problems :(

I was hoping I could control the thread with globals, but it turns out because the thread runs asynchronous to my main code it's screwing everything up

The problem is that my thread and the main program try and do a set_target_bitmap() on two different bitmaps and they are crashing into each other. I don't think a thread is going to work.. I actually need an interrupt

In the original game and DOS interrupt was called every 55 milliseconds and updated the game's global variables. I can't have the interrupt and main game running at the same time, so a thread isn't going to work.

I can't use an allegro event because the interrupt needs to fire at 55 millisecond intervals. I have no mechanism to "check" to see if times up. I need to program counter to jump out of my code.

I can't have the main program running while the interrupt is happening

I can't put this into a program loop because my game don't have one... It's a state machine.

I don't know what to do here...

Edgar Reynaldo
Major Reynaldo
May 2007
avatar

You need a mutex. That's what they were made for.

A caveat, you can only draw on a display if you are on the thread that created the display. Otherwise it won't work right, because of TLS.

When the 'interrupt' fires every 55 ms, lock the mutex and update the globals. When main wants to run, try to lock the mutex or just wait for a lock. Then run main code and then unlock. That way you can have both run in parallel, but synchronize the global access.

Audric
Member #907
January 2001

Sorry if my suggestion is naive, but what exactly is "spending time" in the different states of your state machine ? If it's always functions like sleep(t) or rest(t), you could replace all instances of them with your own sleep_with_interrupts(t), so that you don't need a second thread at all.
It would wait a total of t milliseconds, calling your "interrupt" function when it's time.

The implementation of this is by setting up a queue that ONLY contains 55ms timer events (Don't use the same queue as keyboard/mouse or whatever). You can then use al_wait_for_event_timed() or al_wait_for_event_until() on it, and it will sleep until the specified time has elapsed OR an event is found :
- If the delay has elapsed, you can return
- If an event is found, it means you've been waken up early: execute the interrupt() function once, then compute how much more time you have to sleep, and repeat.

(This very last part may need you to al_get_time() at the very beginning, and again after executing the interruption)

nshade
Member #4,372
February 2004

Augh! It's not working at all :(

Someone may need to explain to me what a mutex is so I can implement it properly. I had others tell me the same thing. Allegro defines it as some kind of struct. I don't understand how a strut can "protect" a function. How do I implement the mutex? How does it work? (How does one block a function with it?)

I have the logic in my function correct, but it's still screwing up.

The offending function is a blit function is causing all the fuss. I'll give you the long and short here...

void BitBlit(int d, int s, int dx, int dy, int sx, int sy, int w, int h)
{
  while (Drawlock) {} //do nothing if something is trying to draw 
  Drawlock = 1; // drawlock went to 0 so we are going to lock it back to we can draw now
  al_set_target_bitmap(tempbuf); //temp buffer for screen to screen blitting
  al_clear_to_color(al_map_rgba(0, 0, 0, 0));  //clear to clear
  al_draw_bitmap_region(bufmap[s], sx, sy, w, h, sx, sy, 0); //copy region to tempbuff
  al_set_target_bitmap(bufmap[d]); //set target to destination
  al_draw_bitmap_region(tempbuf, sx, sy, w, h, dx, dy, 0); //copy from tempbuf to the target buffer
  al_set_target_bitmap(al_get_backbuffer(DISPLAY)); //set backbuffer properly
  if (d == SCREEN) { updateScreen(); }  //if the target buffer was the virtaul display, copy that to the actual display and flip the page
  Drawlock = 0;  //unlock the the function to allow another process to draw.
}

The original game had the ability to copy bitmaps from the display into other buffers, or from the display to the display. This is something that Allegro can't do. I have a special bitmap called "SCREEN" and when that bitmap is ever updated, it is copied to the backbuffer and the the page is flipped to update the actual display.

It looks like when I set "Drawlock" to 1, it's being sometimes ignored by the other process and just draws anyway. Then it sometimes crashes on setting the target bitmap... or it was just put garbage on the screen, or write to the wring buffer.

About the program loop:

I don't have the ability to poll for an event because I do not have a main loop. The game was written in DOS with overlays. Every "screen" in the game was it's own .OVL that was copied into memory from the hard drive to a static location. When the ovl was loaded, the PC went to that memory address and started executing. When another OVL was needed, the old one was written over and that part took control. The means each OVL has, for all intents an purposes, it's own "loop", but the actual game "cycle" is handled by the interrupt because it's "always there". The interrupt does not do input because in DOS it ran in INT 1Ch, and the keyboard in INT 09h and you can't call in interrupt inside another interrupt.

I have long since merged all the overlays into one executable, but the program flow will go to each "section" of the game. Each part updates or gabs input on it's own terms, but some overlays don't do any input and some don't update the screen. The upshot. The overlays and the interrupt alter the game's state by changing global variables. Then each "part" of the game read the globals and updates accordingly, you know, whenever it needs to, not on any set schedule. There are no "ticks" or "sleeps" or "rests" It's just a state machine that asynchronously updates itself based on changes in the global variable area.

A note:
This game was originally developed on the Atari ST, which used a "zero page" that held important global variables that controlled the flow the whole game. It also had very little memory, which made the overlays needed. The interrupt was a vsync sync interrupt that fired on every screen update. When ported to the PC. the pile of globals were kept. The game overlays were still used, and the vsync interrupt was moved to the PC's 8253 timer chip, and an interrupt was attached to that.

I know this is screwy, but it's a dos game from 1991 and I took on the job by the original company to port it to modern systems. If I can figure out this one last thing. I'll have the game running end to end.

EDIT

I read some documentation on pthreads which have me a better understanding what a Mutex is.

My new code looks like this...

void BitBlit(int d, int s, int dx, int dy, int sx, int sy, int w, int h)
{
  al_lock_mutex(Drawlock); //ALLEGRO_MUTEX *Drawlock was set up earlier
  al_set_target_bitmap(tempbuf); //temp buffer for screen to screen blitting
  al_clear_to_color(al_map_rgba(0, 0, 0, 0));  //clear to clear
  al_draw_bitmap_region(bufmap[s], sx, sy, w, h, sx, sy, 0); //copy region to tempbuff
  al_set_target_bitmap(bufmap[d]); //set target to destination
  al_draw_bitmap_region(tempbuf, sx, sy, w, h, dx, dy, 0); //copy from tempbuf to the target buffer
  al_set_target_bitmap(al_get_backbuffer(DISPLAY)); //set backbuffer properly
  if (d == SCREEN) { updateScreen(); }  //if the target buffer was the virtual display, copy that to the actual display and flip the page
  al_unlock_mutex(Drawlock);  //unlock the the function to allow another process to draw.
}

However it didn't solve the problem and still randomly crashing/drawing on the wrong buffer. Just if a different way.. So I'm thinking it's not just the fact that both the thread and the main code are fighting over the same function. I do have a clue In my code, there are 42 times that al_change_target_bitmap() is called. It's not that one blit function that is taking over the other. I think the problem is that while in the blit function, al_change_target_bitmap() is being ran elsewhere and taking the the target bitmap away... Maybe?

To address some things said..

When the 'interrupt' fires every 55 ms, lock the mutex and update the globals. When main wants to run, try to lock the mutex or just wait for a lock.
The problem is that the thread is calling the blit function above that's causing the crashes. If I disabled the calls to the function... everything seems fine.

A caveat, you can only draw on a display if you are on the thread that created the display. Otherwise it won't work right, because of TLS.
I'm not sure what you mean by this. What is TLS? I think it's my target getting taken. Unless you mean I can't call a function that has updateScreen() {This copies a bitmap representing the screen to the backbuffer and does a al_pageflip() in it from the thread.} This means I can't have a thread draw.. well, anything, which defeats the whole purpose of the thread. :(

I would be more than happy to pastebin the code of the .c file I'm working on if anyone wants to take a look at it. It's I can point out the two functions that I'm zeroed in on.

Audric
Member #907
January 2001

nshade said:

I do not have a main loop. (...) each OVL has, for all intents an purposes, it's own "loop"

I encountered exactly the same structure in Grafx2 when porting it from MS-DOS, and I can vouch that you'll have an easier time forgetting the thread idea, and instead, insert an explicit check_interrupt() at the beginning (or end) of each OVL's loop. Even if you end up having 50 of them, it's a completely trivial change, and won't change the existing structure/logic that you're familiar with.
Also, this gives you an opportunity to include al_rest(0.000001), so that your program no longer takes 100% CPU.

Dizzy Egg
Member #10,824
March 2009
avatar

Try releasing the displays etc at the end of the function, al_set_target(NULL)

----------------------------------------------------
Please check out my songs:
https://soundcloud.com/dont-rob-the-machina

Edgar Reynaldo
Major Reynaldo
May 2007
avatar

You want to create a thread that creates an event queue and a timer for your interrupt. Then every time it gets a timer event, it locks and updates your global variables as per the interrupt.

Main however attempts to lock the mutex every time it wants to draw. If it is already locked, the update is occurring, so wait until we get the lock. If we don't have the lock, it's safe to draw now, but lock the mutex anyway so our interrupt thread knows not to trample on our global variables.

Go to: