#if ENABLE_MONO && (DEVELOPMENT_BUILD || UNITY_EDITOR) #pragma warning disable CS0618 // obsolete warnings (stay warning-free also in newer unity versions) using System; using System.Collections.Generic; using System.Linq; using System.Reflection; using System.Threading; using System.Threading.Tasks; using UnityEngine; using Object = UnityEngine.Object; #if UNITY_EDITOR using UnityEditor; #endif namespace SingularityGroup.HotReload { static class Dispatch { // DispatchOnHotReload is called every time a patch is applied (1x per batch of filechanges) public static async Task OnHotReload(List patchedMethods) { var methods = await Task.Run(() => GetOrFillMethodsCacheThreaded()); foreach (var m in methods) { if (m.IsStatic) { InvokeStaticMethod(m, nameof(InvokeOnHotReload), patchedMethods); } else { foreach (var go in GameObject.FindObjectsOfType(m.DeclaringType)) { InvokeInstanceMethod(m, go, patchedMethods); } } } } public static void OnHotReloadLocal(MethodBase originalMethod, MethodBase patchMethod) { if (!Attribute.IsDefined(originalMethod, typeof(InvokeOnHotReloadLocal))) { return; } var attrib = Attribute.GetCustomAttribute(originalMethod, typeof(InvokeOnHotReloadLocal)) as InvokeOnHotReloadLocal; if (!string.IsNullOrEmpty(attrib?.methodToInvoke)) { OnHotReloadLocalCustom(originalMethod, attrib); return; } var patchMethodParams = patchMethod.GetParameters(); if (patchMethodParams.Length == 0) { InvokeStaticMethod(patchMethod, nameof(InvokeOnHotReloadLocal), null); } else if (typeof(MonoBehaviour).IsAssignableFrom(patchMethodParams[0].ParameterType)) { foreach (var go in GameObject.FindObjectsOfType(patchMethodParams[0].ParameterType)) { InvokeInstanceMethodStatic(patchMethod, go); } } else { Log.Warning($"[{nameof(InvokeOnHotReloadLocal)}] {patchMethod.DeclaringType?.Name} {patchMethod.Name} failed. Make sure it's a method with 0 parameters either static or defined on MonoBehaviour."); } } public static void OnHotReloadLocalCustom(MethodBase origianlMethod, InvokeOnHotReloadLocal attrib) { var reloadForType = origianlMethod.DeclaringType; var reloadMethod = reloadForType?.GetMethod(attrib.methodToInvoke, BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic); if (reloadMethod == null) { Log.Warning($"[{nameof(InvokeOnHotReloadLocal)}] failed to find method {attrib.methodToInvoke}. Make sure it exists within the same class."); return; } if (reloadMethod.IsStatic) { InvokeStaticMethod(reloadMethod, nameof(InvokeOnHotReloadLocal), null); } else if (typeof(MonoBehaviour).IsAssignableFrom(reloadForType)) { foreach (var go in GameObject.FindObjectsOfType(reloadForType)) { InvokeInstanceMethod(reloadMethod, go, null); } } else { Log.Warning($"[{nameof(InvokeOnHotReloadLocal)}] {reloadMethod.DeclaringType?.Name} {reloadMethod.Name} failed. Make sure it's a method with 0 parameters either static or defined on MonoBehaviour."); } } private static List methodsCache; private static List GetOrFillMethodsCacheThreaded() { if (methodsCache != null) { return methodsCache; } #if UNITY_2019_1_OR_NEWER && UNITY_EDITOR var methodCollection = UnityEditor.TypeCache.GetMethodsWithAttribute(typeof(InvokeOnHotReload)); var methods = new List(); foreach (var m in methodCollection) { methods.Add(m); } #else var methods = GetMethodsReflection(); #endif methodsCache = methods; return methods; } private static List GetMethodsReflection() { var methods = new List(); try { foreach (var asm in AppDomain.CurrentDomain.GetAssemblies()) { if (asm.FullName == "System" || asm.FullName.StartsWith("System.", StringComparison.Ordinal)) { continue; // big performance optimization } try { foreach (var type in asm.GetTypes()) { try { foreach (var m in type.GetMethods(BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic)) { try { if (Attribute.IsDefined(m, typeof(InvokeOnHotReload))) { methods.Add(m); } } catch (BadImageFormatException) { // silently ignore (can happen, is very annoying if it spams) /* BadImageFormatException: VAR 3 (TOutput) cannot be expanded in this context with 3 instantiations System.Reflection.MonoMethod.GetBaseMethod () (at :0) System.MonoCustomAttrs.GetBase (System.Reflection.ICustomAttributeProvider obj) (at :0) System.MonoCustomAttrs.IsDefined (System.Reflection.ICustomAttributeProvider obj, System.Type attributeType, System.Boolean inherit) (at :0) */ } catch (TypeLoadException) { // silently ignore (can happen, is very annoying if it spams) } catch (Exception e) { ThreadUtility.LogException(new AggregateException(type.Name + "." + m.Name, e)); } } } catch (BadImageFormatException) { // silently ignore (can happen, is very annoying if it spams) } catch (TypeLoadException) { // silently ignore (can happen, is very annoying if it spams) } catch (Exception e) { ThreadUtility.LogException(new AggregateException(type.Name, e)); } } } catch (BadImageFormatException) { // silently ignore (can happen, is very annoying if it spams) } catch (TypeLoadException) { // silently ignore (can happen, is very annoying if it spams) } catch (Exception e) { ThreadUtility.LogException(new AggregateException(asm.FullName, e)); } } } catch (Exception e) { ThreadUtility.LogException(e); } return methods; } private static void InvokeStaticMethod(MethodBase m, string attrName, List patchedMethods) { try { if (patchedMethods != null && m.GetParameters().Length == 1) { m.Invoke(null, new object[] { patchedMethods }); } else { m.Invoke(null, new object[] { }); } } catch (Exception e) { if (m.GetParameters().Length != 0) { Log.Warning($"[{attrName}] {m.DeclaringType?.Name} {m.Name} failed. Make sure it has 0 parameters, or 1 parameter with type List. Exception:\n{e}"); } else { Log.Warning($"[{attrName}] {m.DeclaringType?.Name} {m.Name} failed. Exception\n{e}"); } } } private static void InvokeInstanceMethod(MethodBase m, Object go, List patchedMethods) { try { if (patchedMethods != null && m.GetParameters().Length == 1) { m.Invoke(go, new object[] { patchedMethods }); } else { m.Invoke(go, new object[] { }); } } catch (Exception e) { if (m.GetParameters().Length != 0) { Log.Warning($"[InvokeOnHotReload] {m.DeclaringType?.Name} {m.Name} failed. Make sure it has 0 parameters, or 1 parameter with type List. Exception:\n{e}"); } else { Log.Warning($"[InvokeOnHotReload] {m.DeclaringType?.Name} {m.Name} failed. Exception:\n{e}"); } } } private static void InvokeInstanceMethodStatic(MethodBase m, Object go) { try { m.Invoke(null, new object[] { go }); } catch (Exception e) { if (m.GetParameters().Length != 0) { Log.Warning($"[InvokeOnHotReloadLocal] {m.DeclaringType?.Name} {m.Name} failed. Make sure it has 0 parameters. Exception:\n{e}"); } else { Log.Warning($"[InvokeOnHotReloadLocal] {m.DeclaringType?.Name} {m.Name} failed. Exception:\n{e}"); } } } } } #endif