# 2. Jobs and Burst for GameObject Projects
Part of [[DOTS]]. Previous: [[1. DOTS Mental Model Packages and Data Locality]]. Next: [[3. Entities Authoring Baking and ISystem]].
You don't need Entities to get most of the speed-up for hot loops. Jobs and Burst work in any MonoBehaviour project.
> [!note] Verification status
> `IJobParallelFor`, `IJobParallelForTransform`/`TransformAccessArray`, `[BurstCompile]` and `noise.snoise` are long-standing APIs. The claim that `IJobParallelForTransform` parallelizes **per hierarchy root** comes from earlier Unity docs and community guidance. It was **not re-verified** on 6000.5. **Code not compiled.**
## 2.1 Rules of the Job System
1. Jobs are **structs** that hold only **blittable data and Native containers** (`NativeArray<T>` and so on). No managed objects, no `UnityEngine.Object`.
2. **Schedule** returns a `JobHandle`. **Complete()** before you read results on the main thread.
3. Native containers need explicit **Allocator** lifetimes:
- `Temp`: within one frame, main thread.
- `TempJob`: up to 4 frames, passed to jobs.
- `Persistent`: until you `Dispose()`.
4. The **safety system** throws if two jobs write the same container without a dependency. Read-only fields get `[ReadOnly]`.
5. `[BurstCompile]` on the job struct compiles `Execute` to native SIMD code. Burst supports a subset of C# (no managed allocations or exceptions in hot paths), plus `Unity.Mathematics` types (`float3`, `math.*`).
## 2.2 Example A: Burst heightmap for Terrain
[[1. Terrain Setup and Heightmaps]] generates a heightmap on the main thread. Here's the same idea as a parallel Burst job.
```csharp
// Assets/Scripts/Terrain/HeightmapNoiseJob.cs
using Unity.Burst;
using Unity.Collections;
using Unity.Jobs;
using Unity.Mathematics;
[BurstCompile]
public struct HeightmapNoiseJob : IJobParallelFor
{
[WriteOnly] public NativeArray<float> Heights; // row-major: index = z * Resolution + x
public int Resolution;
public float2 WorldSize; // terrain size in metres (x, z)
public float2 Offset; // seed offset
public float Frequency; // e.g. 0.004
public int Octaves; // e.g. 5
public float Persistence; // e.g. 0.5
public float Lacunarity; // e.g. 2
public void Execute(int index)
{
int x = index % Resolution;
int z = index / Resolution;
float2 world = new float2(x, z) / (Resolution - 1) * WorldSize + Offset;
float sum = 0f, amplitude = 1f, frequency = Frequency, norm = 0f;
for (int o = 0; o < Octaves; o++)
{
sum += amplitude * (noise.snoise(world * frequency) * 0.5f + 0.5f); // 0..1
norm += amplitude;
amplitude *= Persistence;
frequency *= Lacunarity;
}
Heights[index] = sum / norm;
}
}
```
```csharp
// Assets/Scripts/Terrain/BurstTerrainShaper.cs
using Unity.Collections;
using Unity.Jobs;
using Unity.Mathematics;
using UnityEngine;
[RequireComponent(typeof(Terrain))]
public class BurstTerrainShaper : MonoBehaviour
{
public int seed = 42;
public float frequency = 0.004f;
[Range(1, 8)] public int octaves = 5;
public float persistence = 0.5f;
public float lacunarity = 2f;
[ContextMenu("Generate (Burst)")]
public void Generate()
{
TerrainData data = GetComponent<Terrain>().terrainData;
int res = data.heightmapResolution;
var heights = new NativeArray<float>(res * res, Allocator.TempJob);
var rng = new Unity.Mathematics.Random((uint)math.max(1, seed));
var job = new HeightmapNoiseJob
{
Heights = heights,
Resolution = res,
WorldSize = new float2(data.size.x, data.size.z),
Offset = rng.NextFloat2(-10000f, 10000f),
Frequency = frequency,
Octaves = octaves,
Persistence = persistence,
Lacunarity = lacunarity,
};
// Batch size 64: each worker grabs 64 indices at a time.
job.Schedule(heights.Length, 64).Complete();
// TerrainData.SetHeights wants float[y, x] (y = z row). Copy out.
var grid = new float[res, res];
for (int z = 0; z < res; z++)
for (int x = 0; x < res; x++)
grid[z, x] = heights[z * res + x];
heights.Dispose();
data.SetHeights(0, 0, grid);
}
}
```
On a 1025² heightmap with 5 octaves, the noise loop is about 5 million `snoise` calls. Spread across cores with Burst, it should be far faster than the managed loop. **Measure it in the Profiler** rather than trusting a number, because the copy-out is still single-threaded.
## 2.3 Example B: 2,000 schooling fish with IJobParallelForTransform
```csharp
// Assets/Scripts/DOTS/FishSchoolJobs.cs
using Unity.Burst;
using Unity.Collections;
using Unity.Jobs;
using Unity.Mathematics;
using UnityEngine;
using UnityEngine.Jobs;
public class FishSchoolJobs : MonoBehaviour
{
public GameObject fishPrefab;
public int count = 2000;
public float spawnRadius = 20f;
public float speed = 3f;
public float turnRate = 2f;
public Transform attractor;
TransformAccessArray transforms;
NativeArray<float3> velocities;
JobHandle handle;
void Start()
{
transforms = new TransformAccessArray(count);
velocities = new NativeArray<float3>(count, Allocator.Persistent);
var rng = new Unity.Mathematics.Random(1234);
for (int i = 0; i < count; i++)
{
float3 p = (float3)transform.position + rng.NextFloat3Direction() * spawnRadius * rng.NextFloat();
// No shared parent! Transform jobs parallelize across hierarchy roots.
GameObject fish = Instantiate(fishPrefab, p, Quaternion.identity);
transforms.Add(fish.transform);
velocities[i] = rng.NextFloat3Direction() * speed;
}
}
void Update()
{
handle.Complete(); // previous frame's job (safety)
var job = new SwimJob
{
Velocities = velocities,
Target = attractor ? (float3)attractor.position : (float3)transform.position,
Speed = speed,
TurnRate = turnRate,
DeltaTime = Time.deltaTime,
ElapsedTime = (float)Time.timeAsDouble,
};
handle = job.Schedule(transforms);
}
void LateUpdate() => handle.Complete(); // finish before rendering
void OnDestroy()
{
handle.Complete();
if (transforms.isCreated) transforms.Dispose();
if (velocities.IsCreated) velocities.Dispose();
}
[BurstCompile]
struct SwimJob : IJobParallelForTransform
{
public NativeArray<float3> Velocities;
public float3 Target;
public float Speed, TurnRate, DeltaTime, ElapsedTime;
public void Execute(int i, TransformAccess t)
{
float3 pos = t.position;
float3 v = Velocities[i];
float s = ElapsedTime * 0.5f;
float3 wander = new float3(
noise.snoise(new float2(i * 0.37f, s)),
noise.snoise(new float2(i * 0.71f, s + 10f)) * 0.3f,
noise.snoise(new float2(i * 0.13f, s + 20f)));
float3 desired = math.normalizesafe((Target - pos) * 0.05f + wander) * Speed;
v = math.lerp(v, desired, math.saturate(TurnRate * DeltaTime));
t.position = pos + v * DeltaTime;
if (math.lengthsq(v) > 1e-4f)
t.rotation = quaternion.LookRotationSafe(math.normalize(v), math.up());
Velocities[i] = v;
}
}
}
```
### Why `Complete()` in LateUpdate?
Scheduling in Update and completing in LateUpdate gives the workers the rest of the frame (animation, physics, other scripts) to run alongside the main thread. Completing straight after `Schedule` would work, but the main thread would stand idle while it waits.
## 2.4 Burst Inspector and toggles
- **Jobs > Burst > Open Inspector**: see the generated assembly and confirm vectorization.
- **Jobs > Burst > Enable Compilation**: switch it off to compare performance or to debug with breakpoints.
- **Jobs > Burst > Safety Checks**: on in the editor. Leave them on while you develop, and profile in a **development build**.
## 2.5 Patterns
- **Data Locality** ([[Game Programming Patterns (Review)]]): `NativeArray<float3>` velocities instead of a `Fish` component on every object.
- **Update Method**: one manager updates every fish, instead of 2,000 `Update()` calls.
## Exercise
Add a "predator" transform: within 5 m, fish steer away at double speed. That's Level Up!'s **feedback** idea made visible. The school "flinches" as a group.
## Sources
- Job System overview (6000.5): https://docs.unity3d.com/6000.5/Documentation/Manual/job-system-overview.html
- `IJobParallelForTransform`: https://docs.unity3d.com/6000.5/Documentation/ScriptReference/Jobs.IJobParallelForTransform.html
- Burst on 6000.5 (1.8.31): https://docs.unity3d.com/6000.5/Documentation/Manual/com.unity.burst.html
- Mathematics `noise`: https://docs.unity3d.com/Packages/
[email protected]/api/Unity.Mathematics.noise.html
- `TerrainData.SetHeights`: https://docs.unity3d.com/6000.5/Documentation/ScriptReference/TerrainData.SetHeights.html