Writers.cs 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341
  1. using System;
  2. using System.Collections.Generic;
  3. using Mono.CecilX;
  4. using Mono.CecilX.Cil;
  5. // to use Mono.CecilX.Rocks here, we need to 'override references' in the
  6. // Unity.Mirror.CodeGen assembly definition file in the Editor, and add CecilX.Rocks.
  7. // otherwise we get an unknown import exception.
  8. using Mono.CecilX.Rocks;
  9. namespace Mirror.Weaver
  10. {
  11. // not static, because ILPostProcessor is multithreaded
  12. public class Writers
  13. {
  14. // Writers are only for this assembly.
  15. // can't be used from another assembly, otherwise we will get:
  16. // "System.ArgumentException: Member ... is declared in another module and needs to be imported"
  17. AssemblyDefinition assembly;
  18. WeaverTypes weaverTypes;
  19. TypeDefinition GeneratedCodeClass;
  20. Logger Log;
  21. Dictionary<TypeReference, MethodReference> writeFuncs =
  22. new Dictionary<TypeReference, MethodReference>(new TypeReferenceComparer());
  23. public Writers(AssemblyDefinition assembly, WeaverTypes weaverTypes, TypeDefinition GeneratedCodeClass, Logger Log)
  24. {
  25. this.assembly = assembly;
  26. this.weaverTypes = weaverTypes;
  27. this.GeneratedCodeClass = GeneratedCodeClass;
  28. this.Log = Log;
  29. }
  30. public void Register(TypeReference dataType, MethodReference methodReference)
  31. {
  32. if (writeFuncs.ContainsKey(dataType))
  33. {
  34. // TODO enable this again later.
  35. // Writer has some obsolete functions that were renamed.
  36. // Don't want weaver warnings for all of them.
  37. //Log.Warning($"Registering a Write method for {dataType.FullName} when one already exists", methodReference);
  38. }
  39. // we need to import type when we Initialize Writers so import here in case it is used anywhere else
  40. TypeReference imported = assembly.MainModule.ImportReference(dataType);
  41. writeFuncs[imported] = methodReference;
  42. }
  43. void RegisterWriteFunc(TypeReference typeReference, MethodDefinition newWriterFunc)
  44. {
  45. Register(typeReference, newWriterFunc);
  46. GeneratedCodeClass.Methods.Add(newWriterFunc);
  47. }
  48. // Finds existing writer for type, if non exists trys to create one
  49. public MethodReference GetWriteFunc(TypeReference variable, ref bool WeavingFailed)
  50. {
  51. if (writeFuncs.TryGetValue(variable, out MethodReference foundFunc))
  52. return foundFunc;
  53. // this try/catch will be removed in future PR and make `GetWriteFunc` throw instead
  54. try
  55. {
  56. TypeReference importedVariable = assembly.MainModule.ImportReference(variable);
  57. return GenerateWriter(importedVariable, ref WeavingFailed);
  58. }
  59. catch (GenerateWriterException e)
  60. {
  61. Log.Error(e.Message, e.MemberReference);
  62. WeavingFailed = true;
  63. return null;
  64. }
  65. }
  66. //Throws GenerateWriterException when writer could not be generated for type
  67. MethodReference GenerateWriter(TypeReference variableReference, ref bool WeavingFailed)
  68. {
  69. if (variableReference.IsByReference)
  70. {
  71. throw new GenerateWriterException($"Cannot pass {variableReference.Name} by reference", variableReference);
  72. }
  73. // Arrays are special, if we resolve them, we get the element type,
  74. // e.g. int[] resolves to int
  75. // therefore process this before checks below
  76. if (variableReference.IsArray)
  77. {
  78. if (variableReference.IsMultidimensionalArray())
  79. {
  80. throw new GenerateWriterException($"{variableReference.Name} is an unsupported type. Multidimensional arrays are not supported", variableReference);
  81. }
  82. TypeReference elementType = variableReference.GetElementType();
  83. return GenerateCollectionWriter(variableReference, elementType, nameof(NetworkWriterExtensions.WriteArray), ref WeavingFailed);
  84. }
  85. if (variableReference.Resolve()?.IsEnum ?? false)
  86. {
  87. // serialize enum as their base type
  88. return GenerateEnumWriteFunc(variableReference, ref WeavingFailed);
  89. }
  90. // check for collections
  91. if (variableReference.Is(typeof(ArraySegment<>)))
  92. {
  93. GenericInstanceType genericInstance = (GenericInstanceType)variableReference;
  94. TypeReference elementType = genericInstance.GenericArguments[0];
  95. return GenerateCollectionWriter(variableReference, elementType, nameof(NetworkWriterExtensions.WriteArraySegment), ref WeavingFailed);
  96. }
  97. if (variableReference.Is(typeof(List<>)))
  98. {
  99. GenericInstanceType genericInstance = (GenericInstanceType)variableReference;
  100. TypeReference elementType = genericInstance.GenericArguments[0];
  101. return GenerateCollectionWriter(variableReference, elementType, nameof(NetworkWriterExtensions.WriteList), ref WeavingFailed);
  102. }
  103. if (variableReference.IsDerivedFrom<NetworkBehaviour>())
  104. {
  105. return GetNetworkBehaviourWriter(variableReference);
  106. }
  107. // check for invalid types
  108. TypeDefinition variableDefinition = variableReference.Resolve();
  109. if (variableDefinition == null)
  110. {
  111. throw new GenerateWriterException($"{variableReference.Name} is not a supported type. Use a supported type or provide a custom writer", variableReference);
  112. }
  113. if (variableDefinition.IsDerivedFrom<UnityEngine.Component>())
  114. {
  115. throw new GenerateWriterException($"Cannot generate writer for component type {variableReference.Name}. Use a supported type or provide a custom writer", variableReference);
  116. }
  117. if (variableReference.Is<UnityEngine.Object>())
  118. {
  119. throw new GenerateWriterException($"Cannot generate writer for {variableReference.Name}. Use a supported type or provide a custom writer", variableReference);
  120. }
  121. if (variableReference.Is<UnityEngine.ScriptableObject>())
  122. {
  123. throw new GenerateWriterException($"Cannot generate writer for {variableReference.Name}. Use a supported type or provide a custom writer", variableReference);
  124. }
  125. if (variableDefinition.HasGenericParameters)
  126. {
  127. throw new GenerateWriterException($"Cannot generate writer for generic type {variableReference.Name}. Use a supported type or provide a custom writer", variableReference);
  128. }
  129. if (variableDefinition.IsInterface)
  130. {
  131. throw new GenerateWriterException($"Cannot generate writer for interface {variableReference.Name}. Use a supported type or provide a custom writer", variableReference);
  132. }
  133. if (variableDefinition.IsAbstract)
  134. {
  135. throw new GenerateWriterException($"Cannot generate writer for abstract class {variableReference.Name}. Use a supported type or provide a custom writer", variableReference);
  136. }
  137. // generate writer for class/struct
  138. return GenerateClassOrStructWriterFunction(variableReference, ref WeavingFailed);
  139. }
  140. MethodReference GetNetworkBehaviourWriter(TypeReference variableReference)
  141. {
  142. // all NetworkBehaviours can use the same write function
  143. if (writeFuncs.TryGetValue(weaverTypes.Import<NetworkBehaviour>(), out MethodReference func))
  144. {
  145. // register function so it is added to writer<T>
  146. // use Register instead of RegisterWriteFunc because this is not a generated function
  147. Register(variableReference, func);
  148. return func;
  149. }
  150. else
  151. {
  152. // this exception only happens if mirror is missing the WriteNetworkBehaviour method
  153. throw new MissingMethodException($"Could not find writer for NetworkBehaviour");
  154. }
  155. }
  156. MethodDefinition GenerateEnumWriteFunc(TypeReference variable, ref bool WeavingFailed)
  157. {
  158. MethodDefinition writerFunc = GenerateWriterFunc(variable);
  159. ILProcessor worker = writerFunc.Body.GetILProcessor();
  160. MethodReference underlyingWriter = GetWriteFunc(variable.Resolve().GetEnumUnderlyingType(), ref WeavingFailed);
  161. worker.Emit(OpCodes.Ldarg_0);
  162. worker.Emit(OpCodes.Ldarg_1);
  163. worker.Emit(OpCodes.Call, underlyingWriter);
  164. worker.Emit(OpCodes.Ret);
  165. return writerFunc;
  166. }
  167. MethodDefinition GenerateWriterFunc(TypeReference variable)
  168. {
  169. string functionName = $"_Write_{variable.FullName}";
  170. // create new writer for this type
  171. MethodDefinition writerFunc = new MethodDefinition(functionName,
  172. MethodAttributes.Public |
  173. MethodAttributes.Static |
  174. MethodAttributes.HideBySig,
  175. weaverTypes.Import(typeof(void)));
  176. writerFunc.Parameters.Add(new ParameterDefinition("writer", ParameterAttributes.None, weaverTypes.Import<NetworkWriter>()));
  177. writerFunc.Parameters.Add(new ParameterDefinition("value", ParameterAttributes.None, variable));
  178. writerFunc.Body.InitLocals = true;
  179. RegisterWriteFunc(variable, writerFunc);
  180. return writerFunc;
  181. }
  182. MethodDefinition GenerateClassOrStructWriterFunction(TypeReference variable, ref bool WeavingFailed)
  183. {
  184. MethodDefinition writerFunc = GenerateWriterFunc(variable);
  185. ILProcessor worker = writerFunc.Body.GetILProcessor();
  186. if (!variable.Resolve().IsValueType)
  187. WriteNullCheck(worker, ref WeavingFailed);
  188. if (!WriteAllFields(variable, worker, ref WeavingFailed))
  189. return null;
  190. worker.Emit(OpCodes.Ret);
  191. return writerFunc;
  192. }
  193. void WriteNullCheck(ILProcessor worker, ref bool WeavingFailed)
  194. {
  195. // if (value == null)
  196. // {
  197. // writer.WriteBoolean(false);
  198. // return;
  199. // }
  200. //
  201. Instruction labelNotNull = worker.Create(OpCodes.Nop);
  202. worker.Emit(OpCodes.Ldarg_1);
  203. worker.Emit(OpCodes.Brtrue, labelNotNull);
  204. worker.Emit(OpCodes.Ldarg_0);
  205. worker.Emit(OpCodes.Ldc_I4_0);
  206. worker.Emit(OpCodes.Call, GetWriteFunc(weaverTypes.Import<bool>(), ref WeavingFailed));
  207. worker.Emit(OpCodes.Ret);
  208. worker.Append(labelNotNull);
  209. // write.WriteBoolean(true);
  210. worker.Emit(OpCodes.Ldarg_0);
  211. worker.Emit(OpCodes.Ldc_I4_1);
  212. worker.Emit(OpCodes.Call, GetWriteFunc(weaverTypes.Import<bool>(), ref WeavingFailed));
  213. }
  214. // Find all fields in type and write them
  215. bool WriteAllFields(TypeReference variable, ILProcessor worker, ref bool WeavingFailed)
  216. {
  217. uint fields = 0;
  218. foreach (FieldDefinition field in variable.FindAllPublicFields())
  219. {
  220. MethodReference writeFunc = GetWriteFunc(field.FieldType, ref WeavingFailed);
  221. // need this null check till later PR when GetWriteFunc throws exception instead
  222. if (writeFunc == null) { return false; }
  223. FieldReference fieldRef = assembly.MainModule.ImportReference(field);
  224. fields++;
  225. worker.Emit(OpCodes.Ldarg_0);
  226. worker.Emit(OpCodes.Ldarg_1);
  227. worker.Emit(OpCodes.Ldfld, fieldRef);
  228. worker.Emit(OpCodes.Call, writeFunc);
  229. }
  230. return true;
  231. }
  232. MethodDefinition GenerateCollectionWriter(TypeReference variable, TypeReference elementType, string writerFunction, ref bool WeavingFailed)
  233. {
  234. MethodDefinition writerFunc = GenerateWriterFunc(variable);
  235. MethodReference elementWriteFunc = GetWriteFunc(elementType, ref WeavingFailed);
  236. MethodReference intWriterFunc = GetWriteFunc(weaverTypes.Import<int>(), ref WeavingFailed);
  237. // need this null check till later PR when GetWriteFunc throws exception instead
  238. if (elementWriteFunc == null)
  239. {
  240. Log.Error($"Cannot generate writer for {variable}. Use a supported type or provide a custom writer", variable);
  241. WeavingFailed = true;
  242. return writerFunc;
  243. }
  244. ModuleDefinition module = assembly.MainModule;
  245. TypeReference readerExtensions = module.ImportReference(typeof(NetworkWriterExtensions));
  246. MethodReference collectionWriter = Resolvers.ResolveMethod(readerExtensions, assembly, Log, writerFunction, ref WeavingFailed);
  247. GenericInstanceMethod methodRef = new GenericInstanceMethod(collectionWriter);
  248. methodRef.GenericArguments.Add(elementType);
  249. // generates
  250. // reader.WriteArray<T>(array);
  251. ILProcessor worker = writerFunc.Body.GetILProcessor();
  252. worker.Emit(OpCodes.Ldarg_0); // writer
  253. worker.Emit(OpCodes.Ldarg_1); // collection
  254. worker.Emit(OpCodes.Call, methodRef); // WriteArray
  255. worker.Emit(OpCodes.Ret);
  256. return writerFunc;
  257. }
  258. // Save a delegate for each one of the writers into Writer{T}.write
  259. internal void InitializeWriters(ILProcessor worker)
  260. {
  261. ModuleDefinition module = assembly.MainModule;
  262. TypeReference genericWriterClassRef = module.ImportReference(typeof(Writer<>));
  263. System.Reflection.FieldInfo fieldInfo = typeof(Writer<>).GetField(nameof(Writer<object>.write));
  264. FieldReference fieldRef = module.ImportReference(fieldInfo);
  265. TypeReference networkWriterRef = module.ImportReference(typeof(NetworkWriter));
  266. TypeReference actionRef = module.ImportReference(typeof(Action<,>));
  267. MethodReference actionConstructorRef = module.ImportReference(typeof(Action<,>).GetConstructors()[0]);
  268. foreach (KeyValuePair<TypeReference, MethodReference> kvp in writeFuncs)
  269. {
  270. TypeReference targetType = kvp.Key;
  271. MethodReference writeFunc = kvp.Value;
  272. // create a Action<NetworkWriter, T> delegate
  273. worker.Emit(OpCodes.Ldnull);
  274. worker.Emit(OpCodes.Ldftn, writeFunc);
  275. GenericInstanceType actionGenericInstance = actionRef.MakeGenericInstanceType(networkWriterRef, targetType);
  276. MethodReference actionRefInstance = actionConstructorRef.MakeHostInstanceGeneric(assembly.MainModule, actionGenericInstance);
  277. worker.Emit(OpCodes.Newobj, actionRefInstance);
  278. // save it in Writer<T>.write
  279. GenericInstanceType genericInstance = genericWriterClassRef.MakeGenericInstanceType(targetType);
  280. FieldReference specializedField = fieldRef.SpecializeField(assembly.MainModule, genericInstance);
  281. worker.Emit(OpCodes.Stsfld, specializedField);
  282. }
  283. }
  284. }
  285. }