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.