OnHotReloadDispatch.cs 9.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194
  1. #if ENABLE_MONO && (DEVELOPMENT_BUILD || UNITY_EDITOR)
  2. #pragma warning disable CS0618 // obsolete warnings (stay warning-free also in newer unity versions)
  3. using System;
  4. using System.Collections.Generic;
  5. using System.Linq;
  6. using System.Reflection;
  7. using System.Threading;
  8. using System.Threading.Tasks;
  9. using UnityEngine;
  10. using Object = UnityEngine.Object;
  11. #if UNITY_EDITOR
  12. using UnityEditor;
  13. #endif
  14. namespace SingularityGroup.HotReload {
  15. static class Dispatch {
  16. // DispatchOnHotReload is called every time a patch is applied (1x per batch of filechanges)
  17. public static async Task OnHotReload(List<MethodPatch> patchedMethods) {
  18. var methods = await Task.Run(() => GetOrFillMethodsCacheThreaded());
  19. foreach (var m in methods) {
  20. if (m.IsStatic) {
  21. InvokeStaticMethod(m, nameof(InvokeOnHotReload), patchedMethods);
  22. } else {
  23. foreach (var go in GameObject.FindObjectsOfType(m.DeclaringType)) {
  24. InvokeInstanceMethod(m, go, patchedMethods);
  25. }
  26. }
  27. }
  28. }
  29. public static void OnHotReloadLocal(MethodBase originalMethod, MethodBase patchMethod) {
  30. if (!Attribute.IsDefined(originalMethod, typeof(InvokeOnHotReloadLocal))) {
  31. return;
  32. }
  33. var attrib = Attribute.GetCustomAttribute(originalMethod, typeof(InvokeOnHotReloadLocal)) as InvokeOnHotReloadLocal;
  34. if (!string.IsNullOrEmpty(attrib?.methodToInvoke)) {
  35. OnHotReloadLocalCustom(originalMethod, attrib);
  36. return;
  37. }
  38. var patchMethodParams = patchMethod.GetParameters();
  39. if (patchMethodParams.Length == 0) {
  40. InvokeStaticMethod(patchMethod, nameof(InvokeOnHotReloadLocal), null);
  41. } else if (typeof(MonoBehaviour).IsAssignableFrom(patchMethodParams[0].ParameterType)) {
  42. foreach (var go in GameObject.FindObjectsOfType(patchMethodParams[0].ParameterType)) {
  43. InvokeInstanceMethodStatic(patchMethod, go);
  44. }
  45. } else {
  46. 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.");
  47. }
  48. }
  49. public static void OnHotReloadLocalCustom(MethodBase origianlMethod, InvokeOnHotReloadLocal attrib) {
  50. var reloadForType = origianlMethod.DeclaringType;
  51. var reloadMethod = reloadForType?.GetMethod(attrib.methodToInvoke, BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic);
  52. if (reloadMethod == null) {
  53. Log.Warning($"[{nameof(InvokeOnHotReloadLocal)}] failed to find method {attrib.methodToInvoke}. Make sure it exists within the same class.");
  54. return;
  55. }
  56. if (reloadMethod.IsStatic) {
  57. InvokeStaticMethod(reloadMethod, nameof(InvokeOnHotReloadLocal), null);
  58. } else if (typeof(MonoBehaviour).IsAssignableFrom(reloadForType)) {
  59. foreach (var go in GameObject.FindObjectsOfType(reloadForType)) {
  60. InvokeInstanceMethod(reloadMethod, go, null);
  61. }
  62. } else {
  63. 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.");
  64. }
  65. }
  66. private static List<MethodInfo> methodsCache;
  67. private static List<MethodInfo> GetOrFillMethodsCacheThreaded() {
  68. if (methodsCache != null) {
  69. return methodsCache;
  70. }
  71. #if UNITY_2019_1_OR_NEWER && UNITY_EDITOR
  72. var methodCollection = UnityEditor.TypeCache.GetMethodsWithAttribute(typeof(InvokeOnHotReload));
  73. var methods = new List<MethodInfo>();
  74. foreach (var m in methodCollection) {
  75. methods.Add(m);
  76. }
  77. #else
  78. var methods = GetMethodsReflection();
  79. #endif
  80. methodsCache = methods;
  81. return methods;
  82. }
  83. private static List<MethodInfo> GetMethodsReflection() {
  84. var methods = new List<MethodInfo>();
  85. try {
  86. foreach (var asm in AppDomain.CurrentDomain.GetAssemblies()) {
  87. if (asm.FullName == "System" || asm.FullName.StartsWith("System.", StringComparison.Ordinal)) {
  88. continue; // big performance optimization
  89. }
  90. try {
  91. foreach (var type in asm.GetTypes()) {
  92. try {
  93. foreach (var m in type.GetMethods(BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic)) {
  94. try {
  95. if (Attribute.IsDefined(m, typeof(InvokeOnHotReload))) {
  96. methods.Add(m);
  97. }
  98. } catch (BadImageFormatException) {
  99. // silently ignore (can happen, is very annoying if it spams)
  100. /*
  101. BadImageFormatException: VAR 3 (TOutput) cannot be expanded in this context with 3 instantiations
  102. System.Reflection.MonoMethod.GetBaseMethod () (at <c8d0d7b9135640958bff528a1e374758>:0)
  103. System.MonoCustomAttrs.GetBase (System.Reflection.ICustomAttributeProvider obj) (at <c8d0d7b9135640958bff528a1e374758>:0)
  104. System.MonoCustomAttrs.IsDefined (System.Reflection.ICustomAttributeProvider obj, System.Type attributeType, System.Boolean inherit) (at <c8d0d7b9135640958bff528a1e374758>:0)
  105. */
  106. } catch (TypeLoadException) {
  107. // silently ignore (can happen, is very annoying if it spams)
  108. } catch (Exception e) {
  109. ThreadUtility.LogException(new AggregateException(type.Name + "." + m.Name, e));
  110. }
  111. }
  112. } catch (BadImageFormatException) {
  113. // silently ignore (can happen, is very annoying if it spams)
  114. } catch (TypeLoadException) {
  115. // silently ignore (can happen, is very annoying if it spams)
  116. } catch (Exception e) {
  117. ThreadUtility.LogException(new AggregateException(type.Name, e));
  118. }
  119. }
  120. } catch (BadImageFormatException) {
  121. // silently ignore (can happen, is very annoying if it spams)
  122. } catch (TypeLoadException) {
  123. // silently ignore (can happen, is very annoying if it spams)
  124. } catch (Exception e) {
  125. ThreadUtility.LogException(new AggregateException(asm.FullName, e));
  126. }
  127. }
  128. } catch (Exception e) {
  129. ThreadUtility.LogException(e);
  130. }
  131. return methods;
  132. }
  133. private static void InvokeStaticMethod(MethodBase m, string attrName, List<MethodPatch> patchedMethods) {
  134. try {
  135. if (patchedMethods != null && m.GetParameters().Length == 1) {
  136. m.Invoke(null, new object[] { patchedMethods });
  137. } else {
  138. m.Invoke(null, new object[] { });
  139. }
  140. } catch (Exception e) {
  141. if (m.GetParameters().Length != 0) {
  142. Log.Warning($"[{attrName}] {m.DeclaringType?.Name} {m.Name} failed. Make sure it has 0 parameters, or 1 parameter with type List<MethodPatch>. Exception:\n{e}");
  143. } else {
  144. Log.Warning($"[{attrName}] {m.DeclaringType?.Name} {m.Name} failed. Exception\n{e}");
  145. }
  146. }
  147. }
  148. private static void InvokeInstanceMethod(MethodBase m, Object go, List<MethodPatch> patchedMethods) {
  149. try {
  150. if (patchedMethods != null && m.GetParameters().Length == 1) {
  151. m.Invoke(go, new object[] { patchedMethods });
  152. } else {
  153. m.Invoke(go, new object[] { });
  154. }
  155. } catch (Exception e) {
  156. if (m.GetParameters().Length != 0) {
  157. Log.Warning($"[InvokeOnHotReload] {m.DeclaringType?.Name} {m.Name} failed. Make sure it has 0 parameters, or 1 parameter with type List<MethodPatch>. Exception:\n{e}");
  158. } else {
  159. Log.Warning($"[InvokeOnHotReload] {m.DeclaringType?.Name} {m.Name} failed. Exception:\n{e}");
  160. }
  161. }
  162. }
  163. private static void InvokeInstanceMethodStatic(MethodBase m, Object go) {
  164. try {
  165. m.Invoke(null, new object[] { go });
  166. } catch (Exception e) {
  167. if (m.GetParameters().Length != 0) {
  168. Log.Warning($"[InvokeOnHotReloadLocal] {m.DeclaringType?.Name} {m.Name} failed. Make sure it has 0 parameters. Exception:\n{e}");
  169. } else {
  170. Log.Warning($"[InvokeOnHotReloadLocal] {m.DeclaringType?.Name} {m.Name} failed. Exception:\n{e}");
  171. }
  172. }
  173. }
  174. }
  175. }
  176. #endif