i007.cc

i007.cc

优先队列-降维打击

05.价值资料

推荐一个ESC框架的设计

代码编译运行正常,发给你了。整体设计分四层,从下到上讲一遍:


设计核心:Sparse Set

这是整个框架的灵魂,理解它就理解了 EnTT 的核心。

sparse[entity.index()] → dense 中的下标 i
dense[i]               → entity(验证 generation)
components[i]          → 组件数据(与 dense 平行)

 

添加组件:在 dense 和 components 末尾各 push 一个,在 sparse 里记录下标,O(1)。

删除组件:把要删的那个和最后一个交换,然后 pop_back,O(1),dense 始终连续。这是关键——System 迭代时永远在连续内存上跑,CPU prefetch 非常友好。

查找组件sparse[e.index()] 直接拿到下标,O(1),但要多一步 generation 校验,防止悬空引用。


Entity 的 Generation 机制

代码里演示了这个:

创建 enemy → id=1, index=1, gen=0
销毁 enemy → gen[1]++ = 1,index=1 放回 free list
创建 new_entity → index=1 被回收,但 gen=1
old_enemy_ref(index=1, gen=0) → 查 gen[1]=1,不匹配 → invalid

 

这样持有旧 Entity ID 的任何地方都不会误操作到新 entity,是游戏里防止”use after free”的标准手段。


View 的工作方式

cpp
reg.view<Position, Velocity>([dt](Entity e, Position& pos, Velocity& vel) {
    pos.x += vel.vx * dt;
});

 

实现很简单:拿第一个组件类型(Position)的 pool 作为迭代主池,遍历其中每个 entity,检查它是否也有 Velocity。有的话就调用回调。

生产级实现(EnTT 真正做的)会选 size 最小的 pool 作为主池,因为最小的 pool 过滤效率最高。这个框架里用的是简化版,够理解原理。


和 EnTT 的差距在哪里

你看完这个框架之后,如果面试被问到”你了解 EnTT 吗”,可以这样说:

EnTT 在这个基础上额外做了:Archetype group 优化(把频繁共现的组件强制存到同一连续内存块,迭代时真正做到零 cache miss);Signal / observer 系统(组件增删时触发回调);以及非常极致的模板元编程,整个库 header-only,零运行时开销。核心思路和这个框架是一样的。

/**
 * ECS Framework — 参考 EnTT 设计思路的简化实现
 *
 * 核心数据结构:Sparse Set
 *   - sparse[entity]  → dense 数组中的下标(O(1) 查找)
 *   - dense[]         → 连续存储的 entity 列表(O(1) 迭代,cache 友好)
 *   - components[]    → 与 dense 平行的组件数据数组
 *
 * 三个核心概念:
 *   Entity   — 只是一个 ID(32位:高12位 generation + 低20位 index)
 *   Component — 纯数据结构,无任何方法
 *   System   — 纯逻辑函数,通过 Registry::view() 批量处理组件
 */

#include <vector>
#include <unordered_map>
#include <typeindex>
#include <memory>
#include <functional>
#include <cassert>
#include <cstdint>
#include <iostream>
#include <tuple>
#include <cmath>

// ============================================================
// 1. ENTITY
// ============================================================

/**
 * Entity 是一个轻量 ID。
 *
 * 32位布局:[ generation(12位) | index(20位) ]
 *
 * generation 的作用:当一个 entity 被销毁后,其 index 会被回收给新的
 * entity,但 generation 会 +1。持有旧 entity ID 的代码拿去查询时,
 * generation 不匹配,Registry 就能识别这是一个"悬空引用"。
 */
struct Entity {
    static constexpr uint32_t INDEX_BITS = 20;
    static constexpr uint32_t GEN_BITS   = 12;
    static constexpr uint32_t INDEX_MASK = (1u << INDEX_BITS) - 1;
    static constexpr uint32_t INVALID_ID = UINT32_MAX;

    uint32_t id = INVALID_ID;

    uint32_t index()      const { return id & INDEX_MASK; }
    uint32_t generation() const { return id >> INDEX_BITS; }
    bool     valid()      const { return id != INVALID_ID; }

    bool operator==(const Entity& o) const { return id == o.id; }
    bool operator!=(const Entity& o) const { return id != o.id; }

    static Entity make(uint32_t index, uint32_t gen) {
        return {(gen << INDEX_BITS) | (index & INDEX_MASK)};
    }
    static Entity null() { return {INVALID_ID}; }
};

