上篇提到:如果一个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