Rework ECS framework

- Add queue systems when adding/removing components and systems
- Add GlobalData to ECSManager, used to store data that is not specific
  to entities
- Add View class, used to for loop all entities that contains the given
  Components
- Rework how signaling works
This commit is contained in:
Thraix
2026-02-07 18:25:13 +01:00
parent e5866b2dcb
commit 9d5a5314a7
23 changed files with 729 additions and 245 deletions
+1 -1
View File
@@ -430,7 +430,7 @@ namespace Copium
int width, height; int width, height;
glfwGetFramebufferSize(window, &width, &height); glfwGetFramebufferSize(window, &width, &height);
VkExtent2D extent{width, height}; VkExtent2D extent{static_cast<uint32_t>(width), static_cast<uint32_t>(height)};
extent.width = std::clamp(extent.width, capabilities.minImageExtent.width, capabilities.maxImageExtent.width); extent.width = std::clamp(extent.width, capabilities.minImageExtent.width, capabilities.maxImageExtent.width);
extent.height = std::clamp(extent.height, capabilities.minImageExtent.height, capabilities.maxImageExtent.height); extent.height = std::clamp(extent.height, capabilities.minImageExtent.height, capabilities.maxImageExtent.height);
return extent; return extent;
+107 -19
View File
@@ -19,6 +19,16 @@ namespace Copium
std::vector<Component> components; std::vector<Component> components;
ComponentListener<Component>* listener = nullptr; ComponentListener<Component>* listener = nullptr;
enum class QueueOperation
{
Add,
Remove
};
std::vector<QueueOperation> queueOperationOrder;
std::vector<std::pair<EntityId, Component>> addQueue;
std::vector<EntityId> removeQueue;
public: public:
ComponentPool() ComponentPool()
{ {
@@ -35,34 +45,61 @@ namespace Copium
Emplace(entity, component); Emplace(entity, component);
} }
Component& Emplace(EntityId entity, const Component& component) void Emplace(EntityId entity, const Component& component)
{ {
components.push_back(component); addQueue.emplace_back(entity, component);
entities.Emplace(entity); queueOperationOrder.emplace_back(QueueOperation::Add);
if (listener)
listener->Added(entity, components.back());
return components.back();
} }
bool Erase(EntityId entity) override bool Erase(EntityId entity) override
{ {
size_t index = entities.Find(entity); if (entities.Find(entity) == entities.Size())
if (!entities.Erase(entity))
return false; return false;
if (listener)
{ removeQueue.emplace_back(entity);
auto it = components.begin() + index; queueOperationOrder.emplace_back(QueueOperation::Remove);
Component component = *it;
components.erase(it);
listener->Removed(entity, component);
}
else
{
components.erase(components.begin() + index);
}
return true; return true;
} }
void CommitUpdates() override
{
if (queueOperationOrder.empty())
return;
CP_ASSERT(queueOperationOrder.size() == addQueue.size() + removeQueue.size(),
"queueOperationOrder size=%zu doesn't match the sum of the addQueue size=%zu and removeQueue size=%zu, "
"which is %zu",
queueOperationOrder.size(),
addQueue.size(),
removeQueue.size(),
addQueue.size() + removeQueue.size());
int addQueueIndex = 0;
int removeQueueIndex = 0;
for (QueueOperation queueOperation : queueOperationOrder)
{
switch (queueOperation)
{
case QueueOperation::Add:
{
CommitAddComponent(addQueueIndex);
addQueueIndex++;
break;
}
case QueueOperation::Remove:
{
CommitRemoveComponent(removeQueueIndex);
removeQueueIndex++;
break;
}
}
}
removeQueue.clear();
addQueue.clear();
queueOperationOrder.clear();
}
Component& At(size_t index) Component& At(size_t index)
{ {
return operator[](index); return operator[](index);
@@ -111,5 +148,56 @@ namespace Copium
{ {
return components.end(); return components.end();
} }
void CommitAddComponent(int queueIndex)
{
CP_ASSERT(
queueIndex < addQueue.size(), "queueIndex=%d is greater than the addQueueSize=%d", queueIndex, addQueue.size());
const auto& [entity, component] = addQueue[queueIndex];
// TODO: Debugging errors caused by this assert might be a bit difficult, since there wont be any stacktrace for
// where the component was added. Might want to validate this in AddComponent somehow (like looping through
// the queued changes and work out if the component already exists)
CP_ASSERT(Find(entity) == Size(),
"Component already exists in entity (entity=%u, Component=%s)",
entity,
typeid(Component).name());
components.push_back(component);
entities.Emplace(entity);
if (listener)
listener->Added(entity, components.back());
}
void CommitRemoveComponent(int queueIndex)
{
CP_ASSERT(queueIndex < removeQueue.size(),
"queueIndex=%d is greater than the removeQueueSize=%d",
queueIndex,
removeQueue.size());
const auto& entity = removeQueue[queueIndex];
size_t index = entities.Find(entity);
if (!entities.Erase(entity))
{
// TODO: Debugging warnings caused by this might be a bit difficult, since there wont be any stacktrace for
// where the component was added. Might want to validate this in AddComponent somehow (like looping
// through the queued changes and work out if the component already exists)
CP_WARN("Entity did not contain component (entity=%u, Component=%s)", entity, typeid(Component).name());
return;
}
if (listener)
{
auto it = components.begin() + index;
Component component = *it;
components.erase(it);
listener->Removed(entity, component);
}
else
{
components.erase(components.begin() + index);
}
}
}; };
} }
@@ -17,6 +17,7 @@ namespace Copium
virtual size_t Size() = 0; virtual size_t Size() = 0;
virtual bool Erase(EntityId entity) = 0; virtual bool Erase(EntityId entity) = 0;
virtual void CommitUpdates() = 0;
std::vector<EntityId>& GetEntities(); std::vector<EntityId>& GetEntities();
const std::vector<EntityId>& GetEntities() const; const std::vector<EntityId>& GetEntities() const;
}; };
+37 -10
View File
@@ -4,30 +4,37 @@
namespace Copium namespace Copium
{ {
std::vector<EntityId> ECSManager::emptyEntities = {};
ECSManager::ECSManager() ECSManager::ECSManager()
: systemPool{std::make_unique<SystemPool>(this)}
{ {
} }
ECSManager::~ECSManager() ECSManager::~ECSManager()
{ {
for (auto&& components : componentPool) for (auto&& components : componentPools)
{ {
delete components.second; delete components.second;
} }
componentPool.clear(); componentPools.clear();
} }
void ECSManager::UpdateSystems() void ECSManager::CommitEntityUpdates()
{ {
systemPool->Update(); for (auto& componentPool : componentPools)
{
componentPool.second->CommitUpdates();
}
} }
void ECSManager::UpdateSystems(const Signal& signal) void ECSManager::UpdateSystems(const Uuid& systemPoolId)
{ {
// TODO: Maybe we want a different pool for Signal based Systems for performance reasons? auto it = systemPools.find(systemPoolId);
// Maybe even a pool for each type of Signal? CP_ASSERT(it != systemPools.end(), "SystemPool doesn't exist with Uuid=%s", systemPoolId.ToString().c_str());
systemPool->Update(signal); it->second->CommitSignals();
CommitEntityUpdates();
it->second->CommitUpdates();
it->second->Update();
} }
size_t ECSManager::GetEntityCount() const size_t ECSManager::GetEntityCount() const
@@ -37,6 +44,13 @@ namespace Copium
EntityId ECSManager::CreateEntity() EntityId ECSManager::CreateEntity()
{ {
if (!destroyedEntityIds.empty())
{
EntityId newId = *destroyedEntityIds.begin();
destroyedEntityIds.erase(destroyedEntityIds.begin());
return newId;
}
CP_ASSERT(currentEntityId != MAX_NUM_ENTITIES, "No more entities available"); CP_ASSERT(currentEntityId != MAX_NUM_ENTITIES, "No more entities available");
entities.emplace(currentEntityId); entities.emplace(currentEntityId);
currentEntityId++; currentEntityId++;
@@ -47,8 +61,21 @@ namespace Copium
{ {
auto it = entities.find(entity); auto it = entities.find(entity);
CP_ASSERT(it != entities.end(), "Entity does not exist in ECSManager (entity=%u)", entity); CP_ASSERT(it != entities.end(), "Entity does not exist in ECSManager (entity=%u)", entity);
if (entity == currentEntityId - 1)
{
currentEntityId--;
while (!destroyedEntityIds.empty() && *destroyedEntityIds.rbegin() == currentEntityId - 1)
{
destroyedEntityIds.erase(std::prev(destroyedEntityIds.end()));
}
}
else
{
destroyedEntityIds.emplace(entity);
}
entities.erase(it); entities.erase(it);
for (auto&& pool : componentPool) for (auto&& pool : componentPools)
{ {
pool.second->Erase(entity); pool.second->Erase(entity);
} }
+92 -35
View File
@@ -2,6 +2,7 @@
#include <functional> #include <functional>
#include <map> #include <map>
#include <set>
#include <typeindex> #include <typeindex>
#include <unordered_set> #include <unordered_set>
@@ -10,36 +11,68 @@
#include "copium/ecs/Signal.h" #include "copium/ecs/Signal.h"
#include "copium/ecs/SystemPool.h" #include "copium/ecs/SystemPool.h"
#include "copium/util/Common.h" #include "copium/util/Common.h"
#include "copium/util/GenericType.h"
#include "copium/util/Uuid.h"
namespace Copium namespace Copium
{ {
class ECSManager final class ECSManager final
{ {
CP_DELETE_COPY_AND_MOVE_CTOR(ECSManager);
private: private:
std::unordered_set<EntityId> entities; std::unordered_set<EntityId> entities;
std::map<std::type_index, ComponentPoolBase*> componentPool; std::map<std::type_index, ComponentPoolBase*> componentPools;
std::unique_ptr<SystemPool> systemPool; std::map<Uuid, std::unique_ptr<SystemPool>> systemPools;
int currentEntityId = 1; EntityId currentEntityId = 1;
std::set<EntityId> destroyedEntityIds;
std::map<std::type_index, GenericType> globalDatas;
public: public:
static std::vector<EntityId> emptyEntities;
ECSManager(); ECSManager();
~ECSManager(); ~ECSManager();
template <typename SystemClass, typename... Args> template <typename SystemClass, typename... Args>
SystemOrderer AddSystem(const Args&... args) SystemOrderer& AddSystem(const Uuid& systemPoolId)
{ {
return systemPool->AddSystem(typeid(SystemClass), new SystemClass{args...}); auto it = systemPools.find(systemPoolId);
if (it == systemPools.end())
it = systemPools.emplace(systemPoolId, std::make_unique<SystemPool>(this)).first;
return it->second->AddSystem(typeid(SystemClass), new SystemClass{});
} }
template <typename SystemClass> template <typename SystemClass>
void RemoveSystem() void RemoveSystem(const Uuid& systemPoolId)
{ {
systemPool->RemoveSystem(typeid(SystemClass)); auto it = systemPools.find(systemPoolId);
CP_ASSERT(it != systemPools.end(), "SystemPool doesn't exist with Uuid=%s", systemPoolId.ToString().c_str());
it->second->RemoveSystem(typeid(SystemClass));
if (it->second->IsEmpty())
systemPools.erase(it);
} }
void UpdateSystems(); void CommitEntityUpdates();
void UpdateSystems(const Signal& signal);
void UpdateSystems(const Uuid& systemPoolId);
template <typename S, typename... Args>
void SendSignal(const Args&... args)
{
Signal::ValidateSignal<S>();
for (auto& systemPool : systemPools)
{
S* newSignal = new S{args...};
newSignal->uuid = S::UUID;
systemPool.second->SendSignal(newSignal);
}
}
EntityId CreateEntity(); EntityId CreateEntity();
void DestroyEntity(EntityId entity); void DestroyEntity(EntityId entity);
@@ -61,41 +94,36 @@ namespace Copium
else else
{ {
ComponentPool<Component>* pool{new ComponentPool<Component>{}}; ComponentPool<Component>* pool{new ComponentPool<Component>{}};
componentPool.emplace(GetComponentId<Component>(), pool); componentPools.emplace(GetComponentId<Component>(), pool);
pool->SetComponentListener(listener); pool->SetComponentListener(listener);
} }
} }
template <typename... Components> template <typename... Components>
std::tuple<Components&...> AddComponents(EntityId entity, Components&&... components) void AddComponents(EntityId entity, Components&&... components)
{ {
return std::forward_as_tuple(AddComponent(entity, Components(components))...); (AddComponent(entity, components), ...);
} }
template <typename Component, typename... Args> template <typename Component, typename... Args>
Component& AddComponent(EntityId entity, Args&&... args) void AddComponent(EntityId entity, Args&&... args)
{ {
return AddComponent(entity, Component{args...}); AddComponent(entity, Component{args...});
} }
template <typename Component> template <typename Component>
Component& AddComponent(EntityId entity, const Component& component) void AddComponent(EntityId entity, const Component& component)
{ {
auto pool = GetComponentPool<Component>(); auto pool = GetComponentPool<Component>();
if (pool) if (pool)
{ {
CP_ASSERT(!HasComponent<Component>(entity), pool->Emplace(entity, component);
"Component already exists in entity (entity=%u, Component=%s)",
entity,
typeid(Component).name());
return pool->Emplace(entity, component);
} }
else else
{ {
ComponentPool<Component>* pool{new ComponentPool{entity, component}}; ComponentPool<Component>* pool{new ComponentPool{entity, component}};
auto ret = componentPool.emplace(GetComponentId<Component>(), pool); auto ret = componentPools.emplace(GetComponentId<Component>(), pool);
return pool->At(0);
} }
} }
@@ -103,10 +131,7 @@ namespace Copium
void RemoveComponent(EntityId entity) void RemoveComponent(EntityId entity)
{ {
auto pool = GetComponentPoolAssure<Component>(); auto pool = GetComponentPoolAssure<Component>();
CP_ASSERT(pool->Erase(entity), pool->Erase(entity);
"Entity did not contain component (entity=%u, Component=%s)",
entity,
typeid(Component).name());
} }
template <typename... Components> template <typename... Components>
@@ -229,22 +254,54 @@ namespace Copium
return std::type_index(typeid(T)); return std::type_index(typeid(T));
} }
private: template <typename T, typename... Args>
template <typename Component> void AddGlobalData(const Args&... args)
ComponentPool<Component>* GetComponentPool()
{ {
auto it = componentPool.find(GetComponentId<Component>()); auto it = globalDatas.find(typeid(T));
return it == componentPool.end() ? nullptr : static_cast<ComponentPool<Component>*>(it->second); CP_ASSERT(!HasGlobalData<T>(), "Global with typeid=%s already exists. Do nothing", typeid(T).name());
globalDatas.emplace(typeid(T), GenericType::Create<T>(args...));
}
template <typename T>
T& GetGlobalData()
{
auto it = globalDatas.find(typeid(T));
CP_ASSERT(it != globalDatas.end(), "Global with typeid=%s doesn't exist");
return it->second.Get<T>();
}
template <typename T>
bool HasGlobalData()
{
return globalDatas.find(typeid(T)) != globalDatas.end();
}
template <typename T>
void RemoveGlobalData()
{
auto it = globalDatas.find(typeid(T));
CP_ASSERT("Global with typeid=%s doesn't exist. Do nothing", typeid(T).name());
globalDatas.erase(it);
} }
template <typename Component> template <typename Component>
ComponentPool<Component>* GetComponentPoolAssure() ComponentPool<std::remove_const_t<Component>>* GetComponentPool()
{ {
auto it = componentPool.find(GetComponentId<Component>()); auto it = componentPools.find(GetComponentId<Component>());
CP_ASSERT(it != componentPool.end(), return it == componentPools.end() ? nullptr
: static_cast<ComponentPool<std::remove_const_t<Component>>*>(it->second);
}
template <typename Component>
ComponentPool<std::remove_const_t<Component>>* GetComponentPoolAssure()
{
auto it = componentPools.find(GetComponentId<Component>());
CP_ASSERT(it != componentPools.end(),
"Component has not been added to an entity (Component=%s)", "Component has not been added to an entity (Component=%s)",
typeid(Component).name()); typeid(Component).name());
return static_cast<ComponentPool<Component>*>(it->second); return static_cast<ComponentPool<std::remove_const_t<Component>>*>(it->second);
} }
}; };
} }
+6 -1
View File
@@ -40,7 +40,7 @@ namespace Copium
return id != entity.id; return id != entity.id;
} }
Entity::operator bool() const bool Entity::IsValid() const
{ {
if (id == INVALID_ENTITY) if (id == INVALID_ENTITY)
return false; return false;
@@ -49,6 +49,11 @@ namespace Copium
return false; return false;
} }
Entity::operator bool() const
{
return IsValid();
}
void Entity::Invalidate() void Entity::Invalidate()
{ {
id = INVALID_ENTITY; id = INVALID_ENTITY;
+5 -4
View File
@@ -24,6 +24,7 @@ namespace Copium
bool operator!=(const Entity& entity); bool operator!=(const Entity& entity);
operator bool() const; operator bool() const;
bool IsValid() const;
void Invalidate(); void Invalidate();
void Destroy(); void Destroy();
void SetId(EntityId entityId); void SetId(EntityId entityId);
@@ -33,15 +34,15 @@ namespace Copium
static Entity Create(ECSManager* manager); static Entity Create(ECSManager* manager);
template <typename Component, typename... Args> template <typename Component, typename... Args>
inline Component& AddComponent(Args... args) inline void AddComponent(Args... args)
{ {
return manager->AddComponent<Component>(id, args...); manager->AddComponent<Component>(id, args...);
} }
template <typename... Components> template <typename... Components>
std::tuple<Components&...> AddComponents(Components&&... components) void AddComponents(Components&&... components)
{ {
return manager->AddComponents(id, components...); manager->AddComponents(id, components...);
} }
template <typename Component> template <typename Component>
-10
View File
@@ -1,10 +0,0 @@
#include "copium/ecs/Signal.h"
namespace Copium
{
int Signal::GetAllocatedId()
{
allocatedIds++;
return allocatedIds;
}
}
+27 -33
View File
@@ -1,48 +1,42 @@
#pragma once #pragma once
#define CP_SIGNAL_DECLERATION_DEFINITION() \ #include "copium/util/Uuid.h"
static int GetIdStatic() \
{ \
static int id = GetAllocatedId(); \
return id; \
} \
\
int GetId() const \
{ \
return GetIdStatic(); \
}
#define CP_SIGNAL_DECLERATION(SignalClass) \ #define CP_REGISTER_SIGNAL static inline Copium::Uuid UUID = Copium::Uuid()
static int GetIdStatic(); \
int GetId() const override
#define CP_SIGNAL_DEFINITION(SignalClass) \
int SignalClass::GetIdStatic() \
{ \
static int id = GetAllocatedId(); \
return id; \
} \
\
int SignalClass::GetId() const \
{ \
return GetIdStatic(); \
}
namespace Copium namespace Copium
{ {
class Signal class Signal
{ {
private:
static inline int allocatedIds = 0;
public: public:
Signal() = default; Signal() = default;
virtual ~Signal() = default;
virtual int GetId() const = 0; const Uuid& GetUuid() const
{
return uuid;
}
protected: template <typename T>
static int GetAllocatedId(); static void ValidateSignal()
{
static_assert(std::is_base_of_v<Signal, T>, "ValidateSignal : Given T is does not have Signal as base class");
static_assert(
has_static_uuid<T>(),
"ValidateSignal : Given T has not defined a UUID. Did you add CP_REGISTER_SIGNAL to your Signal class?");
}
private:
friend class ECSManager;
Uuid uuid;
template <typename, typename = void>
struct has_static_uuid : std::false_type
{
}; };
template <typename T>
struct has_static_uuid<T, std::void_t<decltype(T::UUID)>> : std::true_type
{
};
};
} }
+34 -24
View File
@@ -3,44 +3,54 @@
#include <set> #include <set>
#include "copium/ecs/ECSManager.h" #include "copium/ecs/ECSManager.h"
#include "copium/ecs/Entity.h" #include "copium/ecs/Signal.h"
#include "copium/ecs/SystemBase.h"
namespace Copium namespace Copium
{ {
template <typename... Components> class System
class System : public SystemBase
{ {
public: public:
void Run() override virtual ~System() = default;
virtual void Run() = 0;
virtual void HandleSignal(const Signal& signal)
{ {
manager->Each<Components...>([&](EntityId entityId, Components&... components)
{ RunEntity(Entity{manager, entityId}, components...); });
} }
void Run(const Signal& signal) override const bool IsSignalSubscribed(const Uuid& uuid) const
{ {
manager->Each<Components...>([&](EntityId entityId, Components&... components) return subscribedSignals.count(uuid) != 0;
{ RunEntity(signal, Entity{manager, entityId}, components...); });
} }
virtual void RunEntity(Entity entity, Components&... components) {}; template <typename S>
virtual void RunEntity(const Signal& signal, Entity entity, Components&... components) {}; void SubscribeToSignal()
// TODO: Not sure if this is the way entities should be validated
std::set<EntityId> loggedEntities;
bool ValidateEntity(Entity entity)
{ {
if (entity && entity.HasComponents<Components...>()) Signal::ValidateSignal<S>();
subscribedSignals.emplace(S::UUID);
}
template <typename S>
void UnsubscribeToSignal()
{ {
loggedEntities.erase(entity); Signal::ValidateSignal<S>();
return true; subscribedSignals.erase(S::UUID);
} }
if (loggedEntities.find(entity) == loggedEntities.end())
CP_WARN("Invalid Entity"); void SetECSManager(ECSManager* manager)
loggedEntities.emplace(entity); {
return false; this->manager = manager;
} }
template <typename S, typename... Args>
void SendSignal(const Args&... args)
{
manager->SendSignal<S>(args...);
}
protected:
ECSManager* manager;
private:
std::set<Uuid> subscribedSignals{};
}; };
} }
-22
View File
@@ -1,22 +0,0 @@
#pragma once
#include "copium/ecs/Signal.h"
namespace Copium
{
class ECSManager;
class SystemBase
{
friend class SystemPool;
protected:
ECSManager* manager;
public:
virtual ~SystemBase() = default;
virtual void Run() = 0;
virtual void Run(const Signal& signal) = 0;
};
}
+23 -2
View File
@@ -10,12 +10,33 @@ namespace Copium
{ {
} }
void SystemOrderer::Before(const std::type_index& otherSystemId) void SystemOrderer::CommitOrdering()
{
for (const auto& [orderOperation, systemId] : orderQueue)
{
switch (orderOperation)
{
case OrderOperation::Before:
{
CommitBefore(systemId);
break;
}
case OrderOperation::After:
{
CommitAfter(systemId);
break;
}
}
}
orderQueue.clear();
}
void SystemOrderer::CommitBefore(const std::type_index& otherSystemId)
{ {
systemPool->MoveSystemBefore(systemId, otherSystemId); systemPool->MoveSystemBefore(systemId, otherSystemId);
} }
void SystemOrderer::After(const std::type_index& otherSystemId) void SystemOrderer::CommitAfter(const std::type_index& otherSystemId)
{ {
systemPool->MoveSystemAfter(systemId, otherSystemId); systemPool->MoveSystemAfter(systemId, otherSystemId);
} }
+21 -10
View File
@@ -1,6 +1,7 @@
#pragma once #pragma once
#include <typeindex> #include <typeindex>
#include <vector>
namespace Copium namespace Copium
{ {
@@ -8,27 +9,37 @@ namespace Copium
class SystemOrderer class SystemOrderer
{ {
private:
std::type_index systemId;
SystemPool* systemPool = nullptr;
public: public:
SystemOrderer(std::type_index systemId, SystemPool* systemPool); SystemOrderer(std::type_index systemId, SystemPool* systemPool);
template <typename Other> template <typename System>
void Before() void Before()
{ {
Before(typeid(Other)); orderQueue.emplace_back(OrderOperation::Before, typeid(System));
} }
template <typename Other> template <typename System>
void After() void After()
{ {
After(typeid(Other)); orderQueue.emplace_back(OrderOperation::After, typeid(System));
} }
private: private:
void Before(const std::type_index& otherSystemId); enum class OrderOperation
void After(const std::type_index& otherSystemId); {
Before,
After
};
std::type_index systemId;
SystemPool* systemPool = nullptr;
std::vector<std::pair<OrderOperation, std::type_index>> orderQueue;
private:
friend class SystemPool;
void CommitOrdering();
void CommitBefore(const std::type_index& otherSystemId);
void CommitAfter(const std::type_index& otherSystemId);
}; };
} }
+107 -21
View File
@@ -2,6 +2,8 @@
#include <algorithm> #include <algorithm>
#include "copium/ecs/System.h"
namespace Copium namespace Copium
{ {
SystemPool::SystemPool(ECSManager* manager) SystemPool::SystemPool(ECSManager* manager)
@@ -19,28 +21,56 @@ namespace Copium
systems.clear(); systems.clear();
} }
SystemOrderer SystemPool::AddSystem(const std::type_index& systemId, SystemBase* system) SystemOrderer& SystemPool::AddSystem(const std::type_index& systemId, System* system)
{ {
system->manager = manager; system->SetECSManager(manager);
CP_ASSERT(systems.find(systemId) == systems.end(), "System already exist in Ecs"); addQueue.emplace_back(systemId, system, SystemOrderer{systemId, this});
systems.emplace(systemId, system); queueOperationOrder.emplace_back(QueueOperation::Add);
systemOrder.emplace_back(system); return std::get<2>(addQueue.back());
return SystemOrderer{systemId, this};
} }
void SystemPool::RemoveSystem(const std::type_index& systemId) void SystemPool::RemoveSystem(const std::type_index& systemId)
{ {
auto it = systems.find(systemId); removeQueue.emplace_back(systemId);
if (it == systems.end()) queueOperationOrder.emplace_back(QueueOperation::Remove);
{
CP_WARN("System does not exist in Ecs");
return;
} }
auto itOrder = std::find(systemOrder.begin(), systemOrder.end(), it->second); void SystemPool::CommitUpdates()
delete it->second; {
systems.erase(it); if (queueOperationOrder.empty())
systemOrder.erase(itOrder); return;
CP_ASSERT(queueOperationOrder.size() == addQueue.size() + removeQueue.size(),
"queueOperationOrder size=%zu doesn't match the sum of the addQueue size=%zu and removeQueue size=%zu, "
"which is %zu",
queueOperationOrder.size(),
addQueue.size(),
removeQueue.size(),
addQueue.size() + removeQueue.size());
int addQueueIndex = 0;
int removeQueueIndex = 0;
for (QueueOperation queueOperation : queueOperationOrder)
{
switch (queueOperation)
{
case QueueOperation::Add:
{
CommitAddSystem(addQueueIndex);
addQueueIndex++;
break;
}
case QueueOperation::Remove:
{
CommitRemoveSystem(removeQueueIndex);
removeQueueIndex++;
break;
}
}
}
removeQueue.clear();
addQueue.clear();
queueOperationOrder.clear();
} }
void SystemPool::Update() void SystemPool::Update()
@@ -51,20 +81,44 @@ namespace Copium
} }
} }
void SystemPool::Update(const Signal& signal) void SystemPool::SendSignal(Signal* signal)
{ {
signalQueue.emplace(signal);
}
void SystemPool::CommitSignals()
{
while (!signalQueue.empty())
{
auto& signal = signalQueue.front();
for (auto& system : systemOrder) for (auto& system : systemOrder)
{ {
system->Run(signal); if (system->IsSignalSubscribed(signal->GetUuid()))
{
system->HandleSignal(*signal);
} }
} }
delete signal;
signalQueue.pop();
}
}
size_t SystemPool::Size() const
{
return systemOrder.size();
}
bool SystemPool::IsEmpty() const
{
return systemOrder.empty();
}
void SystemPool::MoveSystemAfter(const std::type_index& systemId, const std::type_index& afterSystemId) void SystemPool::MoveSystemAfter(const std::type_index& systemId, const std::type_index& afterSystemId)
{ {
auto it1 = systems.find(systemId); auto it1 = systems.find(systemId);
CP_ASSERT(it1 != systems.end(), "System does not exist in SystemPool"); CP_ASSERT(it1 != systems.end(), "System with typeid=%s does not exist in SystemPool", systemId.name());
auto it2 = systems.find(afterSystemId); auto it2 = systems.find(afterSystemId);
CP_ASSERT(it2 != systems.end(), "System does not exist in SystemPool"); CP_ASSERT(it2 != systems.end(), "System with typeid=%s does not exist in SystemPool", afterSystemId.name());
auto itSystemId = std::find(systemOrder.rbegin(), systemOrder.rend(), it1->second); auto itSystemId = std::find(systemOrder.rbegin(), systemOrder.rend(), it1->second);
auto itAfterSystemId = std::find(systemOrder.rbegin(), systemOrder.rend(), it2->second); auto itAfterSystemId = std::find(systemOrder.rbegin(), systemOrder.rend(), it2->second);
@@ -74,13 +128,45 @@ namespace Copium
void SystemPool::MoveSystemBefore(const std::type_index& systemId, const std::type_index& beforeSystemId) void SystemPool::MoveSystemBefore(const std::type_index& systemId, const std::type_index& beforeSystemId)
{ {
auto it1 = systems.find(systemId); auto it1 = systems.find(systemId);
CP_ASSERT(it1 != systems.end(), "System does not exist in SystemPool"); CP_ASSERT(it1 != systems.end(), "System with typeid=%s does not exist in SystemPool", systemId.name());
auto it2 = systems.find(beforeSystemId); auto it2 = systems.find(beforeSystemId);
CP_ASSERT(it2 != systems.end(), "System does not exist in SystemPool"); CP_ASSERT(it2 != systems.end(), "System with typeid=%s does not exist in SystemPool", beforeSystemId.name());
auto itSystemId = std::find(systemOrder.rbegin(), systemOrder.rend(), it1->second); auto itSystemId = std::find(systemOrder.rbegin(), systemOrder.rend(), it1->second);
auto itBeforeSystemId = std::find(systemOrder.rbegin(), systemOrder.rend(), it2->second); auto itBeforeSystemId = std::find(systemOrder.rbegin(), systemOrder.rend(), it2->second);
std::rotate(itSystemId, itSystemId + 1, itBeforeSystemId + 1); std::rotate(itSystemId, itSystemId + 1, itBeforeSystemId + 1);
} }
void SystemPool::CommitAddSystem(int queueIndex)
{
CP_ASSERT(
queueIndex < addQueue.size(), "queueIndex=%d is greater than the addQueueSize=%d", queueIndex, addQueue.size());
auto& [systemId, system, ordering] = addQueue[queueIndex];
CP_ASSERT(systems.find(systemId) == systems.end(), "System with typeid=%s already exists in SystemPool");
systems.emplace(systemId, system);
systemOrder.emplace_back(system);
ordering.CommitOrdering();
}
void SystemPool::CommitRemoveSystem(int queueIndex)
{
CP_ASSERT(queueIndex < removeQueue.size(),
"queueIndex=%d is greater than the removeQueueSize=%d",
queueIndex,
removeQueue.size());
const auto& systemId = removeQueue[queueIndex];
auto it = systems.find(systemId);
CP_ASSERT(it != systems.end(), "System with typeid=%s does not exist in SystemPool", systemId.name());
auto itOrder = std::find(systemOrder.begin(), systemOrder.end(), it->second);
CP_ASSERT(itOrder != systemOrder.end(), "System with typeid=%s does not exist in systemOrder", systemId.name());
delete it->second;
systems.erase(it);
systemOrder.erase(itOrder);
}
} }
+28 -8
View File
@@ -1,37 +1,57 @@
#pragma once #pragma once
#include <map> #include <map>
#include <queue>
#include <typeindex> #include <typeindex>
#include <vector> #include <vector>
#include "copium/ecs/Signal.h" #include "copium/ecs/Signal.h"
#include "copium/ecs/SystemBase.h"
#include "copium/ecs/SystemOrderer.h" #include "copium/ecs/SystemOrderer.h"
#include "copium/util/Common.h" #include "copium/util/Common.h"
namespace Copium namespace Copium
{ {
class ECSManager; class ECSManager;
class System;
class SystemPool final class SystemPool final
{ {
CP_DELETE_COPY_AND_MOVE_CTOR(SystemPool); CP_DELETE_COPY_AND_MOVE_CTOR(SystemPool);
private:
ECSManager* manager;
std::map<std::type_index, SystemBase*> systems;
std::vector<SystemBase*> systemOrder;
public: public:
SystemPool(ECSManager* manager); SystemPool(ECSManager* manager);
~SystemPool(); ~SystemPool();
SystemOrderer AddSystem(const std::type_index& systemId, SystemBase* system); SystemOrderer& AddSystem(const std::type_index& systemId, System* system);
void RemoveSystem(const std::type_index& systemId); void RemoveSystem(const std::type_index& systemId);
void Update(); void Update();
void Update(const Signal& signal); void SendSignal(Signal* signal);
void CommitSignals();
void CommitUpdates();
size_t Size() const;
bool IsEmpty() const;
void MoveSystemAfter(const std::type_index& systemId, const std::type_index& afterSystemId); void MoveSystemAfter(const std::type_index& systemId, const std::type_index& afterSystemId);
void MoveSystemBefore(const std::type_index& systemId, const std::type_index& beforeSystemId); void MoveSystemBefore(const std::type_index& systemId, const std::type_index& beforeSystemId);
private:
ECSManager* manager;
std::map<std::type_index, System*> systems;
std::vector<System*> systemOrder;
enum class QueueOperation
{
Add,
Remove
};
std::vector<QueueOperation> queueOperationOrder;
std::vector<std::tuple<std::type_index, System*, SystemOrderer>> addQueue;
std::vector<std::type_index> removeQueue;
std::queue<Signal*> signalQueue;
void CommitAddSystem(int queueIndex);
void CommitRemoveSystem(int queueIndex);
}; };
} }
+113
View File
@@ -0,0 +1,113 @@
#pragma once
#include "copium/ecs/ECSManager.h"
#include "copium/ecs/Entity.h"
namespace Copium
{
template <typename Component, typename... Components>
struct View
{
class Iterator
{
public:
std::tuple<Entity, Component&, Components&...> operator*()
{
EntityId entityId{entities[index]};
CP_ASSERT((manager->HasComponents<Component, Components...>(entityId)),
"entity doesn't contains all components");
return std::forward_as_tuple<Entity, Component&, Components&...>(
Entity{manager, entityId}, firstPool->At(index), manager->GetComponent<Components>(entityId)...);
}
Iterator& operator++()
{
++index;
FindNextEntity();
return *this;
}
Iterator operator++(int)
{
Iterator it = *this;
++*this;
return it;
}
Iterator operator+(size_t advance)
{
Iterator it = *this;
for (int i = 0; i < advance; i++)
++it;
return it;
}
bool operator==(const Iterator& other) const
{
return index == other.index;
}
bool operator!=(const Iterator& other) const
{
return !(*this == other);
}
private:
friend class View;
ECSManager* manager;
ComponentPool<std::remove_const_t<Component>>* firstPool;
const std::vector<EntityId>& entities;
int index;
Iterator(ECSManager* manager, ComponentPool<std::remove_const_t<Component>>* firstPool)
: manager{manager},
firstPool{firstPool},
entities{firstPool ? firstPool->GetEntities() : ECSManager::emptyEntities},
index{0}
{
}
static Iterator Begin(ECSManager* manager)
{
Iterator iterator{manager, manager->GetComponentPool<Component>()};
iterator.FindNextEntity();
return iterator;
}
static Iterator End(ECSManager* manager)
{
Iterator iterator{manager, manager->GetComponentPool<Component>()};
if (iterator.firstPool)
iterator.index = iterator.firstPool->GetEntities().size();
return iterator;
}
void FindNextEntity()
{
while (index < entities.size() && !manager->HasComponents<Components...>(entities[index]))
{
++index;
}
}
};
View(ECSManager* manager)
: manager{manager}
{
}
Iterator begin()
{
return Iterator::Begin(manager);
}
Iterator end()
{
return Iterator::End(manager);
}
private:
ECSManager* manager;
};
}
@@ -1,16 +0,0 @@
#include "copium/event/EventSignal.h"
namespace Copium
{
EventSignal::EventSignal(const Event& event)
: event{event}
{
}
const Event& EventSignal::GetEvent() const
{
return event;
}
CP_SIGNAL_DEFINITION(EventSignal);
}
@@ -1,22 +0,0 @@
#pragma once
#include "copium/ecs/Signal.h"
#include "copium/event/Event.h"
namespace Copium
{
class EventSignal : public Signal
{
private:
const Event& event;
public:
EventSignal(const Event& event);
const Event& GetEvent() const;
CP_SIGNAL_DECLERATION(EventSignal);
};
}
@@ -102,6 +102,58 @@ namespace Copium
return offset; return offset;
} }
glm::vec2 Renderer::TextUi(
const std::string& str, const glm::vec2& position, const Font& font, float size, const glm::vec3& color)
{
glm::vec2 offset = position;
for (char c : str)
{
if (c == ' ')
{
const Glyph& glyph = font.GetGlyph(c);
offset.x += glyph.advance * size;
continue;
}
else if (c == '\t')
{
const Glyph& glyph = font.GetGlyph(' ');
offset.x += glyph.advance * size * 4;
continue;
}
else if (c == '\n')
{
offset.y += font.GetLineHeight() * size;
offset.x = position.x;
continue;
}
const Glyph& glyph = font.GetGlyph(c);
AllocateQuad();
int texIndex = AllocateSampler(font);
AddVertex(offset + glm::vec2{glyph.boundingBox.l * size, -glyph.boundingBox.t * size},
color,
texIndex,
glm::vec2{glyph.texCoordBoundingBox.l, glyph.texCoordBoundingBox.t},
RendererVertex::TYPE_TEXT);
AddVertex(offset + glm::vec2{glyph.boundingBox.l * size, -glyph.boundingBox.b * size},
color,
texIndex,
glm::vec2{glyph.texCoordBoundingBox.l, glyph.texCoordBoundingBox.b},
RendererVertex::TYPE_TEXT);
AddVertex(offset + glm::vec2{glyph.boundingBox.r * size, -glyph.boundingBox.b * size},
color,
texIndex,
glm::vec2{glyph.texCoordBoundingBox.r, glyph.texCoordBoundingBox.b},
RendererVertex::TYPE_TEXT);
AddVertex(offset + glm::vec2{glyph.boundingBox.r * size, -glyph.boundingBox.t * size},
color,
texIndex,
glm::vec2{glyph.texCoordBoundingBox.r, glyph.texCoordBoundingBox.t},
RendererVertex::TYPE_TEXT);
offset.x += glyph.advance * size;
}
return offset;
}
void Renderer::AddVertex( void Renderer::AddVertex(
const glm::vec2& position, const glm::vec3& color, int texindex, const glm::vec2& texCoord, int type) const glm::vec2& position, const glm::vec3& color, int texindex, const glm::vec2& texCoord, int type)
{ {
@@ -46,6 +46,12 @@ namespace Copium
const Font& font, const Font& font,
float size, float size,
const glm::vec3& color = glm::vec3(1, 1, 1)); const glm::vec3& color = glm::vec3(1, 1, 1));
// Returns the position where the text rendering ended
glm::vec2 TextUi(const std::string& str,
const glm::vec2& position,
const Font& font,
float size,
const glm::vec3& color = glm::vec3(1, 1, 1));
void Begin(CommandBuffer& commandBuffer); void Begin(CommandBuffer& commandBuffer);
void End(); void End();
@@ -0,0 +1,56 @@
#pragma once
#include <functional>
class GenericType
{
public:
template <typename T, typename... Args>
static GenericType Create(const Args&... args)
{
GenericType type;
type.data = new T{args...};
type.deleter = [](void* data) { delete (T*)data; };
return type;
}
GenericType(GenericType&& type)
: data{type.data},
deleter{type.deleter}
{
type.data = nullptr;
}
GenericType& operator=(GenericType&& type)
{
this->data = type.data;
this->deleter = type.deleter;
type.data = nullptr;
return *this;
}
~GenericType()
{
if (data)
{
deleter(data);
}
data = nullptr;
}
template <typename T>
T& Get()
{
return *(T*)data;
}
private:
GenericType() = default;
GenericType(const GenericType& type) = delete;
GenericType& operator=(const GenericType& type) = delete;
void* data;
std::function<void(void*)> deleter;
};
+5
View File
@@ -26,4 +26,9 @@ namespace Copium
return elapsedTime; return elapsedTime;
} }
void Timer::OffsetSeconds(int time)
{
startTime += std::chrono::seconds(time);
}
} }
+1
View File
@@ -15,5 +15,6 @@ namespace Copium
void Start(); void Start();
double Elapsed(); double Elapsed();
double ElapsedRestart(); double ElapsedRestart();
void OffsetSeconds(int time);
}; };
} }