| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363 | #if UNITY_EDITORusing 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
 |