Unity Job System and Burst Compiler: Getting Started

In this tutorial, you’ll learn how to use Unity’s Job System and Burst compiler to create efficient code to simulate water filled with swimming fish. By Ajay Venkat.

Leave a rating/review
Download materials
Save for later
Share

Writing scalable multi-threaded code in games has always been tough, but this is changing rapidly with the release of Unity’s Data-Oriented Technology Stack (DOTS). In this tutorial, you’ll learn how to use Unity’s Job System and Burst compiler to create efficient code to simulate water filled with swimming fish.

You’ll get hands-on experience with the following topics:

  • Turning single-threaded code into efficient jobs.
  • Using the Burst compiler to speed up your projects.
  • Utilizing Unity’s Mathematics system for multi-threading.
  • Modifying mesh data in real time.
Note: This tutorial assumes that you know the basics of Unity development. If you’re new to Unity, check out this great Getting Started in Unity tutorial.

You’ll need a copy of Unity 2019.3 (or newer) installed on your machine to follow this tutorial.

Getting Started

After installing Unity, download the sample project by clicking on the Download Materials button at the top or bottom of this tutorial.

Extract the files and open the Introduction to Job System Starter project in Unity. Open RW using the Project window and take a look at the folder structure:

Folder Structure of Project

Here’s a quick breakdown of what each folder contains:

  • Materials: Materials for the water and fish.
  • Models: Models of the water and fish.
  • Prefabs: A fish prefab, which you’ll instantiate hundreds of times.
  • Scenes: The Main Scene, which you’ll modify.
  • Scripts: Starter scripts ready for you to add your awesome code.

Open the Main Scene and look at the Game view. You’ll see an empty stretch of water. Press the Play button and… nothing happens.

Empty Scene

Press the Stats button on the Game view and note the FPS. The FPS largely depends on the computer you have. You’ll use it throughout the tutorial to benchmark the performance of the Job System.

By the end, you’ll have waves on the water with thousands of fish swimming inside.

Here’s a breakdown of one frame:

  1. The code loops through 10,000 vertices of the water mesh, applying a mathematical function to change its height.
  2. Each of the 1,000 to 2,000 fish gets a random destination and velocity to swim inside the water.
Note: Remember that not all problems require multi-threading. Sometimes, code runs more slowly when it uses unnecessary threading. Multi-threading also comes with a bunch of limitations, which you’ll discover throughout this tutorial.

Installing Required Packages

Before you begin using the Job System, you have to install some packages from the Package Manager. Select Window ▸ Package Manager from the top menu.

Installing Packages from Manager

In the Package Manager, select Advanced ▸ Show preview packages and install the following:

  1. Job System
  2. Burst Compiler
  3. Mathematics

You’ll learn more about the purpose of these packages throughout the tutorial.

Understanding the Job System

So what exactly is the Job System and what makes it different from just writing normal multi-threaded code?

Overall, it allows you to run processes over multiple cores safely and simply, without worrying about race conditions, deadlocks and other issues that usually arise.

Annotation of Multi-threading

The Job System allows games to use all the CPU cores in a computer. All modern CPUs have multiple cores, yet many games don’t take advantage of them. When you split large tasks into multiple smaller chunks and run them in parallel, you run them simultaneously instead of linearly. This greatly improves performance.

Unity’s Job System is a part of their larger project called the Data Oriented Technology Stack (DOTS). DOTS keeps performance in mind from the start. It contains the Job System, Burst compiler and Entity Component System (ECS). The Job System is for highly parallel code. ECS is for efficient memory management and the Burst compiler is for efficient native machine code.

Understanding the Burst Compiler

The Burst compiler works perfectly with the Job System. The mechanisms of the compiler are well beyond the scope of this tutorial, but the basic premise is that it’s able to compile C# code into much more efficient and performant native code.

Unity’s entire scripting uses Mono. Mono is an implementation of .NET that can compile C# on multiple systems such as Windows, Mac and PlayStation. Unfortunately, the cost of being able to execute code on multiple platforms is high. Managed C# will never reach the performance of code designed for a specific platform.

Their solution to this was the Burst compiler, which is a ‘math-aware’ compiler that produces highly optimized machine code depending on the platform. It’s pretty complicated technology that utilizes the LLVM Project. Luckily, all you have to do is add a line or two of code to benefit from it.

You’ve also installed the Unity Mathematics package, which is simply a C# math library that’s used by the Burst compiler for low-level optimization.

Setting up the Wave Generator

For your first step, you’ll create the waves. You’ll use shaded wire-frame mode so you can see the massive number of vertices within the mesh.

Wire-frame mesh view

Understanding Perlin Noise

To create waves on the mesh, you’re going to sample a value from Perlin noise for each vertex to set its height. Perlin noise generates smooth, continuous random heights that can move over time to generate wave-like features.

Here’s some static Perlin noise:

Perlin Noise

You can shift and scale this Perlin noise over time:

Modified Perlin Noise

Setting up the Wave Generator

Open RW/Scripts/WaveGenerator.cs and populate the file with the following namespaces to get started:

using UnityEngine.Jobs;
using Unity.Collections;
using Unity.Burst;
using Unity.Jobs;
using Unity.Mathematics;

The Unity.Collections package brings in Unity’s optimized version of System.Collections. The remaining packages came pre-installed from the Package Manager.

Note the following variables that represent the modifiers of the Perlin noise function:

[Header("Wave Parameters")]
public float waveScale; // 1
public float waveOffsetSpeed; // 2
public float waveHeight; // 3
  1. Wave Scale: Scales the Perlin noise function.
  2. Wave Offset Speed: The speed that the Perlin noise shifts over time.
  3. Wave Height: The height multiplier of the Perlin noise.

Different scene components also have their own reference variables.

Add the following variables:

NativeArray<Vector3> waterVertices;
NativeArray<Vector3> waterNormals;

waterVertices and waterNormals are responsible for transporting the vertices and normals of the water mesh to and from the jobs.

NativeArray comes from the Unity.Collections namespace. It’s a key component of sending and receiving information from jobs. A NativeArray is a child of the NativeContainer value type.