// ============================================================
// 2. SPARSE SET(核心数据结构)
// ============================================================

/**
 * SparseSet<T> 同时满足两个看似矛盾的需求:
 *   - O(1) 按 entity 查询某个组件是否存在
 *   - 组件数据在内存中连续存储,迭代时 cache 友好
 *
 * 实现方式:
 *   sparse:      indexed by entity.index(),存 dense 中的位置
 *   dense:       连续数组,存 entity 列表
 *   components:  与 dense 平行,存实际组件数据
 *
 *   Entity e → sparse[e.index()] = i → components[i]
 */
template<typename T>
class SparseSet {
    static constexpr uint32_t EMPTY = UINT32_MAX;

    std::vector<uint32_t> sparse_;     // [entity index] → dense 下标
    std::vector<Entity>   dense_;      // 连续存储的 entity 列表
    std::vector<T>        components_; // 与 dense_ 平行的组件数据

    void ensure_sparse(uint32_t idx) {
        if (idx >= sparse_.size())
            sparse_.resize(idx + 1, EMPTY);
    }

public:
    // 添加组件(entity e 不能已有此组件)
    template<typename... Args>
    T& emplace(Entity e, Args&&... args) {
        assert(!contains(e) && "entity already has this component");
        ensure_sparse(e.index());

        sparse_[e.index()] = static_cast<uint32_t>(dense_.size());
        dense_.push_back(e);
        components_.emplace_back(std::forward<Args>(args)...);
        return components_.back();
    }

    // 移除组件(swap-and-pop,O(1),保持 dense 连续)
    void remove(Entity e) {
        assert(contains(e) && "entity does not have this component");

        uint32_t idx      = sparse_[e.index()];
        uint32_t last_idx = static_cast<uint32_t>(dense_.size()) - 1;

        if (idx != last_idx) {
            // 把最后一个元素移到被删除的位置
            dense_[idx]      = dense_[last_idx];
            components_[idx] = std::move(components_[last_idx]);
            sparse_[dense_[idx].index()] = idx;  // 更新被移动元素的 sparse 指针
        }

        sparse_[e.index()] = EMPTY;
        dense_.pop_back();
        components_.pop_back();
    }

    // 获取组件引用
    T& get(Entity e) {
        assert(contains(e));
        return components_[sparse_[e.index()]];
    }
    const T& get(Entity e) const {
        assert(contains(e));
        return components_[sparse_[e.index()]];
    }

    bool contains(Entity e) const {
        return e.index() < sparse_.size()
            && sparse_[e.index()] != EMPTY
            && dense_[sparse_[e.index()]] == e;  // generation 校验
    }

    // 迭代器:直接暴露连续的 dense/component 数组
    size_t size() const { return dense_.size(); }

    const std::vector<Entity>& entities()   const { return dense_; }
          std::vector<T>&      components()       { return components_; }
    const std::vector<T>&      components() const { return components_; }
};

// ============================================================
// 3. COMPONENT POOL 基类(用于类型擦除存入 Registry)
// ============================================================

struct IPool {
    virtual ~IPool() = default;
    virtual void remove(Entity e) = 0;
    virtual bool contains(Entity e) const = 0;
};

template<typename T>
struct Pool : IPool, SparseSet<T> {
    void remove(Entity e)         override { SparseSet<T>::remove(e); }
    bool contains(Entity e) const override { return SparseSet<T>::contains(e); }
};

// ============================================================
// 4. REGISTRY(中央管理器)
// ============================================================

class Registry {
    // Entity 管理
    std::vector<uint32_t> generations_;  // 每个 index 对应的当前 generation
    std::vector<uint32_t> free_indices_; // 可回收的 index 列表

    // 组件池:type_index → pool
    std::unordered_map<std::type_index, std::unique_ptr<IPool>> pools_;

    template<typename T>
    Pool<T>& get_or_create_pool() {
        auto key = std::type_index(typeid(T));
        auto it  = pools_.find(key);
        if (it == pools_.end()) {
            auto [inserted_it, ok] = pools_.emplace(key, std::make_unique<Pool<T>>());
            return static_cast<Pool<T>&>(*inserted_it->second);
        }
        return static_cast<Pool<T>&>(*it->second);
    }

    template<typename T>
    Pool<T>* find_pool() {
        auto it = pools_.find(std::type_index(typeid(T)));
        return it != pools_.end() ? static_cast<Pool<T>*>(it->second.get()) : nullptr;
    }

