|
Threading Problems |
RPG Hacker
Member #12,492
January 2011
|
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"} 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 Loading new graphics: 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: 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). 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): 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
|
RPG Hacker said: 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?? 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 .
|
bamccaig
Member #7,536
July 2006
|
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. 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. -- acc.js | al4anim - Allegro 4 Animation library | Allegro 5 VS/NuGet Guide | Allegro.cc Mockup | Allegro.cc <code> Tag | Allegro 4 Timer Example (w/ Semaphores) | Allegro 5 "Winpkg" (MSVC readme) | Bambot | Blog | C++ STL Container Flowchart | Castopulence Software | Check Return Values | Derail? | Is This A Discussion? Flow Chart | Filesystem Hierarchy Standard | Clean Code Talks - Global State and Singletons | How To Use Header Files | GNU/Linux (Debian, Fedora, Gentoo) | rot (rot13, rot47, rotN) | Streaming |
Edgar Reynaldo
Major Reynaldo
May 2007
|
RPG Hacker said: 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. AMCerasoli said: 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. My Website! | EAGLE GUI Library Demos | My Deviant Art Gallery | Spiraloid Preview | A4 FontMaker | Skyline! (Missile Defense) Eagle and Allegro 5 binaries | Older Allegro 4 and 5 binaries | Allegro 5 compile guide |
RPG Hacker
Member #12,492
January 2011
|
AMCerasoli said: 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?? 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 . 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. 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. Edgar Reynaldo said: 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
|
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? RPG Hacker said: 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 You're doing something weird with the loading and I personally have no idea what would happen. Threads combined with loading GFX scare me. References
|
RPG Hacker
Member #12,492
January 2011
|
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? 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
|
RPG Hacker said: 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. RPG Hacker said: 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. Get rid of that laziness. -- acc.js | al4anim - Allegro 4 Animation library | Allegro 5 VS/NuGet Guide | Allegro.cc Mockup | Allegro.cc <code> Tag | Allegro 4 Timer Example (w/ Semaphores) | Allegro 5 "Winpkg" (MSVC readme) | Bambot | Blog | C++ STL Container Flowchart | Castopulence Software | Check Return Values | Derail? | Is This A Discussion? Flow Chart | Filesystem Hierarchy Standard | Clean Code Talks - Global State and Singletons | How To Use Header Files | GNU/Linux (Debian, Fedora, Gentoo) | rot (rot13, rot47, rotN) | Streaming |
RPG Hacker
Member #12,492
January 2011
|
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. 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
Major Reynaldo
May 2007
|
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. My Website! | EAGLE GUI Library Demos | My Deviant Art Gallery | Spiraloid Preview | A4 FontMaker | Skyline! (Missile Defense) Eagle and Allegro 5 binaries | Older Allegro 4 and 5 binaries | Allegro 5 compile guide |
RPG Hacker
Member #12,492
January 2011
|
Edgar Reynaldo said: 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!
|
|