Entity-Component-System
实体组件系统(ECS)架构
- 遵循组合优于继承的原则
- 面向数据设计
- 弱耦合
- 常被应用在游戏开发上
游戏引擎架构演变
- 在ECS架构出现之前,早期的游戏引擎或游戏的架构,多以多层对象继承的方式来组织,都是按空间节点为较基础的对象,一层一层抽象继承下来的
- 后来随着发展,游戏引擎逐渐演变成以Actor Component这种以轻继承,以对象组合代替多层继承的方式来组织
- 其中Actor是个Component的容器,而负责具体逻辑更新部分则由具体的Component负责
- 对象几乎没有多态,做到了对象间的解耦
- 如Reality Engine、UE3以后的版本以及Unity几乎都是这种架构
- 我们要明确ECS架构的本质,组合的是数据数组,而非对象数组。其中Entity本身虽然叫做实体,但并非对象,也不是一个容器,而是一个对象索引的id,是一个标识符,其中并不包含任何数据与逻辑;而Component才是个容器,但它不是一个对象容器,而仅是一个数据容器,其中不包含任何逻辑。
- Entity与Component的关系,往往是Entity充当数据的标识符或Key
来使用,而ECS中的S(System)才是真正负责对数据进行操作的部分 - 换句话说,System才是对具有特定组件的特定实体执行的操作
DOTS1.0中的ECS
- ECS架构一直在演变
- 应用场景不同,各家实现和工作流有差异
- DOTS1.0中的ECS与老版本变化较大,不要看老资料
- DOTS1.0中,为了让大家从面向对象设计向面向数据设计编写代码时,过度更平滑些,引入了不少特性与专有名词,让大家在编写代码时能找到一些面向对象的感觉。因此我们在学习DOTS时,不要先入为主去质疑DOTS下的ECS为什么如此设计,要以DOTS下的ECS工作流方式去学习。另外DOTS中的ECS只是DOTS中的一部分,并不是你了解ECS架构,甚至自己动手实践过ECS就一定能学好
- 开发心态去接受
DOTS下关于ECS的专有名词和概念
Archetypes原型和Archetypes Chunk的概念
- Archetypes依然不是个对象,它依然是一个标识符,它标识的是所有具有相同Component组合的实体类型
- 举个例子,这里由Position、Rotation、Renderer、Rigid Body四个Component组件组合成的Entity A与Entity B共享一种原型,而由Position、Rotation、Renderer三个组件组合成的Entity C则属于另外一种原型
- 这里可以将Archetype原型理解成不同的Entity在内存上的Layout布局
- 如图所示,26个Entity被分为8种原型,而每个Archetype原型所标记的内存会被分成固定大小连续的非托管内存块,被称为Archetype Chunk,这里简称Chunk
- Chunk中会包含共享同一原型的实体组件数组,默认设置下每个Chunk大小为16KB,如果实体组件填充不满的情况下,也要有留白
- Chunk存在的目的,是为了方便做数据并行计算,方便做缓存的prefetch在数据对齐的同时又可以匹配缓存的catch line,另外在Query查询时,无论是使用主线程查询、工作线程并行查询、还是按Chunk查询时,都可以利用其在Component的数组纵向维度上分块对齐的方式来做slice切片
World与EntityManager的概念
- World是一系列Entity的组合,每一个Entity在一个World中是唯一的,统一受到world中的EntityManager结构的管理
- EntityManager负责创建、销毁、修改世界中的实体
Structural Change的概念
- 所有导致需要重新组织内存块或内存块内容的操作都称为Structural Change
- 这里包括两个重点,一个是改变结构,一个是改变内容,这两种改变都只能在主线程中做,而不能在工作线程中做,是Resource Intensity资源密集类型的操作,效率很差
- 应该能想到,添加或删除一个Entity对应的组件,导致所属原型发生变化,就属于Structural Change的概念
- 另外,创建和销毁Entity实体、设置Share Component值也会被视为Structural Change,要尽量避免
- 而当我们明确我们一定不会有Structural Change的操作时,我们可以在编辑时做Bake的操作,虽然会降低运行时的逻辑灵活性,但会提高运行时效率
- 这张图可能会造成误解
- 不同原型内的相同组件,在内存上并不是像上图这样是连续的,这只是为了展示在Query查询时,会通过slice切片方式来阻止查询,让你感觉上是连续的,但并不会造成内存上的移动
Entities and Components术语
System术语
DOTS下的ECS
- Entity : Entity不是一个容器,它只是一个标识符,它用来指示某个对象的存在。系统可以用它来操作,可以通过组件来分配某些属性
- Component : 组件是一个数据容器,没有任何逻辑,一组特定的参数关联在一起并定义属性
- System : 系统是负责对数据进行操作的部分。换句话说,系统是对具有特定属性(组件)的特定实体执行的操作
补充
- 类比例子并不是为了说明缓存分级的原因
- 而是用组织排队去理解面向数据设计,可以更形象的理解ECS、Jobs与Burst在DOTS中分别扮演哪些角色,做了哪些事
- 缓存分层的原因本质是物理距离决定的