123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363 |
- #if UNITY_EDITOR
- using System;
- using System.Linq.Expressions;
- using System.Reflection;
- using UnityEditor;
- using UnityEditor.Callbacks;
- using UnityEngine;
- namespace Kamgam.SkyClouds.URP
- {
- /// <summary>
- /// Allows you to register a callback before compilation
- /// which is then executed automatically after compilation.<br />
- /// <br />
- /// Methods registered are usually executed in FIFO order. Though there
- /// is no guarantee that this will always be the case.
- /// <example>
- /// CrossCompileCallbacks.RegisterCallback(testCallbackA); // It can find the type automatically.
- /// CrossCompileCallbacks.RegisterCallback(typeof(YourClass), "testCallbackA");
- /// </example>
- /// </summary>
- public static class CrossCompileCallbacks
- {
- /// <summary>
- /// If set to true then the callbacks will not be called immediately but
- /// within the next editor update cycle.<br />
- /// Use this to avoid "Calling ... from assembly reloading callbacks are not supported." errors.
- /// </summary>
- public static bool DelayExecutionAfterCompilation
- {
- get => SessionState.GetBool(typeName() + ".DelayExecution", false);
- set => SessionState.SetBool(typeName() + ".DelayExecution", value);
- }
- static string typeName() => typeof(CrossCompileCallbacks).FullName;
- const string _maxIndexKey = ".MaxIndex";
- static string maxIndexKey() => typeName() + _maxIndexKey;
- const string _lastReleasedIndexKey = ".LastReleasedIndex";
- static string lastReleasedIndexKey() => typeName() + _lastReleasedIndexKey;
- const string _indexTypeKey = ".Index[{0}].Type";
- static string indexTypeKey(int index) => string.Format(typeName() + _indexTypeKey, index);
- const string _indexMethodKey = ".Index[{0}].Method";
- static string indexMethodKey(int index) => string.Format(typeName() + _indexMethodKey, index);
- static int getMaxIndex()
- {
- return SessionState.GetInt(maxIndexKey(), -1);
- }
- static int getNextIndex()
- {
- int maxIndex;
- // Try to reuse an old index (update max index if necessary)
- int reusableIndex = SessionState.GetInt(lastReleasedIndexKey(), -1);
- if (reusableIndex >= 0)
- {
- SessionState.SetInt(lastReleasedIndexKey(), -1);
- maxIndex = getMaxIndex();
- if(maxIndex < reusableIndex)
- SessionState.SetInt(maxIndexKey(), reusableIndex);
- return reusableIndex;
- }
- // New index needed (increase max index).
- maxIndex = SessionState.GetInt(maxIndexKey(), -1);
- maxIndex++;
- SessionState.SetInt(maxIndexKey(), maxIndex);
- return maxIndex;
- }
- public static void ReleaseIndex(int index)
- {
- if (index < 0)
- return;
- SessionState.SetInt(lastReleasedIndexKey(), index);
- SessionState.EraseString(indexTypeKey(index));
- SessionState.EraseString(indexMethodKey(index));
- // Decrease or erase max index if needed.
- int maxIndex = getMaxIndex();
- if(index == maxIndex)
- {
- maxIndex--;
- if(maxIndex < 0)
- SessionState.EraseInt(maxIndexKey());
- else
- SessionState.SetInt(maxIndexKey(), maxIndex);
- }
- }
- public static void ReleaseAllOnType(Type type)
- {
- if (type == null)
- return;
- int maxIndex = getMaxIndex();
- for (int i = maxIndex; i >= 0; i--)
- {
- string typeName;
- GetCallbackInfo(i, out typeName, out _);
- if(typeName == type.FullName)
- {
- ReleaseIndex(i);
- }
- }
- }
- /// <summary>
- /// Registers a callback and returns an index >= 0 on success and -1 on failure.
- /// </summary>
- /// <param name="callback">A static method without any parameters.</param>
- /// <returns></returns>
- public static int RegisterCallback(System.Action callback)
- {
- if (callback == null)
- return -1;
- var methodInfo = callback.GetMethodInfo();
- if (methodInfo == null)
- return -1;
- if (!methodInfo.IsStatic)
- {
- Debug.Log("Method needs to be static.");
- return -1;
- }
- return RegisterCallback(methodInfo.DeclaringType, methodInfo.Name);
- }
- /// <summary>
- /// Registers a callback and returns an index >= 0 on success and -1 on failure.
- /// </summary>
- /// <param name="type"></param>
- /// <param name="staticMethodName">A static method without any parameters.</param>
- /// <returns></returns>
- public static int RegisterCallback(Type type, string staticMethodName)
- {
- if (type == null || string.IsNullOrEmpty(staticMethodName))
- {
- Debug.Assert(type != null);
- Debug.Assert(staticMethodName != null);
- return -1;
- }
- // Check if methods has any parameters (that's not supported)
- try
- {
- var flags = BindingFlags.Static | BindingFlags.NonPublic | BindingFlags.Public;
- var methodInfo = type.GetMethod(staticMethodName, flags);
- if (methodInfo == null)
- {
- Debug.LogError("No static method '" + staticMethodName + "' found in '" + type.FullName + "'.");
- return -1;
- }
- if (methodInfo.GetParameters().Length > 0)
- {
- Debug.Assert(methodInfo.GetParameters().Length == 0);
- return -1;
- }
- }
- catch (System.Exception e)
- {
- Debug.LogError($"CrossCompileCallbacks: Error while checking '{staticMethodName}' method parameters. Error:\n" + e.Message);
- }
- int index = getNextIndex();
- SessionState.SetString(indexTypeKey(index), type.FullName);
- SessionState.SetString(indexMethodKey(index), staticMethodName);
- return index;
- }
- public static void GetCallbackInfo(int index, out string typeName, out string methodName)
- {
- typeName = SessionState.GetString(indexTypeKey(index), null);
- methodName = SessionState.GetString(indexMethodKey(index), null);
- }
- [DidReloadScripts(-1)]
- static void onAfterCompilation()
- {
- if (DelayExecutionAfterCompilation)
- {
- EditorApplication.delayCall -= delayedExecuteRegisteredCallbacks;
- EditorApplication.delayCall += delayedExecuteRegisteredCallbacks;
- }
- else
- {
- executeRegisteredCallbacks();
- }
- }
- static void delayedExecuteRegisteredCallbacks()
- {
- EditorApplication.delayCall -= delayedExecuteRegisteredCallbacks;
- executeRegisteredCallbacks();
- }
- static void executeRegisteredCallbacks()
- {
- int maxIndex = getMaxIndex();
- for (int i = maxIndex; i >= 0; i--)
- {
- string typeName;
- string methodName;
- GetCallbackInfo(i, out typeName, out methodName);
- try
- {
- ReleaseIndex(i);
- if (string.IsNullOrEmpty(typeName) || string.IsNullOrEmpty(methodName))
- continue;
- var methodInfo = findStaticMethod(typeName, methodName);
- methodInfo.Invoke(null, null);
- }
- catch (System.Exception e)
- {
- string errorMsg = e.Message;
- if(errorMsg.Contains("invocation") && e.InnerException != null)
- {
- errorMsg += "\n" + e.InnerException.Message;
- }
- Debug.LogError($"CrossCompileCallbacks: Calling '{typeName}.{methodName}' failed. Error:\n" + errorMsg);
- }
- }
- }
- static MethodInfo findStaticMethod(string fullTypeName, string methodName)
- {
- var type = findType(fullTypeName);
- if (type == null)
- return null;
- var flags = BindingFlags.Static | BindingFlags.NonPublic | BindingFlags.Public;
- var methodInfo = type.GetMethod(methodName, flags);
- return methodInfo;
- }
- static Type findType(string fullTypeName)
- {
- Debug.Assert(fullTypeName != null);
- var assemblies = AppDomain.CurrentDomain.GetAssemblies();
- foreach (var assembly in assemblies)
- {
- Type t = assembly.GetType(fullTypeName, throwOnError: false);
- if (t != null)
- return t;
- }
- throw new ArgumentException("Type " + fullTypeName + " doesn't exist in the current app domain.");
- }
- /// <summary>
- /// Utility method to store a static parameterless Action
- /// in the SessionState for retrieval at a later time.
- /// </summary>
- /// <param name="sessionStorageKey"></param>
- /// <param name="action"></param>
- /// <returns></returns>
- public static bool StoreAction(string sessionStorageKey, System.Action action)
- {
- if (action == null)
- return false;
- var methodInfo = action.GetMethodInfo();
- if (methodInfo == null)
- return false;
- if (!methodInfo.IsStatic)
- {
- Debug.Log("Method '"+ methodInfo.Name + "'needs to be static.");
- return false;
- }
- SessionState.SetString(sessionStorageKey + ".Type", methodInfo.DeclaringType.FullName);
- SessionState.SetString(sessionStorageKey + ".Method", methodInfo.Name);
- return true;
- }
- /// <summary>
- /// Retrieves the Action from the SessionState.
- /// </summary>
- /// <param name="sessionStorageKey"></param>
- /// <returns></returns>
- public static System.Action GetStoredAction(string sessionStorageKey)
- {
- var typeName = SessionState.GetString(sessionStorageKey + ".Type", null);
- var methodName = SessionState.GetString(sessionStorageKey + ".Method", null);
- if (string.IsNullOrEmpty(typeName) || string.IsNullOrEmpty(methodName))
- {
- return null;
- }
- var type = findType(typeName);
- if (type == null)
- return null;
- var methodInfo = findStaticMethod(typeName, methodName);
- if (methodInfo == null)
- return null;
- return (Action) Delegate.CreateDelegate(typeof(Action), methodInfo);
- }
- public static void ClearStoredAction(string sessionStorageKey)
- {
- SessionState.EraseString(sessionStorageKey + ".Type");
- SessionState.EraseString(sessionStorageKey + ".Method");
- }
- // Testing
- /*
- [DidReloadScripts]
- static void StartTest()
- {
- Debug.Log("CrossCompileCallbacks: Starting test.");
- RegisterCallback(testCallbackA);
- RegisterCallback(typeof(CrossCompileCallbacks), "testCallbackB");
- var action = GetStoredAction("storedActionA");
- ClearStoredAction("storedActionA");
- if (action != null)
- action.Invoke();
- StoreAction("storedActionA", storedActionA);
- }
- static void testCallbackA()
- {
- Debug.Log("Test callback A executed.");
- }
- static void testCallbackB()
- {
- Debug.Log("Test callback B executed.");
- }
- static void storedActionA()
- {
- Debug.Log("Stored action A executed.");
- }
- //*/
- }
- }
- #endif
|