Skip to content

Instantly share code, notes, and snippets.

@popcron
Last active September 22, 2022 05:23
Show Gist options
  • Save popcron/bf295e193ed5c3b965cd3c805f3ce378 to your computer and use it in GitHub Desktop.
Save popcron/bf295e193ed5c3b965cd3c805f3ce378 to your computer and use it in GitHub Desktop.
Unity player loop extensions for injecting your own update loops
using System;
using System.Collections.Generic;
using System.Text;
using UnityEngine.LowLevel;
using static UnityEngine.LowLevel.PlayerLoopSystem;
public static class PlayerLoopExtensions
{
public static Type GetParentSystemType<T>(this PlayerLoopSystem playerLoop)
{
Stack<PlayerLoopSystem> systems = new Stack<PlayerLoopSystem>();
systems.Push(playerLoop);
while (systems.Count > 0) //safe
{
PlayerLoopSystem parent = systems.Pop();
if (parent.subSystemList is not null)
{
int subSystemCount = parent.subSystemList.Length;
for (int i = 0; i < subSystemCount; i++)
{
PlayerLoopSystem subSystem = parent.subSystemList[i];
if (subSystem.type == typeof(T))
{
return parent.type;
}
systems.Push(subSystem);
}
}
}
return null;
}
public static ref PlayerLoopSystem GetSystem(this PlayerLoopSystem playerLoop, Type type)
{
int length = playerLoop.subSystemList.Length;
for (int i = 0; i < length; i++)
{
ref PlayerLoopSystem subSystemA = ref playerLoop.subSystemList[i];
if (subSystemA.type == type)
{
return ref subSystemA;
}
int? lengthA = subSystemA.subSystemList?.Length;
for (int a = 0; a < lengthA; a++)
{
ref PlayerLoopSystem subSystemB = ref subSystemA.subSystemList[a];
if (subSystemB.type == type)
{
return ref subSystemB;
}
int? lengthB = subSystemB.subSystemList?.Length;
for (int b = 0; b < lengthB; b++)
{
ref PlayerLoopSystem subSystemC = ref subSystemB.subSystemList[b];
if (subSystemC.type == type)
{
return ref subSystemC;
}
int? lengthC = subSystemC.subSystemList?.Length;
for (int c = 0; c < lengthC; c++)
{
ref PlayerLoopSystem subSystemD = ref subSystemC.subSystemList[c];
if (subSystemD.type == type)
{
return ref subSystemD;
}
}
}
}
}
throw new Exception($"Player loop system type {type} doesn't exist");
}
public static ref PlayerLoopSystem GetParentSystem<T>(this PlayerLoopSystem playerLoop)
{
Type parentType = GetParentSystemType<T>(playerLoop);
if (parentType is not null)
{
return ref GetSystem(playerLoop, parentType);
}
else
{
throw new Exception($"Player loop parent type {typeof(T)} doesn't exist");
}
}
public static bool InjectAfter<T>(this PlayerLoopSystem playerLoop, UpdateFunction method, Type systemType)
{
ref PlayerLoopSystem parentSystem = ref GetParentSystem<T>(playerLoop);
int length = parentSystem.subSystemList.Length;
//disallow duplicates
for (int i = 0; i < length; i++)
{
ref PlayerLoopSystem subSystem = ref parentSystem.subSystemList[i];
if (subSystem.type == systemType)
{
return false;
}
}
//find system T and inject after by inserting at i
for (int i = 0; i < length; i++)
{
ref PlayerLoopSystem subSystem = ref parentSystem.subSystemList[i];
if (subSystem.type == typeof(T))
{
PlayerLoopSystem customSystem = new PlayerLoopSystem();
customSystem.type = systemType;
customSystem.updateDelegate = method;
List<PlayerLoopSystem> subSystemList = new List<PlayerLoopSystem>(parentSystem.subSystemList);
subSystemList.Insert(i, customSystem);
parentSystem.subSystemList = subSystemList.ToArray();
return true;
}
}
return false;
}
public static string GetPlayerLoopNames(this PlayerLoopSystem playerLoop, string indent = " ")
{
StringBuilder builder = new StringBuilder();
if (playerLoop.subSystemList is not null)
{
//print the entire tree using a stack for recursion
Stack<(int, PlayerLoopSystem)> stack = new Stack<(int, PlayerLoopSystem)>();
stack.Push((0, playerLoop));
while (stack.Count > 0) //safe
{
(int depth, PlayerLoopSystem system) = stack.Pop();
//print the name of the system
if (system.type is not null)
{
for (int i = 0; i < depth; i++)
{
builder.Append(indent);
}
builder.Append(system.type.FullName);
builder.AppendLine();
}
else
{
depth--;
}
//add the subsystems to the stack
if (system.subSystemList is not null)
{
int length = system.subSystemList.Length;
for (int s = 0; s < length; s++)
{
PlayerLoopSystem subSystem = system.subSystemList[s];
stack.Push((depth + 1, subSystem));
}
}
}
}
return builder.ToString();
}
}
@popcron
Copy link
Author

popcron commented Aug 19, 2022

example in use:

using UnityEngine;
using UnityEngine.LowLevel;
using static UnityEngine.PlayerLoop.FixedUpdate;
using static UnityEngine.PlayerLoop.Update;

public static class Example
{
#if UNITY_EDITOR
    [UnityEditor.Callbacks.DidReloadScripts]
    private static void InitAfterDomainReload()
    {
        InjectPlayerLoop();
    }
#endif

    [RuntimeInitializeOnLoadMethod]
    private static void InitOnPlay()
    {
        InjectPlayerLoop();
    }

    private static void InjectPlayerLoop()
    {
        PlayerLoopSystem playerLoop = PlayerLoop.GetCurrentPlayerLoop();
        playerLoop.InjectAfter<ScriptRunBehaviourUpdate>(Update, typeof(CustomUpdate));
        playerLoop.InjectAfter<ScriptRunBehaviourFixedUpdate>(FixedUpdate, typeof(CustomFixedUpdate));
        PlayerLoop.SetPlayerLoop(playerLoop);

        Debug.Log(playerLoop.GetPlayerLoopNames());
    }

    private static void Update()
    {

    }

    private static void FixedUpdate()
    {

    }

    public struct CustomUpdate { }
    public struct CustomFixedUpdate { }
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment