Job System之Hello World

洪流学堂,让你快人几步。你好,我是跟着大智学Unity的萌新,我叫小新,最近在探索DOTS。

之前说了那么多枯燥的原理,咱们来看看Job System的代码如何写吧。

创建Job

创建Job需要定义一个结构体,实现IJob接口。实现了IJob接口之后,就可以让这个Job和其他Job并行运行了。

到这呢,就可以给Job一个真正的定义了:Job是一个统称,任何实现了IJob接口的结构体,都可以成为一个Job。

创建Job的步骤如下:
1、创建一个实现IJob接口的结构体
2、给结构体添加所需的成员变量,可以使用blittable类型或者NativeContainer类型。
3、在结构体中添加一个Execute方法,具体执行的任务在这个方法里实现。

当执行Job时,Execute方法会在一个内核上执行完毕。

注意:设计job时,记住job操作的是数据的拷贝,除非使用NativeContainer。所以,在主线程访问job数据的唯一方法就是写入NativeContainer

实例代码如下:

// 这个Job的功能:将两个浮点数相加
public struct MyJob : IJob
{
    public float a;
    public float b;
    public NativeArray result;

    public void Execute()
    {
        result[0] = a + b;
    }
}

调度Job

创建Job后,如何执行一个Job呢?

这时候需要调度Job,调度Job的步骤如下:
1、实例化Job
2、构造Job的数据
3、调用Schedule方法。

调用Schedule方法会将Job放到Job执行队列的适当位置。一旦安排了Job后,就不能再中断job执行了。

注意:只能在主线程中调用Schedule方法。

// 创建一个长度为1的native array用来存储job执行后的结果
NativeArray result = new NativeArray(1, Allocator.TempJob);

// 设置job的数据
MyJob jobData = new MyJob();
jobData.a = 10;
jobData.b = 10;
jobData.result = result;

// Schedule
JobHandle handle = jobData.Schedule();

// 等待job执行完毕
handle.Complete();

// 获取result中的数据
float aPlusB = result[0];

// 一定要释放native array申请的内存
result.Dispose();

Job的依赖关系

很多时候,Job并不是独立运行的,需要依赖前一个Job的结果,这时候如何调度呢?

JobHandle

当调用Schedule方法时,会返回一个JobHandle。你可以使用这个JobHandle作为其他job的依赖项。具体方法就是将第一个job的JobHandle传给第二个job调用Schedule时的参数,例如:

JobHandle firstJobHandle = firstJob.Schedule();
secondJob.Schedule(firstJobHandle);

那如果一个job有多个依赖项怎么办呢?这时候可以用JobHandle.CombineDependencies方法合并他们。具体如下:

// 声明一个JobHandle的NativeArray数组
NativeArray handles = new NativeArray(numJobs, Allocator.TempJob);

// 将多个handles放到数组中

// 将多个handles合并到一起
JobHandle jh = JobHandle.CombineDependencies(handles);

等待Job执行完毕

在主线程中如何等待Job执行完毕呢?可以调用JobHandle中的Complete方法强制等待。Complete方法执行过后,你就可以在主线程中安全地访问job中使用的NativeContainer了。

注意
当你调用job的Schedule方法后,job并不会立即开始执行。如果你在主线程中等待job执行完毕,并且你需要访问job使用的NativeContainer中的数据时,你可以调用JobHandle.Complete方法。这个方法会启动job的执行。调用JobHandle的Complete方法后,会将job的NativeContainer所有权还给主线程。所以只有调用过JobHandle上的Complete方法后,主线程才能安全的访问NativeContainer中的数据。同理,也可以调用依赖此job的JobHandle上的Complete方法。例如,你可以调用jobA的Complete方法,也可以调用依赖jobA的JobB的Complete方法。这两种情况下,主线程都可以安全访问jobA使用的NativeContainer。

实例代码

Job代码:

// Job:两个浮点数相加
public struct MyJob : IJob
{
    public float a;
    public float b;
    public NativeArray result;

    public void Execute()
    {
        result[0] = a + b;
    }
}

// Job:给一个值加一
public struct AddOneJob : IJob
{
    public NativeArray result;
    
    public void Execute()
    {
        result[0] = result[0] + 1;
    }
}

主线程代码:

// 创建存储结果的NativeArray
NativeArray result = new NativeArray(1, Allocator.TempJob);

// 设置job #1
MyJob jobData = new MyJob();
jobData.a = 10;
jobData.b = 10;
jobData.result = result;

// 调度job1
JobHandle firstHandle = jobData.Schedule();

// 设置job2
AddOneJob incJobData = new AddOneJob();
incJobData.result = result;

// 调度 job2,依赖job1
JobHandle secondHandle = incJobData.Schedule(firstHandle);

// 等待job2执行完毕
secondHandle.Complete();

// 访问结果
float aPlusB = result[0];

// 释放内存
result.Dispose();

总结

这一节是Job System的Hello World,和其他Hello World一样,并不能解决什么实际问题。等咱们基础知识入门完毕后,再一起来看看Job System的威力。

扩展阅读

【扩展学习】洪流学堂公众号回复DOTS可以阅读本系列所有文章,更有视频教程等着你!


呼~ 今天小新絮絮叨叨的真是够够的了。没讲清楚的地方欢迎评论,咱们一起探索。

我是大智(微信:zhz11235),你的技术探路者,下次见!

别走!点赞收藏哦!

好,你可以走了。

你可能感兴趣的:(Job System之Hello World)