您的位置:首页 > 移动开发 > Unity3D

Unity ECS+Jobs System笔记 访问数据2(六)

2019-08-18 16:38 2581 查看
原文链接:https://docs.unity3d.com/Packages/com.unity.entities@0.0/manual/index.html

来源:https://docs.unity3d.com/Packages/com.unity.entities@0.0/manual/index.html
我会对官方文档内容略作整理,有需要可以查看官方文档

3、IJobChunk

你可以通过在JobComponentSystem中实现IJobChunk来对区块中的数据进行迭代,JobComponentSystem会一个个的对每一个你希望系统去执行的实体所在的区块执行Execute()函数,然后,你可以逐个实体处理每个区块内的数据
使用IJobChunk进行迭代需要更多(相较于IJobForEach而言)的设置,但同时也更加明确,并且是对数据最直接的访问(因为访问的就是内存块)
使用IJobChunk的另一个好处是,您可以检查每个区块中是否存在可选组件(是原型所具有的组件),然后相应处理区块中所有的实体
实现IJobChunk作业需要的步骤包括:

  1. 通过创建EntityQuery来标识要处理的实体
  2. 定义Job结构,包括ArchetypeChunkComponentType对象的字段——以标识Job需要直接访问的组件类型并指定Job是读取还是写入这些组件
  3. 实例化Job结构并在系统OnUpdate()函数中调度Job
  4. 在Execute()函数中,获取Job读取或写入的组件的NativeArray,最后迭代当前块以执行所需的工作

可以参考ECS Sample中的HelloCube例子

通过EntityQuery查询数据

EntityQuery定义了原型必须包含的一些组件类型,以便系统处理与其相关联的区块和实体,原型也可以有其他组件,但它必须至少包含EntityQuery所定义的组件,你还可以排除包含特定类型组件的原型
对于简单查询,可以使用JobComponentSystem.GetEntityQuery()函数,只需传入组件类型:

public class RotationSpeedSystem : JobComponentSystem
{
private EntityQuery m_Group;
protected override void OnCreate()
{
m_Group = GetEntityQuery(typeof(RotationQuaternion), ComponentType.ReadOnly<RotationSpeed>());
}
//…
}

对于更复杂的情况,可以使用EntityQueryDesc,它提供了灵活的查询机制来查询指定组件类型:

  • All——原型中必须包含参数数组中所有的组件
  • Any——原型中至少要包含一个参数数组中的组件
  • None——原型中不能有任何一个参数数组中的组件

例如,以下代码查询了包含RotationQuaternion和RotationSpeed组件,但不包括任何包含Frozen组件的原型

protected override void OnCreate()
{
var query = new EntityQueryDesc
{
None = new ComponentType[]{ typeof(Frozen) },
All = new ComponentType[]{ typeof(RotationQuaternion), ComponentType.ReadOnly<RotationSpeed>() }
}
};
m_Group = GetEntityQuery(query);
}

使用ComponentType.ReadOnly< T > 而不是更简单的typeof表达式,可以以只读的方式查询RotationSpeed
你还可以通过传递EntityQueryDesc数组而不是单个来组合查询,使用or运算组合每个查询,以下代码查询了包含RotationQuaternion组件或RotationSpeed组件(或两者)的原型:

protected override void
7ff7
OnCreate()
{
var query0 = new EntityQueryDesc
{
All = new ComponentType[] {typeof(RotationQuaternion)}
};

var query1 = new EntityQueryDesc
{
All = new ComponentType[] {typeof(RotationSpeed)}
};

m_Group = GetEntityQuery(new EntityQueryDesc[] {query0, query1});
}

注意: 不要在EntityQueryDesc中包含完全可选的组件,要处理可选组件,请在IJobChunk.Execute()中使用chunk.Has< T >()来确定当前ArchetypeChunk是否具有可选组件,由于同一区块中的所有实体具有相同的组件,因此只需要检查每个块是否存在可选组件——而不是检查每个实体
为了提高效率并避免不必要GC,应该在OnCreate()函数中为系统创建EntityQueries,并将结果存储在变量中

定义IJobChunk结构

IJobChunk结构定义了Job运行时所需数据的字段,以及Job的Execute()方法
要访问系统传递给Execute()方法的块内部的组件数组,必须对需要读取或写入的每种类型组件的对象创建ArchetypeChunkComponentType,这些对象允许您获取NativeArrays的实例,从而提供对实体组件的访问,这包含了Execute函数所读写的在Job的实体队列中的所有组件。还可以为EntityQuery中未包含可选组件类型提供ArchetypeChunkComponentType变量。(在尝试访问之前,必须检查以确保当前块具有可选组件)
例如,在HelloCube的IJobChunk示例声明了一个定义ArchetypeChunkComponentType的两个组件变量——RotationQuaternion和RotationSpeed:

[BurstCompile]
struct RotationSpeedJob : IJobChunk
{
public float DeltaTime;
public ArchetypeChunkComponentType<RotationQuaternion> RotationType;
[ReadOnly] public ArchetypeChunkComponentType<RotationSpeed> RotationSpeedType;

public void Execute(ArchetypeChunk chunk, int chunkIndex, int firstEntityIndex){
//...
}
}

