refactor: ECS 迁移到 entt,移除自研 Registry
- Entity = entt::entity(内部自带版本号),Registry = entt::registry - Scene 对外接口不变(AddComponent/GetComponent/Each/延迟销毁/UUID), Renderer 与 App 层零改动完成迁移 - Each 转发 entt view(自动最小池遍历),延迟销毁队列保留 - 删除自研 Registry.h/Entity.h
This commit is contained in:
@@ -1,32 +0,0 @@
|
||||
//
|
||||
// Created by atdunbg on 2026/9/22.
|
||||
//
|
||||
// Entity:实体句柄 = (池内索引, 生成号)。
|
||||
//
|
||||
// 生成号用于甄别悬空引用:实体销毁后索引会回收复用,
|
||||
// 旧的 Entity 句柄因 generation 不匹配会被 Registry 拒绝,
|
||||
// 从机制上杜绝"引用到复用后的错误实体"这类隐式 bug。
|
||||
//
|
||||
|
||||
#ifndef HELLOVULKAN_ENTITY_H
|
||||
#define HELLOVULKAN_ENTITY_H
|
||||
|
||||
#include <Engine/Core/Types.h>
|
||||
|
||||
namespace Engine {
|
||||
|
||||
struct Entity {
|
||||
static constexpr u32 kInvalid = 0xFFFFFFFF;
|
||||
|
||||
u32 index = kInvalid; // 实体槽位索引
|
||||
u32 generation = 0; // 槽位复用计数
|
||||
|
||||
bool Valid() const { return index != kInvalid; }
|
||||
explicit operator bool() const { return Valid(); }
|
||||
bool operator==(const Entity& o) const = default;
|
||||
bool operator!=(const Entity& o) const = default;
|
||||
};
|
||||
|
||||
} // namespace Engine
|
||||
|
||||
#endif //HELLOVULKAN_ENTITY_H
|
||||
@@ -1,312 +0,0 @@
|
||||
//
|
||||
// Created by atdunbg on 2026/9/22.
|
||||
//
|
||||
// Registry:稀疏集 ECS 核心。
|
||||
//
|
||||
// 数据结构(与 EnTT 同族,自研实现):
|
||||
// - 每个组件类型一个 ComponentPool<T>:sparse 索引表 + dense 实体/组件数组
|
||||
// - Add/Remove/Get 全部 O(1);Remove 用 swap-remove(O(1),代价是
|
||||
// dense 顺序会变——渲染遍历只要求"每帧确定",不要求跨帧稳定)
|
||||
// - Each<A,B,...> 以最小的池为主表遍历,其余池做 O(1) Contains 探测,
|
||||
// 复杂度 O(min(|A|,|B|,...))——大场景下的关键保证
|
||||
//
|
||||
// 约束(引擎级正确性):
|
||||
// - 悬空句柄:generation 不匹配直接 FATAL,不静默
|
||||
// - 遍历中禁止结构变更(Add/Remove 组件、Create/Destroy 实体),
|
||||
// 会使池失效——销毁走延迟队列(Scene::DestroyEntity → FlushDestroyQueue)
|
||||
// - 全程无虚调用开销在热路径上(Get/Add 直接走模板池)
|
||||
//
|
||||
|
||||
#ifndef HELLOVULKAN_REGISTRY_H
|
||||
#define HELLOVULKAN_REGISTRY_H
|
||||
|
||||
#include <Engine/Core/Types.h>
|
||||
#include <Engine/Core/Log.h>
|
||||
#include <Engine/ECS/Entity.h>
|
||||
|
||||
#include <array>
|
||||
#include <tuple>
|
||||
#include <vector>
|
||||
#include <memory>
|
||||
|
||||
namespace Engine::ECS {
|
||||
|
||||
// ---------- 组件类型 ID(编译期静态分配) ----------
|
||||
|
||||
using ComponentTypeId = u32;
|
||||
|
||||
namespace detail {
|
||||
inline ComponentTypeId NextComponentTypeId()
|
||||
{
|
||||
static ComponentTypeId counter = 0;
|
||||
return counter++;
|
||||
}
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
ComponentTypeId ComponentTypeIdOf()
|
||||
{
|
||||
static const ComponentTypeId id = detail::NextComponentTypeId();
|
||||
return id;
|
||||
}
|
||||
|
||||
// ---------- 类型擦除池接口(Registry 管理生命周期 + 视图主表遍历用) ----------
|
||||
|
||||
class IPool {
|
||||
public:
|
||||
virtual ~IPool() = default;
|
||||
virtual u32 Size() const = 0;
|
||||
virtual Entity EntityAt(u32 denseIndex) const = 0;
|
||||
virtual bool Remove(Entity e) = 0; // 不存在返回 false
|
||||
};
|
||||
|
||||
// ---------- 稀疏集组件池 ----------
|
||||
|
||||
template<typename T>
|
||||
class ComponentPool final : public IPool {
|
||||
public:
|
||||
static constexpr u32 kInvalid = 0xFFFFFFFF;
|
||||
|
||||
T& Add(Entity e, T component)
|
||||
{
|
||||
if (Contains(e)) {
|
||||
ENGINE_FATAL("ComponentPool: entity ({},{}) already has this component",
|
||||
e.index, e.generation);
|
||||
}
|
||||
if (e.index >= m_Sparse.size()) {
|
||||
m_Sparse.resize(e.index + 1, kInvalid);
|
||||
}
|
||||
|
||||
m_Sparse[e.index] = static_cast<u32>(m_Components.size());
|
||||
m_Entities.push_back(e);
|
||||
m_Components.push_back(std::move(component));
|
||||
return m_Components.back();
|
||||
}
|
||||
|
||||
bool Contains(Entity e) const
|
||||
{
|
||||
return e.index < m_Sparse.size() &&
|
||||
m_Sparse[e.index] != kInvalid &&
|
||||
m_Entities[m_Sparse[e.index]] == e;
|
||||
}
|
||||
|
||||
T& Get(Entity e)
|
||||
{
|
||||
if (!Contains(e)) {
|
||||
ENGINE_FATAL("ComponentPool: entity ({},{}) does not have this component",
|
||||
e.index, e.generation);
|
||||
}
|
||||
return m_Components[m_Sparse[e.index]];
|
||||
}
|
||||
|
||||
const T& Get(Entity e) const
|
||||
{
|
||||
return const_cast<ComponentPool*>(this)->Get(e);
|
||||
}
|
||||
|
||||
bool Remove(Entity e) override
|
||||
{
|
||||
if (!Contains(e)) return false;
|
||||
|
||||
const u32 denseIdx = m_Sparse[e.index];
|
||||
const u32 lastIdx = static_cast<u32>(m_Components.size()) - 1;
|
||||
|
||||
if (denseIdx != lastIdx) {
|
||||
// swap-remove:尾元素补位
|
||||
m_Entities[denseIdx] = m_Entities[lastIdx];
|
||||
m_Components[denseIdx] = std::move(m_Components[lastIdx]);
|
||||
m_Sparse[m_Entities[denseIdx].index] = denseIdx;
|
||||
}
|
||||
|
||||
m_Sparse[e.index] = kInvalid;
|
||||
m_Entities.pop_back();
|
||||
m_Components.pop_back();
|
||||
return true;
|
||||
}
|
||||
|
||||
u32 Size() const override { return static_cast<u32>(m_Components.size()); }
|
||||
Entity EntityAt(u32 denseIndex) const override { return m_Entities[denseIndex]; }
|
||||
|
||||
private:
|
||||
std::vector<u32> m_Sparse; // 实体索引 -> dense 下标(kInvalid = 无)
|
||||
std::vector<Entity> m_Entities; // dense:与 m_Components 平行
|
||||
std::vector<T> m_Components; // dense:组件数据(连续内存,缓存友好)
|
||||
};
|
||||
|
||||
// ---------- Registry ----------
|
||||
|
||||
class Registry {
|
||||
public:
|
||||
Registry() = default;
|
||||
~Registry() = default;
|
||||
|
||||
Registry(const Registry&) = delete;
|
||||
Registry& operator=(const Registry&) = delete;
|
||||
|
||||
// ---- 实体生命周期 ----
|
||||
|
||||
Entity Create()
|
||||
{
|
||||
if (!m_FreeIndices.empty()) {
|
||||
const u32 index = m_FreeIndices.back();
|
||||
m_FreeIndices.pop_back();
|
||||
m_Records[index].alive = true; // generation 已在 Destroy 时递增
|
||||
return Entity{index, m_Records[index].generation};
|
||||
}
|
||||
|
||||
m_Records.push_back({0, true});
|
||||
return Entity{static_cast<u32>(m_Records.size() - 1), 0};
|
||||
}
|
||||
|
||||
void Destroy(Entity e)
|
||||
{
|
||||
if (!IsAlive(e)) {
|
||||
ENGINE_FATAL("Registry::Destroy: stale entity ({},{})",
|
||||
e.index, e.generation);
|
||||
}
|
||||
|
||||
for (auto& pool : m_Pools) {
|
||||
if (pool) pool->Remove(e); // 不在池中就跳过
|
||||
}
|
||||
|
||||
auto& record = m_Records[e.index];
|
||||
record.alive = false;
|
||||
++record.generation;
|
||||
m_FreeIndices.push_back(e.index);
|
||||
}
|
||||
|
||||
bool IsAlive(Entity e) const
|
||||
{
|
||||
return e.Valid() && e.index < m_Records.size() &&
|
||||
m_Records[e.index].alive &&
|
||||
m_Records[e.index].generation == e.generation;
|
||||
}
|
||||
|
||||
// ---- 组件操作 ----
|
||||
|
||||
template<typename T, typename... Args>
|
||||
T& Add(Entity e, Args&&... args)
|
||||
{
|
||||
if (!IsAlive(e)) {
|
||||
ENGINE_FATAL("Registry::Add: stale entity ({},{})", e.index, e.generation);
|
||||
}
|
||||
return AssurePool<T>().Add(e, T{std::forward<Args>(args)...});
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
T& Get(Entity e)
|
||||
{
|
||||
if (!IsAlive(e)) {
|
||||
ENGINE_FATAL("Registry::Get: stale entity ({},{})", e.index, e.generation);
|
||||
}
|
||||
auto* pool = GetPool<T>();
|
||||
if (!pool) {
|
||||
ENGINE_FATAL("Registry::Get: no component pool of this type exists");
|
||||
}
|
||||
return pool->Get(e);
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
T* TryGet(Entity e)
|
||||
{
|
||||
if (!IsAlive(e)) return nullptr;
|
||||
auto* pool = GetPool<T>();
|
||||
return (pool && pool->Contains(e)) ? &pool->Get(e) : nullptr;
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
bool Has(Entity e)
|
||||
{
|
||||
if (!IsAlive(e)) return false;
|
||||
auto* pool = GetPool<T>();
|
||||
return pool && pool->Contains(e);
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
bool Remove(Entity e)
|
||||
{
|
||||
if (!IsAlive(e)) return false;
|
||||
auto* pool = GetPool<T>();
|
||||
return pool && pool->Remove(e);
|
||||
}
|
||||
|
||||
// ---- 遍历(视图)----
|
||||
// fn(Entity, Ts&...);以最小的池为主表,复杂度 O(min pool size)。
|
||||
// 遍历中禁止结构变更(见文件头约束)。
|
||||
template<typename... Ts, typename Fn>
|
||||
void Each(Fn&& fn)
|
||||
{
|
||||
static_assert(sizeof...(Ts) > 0, "Each requires at least one component type");
|
||||
|
||||
std::tuple<ComponentPool<Ts>*...> pools{GetPool<Ts>()...};
|
||||
|
||||
// 任一类型池不存在 → 空集
|
||||
bool allExist = true;
|
||||
std::apply([&allExist](auto*... p) {
|
||||
((p || (allExist = false)) && ...);
|
||||
}, pools);
|
||||
if (!allExist) return;
|
||||
|
||||
// 主池:取最小者
|
||||
const std::array<IPool*, sizeof...(Ts)> basePools{GetPool<Ts>()...};
|
||||
IPool* primary = nullptr;
|
||||
u32 minSize = 0xFFFFFFFF;
|
||||
for (auto* p : basePools) {
|
||||
if (p && p->Size() < minSize) {
|
||||
minSize = p->Size();
|
||||
primary = p;
|
||||
}
|
||||
}
|
||||
if (!primary) return;
|
||||
|
||||
for (u32 i = 0; i < primary->Size(); ++i) {
|
||||
const Entity e = primary->EntityAt(i);
|
||||
if (!IsAlive(e)) continue;
|
||||
|
||||
bool all = true;
|
||||
std::apply([&](auto*... p) {
|
||||
all = (p->Contains(e) && ...); // 短路折叠
|
||||
}, pools);
|
||||
if (!all) continue;
|
||||
|
||||
std::apply([&](auto*... p) {
|
||||
fn(e, p->Get(e)...);
|
||||
}, pools);
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
template<typename T>
|
||||
ComponentPool<T>* GetPool()
|
||||
{
|
||||
const ComponentTypeId id = ComponentTypeIdOf<T>();
|
||||
if (id >= m_Pools.size() || !m_Pools[id]) return nullptr;
|
||||
return static_cast<ComponentPool<T>*>(m_Pools[id].get());
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
ComponentPool<T>& AssurePool()
|
||||
{
|
||||
const ComponentTypeId id = ComponentTypeIdOf<T>();
|
||||
if (id >= m_Pools.size()) {
|
||||
m_Pools.resize(id + 1);
|
||||
}
|
||||
if (!m_Pools[id]) {
|
||||
m_Pools[id] = std::make_unique<ComponentPool<T>>();
|
||||
}
|
||||
return static_cast<ComponentPool<T>&>(*m_Pools[id]);
|
||||
}
|
||||
|
||||
struct EntityRecord {
|
||||
u32 generation = 0;
|
||||
bool alive = false;
|
||||
};
|
||||
|
||||
std::vector<EntityRecord> m_Records;
|
||||
std::vector<u32> m_FreeIndices; // 可复用槽位
|
||||
std::vector<std::unique_ptr<IPool>> m_Pools; // 按 ComponentTypeId 索引
|
||||
};
|
||||
|
||||
} // namespace Engine::ECS
|
||||
|
||||
#endif //HELLOVULKAN_REGISTRY_H
|
||||
@@ -1,24 +1,34 @@
|
||||
//
|
||||
// Created by atdunbg on 2026/9/22.
|
||||
//
|
||||
// Scene:世界容器 = Registry + 场景级设置 + 安全的生命周期封装。
|
||||
// Scene:世界容器 = entt::registry + 场景级设置 + 安全的生命周期封装。
|
||||
//
|
||||
// ECS 基于 entt(skypjack/entt,header-only,稀疏集实现):
|
||||
// - Entity 即 entt::entity(内部自带版本号,悬空句柄由 valid() 拦截)
|
||||
// - 组件池为稀疏集,O(1) 增删查;view 遍历自动取最小池
|
||||
//
|
||||
// 销毁策略:DestroyEntity 只入队,FlushDestroyQueue 在帧尾统一执行,
|
||||
// 保证遍历(Each)过程中组件池结构不变——这是 ECS 正确性的关键约束。
|
||||
// 保证遍历过程中组件池结构不变——这是 ECS 正确性的关键约束。
|
||||
//
|
||||
|
||||
#ifndef HELLOVULKAN_SCENE_H
|
||||
#define HELLOVULKAN_SCENE_H
|
||||
|
||||
#include <Engine/Core/Types.h>
|
||||
#include <Engine/ECS/Registry.h>
|
||||
#include <Engine/Core/UUID.h>
|
||||
#include <Engine/ECS/Components.h>
|
||||
|
||||
#include <entt/entt.hpp>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace Engine::ECS {
|
||||
|
||||
// 实体句柄直接用 entt::entity:
|
||||
// - 判空用 `e == entt::null`(不要用 Entity{} —— 那是 0 号实体!)
|
||||
using Entity = entt::entity;
|
||||
using Registry = entt::registry;
|
||||
|
||||
class Scene {
|
||||
public:
|
||||
Scene() = default;
|
||||
@@ -38,35 +48,38 @@ namespace Engine::ECS {
|
||||
Entity CreateEntity(std::string name = {});
|
||||
void DestroyEntity(Entity e); // 延迟:帧尾统一销毁
|
||||
void FlushDestroyQueue(); // 每帧末尾由 Application 调用
|
||||
bool IsAlive(Entity e) const { return m_Registry.IsAlive(e); }
|
||||
bool IsAlive(Entity e) const { return m_Registry.valid(e); }
|
||||
|
||||
// 按稳定 UUID 查实体(序列化/跨引用场景);找不到返回无效句柄
|
||||
// 按稳定 UUID 查实体(序列化/跨引用场景);找不到返回 entt::null
|
||||
Entity FindEntityByUUID(UUID uuid);
|
||||
|
||||
Registry& GetRegistry() { return m_Registry; }
|
||||
|
||||
// ---- 组件便捷接口(转发 Registry)----
|
||||
// ---- 组件便捷接口(转发 entt)----
|
||||
|
||||
template<typename T, typename... Args>
|
||||
T& AddComponent(Entity e, Args&&... args)
|
||||
{
|
||||
return m_Registry.Add<T>(e, std::forward<Args>(args)...);
|
||||
return m_Registry.emplace<T>(e, std::forward<Args>(args)...);
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
T& GetComponent(Entity e) { return m_Registry.Get<T>(e); }
|
||||
T& GetComponent(Entity e) { return m_Registry.get<T>(e); }
|
||||
|
||||
template<typename T>
|
||||
T* TryGetComponent(Entity e) { return m_Registry.TryGet<T>(e); }
|
||||
T* TryGetComponent(Entity e) { return m_Registry.try_get<T>(e); }
|
||||
|
||||
template<typename T>
|
||||
bool HasComponent(Entity e) { return m_Registry.Has<T>(e); }
|
||||
bool HasComponent(Entity e) { return m_Registry.all_of<T>(e); }
|
||||
|
||||
template<typename T>
|
||||
void RemoveComponent(Entity e) { m_Registry.Remove<T>(e); }
|
||||
void RemoveComponent(Entity e) { m_Registry.remove<T>(e); }
|
||||
|
||||
// 遍历拥有全部 Ts 组件的实体:fn(Entity, Ts&...)
|
||||
// entt view 自动以最小池为主表,复杂度 O(min pool)
|
||||
// 注意:遍历中禁止结构变更(创建/销毁实体、增删组件)——销毁走延迟队列
|
||||
template<typename... Ts, typename Fn>
|
||||
void Each(Fn&& fn) { m_Registry.Each<Ts...>(std::forward<Fn>(fn)); }
|
||||
void Each(Fn&& fn) { m_Registry.view<Ts...>().each(std::forward<Fn>(fn)); }
|
||||
|
||||
// ---- 常用查询 ----
|
||||
|
||||
|
||||
Reference in New Issue
Block a user