Table of Contents
Jobs in ECS
ECS使用Job系统实施行为-ECS的System部分。具体而言,ECS系统就是一个用来 转换存储在实体组件中的数据 而创建的Job。
例如,以下系统更新positions:
using Unity.Burst;
using Unity.Collections;
using Unity.Entities;
using Unity.Jobs;
using Unity.Transforms;
using UnityEngine;
public class MovementSpeedSystem : JobComponentSystem
{
[BurstCompile]
struct MovementSpeedJob : IJobForEach<Position, MovementSpeed>
{
public float dT;
public void Execute(ref Position Position, [ReadOnly] ref MovementSpeed movementSpeed)
{
float3 moveSpeed = movementSpeed.Value * dT;
Position.Value = Position.Value + moveSpeed;
}
}
// OnUpdate runs on the main thread.
protected override JobHandle OnUpdate(JobHandle inputDependencies)
{
var job = new MovementSpeedJob()
{
dT = Time.deltaTime
};
return job.Schedule(this, inputDependencies);
}
}
Job extensions
Unity C# Job System使您可以在多个线程上运行代码。该系统提供调度,并行处理和多线程安全性。Job System
是Unity的核心模块,提供用于创建和运行job的通用接口和类(无论您是否使用ECS)。这些接口包括:
- IJob:创建由Job System调度程序确定的可在任何线程或内核上运行的Job。
- IJobParallelFor:创建一个可以在多个线程上并行运行的Job,以处理NativeContainer的元素。。
- IJobExtensions:提供用于运行Jobs的扩展方法。
- IJobParalllelForExtensions:提供用于运行
IJobParallelFor
作业的扩展方法。 - JobHandle:用于访问预定作业的句柄。JobHandle实例还允许您指定Job之间的依赖关系。
有关Jobs系统的概述,请参见Unity手册中的C#Job System。
Jobs package扩展了Job System以支持ECS。它包含了:
- IJobParallelForDeferExtensions
- IJobParallelForFilter
- JobParallelIndexListExtensions
- [JobStructProduce
](https://docs.unity3d.com/Packages/com.unity.jobs@0.2/api/Unity.Jobs.JobParallelIndexListExtensions.JobStructProduce-1.html)
IJob
IJob允许您安排一个与其他job和主线程并行运行的job。安排作业后,将在工作线程上调用该job的Execute方法。返回的JobHandle可用于确保job已完成。或者可以将它作为依赖项传递给其他job,从而确保在工作线程上一个接一个地执行jobs。
using UnityEngine;
using Unity.Collections;
using Unity.Jobs;
class ApplyVelocitySample : MonoBehaviour
{
struct VelocityJob : IJob
{
// Jobs declare all data that will be accessed in the job
// By declaring it as read only, multiple jobs are allowed to access the data in parallel
[ReadOnly]
public NativeArray<Vector3> velocity;
// By default containers are assumed to be read & write
public NativeArray<Vector3> position;
// Delta time must be copied to the job since jobs generally don't have concept of a frame.
// The main thread waits for the job on the same frame or the next frame, but the job should
// perform work in a deterministic and independent way when running on worker threads.
public float deltaTime;
// The code actually running on the job
public void Execute()
{
// Move the positions based on delta time and velocity
for (var i = 0; i < position.Length; i++)
position[i] = position[i] + velocity[i] * deltaTime;
}
}
public void Update()
{
var position = new NativeArray<Vector3>(500, Allocator.Persistent);
var velocity = new NativeArray<Vector3>(500, Allocator.Persistent);
for (var i = 0; i < velocity.Length; i++)
velocity[i] = new Vector3(0, 10, 0);
// Initialize the job data
var job = new VelocityJob()
{
deltaTime = Time.deltaTime,
position = position,
velocity = velocity
};
// Schedule the job, returns the JobHandle which can be waited upon later on
JobHandle jobHandle = job.Schedule();
// Ensure the job has completed
// It is not recommended to Complete a job immediately,
// since that gives you no actual parallelism.
// You optimally want to schedule a job early in a frame and then wait for it later in the frame.
jobHandle.Complete();
Debug.Log(job.position[0]);
// Native arrays must be disposed manually
position.Dispose();
velocity.Dispose();
}
}
IJobParallelFor
并行job可让您对nattive 容器的每个元素或固定数量的迭代执行相同的独立操作。
调度后,job的Execute(int index)方法将在彼此并行的多个工作线程上调用。
对于从0到提供的长度的每个索引,将执行一次Execute(int index)。每次迭代必须独立于其他迭代(安全系统会为您强制执行此规则)。索引没有保证的顺序,并且在多个内核上并行执行。
Unity自动将job分割成不少于提供的batchSize的块,并根据工作线程的数量,数组的长度和批处理的大小来调度适当数量的job。
通常应根据job中执行的工作量选择batch size
。一个简单的job,例如互相添加几个Vector3的批处理大小可能应该在32到128之间。但是,如果执行的job非常昂贵,则最好使用小batch size
,对于昂贵的工作,最好使用batch size
为1完全可以。IJobParallelFor使用原子操作执行工作窃取。batch size
可以很小,但不是免费的。
返回的JobHandle
可用于确保job已完成。或者可以将它作为依赖项传递给其他作业,从而确保在工作线程上一个接一个地执行job。
using UnityEngine;
using Unity.Collections;
using Unity.Jobs;
class ApplyVelocityParallelForSample : MonoBehaviour
{
struct VelocityJob : IJobParallelFor
{
// Jobs declare all data that will be accessed in the job
// By declaring it as read only, multiple jobs are allowed to access the data in parallel
[ReadOnly]
public NativeArray<Vector3> velocity;
// By default containers are assumed to be read & write
public NativeArray<Vector3> position;
// Delta time must be copied to the job since jobs generally don't have concept of a frame.
// The main thread waits for the job same frame or next frame, but the job should do work deterministically
// independent on when the job happens to run on the worker threads.
public float deltaTime;
// The code actually running on the job
public void Execute(int i)
{
// Move the positions based on delta time and velocity
position[i] = position[i] + velocity[i] * deltaTime;
}
}
public void Update()
{
var position = new NativeArray<Vector3>(500, Allocator.Persistent);
var velocity = new NativeArray<Vector3>(500, Allocator.Persistent);
for (var i = 0; i < velocity.Length; i++)
velocity[i] = new Vector3(0, 10, 0);
// Initialize the job data
var job = new VelocityJob()
{
deltaTime = Time.deltaTime,
position = position,
velocity = velocity
};
// Schedule a parallel-for job. First parameter is how many for-each iterations to perform.
// The second parameter is the batch size,
// essentially the no-overhead innerloop that just invokes Execute(i) in a loop.
// When there is a lot of work in each iteration then a value of 1 can be sensible.
// When there is very little work values of 32 or 64 can make sense.
JobHandle jobHandle = job.Schedule(position.Length, 64);
// Ensure the job has completed.
// It is not recommended to Complete a job immediately,
// since that reduces the chance of having other jobs run in parallel with this one.
// You optimally want to schedule a job early in a frame and then wait for it later in the frame.
jobHandle.Complete();
Debug.Log(job.position[0]);
// Native arrays must be disposed manually.
position.Dispose();
velocity.Dispose();
}
}