系统在OnUpdate()函数中为这些变量赋值,当ECS框架运行Job时,变量在Execute()方法内部使用
Job还使用Unity delta时间为3D对象的旋转设置动画,该示例还使用struct字段将此值传递给Execute方法

编写Execute方法

IJobChunk Execute()方法的写法是:

public void Execute(ArchetypeChunk chunk, int chunkIndex, int firstEntityIndex)

该chunk参数是包含要在此Job迭代中处理的实体和组件的内存块的句柄,由于chunk只能包含单个原型,因此块中的所有实体都具有相同的组件
使用chunk参数获取组件的NativeArray实例:

var chunkRotations = chunk.GetNativeArray(RotationType);
var chunkRotationSpeeds = chunk.GetNativeArray(RotationSpeedType);

这些数组都是对齐的,因此每个实体在不同数组中有着相同的索引,也就可以正常使用for循环遍历组件数组,使用chunk.Count得到存储在当前块的实体的数量:

for (var i = 0; i < chunk.Count; i++)
{
var rotation = chunkRotations[i];
var rotationSpeed = chunkRotationSpeeds[i];

// Rotate something about its up vector at the speed given by RotationSpeed.
chunkRotations[i] = new RotationQuaternion
{
Value = math.mul(math.normalize(rotation.Value),
quaternion.AxisAngle(math.up(), rotationSpeed.RadiansPerSecond * DeltaTime))
};
}

如果你在EntityQueryDesc使用Any过滤器或者根本没有查询中出现的可选组件,那么就可以在使用它之前用ArchetypeChunk.Has< T >函数测试当前块是否包含其中一个组件

if (chunk.Has<OptionalComp>(OptionalCompType))
{
//...
}

注意: 如果使用并发实体命令缓冲区,请将chunkIndex参数作为jobIndex参数传递给命令缓冲区函数。

跳过包含未改变的实体的区块

如果只需在组件值更改时更新实体,则可以将该组件类型添加到EntityQuery的更改筛选器(change filter)中——用于选择Job所需的实体和块,例如,如果你有一个系统读取两个组件,并且只需要在前两个组件中的一个发生更改时更新第三个组件,则可以使用EntityQuery,如下所示:

EntityQuery m_Group;
protected override void OnCreate()
{
m_Group = GetEntityQuery(typeof(Output),
ComponentType.ReadOnly<InputA>(),
ComponentType.ReadOnly<InputB>());
m_Group.SetFilterChanged(new ComponentType{ typeof(InputA), typeof(InputB)});
}

EntityQuery更改筛选器最多支持两个组件,如果要查看更多或布使用EntityQuery,可以选择手动进行检查,要进行此检查,就使用ArchetypeChunk.DidChange()函数将组件的块更改版本与系统的LastSystemVersion进行比较,如果此函数返回false,则可以跳过当前块,因为自上次系统运行以来,该类型的所有组件都没有更改
系统中的LastSystemVersion必须使用struct传递给Job

[BurstCompile]
struct UpdateJob : IJobChunk
{
public ArchetypeChunkComponentType<InputA> InputAType;
public ArchetypeChunkComponentType<InputB> InputBType;
[ReadOnly] public ArchetypeChunkComponentType<Output> OutputType;
public uint LastSystemVersion;

public void Execute(ArchetypeChunk chunk, int chunkIndex, int firstEntityIndex){
var inputAChanged = chunk.DidChange(InputAType, LastSystemVersion);
var inputBChanged = chunk.DidChange(InputBType, LastSystemVersion);
if (!(inputAChanged || inputBChanged))
return;
//...
}

与所有Job结构字段一样,您必须在安排Job之前分配其变量:

var job = new UpdateJob()
{
LastSystemVersion = this.LastSystemVersion,
//… initialize other fields
}

请注意,为了提高效率,版本更改适用于整个块而不是单个实体,如果某个块已被另一个能够写入该类型组件的Job访问,则该组件的版本将更改,并且DidChange()函数返回true

实例化并安排Job

要运行IJobChunk的Job,必须创建Job结构体,设置结构体的字段,然后安排Job,在JobComponentSystem的OnUpdate()函数中执行此操作时,系统会以每帧调度Job

// OnUpdate runs on the main thread.
protected override JobHandle OnUpdate(JobHandle inputDependencies)
{
var job = new RotationSpeedJob()
{
RotationType = GetArchetypeChunkComponentType<RotationQuaternion>(false),
RotationSpeedType = GetArchetypeChunkComponentType<RotationSpeed>(true),
DeltaTime = Time.deltaTime
};

return job.Schedule(m_Group, inputDependencies);
}

当你调用GetArchetypeChunkComponentType函数来设置组件类型变量时,确保作业所读取的组件是只读的,正确设置这些参数会对ECS框架调度作业的效率产生重大影响,这些访问模式设置必须使结构体定义和EntityQuery中的设置相匹配
不要缓存GetArchetypeChunkComponentType在系统类变量中的返回值,因为系统每次运行时都必须调用该函数,并将更新后的值传递给Job

内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: