Last active
May 19, 2020 19:53
-
-
Save forestrf/acc99091fcb7bda2a91e98a5ccb6be1a to your computer and use it in GitHub Desktop.
Hook into before and after common Unity update functions
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System; | |
using System.Collections.Generic; | |
using System.Runtime.CompilerServices; | |
using UnityEngine; | |
using UnityEngine.LowLevel; | |
using UnityEngine.Profiling; | |
// http://web.archive.org/web/20180315122537/http://beardphantom.com/ghost-stories/unity-2018-and-playerloop/ | |
namespace Ashkatchap.ExtraUpdaters { | |
/// <summary> | |
/// Hook into before and after common update functions. | |
/// | |
/// Example: | |
/// AshUpdater<FixedUpdate.ScriptRunBehaviourFixedUpdate>.Add(-10, someCachedAction, "Some Description"); | |
/// </summary> | |
/// /// <typeparam name="T">struct from <see cref="UnityEngine.PlayerLoop"/>. Example: <see cref="UnityEngine.PlayerLoop.Update.ScriptRunBehaviourUpdate"/></typeparam> | |
public static class AshUpdater<T> { | |
static AshCollections.SortedList<int, HashSet<Item>> negative = new AshCollections.SortedList<int, HashSet<Item>>(); | |
static AshCollections.SortedList<int, HashSet<Item>> positive = new AshCollections.SortedList<int, HashSet<Item>>(); | |
static AshUpdater() { | |
var currentLoop = PlayerLoop.GetCurrentPlayerLoop(); | |
currentLoop = PlayerLoopSystemInjector.Inject(currentLoop, OnPre, OnPost); // prevents Play from working! | |
PlayerLoop.SetPlayerLoop(currentLoop); | |
} | |
public static void OnPre() { On(negative, "Pre"); } | |
public static void OnPost() { On(positive, "Post"); } | |
[MethodImpl(MethodImplOptions.AggressiveInlining)] | |
static void On(AshCollections.SortedList<int, HashSet<Item>> list, string msg) { | |
#if UNITY_EDITOR | |
// https://answers.unity.com/questions/878083/detect-play-mode-about-to-be-entered.html#answer-1366776 | |
if (!UnityEditor.EditorApplication.isPlaying && UnityEditor.EditorApplication.timeSinceStartup == 0) return; | |
#endif | |
Profiler.BeginSample(msg); // This string can't be "OnPre" or "OnPost" // This crashes and doesn't let Unity Play | |
foreach (var callbacks in list) { | |
foreach (var callback in callbacks.Value) { | |
Profiler.BeginSample(callback.profilerName); | |
try { | |
callback.action(); | |
} | |
catch (Exception e) { | |
Debug.LogError(e); | |
} | |
Profiler.EndSample(); | |
} | |
} | |
Profiler.EndSample(); | |
} | |
/// <param name="index">Lower numbers execute first. <= 0 runs before the hooked function. > 0 runs after the hooked function.</param> | |
/// <param name="action">What to execute. Must be a reference so that it can be removed later.</param> | |
/// <param name="profilerName">Profiler name to use when sampling.</param> | |
public static void Add(int index, Action action, string profilerName) { | |
void Add(AshCollections.SortedList<int, HashSet<Item>> list) { | |
if (!list.TryGetValue(index, out var items)) { | |
items = ItemsPool.GetNew(); | |
list.Add(index, items); | |
} | |
items.Add(new Item(action, profilerName)); | |
} | |
if (index > 0) Add(positive); | |
else Add(negative); | |
} | |
public static void Remove(int index, Action action) { | |
void Remove(AshCollections.SortedList<int, HashSet<Item>> list) { | |
if (!list.TryGetValue(index, out var items)) Debug.LogError("Removing an updater that doesn't exist (list not found)"); | |
else if (!items.Remove(new Item(action, null))) Debug.LogError("Removing an updater that doesn't exist (Action not found)"); | |
if (items.Count == 0) { | |
ItemsPool.GiveBack(items); | |
list.Remove(index); | |
} | |
} | |
if (index > 0) Remove(positive); | |
else Remove(negative); | |
} | |
public static class PlayerLoopSystemInjector { | |
public static PlayerLoopSystem Inject(PlayerLoopSystem pls, PlayerLoopSystem.UpdateFunction pre, PlayerLoopSystem.UpdateFunction post) { | |
if (pls.subSystemList != null) { | |
for (int i = 0; i < pls.subSystemList.Length; i++) { | |
pls.subSystemList[i] = Inject(pls.subSystemList[i], pre, post); | |
} | |
} | |
if (pls.type == typeof(T)) { | |
return new PlayerLoopSystem() { | |
subSystemList = new PlayerLoopSystem[] { | |
new PlayerLoopSystem() { | |
updateDelegate = pre, | |
type = typeof(AshUpdater<T>) | |
}, | |
pls, | |
new PlayerLoopSystem() { | |
updateDelegate = post, | |
type = typeof(AshUpdater<T>) | |
}, | |
} | |
}; | |
} | |
return pls; | |
} | |
} | |
static class ItemsPool { | |
private static readonly Stack<HashSet<Item>> items = new Stack<HashSet<Item>>(32); | |
[MethodImpl(MethodImplOptions.AggressiveInlining)] | |
public static HashSet<Item> GetNew() => items.Count > 0 ? items.Pop() : new HashSet<Item>(ItemComparer.Instance); | |
[MethodImpl(MethodImplOptions.AggressiveInlining)] | |
public static void GiveBack(HashSet<Item> obj) => items.Push(obj); | |
} | |
} | |
struct Item { | |
public Action action; | |
public string profilerName; | |
public Item(Action action, string profilerName) { | |
this.action = action; | |
this.profilerName = profilerName; | |
} | |
} | |
class ItemComparer : IEqualityComparer<Item> { | |
public static readonly ItemComparer Instance = new ItemComparer(); | |
[MethodImpl(MethodImplOptions.AggressiveInlining)] | |
public bool Equals(Item x, Item y) => x.action == y.action; | |
[MethodImpl(MethodImplOptions.AggressiveInlining)] | |
public int GetHashCode(Item obj) => obj.action != null ? obj.action.GetHashCode() : 0; | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment