Squishy Maps for Soft Body Modelling Using Generalised Chain Mail
KIT308/408 (Advanced) Multicore Architecture and Programming
Unity DOTS
Dr. Ian Lewis
Discipline of ICT, School of TED
University of Tasmania, Australia
1
Multiple systems
Slogan “Performance by default”
Main idea “…is to take advantage of the CPU’s cache and your GPU’s SIMD capabilities to achieve incredible performance output”
Sound familiar?
Important ones for today
ECS
Entity–Component System
Job System
Burst Compiler
Just trying to give you the flavour of this kind of thing
To see that the techniques we’ve used in this unit have “real-world” analogues
2
Unity Data-Oriented Technology Stack
The content for these slides is heavily lifted from a 3-part series on Medium
https://medium.com/@nikolay.karagyozov1/getting-started-with-unity-dots-part-1-ecs-7f963777db8e
https://medium.com/@nikolay.karagyozov1/getting-started-with-unity-dots-part-2-c-job-system-6f316aa05437
https://medium.com/@nikolay.karagyozov1/getting-started-with-unity-dots-part-3-burst-compiler-7d639274ca1e
And some from this Unity tech talk:
When needing to solve a specific problem or bypassing a technical hurdle
e.g. too many spawned objects makes the game hitch
Can be implemented incrementally
Can have only parts of the game implemented using DOTS
Pay attention to the roadmap, because this is all still a little experimental
3
When to use DOTS
Unity Entity–Component System
Unity DOTS
4
Normal Unity GameObject memory layout
This is basically AoS
5
Unity ECS Memory Layout
Unity ECS memory layout
This is basically SoA
Want to combine like concepts into single list of values
So here
Speed
Health
Rigidbody
6
How to Structure Data
Want to combine like concepts into single operation
So here
Update direction
Update position in world
7
How to Structure Functionality
Entity
Like a GameObject substitute, built around a “where there is on there is many” idea
Component
Not the usual Unity component
Models the data that will be processed
System
Systems are scripts that read component data and compute the next state
8
Unity ECS
In this example, we have 3 entities in our world: A, B, and C
The flow can be described like this
A system fetches all translation and rotation components in our world
It performs calculations in bulk
Finally, it writes the output result back to all of the fetched entities
9
Unity ECS
System calculation
T is an array of all translations
R is an array of all rotations
T * R is performed for all indexes of the “list”
10
Unity ECS
In this example
A and B have a Renderer component
C doesn’t
ECS “collects” needed entity data before doing the calculation
Can filter how ever you want
e.g. just get all entities with Renderer
11
Unity ECS
Unity groups like ECS entities into archetypes
For performance
You can add / remove components from entities at runtime
Changes their archetype
12
Unity ECS Archetypes
Archetypes allow for memory chunking
Every chunk contains multiple entities of the same archetype
It’s a bit AoSoA
13
Unity ECS Archetypes
Entities are created by an EntityManager
Which is responsible for maintaining a list of all entities and organising data for speed
CreateEntity can create an entity from
Array of components
EntityArchetype
Existing Entity
14
ECS Entities
Ran out of time to finish presentation 🙁
15
[Insert missing slides here]
Can use Convert to tell ECS to make “copies” of the data
public void Convert(Entity entity, EntityManager manager, GameObjectConversionSystem conversion)
{
manager.AddComponent(entity, typeof(whatever)
manager
manager.AddComponentData(entity,
}
16
Converting GameObjects to ECS Entities
Entities.ForEach((ref A outputComponent, in B readComponent) => { … });
17
Entity ForEach
[WriteGroup(SoldierHealth)]
public struct DoubleHealthRegenBuff : IComponentData { … }
public struct SoldierHealth : IComponentData { … }
// NormalHealthRegenSystem.cs:
…
Entities.WithEntityQueryOptions(EntityQueryOptions.FilterWriteGroup)
.ForEach((ref SoldierHealth health) => {
// compute health regen at 1x speed
}).ScheduleParallel();
18
Calling ECS Queries
Unity Job System
Unity DOTS
19
The Job System helps create multithreaded code
Via the creation of jobs, not threads
A job is small unit of work
Does something specific
Gets parameters
Operates on data
The Job System manages a pool of worker threads across many cores
Usually one worker per logical core
20
Unity Job System
All jobs are put in the job queue
Worker threads then pick jobs from the queue
Some jobs might have dependencies (on other jobs)
The Job System makes sure everything happens in the correct order
21
Unity Job System Scheduling
Managed vs Unmanaged memory
i.e. garbage collected or not
Blittable types
Same memory layout in managed and unmanaged code
Race condition safety
Jobs always work with copies of the original data
Copying data from managed to unmanaged memory
22
Unity Job System Memory
Native containers work on pointers to unmanaged memory
NativeArray, NativeList, NativeHashMap, NativeMultiHashMap, NativeQueue
When creating these you specify an allocator
Temp
Exists for 1 frame
TempJob
Exists for 4 frames
Persistent
Last forever, but slower
23
Unity Job System Native Containers
NativeArray
new NativeArray
(1, Allocator.TempJob);
To create a job:
Create a struct that implements Ijob
Add member variables
Blittable types or native containers
Implement Execute()
24
Creating Jobs
public struct AddJob : IJob
{
public float a;
public float b;
public NativeArray
public void Execute()
{
result[0] = a + b;
}
}
To schedule a job:
Instantiate the job
Populate its data
Call Schedule() from the main thread
25
Scheduling Jobs
var result = new NativeArray
Allocator.TempJob);
var job = new AddJob();
job.a = 10;
job.b = 20;
job.result = result;
JobHandle handle = jobData.Schedule();
// Wait for the job to complete
handle.Complete();
// get result
float aPlusB = result[0];
// clean up
result.Dispose();
Dependencies
ParallelFor jobs
Job Design Patterns
26
Topics We Didn’t Cover
Unity Burst Compiler
Unity DOTS
27
C# traditionally compiles to IL / .NET bytecode
Which is then interpreted (or compiled Just-In-Time)
Although Unity also uses IL2CPP to try and increase efficiency
Burst uses LLVM
So does Swift, Rust, Kotlin Native, etc.
Burst compiles jobs to improve performance
28
Burst Compiler
Burst is very good at optimising math from Unity.Mathematics
Using SIMD operations
The compiler does this, not the programmer
29
SIMD
Just need to an attribute to a job
Magic!
30
Using Burst
[BurstCompile]
struct ExampleJob : IJob { … }
Lots of support for this “magic”
Can use structs and value types
Built for ECS
Support for generics
Can throw exceptions (but only in the editor)
Speed
31
Burst Features
Magic does work for everything
Can’t use reference types
e.g. classes, strings, etc.
Can’t access GameObject / Component code
Can’t write to static variables
No support for catching exceptions (try/catch)
So got
32
Burst Limitations
Use this when you have existing code that still uses GameObjects / classes and still want to use Burst for some parts
You create a job that would execute your logic
You create a struct that holds only the data you need for the job and strips away everything else that is unnecessary
Copy the data into the new struct
Pass the struct as job parameter
Schedule the job
Burst compiles it
Save the job result it a native container
Copy the native container back to the original classes
33
Copy/Burst/Copy Design Pattern
public List
…
}
public Vector3 IntensiveCalculation(
List
for (int i=0; i < values.Count; i++) {
values[i].position = Vector3.zero;
}
}
...
// in Update()
var data = GetData();
IntensiveCalculation(data);
[BurstCompile]
struct ExampleJob : Ijob {
public NativeArray
public void Execute() {
for (int i=0; i
34
Copy/Burst/Copy Example: Part 1
public List
…
}
public Vector3 IntensiveCalculation(
List
for (int i=0; i < values.Count; i++) {
values[i].position = Vector3.zero;
}
}
...
// in Update()
var data = GetData();
IntensiveCalculation(data);
// in Update()
var data = GetData();
var jobData = new NativeArray
// Copy
for (int = 0; i < jobData.Length; i++) {
jobData[i].position = data[i].position
}
// Burst
var job = new ExampleJob {
Data = jobData
}
job.Run();
// Copy
for (int = 0; i < jobData.Length; i++) {
data[i].position = jobData[i].position
}
35
Copy/Burst/Copy Example: Part 2
Burst uses NativeArray
So need to treat 2D (or higher) arrays as 1D ones
36
Using NativeArray
var matrix = new int[height, width];
...
matrix[x, y] = ...;
Becomes:
var matrix = new NativeArray
height * width,
Allocator.Persistent
);
…
matrix[x * height + y] = …;
Floating-point calculation mode
Strict
Don’t do weird things
Fast
Do all the weird things
e.g. y/x -> 1/x * y
37
Burst Compiler Options: FloatMode
[BurstCompile(FloatMode = FloatMode.Fast]
struct ExampleJob : IJob { … }
Floating-point precision mode
Medium
3.5 ULP accuracy
High
1 ULP accuracy
ULP = unit of least precision
The difference between two consecutive floating-point numbers is equal to 1 ULP
38
Burst Compiler Options: FloatPrecision
[BurstCompile(FloatPrecision =
FloatPrecision.Medium]
struct ExampleJob : IJob { … }