ECS 同步点

Sync points

同步点(sync point)是程序执行中的一个点,它将等待到目前为止调度的所有作业的完成. 同步点因此限制了您在一段时间内使用作业系统中所有可用的工作线程的能力. 你应该尽量避免同步点。

Structural changes

同步点是由在有其他作业操作组件时无法安全执行的操作引起的. Structural changes 是造成同步点的主要原因:

  • Creating entities:创建实体
  • Deleting entities:删除实体
  • Adding components to an entity:添加组件
  • Removing components from an entity:移除组件
  • Changing the value of shared components:更改shared component 的值

  广泛的说,任何改变实体原型或者造成块中实体顺序改变的操作都是一个结构改变 structural change. 这些结构更改只能在主线程上执行。

Structural changes 不仅需要一个同步点,还会使对任何组件数据的直接引用无效. 这包括 DynamicBuffer<T> 的实例和一些直接访问组件的方法都变成无效的ComponentSystemBase.GetComponentDataFromEntity.

Avoiding sync points

可以使用 entity command buffers (ECBs) 让结构性的改变排成队,而不是立即执行他们 structural changes instead of immediately performing them. Commands 可以在一帧中的某个时间执行,减少同步点

 

Write groups

一种常见的 ECS pattern 模式是读取一组组件,然后把这组组件(input)的数据写入另一组组件(output).然而,在某些情况下,你想根据不同的输入,在系统中重写或者是更新的输出组件 . Write groups 提供了一个系统覆盖另一个系统的机制,而不需要改变其他的系统provide a mechanism for one system to override another, even when you cannot change the other system.

  write group(写入组)是一组带有 WriteGroup attribute 属性的组件. 当你创建系统的时候,你可以使用 write groups,然后其它的系统会排除掉这些组件,如果不添加该属性,其它的系统会通过你的系统选择并访问这些组件.这种可选的机制让你能够基于实体自己的逻辑来更新组件,同时让系统能够正常对其它组件操作

 要使用 write groups,你必须在系统的查询当中使用 write group filter option. 它会排除在包含write group中组件的所有实体

使用 write groups重写系统,把组件标记为 write group 的一部分. 原始系统会忽略包含这些组件的任何实体,您可以使用自己的系统更新这些实体的数据。

A motivating example

作为一个具体的例子,考虑以下设置:你使用一个外部资源包根据角色的生命值来给游戏中的所有角色着色.比如资源包里有两种组件: HealthComponent and ColorComponent.

public struct HealthComponent : IComponentData
{
   public int Value;
}

public struct ColorComponent : IComponentData
{
   public float4 Value;
}

除此之外,还有两种系统:

  1. the ComputeColorFromHealthSystem (从 HealthComponent 读取,写入 ColorComponent)
  2. the RenderWithColorComponent (从 ColorComponent读取).

  当玩家使用一种道具是,让角色变得无敌,这时候角色应该为金色. 给玩家实体添加InvincibleTagComponent 表示无敌

你创建自己的系统,更改ColorComponent 的值, 但理想情况下 ComputeColorFromHealthSystem 不会一开始就计算实体的颜色. 他应该忽略有InvincibleTagComponent组件的实体.当屏幕上有成千上万的玩家时,这一点就显得尤为重要. 但是,另一个资源包里的系统并不知道你的 InvincibleTagComponent组件.您仍然希望使用这个包,因为它已经解决了您的问题.

这正是 write groups解决的问题: 它允许系统在查询中忽略我们已知的需要更改其值的实体.要做到这个需要两个条件:

  1. InvincibleTagComponent 必须标记为 ColorComponent: csharp的 write group [WriteGroup(typeof(ColorComponent))] struct InvincibleTagComponent : IComponentData {} The ColorComponent的write group由所有标记为WriteGroup 属性类型为typeof(ColorComponent) 的组件组成.
  2. ComputeColorFromHealthSystem必须显示支持 write groups. 要做到这点, system 需要显示的声明EntityQueryOptions.FilterWriteGroup option 为所有的声明

The ComputeColorFromHealthSystem 可以像下列方式实现:

...
protected override JobHandle OnUpdate(JobHandle inputDependencies) {
   return Entities
      .WithName("ComputeColor")
      .WithEntityQueryOptions(EntityQueryOptions.FilterWriteGroup) // support write groups
      .ForEach((ref ColorComponent color, in HealthComponent health) => {
         // compute color here
      }).Schedule(inputDependencies);
}
...

当上面运行时,具体执行了下列步骤:

  1. 系统检测到你要写入ColorComponent (因为你加了ref关键字),
  2. 查找ColorComponent的写入组,发现 InvincibleTagComponent 在里面
  3. 它将排除所有具有 InvincibleTagComponent的实体.

 这样做的好处是让系统能够基于组件类型排除实体,这些类型的组件来自不同的资源包,系统彼此之间不知道

Note:  更多的例子去查看Unity.Transforms 代码, 它使用 write groups给它每一个更新的组件,包括LocalToWorld.

Creating write groups

  你可以通过添加 WriteGroup属性 来声明write group里面包含哪种组件类型WriteGroup属性包含一个参数,用来更新它里面的组件组件类型 . 单个组件可以是多个write group的成员.

