using System; using System.Collections.Generic; using System.Text; using UnityEngine; using UnityEngine.LowLevel; namespace XRLib { public static partial class PlayerLoopInterface { private static List insertedSystems = new(); [RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.AfterAssembliesLoaded)] private static void Initialize() { PlayerLoopQuitChecker.GameQuitCallback += () => { foreach (var playerLoopSystem in insertedSystems) TryRemoveSystem(playerLoopSystem.type); insertedSystems.Clear(); }; Hooker.Initialize(); } private enum InsertType { Before, After } public static void InsertSystemAfter(Type newSystemMarker, PlayerLoopSystem.UpdateFunction newSystemUpdate, Type insertAfter) { var playerLoopSystem = new PlayerLoopSystem { type = newSystemMarker, updateDelegate = newSystemUpdate }; InsertSystemAfter(playerLoopSystem, insertAfter); } public static void InsertSystemBefore(Type newSystemMarker, PlayerLoopSystem.UpdateFunction newSystemUpdate, Type insertBefore) { var playerLoopSystem = new PlayerLoopSystem { type = newSystemMarker, updateDelegate = newSystemUpdate }; InsertSystemBefore(playerLoopSystem, insertBefore); } public static void InsertSystemAfter(PlayerLoopSystem toInsert, Type insertAfter) { if (toInsert.type == null) throw new ArgumentException("The inserted player loop system must have a marker type!", nameof(toInsert.type)); if (toInsert.updateDelegate == null) throw new ArgumentException("The inserted player loop system must have an update delegate!", nameof(toInsert.updateDelegate)); if (insertAfter == null) throw new ArgumentNullException(nameof(insertAfter)); var rootSystem = PlayerLoop.GetCurrentPlayerLoop(); InsertSystem(ref rootSystem, toInsert, insertAfter, InsertType.After, out var couldInsert); if (!couldInsert) { throw new ArgumentException($"When trying to insert the type {toInsert.type.Name} into the player loop after {insertAfter.Name}, " + $"{insertAfter.Name} could not be found in the current player loop!"); } insertedSystems.Add(toInsert); PlayerLoop.SetPlayerLoop(rootSystem); } public static void InsertSystemBefore(PlayerLoopSystem toInsert, Type insertBefore) { if (toInsert.type == null) throw new ArgumentException("The inserted player loop system must have a marker type!", nameof(toInsert.type)); if (toInsert.updateDelegate == null) throw new ArgumentException("The inserted player loop system must have an update delegate!", nameof(toInsert.updateDelegate)); if (insertBefore == null) throw new ArgumentNullException(nameof(insertBefore)); var rootSystem = PlayerLoop.GetCurrentPlayerLoop(); InsertSystem(ref rootSystem, toInsert, insertBefore, InsertType.Before, out var couldInsert); if (!couldInsert) { throw new ArgumentException($"When trying to insert the type {toInsert.type.Name} into the player loop before {insertBefore.Name}, " + $"{insertBefore.Name} could not be found in the current player loop!"); } insertedSystems.Add(toInsert); PlayerLoop.SetPlayerLoop(rootSystem); } public static bool TryRemoveSystem(Type type) { if (type == null) throw new ArgumentNullException(nameof(type), "Trying to remove a null type!"); var currentSystem = PlayerLoop.GetCurrentPlayerLoop(); var couldRemove = TryRemoveTypeFrom(ref currentSystem, type); PlayerLoop.SetPlayerLoop(currentSystem); return couldRemove; } private static bool TryRemoveTypeFrom(ref PlayerLoopSystem currentSystem, Type type) { var subSystems = currentSystem.subSystemList; if (subSystems == null) return false; for (int i = 0; i < subSystems.Length; i++) { if (subSystems[i].type == type) { var newSubSystems = new PlayerLoopSystem[subSystems.Length - 1]; Array.Copy(subSystems, newSubSystems, i); Array.Copy(subSystems, i + 1, newSubSystems, i, subSystems.Length - i - 1); currentSystem.subSystemList = newSubSystems; return true; } if (TryRemoveTypeFrom(ref subSystems[i], type)) return true; } return false; } public static PlayerLoopSystem CopySystem(PlayerLoopSystem system) { // PlayerLoopSystem is a struct. var copy = system; // but the sub system list is an array. if (system.subSystemList != null) { copy.subSystemList = new PlayerLoopSystem[system.subSystemList.Length]; for (int i = 0; i < copy.subSystemList.Length; i++) { copy.subSystemList[i] = CopySystem(system.subSystemList[i]); } } return copy; } private static void InsertSystem(ref PlayerLoopSystem currentLoopRecursive, PlayerLoopSystem toInsert, Type insertTarget, InsertType insertType, out bool couldInsert) { var currentSubSystems = currentLoopRecursive.subSystemList; if (currentSubSystems == null) { couldInsert = false; return; } int indexOfTarget = -1; for (int i = 0; i < currentSubSystems.Length; i++) { if (currentSubSystems[i].type == insertTarget) { indexOfTarget = i; break; } } if (indexOfTarget != -1) { var newSubSystems = new PlayerLoopSystem[currentSubSystems.Length + 1]; var insertIndex = insertType == InsertType.Before ? indexOfTarget : indexOfTarget + 1; for (int i = 0; i < newSubSystems.Length; i++) { if (i < insertIndex) newSubSystems[i] = currentSubSystems[i]; else if (i == insertIndex) { newSubSystems[i] = toInsert; } else { newSubSystems[i] = currentSubSystems[i - 1]; } } couldInsert = true; currentLoopRecursive.subSystemList = newSubSystems; } else { for (var i = 0; i < currentSubSystems.Length; i++) { var subSystem = currentSubSystems[i]; InsertSystem(ref subSystem, toInsert, insertTarget, insertType, out var couldInsertInInner); if (couldInsertInInner) { currentSubSystems[i] = subSystem; couldInsert = true; return; } } couldInsert = false; } } public static string CurrentLoopToString() { return PrintSystemToString(PlayerLoop.GetCurrentPlayerLoop()); } private static string PrintSystemToString(PlayerLoopSystem s) { List<(PlayerLoopSystem, int)> systems = new(); AddRecursively(s, 0); void AddRecursively(PlayerLoopSystem system, int depth) { systems.Add((system, depth)); if (system.subSystemList != null) foreach (var subsystem in system.subSystemList) AddRecursively(subsystem, depth + 1); } StringBuilder sb = new(); sb.AppendLine("Systems"); sb.AppendLine("======="); foreach (var (system, depth) in systems) { // root system has a null type, all others has a marker type. Append($"System Type: {system.type?.Name ?? "NULL"}"); // This is a C# delegate, so it's only set for functions created on the C# side. Append($"Delegate: {system.updateDelegate}"); // This is a pointer, probably to the function getting run internally. Has long values (like 140700263204024) for the builtin ones concrete ones, // while the builtin grouping functions has 0. So UnityEngine.PlayerLoop.Update has 0, while UnityEngine.PlayerLoop.Update.ScriptRunBehaviourUpdate // has a concrete value. Append($"Update Function: {system.updateFunction}"); // The loopConditionFunction seems to be a red herring. It's set to a value for only UnityEngine.PlayerLoop.FixedUpdate, but setting a different // system to have the same loop condition function doesn't seem to do anything Append($"Loop Condition Function: {system.loopConditionFunction}"); // null rather than an empty array when it's empty. Append($"{system.subSystemList?.Length ?? 0} subsystems"); void Append(string s) { for (int i = 0; i < depth; i++) sb.Append(" "); sb.AppendLine(s); } } return sb.ToString(); } } }