EnTT: 实体和组件的存储

梳理 EnTT 中实体、组件池、稀疏集、墓碑和标识符回收之间的存储关系。

ECS 的核心是通过为 实体 附加不同的 组件 来组合成不同的对象,而 EnTT 使用 稀疏集 来作为存储的实现

  • 存储组件
    作为组件池的存储时, entt::null稀疏数组 中用于表示某个 实体 不含有当前组件池存储的组件, 而当实体被移除组件时, entt::tombstone 将在 密集数组 中找到原先组件所在的位置, 并占据该位置, 而不是默认的替换并弹出, 以此可以保证 指针稳定性 (该特性需要启用)

    真正的组件可能存储与另一个 密集数组 中, 也就是说 组件池 可能有一个 稀疏数组 二个 密集数组, 但我无法确定

    还会使用 隐式链表 追踪 entt::tombstone 参见

  • 存储实体
    存储实体时使用特化的 稀疏集, 可以看作是一个组件类型是 实体类型特殊组件池, 在这种情况下, 它既没有 entt::null 也没有 entt::tombstone, 而是在 稀疏数组 中维护一个 隐式链表 用于加速 标识符回收

    最新版本已经不使用隐式链表, 而是使用稀疏集在 密集数组 中使用分区 参见

标识符回收

为什么需要标识符回收? 想象一下, 如果不重复利用实体标识符, 那么将导致 稀疏数组 中的空洞越来越多, 而新建实体将不断增长 稀疏数组 的大小

甚至在一些原本很小的组件池中, 突然为一个标识符很大的实体附加组件, 将会导致该组件池的 稀疏数组 瞬间扩容至至少该标识符大小的长度

所以 实体 存储即使从来不会删除实体, 但是会在 实体摧毁 时对其执行回收而不是删除, 被回收意味着可以被新创建的实体再次利用, 新创建的实体如果使用了回收的实体, 则会将其版本号增长, 显然这是因为 标识符 ID 无法进行修改, 只能通过修改版本号来区分之前的实体已经被摧毁失效, 当前的实体是新创建的