0
\$\begingroup\$

I'm writing an entity component system and want to store each type of component separately and contiguously in memory in a way that's easy to iterate over.

Component is a simple base class from which like classes like MoveComponent derive.

I store components inside the World class:

struct World {
    ...
    std::vector<Component> components;
};

This would work fine if there was only one type of component, just a Component class. The problem is I don't know how to handle component subclasses. For example every MoveComponent should be stored contiguously in memory in a way that I can easily iterate over and process them.

I want to avoid this kind of copy-paste junk code for hopefully obvious reasons:

struct World {
    ...
    std::vector<MoveComponent> move_components;
    std::vector<PhysicsComponent> physics_components;
    etc.
};

I'd like to instead structure it in a way that permits iterating over each component of a certain type in a World like, for example, this (inspired by Overwatch's entity component system video, see here):

for (MoveComponent *mc : ComponentIterator<MoveComponent>(world)) { ... }

I'm at a loss as to how to implement this.

\$\endgroup\$
2
  • 3
    \$\begingroup\$ Did you check how EnTT does it? \$\endgroup\$
    – Vaillancourt
    Commented Apr 3, 2021 at 3:18
  • 2
    \$\begingroup\$ I recommend avoiding Blizzard's presentations, they often oversell their achievements while also being shallow on technical details. For example, you are not supposed to iterate over single component; rather, system is supposed to iterate over the set of multiple components. \$\endgroup\$ Commented Apr 3, 2021 at 4:53

1 Answer 1

1
\$\begingroup\$

Entity component systems store data in one of three ways:

  1. Centrally allocated arrays, accessable by all systems.
  2. Contigious arrays of subsets of components that each system is interested in.
  3. Contigious array of "entities" allocated in a centrally located memory buffer.

Naughty Dog uses scenario 1. An entity in this context is just a collection of numbers:

struct Entity
{
    static const int FOO = 0;
    static const int BAR = 1;
    int components[MAX_COMPONENTS];
    uint32_t id;
};

The "components" in the above code are either "-1" meaning "no component of this type, or a positive number, zero inclusive. This is an index to a centrally stored array of each set of components:

class ComponentAllocator
{
public:
    FooComponent foo_components[MAX_FOO_COUNT];
    BarComponent bar_components[MAX_BAR_COUNT];
};

Then any system can use some singleton/service locator pattern to get access to it, and iterate over it:

void FooBarSystem::update()
{
    for (auto& entity: entities) //entities is just a collection of type Entity
    {
        Foo& foo = getComponentAllocator().foo_components[entity.components[FOO]];
        Bar& bar = getComponentAllocator().bar_components[entity.components[BAR]];
        // Do something that requires interaction between foo and bar.
        // ...
    }
}

In scenario 2, your entity is the storage for each set of components, and each system stores a collection of proxies to that entity.

class Entity
{
public:
    std::map<std::type_index, std::shared_ptr<IComponent>> m_Components;
    uint32_t id;
    // some helpful functions for adding and removing via templates.
}

Each system stores a "proxy" to the entity, but only has access to a subset of the components. The component is also a "subject", from the subject/observer pattern, which allows any change in the component to propogate to any interested proxy. Whilst this is a good way to do things, it carries a lot of boiler plate, and isn't good for the novice to learn.

Scenario 3 just allocates a byte array, which gets set with as many components as you need. This is also complex, but very good for embedded systems, such as mobile devices and consoles.

I have purposely left out in depth explanations on most of this stuff, as it would take too long to explain all three, so I recommend you look them up and figure out which is best for you.

\$\endgroup\$

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .