Skip to content

Instantly share code, notes, and snippets.

@forestrf
Last active May 19, 2020 19:53
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save forestrf/acc99091fcb7bda2a91e98a5ccb6be1a to your computer and use it in GitHub Desktop.
Save forestrf/acc99091fcb7bda2a91e98a5ccb6be1a to your computer and use it in GitHub Desktop.
Hook into before and after common Unity update functions
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