Unity DOTS中ECS核心架构详解

发布时间 2023-11-06 13:56:57作者: rain4414

最近DOTS终于发布了正式的版本, 我们来分享一下DOTS中ECS的几个关键概念与结构,方便大家上手学习掌握Unity DOTS开发。

 

ECS中的World

  Unity DOTS ECS架构中所有的Entity都是被放到了World对象里面,每个Entity在World里面都有唯一的Id号。Unity DOTS 可以同时支持很多个World, DOTS会在运行的时候创建一个默认的World。World包含了它所需的所有System, System迭代计算的时候,使用World里面的Entity中的Component数据。如果不想要一运行就创建一个默认的World,我们可以通过以下宏开关来控制:

#UNITY_DISABLE_AUTOMATIC_SYSTEM_BOOTSTRAP_RUNTIME_WORLD

关闭运行模式下启动的时候,创建一个默认的World;

#UNITY_DISABLE_AUTOMATIC_SYSTEM_BOOTSTRAP_EDITOR_WORLD

关闭编辑器模式下创建默认的World对象;

#UNITY_DISABLE_AUTOMATIC_SYSTEM_BOOTSTRAP

关闭掉编辑器模式/运行模式下的默认的World的创建;

EntityManager对象

  每个World, 有且只有一个EntityManager对象。EntityManager对象负责Entity的管理,包含但不限于: 创建Entity, 销毁Entity, 修改Entity中的数据等。

Entity对象

    DOTS 中的每一个实体,我们称为Entity, 它本身是一个容器,可以理解为一个轻量级的gameObject, 它的数据全部存放在它的对应的组件里面。Entiy里面的所有组件数据内存会并行一起排布。这样保证了Entity 数据内存的高效访问与Cache命中。相当于把Entity所包含的所有组件数据的内存打包到一个内存块中。比如一个A类Entity, 它包含了有M,N两个组件数据,当我们给Entity来分布组件数据内存的时候,是把M+N两个组件的内存看作一种类型,线性排布在一起分配。存储数据的时候M,就操作这个内存块的M的部分。操作N组件数据的时候,就操作这个内存块的N的部分。Entity中的所有组件组成的内存块看作的这个类型,我们叫做ArchType。相同类型的Entity(所有组件的组成结构相同)属于同一种ArchType。

 

ArchType与Chunk高效的内存分配器

高效的内存分配器需要具备几个特点:

  1: 高效地分配与释放;

  2: 避免大量的分配与释放后造成的内存碎片;

上文提到,Entity中的所有组件数据是排布在一块内存里面的,每一种不同的排布,就会对应一种"类型"ArchType, DOTS 高效的内存分配器只需要基于ArchType所对应的内存块大小来进行分配就可以了。DOTS的entity组件数据的内存分配器基于Chunk设计,每个Chunk的大小为16kb,每个Chunk只会分配同一种类型的ArchType, 根据ArchType的组件组合,我们就可以计算出它们一共所需要的内存大小,我们就从Chunk中固定分配对应的内存大小就可以了,这样Entity对应的所有组件内存非常高效的分配与释放,同时每种Chunk只存放一种ArchType类型的内存块,每次分配的内存大小都是一样,这样可以避免内存碎片。基于ArchType的内存排布如下所示:

===============================

ArchType1:

chunk1【e1(c1c2),e2(c1c2),e3(c1c2)】

chunk2【e4(c1c2),e5(c1c2),e6(c1c2)】

...

======================

ArchType2:

chunk1【e1(c3c4),e2(c3c4),e3(c3c4)】

chunk2【e4(c3c4),e5(c3c4),e6(c3c4)】

...

===============================

ArchType3: 

chunk1【e1(c5c6),e2(c5c6),e3(c5c6)】

chunk2【e3(c5c6),e4(c5c6),e5(c5c6)】

...

===============================

System与JobSystem

   我们学C语言的时候,听到过一句名言,程序=数据结构+算法。Entity解决了数据存储的问题,System就是算法。算法所需要的数据,来源于Entity中的Component。DOTS提供机制,System可以访问到entity中的组件数据,拿到这些数据后再做逻辑迭代计算与处理。默认System是运行在Unity的main thread上的,为了发挥多核优势,把可以用多线程处理的任务使用多线程,Unity 还提供了JobSystem机制,通过多线程的线程池来迭代计算JobSystem,不放main thread上提升程序的效率。

 

总结如下:

DOTS中会有一个World对象,每个Word对象会有一个EntityManager负责Entity的管理,内部使用了高效的基于ArchType与Chunk机制的内存分配。所有的system会加入到World里面来进行统一迭代,System可以访问Entity中的Component数据。同时JobSystem可以让我们的算法迭代基于多线程处理。配上总结图如下: