Simple to use Update Manager pattern for Unity + Jobified Update for MonoBehaviour
s and pure C# classes alike.
Using these may improve your game's CPU usage when there are thousands of objects updating every frame.
More info on Update Manager vs traditional Update: https://github.com/Menyus777/Game-engine-specific-optimization-techniques-for-Unity
-
Use
UpdateManager
to call objects'ManagedUpdate
,ManagedLateUpdate
orManagedFixedUpdate
method, bypassing Unity's native <-> C# interop -
Both
MonoBehaviour
and pure C# classes are supported, just implementIUpdatable
,ILateUpdatable
and/orIFixedUpdatable
interface and register the object to be updated using itsRegisterInManager
extension method.Remember to unregister the objects with
UnregisterInManager
when necessary. -
Inherit
AManagedBehaviour
to automatically register/unregister MonoBehaviours inUpdateManager
in theirOnEnable
/OnDisable
messages. The class still needs to implement theIUpdatable
,ILateUpdatable
and/orIFixedUpdatable
interfaces for any managed update methods to be run. -
Profiler markers are used to show managed methods in the Unity profiler.
Job System:
-
Use
UpdateJobManager<MyIUpdateJobStruct>
to run jobs every frame using Unity's Job system -
Use
UpdateTransformJobManager<MyIUpdateTransformJobStruct>
to run jobs withTransformAccess
every frame using Unity's Job system, so you can change your objects' transforms from jobs -
Both
MonoBehaviour
and pure C# classes are supported, just implementIJobUpdatable<MyIUpdateJobStruct>
orITransformJobUpdatable<MyIUpdateTransformJobStruct>
interface and register the object to be updated using theRegisterInManager
extension method.Remember to unregister the objects with
UnregisterInManager
when necessary. -
Inherit
AJobBehaviour<MyIUpdateTransformJobStruct>
to automatically register/unregister MonoBehaviours for update jobs in theirOnEnable
/OnDisable
messages -
Burst compilation is enabled when you implement
IBurstUpdateJob<BurstUpdateJob<MyJobStruct>>
instead ofIUpdateJob
in your job struct type. The same is true forIBurstUpdateTransformJob<BurstUpdateTransformJob<MyJobStruct>>
vsIUpdateTransformJob
. -
Job data may be modified from within jobs and fetched anytime. This package uses double buffering to let you read values even while jobs are running and modifying data.
-
UpdateJobTime
class with information from Unity'sTime
class that you can access from within jobs (deltaTime
,time
, etc...) -
Configurable job batch size using
[JobBatchSize(...)]
attribute in job structs. This is ignored in read-write transform jobs. -
Add dependencies between managed jobs using
[DependsOn(typeof(MyJobDependency1), ...)]
.For now, no dependency cycle detection is performed, so job runners may get deadlocked if you misuse it.
-
UpdateManager
doesn't have the concept of script execution order like Unity MonoBehaviours, so don't rely on execution order. -
Read-write transform jobs are only parallelized if the objects live in hierarchies with different root objects. This is a limitation of Unity's job system.
Read-only transform jobs, marked by the
[ReadOnlyTransformAccess]
attribute, don't have this restriction. -
Although native container fields (
NativeArray
,NativeList
...) are supported in managed jobs, the thread safety system provided by Unity is not applied to them. Use them with care!
This package is available on the openupm registry and can be installed using the openupm-cli:
openupm add com.gilzoide.update-manager
Otherwise, you can install directly using the Unity Package Manager with the following URL:
https://github.com/gilzoide/unity-update-manager.git#1.5.2
Or you can clone this repository or download a snapshot of it directly inside your project's Assets
or Packages
folder.
using Gilzoide.UpdateManager;
using UnityEngine;
public class MyManagedUpdatableBehaviour : AManagedBehaviour, IUpdatable, ILateUpdatable, IFixedUpdatable
{
public void ManagedUpdate()
{
Debug.Log("Called every frame, alongside other scripts' Update message");
}
public void ManagedLateUpdate()
{
Debug.Log("Also called every frame, alongside other scripts' LateUpdate message");
}
public void ManagedFixedUpdate()
{
Debug.Log("Also called every frame, alongside other scripts' FixedUpdate message");
}
}
using Gilzoide.UpdateManager;
using UnityEngine;
public class MyUpdatable : IUpdatable, ILateUpdatable, IFixedUpdatable
{
public void ManagedUpdate()
{
Debug.Log("Called every frame, alongside other scripts' Update message");
}
public void ManagedLateUpdate()
{
Debug.Log("Also called every frame, alongside other scripts' LateUpdate message");
}
public void ManagedFixedUpdate()
{
Debug.Log("Also called every frame, alongside other scripts' FixedUpdate message");
}
// call this when you want Updates to start running
public void StartUpdating()
{
this.RegisterInManager();
// ^ alias for `UpdateManager.Instance.Register(this)`
}
// call this when necessary to stop the updates
public void StopUpdating()
{
this.UnregisterInManager();
// ^ alias for `UpdateManager.Instance.Unregister(this)`
}
}
using System.Collections;
using Gilzoide.UpdateManager.Jobs;
using UnityEngine;
using UnityEngine.Jobs;
// 1. Create the Job struct
//
// Note: Implement `IBurstUpdateTransformJob<BurstUpdateTransformJob<MoveJob>>`
// instead if you want Burst to compile the job
public struct MoveJob : IUpdateTransformJob
{
public Vector3 Direction;
public float Speed;
public bool SomethingHappened;
public void Execute(TransformAccess transform)
{
Debug.Log("This will be called every frame using Unity's Job system");
// This runs outside of the Main Thread, so
// we need to use `UpdateJobTime` instead of `Time`
float deltaTime = UpdateJobTime.deltaTime;
// You can modify the Transform in jobs!
transform.localPosition += Direction * Speed * deltaTime;
// You can modify the struct's value and fetch them later!
SomethingHappened = true;
}
}
// 2. Create the job-updated behaviour
public class MyJobifiedBehaviour : AJobBehaviour<MoveJob>
{
// set the parameters in Unity's Inspector
public Vector3 Direction;
public float Speed;
// (optional) Set the data passed to the first job run
public override MoveJob InitialJobData => new MoveJob
{
Direction = Direction,
Speed = Speed,
};
IEnumerator Start()
{
// wait a frame to see if something happened
yield return null;
// use the `JobData` property to fetch the current data
MoveJob currentData = JobData;
// should print "Something happened: true"
Debug.Log("Something happened: " + currentData.SomethingHappened);
}
}
using Gilzoide.UpdateManager.Jobs;
using UnityEngine;
// 1. Create the Job struct
//
// Note: Implement `IBurstUpdateJob<BurstUpdateJob<MoveJob>>`
// instead if you want Burst to compile the job
public struct CountJob : IUpdateJob
{
public int Count;
public void Execute()
{
Debug.Log("This will be called every frame using Unity's Job system");
Count++;
}
}
// 2. Create the job-updated class
public class MyJobifiedBehaviour : IJobUpdatable<CountJob>
{
// Set the data passed to the first job run
public CountJob InitialJobData => default;
// call this when you want Updates to start running
public void StartUpdating()
{
this.RegisterInManager();
// ^ alias for `UpdateJobManager<CountJob>.Instance.Register(this)`
}
// call this when necessary to stop the updates
public void StopUpdating()
{
this.UnregisterInManager();
// ^ alias for `UpdateJobManager<CountJob>.Instance.Unregister(this)`
}
// fetch current data using `this.GetJobData`
public int CurrentCount => this.GetJobData().Count;
}
- Test with 2000 spinning cubes running at 30 FPS in a Xiaomi Redmi 4X Android device.
- 1000 spinning game objects running in an automated performace testing running in a M1 Macbook Pro