2
\$\begingroup\$

Learning & Implementing an ECS system for my game, I'm wondering how I am supposed to parallelize the systems on multiple threads. Different systems can't run at the same time (unless they operate on completely different set of components), so I guess the most common option is to run each system sequentially and parallelize the iteration they do on components, in a parallel_for fashion. The problem with this is that parallel_for assumes each iteration is self contained, which is not always true. In case an entity needs to access the component of another entity, and maybe modify it (very common in gameplay programming), we cannot really run a parallel_for as often we don't know what entities will want to access (through the system) what other entities.

How is this handled in ECS?

\$\endgroup\$
3
  • 1
    \$\begingroup\$ Both the approaches you describe are used. Use the one that works for you, or mix them if that works for you. \$\endgroup\$
    – DMGregory
    Feb 28 at 17:15
  • 2
    \$\begingroup\$ You may be interested in past Q&A on this topic, including Reading and writing components from multiple systems in ECS and Each system on a different thread or thread pool?. You might also want to look at how it's handled in Unity's DOTS approach, where each job has to explicitly state what component buffers it reads and what component buffers it can mutate, allowing the scheduler to overlap jobs that don't mutate data being read by other jobs. \$\endgroup\$
    – DMGregory
    Feb 28 at 17:20
  • \$\begingroup\$ Thanks for the useful resources :) \$\endgroup\$
    – yggdrasil
    Mar 1 at 1:42
2
\$\begingroup\$

Pipelines

The simplest implementation of ECS will run systems sequentially one after the other. Perhaps you are familiar with the idea of fixing your timestep. Simply running systems sequentially in a predefined order blows past that concept.

The solution is to have pipelines. Then you can have a pipeline for physics, one pipeline for rendering, and another for input, for example. Or as many as you would want. Each pipeline has a different configuration. The first criteria is, of course, how often to run the pipeline.

My suggestion is have pipelines run sequentially, one after the other. Then you can run the systems in the current pipeline concurrently. That way you know that only the systems of the current pipeline are running. For instance, the systems in the rendering pipeline know that the systems in the physics pipeline will not move entities (update their position components) because that is a different pipeline, and pipelines run sequentially.

With that said, if you need to, for example, split the physics pipeline in two, to make sure one stage is completed before running the other, that is ok. Furthermore if a pipeline ends up having only one system, that is ok too. Use this to ensure that you will not have systems stepping on each other.


Read-Write Locks

You can use pipelines to have control over which systems can run concurrently, which allows you to prevent running in parallel systems that may step on each other. However, that can be error prone, as it requires to design you pipelines carefully. There is, of course, another way to ensure this: locks.

As you might know, having system declare what kinds of component they need on initialization is a good idea. It allows some optimizations. For example, if a system wants entities that have two kinds of component, we have a couple options:

  • We can go over every entity and checking if they have the components, every time the system runs.
  • We can keep a list of the entities that match the criteria, and every time one of those components is added or removed we can update that list.

The latter is only possible if the system declares the kinds of components it wants before hand. Plus, multiple systems that want the same set of component kinds can share list. You might also be interested in the concept of Archetypes. Although I prefer to think about these as queries.

Ok, extend that idea to include not only which kinds of components the system will read, but also which kinds of components the system will write (we can assume that write access implies read access). And now you can consider read-write locks on kinds of components:

  • If a system that wants to write a kind of component is running, you cannot run any systems that want to read or write that same kind of component at the same time.
  • Otherwise, multiple systems that want to read (but not write) the same kind of component can run at the same time. Any system that wants to write that kind of component must wait.

On running a system for multiple entities in parallel

A system runs over the components sequentially. One of the talking points of ECS is that they store components contiguously in memory, and to really take advantage of that you want to run over them orderly.

However, that does not mean that running a system in parallel is imposible. Yet, in order to do so we need to consider whether or not that introduces race conditions. A solution is to have a flag indicating if a system should run in parallel or sequentially.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.