3

Lately I've been working on a small personal project which is basically an Entity Component System framework with autoupdated Systems.

While I have a pretty good idea on the way the framework should work, because of lack of experience I am having trouble with actually keeping everything decoupled.

Some details on the framework:

Each entity is defined by it's components.

Systems are responsible for the actually modifying the entities by changing their components.

In order to improve locality of reference, instead of keeping each component in the appropriate entity, all components are stored in homogenous vectors and each entity keeps a list of indices to each vector.

Since each System modifies specific components, it should keep a list only of the entities with the corresponding components.

How I dealt with all of this until now was to have a ComponentManager, an EntityManager and a SystemManager. These classes however have very tight coupling with each other. The EntitManager needs to have access to the ComponentManager in order to handle the size of the the index lists and the mapping of each component type to them. It also needs access to actually add the components in the appropriate vector. Another coupling is between the EntityManager and the SystemManager. Whenever an entity is created, it needs to be added to the list of the appropriate Systems.

A general event bus would appear to help but I am not sure how to implement it without making it global.

How do I improve this design by removing coupling while still maintaining the system's functionality?

Deduplicator
  • 9,209
Veritas
  • 131

2 Answers2

3

How do I improve this design by removing coupling while still maintaining the system's functionality?

You don't go much into your functionality, so I'm going to make some guesses and go over the general approaches:

  • System -> Component - you say that the System only works with specific components (say, a physics system changing velocities), then why does the system care about entities at all? The system should be free to work with the components in relative isolation.
  • Entity -> Component - you mention that there's some manner of index lists between the entity and its component. This strikes me as odd. Entities should have references to their components (or vice versa) - even if that's as thin as a foreign key. By using indexes, you're exposing an implementation detail from the ComponentManager that is harming your coupling.
  • Entity -> System - again, if you have systems work with components, then there should be no tie here. An entity is created, which knows which components to make (or vice versa, the components are built and then aggregated/dependency resolved by the entity) - regardless of the system manipulating them.

Personally, the design I've seen work well is having the entity be a very, very thin layer that aggregates the components, acting as a context so that the components can play nice with one another. The outside world can then query and act on the components' public interface.

Telastyn
  • 110,259
2

From what I can understand, you have something like this:

enter image description here

And all this tight coupling between these Managers is strictly for optimization purposes (using indices to cut costs on the size of a pointer while gaining easy spatial locality using a vector, e.g.).

In this case, I'm going to suggest something bizarre.

enter image description here

The idea is to get everything you need from a slightly more monolithic EcsManager object consolidating all of these concerns. This might seem like a borderline violation of SRP, but when you have these manager designs so tightly coupled together, it might simplify the practical implementation and design quite a bit over 3 classes that are really tightly coupled together.

Effectively that's what you have to me with these three managers so tightly coupled together. They want to become one manager to rule them all. I think we can get away with this because the needs of an ECS framework won't have varying requirements -- what an ECS needs is well-understood.

You could still keep these "sub-managers" if that simplifies the implementation of EcsManager a bit, though it might be even simpler if you just did this:

enter image description here

A general event bus would appear to help but I am not sure how to implement it without making it global.

For this part, I think it'd help both efficiency and simplicity to just use a lazy approach. For example, when new entities are added to a system, new components to an entity, or new component types of interest are added to a system, you can just mark the relevant systems as "dirty". Then when you get to a point where systems are processed, if they are dirty, they can retrieve the latest list of entities containing the components the system is interested in, and perhaps with the indices to the matching entities sorted on the fly (will improve spatial locality).

Your EcsManager then actually stores all the real, meaty data associated with the ECS framework. Your Entity and System and Component classes then just kind of turn into handles for convenience.

I think this is the easiest approach given the kind of design you are establishing, and also make achieving both thread safety and efficiency a little bit easier (though thread efficiency is tough here).