推荐一个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
*/
