Writers.cs 15 KB

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