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:
2026-09-22 13:51:48 +08:00
parent 8afdd9b245
commit b73e197783
6 changed files with 41 additions and 370 deletions
-32
View File
@@ -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
-312
View File
@@ -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
+25 -12
View File
@@ -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)); }
// ---- 常用查询 ----