Multithreading. The answer to a common problem with sizable games/engines: Being able to keep up with the huge amounts of processing that has to be performed in a modern game. Or is it really the answer?
Depending on the scale and size of the game you’re aiming to make, multithreading might be something you’re able to ignore completely. For my own game however, this is not the case. I set quite a lofty goal when it came to my own game: Being able to generate marching cube-based voxel objects procedurally, at run-time.
You may think “Minecraft already does this with cubes!” and you’d be correct. But I wanted to go as far as generating entire ships, stations and terrains to fight on, all at run-time. For the most part I’ve managed to pull it off, thanks to implementing multi-threading at an early stage during the development of the voxel part of the game engine.
However, the rest of the game engine suffered due to my laser focus on voxelization which as you would expect, eventually came back to bite me.
Generating voxel data takes no time at all, but uploading the mesh to the GPU and rendering it is where those big bitey teeth appeared. Rendering and updating being in the same thread was causing the renderer to be held back by everything else needing to update too. This badly dragged down the frame rate.
I finally realized what kind of mess I was in after reading an article about game engine architecture by Michael Kissner. He describes a system for sending messages between different sub-systems of a game engine. A kind of internal network only used by the engine. So this is where I spent the last two weeks of development, implementing a message system and separating the various different systems of the engine into separate thread/modules.
Surprisingly, it’s going quite well. I originally thought it would turn into an utter mess of old, partly refactored code mixed with multithreaded messaging code, but that isn’t the case (so far!). Here’s my attempt to demonstrate the structure I ended up with:
Each sub-system has its own thread. Each of those request frame-time from a scheduler which acts like a policeman to determine whether there is enough frame-time left for a thread to run (or run several times). This avoids the situation for example, where one thread might be updating fifty times faster than the rest of the reads, which would allow it to spam messages to all the other systems, creating a backlog of messages that the other threads cannot handle fast enough. This would eventually cause the program to run out of memory as the message backlog grows in size.
So far it’s been working well in the tests. At the end of it I’m expecting a significant FPS improvement and much more efficient usage of CPU time. But I haven’t tried to render a full scene or anything complex yet. For now though, the refactoring must go on!