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 Apr 3 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\$ – Shadows In Rain Apr 3 at 4:53
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\$

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.