SyncVarAttributeProcessor.cs 20 KB

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