    template<typename T>
    const Pool<T>* find_pool() const {
        auto it = pools_.find(std::type_index(typeid(T)));
        return it != pools_.end() ? static_cast<const Pool<T>*>(it->second.get()) : nullptr;
    }

public:
    // ── Entity 生命周期 ──────────────────────────────────────

    Entity create() {
        if (!free_indices_.empty()) {
            uint32_t idx = free_indices_.back();
            free_indices_.pop_back();
            return Entity::make(idx, generations_[idx]);
        }
        uint32_t idx = static_cast<uint32_t>(generations_.size());
        generations_.push_back(0);
        return Entity::make(idx, 0);
    }

    void destroy(Entity e) {
        assert(valid(e) && "destroying invalid entity");
        // 移除所有组件
        for (auto& [type, pool] : pools_)
            if (pool->contains(e)) pool->remove(e);
        // 回收 index,generation +1 使旧引用失效
        ++generations_[e.index()];
        free_indices_.push_back(e.index());
    }

    bool valid(Entity e) const {
        return e.index() < generations_.size()
            && generations_[e.index()] == e.generation();
    }

    // ── 组件操作 ─────────────────────────────────────────────

    template<typename T, typename... Args>
    T& emplace(Entity e, Args&&... args) {
        return get_or_create_pool<T>().emplace(e, std::forward<Args>(args)...);
    }

    template<typename T>
    void remove(Entity e) {
        auto* pool = find_pool<T>();
        assert(pool && pool->contains(e));
        pool->remove(e);
    }

    template<typename T>
    T& get(Entity e) {
        auto* pool = find_pool<T>();
        assert(pool && "component pool does not exist");
        return pool->get(e);
    }

    template<typename T>
    bool has(Entity e) const {
        const auto* pool = find_pool<T>();
        return pool && pool->contains(e);
    }

    // ── View:遍历同时拥有多个组件的 entity ─────────────────

    /**
     * view<A, B, C>() 的策略:
     *   选出组件数量最少的那个 Pool 作为"主池"来迭代,
     *   对其中每个 entity 检查是否也有其他组件。
     *   这样迭代量最小,同时主池的组件数据是连续访问的。
     */
    // view 接受任意可调用对象(lambda、函数指针等)
    // Ts 显式指定要查询的组件类型,Func 由编译器推导
    template<typename... Ts, typename Func>
    void view(Func&& func) {
        // 以 Ts 中第一个类型的 pool 作为迭代主池(简化策略)
        // 生产级实现会选择 size 最小的 pool 以减少迭代量
        auto* primary_pool = find_pool<std::tuple_element_t<0, std::tuple<Ts...>>>();
        if (!primary_pool) return;

        // 迭代主池,对每个 entity 检查是否同时拥有所有 Ts 组件
        // 注意:倒序迭代以安全应对回调内部删除组件的情况
        for (size_t i = primary_pool->entities().size(); i-- > 0; ) {
            Entity e = primary_pool->entities()[i];
            if ((has<Ts>(e) && ...))
                func(e, get<Ts>(e)...);
        }
    }
};

// ============================================================
// 5. COMPONENTS(纯数据,无方法)
// ============================================================

struct Position {
    float x, y, z;
};

struct Velocity {
    float vx, vy, vz;
};

struct Health {
    int current;
    int max;
};

struct Renderable {
    uint32_t mesh_id;
    uint32_t material_id;
};

struct AI {
    enum class State { Idle, Chase, Attack, Flee } state = State::Idle;
    Entity target = Entity::null();
    float  detection_range = 10.f;
};

// ============================================================
// 6. SYSTEMS(纯逻辑函数)
// ============================================================

namespace Systems {

    // 移动系统:处理所有有 Position + Velocity 的 entity
    void movement(Registry& reg, float dt) {
        reg.view<Position, Velocity>([dt](Entity e, Position& pos, Velocity& vel) {
            pos.x += vel.vx * dt;
            pos.y += vel.vy * dt;
            pos.z += vel.vz * dt;
        });
    }

    // 生命值恢复系统:每帧给所有有 Health 的 entity 回 1 点血
    void health_regen(Registry& reg) {
        reg.view<Health>([](Entity e, Health& hp) {
            if (hp.current < hp.max)
                hp.current = std::min(hp.current + 1, hp.max);
        });
    }

