diff --git a/App/main.cpp b/App/main.cpp index 809d38e..c76b6c8 100644 --- a/App/main.cpp +++ b/App/main.cpp @@ -115,7 +115,7 @@ protected: } private: - Entity m_CameraEntity{}; + Entity m_CameraEntity{entt::null}; // 注意:entt::entity{} 是 0 号实体,空句柄要用 entt::null std::vector m_Spinners; f32 m_Time = 0.0f; }; diff --git a/Engine/CMakeLists.txt b/Engine/CMakeLists.txt index dfe6aeb..9ee53d0 100644 --- a/Engine/CMakeLists.txt +++ b/Engine/CMakeLists.txt @@ -8,6 +8,7 @@ add_library(Engine STATIC ${ENGINE_SOURCES}) target_include_directories(Engine PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/include + ${CMAKE_SOURCE_DIR}/Lib/entt/src ) # 链接第三方库 diff --git a/Engine/include/Engine/ECS/Entity.h b/Engine/include/Engine/ECS/Entity.h deleted file mode 100644 index d91c17d..0000000 --- a/Engine/include/Engine/ECS/Entity.h +++ /dev/null @@ -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 - -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 diff --git a/Engine/include/Engine/ECS/Registry.h b/Engine/include/Engine/ECS/Registry.h deleted file mode 100644 index c33b433..0000000 --- a/Engine/include/Engine/ECS/Registry.h +++ /dev/null @@ -1,312 +0,0 @@ -// -// Created by atdunbg on 2026/9/22. -// -// Registry:稀疏集 ECS 核心。 -// -// 数据结构(与 EnTT 同族,自研实现): -// - 每个组件类型一个 ComponentPool:sparse 索引表 + dense 实体/组件数组 -// - Add/Remove/Get 全部 O(1);Remove 用 swap-remove(O(1),代价是 -// dense 顺序会变——渲染遍历只要求"每帧确定",不要求跨帧稳定) -// - Each 以最小的池为主表遍历,其余池做 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 -#include -#include - -#include -#include -#include -#include - -namespace Engine::ECS { - - // ---------- 组件类型 ID(编译期静态分配) ---------- - - using ComponentTypeId = u32; - - namespace detail { - inline ComponentTypeId NextComponentTypeId() - { - static ComponentTypeId counter = 0; - return counter++; - } - } - - template - 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 - 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(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(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(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(m_Components.size()); } - Entity EntityAt(u32 denseIndex) const override { return m_Entities[denseIndex]; } - - private: - std::vector m_Sparse; // 实体索引 -> dense 下标(kInvalid = 无) - std::vector m_Entities; // dense:与 m_Components 平行 - std::vector 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(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 - T& Add(Entity e, Args&&... args) - { - if (!IsAlive(e)) { - ENGINE_FATAL("Registry::Add: stale entity ({},{})", e.index, e.generation); - } - return AssurePool().Add(e, T{std::forward(args)...}); - } - - template - T& Get(Entity e) - { - if (!IsAlive(e)) { - ENGINE_FATAL("Registry::Get: stale entity ({},{})", e.index, e.generation); - } - auto* pool = GetPool(); - if (!pool) { - ENGINE_FATAL("Registry::Get: no component pool of this type exists"); - } - return pool->Get(e); - } - - template - T* TryGet(Entity e) - { - if (!IsAlive(e)) return nullptr; - auto* pool = GetPool(); - return (pool && pool->Contains(e)) ? &pool->Get(e) : nullptr; - } - - template - bool Has(Entity e) - { - if (!IsAlive(e)) return false; - auto* pool = GetPool(); - return pool && pool->Contains(e); - } - - template - bool Remove(Entity e) - { - if (!IsAlive(e)) return false; - auto* pool = GetPool(); - return pool && pool->Remove(e); - } - - // ---- 遍历(视图)---- - // fn(Entity, Ts&...);以最小的池为主表,复杂度 O(min pool size)。 - // 遍历中禁止结构变更(见文件头约束)。 - template - void Each(Fn&& fn) - { - static_assert(sizeof...(Ts) > 0, "Each requires at least one component type"); - - std::tuple*...> pools{GetPool()...}; - - // 任一类型池不存在 → 空集 - bool allExist = true; - std::apply([&allExist](auto*... p) { - ((p || (allExist = false)) && ...); - }, pools); - if (!allExist) return; - - // 主池:取最小者 - const std::array basePools{GetPool()...}; - 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 - ComponentPool* GetPool() - { - const ComponentTypeId id = ComponentTypeIdOf(); - if (id >= m_Pools.size() || !m_Pools[id]) return nullptr; - return static_cast*>(m_Pools[id].get()); - } - - template - ComponentPool& AssurePool() - { - const ComponentTypeId id = ComponentTypeIdOf(); - if (id >= m_Pools.size()) { - m_Pools.resize(id + 1); - } - if (!m_Pools[id]) { - m_Pools[id] = std::make_unique>(); - } - return static_cast&>(*m_Pools[id]); - } - - struct EntityRecord { - u32 generation = 0; - bool alive = false; - }; - - std::vector m_Records; - std::vector m_FreeIndices; // 可复用槽位 - std::vector> m_Pools; // 按 ComponentTypeId 索引 - }; - -} // namespace Engine::ECS - -#endif //HELLOVULKAN_REGISTRY_H diff --git a/Engine/include/Engine/ECS/Scene.h b/Engine/include/Engine/ECS/Scene.h index 04fba45..801beb5 100644 --- a/Engine/include/Engine/ECS/Scene.h +++ b/Engine/include/Engine/ECS/Scene.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 -#include +#include #include +#include #include #include 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 T& AddComponent(Entity e, Args&&... args) { - return m_Registry.Add(e, std::forward(args)...); + return m_Registry.emplace(e, std::forward(args)...); } template - T& GetComponent(Entity e) { return m_Registry.Get(e); } + T& GetComponent(Entity e) { return m_Registry.get(e); } template - T* TryGetComponent(Entity e) { return m_Registry.TryGet(e); } + T* TryGetComponent(Entity e) { return m_Registry.try_get(e); } template - bool HasComponent(Entity e) { return m_Registry.Has(e); } + bool HasComponent(Entity e) { return m_Registry.all_of(e); } template - void RemoveComponent(Entity e) { m_Registry.Remove(e); } + void RemoveComponent(Entity e) { m_Registry.remove(e); } + // 遍历拥有全部 Ts 组件的实体:fn(Entity, Ts&...) + // entt view 自动以最小池为主表,复杂度 O(min pool) + // 注意:遍历中禁止结构变更(创建/销毁实体、增删组件)——销毁走延迟队列 template - void Each(Fn&& fn) { m_Registry.Each(std::forward(fn)); } + void Each(Fn&& fn) { m_Registry.view().each(std::forward(fn)); } // ---- 常用查询 ---- diff --git a/Engine/src/ECS/Scene.cpp b/Engine/src/ECS/Scene.cpp index 581a5c4..ab1ed41 100644 --- a/Engine/src/ECS/Scene.cpp +++ b/Engine/src/ECS/Scene.cpp @@ -9,32 +9,33 @@ namespace Engine::ECS { Entity Scene::CreateEntity(std::string name) { - Entity e = m_Registry.Create(); + Entity e = m_Registry.create(); // IdComponent 自动挂载:每个实体出生即有稳定 UUID - m_Registry.Add(e, IdComponent{UUID::New()}); + m_Registry.emplace(e, IdComponent{UUID::New()}); if (!name.empty()) { - m_Registry.Add(e, NameComponent{std::move(name)}); + m_Registry.emplace(e, NameComponent{std::move(name)}); } return e; } Entity Scene::FindEntityByUUID(UUID uuid) { - if (!uuid.Valid()) return {}; + if (!uuid.Valid()) return entt::null; - Entity found{}; - m_Registry.Each([&](Entity e, IdComponent& id) { - if (!found && id.uuid == uuid) found = e; + Entity found = entt::null; + m_Registry.view().each([&](Entity e, IdComponent& id) { + if (found == entt::null && id.uuid == uuid) found = e; }); return found; } void Scene::DestroyEntity(Entity e) { - if (!m_Registry.IsAlive(e)) { - ENGINE_FATAL("Scene::DestroyEntity: stale entity ({},{})", - e.index, e.generation); + if (!m_Registry.valid(e)) { + ENGINE_FATAL("Scene::DestroyEntity: stale entity ({})", + static_cast(e)); } + // 延迟到帧尾,保证遍历期间池结构不变 m_PendingDestroy.push_back(e); } @@ -43,8 +44,8 @@ namespace Engine::ECS { if (m_PendingDestroy.empty()) return; for (Entity e : m_PendingDestroy) { - if (m_Registry.IsAlive(e)) { - m_Registry.Destroy(e); + if (m_Registry.valid(e)) { + m_Registry.destroy(e); // entt 自动回收槽位并递增版本号 } } m_PendingDestroy.clear(); @@ -53,7 +54,7 @@ namespace Engine::ECS { CameraComponent* Scene::GetPrimaryCamera() { CameraComponent* found = nullptr; - m_Registry.Each([&](Entity, CameraComponent& cc) { + m_Registry.view().each([&](Entity, CameraComponent& cc) { if (!found && cc.primary) found = &cc; }); return found;