上篇提到:如果一个job有多个依赖项,你可以使用JobHandle.CombineDependencies方法来合并他们。CombineDependencies允许你将他们传递给Schedule方法。例如生活中想要煮面条,我们要向锅中加入水,然后等水烧开才能下面条,下面条就要依赖:加水、水烧开这两个条件,JobHandle结构体提供了静态方法来实现,它返回的也是个JobHandle。
案例八:Job组合关系控制Cube向上移动并旋转(Scne9)
脚本:IJobParallelForTransformCombine
using UnityEngine;
using Unity.Jobs;
using Unity.Entities;
using Unity.Collections;
using UnityEngine.Jobs;
using Unity.Mathematics;
using Unity.Burst;
public class IJobParallelForTransformCombine : MonoBehaviour
{
[BurstCompile]
struct VelocityJob : IJobParallelFor
{
public NativeArray<Vector3> positions;
[ReadOnly]
public NativeArray<Vector3> velocitys;
public float delaTime;
// 并行化 让一个线程做数组的一部分处理
public void Execute(int index)
{
positions[index] = positions[index] + velocitys[index] * delaTime;
}
}
[BurstCompile]
// 物体旋转
struct RotateJob : IJobParallelFor
{
public NativeArray<quaternion> quaternions;
[ReadOnly]
public float deltaTime;
public void Execute(int index)
{
quaternions[index] = math.mul(math.normalize(quaternions[index]), quaternion.AxisAngle(math.up(), 5 * deltaTime));
}
}
[BurstCompile]
// 注意这个 job扩展是定义在 UnityEngine.Jobs命名空间下
struct ApplyTransform : IJobParallelForTransform
{
[ReadOnly]
public NativeArray<Vector3> positions;
[ReadOnly]
public NativeArray<quaternion> quaternions;
public void Execute(int index, TransformAccess transform)
{
transform.position = positions[index];
transform.rotation = quaternions[index];
}
}
public int gameCount = 300;
public GameObject prefab;
public GameObject[] gameObjs;
private TransformAccessArray tranAccessArray;
void Start()
{
gameObjs = new GameObject[gameCount];
tranAccessArray = new TransformAccessArray(gameCount); // 注意 这种类型数组必须将capacity主动填上 不能像list一样直接new便可以add
for (int i = 0; i < gameCount; i++)
{
gameObjs[i] = Instantiate<GameObject>(prefab);
gameObjs[i].transform.position = UnityEngine.Random.insideUnitSphere * 40;
tranAccessArray.Add(gameObjs[i].transform);
}
tmpPositions = new NativeArray<Vector3>(gameCount, Allocator.Persistent);
tmpVelocitys = new NativeArray<Vector3>(gameCount, Allocator.Persistent);
tmpQuaternion = new NativeArray<quaternion>(gameCount, Allocator.Persistent);
}
NativeArray<Vector3> tmpPositions;
NativeArray<Vector3> tmpVelocitys;
NativeArray<quaternion> tmpQuaternion;
void Update()
{
// 1.准备数据
for (int i = 0; i < gameCount; i++)
{
tmpVelocitys[i] = new Vector3(0, 1, 0);
//tmpPositions[i] = tmpPositions[i] + tmpVelocitys[i] * Time.deltaTime;
tmpPositions[i] = gameObjs[i].transform.position;
tmpQuaternion[i] = gameObjs[i].transform.rotation;
}
VelocityJob job = new VelocityJob()
{
positions = tmpPositions,
delaTime = Time.deltaTime,
velocitys = tmpVelocitys
};
RotateJob rotateJob = new RotateJob()
{
deltaTime = Time.deltaTime,
quaternions = tmpQuaternion
};
//依赖按照速度计算的到的位置数组
ApplyTransform applyTransform = new ApplyTransform()
{
positions = tmpPositions,
quaternions = tmpQuaternion
};
// 2.执行
//信号量 主线程如何知道子线程执行完毕 gameCount 指定总共子线程执行数据数量 10:每个子线程以下处理多少次
JobHandle jobHandle = job.Schedule(gameCount, 10); // 移动 Job
JobHandle rotateHandle = rotateJob.Schedule(gameCount, 10); // 旋转 Job
JobHandle combineHandle = JobHandle.CombineDependencies(jobHandle, rotateHandle); // 赋值job 共同依赖联合的句柄~~
JobHandle tranHandle = applyTransform.Schedule(tranAccessArray, combineHandle); // 对 obj 赋值 job
// 3.同步
jobHandle.Complete();
rotateHandle.Complete();
tranHandle.Complete();
//4。更新位置
//for (int i = 0; i < gameCount; i++)
//{
// gameObjs[i].transform.position = tmpPositions[i];
//}
}
private void OnDestroy()
{
this.tmpPositions.Dispose();
this.tmpQuaternion.Dispose();
this.tmpVelocitys.Dispose();
this.tranAccessArray.Dispose();
}
}
在项目中Burst Compiler与Job作业系统一起使用,通过在JOB结构体上添加 [BurstCompile]特性进行编译提高性能,Job菜单使用来关闭和打开调试选项,使用类Shader语法的Unity.Mathematics库提高编译性能 float ,float3,int2,int3,bool2,bool3,etc等。
Burst编译器的原理:
1.Brust编译器是以LLVM为基础的后端编译技术。
2.编译器的原理分为5个步骤:源代码-》前端-》优化器-》后端-》机器码
3.LLVM的定义了抽象语言IR,前端负责将源代码(c#)编译成IR,优化器负责优化IR,后端负责将IR生成目标语言这里就是机器码。
4.正式因为抽象语言IR的存在,所以LLVM支持的语言很多,而且也方便扩展C#、ActionScript、Ada、D语言、Fortran、GLSL、Haskell、Java字节码、Objective-C、Swift、Python、Ruby、Rust、Scala等语言。
5.LLVM代码是开源的,所以Unity很适合用它来做Burst的编译。
6.遗憾的是LLVM对C#的GC做的不好,所以burst只支持值类型数据编译,不支持引用类型数据编译。
最终Unity能够快速、高效的运转游戏实际就是要用到 UnityECS+Unity Job System+Burst编译器 来实现。程序员只需要学前两种的开发方式和最后Burst去编译。
远程项目仓库(github):https://github.com/h1031464180/UnityECS