    // AI 系统:处理有 AI + Position 的 entity
    void ai_update(Registry& reg) {
        reg.view<AI, Position>([&](Entity e, AI& ai, Position& pos) {
            if (!reg.valid(ai.target)) {
                ai.state  = AI::State::Idle;
                ai.target = Entity::null();
                return;
            }
            // 计算与目标的距离(简化)
            auto& target_pos = reg.get<Position>(ai.target);
            float dx = target_pos.x - pos.x;
            float dz = target_pos.z - pos.z;
            float dist = std::sqrt(dx * dx + dz * dz);

            if (dist < 1.5f)
                ai.state = AI::State::Attack;
            else if (dist < ai.detection_range)
                ai.state = AI::State::Chase;
            else
                ai.state = AI::State::Idle;
        });
    }

}

// ============================================================
// 7. 主程序演示
// ============================================================

int main() {
    Registry reg;

    // ── 创建实体:玩家 ───────────────────────────────────────
    Entity player = reg.create();
    reg.emplace<Position>(player, 0.f, 0.f, 0.f);
    reg.emplace<Velocity>(player, 1.f, 0.f, 0.5f);
    reg.emplace<Health>  (player, 100, 100);
    reg.emplace<Renderable>(player, 1001u, 2001u);
    std::cout << "Player entity: " << player.id << "\n";

    // ── 创建实体:敌人 ───────────────────────────────────────
    Entity enemy = reg.create();
    reg.emplace<Position>  (enemy, 5.f, 0.f, 5.f);
    reg.emplace<Velocity>  (enemy, 0.f, 0.f, 0.f);
    reg.emplace<Health>    (enemy, 50, 50);
    reg.emplace<Renderable>(enemy, 1002u, 2002u);
    reg.emplace<AI>        (enemy);
    reg.get<AI>(enemy).target = player;  // 瞄准玩家
    std::cout << "Enemy  entity: " << enemy.id << "\n\n";

    // ── 创建实体:静态场景物件(只有 Position + Renderable)──
    Entity tree = reg.create();
    reg.emplace<Position>  (tree, 10.f, 0.f, 10.f);
    reg.emplace<Renderable>(tree, 3001u, 4001u);

    // ── 模拟 3 帧 ────────────────────────────────────────────
    for (int frame = 0; frame < 3; ++frame) {
        float dt = 0.016f; // 16ms per frame

        Systems::movement   (reg, dt);
        Systems::health_regen(reg);
        Systems::ai_update  (reg);

        auto& ppos = reg.get<Position>(player);
        auto& eai  = reg.get<AI>(enemy);

        const char* ai_state_str[] = {"Idle", "Chase", "Attack", "Flee"};
        std::cout << "Frame " << frame + 1 << " | "
                  << "Player(" << ppos.x << ", " << ppos.z << ") | "
                  << "Enemy AI: " << ai_state_str[static_cast<int>(eai.state)]
                  << "\n";
    }

    // ── ECS 组合灵活性演示 ───────────────────────────────────
    std::cout << "\n[演示] 给树添加 Velocity,它就能被 MovementSystem 处理\n";
    reg.emplace<Velocity>(tree, 0.f, 0.f, 0.1f);
    Systems::movement(reg, 0.016f);
    auto& tpos = reg.get<Position>(tree);
    std::cout << "Tree new Z: " << tpos.z << "\n";

    std::cout << "\n[演示] 销毁 enemy,旧引用变无效\n";
    Entity old_enemy_ref = enemy;  // 保存旧引用
    reg.destroy(enemy);
    std::cout << "old enemy ref valid: " << std::boolalpha << reg.valid(old_enemy_ref) << "\n";

    // 回收的 index 创建新 entity,generation 不同,旧引用仍然无效
    Entity new_entity = reg.create();
    std::cout << "new entity index: "      << new_entity.index()      << "\n";
    std::cout << "new entity generation: " << new_entity.generation() << "\n";
    std::cout << "old ref still invalid: " << !reg.valid(old_enemy_ref) << "\n";

    return 0;
}

/*
预期输出:
Player entity: 0
Enemy  entity: 1048576   ← index=1, gen=0 → id = (0<<20)|1

Frame 1 | Player(0.016, 0.008) | Enemy AI: Idle
Frame 2 | Player(0.032, 0.016) | Enemy AI: Idle
Frame 3 | Player(0.048, 0.024) | Enemy AI: Idle

[演示] 给树添加 Velocity,它就能被 MovementSystem 处理
Tree new Z: 10.0016

[演示] 销毁 enemy,旧引用变无效
old enemy ref valid: false
new entity index: 1        ← index 被回收
new entity generation: 1   ← generation +1,旧引用失效
old ref still invalid: true
*/

 

发表回复