Last active
March 16, 2021 22:14
-
-
Save JakubNei/b32f0b8ce9b58f9d0118 to your computer and use it in GitHub Desktop.
This file contains 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
// #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