2
\$\begingroup\$

I tried to write a simple entity component system for my game engine. Each scene will have an EntityManager. It will create entities and add or remove components from them. An entity will be destroyed if its isAlive flag is set to false.

Example usage

class Position : public Component
{
    Position(float x, float y, float z)
        : x(x)
        , y(y)
        , z(z)
    {}

    float x, y, z;
}

EntityManager manager;

Entity& entity = manager.addEntity();
manager.entityAddComponent<Position>(entity, 1.0f, 2.0f, 3.0f);

auto& positions = manager.getComponentPool<Position>();
for (Position& pos : positions)
{
    // update
}

manager.removeDeadEntities();

Here is the code.

component id generator

static size_t registeredComponentCount = 0;

template <typename T>
size_t getComponentId()
{
    static const size_t id = registeredComponentCount++;
    return id;
}

Entity

class Entity
{
    friend EntityManager;
    template <typename T>
    friend class ComponentPool;

public:
    static constexpr size_t MAX_COMPONENTS = 32;

private:
    Entity(size_t id)
        : m_components({ nullptr })
        , m_id(id)
        , isAlive(true)
    {}

    Entity(const Entity&) = delete;
    Entity& operator= (const Entity&) = delete;

public:
    bool isAlive;

private:
    std::array<Component*, MAX_COMPONENTS> m_components;
    size_t m_id;
};

Component

struct Component
{
    friend EntityManager;
    template <typename T>
    friend class ComponentPool;

public:
    template<typename T>
    T& getComponent()
    {
        return reinterpret_cast<T>(m_entity->m_components[getComponentId<T>]);
    }

    Entity& getEntity()
    {
        return *m_entity;
    }

    Entity& getEntity();

private:
    Entity* m_entity;
};

EntityManager class

class EntityManager
{
public:
    static constexpr size_t MAX_ENTITES = 1024;

public:
    EntityManager();
    ~EntityManager();
    EntityManager(const EntityManager&) = delete;
    EntityManager& operator= (const EntityManager&) = delete;

    Entity& addEntity();

    void removeDeadEntities();

    template<typename T, typename ...Args>
    void entityAddComponent(Entity& entity, Args&&... args)
    {
        T* component = getComponentPool<T>().addComponent(std::forward<Args>(args)...);
        component->m_entity = &entity;
        entity.m_components[getComponentId<T>()] = reinterpret_cast<Component*>(component);
    }

    template<typename T>
    void entityRemoveComponent(Entity& entity)
    {
        getComponentPool<T>().removeComponent(entity.m_components[getComponentId<T>()]);
    }


    template<typename T>
    ComponentPool<T>& getComponentPool()
    {
        size_t id = getComponentId<T>();

        if (m_initializedComponentPools.test(id) == false)
        {
            m_componentPools[id] = reinterpret_cast<BaseComponentPool*>(new ComponentPool<T>());
            m_initializedComponentPools.set(id, true);
        }

        return *reinterpret_cast<ComponentPool<T>*>(m_componentPools[id]);
    }

private:
    UninitializedArray<Entity, MAX_ENTITES> m_entites;
    size_t m_entityCount;

    size_t m_createdEntityCount;

    BaseComponentPool* m_componentPools[Entity::MAX_COMPONENTS];
    std::bitset<Entity::MAX_COMPONENTS> m_initializedComponentPools;
};

ComponentPool class

class BaseComponentPool
{
public:
    virtual ~BaseComponentPool() {};
    virtual void removeComponent(Component* component) = 0;
};

template<typename T>
class ComponentPool : BaseComponentPool
{
public:
    ComponentPool()
        : m_size(0)
    {}

    ~ComponentPool() override
    {
        for (size_t i = 0; i < m_size; i++)
        {
            m_data[i].~T();
        }
    }

    ComponentPool(const ComponentPool&) = delete;
    ComponentPool& operator= (const ComponentPool&) = delete;

    template<typename ...Args>
    T* addComponent(Args && ...args)
    {
        T* component = &m_data[m_size];
        new (&m_data[m_size]) T(std::forward<Args>(args)...);
        m_size++;
        return component;
    }

    void removeComponent(T* component)
    {
        component->~T();
        if (component != &m_data[m_size - 1])
        {
            new (component) T(std::move(m_data[m_size - 1]));
            component->m_entity->m_components[getComponentId<T>()] = component;
        }
        m_size--;
    }

