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?

1
  • In that case keep the GD.SE questions as is and re-word this one to make it generic (start with the subject).
    – Den
    Commented Aug 27, 2014 at 15:33

2 Answers 2

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.

5
  • Systems can't act on isolate components because they may require more than one component to register an entity. Indices are necessary if I want to keep the components in contiguous memory. Pointers don't work because of possible reallocations and references don't allow for a common interface. The user doesn't need to know about the implementation, a Mapper class is responsible for retrieving the appropriate component by accessing the entity.
    – Veritas
    Commented Aug 27, 2014 at 18:41
  • @veritas - Systems can act on bundles of components then. If your components aren't somewhat isolated, then I question using that architecture. Having pointers (or references) to locations in an array is totally viable - even if it makes re-sizing the things a complete mess.
    – Telastyn
    Commented Aug 27, 2014 at 18:45
  • @Telastyn I've run into two cases where systems need access to the entity being processed and not just its components. A system might need to add a component to an entity, or remove one. It might also need to check if an entity is the same as the one being processed; for example imagine a system where you need to find the nearest entity to the entity being processed (that could probably be handled with some kind of "identity" component, but using the identity of the entity itself seems more straightforward).
    – Hey
    Commented Nov 26, 2014 at 8:21
  • @hey - I suppose, but changing the components at runtime seems like setting yourself up for failure (and debugging pain). In the second scenario, I would expect that component doing the check to reference itself, not its owner - or to have the entity ID passed in.
    – Telastyn
    Commented Nov 26, 2014 at 12:37
  • @Telastyn good point about the second case, the system could just check if two "position" components have the same identity. For the first case, the ability to change components on the fly is one of the things about this pattern that appeals to me. Maybe I'm abusing the pattern by doing this, but I assumed the OP here was doing the same: "Systems are responsible for the actually modifying the entities by changing their components." But maybe this means changing the properties of components, not changing which components an entity is composed of. I might write up a question about that later.
    – Hey
    Commented Nov 26, 2014 at 20:40
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).

1
  • 1
    Thanks for the effort put in this answer. I arrived to similar solutions before I completely changed the design.
    – Veritas
    Commented Jan 8, 2016 at 14:34

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

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