Jobs work in a fairly specific way (in order to get the most performance and safe guard against multithreading issues) and this dictates what they are good for (and what they are not good for too).
Example 1
Jobs are not meant to perform menial tasks. In fact, in certain cases actually scheduling the job and completing it will take more resources/time than performing the same calculation on the main thread.
BadJob.cs
public struct WasteOfTime : IJob
{
public float a;
public float b;
public NativeArray<float> result;
public void Execute()
{
result[0] = a + b;
}
}
Bad.cs
NativeArray<float> result = new NativeArray<float>(1, Allocator.TempJob);
MyJob jobData = new MyJob();
jobData.a = 10;
jobData.b = 10;
jobData.result = result;
JobHandle handle = jobData.Schedule();
handle.Complete();
Debug.Log(result[0]);
result.Dispose();
Example 2
A good use for a job might be converting seconds to hours, minutes, and seconds.
ConvertSecondsToHoursMinutesSecondsJob.cs
public struct ConvertSecondsToHoursMinutesSecondsJob : IJob
{
public int seconds;
public NativeArray<float> result;
public void Execute()
{
// convert given number of seconds to hours, minutes, and seconds HH:MM:SS
}
}
ConvertSecondsToHoursMinutesSeconds.cs
public int hours;
public int minutes;
public int seconds;
NativeArray<int> result = new NativeArray<float>(3, Allocator.TempJob);
ConvertSecondsToHoursMinutesSecondsJob jobData = new ConvertSecondsToHoursMinutesSecondsJob ();
jobData.seconds = 3730 // 1 hour, 2 minutes, 10 seconds
jobData.result = result;
JobHandle handle = jobData.Schedule();
handle.Complete();
hours = result[0];
minutes = result[1];
seconds = result[2];
result.Dispose();
That’s not how it works.
The data inside the struct is special. You’re not allowed to touch it with your dirty, sticky, unoptimized hands. However, you can pass it data and kindly ask a job to tell you the result.
This result can then be passed back into a “normal” float/bool/string and then you can use it.
ConvertSecondsToHoursMinutesSecondsJob.cs
public struct ConvertSecondsToHoursMinutesSecondsJob : IJob
{
// You can not reference this! When you create a job you are essentially
// creating a copy of this struct in order to pass it data.
public int seconds;
// You need to store the result in a native array (doesn't have to be called result)
// This is how you will access the data when the job has finished.
public NativeArray<float> result;
public void Execute()
{
// convert given number of seconds to hours, minutes, and seconds HH:MM:SS
}
}
ConvertSecondsToHoursMinutesSeconds.cs
// setup "normal" variables to store the results of a job
public int hours;
public int minutes;
public int seconds;
// setup a native array that will be passed to the job
NativeArray<int> result = new NativeArray<float>(3, Allocator.TempJob);
// job data
ConvertSecondsToHoursMinutesSecondsJob jobData = new ConvertSecondsToHoursMinutesSecondsJob ();
// give it some data
jobData.seconds = 3730 // 1 hour, 2 minutes, 10 seconds
// pass the native array
jobData.result = result;
// schedule the job
JobHandle handle = jobData.Schedule();
// complete it
handle.Complete();
// get the result of the calculation and put it back into the "normal" variables for later use
hours = result[0];
minutes = result[1];
seconds = result[2];
// get rid of it now because we don't need it anymore
result.Dispose();
A few things to note:
-
You should schedule jobs as early as possible, and only complete as late as possible. Completing in LateUpdate is common. However, TempJobs get 4 frames. So you can “wait” a maximum of 3 entire frames to complete a job for max performance.
-
If you are only passing data to a job it can be marked as ready only. This tells the job that it will only be reading that data, not writing to it. In the above example, the seconds input could be marked as ready only as the job would never overwrite the provided seconds (kind of like a static modifier)
-
You can use IJobParrallelForTransform for rotating or translating gameobject transforms. For example, you can use it for world origin shifting.
ExampleJob.cs
public struct ExampleJob : IJob
{
public int numberA;
public int numberB;
public NativeArray<bool> result;
public void Execute()
{
if (numberA == numberB)
{
result[0] = true;
}
}
}
ExampleScript.cs
public bool someBoolean;
public void SomeMethod()
{
NativeArray<bool> result = new NativeArray<bool>(1, Allocator.TempJob);
ExampleJob jobData = new ExampleJob();
jobData.numberA = 10;
jobData.numberB = 10;
jobData.result = result;
JobHandle handle = jobData.Schedule();
handle.Complete();
someBoolean = result[0];
result.Dispose();
}
SecondScript.cs
public ExampleScript exampleScript;
public bool anotherBoolean;
exampleScript.SomeMethod();
anotherBoolean = exampleScript.someBoolean;
It might need to be optimised more in terms of when the job is completed… but that would probably work. Call a method from the other script that initiates a job.