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); } } }
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 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 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, 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)); } }