Progress Update #32
Multithreading Round 2
My main goal this sprint was to get a smoother and more readable save and load process. Before this week I had random variables in the game mode that would track the loading and first time generation of planets. This made the code very hard to understand and was in dire need of a rewrite.
Loading the Game
I started with the loading side of things as the save function only took a few milliseconds and didn't need as much help as the loading side of things did. My goal here included a few key parts:
- The load function takes place on a different thread so that the animation of the load screen stays fluid.
- At certain points, the load function should increment an enum as to give a sign of progress.
- Have all terrestrial planets generated before removing the loading widget from the screen.
- Make sure that all data is still being applied correctly.
To put the load function on a different thread I tried the same method as the terrain face generation, that is to use AsyncTask() on any thread and call back to the game thread when done. When I tried this though I noticed that much of the functionality of the load function (e.g. spawning an actor, creating an asset, changing an actor's transform, etc.) would actually need to be run on the game thread otherwise it would crash. For this I tried to do multiple callbacks but it didn't have the effect I wanted. When I tried the async task on "any thread" it would execute the load but would also freeze the game thread and thus the animation would stop, and the load progress would not be updated. Until the thread finished and the load widget would unceremoniously disappear.
I tried again on the "local queue" thread, that let the animation keep playing and the game thread kept ticking, but the code was never being run. I set breakpoints and everything I could think of and the load function was not being executed.
After two days of this I finally threw in the towel on that approach as the sprint wasn't slowing down. I found an article from Ben UI showing the use of a class called FRunnable (I also found a video showing the same thing). In theory this is supposed to pop open an new thread, run some code, and stop when asked or the task is finished. I say "in theory" because it just didn't want to work with me. No matter what I tried I could not get it to play nice. Sometimes it crashed, sometimes it would get stuck, and sometimes it didn't even run at all.
At some point I realized that it was going to have the same problem as the first attempt with calling back to the game thread and all that. It was here that I put bullet point 2 on the back burner once again and moved on out of necessity. I could do some very minor async work by calling Save/LoadFromSlotAsync() instead of Save/LoadFromSlot(). This keeps all the big calculations and memory reads on the main thread and only offloads the read/write to file to a different thread which in the end is barely noticeable :/
Moving on to bullet point 3. I had a solution for this already but, as I mentioned, it was on the verge of unreadable. I cleaned it up by offloading all that code to a separate namespace. Although still in the same files, it is way easier to understand. The generation of the terrestrial planets is the last stage of loading, and when that is reached what will happen is as follows:
- A planet's OnPlanetGenerated delegate is assigned to function NewGeneratedPlanet().
- That Planet is generated.
- When generation is finished, NewGeneratedPlanet() checks if all planets have been generated.
- If all planets are ready to go, the game is unpaused.
- Otherwise another planet will be generated.
That's basically it. This week was a lot of learning and trial & error. I am not quite satisfied with the state of saving/loading and so I am now a day behind schedule as I did not get a chance to do a UI facelift as I had planned to. I have also found a video by Unreal on async loading screens so I may give it a try but no promises.
Comments
Post a Comment