Skip to content

Instantly share code, notes, and snippets.

@JakubNei
Last active March 16, 2021 22:14
Show Gist options
  • Save JakubNei/b32f0b8ce9b58f9d0118 to your computer and use it in GitHub Desktop.
Save JakubNei/b32f0b8ce9b58f9d0118 to your computer and use it in GitHub Desktop.
// #define USE_VEXE_FAST_REFLECTION // download it at https://github.com/vexe/Fast.Reflection increases reflection invoke performance significantly
using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Text;
using System.Reflection;
#if USE_VEXE_FAST_REFLECTION
using Vexe.Runtime.Extensions;
#endif
public class Scheduler : UnityEngine.MonoBehaviour
{
enum WhenToCall
{
OnStart,
OnLogicUpdate,
OnGraphicsUpdate,
OnLateGraphicsUpdate,
OnPhysicsUpdate,
OnGUI,
OnDestroy,
}
class MethodEntry
{
public MethodEntry(MethodInfo method, WhenToCall when)
{
this.method = method;
this.when = when;
var parameters = method.GetParameters();
hasTimeDeltaParam = parameters.Length == 1 && (parameters[0].ParameterType == typeof(float) || parameters[0].ParameterType == typeof(double));
deltaParamIsDouble = hasTimeDeltaParam && parameters[0].ParameterType == typeof(double);
isUnityMonoBehavior = typeof(UnityEngine.Behaviour).IsAssignableFrom(method.DeclaringType);
#if USE_VEXE_FAST_REFLECTION
compiledMethod = method.DelegateForCall();
#endif
}
public void InvokeIt(object instance, double deltaTime)
{
if (isUnityMonoBehavior)
{
var behavior = instance as UnityEngine.Behaviour;
if (!behavior.enabled) return;
}
#if USE_VEXE_FAST_REFLECTION
if (hasTimeDeltaParam)
{
if (deltaParamIsDouble) compiledMethod(instance, new object[] { deltaTime });
else compiledMethod(instance, new object[] { (float)deltaTime });
}
else compiledMethod(instance, null);
#else
if (hasTimeDeltaParam)
{
if (deltaParamIsDouble) method.Invoke(instance, new object[] { deltaTime });
else method.Invoke(instance, new object[] { (float)deltaTime });
}
else method.Invoke(instance, null);
#endif
}
public override string ToString()
{
return method.DeclaringType.FullName + "." + method.Name;
}
bool isUnityMonoBehavior;
public readonly MethodInfo method;
public WhenToCall when;
public bool hasTimeDeltaParam;
bool deltaParamIsDouble;
#if USE_VEXE_FAST_REFLECTION
MethodCaller<object, object> compiledMethod;
#endif
}
class TypeEntry
{
public MethodEntry[] methodEntries;
public int numberOfOnStartMethods;
public bool IsWorthAdding()
{
return methodEntries.Length > 0;
}
}
Dictionary<Type, TypeEntry> typeEntries = new Dictionary<Type, TypeEntry>();
class InstanceEntry
{
public object instance;
public TypeEntry typeEntry;
public int numberOfCompletedOnStartMethods;
public float[] canCallMethodEntryIn;
public DateTime[] lastTimes;
}
HashSet<InstanceEntry> instanceEntries = new HashSet<InstanceEntry>();
HashSet<object> instancesWeAlreadyHave = new HashSet<object>(); // to prevent duplicates
// because calling methods of InstanceEntry might add/remove InstanceEntry and thus invalidating the IEnumerable
Queue<InstanceEntry> instanceEntries_deferedAdd = new Queue<InstanceEntry>();
Queue<InstanceEntry> instanceEntries_deferedRemove = new Queue<InstanceEntry>();
public void Add(object instance)
{
if (instance == null) return;
if (instancesWeAlreadyHave.Contains(instance)) return;
var type = instance.GetType();
var typeEntry = GetTypeEntryFor(type);
if (typeEntry.IsWorthAdding())
{
var instanceEntry = new InstanceEntry()
{
instance = instance,
typeEntry = typeEntry,
lastTimes = System.Linq.Enumerable.Repeat(DateTime.Now, typeEntry.methodEntries.Length).ToArray(),
canCallMethodEntryIn = System.Linq.Enumerable.Repeat(0.0f, typeEntry.methodEntries.Length).ToArray(),
};
lock (instanceEntries_deferedAdd) instanceEntries_deferedAdd.Enqueue(instanceEntry);
}
}
public void Remove(object instance)
{
if (instance == null) return;
if (instancesWeAlreadyHave.Contains(instance))
{
var instanceEntry = instanceEntries.First(i => i.instance == instance);
lock (instanceEntries_deferedRemove) instanceEntries_deferedRemove.Enqueue(instanceEntry);
}
}
TypeEntry GetTypeEntryFor(Type type)
{
TypeEntry typeEntry;
if (!typeEntries.TryGetValue(type, out typeEntry))
{
var methods = type.GetMethods(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.FlattenHierarchy);
var methodEntries = new List<MethodEntry>();
var isUnityBehavior = typeof(UnityEngine.MonoBehaviour).IsAssignableFrom(type);
// get more method entries from method names
foreach (var method in methods)
{
if (method.IsStatic || method.IsGenericMethod) continue;
var p = method.GetParameters();
if (p.Length > 1) continue;
var hasTimeDeltaParam = p.Length == 1 && (p[0].ParameterType == typeof(float) || p[0].ParameterType == typeof(double));
if (p.Length == 1 && !hasTimeDeltaParam) continue;
WhenToCall whenToCall;
if (method.Name == "OnStart" || method.Name.StartsWith("OnStart_"))
{
whenToCall = WhenToCall.OnStart;
}
else if (method.Name == "OnLogic" || method.Name.StartsWith("OnLogic_"))
{
whenToCall = WhenToCall.OnLogicUpdate;
}
else if (method.Name == "OnGraphics" || method.Name.StartsWith("OnGraphics_"))
{
whenToCall = WhenToCall.OnGraphicsUpdate;
}
else if (method.Name == "OnLateGraphics" || method.Name.StartsWith("OnLateGraphics_"))
{
whenToCall = WhenToCall.OnLateGraphicsUpdate;
}
else if (method.Name == "OnPhysics" || method.Name.StartsWith("OnPhysics_"))
{
whenToCall = WhenToCall.OnPhysicsUpdate;
}
else if ((method.Name == "OnGUI" || method.Name.StartsWith("OnGUI_")) && !isUnityBehavior)
{
whenToCall = WhenToCall.OnGUI;
}
else
{
continue;
}
var methodEntry = new MethodEntry(method, whenToCall);
methodEntries.Add(methodEntry);
//Debug.Log(type + " => " + whenToCall + " => " + method.Name);
}
// finally save all into type entry
typeEntry = new TypeEntry()
{
methodEntries = methodEntries.ToArray(),
numberOfOnStartMethods = methodEntries.Where(e => e.when == WhenToCall.OnStart).Count(),
};
typeEntries[type] = typeEntry;
}
return typeEntry;
}
void CallOnOneInstance(InstanceEntry instanceEntry, WhenToCall when)
{
if (instanceEntry == null || instanceEntry.instance == null) return;
double deltaTime;
deltaTime = UnityEngine.Time.deltaTime;
var now = DateTime.Now;
var len = instanceEntry.typeEntry.methodEntries.Length;
for (int i = 0; i < len; i++)
{
var entry = instanceEntry.typeEntry.methodEntries[i];
if (entry.when == when)
{
try
{
if (entry.hasTimeDeltaParam)
{
deltaTime = (now - instanceEntry.lastTimes[i]).TotalSeconds;
instanceEntry.lastTimes[i] = now;
instanceEntry.typeEntry.methodEntries[i].InvokeIt(instanceEntry.instance, deltaTime);
}
else
{
instanceEntry.typeEntry.methodEntries[i].InvokeIt(instanceEntry.instance, 0);
}
}
catch (Exception e)
{
//Debug.LogError("exception in:" + instanceEntry.typeEntry.methodEntries[i] + " called when: " + when + " " + e.InnerException.Message);
if (e.InnerException != null) Debug.LogException(e.InnerException);
else Debug.LogException(e);
}
}
}
}
void CallOnAllInstances(WhenToCall when)
{
UnityEngine.Profiler.BeginSample("updating all " + when);
foreach (var instance in instanceEntries)
{
CallOnOneInstance(instance, when);
}
UnityEngine.Profiler.EndSample();
}
void DeferredActions()
{
while (instanceEntries_deferedAdd.Count > 0)
{
var instanceEntry = instanceEntries_deferedAdd.Dequeue();
if (instancesWeAlreadyHave.Contains(instanceEntry.instance) == false)
{
CallOnOneInstance(instanceEntry, WhenToCall.OnStart);
instancesWeAlreadyHave.Add(instanceEntry.instance);
instanceEntries.Add(instanceEntry);
}
}
while (instanceEntries_deferedRemove.Count > 0)
{
var instanceEntry = instanceEntries_deferedRemove.Dequeue();
if (instancesWeAlreadyHave.Contains(instanceEntry.instance))
{
CallOnOneInstance(instanceEntry, WhenToCall.OnDestroy);
instancesWeAlreadyHave.Remove(instanceEntry.instance);
instanceEntries.Remove(instanceEntry);
}
}
}
void Update()
{
instanceEntries.RemoveWhere(e => e == null || e.instance == null);
instancesWeAlreadyHave.RemoveWhere(e => e == null);
DeferredActions();
CallOnAllInstances(WhenToCall.OnGraphicsUpdate);
}
void LateUpdate()
{
CallOnAllInstances(WhenToCall.OnLateGraphicsUpdate);
CallOnAllInstances(WhenToCall.OnLogicUpdate);
}
void FixedUpdate()
{
CallOnAllInstances(WhenToCall.OnPhysicsUpdate);
}
void OnGUI()
{
CallOnAllInstances(WhenToCall.OnGUI);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment