// adds Mirror.GeneratedNetworkCode.InitReadWriters() method that // registers all generated writers into Mirror.Writer<T> static class. // -> uses [RuntimeInitializeOnLoad] attribute so it's invoke at runtime // -> uses [InitializeOnLoad] if UnityEditor is referenced so it works // in Editor and in tests too // // use ILSpy to see the result (it's in the DLL's 'Mirror' namespace) public static void InitializeReaderAndWriters(AssemblyDefinition currentAssembly, WeaverTypes weaverTypes, Writers writers, Readers readers, TypeDefinition GeneratedCodeClass) { MethodDefinition initReadWriters = new MethodDefinition("InitReadWriters", MethodAttributes.Public | MethodAttributes.Static, weaverTypes.Import(typeof(void))); // add [RuntimeInitializeOnLoad] in any case Helpers.AddRuntimeInitializeOnLoadAttribute(currentAssembly, weaverTypes, initReadWriters); // add [InitializeOnLoad] if UnityEditor is referenced if (Helpers.IsEditorAssembly(currentAssembly)) { Helpers.AddInitializeOnLoadAttribute(currentAssembly, weaverTypes, initReadWriters); } // fill function body with reader/writer initializers ILProcessor worker = initReadWriters.Body.GetILProcessor(); // for debugging: add a log to see if initialized on load //worker.Emit(OpCodes.Ldstr, $"[InitReadWriters] called!"); //worker.Emit(OpCodes.Call, Weaver.weaverTypes.logWarningReference); writers.InitializeWriters(worker); readers.InitializeReaders(worker); worker.Emit(OpCodes.Ret); GeneratedCodeClass.Methods.Add(initReadWriters); }
// we need to inject several initializations into NetworkBehaviour cctor void InjectIntoStaticConstructor(ref bool WeavingFailed) { if (commands.Count == 0 && clientRpcs.Count == 0 && targetRpcs.Count == 0) { return; } // find static constructor MethodDefinition cctor = netBehaviourSubclass.GetMethod(".cctor"); bool cctorFound = cctor != null; if (cctor != null) { // remove the return opcode from end of function. will add our own later. if (!RemoveFinalRetInstruction(cctor)) { Log.Error($"{netBehaviourSubclass.Name} has invalid class constructor", cctor); WeavingFailed = true; return; } } else { // make one! cctor = new MethodDefinition(".cctor", MethodAttributes.Private | MethodAttributes.HideBySig | MethodAttributes.SpecialName | MethodAttributes.RTSpecialName | MethodAttributes.Static, weaverTypes.Import(typeof(void))); } // Static constructors are lazily called when the class is first "used" // https://docs.microsoft.com/en-us/dotnet/csharp/programming-guide/classes-and-structs/static-constructors // > It is called automatically before the first instance is created or any static members are referenced. // // This means, in particular circumstances, client and server may diverge on which classes they have "loaded" // and thus the rpc index will be desynced // // One such example would be on-demand-loading of prefabs via custom spawn handlers: // A game might not want to preload all its monster prefabs since there's quite many of them // and it is practically impossible to encounter them all (or even a majority of them) in a single play // session. // So a custom spawn handler is used to load the monster prefabs on demand. // All monsters would have the "Monster" NetworkBehaviour class, which would have a few RPCs on it. // Since the monster prefabs are loaded ONLY when needed via a custom spawn handler and the Monster class // itself isn't referenced or "loaded" by anything else other than the prefab, the monster classes static // constructor will not have been called when a client first joins a game, while it may have been called // on the server/host. This will in turn cause Rpc indexes to not match up between client/server // // By just forcing the static constructors to run via the [RuntimeInitializeOnLoadMethod] attribute // this is not a problem anymore, since all static constructors are always run and all RPCs will // always be registered by the time they are used if (!cctor.HasCustomAttribute <RuntimeInitializeOnLoadMethodAttribute>()) { Helpers.AddRuntimeInitializeOnLoadAttribute(assembly, weaverTypes, cctor); } ILProcessor cctorWorker = cctor.Body.GetILProcessor(); // register all commands in cctor for (int i = 0; i < commands.Count; ++i) { CmdResult cmdResult = commands[i]; GenerateRegisterCommandDelegate(cctorWorker, weaverTypes.registerCommandReference, commandInvocationFuncs[i], cmdResult); } // register all client rpcs in cctor for (int i = 0; i < clientRpcs.Count; ++i) { ClientRpcResult clientRpcResult = clientRpcs[i]; GenerateRegisterRemoteDelegate(cctorWorker, weaverTypes.registerRpcReference, clientRpcInvocationFuncs[i], clientRpcResult.method.FullName); } // register all target rpcs in cctor for (int i = 0; i < targetRpcs.Count; ++i) { GenerateRegisterRemoteDelegate(cctorWorker, weaverTypes.registerRpcReference, targetRpcInvocationFuncs[i], targetRpcs[i].FullName); } // add final 'Ret' instruction to cctor cctorWorker.Append(cctorWorker.Create(OpCodes.Ret)); if (!cctorFound) { netBehaviourSubclass.Methods.Add(cctor); } // in case class had no cctor, it might have BeforeFieldInit, so injected cctor would be called too late netBehaviourSubclass.Attributes &= ~TypeAttributes.BeforeFieldInit; }