    void removeComponent(Component* component) override
    {
        removeComponent(reinterpret_cast<T*>(component));
    }

    T* begin()
    {
        return m_data.data();
    }

    T* end()
    {
        return m_data.data() + m_size;
    }

    const T* cbegin() const
    {
        return m_data();
    }

    const T* cend() const
    {
        return m_data() + m_size;
    }

private:
    UninitializedArray<T, EntityManager::MAX_ENTITES> m_data;
    size_t m_size;
};

System class

class System
{
public:
    virtual ~System() = 0;
    virtual void update(Scene& scene) = 0;
};

Helper class UninitializedArray

template <typename T, size_t size>
class UninitializedArray
{
public:
    T& operator[](size_t index)
    {
        return reinterpret_cast<T*>(m_data)[index];
    }

    T* data()
    {
        return reinterpret_cast<T*>(m_data);
    }

private:
    alignas(alignof(T)) unsigned char m_data[sizeof(T) * size];
};

Is there a better way to implement removing components without using virtual function calls?

Does the m_initializedComponentPools bitset in EntityManager make sense or would it be better to just set the uninitialized pools to nullptr?

Would it be better to register what types of components are going to be used so the component pools are initialized before the game loop starts?

Any tips on how to improve the code would be appreciated.

\$\endgroup\$

1 Answer 1

2
\$\begingroup\$

Answers to your questions

Is there a better way to implement removing components without using virtual function calls?

You have to have some way to call the right ComponentPool<T>::removeComponent() corresponding to the component type. With virtual functions, this is handled for you by the language. If you want to do it without a virtual function call, then you'd need some way to store the component type in a BaseComponentPool and then dispatch the right version of removeComponent() based on it. It's going to be more work for little to no gain.

Also, there is actually no virtual function call being done in your code: you already explicitly pass the desired type to EntityManager::entityRemoveComponent(), and then it will call getComponentPool<T>, which will return a ComponentPool<T> &, not a BaseComponentPool &, so it can directly call the right removeComponent(). You can remove removeComponent() from BaseComponentPool and your code should still compile and run fine. You should keep the virtual destructor though.

Does the m_initializedComponentPools bitset in EntityManager make sense or would it be better to just set the uninitialized pools to nullptr?

I would just set them to nullptr, or better yet make it an array of std::unique_ptrs:

std::array<std::unique_ptr<BaseComponentPool>, Entity::MAX_COMPONENTS> m_componentPools;

Would it be better to register what types of components are going to be used so the component pools are initialized before the game loop starts?

If you can do that, yes. Not only allows it to have the pools be initialized beforehand, it also ensures you know exactly the right size of EntityManager::m_componentPools. You could even consider making m_componentPools a std::tuple of component pool types then, so you don't need a virtual base class anymore:

template<typename... Ts>
class EntityManager
{
    ...
    template<typename T>
    ComponentPool<T>& getcomponentPool()
    {
         return std::get<ComponentPool<T>>(m_componentPools);
    }
    ...
    std::tuple<ComponentPool<Ts>...> m_componentPools;
}

Simplify entityAddComponent()

Instead of making entityAddComponent() be a variadic template that takes all the arguments necessary to construct a new component, consider having it take a single parameter for the component, like so:

template<typename T>
void entityAddComponent(Entity& entity, T&& component)
{
    T* component = getComponentPool<T>().addComponent(std::forward<T>(component));
     ...
}

This way, addComponent can copy or move-construct a new component. The main benefit it that this now allows the compiler to deduce T automatically, so you can write:

manager.entityAddComponent(entity, Position{1.0f, 2.0f, 3.0f});

This becomes especially helpful if you already have the position in a variable, in which case you can do:

Position position{1.0f, 2.0f, 3.0f};
manager.entityAddComponent(entity, position);

Instead of:

manager.entityAddComponent<Position>(entity, position.x, position.y, position.z);

Of course, this won't work if the component type doesn't have copy or move constructors.

Make IDs const

The ID of an entity is set a construction time and should never change afterwards, so make it const.

Consider storing entities and components in std::deques

The UninitializedArray is indeed a good way to avoid default-constructing a large number of components, but you still need to specify the maximum number of elements you want it to hold up-front. There is a standard container that you can use instead: std::deque. It provides fast index-based access, pointers to elements are stable, and it grows as required.

\$\endgroup\$

Your Answer

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

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