天天看点

Unity Entity Component System --- System Update Order

通过Component System Groups来指定systems的执行顺序。可以通过在系统的类的定义上使用[UpdateInGroup] Attribute来指定该系统在哪个group中执行。还可以使用[UpdateBefore] [UpdateAfter]来指定在该Group内的执行顺序。

ECS框架创建了一组默认的system groups,可以利用这些groups来控制我们的系统的执行顺序。还可以将一个Group创建到另一个Group内,这样来进一步控制系统的执行顺序(Group是可以嵌套的)。

Component System Groups

ComponentSystemGroup类组织若干相关的Systems到一起,并以一定的顺序执行。ComponentSystemGroup是ComponentSystemBase的派生类,因此它具有一个componenty system应该有的特性,可以跟其它系统关联,有OnUpdate方法,等。最主要的,这意味着Component System Croups可以嵌套到其它Component System Group中,建立层次关系。

默认地,当ComponentSystemGroup的Update调用时,它会调用所有保存的系统列表里的系统的Update()。如果一个子Component System是group时,会递归调用它的子系统列表,按照深度优先遍历整棵层次树。

System Ordering Attributes

目前系统维护了一套顺序Attributes:

  • [UpdateInGroup] — 为该系统指定是哪个group的成员。如果没有该修饰,默认会添加到World的SimulationSystemGroup中。
  • [UpdateBefore] [UpdateAfter] — 控制组内系统的相对执行顺序。使用这些修饰的系统类型,必须在同一个group中。对于跨group的顺序,按照包含2个systems的最深的group决定:

    例如:SystemA在GroupA,SystemB在GroupB,GroupA和GroupB都在GroupC,那么SystemA和SystemB的隐含顺序为Group A和GroupB的顺序。

  • [DisableAutoCreation] — 禁止默认 World初始化时自动创建System。之后需要自己明确的创建和更新这些系统。当然,也可以创建完系统后,添加到一个ComponentSystemGroup中,那么系统的Update会被Group自动调用。

Default System Groups

默认的World包含了一个ComponentSystemGroup实例的层级树。仅三个根级system groups被添加到Unity的逻辑循环中。下面的列表展示了这三个组中预定义的子系统:

  • InitializationSystemGroup(在主循环的Initialization阶段结束时执行)
    • BeginInitializationEntityCommandBufferSystem
    • CopyInitialTransformFromGameObjectSystem
    • SubSceneLiveLinkSystem
    • SubSceneStreamingSystem
    • EndInitializationEntityCommandBufferSystem
  • SimulationSystemGroup(主循环的Update结束时执行)
    • BeginSimulationEntityCommandBufferSystem
    • TransformSystemGroup
      • EndFrameParentSystem
      • CopyTransformFromGameObjectSystem
      • EndFrameTRSToLocalToWorldSystem
      • EndFrameTRSToLocalToParentSystem
      • EndFrameLocalToParentSystem
      • CopyTransformToGameObjectSystem
    • LateSimulationSystemGroup
    • EndSimulationEntityCommandBufferSystem
  • PresentationSystemGroup(主循环的PreLateUpdate结束时调用)
    • BeginPresentationEntityCommandBufferSystem
    • CreateMissingRenderBoundsFromMeshRenderer RenderingSystemBootstrap
    • RenderBoundsUpdateSystem RenderMeshSystem LODGroupSystemV1
    • LodRequirementsUpdateSystem
    • EndPresentationEntityCommandBufferSystem

这个列表的内容目前还在优化中,会发生变化。

Multiple Worlds

可以创建除了默认World的多个其它Worlds,或者替换默认World。同样的Component System可以创建到多个World中,并且可以以不同的更新频率,在不同的时间点更新。

现在,不支持手动地更新World内的系统;你只能控制某个系统被创建到哪个World中的哪个System Group中。也即是,自定义的WorldB可以实例化SystemX和SystemY,并将SystemX添加到默认World的SimulationSystemGroup,将SystemY添加到默认World的PresentationSystemGroup中。这些Systems的顺序关系在它们所在的Group中被管理,并按照Group的顺序更新。

为了实现这种用法,提供了ICustomBootstrap 接口类:

public interface ICustomBootstrap
{
	List<Type> Initialize(List<Type> systems);
}
           

实现该接口时,一个完整的System类型的列表被传递到Initialize()方法中,而且它在默认World初始化前调用。自定义的启动加载器可以遍历这个列表并且创建需要的System。可以返回一个System列表,该列表内的系统将被默认World初始化。(不返回则不创建)

例如,下面是个典型的MyCustomBootstrap.Initialize()的实现过程:

  1. 创建一个World和它的最上层的ComponentSystemGroups。
  2. 对所有的System类型:

    遍历ComponentSystemGroup层次,找到该System应该被添加到哪个Group中。

    如果找到Group,在该World中创建System并调用group.AddSystemToUpdateList()接口添加到层次结构中。

    如果没有找到要添加到的Group,那么,添加到返回的System类型列表中,返回到DefaultWorldInitialization。

  3. 调用所有顶层的groups的group.SortSystemUpdateList()。

    Optionally add them to one of the default world groups

  4. 返回未处理的系统的列表给DefaultWorldInitializtion。

注:ECS框架通过反射用户自定义的ICustomBootstrap实现。

建议:

  • 用[UpdateInGroup]来指定你的系统在哪个Group中执行。如果不指定,默认是SimulationSystemGroup。
  • 用手动更新的ComponentSystemGroups来在Unity主循环的其它阶段更新Systems。为ComponentSystem或者ComponentSystemGroup添加[DisableAutoCreation]修饰来避免被自动添加到默认的SystemGroups中,可以手动地通过接口World.GetOrCreateSystem()来创建System并在主线程中调用MySystem.Update()来执行。这是将System添加到主循环其它阶段去执行的简单的办法。(例如,你可以创建一个系统,在一帧的前面或者后面执行。)
  • 尽可能的使用已有的EntityCommandBufferSystem,而不要自己创建。一个EntityCommandBufferSystem代表一个SyncPoint,主线程要等待所有工作线程的工作完成,以执行它的缓存的EntityCommandBuffers。重复利用预定义在顶层SystemGroup中的Begin/End Systems来避免SyncPoints,提升效率。
  • 避免将自定义逻辑写到ComponentSystemGroup.OnUpdate()中。因为ComponentSystemGroup本身是个ComponentSystem,让人想要将用户逻辑添加到它的OnUpdate()中来执行一些逻辑,创建Jobs,等。我们反对这么做,因为从表面上很难马上确定,它的Execute是在group‘s的其它成员System之前还是之后。最好就是让SystemGroups来执行grouping机制,而在其它的System中实现我们的逻辑,并明确地指定它们的相对执行顺序。