我目前正在学习实体-组件-系统架构数据驱动设计,以此来应对糟糕的 OOP 设计并将数据获取优化为 CPU 友好的操作。
我正在尝试编写一个简单的 ECS 系统来尝试遵循这些原则:
- 通过将实体/组件存储在连续的内存块中并最大限度地减少缓存未命中,对 CPU 友好
- 未设置系统中实体/组件/系统数量的上限
到目前为止,我的基本实现如下:
Entity
m_ID
每个新创建的实体都会增加一个计数成员的类。用于包含子实体的
。用于包含实体使用的组件的签名。我选择了,因为我希望能够作为在运行时添加/删除组件的要求,并且我不希望为实体拥有的组件大小设置上限,因此我无法在编译时知道大小,所以我决定使用将每个存储为 1 位的专业化,这样可以节省一些空间并允许增长。其中键是组件的 ID,值是组件 ID 到驻留在类中的组件向量的映射的索引。std::vector<Entity*>
std::vector<bool>
std::vector<bool>
std::bitset<S>
std::vector<bool>
bool
std::unordered_map<unsigned int, unsigned int>
std::vector<unsigned int, std::vector<ComponentBase>>
ECSManager
Component
每个组件类型具有唯一 ID 的类,使用模板Component<T>::ID
为每个组件类型生成唯一 ID。
System
包含std::vector<bool>
负责保存系统想要操作的实体的签名的类取决于它们是否具有足够的组件签名。
现在我确定我的实现并不接近我的要求,例如,我不确定它是否像 ECS 那样对 CPU 友好,因为我严重依赖std::unordered_map
链表实现的。但老实说,我根本不确定,所以这就是为什么我在这里发布我的实现,希望得到关于什么是好的,什么不是以及我可以如何让它变得更好的反馈。
ComponentBase.h
:
#pragma once
#include <atomic>
static std::atomic<unsigned int> s_CurrentId;
class ComponentBase
{
public:
inline static unsigned GetID() { return ++s_CurrentId; }
};
Component.h
:
#pragma once
#include "ComponentBase.h"
template <typename T>
class Component : public ComponentBase
{
public:
static const unsigned int ID = ComponentBase::GetID();
};
Entity.h
:
#pragma once
#include <vector>
#include <unordered_map>
class Entity
{
private:
unsigned int m_Id;
std::vector<Entity*> m_Children;
std::vector<bool> m_Signature;
std::unordered_map<unsigned int, unsigned int> m_ComponentsIndex;
protected:
Entity();
friend class ComponentManager;
friend class Scene;
public:
Entity(const Entity& other) = delete;
virtual ~Entity();
Entity& operator=(const Entity& other) = delete;
inline unsigned int GetID() { return m_Id; }
void AddChild(Entity* entity);
inline const std::vector<Entity*>& GetChildren() const { return m_Children; }
void AddComponent(unsigned int componentID, unsigned int index);
void RemoveComponent(unsigned int componentID);
inline const std::vector<bool>& GetSignature() const { return m_Signature; }
inline const std::unordered_map<unsigned int, unsigned int>& GetComponentsIndex() const { return m_ComponentsIndex; }
};
Entity.cpp
:
#include "Entity.h"
#include <atomic>
static std::atomic<unsigned int> s_CurrentId;
Entity::Entity()
: m_Id(s_CurrentId++)
{
}
Entity::~Entity()
{
for (const auto& child : m_Children)
delete child;
}
void Entity::AddChild(Entity* entity)
{
m_Children.push_back(entity);
}
void Entity::AddComponent(unsigned int componentID, unsigned int index)
{
m_Signature[componentID] = true;
m_ComponentsIndex[componentID] = index;
}
void Entity::RemoveComponent(unsigned int componentID)
{
m_Signature[componentID] = false;
m_ComponentsIndex.erase(componentID);
}
System.h
:
#pragma once
#include <vector>
#include "Entity.h"
class System
{
private:
std::vector<bool> m_ComponentIDs;
protected:
System(std::vector<unsigned int>& componentIDs);
public:
virtual ~System();
virtual void Update() = 0;
};
System.cpp
:
#include "System.h"
System::System(std::vector<unsigned int>& componentIDs)
{
for (const auto& componentID : componentIDs)
m_ComponentIDs[componentID] = true;
}
System::~System()
{
}
ECSManager.h
:
#pragma once
#include <vector>
#include <unordered_map>
#include "../Entity.h"
#include "ComponentBase.h"
class ECSManager
{
private:
std::unordered_map<unsigned int, std::vector<ComponentBase>> m_Components;
std::unordered_map<unsigned int, Entity> m_Entities;
private:
ECSManager();
public:
const Entity& CreateEntity()
{
Entity e;
m_Entities.emplace(e.GetID(), std::move(e));
return m_Entities[e.GetID()];
}
template <typename TComponent, typename... Args>
void AddComponent(unsigned int entityId, Args&&... args)
{
unsigned int componentID = TComponent::ID;
if (m_Components[componentID] == m_Components.end())
m_Components[componentID] = std::vector<TComponent>();
m_Components[componentID].push_back(T(std::forward<Args>(args)...));
m_Entities[entityId].AddComponent(componentID, m_Components[componentID].size() - 1);
if (m_Signatures[m_Entities[entityId].GetSignature()] == m_Signatures.end())
m_Signatures[m_Entities[entityId].GetSignature()] = std::vector<unsigned int>();
m_Signatures[m_Entities[entityId].GetSignature()].push_back(entityId);
}
template <typename TComponent>
TComponent& GetComponent(Entity& entity)
{
return m_Components[TComponent::ID][entity.GetComponentsIndex()[TComponent::ID]];
}
template <typename TComponent>
std::vector<TComponent>& GetComponents()
{
unsigned int componentID = TComponent::ID;
return m_Components[componentID];
}
std::vector<Entity&> GetEntities(std::vector<bool> componentIDs)
{
std::vector<Entity&> entities;
for (auto& entity : m_Entities)
{
const auto& entitySignature = entity.second.GetSignature();
if (componentIDs.size() > entitySignature.size())
continue;
bool equal = true;
for (std::size_t i = 0; i != componentIDs.size(); i++)
{
if (componentIDs[i] & entitySignature[i] == 0)
{
equal = false;
break;
}
}
if (!equal)
continue;
entities.push_back(entity.second);
}
return entities;
}
};