public void Process() { if (m_td.HasGenericParameters) { Weaver.fail = true; Log.Error("NetworkBehaviour " + m_td.Name + " cannot have generic parameters"); return; } Weaver.DLog(m_td, "Process Start"); MarkAsProcessed(m_td); SyncVarProcessor.ProcessSyncVars(m_td, m_SyncVars, m_SyncObjects, m_SyncVarNetIds); Weaver.ResetRecursionCount(); ProcessMethods(); SyncEventProcessor.ProcessEvents(m_td, m_Events, m_EventInvocationFuncs); if (Weaver.fail) { return; } GenerateConstants(); Weaver.ResetRecursionCount(); GenerateSerialization(); if (Weaver.fail) { return; } GenerateDeSerialization(); Weaver.DLog(m_td, "Process Done"); }
public void Process() { if (netBehaviourSubclass.HasGenericParameters) { Weaver.Error($"{netBehaviourSubclass} cannot have generic parameters"); return; } Weaver.DLog(netBehaviourSubclass, "Process Start"); MarkAsProcessed(netBehaviourSubclass); SyncVarProcessor.ProcessSyncVars(netBehaviourSubclass, syncVars, syncObjects, syncVarNetIds); ProcessMethods(); SyncEventProcessor.ProcessEvents(netBehaviourSubclass, eventRpcs, eventRpcInvocationFuncs); if (Weaver.WeavingFailed) { return; } GenerateConstants(); GenerateSerialization(); if (Weaver.WeavingFailed) { return; } GenerateDeSerialization(); Weaver.DLog(netBehaviourSubclass, "Process Done"); }
public NetworkBehaviourProcessor(TypeDefinition td, Readers readers, Writers writers, PropertySiteProcessor propertySiteProcessor, IWeaverLogger logger) { Weaver.DLog(td, "NetworkBehaviourProcessor"); netBehaviourSubclass = td; this.logger = logger; serverRpcProcessor = new ServerRpcProcessor(netBehaviourSubclass.Module, readers, writers, logger); clientRpcProcessor = new ClientRpcProcessor(netBehaviourSubclass.Module, readers, writers, logger); syncVarProcessor = new SyncVarProcessor(netBehaviourSubclass.Module, readers, writers, propertySiteProcessor, logger); syncObjectProcessor = new SyncObjectProcessor(readers, writers, logger); }
void DeserializeField(FieldDefinition syncVar, ILProcessor worker, MethodDefinition deserialize) { // check for Hook function MethodDefinition hookMethod = SyncVarProcessor.GetHookMethod(netBehaviourSubclass, syncVar); if (IsNetworkIdentityField(syncVar)) { DeserializeNetworkIdentityField(syncVar, worker, deserialize, hookMethod); } else { DeserializeNormalField(syncVar, worker, deserialize, hookMethod); } }
// return true if modified public bool Process() { // only process once if (WasProcessed(netBehaviourSubclass)) { return(false); } Weaver.DLog(netBehaviourSubclass, "Found NetworkBehaviour " + netBehaviourSubclass.FullName); if (netBehaviourSubclass.HasGenericParameters) { Weaver.Error($"{netBehaviourSubclass.Name} cannot have generic parameters", netBehaviourSubclass); // originally Process returned true in every case, except if already processed. // maybe return false here in the future. return(true); } Weaver.DLog(netBehaviourSubclass, "Process Start"); MarkAsProcessed(netBehaviourSubclass); // deconstruct tuple and set fields (syncVars, syncVarNetIds) = SyncVarProcessor.ProcessSyncVars(netBehaviourSubclass); syncObjects = SyncObjectProcessor.FindSyncObjectsFields(netBehaviourSubclass); ProcessMethods(); SyncEventProcessor.ProcessEvents(netBehaviourSubclass, eventRpcs, eventRpcInvocationFuncs); if (Weaver.WeavingFailed) { // originally Process returned true in every case, except if already processed. // maybe return false here in the future. return(true); } GenerateConstants(); GenerateSerialization(); if (Weaver.WeavingFailed) { // originally Process returned true in every case, except if already processed. // maybe return false here in the future. return(true); } GenerateDeSerialization(); Weaver.DLog(netBehaviourSubclass, "Process Done"); return(true); }
void DeserializeField(FieldDefinition syncVar, ILProcessor serWorker, MethodDefinition deserialize) { // check for Hook function if (!SyncVarProcessor.CheckForHookFunction(netBehaviourSubclass, syncVar, out MethodDefinition foundMethod)) { return; } // [SyncVar] GameObject/NetworkIdentity? /* * Generates code like: * uint oldNetId = ___qNetId; * GameObject oldSyncVar = syncvar.getter; // returns GetSyncVarGameObject(___qNetId) * ___qNetId = reader.ReadPackedUInt32(); * if (!SyncVarEqual(oldNetId, ref ___goNetId)) * { * OnSetQ(oldSyncVar, syncvar.getter); // getter returns GetSyncVarGameObject(___qNetId) * } */ if (syncVar.FieldType.FullName == Weaver.gameObjectType.FullName || syncVar.FieldType.FullName == Weaver.NetworkIdentityType.FullName) { // GameObject/NetworkIdentity SyncVar: // OnSerialize sends writer.Write(go); // OnDeserialize reads to __netId manually so we can use // lookups in the getter (so it still works if objects // move in and out of range repeatedly) FieldDefinition netIdField = syncVarNetIds[syncVar]; // uint oldNetId = ___qNetId; VariableDefinition oldNetId = new VariableDefinition(Weaver.uint32Type); deserialize.Body.Variables.Add(oldNetId); serWorker.Append(serWorker.Create(OpCodes.Ldarg_0)); serWorker.Append(serWorker.Create(OpCodes.Ldfld, netIdField)); serWorker.Append(serWorker.Create(OpCodes.Stloc, oldNetId)); // GameObject/NetworkIdentity oldSyncVar = syncvar.getter; VariableDefinition oldSyncVar = new VariableDefinition(syncVar.FieldType); deserialize.Body.Variables.Add(oldSyncVar); serWorker.Append(serWorker.Create(OpCodes.Ldarg_0)); serWorker.Append(serWorker.Create(OpCodes.Ldfld, syncVar)); serWorker.Append(serWorker.Create(OpCodes.Stloc, oldSyncVar)); // read id and store in netId field BEFORE calling the hook // -> this makes way more sense. by definition, the hook is // supposed to be called after it was changed. not before. // -> setting it BEFORE calling the hook fixes the following bug: // https://github.com/vis2k/Mirror/issues/1151 in host mode // where the value during the Hook call would call Cmds on // the host server, and they would all happen and compare // values BEFORE the hook even returned and hence BEFORE the // actual value was even set. serWorker.Append(serWorker.Create(OpCodes.Ldarg_0)); // put 'this.' onto stack for 'this.netId' below serWorker.Append(serWorker.Create(OpCodes.Ldarg_1)); // reader. for 'reader.Read()' below serWorker.Append(serWorker.Create(OpCodes.Call, Readers.GetReadFunc(Weaver.uint32Type))); // Read() serWorker.Append(serWorker.Create(OpCodes.Stfld, netIdField)); // netId if (foundMethod != null) { // call Hook(this.GetSyncVarGameObject/NetworkIdentity(reader.ReadPackedUInt32())) // because we send/receive the netID, not the GameObject/NetworkIdentity // but only if SyncVar changed. otherwise a client would // get hook calls for all initial values, even if they // didn't change from the default values on the client. // see also: https://github.com/vis2k/Mirror/issues/1278 // IMPORTANT: for GameObjects/NetworkIdentities we usually // use SyncVarGameObjectEqual to compare equality. // in this case however, we can just use // SyncVarEqual with the two uint netIds. // => this is easier weaver code because we don't // have to get the GameObject/NetworkIdentity // from the uint netId // => this is faster because we void one // GetComponent call for GameObjects to get // their NetworkIdentity when comparing. // Generates: if (!SyncVarEqual); Instruction syncVarEqualLabel = serWorker.Create(OpCodes.Nop); // 'this.' for 'this.SyncVarEqual' serWorker.Append(serWorker.Create(OpCodes.Ldarg_0)); // 'oldNetId' serWorker.Append(serWorker.Create(OpCodes.Ldloc, oldNetId)); // 'ref this.__netId' serWorker.Append(serWorker.Create(OpCodes.Ldarg_0)); serWorker.Append(serWorker.Create(OpCodes.Ldflda, netIdField)); // call the function GenericInstanceMethod syncVarEqualGm = new GenericInstanceMethod(Weaver.syncVarEqualReference); syncVarEqualGm.GenericArguments.Add(netIdField.FieldType); serWorker.Append(serWorker.Create(OpCodes.Call, syncVarEqualGm)); serWorker.Append(serWorker.Create(OpCodes.Brtrue, syncVarEqualLabel)); // call the hook serWorker.Append(serWorker.Create(OpCodes.Ldarg_0)); // this. serWorker.Append(serWorker.Create(OpCodes.Ldloc, oldSyncVar)); // oldSyncVar GO/NI serWorker.Append(serWorker.Create(OpCodes.Ldarg_0)); // this. serWorker.Append(serWorker.Create(OpCodes.Ldfld, syncVar)); // syncvar.get (finds current GO/NI from netId) serWorker.Append(serWorker.Create(OpCodes.Call, foundMethod)); // Generates: end if (!SyncVarEqual); serWorker.Append(syncVarEqualLabel); } } // [SyncVar] int/float/struct/etc.? /* * Generates code like: * int oldValue = a; // for hook * Networka = reader.ReadPackedInt32(); * if (!SyncVarEqual(oldValue, ref a)) * { * OnSetA(oldValue, Networka); * } */ else { MethodReference readFunc = Readers.GetReadFunc(syncVar.FieldType); if (readFunc == null) { Weaver.Error($"{syncVar} has unsupported type. Use a supported Mirror type instead"); return; } // T oldValue = value; VariableDefinition oldValue = new VariableDefinition(syncVar.FieldType); deserialize.Body.Variables.Add(oldValue); serWorker.Append(serWorker.Create(OpCodes.Ldarg_0)); serWorker.Append(serWorker.Create(OpCodes.Ldfld, syncVar)); serWorker.Append(serWorker.Create(OpCodes.Stloc, oldValue)); // read value and store in syncvar BEFORE calling the hook // -> this makes way more sense. by definition, the hook is // supposed to be called after it was changed. not before. // -> setting it BEFORE calling the hook fixes the following bug: // https://github.com/vis2k/Mirror/issues/1151 in host mode // where the value during the Hook call would call Cmds on // the host server, and they would all happen and compare // values BEFORE the hook even returned and hence BEFORE the // actual value was even set. serWorker.Append(serWorker.Create(OpCodes.Ldarg_0)); // put 'this.' onto stack for 'this.syncvar' below serWorker.Append(serWorker.Create(OpCodes.Ldarg_1)); // reader. for 'reader.Read()' below serWorker.Append(serWorker.Create(OpCodes.Call, readFunc)); // reader.Read() serWorker.Append(serWorker.Create(OpCodes.Stfld, syncVar)); // syncvar if (foundMethod != null) { // call hook // but only if SyncVar changed. otherwise a client would // get hook calls for all initial values, even if they // didn't change from the default values on the client. // see also: https://github.com/vis2k/Mirror/issues/1278 // Generates: if (!SyncVarEqual); Instruction syncVarEqualLabel = serWorker.Create(OpCodes.Nop); // 'this.' for 'this.SyncVarEqual' serWorker.Append(serWorker.Create(OpCodes.Ldarg_0)); // 'oldValue' serWorker.Append(serWorker.Create(OpCodes.Ldloc, oldValue)); // 'ref this.syncVar' serWorker.Append(serWorker.Create(OpCodes.Ldarg_0)); serWorker.Append(serWorker.Create(OpCodes.Ldflda, syncVar)); // call the function GenericInstanceMethod syncVarEqualGm = new GenericInstanceMethod(Weaver.syncVarEqualReference); syncVarEqualGm.GenericArguments.Add(syncVar.FieldType); serWorker.Append(serWorker.Create(OpCodes.Call, syncVarEqualGm)); serWorker.Append(serWorker.Create(OpCodes.Brtrue, syncVarEqualLabel)); // call the hook serWorker.Append(serWorker.Create(OpCodes.Ldarg_0)); // this. serWorker.Append(serWorker.Create(OpCodes.Ldloc, oldValue)); // oldvalue serWorker.Append(serWorker.Create(OpCodes.Ldarg_0)); // this. serWorker.Append(serWorker.Create(OpCodes.Ldfld, syncVar)); // syncvar.get serWorker.Append(serWorker.Create(OpCodes.Call, foundMethod)); // Generates: end if (!SyncVarEqual); serWorker.Append(syncVarEqualLabel); } } }
/// <summary> /// [SyncVar] int/float/struct/etc.? /// </summary> /// <param name="syncVar"></param> /// <param name="serWorker"></param> /// <param name="deserialize"></param> /// <param name="initialState"></param> /// <param name="hookResult"></param> void DeserializeNormalField(FieldDefinition syncVar, ILProcessor serWorker, MethodDefinition deserialize, MethodDefinition hookMethod) { /* * Generates code like: * // for hook * int oldValue = a; * Networka = reader.ReadPackedInt32(); * if (!SyncVarEqual(oldValue, ref a)) * { * OnSetA(oldValue, Networka); * } */ MethodReference readFunc = Readers.GetReadFunc(syncVar.FieldType); if (readFunc == null) { Weaver.Error($"{syncVar.Name} has unsupported type. Use a supported Mirror type instead", syncVar); return; } // T oldValue = value; VariableDefinition oldValue = new VariableDefinition(syncVar.FieldType); deserialize.Body.Variables.Add(oldValue); serWorker.Append(serWorker.Create(OpCodes.Ldarg_0)); serWorker.Append(serWorker.Create(OpCodes.Ldfld, syncVar)); serWorker.Append(serWorker.Create(OpCodes.Stloc, oldValue)); // read value and store in syncvar BEFORE calling the hook // -> this makes way more sense. by definition, the hook is // supposed to be called after it was changed. not before. // -> setting it BEFORE calling the hook fixes the following bug: // https://github.com/vis2k/Mirror/issues/1151 in host mode // where the value during the Hook call would call Cmds on // the host server, and they would all happen and compare // values BEFORE the hook even returned and hence BEFORE the // actual value was even set. // put 'this.' onto stack for 'this.syncvar' below serWorker.Append(serWorker.Create(OpCodes.Ldarg_0)); // reader. for 'reader.Read()' below serWorker.Append(serWorker.Create(OpCodes.Ldarg_1)); // reader.Read() serWorker.Append(serWorker.Create(OpCodes.Call, readFunc)); // syncvar serWorker.Append(serWorker.Create(OpCodes.Stfld, syncVar)); if (hookMethod != null) { // call hook // but only if SyncVar changed. otherwise a client would // get hook calls for all initial values, even if they // didn't change from the default values on the client. // see also: https://github.com/vis2k/Mirror/issues/1278 // Generates: if (!SyncVarEqual); Instruction syncVarEqualLabel = serWorker.Create(OpCodes.Nop); // 'this.' for 'this.SyncVarEqual' serWorker.Append(serWorker.Create(OpCodes.Ldarg_0)); // 'oldValue' serWorker.Append(serWorker.Create(OpCodes.Ldloc, oldValue)); // 'ref this.syncVar' serWorker.Append(serWorker.Create(OpCodes.Ldarg_0)); serWorker.Append(serWorker.Create(OpCodes.Ldflda, syncVar)); // call the function GenericInstanceMethod syncVarEqualGm = new GenericInstanceMethod(WeaverTypes.syncVarEqualReference); syncVarEqualGm.GenericArguments.Add(syncVar.FieldType); serWorker.Append(serWorker.Create(OpCodes.Call, syncVarEqualGm)); serWorker.Append(serWorker.Create(OpCodes.Brtrue, syncVarEqualLabel)); // call the hook // Generates: OnValueChanged(oldValue, this.syncVar); SyncVarProcessor.WriteCallHookMethodUsingField(serWorker, hookMethod, oldValue, syncVar); // Generates: end if (!SyncVarEqual); serWorker.Append(syncVarEqualLabel); } }
/// <summary> /// [SyncVar] GameObject/NetworkIdentity? /// </summary> /// <param name="syncVar"></param> /// <param name="worker"></param> /// <param name="deserialize"></param> /// <param name="initialState"></param> /// <param name="hookResult"></param> void DeserializeNetworkIdentityField(FieldDefinition syncVar, ILProcessor worker, MethodDefinition deserialize, MethodDefinition hookMethod) { /* * Generates code like: * uint oldNetId = ___qNetId; * // returns GetSyncVarGameObject(___qNetId) * GameObject oldSyncVar = syncvar.getter; * ___qNetId = reader.ReadPackedUInt32(); * if (!SyncVarEqual(oldNetId, ref ___goNetId)) * { * // getter returns GetSyncVarGameObject(___qNetId) * OnSetQ(oldSyncVar, syncvar.getter); * } */ // GameObject/NetworkIdentity SyncVar: // OnSerialize sends writer.Write(go); // OnDeserialize reads to __netId manually so we can use // lookups in the getter (so it still works if objects // move in and out of range repeatedly) FieldDefinition netIdField = syncVarNetIds[syncVar]; // uint oldNetId = ___qNetId; VariableDefinition oldNetId = new VariableDefinition(WeaverTypes.Import <uint>()); deserialize.Body.Variables.Add(oldNetId); worker.Append(worker.Create(OpCodes.Ldarg_0)); worker.Append(worker.Create(OpCodes.Ldfld, netIdField)); worker.Append(worker.Create(OpCodes.Stloc, oldNetId)); // GameObject/NetworkIdentity oldSyncVar = syncvar.getter; VariableDefinition oldSyncVar = new VariableDefinition(syncVar.FieldType); deserialize.Body.Variables.Add(oldSyncVar); worker.Append(worker.Create(OpCodes.Ldarg_0)); worker.Append(worker.Create(OpCodes.Ldfld, syncVar)); worker.Append(worker.Create(OpCodes.Stloc, oldSyncVar)); // read id and store in netId field BEFORE calling the hook // -> this makes way more sense. by definition, the hook is // supposed to be called after it was changed. not before. // -> setting it BEFORE calling the hook fixes the following bug: // https://github.com/vis2k/Mirror/issues/1151 in host mode // where the value during the Hook call would call Cmds on // the host server, and they would all happen and compare // values BEFORE the hook even returned and hence BEFORE the // actual value was even set. // put 'this.' onto stack for 'this.netId' below worker.Append(worker.Create(OpCodes.Ldarg_0)); // reader. for 'reader.Read()' below worker.Append(worker.Create(OpCodes.Ldarg_1)); // Read() worker.Append(worker.Create(OpCodes.Call, Readers.GetReadFunc(WeaverTypes.Import <uint>()))); // netId worker.Append(worker.Create(OpCodes.Stfld, netIdField)); if (hookMethod != null) { // call Hook(this.GetSyncVarGameObject/NetworkIdentity(reader.ReadPackedUInt32())) // because we send/receive the netID, not the GameObject/NetworkIdentity // but only if SyncVar changed. otherwise a client would // get hook calls for all initial values, even if they // didn't change from the default values on the client. // see also: https://github.com/vis2k/Mirror/issues/1278 // IMPORTANT: for GameObjects/NetworkIdentities we usually // use SyncVarGameObjectEqual to compare equality. // in this case however, we can just use // SyncVarEqual with the two uint netIds. // => this is easier weaver code because we don't // have to get the GameObject/NetworkIdentity // from the uint netId // => this is faster because we void one // GetComponent call for GameObjects to get // their NetworkIdentity when comparing. // Generates: if (!SyncVarEqual); Instruction syncVarEqualLabel = worker.Create(OpCodes.Nop); // 'this.' for 'this.SyncVarEqual' worker.Append(worker.Create(OpCodes.Ldarg_0)); // 'oldNetId' worker.Append(worker.Create(OpCodes.Ldloc, oldNetId)); // 'ref this.__netId' worker.Append(worker.Create(OpCodes.Ldarg_0)); worker.Append(worker.Create(OpCodes.Ldflda, netIdField)); // call the function GenericInstanceMethod syncVarEqualGm = new GenericInstanceMethod(WeaverTypes.syncVarEqualReference); syncVarEqualGm.GenericArguments.Add(netIdField.FieldType); worker.Append(worker.Create(OpCodes.Call, syncVarEqualGm)); worker.Append(worker.Create(OpCodes.Brtrue, syncVarEqualLabel)); // call the hook // Generates: OnValueChanged(oldValue, this.syncVar); SyncVarProcessor.WriteCallHookMethodUsingField(worker, hookMethod, oldSyncVar, syncVar); // Generates: end if (!SyncVarEqual); worker.Append(syncVarEqualLabel); } }
void DeserializeField(FieldDefinition syncVar, ILProcessor serWorker, MethodDefinition deserialize) { // check for Hook function if (!SyncVarProcessor.CheckForHookFunction(netBehaviourSubclass, syncVar, out MethodDefinition foundMethod)) { return; } if (syncVar.FieldType.FullName == Weaver.gameObjectType.FullName || syncVar.FieldType.FullName == Weaver.NetworkIdentityType.FullName) { // GameObject/NetworkIdentity SyncVar: // OnSerialize sends writer.Write(go); // OnDeserialize reads to __netId manually so we can use // lookups in the getter (so it still works if objects // move in and out of range repeatedly) FieldDefinition netIdField = syncVarNetIds[syncVar]; VariableDefinition tmpValue = new VariableDefinition(Weaver.uint32Type); deserialize.Body.Variables.Add(tmpValue); // read id and store in a local variable serWorker.Append(serWorker.Create(OpCodes.Ldarg_1)); serWorker.Append(serWorker.Create(OpCodes.Call, Weaver.NetworkReaderReadPackedUInt32)); serWorker.Append(serWorker.Create(OpCodes.Stloc, tmpValue)); if (foundMethod != null) { // call Hook(this.GetSyncVarGameObject/NetworkIdentity(reader.ReadPackedUInt32())) // because we send/receive the netID, not the GameObject/NetworkIdentity serWorker.Append(serWorker.Create(OpCodes.Ldarg_0)); // this. serWorker.Append(serWorker.Create(OpCodes.Ldarg_0)); serWorker.Append(serWorker.Create(OpCodes.Ldloc, tmpValue)); serWorker.Append(serWorker.Create(OpCodes.Ldarg_0)); serWorker.Append(serWorker.Create(OpCodes.Ldflda, syncVar)); if (syncVar.FieldType.FullName == Weaver.gameObjectType.FullName) { serWorker.Append(serWorker.Create(OpCodes.Callvirt, Weaver.getSyncVarGameObjectReference)); } else if (syncVar.FieldType.FullName == Weaver.NetworkIdentityType.FullName) { serWorker.Append(serWorker.Create(OpCodes.Callvirt, Weaver.getSyncVarNetworkIdentityReference)); } serWorker.Append(serWorker.Create(OpCodes.Call, foundMethod)); } // set the netid field serWorker.Append(serWorker.Create(OpCodes.Ldarg_0)); serWorker.Append(serWorker.Create(OpCodes.Ldloc, tmpValue)); serWorker.Append(serWorker.Create(OpCodes.Stfld, netIdField)); } else { MethodReference readFunc = Readers.GetReadFunc(syncVar.FieldType); if (readFunc == null) { Weaver.Error("GenerateDeSerialization for " + netBehaviourSubclass.Name + " unknown type [" + syncVar.FieldType + "]. Mirror [SyncVar] member variables must be basic types."); return; } VariableDefinition tmpValue = new VariableDefinition(syncVar.FieldType); deserialize.Body.Variables.Add(tmpValue); // read value and put it in a local variable serWorker.Append(serWorker.Create(OpCodes.Ldarg_1)); serWorker.Append(serWorker.Create(OpCodes.Call, readFunc)); serWorker.Append(serWorker.Create(OpCodes.Stloc, tmpValue)); if (foundMethod != null) { // call hook serWorker.Append(serWorker.Create(OpCodes.Ldarg_0)); serWorker.Append(serWorker.Create(OpCodes.Ldloc, tmpValue)); serWorker.Append(serWorker.Create(OpCodes.Call, foundMethod)); } // set the property serWorker.Append(serWorker.Create(OpCodes.Ldarg_0)); serWorker.Append(serWorker.Create(OpCodes.Ldloc, tmpValue)); serWorker.Append(serWorker.Create(OpCodes.Stfld, syncVar)); } }
void GenerateDeSerialization() { Weaver.DLog(m_td, " GenerateDeSerialization"); foreach (MethodDefinition m in m_td.Methods) { if (m.Name == "OnDeserialize") { return; } } if (m_SyncVars.Count == 0) { // no synvars, no need for custom OnDeserialize return; } MethodDefinition serialize = new MethodDefinition("OnDeserialize", MethodAttributes.Public | MethodAttributes.Virtual | MethodAttributes.HideBySig, Weaver.voidType); serialize.Parameters.Add(new ParameterDefinition("reader", ParameterAttributes.None, Weaver.scriptDef.MainModule.ImportReference(Weaver.NetworkReaderType))); serialize.Parameters.Add(new ParameterDefinition("initialState", ParameterAttributes.None, Weaver.boolType)); ILProcessor serWorker = serialize.Body.GetILProcessor(); MethodReference baseDeserialize = Resolvers.ResolveMethodInParents(m_td.BaseType, Weaver.scriptDef, "OnDeserialize"); if (baseDeserialize != null) { serWorker.Append(serWorker.Create(OpCodes.Ldarg_0)); // base serWorker.Append(serWorker.Create(OpCodes.Ldarg_1)); // reader serWorker.Append(serWorker.Create(OpCodes.Ldarg_2)); // initialState serWorker.Append(serWorker.Create(OpCodes.Call, baseDeserialize)); } // Generates: if (initialState); Instruction initialStateLabel = serWorker.Create(OpCodes.Nop); serWorker.Append(serWorker.Create(OpCodes.Ldarg_2)); serWorker.Append(serWorker.Create(OpCodes.Brfalse, initialStateLabel)); foreach (FieldDefinition syncVar in m_SyncVars) { // assign value serWorker.Append(serWorker.Create(OpCodes.Ldarg_0)); serWorker.Append(serWorker.Create(OpCodes.Ldarg_1)); if (syncVar.FieldType.FullName == Weaver.gameObjectType.FullName || syncVar.FieldType.FullName == Weaver.NetworkIdentityType.FullName) { // GameObject/NetworkIdentity SyncVar: // OnSerialize sends writer.Write(go); // OnDeserialize reads to __netId manually so we can use // lookups in the getter (so it still works if objects // move in and out of range repeatedly) FieldDefinition netIdField = m_SyncVarNetIds[syncVar]; serWorker.Append(serWorker.Create(OpCodes.Callvirt, Weaver.NetworkReaderReadPacked32)); serWorker.Append(serWorker.Create(OpCodes.Stfld, netIdField)); } else { MethodReference readFunc = Weaver.GetReadFunc(syncVar.FieldType); if (readFunc != null) { serWorker.Append(serWorker.Create(OpCodes.Call, readFunc)); } else { Log.Error("GenerateDeSerialization for " + m_td.Name + " unknown type [" + syncVar.FieldType + "]. UNet [SyncVar] member variables must be basic types."); Weaver.fail = true; return; } serWorker.Append(serWorker.Create(OpCodes.Stfld, syncVar)); } } serWorker.Append(serWorker.Create(OpCodes.Ret)); // Generates: end if (initialState); serWorker.Append(initialStateLabel); // setup local for dirty bits serialize.Body.InitLocals = true; VariableDefinition dirtyBitsLocal = new VariableDefinition(Weaver.int64Type); serialize.Body.Variables.Add(dirtyBitsLocal); // get dirty bits serWorker.Append(serWorker.Create(OpCodes.Ldarg_1)); serWorker.Append(serWorker.Create(OpCodes.Callvirt, Weaver.NetworkReaderReadPacked64)); serWorker.Append(serWorker.Create(OpCodes.Stloc_0)); // conditionally read each syncvar int dirtyBit = Weaver.GetSyncVarStart(m_td.BaseType.FullName); // start at number of syncvars in parent foreach (FieldDefinition syncVar in m_SyncVars) { Instruction varLabel = serWorker.Create(OpCodes.Nop); // check if dirty bit is set serWorker.Append(serWorker.Create(OpCodes.Ldloc_0)); serWorker.Append(serWorker.Create(OpCodes.Ldc_I8, 1L << dirtyBit)); serWorker.Append(serWorker.Create(OpCodes.And)); serWorker.Append(serWorker.Create(OpCodes.Brfalse, varLabel)); // check for Hook function MethodDefinition foundMethod; if (!SyncVarProcessor.CheckForHookFunction(m_td, syncVar, out foundMethod)) { return; } if (syncVar.FieldType.FullName == Weaver.gameObjectType.FullName || syncVar.FieldType.FullName == Weaver.NetworkIdentityType.FullName) { // GameObject/NetworkIdentity SyncVar: // OnSerialize sends writer.Write(go); // OnDeserialize reads to __netId manually so we can use // lookups in the getter (so it still works if objects // move in and out of range repeatedly) FieldDefinition netIdField = m_SyncVarNetIds[syncVar]; if (foundMethod == null) { serWorker.Append(serWorker.Create(OpCodes.Ldarg_0)); serWorker.Append(serWorker.Create(OpCodes.Ldarg_1)); serWorker.Append(serWorker.Create(OpCodes.Callvirt, Weaver.NetworkReaderReadPacked32)); serWorker.Append(serWorker.Create(OpCodes.Stfld, netIdField)); } else { // call Hook(this.GetSyncVarGameObject/NetworkIdentity(reader.ReadPackedUInt32())) // because we send/receive the netID, not the GameObject/NetworkIdentity serWorker.Append(serWorker.Create(OpCodes.Ldarg_0)); // this. serWorker.Append(serWorker.Create(OpCodes.Ldarg_0)); serWorker.Append(serWorker.Create(OpCodes.Ldarg_1)); serWorker.Append(serWorker.Create(OpCodes.Callvirt, Weaver.NetworkReaderReadPacked32)); serWorker.Append(serWorker.Create(OpCodes.Ldarg_0)); serWorker.Append(serWorker.Create(OpCodes.Ldflda, syncVar)); if (syncVar.FieldType.FullName == Weaver.gameObjectType.FullName) { serWorker.Append(serWorker.Create(OpCodes.Callvirt, Weaver.getSyncVarGameObjectReference)); } else if (syncVar.FieldType.FullName == Weaver.NetworkIdentityType.FullName) { serWorker.Append(serWorker.Create(OpCodes.Callvirt, Weaver.getSyncVarNetworkIdentityReference)); } serWorker.Append(serWorker.Create(OpCodes.Call, foundMethod)); } } else { MethodReference readFunc = Weaver.GetReadFunc(syncVar.FieldType); if (readFunc == null) { Log.Error("GenerateDeSerialization for " + m_td.Name + " unknown type [" + syncVar.FieldType + "]. UNet [SyncVar] member variables must be basic types."); Weaver.fail = true; return; } if (foundMethod == null) { // just assign value serWorker.Append(serWorker.Create(OpCodes.Ldarg_0)); serWorker.Append(serWorker.Create(OpCodes.Ldarg_1)); serWorker.Append(serWorker.Create(OpCodes.Call, readFunc)); serWorker.Append(serWorker.Create(OpCodes.Stfld, syncVar)); } else { // call hook instead serWorker.Append(serWorker.Create(OpCodes.Ldarg_0)); serWorker.Append(serWorker.Create(OpCodes.Ldarg_1)); serWorker.Append(serWorker.Create(OpCodes.Call, readFunc)); serWorker.Append(serWorker.Create(OpCodes.Call, foundMethod)); } } serWorker.Append(varLabel); dirtyBit += 1; } if (Weaver.generateLogErrors) { serWorker.Append(serWorker.Create(OpCodes.Ldstr, "Injected Deserialize " + m_td.Name)); serWorker.Append(serWorker.Create(OpCodes.Call, Weaver.logErrorReference)); } serWorker.Append(serWorker.Create(OpCodes.Ret)); m_td.Methods.Add(serialize); }
void DeserializeField(FieldDefinition syncVar, ILProcessor serWorker) { // check for Hook function MethodDefinition foundMethod; if (!SyncVarProcessor.CheckForHookFunction(m_td, syncVar, out foundMethod)) { return; } if (syncVar.FieldType.FullName == Weaver.gameObjectType.FullName || syncVar.FieldType.FullName == Weaver.NetworkIdentityType.FullName) { // GameObject/NetworkIdentity SyncVar: // OnSerialize sends writer.Write(go); // OnDeserialize reads to __netId manually so we can use // lookups in the getter (so it still works if objects // move in and out of range repeatedly) FieldDefinition netIdField = m_SyncVarNetIds[syncVar]; if (foundMethod == null) { serWorker.Append(serWorker.Create(OpCodes.Ldarg_0)); serWorker.Append(serWorker.Create(OpCodes.Ldarg_1)); serWorker.Append(serWorker.Create(OpCodes.Callvirt, Weaver.NetworkReaderReadPacked32)); serWorker.Append(serWorker.Create(OpCodes.Stfld, netIdField)); } else { // call Hook(this.GetSyncVarGameObject/NetworkIdentity(reader.ReadPackedUInt32())) // because we send/receive the netID, not the GameObject/NetworkIdentity serWorker.Append(serWorker.Create(OpCodes.Ldarg_0)); // this. serWorker.Append(serWorker.Create(OpCodes.Ldarg_0)); serWorker.Append(serWorker.Create(OpCodes.Ldarg_1)); serWorker.Append(serWorker.Create(OpCodes.Callvirt, Weaver.NetworkReaderReadPacked32)); serWorker.Append(serWorker.Create(OpCodes.Ldarg_0)); serWorker.Append(serWorker.Create(OpCodes.Ldflda, syncVar)); if (syncVar.FieldType.FullName == Weaver.gameObjectType.FullName) { serWorker.Append(serWorker.Create(OpCodes.Callvirt, Weaver.getSyncVarGameObjectReference)); } else if (syncVar.FieldType.FullName == Weaver.NetworkIdentityType.FullName) { serWorker.Append(serWorker.Create(OpCodes.Callvirt, Weaver.getSyncVarNetworkIdentityReference)); } serWorker.Append(serWorker.Create(OpCodes.Call, foundMethod)); } } else { MethodReference readFunc = Weaver.GetReadFunc(syncVar.FieldType); if (readFunc == null) { Weaver.Error("GenerateDeSerialization for " + m_td.Name + " unknown type [" + syncVar.FieldType + "]. Mirror [SyncVar] member variables must be basic types."); return; } if (foundMethod == null) { // just assign value serWorker.Append(serWorker.Create(OpCodes.Ldarg_0)); serWorker.Append(serWorker.Create(OpCodes.Ldarg_1)); serWorker.Append(serWorker.Create(OpCodes.Call, readFunc)); serWorker.Append(serWorker.Create(OpCodes.Stfld, syncVar)); } else { // call hook instead serWorker.Append(serWorker.Create(OpCodes.Ldarg_0)); serWorker.Append(serWorker.Create(OpCodes.Ldarg_1)); serWorker.Append(serWorker.Create(OpCodes.Call, readFunc)); serWorker.Append(serWorker.Create(OpCodes.Call, foundMethod)); } } }
void DeserializeField(FieldDefinition syncVar, ILProcessor serWorker, MethodDefinition deserialize) { // check for Hook function if (!SyncVarProcessor.CheckForHookFunction(netBehaviourSubclass, syncVar, out MethodDefinition foundMethod)) { return; } // [SyncVar] GameObject/NetworkIdentity? /* * Generates code like: * uint num = reader.ReadPackedUInt32(); * if (!SyncVarEqual(num, ref q)) * { * OnSetQ(GetSyncVarGameObject(num, ref q)); * } * ___qNetId = num; */ if (syncVar.FieldType.FullName == Weaver.gameObjectType.FullName || syncVar.FieldType.FullName == Weaver.NetworkIdentityType.FullName) { // GameObject/NetworkIdentity SyncVar: // OnSerialize sends writer.Write(go); // OnDeserialize reads to __netId manually so we can use // lookups in the getter (so it still works if objects // move in and out of range repeatedly) FieldDefinition netIdField = syncVarNetIds[syncVar]; VariableDefinition tmpValue = new VariableDefinition(Weaver.uint32Type); deserialize.Body.Variables.Add(tmpValue); // read id and store in a local variable serWorker.Append(serWorker.Create(OpCodes.Ldarg_1)); serWorker.Append(serWorker.Create(OpCodes.Call, Readers.GetReadFunc(Weaver.uint32Type))); serWorker.Append(serWorker.Create(OpCodes.Stloc, tmpValue)); if (foundMethod != null) { // call Hook(this.GetSyncVarGameObject/NetworkIdentity(reader.ReadPackedUInt32())) // because we send/receive the netID, not the GameObject/NetworkIdentity // but only if SyncVar changed. otherwise a client would // get hook calls for all initial values, even if they // didn't change from the default values on the client. // see also: https://github.com/vis2k/Mirror/issues/1278 // IMPORTANT: for GameObjects/NetworkIdentities we usually // use SyncVarGameObjectEqual to compare equality. // in this case however, we can just use // SyncVarEqual with the two uint netIds. // => this is easier weaver code because we don't // have to get the GameObject/NetworkIdentity // from the uint netId // => this is faster because we void one // GetComponent call for GameObjects to get // their NetworkIdentity when comparing. // Generates: if (!SyncVarEqual); Instruction syncVarEqualLabel = serWorker.Create(OpCodes.Nop); // 'this.' for 'this.SyncVarEqual' serWorker.Append(serWorker.Create(OpCodes.Ldarg_0)); // 'tmpValue' serWorker.Append(serWorker.Create(OpCodes.Ldloc, tmpValue)); // 'ref this.__netId' serWorker.Append(serWorker.Create(OpCodes.Ldarg_0)); serWorker.Append(serWorker.Create(OpCodes.Ldflda, netIdField)); // call the function GenericInstanceMethod syncVarEqualGm = new GenericInstanceMethod(Weaver.syncVarEqualReference); syncVarEqualGm.GenericArguments.Add(netIdField.FieldType); serWorker.Append(serWorker.Create(OpCodes.Call, syncVarEqualGm)); serWorker.Append(serWorker.Create(OpCodes.Brtrue, syncVarEqualLabel)); // call the hook serWorker.Append(serWorker.Create(OpCodes.Ldarg_0)); // this. serWorker.Append(serWorker.Create(OpCodes.Ldarg_0)); serWorker.Append(serWorker.Create(OpCodes.Ldloc, tmpValue)); serWorker.Append(serWorker.Create(OpCodes.Ldarg_0)); serWorker.Append(serWorker.Create(OpCodes.Ldflda, syncVar)); if (syncVar.FieldType.FullName == Weaver.gameObjectType.FullName) { serWorker.Append(serWorker.Create(OpCodes.Callvirt, Weaver.getSyncVarGameObjectReference)); } else if (syncVar.FieldType.FullName == Weaver.NetworkIdentityType.FullName) { serWorker.Append(serWorker.Create(OpCodes.Callvirt, Weaver.getSyncVarNetworkIdentityReference)); } serWorker.Append(serWorker.Create(OpCodes.Call, foundMethod)); // Generates: end if (!SyncVarEqual); serWorker.Append(syncVarEqualLabel); } // set the netid field serWorker.Append(serWorker.Create(OpCodes.Ldarg_0)); serWorker.Append(serWorker.Create(OpCodes.Ldloc, tmpValue)); serWorker.Append(serWorker.Create(OpCodes.Stfld, netIdField)); } // [SyncVar] int/float/struct/etc.? /* * Generates code like: * int num = reader.ReadPackedInt32(); * if (!SyncVarEqual(num, ref a)) * { * OnSetA(num); * } * Networka = num; */ else { MethodReference readFunc = Readers.GetReadFunc(syncVar.FieldType); if (readFunc == null) { Weaver.Error($"{syncVar} has unsupported type. Use a supported Mirror type instead"); return; } VariableDefinition tmpValue = new VariableDefinition(syncVar.FieldType); deserialize.Body.Variables.Add(tmpValue); // read value and put it in a local variable serWorker.Append(serWorker.Create(OpCodes.Ldarg_1)); serWorker.Append(serWorker.Create(OpCodes.Call, readFunc)); serWorker.Append(serWorker.Create(OpCodes.Stloc, tmpValue)); if (foundMethod != null) { // call hook // but only if SyncVar changed. otherwise a client would // get hook calls for all initial values, even if they // didn't change from the default values on the client. // see also: https://github.com/vis2k/Mirror/issues/1278 // Generates: if (!SyncVarEqual); Instruction syncVarEqualLabel = serWorker.Create(OpCodes.Nop); // 'this.' for 'this.SyncVarEqual' serWorker.Append(serWorker.Create(OpCodes.Ldarg_0)); // 'tmpValue' serWorker.Append(serWorker.Create(OpCodes.Ldloc, tmpValue)); // 'ref this.syncVar' serWorker.Append(serWorker.Create(OpCodes.Ldarg_0)); serWorker.Append(serWorker.Create(OpCodes.Ldflda, syncVar)); // call the function GenericInstanceMethod syncVarEqualGm = new GenericInstanceMethod(Weaver.syncVarEqualReference); syncVarEqualGm.GenericArguments.Add(syncVar.FieldType); serWorker.Append(serWorker.Create(OpCodes.Call, syncVarEqualGm)); serWorker.Append(serWorker.Create(OpCodes.Brtrue, syncVarEqualLabel)); // call the hook serWorker.Append(serWorker.Create(OpCodes.Ldarg_0)); serWorker.Append(serWorker.Create(OpCodes.Ldloc, tmpValue)); serWorker.Append(serWorker.Create(OpCodes.Call, foundMethod)); // Generates: end if (!SyncVarEqual); serWorker.Append(syncVarEqualLabel); } // set the property serWorker.Append(serWorker.Create(OpCodes.Ldarg_0)); serWorker.Append(serWorker.Create(OpCodes.Ldloc, tmpValue)); serWorker.Append(serWorker.Create(OpCodes.Stfld, syncVar)); } }