gilzoide / unity-update-manager

Simple to use Update Manager pattern for Unity + Jobified Update for MonoBehaviours and pure C# classes alike
The Unlicense
93 stars 10 forks source link
job-system jobs unity unity3d update update-manager upm upm-package

Update Manager

openupm

Simple to use Update Manager pattern for Unity + Jobified Update for MonoBehaviours 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

Features

Job System:

Caveats

How to install

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.

How to use

UpdateManager + MonoBehaviour

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");
    }
}

UpdateManager with pure C# class

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)`
    }
}

UpdateTransformJobManager + MonoBehaviour

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);
    }
}

UpdateJobManager + pure C# class

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;
}

Benchmarks

  1. Test with 2000 spinning cubes running at 30 FPS in a Xiaomi Redmi 4X Android device.
    • Plain Update: 12\~13ms updating, 8\~10ms spare in frame
    • Update Manager: 7\~8ms updating, 12\~15ms spare in frame
    • Fully parallelized transform job: \~2ms updating, 18\~20ms spare in frame
  2. 1000 spinning game objects running in an automated performace testing running in a M1 Macbook Pro
    • Plain Update: \~1.01ms updating
    • Update Manager: \~0.91ms updating
    • Fully parallelized transform job: \~0.60ms updating