IJobEntity 的执行单位是 Entity,而 IJobChunk 的执行单位是 Chunk

Chunk

chunk 相当于一个存储数据的区域,其中:

  • 单个 chunk 的内存为 16kb。
  • 单个 chunk 只存储相同 archetype 的 entity。如上图,如果我们从 Entity A 中删除 Renderer 组件,那么其将从 M 转移到 N。

在 DOTS 当中会把相同组件的实体放在一起,也就是 chunk。把相同类型的数据连续存放在一块内存里,可以提高读取数据的效率。实际上 chunk 被划分为 parallel arrays (就当作给其翻译为并行数组吧,此处的意思是数组平行存储而不是具有任何嵌套关系)。

此处给出一个例子:

此处的 chunk 存储的 archetype 由 Component A, B, C 构成。其可以存储的 entity 数量约为:

1
int maxEntity = 16539 / (sizeof(id) + sizeof(A) + sizeof(B) + sizeof(C))

如图所示:这个 chunk 被划分为四个逻辑数组,每个数组的大小都是 maxEntities :一个数组用于 ID,一个用于 A 组件,一个用于 B 组件,一个用于 C 组件。当然,块还存储了这些数组的偏移量 以及 当前存储的实体个数。chunk 的第一个实体存储在所有四个数组的索引 0 处,第二个实体存储在索引 1 处,第三个实体存储在索引 2 处,依此类推。如果块有 100 个存储的实体,但我们随后删除了索引 37 处的实体,则计数将减少到 99,索引 99 处的实体将向下移动到索引 37 以填补空白。也就是使用末尾元素去填补。

Archetype

根据上面对 chunk 的介绍,差不多已经对 archetype 有了一定的概念。

刚刚才说过,一个 chunk 的大小只有 16kb 对吧。当 entity 太多的时候,我们一个 chunk 就会遇到到达容量上限的问题了。那此时就会再开一个 chunk,如上图,虽然有三个 chunk,但是它们都属于一个原型,也就是一个 archetype。archetype 也就是存放了相同 component 的类型。

Code

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
namespace HelloCube.IJobChunk
{
public partial struct RotationSystem : ISystem
{
[BurstCompile]
public void OnCreate(ref SystemState state)
{
state.RequireForUpdate<Execute.IJobChunk>();
}

[BurstCompile]
public void OnUpdate(ref SystemState state)
{
var spinningCubeQuery = SystemAPI.QueryBuilder().WithAll<RotationSpeed, LocalTransform>().Build();

var job = new RotationJob
{
TransformTypeHandle = SystemAPI.GetComponentTypeHandle<LocalTransform>(),
RotationSpeedTypeHandle = SystemAPI.GetComponentTypeHandle<RotationSpeed>(true),
DeltaTime = SystemAPI.Time.DeltaTime
};

// 与 IJobEntity 不同,IJobChunk 需要手动传递查询。
// 此外,IJobChunk 不会隐式传递和分配 state.Dependency JobHandle。
// (通过传递和分配 state.Dependency JobHandle 的模式,确保在不同系统中调度的实体作业将按需要相互依赖。)
state.Dependency = job.ScheduleParallel(spinningCubeQuery, state.Dependency);
}
}

[BurstCompile]
struct RotationJob : Unity.Entities.IJobChunk
{
public ComponentTypeHandle<LocalTransform> TransformTypeHandle;
[ReadOnly] public ComponentTypeHandle<RotationSpeed> RotationSpeedTypeHandle;
public float DeltaTime;

public void Execute(in ArchetypeChunk chunk, int unfilteredChunkIndex,
bool useEnabledMask, in v128 chunkEnabledMask)
{
// useEnableMask 参数为 true 表示在块中有一个或多个实体的查询组件被禁用。
// 如果查询组件类型都没有实现 IEnableableComponent,我们可以假设 useEnabledMask 将始终为 false。
// 但是,为了以防万一,最好添加此保护检查,以防将来有人更改查询或组件类型。
Assert.IsFalse(useEnabledMask);

var transforms = chunk.GetNativeArray(ref TransformTypeHandle);
var rotationSpeeds = chunk.GetNativeArray(ref RotationSpeedTypeHandle);

for (int i = 0, chunkEntityCount = chunk.Count; i < chunkEntityCount; i++)
{
transforms[i] = transforms[i].RotateY(rotationSpeeds[i].RadiansPerSecond * DeltaTime);
}
}
}
}

相关链接: