Allegro.cc - Online Community

Allegro.cc Forums » Programming Questions » Threading Problems

Credits go to bamccaig for helping out!
This thread is locked; no one can reply to it. rss feed Print
Threading Problems
RPG Hacker
Member #12,492
January 2011
avatar

I'm having a little problem, which, I think, is related to threads. First I'll explain the situation.

I have split my game into two threads: The logic thread (running at 25 FPS) and the display thread (running at any FPS, 60 with my current settings). It'll only be a simple Mario-styled platformer, so two threads should be enough. Since I love internationalization I included a language feature, which I wanted to be powerful, yet easy to use and player-friendly. Language files called "lang[name].lng" are saved in the language folder. The game then reads strings from the currently selected language file. Anyways, I also wanted to have a proper language selection screen that recognizes all valid language files in the folder, so that the player doesn't have to mess around with the config file. The screen looks somewhat like this:

{"name":"langscreen1.png","src":"\/\/djungxnpq2nug.cloudfront.net\/image\/cache\/7\/7\/7797045a3e2a3cb983ada6cdf4da0cb6.png","w":320,"h":240,"tn":"\/\/djungxnpq2nug.cloudfront.net\/image\/cache\/7\/7\/7797045a3e2a3cb983ada6cdf4da0cb6"}langscreen1.png

To make the selection as fast as possible (while still keeping it player-friendly and easy to program) only the graphics (flag, name) of the currently selected language file are loaded. The pathes are read from the language files themselfs. This means: Whenever you press left or right, the graphics currently in RAM are cleared and the new graphics are loaded. This happens in the logic thread. Since I obviously also need the graphics for the display thread I'm using mutexes. Now for the problem: In some rare cases when I press left or right and the new graphics are supposed to load some of the graphics stay black or keep their magic pink. So instead of
{"name":"langscreen3.png","src":"\/\/djungxnpq2nug.cloudfront.net\/image\/cache\/e\/9\/e9f3e76ea90d7f77a1f06d2877ac8e5f.png","w":320,"h":240,"tn":"\/\/djungxnpq2nug.cloudfront.net\/image\/cache\/e\/9\/e9f3e76ea90d7f77a1f06d2877ac8e5f"}langscreen3.png
I get
{"name":"langscreen2.png","src":"\/\/djungxnpq2nug.cloudfront.net\/image\/cache\/9\/4\/94cc8a060c5d791d06eb2716e683dac0.png","w":320,"h":240,"tn":"\/\/djungxnpq2nug.cloudfront.net\/image\/cache\/9\/4\/94cc8a060c5d791d06eb2716e683dac0"}langscreen2.png
Since this error stays until I press left/right again I suppose it can only be happening while loading the graphics. I'm also pretty sure it's a threading related problem, since I can't think of any other reason for this. Here are some code extracts with the concerning functions.

Loading new graphics:

#SelectExpand
1/* Load language graphics */ 2void LoadLangGFX(ALLEGRO_BITMAP *&LangFlagGFX, ALLEGRO_BITMAP *&LangNameGFX, ALLEGRO_BITMAP *&LangArrowGFX, char *Language) 3{ 4 /* Destroy old graphics */ 5 if (LangFlagGFX != NULL) 6 { 7 al_destroy_bitmap(LangFlagGFX); 8 LangFlagGFX = NULL; 9 } 10 if (LangNameGFX != NULL) 11 { 12 al_destroy_bitmap(LangNameGFX); 13 LangNameGFX = NULL; 14 } 15 16 /* Load arrow graphic */ 17 if (LangArrowGFX == NULL) 18 { 19 LangArrowGFX = al_load_bitmap("graphics\\RPG_Hacker-Lang_Arrow_Left.png"); 20 if (LangArrowGFX == NULL) 21 Shutdown(10, "graphics\\RPG_Hacker-Lang_Arrow_Left.png"); 22 } 23 24 char *FilePath = NULL; // Path to graphic to be loaded 25 26 /* Load flag graphic */ 27 FilePath = GetLangString(Language, "icon"); 28 if (FilePath != NULL) 29 { 30 LangFlagGFX = al_load_bitmap(FilePath); 31 delete FilePath; 32 } 33 else 34 LangFlagGFX = NULL; 35 36 if (LangFlagGFX == NULL) 37 { 38 LangFlagGFX = al_load_bitmap("graphics\\RPG_Hacker-Flag_XX.png"); 39 if (LangFlagGFX == NULL) 40 Shutdown(10, "graphics\\RPG_Hacker-Flag_XX.png"); 41 } 42 43 /* Load name graphic */ 44 FilePath = GetLangString(Language, "namegfx"); 45 if (FilePath != NULL) 46 { 47 LangNameGFX = al_load_bitmap(FilePath); 48 delete FilePath; 49 } 50 else 51 LangNameGFX = NULL; 52 53 if (LangNameGFX == NULL) 54 { 55 LangNameGFX = al_load_bitmap("graphics\\RPG_Hacker-Name_XX.png"); 56 if (LangNameGFX == NULL) 57 Shutdown(10, "graphics\\RPG_Hacker-Name_XX.png"); 58 } 59 60 /* Convert transparency masks */ 61 if (LangArrowGFX != NULL) 62 al_convert_mask_to_alpha(LangArrowGFX, al_map_rgb(255, 0, 255)); 63 if (LangFlagGFX != NULL) 64 al_convert_mask_to_alpha(LangFlagGFX, al_map_rgb(255, 0, 255)); 65 if (LangNameGFX != NULL) 66 al_convert_mask_to_alpha(LangNameGFX, al_map_rgb(255, 0, 255)); 67}

Removing old graphics:

#SelectExpand
1/* Destroy language graphics in RAM */ 2void DestroyLangGFX(ALLEGRO_BITMAP *&LangFlagGFX, ALLEGRO_BITMAP *&LangNameGFX, ALLEGRO_BITMAP *&LangArrowGFX) 3{ 4 if (LangFlagGFX != NULL) 5 { 6 al_destroy_bitmap(LangFlagGFX); 7 LangFlagGFX = NULL; 8 } 9 if (LangNameGFX != NULL) 10 { 11 al_destroy_bitmap(LangNameGFX); 12 LangNameGFX = NULL; 13 } 14 if (LangArrowGFX != NULL) 15 { 16 al_destroy_bitmap(LangArrowGFX); 17 LangArrowGFX = NULL; 18 } 19}

Jump to next language file when pressing right (it's the same for left except for the first line).

#SelectExpand
1[...] 2else if (CursorKey(RightKey)) 3 { 4 /* Find next language */ 5 LangNext(Language); 6 7 al_lock_mutex(LangMutex); 8 DestroyLangGFX(LangFlagGFX, LangNameGFX, LangArrowGFX); 9 LoadLangGFX(LangFlagGFX, LangNameGFX, LangArrowGFX, Language); 10 al_unlock_mutex(LangMutex); 11 al_rest(0); 12 13 LangArrowTimer = 0; // For blinking arrows 14 al_play_sample(SoundEffect[0], 1.0, 0.0, 1.0, ALLEGRO_PLAYMODE_ONCE, NULL); 15 16 } 17[...]

Display thread (only the part, where the concerning graphics are displayed):

#SelectExpand
1[...] 2 al_lock_mutex(LangMutex); 3 al_lock_mutex(DisplayMutex); // This mutex protects the ALLEGRO_DISPLAY and probably isn't necessary 4 5 al_set_target_bitmap(Backbuffer); 6 7 if (LangFlagGFX != NULL) 8 al_draw_bitmap( 9 LangFlagGFX, (BackbufferWidth() / 2) - (al_get_bitmap_width(LangFlagGFX) / 2), 10 (GameHeight / 2) - (al_get_bitmap_height(LangFlagGFX) / 2) - 5, 0); 11 12 if (LangNameGFX != NULL) 13 al_draw_bitmap( 14 LangNameGFX, (BackbufferWidth() / 2) - (al_get_bitmap_width(LangNameGFX) / 2), 15 (GameHeight / 2) - (al_get_bitmap_height(LangArrowGFX) / 2) + 16 (al_get_bitmap_height(LangFlagGFX) / 2) + 5, 0); 17 18 if (LangArrowTimer >= FPS / 2 && LangArrowTimer <= FPS && LangArrowGFX != NULL) 19 { 20 al_draw_bitmap( 21 LangArrowGFX, (BackbufferWidth() / 2) - (al_get_bitmap_width(LangArrowGFX) / 2) - 20, 22 (GameHeight / 2) - (al_get_bitmap_height(LangArrowGFX) / 2) - 5, 0); 23 al_draw_bitmap( 24 LangArrowGFX, (BackbufferWidth() / 2) - (al_get_bitmap_width(LangArrowGFX) / 2) + 20, 25 (GameHeight / 2) - (al_get_bitmap_height(LangArrowGFX) / 2) - 5, ALLEGRO_FLIP_HORIZONTAL); 26 } 27 28 al_unlock_mutex(DisplayMutex); 29 al_unlock_mutex(LangMutex); 30 al_rest(0); 31[...]

Any ideas?

AMCerasoli
Member #11,955
May 2010
avatar

The logic thread (running at 25 FPS) and the display thread (running at any FPS, 60 with my current settings).

Why to run the display thread faster than the logic thread? you'll be redrawing the same all the time... Most of the time what it most CPU and GPU takes is the drawing process.

Quote:

It'll only be a simple Mario-styled platformer, so two threads should be enough.

Actually just one thread would be more than enough.

Quote:

This means: Whenever you press left or right, the graphics currently in RAM are cleared and the new graphics are loaded.

I wouldn't do that if I were you, remember that loading graphics is very slow, depending in the size of your image you can easily notice the screen getting stuck if you have another animation running. You say this happens in the logic thread, and what's the logic thread? the one that initialize allegro and create a display or another thread?. Because if it's another thread you would be loading the image as a memory bitmap and that "mode" is very slow when drawing.

Quote:

Since I obviously also need the graphics for the display thread I'm using mutexes.

What?? :o are you drawing in both threads?

LoadLangGFX(ALLEGRO_BITMAP *&LangFlagGFX

Man what's that? `*&` ? I have never seen that before.

I haven't see all your code but you should never be using a mutx to protect the display pointer... that sounds very crazy :P .

bamccaig
Member #7,536
July 2006
avatar

You should just load all flag images at once (or, store them all in one file; then only draw the appropriate portion at a time). File I/O is very slow by comparison to how fast the computer is running. Memory is very cheap these days. I have 8 billion bytes, ffs! Your few kB of images are not going to be noticed. :P This is a case of premature (and backwards) optimization. Don't worry about it. Load all of the flag images, let the user select his language, and optionally unload the flag images.

Edgar Reynaldo
Member #8,592
May 2007
avatar

Whenever you press left or right, the graphics currently in RAM are cleared and the new graphics are loaded. This happens in the logic thread.

Your logic thread doesn't have a display attached or associated with it, so neither do the graphics that are loaded in it. That may be the reason they are showing up blank. They will also be memory bitmaps, which are slow.

LoadLangGFX(ALLEGRO_BITMAP *&LangFlagGFX

Man what's that? `*&` ? I have never seem that before.

It is a reference to an ALLEGRO_BITMAP pointer. It works just like any other reference would.

RPG Hacker
Member #12,492
January 2011
avatar

Why to run the display thread faster than the logic thread? you'll be redrawing the same all the time... Most of the time what it most CPU and GPU takes is the drawing process.

I'm using interpolation, so the display thread can run at any FPS and still profit.

Quote:

You say this happens in the logic thread, and what's the logic thread? the one that initialize allegro and create a display or another thread?. Because if it's another thread you would be loading the image as a memory bitmap and that "mode" is very slow when drawing.

The logic thread is basically main().

Quote:

What?? :o are you drawing in both threads?

No. In the logic thread I only load the graphics and in the display thread I display them.

Quote:

Man what's that? `*&` ? I have never seen that before.

It's called "call by reference". Google it. In this case a pointer is passed by reference, therefore I have both, a * and a &.

Quote:

I haven't see all your code but you should never be using a mutx to protect the display pointer... that sounds very crazy :P .

Well, the logic/main thread creates and destroys the display (it has to in my case), so I guess it IS a shared variable, but I guess it probably won't ever lead to conflicts. I just started with threaded programming and wanted to make absolutely sure not to mess anything up, so I used mutexes in this case.

bamccaig said:

You should just load all flag images at once (or, store them all in one file; then only draw the appropriate portion at a time). File I/O is very slow by comparison to how fast the computer is running. Memory is very cheap these days. I have 8 billion bytes, ffs! Your few kB of images are not going to be noticed. :P This is a case of premature (and backwards) optimization. Don't worry about it. Load all of the flag images, let the user select his language, and optionally unload the flag images.

Problem is just that I wanted to keep the code as simple as possible and also make it recognize language files that have just been moved to the language folder. Then again I guess it's the better idea. I could make a linked list of structs that contain the language name and the pointers to the graphics. That would also make the whole file accessing a lot easier. I guess it isn't that bad if languages aren't recognized during run-time.

Your logic thread doesn't have a display attached or associated with it, so neither do the graphics that are loaded in it. That may be the reason they are showing up blank. They will also be memory bitmaps, which are slow.

I doubt this is the problem, since it happens so rarely. Also it's not always just blank graphics, sometimes the magic pink isn't converted to transparency. It also stays like that until I change the language. If it really was a problem with the display it would most likely only last for a frame or so and then be fixed. On top of that I'm not even drawing on the display directly. I'm drawing on a backbuffer I created myself in the logic thread, so if the image shows up only partially on the screen that means the error must have already been on the Backbuffer.

weapon_S
Member #7,859
October 2006
avatar

Why reload the arrows too? I'm thinking, but I can't really come up with intelligent remarks about how you use threads...[1] "Don't" perhaps?

If it really was a problem with the display it would most likely only last for a frame or so and then be fixed.

Nah, it would probably crash entirely :P You're doing something weird with the loading and I personally have no idea what would happen. Threads combined with loading GFX scare me.
Wait... How are you calling the display-thread? Which one is main?

References

  1. You could use threads "to make the selection as fast as possible (while still keeping it player-friendly and easy to program)", but I think that is off-topic. But I keep thinking in terms of the latter use of threads.
RPG Hacker
Member #12,492
January 2011
avatar

weapon_S said:

Why reload the arrows too?

I just didn't feel like writing two seperate functions for destroying the graphics, so I just made it reload the arrow graphics, too.

Quote:

Wait... How are you calling the display-thread? Which one is main?

What do you mean by "how"? Do you mean this?

#SelectExpand
1[...] 2 /* Create thread */ 3 DisplayThread = al_create_thread(DisplayGame, NULL); 4 al_start_thread(DisplayThread); 5 6 /* Run game */ 7 RunGame(); 8 al_join_thread(DisplayThread, NULL); 9[...]

This is from main(), so main() starts DisplayThread (which loops until you exit the game) and then goes into the logic loop with RunGame() (also loops until you exit). Nothing happens in DisplayThread until a few preparations in RunGame() are done, so it's OK to start it first here.

bamccaig
Member #7,536
July 2006
avatar

Problem is just that I wanted to keep the code as simple as possible and also make it recognize language files that have just been moved to the language folder. Then again I guess it's the better idea. I could make a linked list of structs that contain the language name and the pointers to the graphics. That would also make the whole file accessing a lot easier. I guess it isn't that bad if languages aren't recognized during run-time.

Your original solution (multiple threads and repeatedly loading/unloading) is the opposite of simple, which is exactly why you're having trouble. :) Take your own advice and make it simpler. You can still make graphics recognizable at run-time by waiting to load the flags until the last second, but then loading them all. Whenever the user gets to the flag screen (or just before, if you can predict it), load up all detected flag files. :) When they leave the flag screen destroy all of the flag bitmaps. :)

I just didn't feel like writing two seperate functions for destroying the graphics, so I just made it reload the arrow graphics, too.

That is a very bad sign. :P Get rid of that laziness.

RPG Hacker
Member #12,492
January 2011
avatar

bamccaig said:

Your original solution (multiple threads and repeatedly loading/unloading) is the opposite of simple, which is exactly why you're having trouble. :) Take your own advice and make it simpler. You can still make graphics recognizable at run-time by waiting to load the flags until the last second, but then loading them all. Whenever the user gets to the flag screen (or just before, if you can predict it), load up all detected flag files. :) When they leave the flag screen destroy all of the flag bitmaps. :)

I guess I'll do that. I just hope it'll really fix the bug.

Quote:

That is a very bad sign. :P Get rid of that laziness.

This is just for testing purposes, anyways. I will get rid of it once the selection screen is done.

Edgar Reynaldo
Member #8,592
May 2007
avatar

When I said the resources you loaded didn't have a display attached to them, I meant it. Displays and OpenGL/DX contexts are thread local in Allegro 5. This can lead to all kinds of bizarre behaviour when loading resources in a different thread than the one where you created your display. Search the forums if you don't believe me.

RPG Hacker
Member #12,492
January 2011
avatar

When I said the resources you loaded didn't have a display attached to them, I meant it. Displays and OpenGL/DX contexts are thread local in Allegro 5. This can lead to all kinds of bizarre behaviour when loading resources in a different thread than the one where you created your display. Search the forums if you don't believe me.

I believe you. The thing is just that - as stated above - in my game the logic thread does both: Create a display AND load graphics. The display thread only transfers the logic thread's graphics to the logic thread's display. I don't know if THAT causes any problems, but I don't really have another choice than to create the display in the logic thread because I'm reading window settings from the config file.

Anyways, I'm done rewriting my code now the way bamccaig suggested. I can't tell for sure if this fixed the bug since it only occured when loading graphics, which happens only a single time now. I'd have to restart the program hundreds of times to find out whether the bug is really fixed or just still pretty rare. Since I don't feel like that I'll just assume it's fixed for now. Thanks for helping out!

Go to: