SyncVarProcessor.cs 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472
  1. using System.Collections.Generic;
  2. using System.Linq;
  3. using Mono.CecilX;
  4. using Mono.CecilX.Cil;
  5. namespace Mirror.Weaver
  6. {
  7. /// <summary>
  8. /// Processes [SyncVar] in NetworkBehaviour
  9. /// </summary>
  10. public static class SyncVarProcessor
  11. {
  12. // ulong = 64 bytes
  13. const int SyncVarLimit = 64;
  14. static string HookParameterMessage(string hookName, TypeReference ValueType)
  15. => string.Format("void {0}({1} oldValue, {1} newValue)", hookName, ValueType);
  16. // Get hook method if any
  17. public static MethodDefinition GetHookMethod(TypeDefinition td, FieldDefinition syncVar)
  18. {
  19. CustomAttribute syncVarAttr = syncVar.GetCustomAttribute<SyncVarAttribute>();
  20. if (syncVarAttr == null)
  21. return null;
  22. string hookFunctionName = syncVarAttr.GetField<string>("hook", null);
  23. if (hookFunctionName == null)
  24. return null;
  25. return FindHookMethod(td, syncVar, hookFunctionName);
  26. }
  27. static MethodDefinition FindHookMethod(TypeDefinition td, FieldDefinition syncVar, string hookFunctionName)
  28. {
  29. List<MethodDefinition> methods = td.GetMethods(hookFunctionName);
  30. List<MethodDefinition> methodsWith2Param = new List<MethodDefinition>(methods.Where(m => m.Parameters.Count == 2));
  31. if (methodsWith2Param.Count == 0)
  32. {
  33. Weaver.Error($"Could not find hook for '{syncVar.Name}', hook name '{hookFunctionName}'. " +
  34. $"Method signature should be {HookParameterMessage(hookFunctionName, syncVar.FieldType)}",
  35. syncVar);
  36. return null;
  37. }
  38. foreach (MethodDefinition method in methodsWith2Param)
  39. {
  40. if (MatchesParameters(syncVar, method))
  41. {
  42. return method;
  43. }
  44. }
  45. Weaver.Error($"Wrong type for Parameter in hook for '{syncVar.Name}', hook name '{hookFunctionName}'. " +
  46. $"Method signature should be {HookParameterMessage(hookFunctionName, syncVar.FieldType)}",
  47. syncVar);
  48. return null;
  49. }
  50. static bool MatchesParameters(FieldDefinition syncVar, MethodDefinition method)
  51. {
  52. // matches void onValueChange(T oldValue, T newValue)
  53. return method.Parameters[0].ParameterType.FullName == syncVar.FieldType.FullName &&
  54. method.Parameters[1].ParameterType.FullName == syncVar.FieldType.FullName;
  55. }
  56. public static MethodDefinition GenerateSyncVarGetter(FieldDefinition fd, string originalName, FieldDefinition netFieldId)
  57. {
  58. //Create the get method
  59. MethodDefinition get = new MethodDefinition(
  60. "get_Network" + originalName, MethodAttributes.Public |
  61. MethodAttributes.SpecialName |
  62. MethodAttributes.HideBySig,
  63. fd.FieldType);
  64. ILProcessor worker = get.Body.GetILProcessor();
  65. // [SyncVar] GameObject?
  66. if (fd.FieldType.Is<UnityEngine.GameObject>())
  67. {
  68. // return this.GetSyncVarGameObject(ref field, uint netId);
  69. // this.
  70. worker.Emit(OpCodes.Ldarg_0);
  71. worker.Emit(OpCodes.Ldarg_0);
  72. worker.Emit(OpCodes.Ldfld, netFieldId);
  73. worker.Emit(OpCodes.Ldarg_0);
  74. worker.Emit(OpCodes.Ldflda, fd);
  75. worker.Emit(OpCodes.Call, WeaverTypes.getSyncVarGameObjectReference);
  76. worker.Emit(OpCodes.Ret);
  77. }
  78. // [SyncVar] NetworkIdentity?
  79. else if (fd.FieldType.Is<NetworkIdentity>())
  80. {
  81. // return this.GetSyncVarNetworkIdentity(ref field, uint netId);
  82. // this.
  83. worker.Emit(OpCodes.Ldarg_0);
  84. worker.Emit(OpCodes.Ldarg_0);
  85. worker.Emit(OpCodes.Ldfld, netFieldId);
  86. worker.Emit(OpCodes.Ldarg_0);
  87. worker.Emit(OpCodes.Ldflda, fd);
  88. worker.Emit(OpCodes.Call, WeaverTypes.getSyncVarNetworkIdentityReference);
  89. worker.Emit(OpCodes.Ret);
  90. }
  91. else if (fd.FieldType.IsDerivedFrom<NetworkBehaviour>())
  92. {
  93. // return this.GetSyncVarNetworkBehaviour<T>(ref field, uint netId);
  94. // this.
  95. worker.Emit(OpCodes.Ldarg_0);
  96. worker.Emit(OpCodes.Ldarg_0);
  97. worker.Emit(OpCodes.Ldfld, netFieldId);
  98. worker.Emit(OpCodes.Ldarg_0);
  99. worker.Emit(OpCodes.Ldflda, fd);
  100. MethodReference getFunc = WeaverTypes.getSyncVarNetworkBehaviourReference.MakeGeneric(fd.FieldType);
  101. worker.Emit(OpCodes.Call, getFunc);
  102. worker.Emit(OpCodes.Ret);
  103. }
  104. // [SyncVar] int, string, etc.
  105. else
  106. {
  107. worker.Emit(OpCodes.Ldarg_0);
  108. worker.Emit(OpCodes.Ldfld, fd);
  109. worker.Emit(OpCodes.Ret);
  110. }
  111. get.Body.Variables.Add(new VariableDefinition(fd.FieldType));
  112. get.Body.InitLocals = true;
  113. get.SemanticsAttributes = MethodSemanticsAttributes.Getter;
  114. return get;
  115. }
  116. public static MethodDefinition GenerateSyncVarSetter(TypeDefinition td, FieldDefinition fd, string originalName, long dirtyBit, FieldDefinition netFieldId)
  117. {
  118. //Create the set method
  119. MethodDefinition set = new MethodDefinition("set_Network" + originalName, MethodAttributes.Public |
  120. MethodAttributes.SpecialName |
  121. MethodAttributes.HideBySig,
  122. WeaverTypes.Import(typeof(void)));
  123. ILProcessor worker = set.Body.GetILProcessor();
  124. // if (!SyncVarEqual(value, ref playerData))
  125. Instruction endOfMethod = worker.Create(OpCodes.Nop);
  126. // this
  127. worker.Emit(OpCodes.Ldarg_0);
  128. // new value to set
  129. worker.Emit(OpCodes.Ldarg_1);
  130. // reference to field to set
  131. // make generic version of SetSyncVar with field type
  132. if (fd.FieldType.Is<UnityEngine.GameObject>())
  133. {
  134. // reference to netId Field to set
  135. worker.Emit(OpCodes.Ldarg_0);
  136. worker.Emit(OpCodes.Ldfld, netFieldId);
  137. worker.Emit(OpCodes.Call, WeaverTypes.syncVarGameObjectEqualReference);
  138. }
  139. else if (fd.FieldType.Is<NetworkIdentity>())
  140. {
  141. // reference to netId Field to set
  142. worker.Emit(OpCodes.Ldarg_0);
  143. worker.Emit(OpCodes.Ldfld, netFieldId);
  144. worker.Emit(OpCodes.Call, WeaverTypes.syncVarNetworkIdentityEqualReference);
  145. }
  146. else if (fd.FieldType.IsDerivedFrom<NetworkBehaviour>())
  147. {
  148. // reference to netId Field to set
  149. worker.Emit(OpCodes.Ldarg_0);
  150. worker.Emit(OpCodes.Ldfld, netFieldId);
  151. MethodReference getFunc = WeaverTypes.syncVarNetworkBehaviourEqualReference.MakeGeneric(fd.FieldType);
  152. worker.Emit(OpCodes.Call, getFunc);
  153. }
  154. else
  155. {
  156. worker.Emit(OpCodes.Ldarg_0);
  157. worker.Emit(OpCodes.Ldflda, fd);
  158. GenericInstanceMethod syncVarEqualGm = new GenericInstanceMethod(WeaverTypes.syncVarEqualReference);
  159. syncVarEqualGm.GenericArguments.Add(fd.FieldType);
  160. worker.Emit(OpCodes.Call, syncVarEqualGm);
  161. }
  162. worker.Emit(OpCodes.Brtrue, endOfMethod);
  163. // T oldValue = value;
  164. // TODO for GO/NI we need to backup the netId don't we?
  165. VariableDefinition oldValue = new VariableDefinition(fd.FieldType);
  166. set.Body.Variables.Add(oldValue);
  167. worker.Emit(OpCodes.Ldarg_0);
  168. worker.Emit(OpCodes.Ldfld, fd);
  169. worker.Emit(OpCodes.Stloc, oldValue);
  170. // this
  171. worker.Emit(OpCodes.Ldarg_0);
  172. // new value to set
  173. worker.Emit(OpCodes.Ldarg_1);
  174. // reference to field to set
  175. worker.Emit(OpCodes.Ldarg_0);
  176. worker.Emit(OpCodes.Ldflda, fd);
  177. // dirty bit
  178. // 8 byte integer aka long
  179. worker.Emit(OpCodes.Ldc_I8, dirtyBit);
  180. if (fd.FieldType.Is<UnityEngine.GameObject>())
  181. {
  182. // reference to netId Field to set
  183. worker.Emit(OpCodes.Ldarg_0);
  184. worker.Emit(OpCodes.Ldflda, netFieldId);
  185. worker.Emit(OpCodes.Call, WeaverTypes.setSyncVarGameObjectReference);
  186. }
  187. else if (fd.FieldType.Is<NetworkIdentity>())
  188. {
  189. // reference to netId Field to set
  190. worker.Emit(OpCodes.Ldarg_0);
  191. worker.Emit(OpCodes.Ldflda, netFieldId);
  192. worker.Emit(OpCodes.Call, WeaverTypes.setSyncVarNetworkIdentityReference);
  193. }
  194. else if (fd.FieldType.IsDerivedFrom<NetworkBehaviour>())
  195. {
  196. // reference to netId Field to set
  197. worker.Emit(OpCodes.Ldarg_0);
  198. worker.Emit(OpCodes.Ldflda, netFieldId);
  199. MethodReference getFunc = WeaverTypes.setSyncVarNetworkBehaviourReference.MakeGeneric(fd.FieldType);
  200. worker.Emit(OpCodes.Call, getFunc);
  201. }
  202. else
  203. {
  204. // make generic version of SetSyncVar with field type
  205. GenericInstanceMethod gm = new GenericInstanceMethod(WeaverTypes.setSyncVarReference);
  206. gm.GenericArguments.Add(fd.FieldType);
  207. // invoke SetSyncVar
  208. worker.Emit(OpCodes.Call, gm);
  209. }
  210. MethodDefinition hookMethod = GetHookMethod(td, fd);
  211. if (hookMethod != null)
  212. {
  213. //if (NetworkServer.localClientActive && !getSyncVarHookGuard(dirtyBit))
  214. Instruction label = worker.Create(OpCodes.Nop);
  215. worker.Emit(OpCodes.Call, WeaverTypes.NetworkServerGetLocalClientActive);
  216. worker.Emit(OpCodes.Brfalse, label);
  217. worker.Emit(OpCodes.Ldarg_0);
  218. worker.Emit(OpCodes.Ldc_I8, dirtyBit);
  219. worker.Emit(OpCodes.Call, WeaverTypes.getSyncVarHookGuard);
  220. worker.Emit(OpCodes.Brtrue, label);
  221. // setSyncVarHookGuard(dirtyBit, true);
  222. worker.Emit(OpCodes.Ldarg_0);
  223. worker.Emit(OpCodes.Ldc_I8, dirtyBit);
  224. worker.Emit(OpCodes.Ldc_I4_1);
  225. worker.Emit(OpCodes.Call, WeaverTypes.setSyncVarHookGuard);
  226. // call hook (oldValue, newValue)
  227. // Generates: OnValueChanged(oldValue, value);
  228. WriteCallHookMethodUsingArgument(worker, hookMethod, oldValue);
  229. // setSyncVarHookGuard(dirtyBit, false);
  230. worker.Emit(OpCodes.Ldarg_0);
  231. worker.Emit(OpCodes.Ldc_I8, dirtyBit);
  232. worker.Emit(OpCodes.Ldc_I4_0);
  233. worker.Emit(OpCodes.Call, WeaverTypes.setSyncVarHookGuard);
  234. worker.Append(label);
  235. }
  236. worker.Append(endOfMethod);
  237. worker.Emit(OpCodes.Ret);
  238. set.Parameters.Add(new ParameterDefinition("value", ParameterAttributes.In, fd.FieldType));
  239. set.SemanticsAttributes = MethodSemanticsAttributes.Setter;
  240. return set;
  241. }
  242. public static void ProcessSyncVar(TypeDefinition td, FieldDefinition fd, Dictionary<FieldDefinition, FieldDefinition> syncVarNetIds, long dirtyBit)
  243. {
  244. string originalName = fd.Name;
  245. Weaver.DLog(td, "Sync Var " + fd.Name + " " + fd.FieldType);
  246. // GameObject/NetworkIdentity SyncVars have a new field for netId
  247. FieldDefinition netIdField = null;
  248. // NetworkBehaviour has different field type than other NetworkIdentityFields
  249. if (fd.FieldType.IsDerivedFrom<NetworkBehaviour>())
  250. {
  251. netIdField = new FieldDefinition("___" + fd.Name + "NetId",
  252. FieldAttributes.Private,
  253. WeaverTypes.Import<NetworkBehaviour.NetworkBehaviourSyncVar>());
  254. syncVarNetIds[fd] = netIdField;
  255. }
  256. else if (fd.FieldType.IsNetworkIdentityField())
  257. {
  258. netIdField = new FieldDefinition("___" + fd.Name + "NetId",
  259. FieldAttributes.Private,
  260. WeaverTypes.Import<uint>());
  261. syncVarNetIds[fd] = netIdField;
  262. }
  263. MethodDefinition get = GenerateSyncVarGetter(fd, originalName, netIdField);
  264. MethodDefinition set = GenerateSyncVarSetter(td, fd, originalName, dirtyBit, netIdField);
  265. //NOTE: is property even needed? Could just use a setter function?
  266. //create the property
  267. PropertyDefinition propertyDefinition = new PropertyDefinition("Network" + originalName, PropertyAttributes.None, fd.FieldType)
  268. {
  269. GetMethod = get,
  270. SetMethod = set
  271. };
  272. //add the methods and property to the type.
  273. td.Methods.Add(get);
  274. td.Methods.Add(set);
  275. td.Properties.Add(propertyDefinition);
  276. Weaver.WeaveLists.replacementSetterProperties[fd] = set;
  277. // replace getter field if GameObject/NetworkIdentity so it uses
  278. // netId instead
  279. // -> only for GameObjects, otherwise an int syncvar's getter would
  280. // end up in recursion.
  281. if (fd.FieldType.IsNetworkIdentityField())
  282. {
  283. Weaver.WeaveLists.replacementGetterProperties[fd] = get;
  284. }
  285. }
  286. public static (List<FieldDefinition> syncVars, Dictionary<FieldDefinition, FieldDefinition> syncVarNetIds) ProcessSyncVars(TypeDefinition td)
  287. {
  288. List<FieldDefinition> syncVars = new List<FieldDefinition>();
  289. Dictionary<FieldDefinition, FieldDefinition> syncVarNetIds = new Dictionary<FieldDefinition, FieldDefinition>();
  290. // the mapping of dirtybits to sync-vars is implicit in the order of the fields here. this order is recorded in m_replacementProperties.
  291. // start assigning syncvars at the place the base class stopped, if any
  292. int dirtyBitCounter = Weaver.WeaveLists.GetSyncVarStart(td.BaseType.FullName);
  293. // find syncvars
  294. foreach (FieldDefinition fd in td.Fields)
  295. {
  296. if (fd.HasCustomAttribute<SyncVarAttribute>())
  297. {
  298. if ((fd.Attributes & FieldAttributes.Static) != 0)
  299. {
  300. Weaver.Error($"{fd.Name} cannot be static", fd);
  301. continue;
  302. }
  303. if (fd.FieldType.IsArray)
  304. {
  305. Weaver.Error($"{fd.Name} has invalid type. Use SyncLists instead of arrays", fd);
  306. continue;
  307. }
  308. if (SyncObjectInitializer.ImplementsSyncObject(fd.FieldType))
  309. {
  310. Weaver.Warning($"{fd.Name} has [SyncVar] attribute. SyncLists should not be marked with SyncVar", fd);
  311. }
  312. else
  313. {
  314. syncVars.Add(fd);
  315. ProcessSyncVar(td, fd, syncVarNetIds, 1L << dirtyBitCounter);
  316. dirtyBitCounter += 1;
  317. if (dirtyBitCounter == SyncVarLimit)
  318. {
  319. Weaver.Error($"{td.Name} has too many SyncVars. Consider refactoring your class into multiple components", td);
  320. continue;
  321. }
  322. }
  323. }
  324. }
  325. // add all the new SyncVar __netId fields
  326. foreach (FieldDefinition fd in syncVarNetIds.Values)
  327. {
  328. td.Fields.Add(fd);
  329. }
  330. Weaver.WeaveLists.SetNumSyncVars(td.FullName, syncVars.Count);
  331. return (syncVars, syncVarNetIds);
  332. }
  333. public static void WriteCallHookMethodUsingArgument(ILProcessor worker, MethodDefinition hookMethod, VariableDefinition oldValue)
  334. {
  335. WriteCallHookMethod(worker, hookMethod, oldValue, null);
  336. }
  337. public static void WriteCallHookMethodUsingField(ILProcessor worker, MethodDefinition hookMethod, VariableDefinition oldValue, FieldDefinition newValue)
  338. {
  339. if (newValue == null)
  340. {
  341. Weaver.Error("NewValue field was null when writing SyncVar hook");
  342. }
  343. WriteCallHookMethod(worker, hookMethod, oldValue, newValue);
  344. }
  345. static void WriteCallHookMethod(ILProcessor worker, MethodDefinition hookMethod, VariableDefinition oldValue, FieldDefinition newValue)
  346. {
  347. WriteStartFunctionCall();
  348. // write args
  349. WriteOldValue();
  350. WriteNewValue();
  351. WriteEndFunctionCall();
  352. // *** Local functions used to write OpCodes ***
  353. // Local functions have access to function variables, no need to pass in args
  354. void WriteOldValue()
  355. {
  356. worker.Emit(OpCodes.Ldloc, oldValue);
  357. }
  358. void WriteNewValue()
  359. {
  360. // write arg1 or this.field
  361. if (newValue == null)
  362. {
  363. worker.Emit(OpCodes.Ldarg_1);
  364. }
  365. else
  366. {
  367. // this.
  368. worker.Emit(OpCodes.Ldarg_0);
  369. // syncvar.get
  370. worker.Emit(OpCodes.Ldfld, newValue);
  371. }
  372. }
  373. // Writes this before method if it is not static
  374. void WriteStartFunctionCall()
  375. {
  376. // don't add this (Ldarg_0) if method is static
  377. if (!hookMethod.IsStatic)
  378. {
  379. // this before method call
  380. // e.g. this.onValueChanged
  381. worker.Emit(OpCodes.Ldarg_0);
  382. }
  383. }
  384. // Calls method
  385. void WriteEndFunctionCall()
  386. {
  387. // only use Callvirt when not static
  388. OpCode opcode = hookMethod.IsStatic ? OpCodes.Call : OpCodes.Callvirt;
  389. worker.Emit(opcode, hookMethod);
  390. }
  391. }
  392. }
  393. }