ReaderWriterProcessor.cs 7.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179
  1. // finds all readers and writers and register them
  2. using System;
  3. using System.Linq;
  4. using Mono.CecilX;
  5. using Mono.CecilX.Cil;
  6. using UnityEditor;
  7. using UnityEditor.Compilation;
  8. using UnityEngine;
  9. namespace Mirror.Weaver
  10. {
  11. public static class ReaderWriterProcessor
  12. {
  13. public static bool Process(AssemblyDefinition CurrentAssembly)
  14. {
  15. Readers.Init();
  16. Writers.Init();
  17. foreach (Assembly unityAsm in CompilationPipeline.GetAssemblies())
  18. {
  19. if (unityAsm.name == "Mirror")
  20. {
  21. using (DefaultAssemblyResolver asmResolver = new DefaultAssemblyResolver())
  22. using (AssemblyDefinition assembly = AssemblyDefinition.ReadAssembly(unityAsm.outputPath, new ReaderParameters { ReadWrite = false, ReadSymbols = false, AssemblyResolver = asmResolver }))
  23. {
  24. ProcessAssemblyClasses(CurrentAssembly, assembly);
  25. }
  26. }
  27. }
  28. return ProcessAssemblyClasses(CurrentAssembly, CurrentAssembly);
  29. }
  30. static bool ProcessAssemblyClasses(AssemblyDefinition CurrentAssembly, AssemblyDefinition assembly)
  31. {
  32. bool modified = false;
  33. foreach (TypeDefinition klass in assembly.MainModule.Types)
  34. {
  35. // extension methods only live in static classes
  36. // static classes are represented as sealed and abstract
  37. if (klass.IsAbstract && klass.IsSealed)
  38. {
  39. // if assembly has any declared writers then it is "modified"
  40. modified |= LoadDeclaredWriters(CurrentAssembly, klass);
  41. modified |= LoadDeclaredReaders(CurrentAssembly, klass);
  42. }
  43. }
  44. foreach (TypeDefinition klass in assembly.MainModule.Types)
  45. {
  46. // if assembly has any network message then it is modified
  47. modified |= LoadMessageReadWriter(CurrentAssembly.MainModule, klass);
  48. }
  49. return modified;
  50. }
  51. static bool LoadMessageReadWriter(ModuleDefinition module, TypeDefinition klass)
  52. {
  53. bool modified = false;
  54. if (!klass.IsAbstract && !klass.IsInterface && klass.ImplementsInterface<NetworkMessage>())
  55. {
  56. Readers.GetReadFunc(module.ImportReference(klass));
  57. Writers.GetWriteFunc(module.ImportReference(klass));
  58. modified = true;
  59. }
  60. foreach (TypeDefinition td in klass.NestedTypes)
  61. {
  62. modified |= LoadMessageReadWriter(module, td);
  63. }
  64. return modified;
  65. }
  66. static bool LoadDeclaredWriters(AssemblyDefinition currentAssembly, TypeDefinition klass)
  67. {
  68. // register all the writers in this class. Skip the ones with wrong signature
  69. bool modified = false;
  70. foreach (MethodDefinition method in klass.Methods)
  71. {
  72. if (method.Parameters.Count != 2)
  73. continue;
  74. if (!method.Parameters[0].ParameterType.Is<NetworkWriter>())
  75. continue;
  76. if (!method.ReturnType.Is(typeof(void)))
  77. continue;
  78. if (!method.HasCustomAttribute<System.Runtime.CompilerServices.ExtensionAttribute>())
  79. continue;
  80. if (method.HasGenericParameters)
  81. continue;
  82. TypeReference dataType = method.Parameters[1].ParameterType;
  83. Writers.Register(dataType, currentAssembly.MainModule.ImportReference(method));
  84. modified = true;
  85. }
  86. return modified;
  87. }
  88. static bool LoadDeclaredReaders(AssemblyDefinition currentAssembly, TypeDefinition klass)
  89. {
  90. // register all the reader in this class. Skip the ones with wrong signature
  91. bool modified = false;
  92. foreach (MethodDefinition method in klass.Methods)
  93. {
  94. if (method.Parameters.Count != 1)
  95. continue;
  96. if (!method.Parameters[0].ParameterType.Is<NetworkReader>())
  97. continue;
  98. if (method.ReturnType.Is(typeof(void)))
  99. continue;
  100. if (!method.HasCustomAttribute<System.Runtime.CompilerServices.ExtensionAttribute>())
  101. continue;
  102. if (method.HasGenericParameters)
  103. continue;
  104. Readers.Register(method.ReturnType, currentAssembly.MainModule.ImportReference(method));
  105. modified = true;
  106. }
  107. return modified;
  108. }
  109. static bool IsEditorAssembly(AssemblyDefinition currentAssembly)
  110. {
  111. // we want to add the [InitializeOnLoad] attribute if it's available
  112. // -> usually either 'UnityEditor' or 'UnityEditor.CoreModule'
  113. return currentAssembly.MainModule.AssemblyReferences.Any(assemblyReference =>
  114. assemblyReference.Name.StartsWith(nameof(UnityEditor))
  115. );
  116. }
  117. // adds Mirror.GeneratedNetworkCode.InitReadWriters() method that
  118. // registers all generated writers into Mirror.Writer<T> static class.
  119. // -> uses [RuntimeInitializeOnLoad] attribute so it's invoke at runtime
  120. // -> uses [InitializeOnLoad] if UnityEditor is referenced so it works
  121. // in Editor and in tests too
  122. //
  123. // use ILSpy to see the result (it's in the DLL's 'Mirror' namespace)
  124. public static void InitializeReaderAndWriters(AssemblyDefinition currentAssembly)
  125. {
  126. MethodDefinition rwInitializer = new MethodDefinition("InitReadWriters", MethodAttributes.Public |
  127. MethodAttributes.Static,
  128. WeaverTypes.Import(typeof(void)));
  129. // add [RuntimeInitializeOnLoad] in any case
  130. System.Reflection.ConstructorInfo attributeconstructor = typeof(RuntimeInitializeOnLoadMethodAttribute).GetConstructor(new[] { typeof(RuntimeInitializeLoadType) });
  131. CustomAttribute customAttributeRef = new CustomAttribute(currentAssembly.MainModule.ImportReference(attributeconstructor));
  132. customAttributeRef.ConstructorArguments.Add(new CustomAttributeArgument(WeaverTypes.Import<RuntimeInitializeLoadType>(), RuntimeInitializeLoadType.BeforeSceneLoad));
  133. rwInitializer.CustomAttributes.Add(customAttributeRef);
  134. // add [InitializeOnLoad] if UnityEditor is referenced
  135. if (IsEditorAssembly(currentAssembly))
  136. {
  137. System.Reflection.ConstructorInfo initializeOnLoadConstructor = typeof(InitializeOnLoadMethodAttribute).GetConstructor(new Type[0]);
  138. CustomAttribute initializeCustomConstructorRef = new CustomAttribute(currentAssembly.MainModule.ImportReference(initializeOnLoadConstructor));
  139. rwInitializer.CustomAttributes.Add(initializeCustomConstructorRef);
  140. }
  141. // fill function body with reader/writer initializers
  142. ILProcessor worker = rwInitializer.Body.GetILProcessor();
  143. // for debugging: add a log to see if initialized on load
  144. //worker.Emit(OpCodes.Ldstr, $"[InitReadWriters] called!");
  145. //worker.Emit(OpCodes.Call, WeaverTypes.logWarningReference);
  146. Writers.InitializeWriters(worker);
  147. Readers.InitializeReaders(worker);
  148. worker.Emit(OpCodes.Ret);
  149. Weaver.GeneratedCodeClass.Methods.Add(rwInitializer);
  150. }
  151. }
  152. }