Unity Job System and Burst Compiler: Getting Started
- Getting Started
- Installing Required Packages
- Understanding the Job System
- Understanding the Burst Compiler
- Setting up the Wave Generator
- Understanding Perlin Noise
- Setting up the Wave Generator
- Understanding the Native Container
- Restrictions of the Native Container
- Initializing the Wave Generator
- Implementing Job System Into Wave Generator
- Setting up the Job
- Writing the Functionality of the Job
- Scheduling the Job
- Completing the Job
- Implementing the Burst Compiler
- Creating Swimming Fish in the Water
- Spawning the Fish
- Creating the Movement Job
- Scheduling the Movement Job
- Inspecting the Profiler
- Where to Go From Here?
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.
You’ll need a copy of Unity 2019.3 (or newer) installed on your machine to follow this tutorial.
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:
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.
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:
- The code loops through 10,000 vertices of the water mesh, applying a mathematical function to change its height.
- Each of the 1,000 to 2,000 fish gets a random destination and velocity to swim inside the water.
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.
In the Package Manager, select Advanced ▸ Show preview packages and install the following:
- Job System
- Burst Compiler
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.
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.
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:
You can shift and scale this Perlin noise over time:
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;
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
- Wave Scale: Scales the Perlin noise function.
- Wave Offset Speed: The speed that the Perlin noise shifts over time.
- 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;
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.