Add ECS framework

This commit is contained in:
Thraix
2023-05-11 22:33:10 +02:00
parent 94d4aa9356
commit 412d74ade3
12 changed files with 673 additions and 0 deletions
+10
View File
@@ -178,6 +178,10 @@
<ClCompile Include="src\copium\core\Device.cpp" />
<ClCompile Include="src\copium\core\Vulkan.cpp" />
<ClCompile Include="src\copium\core\Window.cpp" />
<ClCompile Include="src\copium\ecs\ComponentPoolBase.cpp" />
<ClCompile Include="src\copium\ecs\ECSManager.cpp" />
<ClCompile Include="src\copium\ecs\Entity.cpp" />
<ClCompile Include="src\copium\ecs\EntitySet.cpp" />
<ClCompile Include="src\copium\event\Event.cpp" />
<ClCompile Include="src\copium\event\EventDispatcher.cpp" />
<ClCompile Include="src\copium\event\KeyPressEvent.cpp" />
@@ -236,6 +240,12 @@
<ClInclude Include="src\copium\core\Device.h" />
<ClInclude Include="src\copium\core\Vulkan.h" />
<ClInclude Include="src\copium\core\Window.h" />
<ClInclude Include="src\copium\ecs\ComponentPool.h" />
<ClInclude Include="src\copium\ecs\ComponentPoolBase.h" />
<ClInclude Include="src\copium\ecs\Config.h" />
<ClInclude Include="src\copium\ecs\ECSManager.h" />
<ClInclude Include="src\copium\ecs\Entity.h" />
<ClInclude Include="src\copium\ecs\EntitySet.h" />
<ClInclude Include="src\copium\event\Event.h" />
<ClInclude Include="src\copium\event\EventDispatcher.h" />
<ClInclude Include="src\copium\event\EventHandler.h" />
+30
View File
@@ -186,6 +186,18 @@
<ClCompile Include="src\copium\util\BoundingBox.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="src\copium\ecs\ECSManager.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="src\copium\ecs\EntitySet.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="src\copium\ecs\ComponentPoolBase.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="src\copium\ecs\Entity.cpp">
<Filter>Source Files</Filter>
</ClCompile>
</ItemGroup>
<ItemGroup>
<ClInclude Include="src\copium\sampler\DepthAttachment.h">
@@ -380,5 +392,23 @@
<ClInclude Include="src\copium\util\BoundingBox.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="src\copium\ecs\ComponentPool.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="src\copium\ecs\Config.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="src\copium\ecs\ECSManager.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="src\copium\ecs\Entity.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="src\copium\ecs\EntitySet.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="src\copium\ecs\ComponentPoolBase.h">
<Filter>Header Files</Filter>
</ClInclude>
</ItemGroup>
</Project>
@@ -0,0 +1,90 @@
#pragma once
#include "copium/ecs/Config.h"
#include "copium/ecs/EntitySet.h"
#include "copium/ecs/ComponentPoolBase.h"
#include <vector>
namespace Copium
{
template <typename Component>
class ComponentPool : public ComponentPoolBase
{
using Iterator = typename std::vector<Component>::iterator;
private:
std::vector<Component> components;
public:
ComponentPool(EntityID entity, const Component& component)
{
Emplace(entity, component);
}
Component& Emplace(EntityID entity, const Component& component)
{
components.push_back(component);
entities.Emplace(entity);
return components.back();
}
void Pop()
{
components.pop_back();
entities.Pop();
}
bool Erase(EntityID entity)
{
size_t index = entities.Find(entity);
if (!entities.Erase(entity))
return false;
components.erase(components.begin() + index);
return true;
}
Component& At(size_t index)
{
return operator[](index);
}
size_t Find(EntityID entity)
{
return entities.Find(entity);
}
Component* FindComponent(EntityID entity)
{
size_t index = Find(entity);
if (index < Size())
return &components[index];
return nullptr;
}
Component& operator[](size_t index)
{
CP_ASSERT(index < components.size(), "Index Out of Bound Exception");
return components[index];
}
size_t Size()
{
return components.size();
}
Iterator Back()
{
return components.back();
}
Iterator begin()
{
return components.begin();
}
Iterator end()
{
return components.end();
}
};
}
@@ -0,0 +1,14 @@
#include "copium/ecs/ComponentPoolBase.h"
namespace Copium
{
std::vector<EntityID>& ComponentPoolBase::GetEntities()
{
return entities.GetList();
}
const std::vector<EntityID>& ComponentPoolBase::GetEntities() const
{
return entities.GetList();
}
}
@@ -0,0 +1,23 @@
#pragma once
#include "copium/ecs/Config.h"
#include "copium/ecs/EntitySet.h"
#include <vector>
namespace Copium
{
class ComponentPoolBase
{
protected:
EntitySet entities;
public:
virtual ~ComponentPoolBase() = default;
virtual size_t Size() = 0;
virtual void Pop() = 0;
virtual bool Erase(EntityID entity) = 0;
std::vector<EntityID>& GetEntities();
const std::vector<EntityID>& GetEntities() const;
};
}
+11
View File
@@ -0,0 +1,11 @@
#pragma once
#include <stdint.h>
#include <numeric>
namespace Copium
{
using EntityID = uint32_t;
const static uint32_t MAX_NUM_ENTITIES = std::numeric_limits<uint32_t>::max();
const static uint32_t INVALID_ENTITY = 0;
}
@@ -0,0 +1,50 @@
#include "copium/ecs/ECSManager.h"
#include "copium/util/Common.h"
namespace Copium
{
ECSManager::~ECSManager()
{
for (auto&& components : componentPool)
{
delete components.second;
}
componentPool.clear();
}
size_t ECSManager::GetEntityCount() const
{
return entities.size();
}
EntityID ECSManager::CreateEntity()
{
CP_ASSERT(currentEntityId != MAX_NUM_ENTITIES, "No more entities available");
entities.emplace(currentEntityId);
currentEntityId++;
return currentEntityId - 1;
}
void ECSManager::DestroyEntity(EntityID entity)
{
auto it = entities.find(entity);
CP_ASSERT(it != entities.end(), "Entity does not exist in ECSManager (entity=%u)", entity);
entities.erase(it);
for (auto&& pool : componentPool)
{
pool.second->Erase(entity);
}
}
bool ECSManager::ValidEntity(EntityID entity)
{
return entities.find(entity) != entities.end();
}
void ECSManager::Each(std::function<void(EntityID)> function)
{
for (auto e : entities)
function(e);
}
}
+196
View File
@@ -0,0 +1,196 @@
#pragma once
#include "copium/ecs/ComponentPool.h"
#include "copium/ecs/Config.h"
#include "copium/util/Common.h"
#include <functional>
#include <map>
#include <typeindex>
#include <unordered_set>
#include <vector>
namespace Copium
{
class ECSManager final
{
private:
std::unordered_set<EntityID> entities;
std::map<std::type_index, ComponentPoolBase*> componentPool;
int currentEntityId = 1;
public:
~ECSManager();
EntityID CreateEntity();
void DestroyEntity(EntityID entity);
size_t GetEntityCount() const;
bool ValidEntity(EntityID entity);
void Each(std::function<void(EntityID)> function);
template <typename... Components>
std::tuple<Components&...> AddComponents(EntityID entity, Components&&... components)
{
return std::forward_as_tuple(AddComponent(entity, Components(components))...);
}
template <typename Component, typename... Args>
Component& AddComponent(EntityID entity, Args&&... args)
{
return AddComponent(entity, Component(args...));
}
template <typename Component>
Component& AddComponent(EntityID entity, const Component& component)
{
auto pool = GetComponentPool<Component>();
if (pool)
{
CP_ASSERT(!HasComponent<Component>(entity), "Component already exists in entity (entity=%u, Component=%s)", entity, typeid(Component).name());
return pool->Emplace(entity, component);
}
else
{
ComponentPool<Component>* pool{new ComponentPool{entity, component}};
auto ret = componentPool.emplace(GetComponentId<Component>(), pool);
return pool->At(0);
}
}
template <typename Component>
void RemoveComponent(EntityID entity)
{
auto pool = GetComponentPoolAssure<Component>();
CP_ASSERT(pool->Erase(entity), "Entity did not contain component (entity=%u, Component=%s)", entity, typeid(Component).name());
}
template <typename... Components>
void RemoveComponents(EntityID entity)
{
(RemoveComponent<Components>(entity), ...);
}
template <typename Component>
Component& GetComponent(EntityID entity)
{
auto pool = GetComponentPoolAssure<Component>();
Component* component = pool->FindComponent(entity);
ASSERT(component, "Entity did not contain component (entity=%u, Component=%s)", entity, typeid(Component).name());
return *component;
}
template <typename Component>
bool HasComponent(EntityID entity)
{
auto pool = GetComponentPool<Component>();
if (pool)
return pool->Find(entity) != pool->Size();
return false;
}
template <typename... Components>
bool HasComponents(EntityID entity)
{
return (HasComponent<Components>(entity) && ...);
}
template <typename... Components>
bool HasAnyComponent(EntityID entity)
{
return (HasComponent<Components>(entity) || ...);
}
template <typename Component, typename... Components, typename Func>
void Each(Func function)
{
auto pool = GetComponentPool<Component>();
if (pool)
{
size_t i = 0;
for (auto entity : pool->GetEntities())
{
if (HasComponents<Components...>(entity))
{
std::apply(function, std::forward_as_tuple(entity, pool->At(i), GetComponent<Components>(entity)...));
}
i++;
}
}
}
template <typename Component>
void Each(std::function<void(EntityID, Component&)> function)
{
auto pool = GetComponentPool<Component>();
if (pool)
{
size_t i = 0;
for (auto e : pool->GetEntities())
{
function(e, pool->At(i));
i++;
}
}
}
template <typename Component, typename... Components, typename Func>
EntityID Find(Func function)
{
auto pool = GetComponentPool<Component>();
if (pool)
{
size_t i = 0;
for (auto entity : pool->GetEntities())
{
if (HasComponents<Components...>(entity))
{
if (std::apply(function, std::forward_as_tuple(entity, pool->At(i), GetComponent<Components>(entity)...)))
return entity;
}
i++;
}
}
return 0;
}
template <typename Component>
EntityID Find(std::function<bool(EntityID, Component&)> function)
{
auto pool = GetComponentPool<Component>();
if (pool)
{
size_t i = 0;
for (auto e : pool->GetEntities())
{
if (function(e, pool->At(i)))
return e;
i++;
}
}
return 0;
}
template <typename T>
std::type_index GetComponentId()
{
return std::type_index(typeid(T));
}
private:
template <typename Component>
ComponentPool<Component>* GetComponentPool()
{
auto it = componentPool.find(GetComponentId<Component>());
return it == componentPool.end() ? nullptr : static_cast<ComponentPool<Component>*>(it->second);
}
template <typename Component>
ComponentPool<Component>* GetComponentPoolAssure()
{
auto it = componentPool.find(GetComponentId<Component>());
CP_ASSERT(it != componentPool.end(), "Component has not been added to an entity (Component=%s)", typeid(Component).name());
return static_cast<ComponentPool<Component>*>(it->second);
}
};
}
+76
View File
@@ -0,0 +1,76 @@
#include "copium/ecs/Entity.h"
namespace Copium
{
Entity::Entity()
: manager{nullptr}, id{INVALID_ENTITY}
{}
Entity::Entity(ECSManager* manager)
: manager{manager}, id{INVALID_ENTITY}
{}
Entity::Entity(ECSManager* manager, EntityID id)
: manager{manager}, id{id}
{}
Entity::operator EntityID() const
{
return id;
}
void Entity::operator=(EntityID entityId)
{
id = entityId;
}
bool Entity::operator==(const Entity& entity)
{
return id == entity.id;
}
bool Entity::operator!=(const Entity& entity)
{
return id != entity.id;
}
Entity::operator bool() const
{
if (id == INVALID_ENTITY)
return false;
if (manager)
return manager->ValidEntity(id);
return false;
}
void Entity::Invalidate()
{
id = INVALID_ENTITY;
}
void Entity::Destroy()
{
if (*this)
manager->DestroyEntity(id);
}
void Entity::SetID(EntityID aId)
{
id = aId;
}
EntityID Entity::GetID() const
{
return id;
}
ECSManager* Entity::GetManager() const
{
return manager;
}
Entity Entity::Create(ECSManager* manager)
{
return {manager, manager->CreateEntity()};
}
}
+83
View File
@@ -0,0 +1,83 @@
#pragma once
#include "copium/ecs/Config.h"
#include "copium/ecs/ECSManager.h"
namespace Copium
{
class Entity
{
friend class ECSManager;
private:
ECSManager* manager;
EntityID id;
public:
Entity();
Entity(ECSManager* manager);
Entity(ECSManager* manager, EntityID id);
operator EntityID() const;
void operator=(EntityID entityId);
bool operator==(const Entity& entity);
bool operator!=(const Entity& entity);
operator bool() const;
void Invalidate();
void Destroy();
void SetID(EntityID aId);
EntityID GetID() const;
ECSManager* GetManager() const;
static Entity Create(ECSManager* manager);
template <typename Component, typename... Args>
inline Component& AddComponent(Args... args)
{
return manager->AddComponent<Component>(id, args...);
}
template <typename... Components>
std::tuple<Components&...> AddComponents(Components&&... components)
{
return manager->AddComponents(id, components...);
}
template <typename Component>
inline void RemoveComponent()
{
return manager->RemoveComponent<Component>(id);
}
template <typename... Components>
inline void RemoveComponents()
{
return manager->RemoveComponents<Components...>(id);
}
template <typename Component>
inline Component& GetComponent() const
{
return manager->GetComponent<Component>(id);
}
template <typename Component>
inline bool HasComponent() const
{
return manager->HasComponent<Component>(id);
}
template <typename... Components>
inline bool HasComponents() const
{
return manager->HasComponents<Components...>(id);
}
template <typename... Components>
inline bool HasAnyComponent() const
{
return manager->HasAnyComponent<Components...>(id);
}
};
}
+63
View File
@@ -0,0 +1,63 @@
#include "copium/ecs/EntitySet.h"
namespace Copium
{
bool EntitySet::Emplace(EntityID entity)
{
auto res = entitiesMap.emplace(entity, entitiesList.size());
// Check if already exists
if (!res.second)
return false;
entitiesList.push_back(entity);
return true;
}
bool EntitySet::Erase(EntityID entity)
{
auto it = entitiesMap.find(entity);
if (it == entitiesMap.end())
return false;
size_t componentPos = it->second;
entitiesList.erase(entitiesList.begin() + it->second);
entitiesMap.erase(it);
for (auto&& entityPos : entitiesMap)
{
if (entityPos.second > componentPos)
{
entityPos.second--;
}
}
return true;
}
bool EntitySet::Pop()
{
if (entitiesList.size() == 0)
return false;
entitiesMap.erase(entitiesMap.find(entitiesList.back()));
entitiesList.pop_back();
return true;
}
size_t EntitySet::Find(EntityID entity)
{
auto it = entitiesMap.find(entity);
if (it == entitiesMap.end())
return entitiesList.size();
return it->second;
}
size_t EntitySet::Size() const
{
return entitiesList.size();
}
std::vector<EntityID>& EntitySet::GetList() { return entitiesList; }
const std::vector<EntityID>& EntitySet::GetList() const { return entitiesList; }
std::vector<EntityID>::iterator EntitySet::begin() { return entitiesList.begin(); }
std::vector<EntityID>::iterator EntitySet::end() { return entitiesList.end(); }
}
+27
View File
@@ -0,0 +1,27 @@
#pragma once
#include "copium/ecs/Config.h"
#include <unordered_map>
#include <vector>
namespace Copium
{
class EntitySet
{
private:
std::vector<EntityID> entitiesList;
std::unordered_map<EntityID, size_t> entitiesMap; // Maps the entity id to a component index
public:
bool Emplace(EntityID entity);
bool Erase(EntityID entity);
bool Pop();
size_t Find(EntityID entity);
size_t Size() const;
std::vector<EntityID>& GetList();
const std::vector<EntityID>& GetList() const;
std::vector<EntityID>::iterator begin();
std::vector<EntityID>::iterator end();
};
}