diff --git a/Assets/Mirror/Editor/Weaver/Processors/NetworkBehaviourProcessor.cs b/Assets/Mirror/Editor/Weaver/Processors/NetworkBehaviourProcessor.cs index a85ccbbbf..b0f0e2195 100644 --- a/Assets/Mirror/Editor/Weaver/Processors/NetworkBehaviourProcessor.cs +++ b/Assets/Mirror/Editor/Weaver/Processors/NetworkBehaviourProcessor.cs @@ -424,113 +424,151 @@ void DeserializeField(FieldDefinition syncVar, ILProcessor serWorker, MethodDefi // check for Hook function MethodDefinition hookMethod = SyncVarProcessor.GetHookMethod(netBehaviourSubclass, syncVar); - // [SyncVar] GameObject/NetworkIdentity? - /* - 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); - } - */ - if (syncVar.FieldType.FullName == Weaver.gameObjectType.FullName || - syncVar.FieldType.FullName == Weaver.NetworkIdentityType.FullName) + if (IsNetworkIdentityField(syncVar)) { - // 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. - // put 'this.' onto stack for 'this.netId' below - serWorker.Append(serWorker.Create(OpCodes.Ldarg_0)); - // reader. for 'reader.Read()' below - serWorker.Append(serWorker.Create(OpCodes.Ldarg_1)); - // Read() - serWorker.Append(serWorker.Create(OpCodes.Call, Readers.GetReadFunc(Weaver.uint32Type))); - // netId - serWorker.Append(serWorker.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 = 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 - // this. - serWorker.Append(serWorker.Create(OpCodes.Ldarg_0)); - // oldSyncVar GO/NI - serWorker.Append(serWorker.Create(OpCodes.Ldloc, oldSyncVar)); - // this. - serWorker.Append(serWorker.Create(OpCodes.Ldarg_0)); - // syncvar.get (finds current GO/NI from netId) - serWorker.Append(serWorker.Create(OpCodes.Ldfld, syncVar)); - serWorker.Append(serWorker.Create(OpCodes.Callvirt, hookMethod)); - - // Generates: end if (!SyncVarEqual); - serWorker.Append(syncVarEqualLabel); - } + DeserializeNetworkIdentityField(syncVar, serWorker, deserialize, hookMethod); } - // [SyncVar] int/float/struct/etc.? + else + { + DeserializeNormalField(syncVar, serWorker, deserialize, hookMethod); + } + } + + /// + /// Is the field a NetworkIdentity or GameObject + /// + /// + /// + static bool IsNetworkIdentityField(FieldDefinition syncVar) + { + return syncVar.FieldType.FullName == Weaver.gameObjectType.FullName || + syncVar.FieldType.FullName == Weaver.NetworkIdentityType.FullName; + } + + /// + /// [SyncVar] GameObject/NetworkIdentity? + /// + /// + /// + /// + /// + /// + void DeserializeNetworkIdentityField(FieldDefinition syncVar, ILProcessor serWorker, 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(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. + // put 'this.' onto stack for 'this.netId' below + serWorker.Append(serWorker.Create(OpCodes.Ldarg_0)); + // reader. for 'reader.Read()' below + serWorker.Append(serWorker.Create(OpCodes.Ldarg_1)); + // Read() + serWorker.Append(serWorker.Create(OpCodes.Call, Readers.GetReadFunc(Weaver.uint32Type))); + // netId + serWorker.Append(serWorker.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 = 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 + // this. + serWorker.Append(serWorker.Create(OpCodes.Ldarg_0)); + // oldSyncVar GO/NI + serWorker.Append(serWorker.Create(OpCodes.Ldloc, oldSyncVar)); + // this. + serWorker.Append(serWorker.Create(OpCodes.Ldarg_0)); + // syncvar.get (finds current GO/NI from netId) + serWorker.Append(serWorker.Create(OpCodes.Ldfld, syncVar)); + serWorker.Append(serWorker.Create(OpCodes.Callvirt, hookMethod)); + + // Generates: end if (!SyncVarEqual); + serWorker.Append(syncVarEqualLabel); + } + } + + /// + /// [SyncVar] int/float/struct/etc.? + /// + /// + /// + /// + /// + /// + void DeserializeNormalField(FieldDefinition syncVar, ILProcessor serWorker, MethodDefinition deserialize, MethodDefinition hookMethod) + { /* Generates code like: // for hook @@ -541,78 +579,76 @@ void DeserializeField(FieldDefinition syncVar, ILProcessor serWorker, MethodDefi OnSetA(oldValue, Networka); } */ - else + + MethodReference readFunc = Readers.GetReadFunc(syncVar.FieldType); + if (readFunc == null) { - MethodReference readFunc = Readers.GetReadFunc(syncVar.FieldType); - if (readFunc == null) - { - Weaver.Error($"{syncVar.Name} has unsupported type. Use a supported Mirror type instead", syncVar); - return; - } + 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); + // 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(Weaver.syncVarEqualReference); + syncVarEqualGm.GenericArguments.Add(syncVar.FieldType); + serWorker.Append(serWorker.Create(OpCodes.Call, syncVarEqualGm)); + serWorker.Append(serWorker.Create(OpCodes.Brtrue, syncVarEqualLabel)); + + // call the hook + // this. + serWorker.Append(serWorker.Create(OpCodes.Ldarg_0)); + // oldvalue + serWorker.Append(serWorker.Create(OpCodes.Ldloc, oldValue)); + // this. + serWorker.Append(serWorker.Create(OpCodes.Ldarg_0)); + // syncvar.get serWorker.Append(serWorker.Create(OpCodes.Ldfld, syncVar)); - serWorker.Append(serWorker.Create(OpCodes.Stloc, oldValue)); + serWorker.Append(serWorker.Create(OpCodes.Callvirt, hookMethod)); - // 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(Weaver.syncVarEqualReference); - syncVarEqualGm.GenericArguments.Add(syncVar.FieldType); - serWorker.Append(serWorker.Create(OpCodes.Call, syncVarEqualGm)); - serWorker.Append(serWorker.Create(OpCodes.Brtrue, syncVarEqualLabel)); - - // call the hook - // this. - serWorker.Append(serWorker.Create(OpCodes.Ldarg_0)); - // oldvalue - serWorker.Append(serWorker.Create(OpCodes.Ldloc, oldValue)); - // this. - serWorker.Append(serWorker.Create(OpCodes.Ldarg_0)); - // syncvar.get - serWorker.Append(serWorker.Create(OpCodes.Ldfld, syncVar)); - serWorker.Append(serWorker.Create(OpCodes.Callvirt, hookMethod)); - - // Generates: end if (!SyncVarEqual); - serWorker.Append(syncVarEqualLabel); - } + // Generates: end if (!SyncVarEqual); + serWorker.Append(syncVarEqualLabel); } }