6

I have been trying to wrap my head around how ECS works when there are components which are shared or dependent. I've read numerous articles on ECS and can't seem to find a definitive answer to this.

Assume the following scenario:

I have an entity which has a ModelComponent (or MeshComponent), a PositionComponent and a ParticlesComponent (or EmitterComponent).

The ModelRenderSystem needs both the ModelComponent and the PositionComponent.

The ParticleRenderSystem needs ParticlesComponent and the PositionComponent.

In the ModelRenderSystem, for cache efficiency / locality, I would like run through all the ModelComponents which are in a compact array and render them, however for each model I need to pull the PositionComponent. I haven't even started thinking about how to deal with the textures, shaders etc for each model (which will definitely blow the cache).

A similar issue with the ParticleRenderSystem.. I need both the ParticlesComponent as well as the PositionComponent, and I want to be able to run through all ParticlesComponents in a cache efficient / friendly manner.

I considered having ModelComponent and ParticlesComponent each having their own position, but they will need to be synched every time the models position changes (imagine a particle effect on a character). This adds another entity or component which needs to track and synch components or values (and potentially negates any cache efficiency).

How does everyone else handle these kinds of dependency issues?

Driv
  • 123
  • 8
  • 1
    I've started work on an ECS implementation, and immediately came up with the same question as this (and someone odd to see only a single answer with (now) 3 upvotes). 2 years on from the original question, did you come up with a satisfactory solution? It seems like you need to choose between redundant duplicate data (and sync issues), or worse CPU cache performance. – Mark Ingram Jan 07 '20 at 22:23

1 Answers1

3

One way to reduce the complexity could be to invert flow of data.

Consider that your ModelRenderSystem has a listener callback that allows the entity framework to inform it that an entity has been added to the simulation that contains both a position and model component. During this callback, the system could register a callback on the position component or the system that owns that component allowing the ModelRenderSystem to be informed when that position object changes.

As the change events from the position changes come in, the ModelRenderSystem can queue up a list of modifications it must replicate during its update phase and then during update, its really a simple lookup each modifications model and set the position to the value in the event.

The benefit is that per frame, you're only ever replicating position changes that actually changed during the frame and you minimize lookups needed to replicate the data. While the update of the position propagates to various systems of interest may not be as cache friendly, the gains you observe otherwise out weigh that.

Lastly, don't forget that systems do not necessarily need to iterate over the components proper. The components in your entity system exist to allow you to toggle plug-able behavior easily. The systems can always manage a more cache friendly data structure and using the above callback approach, allows you to do that and manage data replication super easily with minimal coupling.

Naros
  • 19,928
  • 3
  • 41
  • 71
  • Thanks. Some great advice. – Driv Dec 11 '17 at 05:04
  • I am curious. In messaging technique, there are 2 links `Position component` -> `message` -> `Render component`. It is less likely that contiguous-index of `position` will be placed in the same order as `render` e.g. `position[0]`->`message`->`render[1234]`, `position[1]`->`message`->`render[98]`. Thus, in every message implementation, it is impossible to make BOTH links be cache friendly, right? I believe messaging is faster than OP's, but can it be even better? – cppBeginner Jan 25 '18 at 02:11
  • It's not impossible. I could see having a message queue that is sorted based on entity-id ascending and your render component array sorted in the same fashion might be quite cache friendly as you can take advantage of memory prefetching as you iterate the two arrays. But again, the idea behind messaging approaches is that per-frame, you are dealing with a tiny subset entries to update, therefore the impact of those less cache friendly operations would be negligible compared to other potential bulk update operations you may do for other non-message-isk type loops. – Naros Jan 25 '18 at 19:53