Engine Core Design - Part 1
Jul 21, 2019 • openjge-dev
I’ve spent the past week planning in detail how the Core module is going to function, and wow, it’s taken quite some time. I started with the engine loop, since it seemed like the logical first step and I thought it would be fairly easy to design, but I soon realised how wrong I was. Before I knew it, I was thinking about events, commands, queues, components, and the much dreaded concept of multithreading! Well, the engine loop itself wasn’t that hard to figure out (I already used the same design in my previous rendering engine) but each of the loop’s phases required me to think about how it uses other classes in the module and drives all other systems in the engine. Most of the hard work has been summarized in the Engine Core Documentation, but I just wanted to talk about some of the struggles I faced in figuring things out, ‘cause after all, I’m no genius and this probably took me considerately longer to put together than most competent developers :p. But it’s progress nonetheless! Anyway, I already had a fairly good idea of how both events, commands, and updates would function, which I covered in the Engine Overview Documentation. But what I didn’t put much consideration into when writing that was multithreading. So when it came time to design the Core module in detail, this became a bit of a sticking point. Here’s what I came up with when I first started putting a multithreading diagram together:
This is how I was originally planning on doing multithreading within the engine, but after thinking about it a bit more, it became apparent that it’s far from the most efficient method of handling execution across multiple threads and introduces a bunch of problems. For example, distributing component updates across multiple threads at the state level means that we will only be using as many cores as there are states in one of the three game loop phases (Input, Update, or Render). And most of the time, there won’t be more than one or two different states that need to be updated, leaving all the remaining cores wasted. As you can see, this implementation scales terribly. The biggest problem with this design though is that it doesn’t allow for component updates that manipulate entity state to be run in parallel, defeating the purpose of being multithreaded in the first place. Hold up, doesn’t double buffering solve that problem? Not quite - double buffering only works when there is a single component manipulating entity state and the rest of the components are reading it. Consider an input component and a physics component of the same entity being updated in parallel. The input component updates the entity’s speed vector based on an input event, while the physics component updates that same vector based on the results of a collision. Since both updates occur at the same time, the physics component reads an older value of the speed vector from the entity’s front buffer, calculating the new value post collision. Now, it puts this newly calculated value in the entity’s back buffer. However, the input component also updated the entity’s speed vector, which has just been overwritten by the physics component. Thus, the game loop now becomes non-deterministic.
It became clear that this method of multithreading wouldn’t work, and that to preserve deterministic behaviour in the engine loop, component updates would need to occur sequentially.
After a bit more research, I came across the concept of data-parallel threading, and realised that it would better suit concurrent component updates compared to the task-parallel approach I’d previously been thinking of taking. I found this discussion board to be very helpful in covering correct multithreading implementations, and this article for providing a good explanation of data-parallelism compared to task-parallelism. I could then come up with a design that would maintain component update order within entities, yet allowed for the utilization of all available threads during a state update. It looks like so:
Problem solved! Or at least I hope it is and I’m not overlooking anything. This has been a bit of a longer entry than previous ones I’ve written, but I thought it would be interesting to cover the development with a little more depth. And now that I’ve got these design plans together, I think I can finally start writing the Core classes. To be continued…
- Older
- Newer