EnTT源码分析【一】:什么是ECS

本文分析了开源项目EnTT v3.12.2的原理和实现。述说了ECS架构。

ECS架构

ECS的用法

ECS是一个典型的组合优于继承的架构。

Entity, Component, System架构:

  • Entity:游戏中的某个实体,通常用正整数实现
  • Component:游戏中的组件,用于附加在Entity上。例如物理系统中的RigidBody,渲染部分的PBRMaterial等。一般是不含有方法的纯粹数据
  • System:用于处理附加在Entity的Component。

一个ECS例子如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// 以下源码仅为示例,并不严谨

void UpdatePhysics(entt::registry& reg) {
    auto entities = reg.view<RigidBody>();
    for (auto& [entity, rigidbody] : entities) {
        // 在这里更新物理
        rigidbody.acc = rigidbody.force * rigidboyd.mass;
        rigidbody.vel = rigidbody.acc * entt::resource_cache<Timer, TimerLoader>{}[0].time();
    }
}

int main() {
    entt::registry reg; // EnTT中用于管理整个ECS的结构

    auto entity = reg.create(); // 创建一个Entity
    reg.emplace(entity, RigidBody::Create(math::Vec2(100.0, 200.0)));   // 创建一个物理组件并附加到Entity上

    // 游戏循环中
    while (!shouldClose) {
        // ...
        UpdatePhysics(reg);
        // ...
    }
}

一般来说。一个ECS系统包含如下几个部分:

  • World:即管理整个ECS数据的地方,在EnTT中是entt::registry
  • Querier:查询器,用来得到含有某个特定组件的实体们,或者从实体得到组件。在EnTT中是entt::view
  • Resource:[可选],资源。本质上是组件,但不附加在任何实体上,且一般全局只有一份。EnTT中是entt::resource_cache
  • System:系统,在游戏循环中被调用用来对组件进行实际操作。EnTT中没有特定类型,自己写函数进行组件操作即可。Bevy中有StartupSystem用于程序启动时调用一次,以及UpdateSystem用于每帧调用。

还可以分的更细致,比如Bevy中将一些功能从World中抽出:

  • Commands:创建实体/将组件附加在实体/删除实体 的帮助类
  • Resources:用于方便地从World中得到资源

ECS的优点

相比传统的OO,ECS优点如下:

  • OO编程通过类继承来组合数据/功能。这有可能导致产生冗余数据。但ECS可以将数据分为不相干的多个部分,并通过组件插拔的方式灵活地组合。
  • OO编程对CPU Cache不友好。摩尔定律指出,CPU每18个月性能翻倍。但内存性能增长速度没有跟上CPU,这导致利用CPU Cache数据是提升性能非常必要的手段。在近几年,由于物理原因,摩尔定律在慢慢失效,导致现在CPU提升性能的常见方法是堆核(多核CPU)。所以利用多核的优势进行并行编程也是必要的手段。而ECS通过将数据分为多个无关部分,各部分的处理更有利于并行。在Bevy游戏引擎中,就已经实现不相关系统并行运行的功能。

由于EnTT的System部分由用户自己编写,所以EnTT并没有提供任何System并行支持,本系列也不会说。并行支持部分可自行参阅flecs源码。

EnTT的工程结构

下面列举了src/文件夹下的结构(src下是源码部分)。可选部分指可以使用,不使用的话不会参与编译(独立模块)。

  • config:用于通过宏控制EnTT的某些配置
  • container:一些通用容器
  • core:核心算法,traits,any,内存分配器和一些工具
  • entity:整个EnTT最核心的部分,是entt::registry实现的地方,有着ECS大部分功能
  • graph:[可选]。 用于辅助开发者做出管线流图的东西。不会在本系列中分析
  • locator:[可选]。服务定位器
  • meta:[可选]。动态反射,不会在本系列中分析
  • platform:平台相关
  • poly:[可选]。用于做静态多态的东西。不会在本系列中分析
  • process:[可选]。文档里没说,应该是用于任务调度的东西。不会在本系列中分析
  • resource:[可选]。用于提供Resource支持
  • signal:信号和委托部分,用于提供信号,事件分发&接收。本质上是个委托框架

EnTT的单元测试做的很好,所有模块对应的单测在test/目录下。

updatedupdated2023-08-102023-08-10