例如,如果您有一个系统,每当一个实体上有组件a或B时,就向组件W进行写操作,那么您可以为W定义一个写组,如下所示

public struct W : IComponentData
{
   public int Value;
}

[WriteGroup(typeof(W))]
public struct A : IComponentData
{
   public int Value;
}

[WriteGroup(typeof(W))]
public struct B : IComponentData
{
   public int Value;
}

注意,您没有将WriteGroup自己(上面示例中的W组件)添加到它自己的WriteGroup中。

Enabling write group filtering

打开 write group 检索, set the FilterWriteGroups flag on your job:

public class AddingSystem : JobComponentSystem
{
   protected override JobHandle OnUpdate(JobHandle inputDeps) {
      return Entities
         .WithEntityQueryOptions(EntityQueryOptions.FilterWriteGroup) // support write groups
         .ForEach((ref W w, in B b) => {
            // perform computation here
         }).Schedule(inputDeps);}
}

For query description objects, set the flag when you create the query:

public class AddingSystem : JobComponentSystem
{
   private EntityQuery m_Query;

   protected override void OnCreate()
   {
       var queryDescription = new EntityQueryDesc
       {
           All = new ComponentType[] {
              ComponentType.ReadWrite<W>(),
              ComponentType.ReadOnly<B>()
           },
           Options = EntityQueryOptions.FilterWriteGroup
       };
       m_Query = GetEntityQuery(queryDescription);
   }
   // Define Job and schedule...
}

当你在query中打开write group filtering  , the query会把write group中的所有组件添加到 None列表里面,除非你显示的把他们添加到All 或者 Any 列表里面, 结果就是查询会选择那些在write group里面的实体.如果该实体有一个或多个来自write group的组件,则查询会忽略它 If an entity has one or more additional components from that write group, the query rejects it.

上面的结果就是:

  • 排除所有含 component A的组件, 因为 W 是可写入的 and AW写入组的一部分
  • 不会排除含有B.组件的实体,即使他也是w的一部分,但是它显式的声明了all

Overriding another system that uses write groups

如果一个系统在查询中使用了 write group 检索,你可以重写这个系统,并且能够使用你自己的系统写入这些组件. 为了重写system, 把你自己的组件添加到其它系统要写入的组件的写入组里面. 因为 write group 检索器会排除掉在write group里面的组件,任何含有其中组件的实体,都会被系统查询忽略掉

比如:如果你想通过声明角度和旋转的轴来更改你实体的朝向,你可以创建一个组件和一个系统,把角度和轴的值转换成一个四元数,然后把它写入到 Unity.Transforms.Rotation 组件里面. 为了防止 Unity.Transforms 系统更新 Rotation, 不管你现在还有没有其它组件,你可以把你的组件放到 Rotation的写入组里面:

using System;
using Unity.Collections;
using Unity.Entities;
using Unity.Transforms;
using Unity.Mathematics;

[Serializable]
[WriteGroup(typeof(Rotation))]
public struct RotationAngleAxis : IComponentData
{
   public float Angle;
   public float3 Axis;
}

然后,你可以用RotationAngleAxis组件更新任何实体,而不会引起争用:

using Unity.Burst;
using Unity.Entities;
using Unity.Jobs;
using Unity.Collections;
using Unity.Mathematics;
using Unity.Transforms;

public class RotationAngleAxisSystem : JobComponentSystem
{
   protected override JobHandle OnUpdate(JobHandle inputDependencies)
   {
      return Entities.ForEach((ref Rotation destination, in RotationAngleAxis source) =>
      {
         destination.Value = quaternion.AxisAngle(math.normalize(source.Axis), source.Angle);
      }).Schedule(inputDependencies);
   }
}

Extending another system that uses write groups

如果您想扩展另一个系统,而不是直接覆盖它, 而且,您希望允许将来的系统覆盖或扩展您的系统,你可以打开你自己系统上的 write group filtering ,这样默认就不会有系统处理组件组合.你必须在每个查询中显示的声明,才能处理每个组合.

作为一个示例,让我们回到前面描述的示例,该示例定义了一个包含针对组件W的组件a和B的写组. 如果你只是添加一个新的组件c,然后把它添加到write group里面,则新的系统知道c可以用来查询实体,即使这些实体不包含a或者b,但是如果新系统打开了write group ,就不会用c来查找实体了,你如你只需要c,则write group排除掉那些包含a或者b的实体,如果想要得到包含a或者b的实体,则需要显示的在查询中声明. (你可以使用 “Any”来声明 clause of the query when appropriate.)

var query = new EntityQueryDesc
{
   All = new ComponentType[] {ComponentType.ReadOnly<C>(), ComponentType.ReadWrite<W>()},
   Any = new ComponentType[] {ComponentType.ReadOnly<A>(), ComponentType.ReadOnly<B>()},
   Options = EntityQueryOptions.FilterWriteGroup
};

 任何包含write group中的组件组合的实体,如果没有显示的声明,则不会被任何系统处理,但是,在程序中首先创建这样的实体很可能是一个逻辑错误。Any entities containing combinations of components in the write group that are not explicitly mentioned will not be handled by any system that writes to the target of the write group (and filters on write groups). But then, it is most likely a logical error in the program to create such entities in the first place.

 

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

TO_ZRG

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值