Game engines such as Source and Unreal allow entities to implement interfaces that will be used for communication between the entities as well as various engine systems, such as the physics simulation. All of this code is usually executed in some total order. Implementations of these interfaces are permitted to do any kind of access to the game state, both reads and writes. While parallelizing such programs unmodified could perhaps be achieved with the use of transactional memory, with a few small changes it’s possible to achieve extensive parallelization using conventional means, all while keeping the reasoning habits we’re already used to almost unchanged.
// the skeleton
In order to enable parallelization, concurrent reads-writes must be avoided. Most interfaces can get away with just reading the game state and deferring the mutations until after all reads are done. An interface for mutating entities that I found to work well for me are per-entity queues of mutator functions. These functions are permitted to read and write the state of the entity they were enqueued for, as well as enqueue more mutations, but not read the state of any other entities.
// + per-entity mutator queues and mutator processing pass
To ensure determinism, these functions should be ordered in some stable manner. Sorting by the IDs of the entities that did the enqueueing works well, the order is exactly the same as what we would’ve gotten if we ran everything in-order.
// + sorting
We also need an extra queue to handle mutations of the game state bits that all entities share. An example of such a mutation is entity creation, which bumps the entity ID counter.
// + global mutation queue