Skip to main content
Spelling and grammar
Source Link
Toby Speight
  • 88.3k
  • 14
  • 104
  • 327

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 it's isAliveits isAlive flag is set to false.

Example usage

Example usage

 

component id generator

component id generator

Entity

Entity

Component

Component

EntityManager

EntityManager class

ComponentPool

ComponentPool class

System

System class

Helper class UninitializedArray

Helper class UninitializedArray

 

Does the m_initializedComponentPoolsm_initializedComponentPools bitset in EntityManagerEntityManager make sense or would it be better to just set the uninitialized pools to nullptrnullptr?

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 it's isAlive flag is set to false.

Example usage

component id generator

Entity

Component

EntityManager

ComponentPool

System

Helper class UninitializedArray

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

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

 

component id generator

Entity

Component

EntityManager class

ComponentPool class

System class

Helper class UninitializedArray

 

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

Source Link

Implementation of an entity component system in c++

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 it's 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 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 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 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.