static void GenerateDeSerialization(TypeDefinition td) { Weaver.DLog(td, " GenerateDeserialization"); foreach (MethodDefinition m in td.Methods) { if (m.Name == "Deserialize") { return; } } if (td.Fields.Count == 0) { return; } MethodDefinition serializeFunc = new MethodDefinition("Deserialize", MethodAttributes.Public | MethodAttributes.Virtual | MethodAttributes.HideBySig, Weaver.voidType); serializeFunc.Parameters.Add(new ParameterDefinition("reader", ParameterAttributes.None, Weaver.CurrentAssembly.MainModule.ImportReference(Weaver.NetworkReaderType))); ILProcessor serWorker = serializeFunc.Body.GetILProcessor(); // call base MethodReference baseDeserialize = Resolvers.ResolveMethodInParents(td.BaseType, Weaver.CurrentAssembly, "Deserialize"); if (baseDeserialize != null) { serWorker.Append(serWorker.Create(OpCodes.Ldarg_0)); // base serWorker.Append(serWorker.Create(OpCodes.Ldarg_1)); // writer serWorker.Append(serWorker.Create(OpCodes.Call, baseDeserialize)); } foreach (FieldDefinition field in td.Fields) { if (field.IsStatic || field.IsPrivate || field.IsSpecialName) { continue; } MethodReference readerFunc = Readers.GetReadFunc(field.FieldType); if (readerFunc != null) { serWorker.Append(serWorker.Create(OpCodes.Ldarg_0)); serWorker.Append(serWorker.Create(OpCodes.Ldarg_1)); serWorker.Append(serWorker.Create(OpCodes.Call, readerFunc)); serWorker.Append(serWorker.Create(OpCodes.Stfld, field)); } else { Weaver.Error($"{field} has unsupported type"); return; } } serWorker.Append(serWorker.Create(OpCodes.Ret)); td.Methods.Add(serializeFunc); }
public static bool ReadArguments(MethodDefinition method, Readers readers, Logger Log, ILProcessor worker, RemoteCallType callType, ref bool WeavingFailed) { // read each argument // example result /* * CallCmdDoSomething(reader.ReadPackedInt32(), reader.ReadNetworkIdentity()); */ bool skipFirst = callType == RemoteCallType.TargetRpc && TargetRpcProcessor.HasNetworkConnectionParameter(method); // arg of calling function, arg 0 is "this" so start counting at 1 int argNum = 1; foreach (ParameterDefinition param in method.Parameters) { // NetworkConnection is not sent via the NetworkWriter so skip it here // skip first for NetworkConnection in TargetRpc if (argNum == 1 && skipFirst) { argNum += 1; continue; } // skip SenderConnection in Command if (IsSenderConnection(param, callType)) { argNum += 1; continue; } MethodReference readFunc = readers.GetReadFunc(param.ParameterType, ref WeavingFailed); if (readFunc == null) { Log.Error($"{method.Name} has invalid parameter {param}. Unsupported type {param.ParameterType}, use a supported Mirror type instead", method); WeavingFailed = true; return(false); } worker.Emit(OpCodes.Ldarg_1); worker.Emit(OpCodes.Call, readFunc); // conversion.. is this needed? if (param.ParameterType.Is <float>()) { worker.Emit(OpCodes.Conv_R4); } else if (param.ParameterType.Is <double>()) { worker.Emit(OpCodes.Conv_R8); } } return(true); }
public bool ReadArguments(MethodDefinition method, ILProcessor worker, bool skipFirst) { // read each argument // example result /* * CallCmdDoSomething(reader.ReadPackedInt32(), reader.ReadNetworkIdentity()); */ // arg of calling function, arg 0 is "this" so start counting at 1 int argNum = 1; foreach (ParameterDefinition param in method.Parameters) { // NetworkConnection is not sent via the NetworkWriter so skip it here // skip first for NetworkConnection in TargetRpc if (argNum == 1 && skipFirst) { argNum += 1; continue; } // skip SenderConnection in ServerRpc if (IsNetworkConnection(param.ParameterType)) { argNum += 1; continue; } SequencePoint sequencePoint = method.DebugInformation.SequencePoints.ElementAtOrDefault(0); MethodReference readFunc = readers.GetReadFunc(param.ParameterType, sequencePoint); if (readFunc == null) { logger.Error($"{method.Name} has invalid parameter {param}. Unsupported type {param.ParameterType}, use a supported MirrorNG type instead", method); return(false); } worker.Append(worker.Create(OpCodes.Ldarg_1)); worker.Append(worker.Create(OpCodes.Call, readFunc)); // conversion.. is this needed? if (param.ParameterType.Is <float>()) { worker.Append(worker.Create(OpCodes.Conv_R4)); } else if (param.ParameterType.Is <double>()) { worker.Append(worker.Create(OpCodes.Conv_R8)); } } return(true); }
private static void LoadMessageReadWriter(ModuleDefinition module, TypeDefinition klass) { if (!klass.IsAbstract && !klass.IsInterface && klass.ImplementsInterface <NetworkMessage>()) { Readers.GetReadFunc(module.ImportReference(klass)); Writers.GetWriteFunc(module.ImportReference(klass)); } foreach (TypeDefinition td in klass.NestedTypes) { LoadMessageReadWriter(module, td); } }
static void GenerateDeSerialization(TypeDefinition td) { Weaver.DLog(td, " GenerateDeserialization"); foreach (MethodDefinition m in td.Methods) { if (m.Name == "Deserialize") { return; } } if (td.Fields.Count == 0) { return; } MethodDefinition serializeFunc = new MethodDefinition("Deserialize", MethodAttributes.Public | MethodAttributes.Virtual | MethodAttributes.HideBySig, Weaver.voidType); serializeFunc.Parameters.Add(new ParameterDefinition("reader", ParameterAttributes.None, Weaver.CurrentAssembly.MainModule.ImportReference(Weaver.NetworkReaderType))); ILProcessor serWorker = serializeFunc.Body.GetILProcessor(); foreach (FieldDefinition field in td.Fields) { if (field.IsStatic || field.IsPrivate || field.IsSpecialName) { continue; } MethodReference readerFunc = Readers.GetReadFunc(field.FieldType); if (readerFunc != null) { serWorker.Append(serWorker.Create(OpCodes.Ldarg_0)); serWorker.Append(serWorker.Create(OpCodes.Ldarg_1)); serWorker.Append(serWorker.Create(OpCodes.Call, readerFunc)); serWorker.Append(serWorker.Create(OpCodes.Stfld, field)); } else { Weaver.Error("GenerateDeSerialization for " + td.Name + " unknown type [" + field.FieldType + "]. [SyncVar] member variables must be basic types."); return; } } serWorker.Append(serWorker.Create(OpCodes.Ret)); td.Methods.Add(serializeFunc); }
private static void CallReader(ILProcessor serWorker, FieldDefinition field) { MethodReference readerFunc = Readers.GetReadFunc(field.FieldType); if (readerFunc != null) { serWorker.Append(serWorker.Create(OpCodes.Ldarg_0)); serWorker.Append(serWorker.Create(OpCodes.Ldarg_1)); serWorker.Append(serWorker.Create(OpCodes.Call, readerFunc)); serWorker.Append(serWorker.Create(OpCodes.Stfld, field)); } else { Weaver.Error($"{field.Name} has unsupported type", field); } }
static bool LoadMessageReadWriter(ModuleDefinition module, TypeDefinition klass) { bool modified = false; if (!klass.IsAbstract && !klass.IsInterface && klass.ImplementsInterface <NetworkMessage>()) { Readers.GetReadFunc(module.ImportReference(klass)); Writers.GetWriteFunc(module.ImportReference(klass)); modified = true; } foreach (TypeDefinition td in klass.NestedTypes) { modified |= LoadMessageReadWriter(module, td); } return(modified); }
public static bool ReadArguments(MethodDefinition method, ILProcessor worker, bool skipFirst) { // read each argument // example result /* * CallCmdDoSomething(reader.ReadPackedInt32(), reader.ReadNetworkIdentity()); */ // arg of calling function, arg 0 is "this" so start counting at 1 int argNum = 1; foreach (ParameterDefinition param in method.Parameters) { // NetworkConnection is not sent via the NetworkWriter so skip it here // skip first for NetworkConnection in TargetRpc if (argNum == 1 && skipFirst) { argNum += 1; continue; } MethodReference readFunc = Readers.GetReadFunc(param.ParameterType); if (readFunc == null) { Weaver.Error($"{method.Name} has invalid parameter {param}. Unsupported type {param.ParameterType}, use a supported Mirror type instead", method); return(false); } worker.Append(worker.Create(OpCodes.Ldarg_1)); worker.Append(worker.Create(OpCodes.Call, readFunc)); // conversion.. is this needed? if (param.ParameterType.FullName == Weaver.singleType.FullName) { worker.Append(worker.Create(OpCodes.Conv_R4)); } else if (param.ParameterType.FullName == Weaver.doubleType.FullName) { worker.Append(worker.Create(OpCodes.Conv_R8)); } } return(true); }
static bool GenerateDeserialization(string methodName, TypeDefinition td, TypeReference itemType, TypeReference mirrorBaseType) { Weaver.DLog(td, " GenerateDeserialization"); bool existing = td.HasMethodInBaseType(methodName, mirrorBaseType); if (existing) { return(true); } // this check needs to happen inside GenerateDeserialization because // we need to check if user has made custom function above if (itemType.IsGenericInstance) { Weaver.Error($"Can not create Serialize or Deserialize for generic element in {td.Name}. Override virtual methods with custom Serialize and Deserialize to use {itemType.Name} in SyncList", td); return(false); } var deserializeFunction = new MethodDefinition(methodName, MethodAttributes.Public | MethodAttributes.Virtual | MethodAttributes.Public | MethodAttributes.HideBySig, itemType); deserializeFunction.Parameters.Add(new ParameterDefinition("reader", ParameterAttributes.None, WeaverTypes.Import <NetworkReader>())); ILProcessor worker = deserializeFunction.Body.GetILProcessor(); MethodReference readerFunc = Readers.GetReadFunc(itemType); if (readerFunc != null) { worker.Append(worker.Create(OpCodes.Ldarg_1)); worker.Append(worker.Create(OpCodes.Call, readerFunc)); worker.Append(worker.Create(OpCodes.Ret)); } else { Weaver.Error($"{td.Name} has sync object generic type {itemType.Name}. Use a type supported by mirror instead", td); return(false); } td.Methods.Add(deserializeFunction); return(true); }
/// <summary> /// Generates serialization methods for synclists /// </summary> /// <param name="td">The synclist class</param> /// <param name="mirrorBaseType">the base SyncObject td inherits from</param> static void GenerateReadersAndWriters(TypeReference tr) { if (tr is GenericInstanceType genericInstance) { foreach (TypeReference argument in genericInstance.GenericArguments) { if (!argument.IsGenericParameter) { Readers.GetReadFunc(argument); Writers.GetWriteFunc(argument); } } } if (tr != null) { GenerateReadersAndWriters(tr.Resolve().BaseType); } }
// Generates serialization methods for synclists static void GenerateReadersAndWriters(Writers writers, Readers readers, TypeReference tr, ref bool WeavingFailed) { if (tr is GenericInstanceType genericInstance) { foreach (TypeReference argument in genericInstance.GenericArguments) { if (!argument.IsGenericParameter) { readers.GetReadFunc(argument, ref WeavingFailed); writers.GetWriteFunc(argument, ref WeavingFailed); } } } if (tr != null) { GenerateReadersAndWriters(writers, readers, tr.Resolve().BaseType, ref WeavingFailed); } }
private static MethodReference GenerateDeserialization(string methodName, TypeDefinition td, TypeReference itemType) { Weaver.DLog(td, " GenerateDeserialization"); foreach (var m in td.Methods) { if (m.Name == methodName) { return(m); } } var deserializeFunction = new MethodDefinition(methodName, MethodAttributes.Public | MethodAttributes.Virtual | MethodAttributes.Public | MethodAttributes.HideBySig, itemType); deserializeFunction.Parameters.Add(new ParameterDefinition("reader", ParameterAttributes.None, Weaver.CurrentAssembly.MainModule.ImportReference(Weaver.NetworkReaderType))); var serWorker = deserializeFunction.Body.GetILProcessor(); var readerFunc = Readers.GetReadFunc(itemType); if (readerFunc != null) { serWorker.Append(serWorker.Create(OpCodes.Ldarg_1)); serWorker.Append(serWorker.Create(OpCodes.Call, readerFunc)); serWorker.Append(serWorker.Create(OpCodes.Ret)); } else { Weaver.Error($"{td} cannot have item of type {itemType}. Use a type supported by mirror instead"); return(null); } td.Methods.Add(deserializeFunction); return(deserializeFunction); }
public static bool ProcessNetworkReaderParameters(MethodDefinition md, ILProcessor worker, bool skipFirst) { var count = 0; // read cmd args from NetworkReader foreach (var arg in md.Parameters) { if (count++ == 0 && skipFirst) { continue; } var readFunc = Readers.GetReadFunc(arg.ParameterType); //? if (readFunc != null) { worker.Append(worker.Create(OpCodes.Ldarg_1)); worker.Append(worker.Create(OpCodes.Call, readFunc)); // conversion.. is this needed? if (arg.ParameterType.FullName == Weaver.singleType.FullName) { worker.Append(worker.Create(OpCodes.Conv_R4)); } else if (arg.ParameterType.FullName == Weaver.doubleType.FullName) { worker.Append(worker.Create(OpCodes.Conv_R8)); } } else { Weaver.Error( $"{md} has invalid parameter {arg}. Unsupported type {arg.ParameterType}, use a supported Mirror type instead"); return(false); } } return(true); }
static MethodReference GenerateDeserialization(string methodName, TypeDefinition td, TypeReference itemType) { Weaver.DLog(td, " GenerateDeserialization"); foreach (MethodDefinition m in td.Methods) { if (m.Name == methodName) { return(m); } } MethodDefinition deserializeFunction = new MethodDefinition(methodName, MethodAttributes.Public | MethodAttributes.Virtual | MethodAttributes.Public | MethodAttributes.HideBySig, itemType); deserializeFunction.Parameters.Add(new ParameterDefinition("reader", ParameterAttributes.None, Weaver.CurrentAssembly.MainModule.ImportReference(Weaver.NetworkReaderType))); ILProcessor serWorker = deserializeFunction.Body.GetILProcessor(); MethodReference readerFunc = Readers.GetReadFunc(itemType); if (readerFunc != null) { serWorker.Append(serWorker.Create(OpCodes.Ldarg_1)); serWorker.Append(serWorker.Create(OpCodes.Call, readerFunc)); serWorker.Append(serWorker.Create(OpCodes.Ret)); } else { Weaver.Error("GenerateDeserialization for " + td.Name + " unknown type [" + itemType + "]. Member variables must be basic types."); return(null); } td.Methods.Add(deserializeFunction); return(deserializeFunction); }
private void GenerateReadersWriters(TypeReference parameterType, SequencePoint sequencePoint) { if (!parameterType.IsGenericParameter && parameterType.CanBeResolved()) { TypeDefinition typeDefinition = parameterType.Resolve(); if (typeDefinition.IsClass && !typeDefinition.IsValueType) { MethodDefinition constructor = typeDefinition.GetMethod(".ctor"); bool hasAccess = constructor.IsPublic || constructor.IsAssembly && typeDefinition.Module == module; if (!hasAccess) { return; } } writers.GetWriteFunc(parameterType, sequencePoint); readers.GetReadFunc(parameterType, sequencePoint); } }
private static void GenerateReadersWriters(ModuleDefinition module, TypeReference parameterType) { if (!parameterType.IsGenericParameter && parameterType.CanBeResolved()) { TypeDefinition typeDefinition = parameterType.Resolve(); if (typeDefinition.IsClass && !typeDefinition.IsValueType) { MethodDefinition constructor = typeDefinition.GetMethod(".ctor"); bool hasAccess = constructor.IsPublic || constructor.IsAssembly && typeDefinition.Module == module; if (!hasAccess) { return; } } parameterType = module.ImportReference(parameterType); Writers.GetWriteFunc(parameterType); Readers.GetReadFunc(parameterType); } }
public static bool ProcessNetworkReaderParameters(TypeDefinition td, MethodDefinition md, ILProcessor worker, bool skipFirst) { int count = 0; // read cmd args from NetworkReader foreach (ParameterDefinition arg in md.Parameters) { if (count++ == 0 && skipFirst) { continue; } MethodReference readFunc = Readers.GetReadFunc(arg.ParameterType); //? if (readFunc != null) { worker.Append(worker.Create(OpCodes.Ldarg_1)); worker.Append(worker.Create(OpCodes.Call, readFunc)); // conversion.. is this needed? if (arg.ParameterType.FullName == Weaver.singleType.FullName) { worker.Append(worker.Create(OpCodes.Conv_R4)); } else if (arg.ParameterType.FullName == Weaver.doubleType.FullName) { worker.Append(worker.Create(OpCodes.Conv_R8)); } } else { Weaver.Error("ProcessNetworkReaderParameters for " + td.Name + ":" + md.Name + " type " + arg.ParameterType + " not supported"); return(false); } } return(true); }
void GenerateDeSerialization() { Weaver.DLog(netBehaviourSubclass, " GenerateDeSerialization"); foreach (MethodDefinition m in netBehaviourSubclass.Methods) { if (m.Name == "OnDeserialize") { return; } } if (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.CurrentAssembly.MainModule.ImportReference(Weaver.NetworkReaderType))); serialize.Parameters.Add(new ParameterDefinition("initialState", ParameterAttributes.None, Weaver.boolType)); ILProcessor serWorker = serialize.Body.GetILProcessor(); // setup local for dirty bits serialize.Body.InitLocals = true; VariableDefinition dirtyBitsLocal = new VariableDefinition(Weaver.int64Type); serialize.Body.Variables.Add(dirtyBitsLocal); MethodReference baseDeserialize = Resolvers.ResolveMethodInParents(netBehaviourSubclass.BaseType, Weaver.CurrentAssembly, "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 syncVars) { DeserializeField(syncVar, serWorker, serialize); } serWorker.Append(serWorker.Create(OpCodes.Ret)); // Generates: end if (initialState); serWorker.Append(initialStateLabel); // get dirty bits serWorker.Append(serWorker.Create(OpCodes.Ldarg_1)); serWorker.Append(serWorker.Create(OpCodes.Call, Readers.GetReadFunc(Weaver.uint64Type))); serWorker.Append(serWorker.Create(OpCodes.Stloc_0)); // conditionally read each syncvar int dirtyBit = Weaver.GetSyncVarStart(netBehaviourSubclass.BaseType.FullName); // start at number of syncvars in parent foreach (FieldDefinition syncVar in 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)); DeserializeField(syncVar, serWorker, serialize); serWorker.Append(varLabel); dirtyBit += 1; } if (Weaver.GenerateLogErrors) { serWorker.Append(serWorker.Create(OpCodes.Ldstr, "Injected Deserialize " + netBehaviourSubclass.Name)); serWorker.Append(serWorker.Create(OpCodes.Call, Weaver.logErrorReference)); } serWorker.Append(serWorker.Create(OpCodes.Ret)); netBehaviourSubclass.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 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 DeserializeField(FieldDefinition syncVar, ILProcessor worker, ref bool WeavingFailed) { // put 'this.' onto stack for 'this.syncvar' below worker.Append(worker.Create(OpCodes.Ldarg_0)); // push 'ref T this.field' worker.Emit(OpCodes.Ldarg_0); // if the netbehaviour class is generic, we need to make the field reference generic as well for correct IL if (netBehaviourSubclass.HasGenericParameters) { worker.Emit(OpCodes.Ldflda, syncVar.MakeHostInstanceGeneric()); } else { worker.Emit(OpCodes.Ldflda, syncVar); } // hook? then push 'new Action<T,T>(Hook)' onto stack MethodDefinition hookMethod = syncVarAttributeProcessor.GetHookMethod(netBehaviourSubclass, syncVar, ref WeavingFailed); if (hookMethod != null) { syncVarAttributeProcessor.GenerateNewActionFromHookMethod(syncVar, worker, hookMethod); } // otherwise push 'null' as hook else { worker.Emit(OpCodes.Ldnull); } // call GeneratedSyncVarDeserialize<T>. // special cases for GameObject/NetworkIdentity/NetworkBehaviour // passing netId too for persistence. if (syncVar.FieldType.Is <UnityEngine.GameObject>()) { // reader worker.Emit(OpCodes.Ldarg_1); // GameObject setter needs one more parameter: netId field ref FieldDefinition netIdField = syncVarNetIds[syncVar]; worker.Emit(OpCodes.Ldarg_0); worker.Emit(OpCodes.Ldflda, netIdField); worker.Emit(OpCodes.Call, weaverTypes.generatedSyncVarDeserialize_GameObject); } else if (syncVar.FieldType.Is <NetworkIdentity>()) { // reader worker.Emit(OpCodes.Ldarg_1); // NetworkIdentity deserialize needs one more parameter: netId field ref FieldDefinition netIdField = syncVarNetIds[syncVar]; worker.Emit(OpCodes.Ldarg_0); worker.Emit(OpCodes.Ldflda, netIdField); worker.Emit(OpCodes.Call, weaverTypes.generatedSyncVarDeserialize_NetworkIdentity); } // TODO this only uses the persistent netId for types DERIVED FROM NB. // not if the type is just 'NetworkBehaviour'. // this is what original implementation did too. fix it after. else if (syncVar.FieldType.IsDerivedFrom <NetworkBehaviour>()) { // reader worker.Emit(OpCodes.Ldarg_1); // NetworkIdentity deserialize needs one more parameter: netId field ref // (actually its a NetworkBehaviourSyncVar type) FieldDefinition netIdField = syncVarNetIds[syncVar]; worker.Emit(OpCodes.Ldarg_0); worker.Emit(OpCodes.Ldflda, netIdField); // make generic version of GeneratedSyncVarSetter_NetworkBehaviour<T> MethodReference getFunc = weaverTypes.generatedSyncVarDeserialize_NetworkBehaviour_T.MakeGeneric(assembly.MainModule, syncVar.FieldType); worker.Emit(OpCodes.Call, getFunc); } else { // T value = reader.ReadT(); // this is still in IL because otherwise weaver generated // readers/writers don't seem to work in tests. // besides, this also avoids reader.Read<T> overhead. MethodReference readFunc = readers.GetReadFunc(syncVar.FieldType, ref WeavingFailed); if (readFunc == null) { Log.Error($"{syncVar.Name} has unsupported type. Use a supported Mirror type instead", syncVar); WeavingFailed = true; return; } // reader. for 'reader.Read()' below worker.Emit(OpCodes.Ldarg_1); // reader.Read() worker.Emit(OpCodes.Call, readFunc); // make generic version of GeneratedSyncVarDeserialize<T> MethodReference generic = weaverTypes.generatedSyncVarDeserialize.MakeGeneric(assembly.MainModule, syncVar.FieldType); worker.Emit(OpCodes.Call, generic); } }
void GenerateDeSerialization() { Weaver.DLog(netBehaviourSubclass, " GenerateDeSerialization"); const string DeserializeMethodName = "DeserializeSyncVars"; if (netBehaviourSubclass.GetMethod(DeserializeMethodName) != null) { return; } if (syncVars.Count == 0) { // no synvars, no need for custom OnDeserialize return; } var serialize = new MethodDefinition(DeserializeMethodName, MethodAttributes.Public | MethodAttributes.Virtual | MethodAttributes.HideBySig, WeaverTypes.Import(typeof(void))); serialize.Parameters.Add(new ParameterDefinition("reader", ParameterAttributes.None, WeaverTypes.Import <NetworkReader>())); serialize.Parameters.Add(new ParameterDefinition("initialState", ParameterAttributes.None, WeaverTypes.Import <bool>())); ILProcessor serWorker = serialize.Body.GetILProcessor(); // setup local for dirty bits serialize.Body.InitLocals = true; var dirtyBitsLocal = new VariableDefinition(WeaverTypes.Import <long>()); serialize.Body.Variables.Add(dirtyBitsLocal); MethodReference baseDeserialize = Resolvers.TryResolveMethodInParents(netBehaviourSubclass.BaseType, Weaver.CurrentAssembly, DeserializeMethodName); if (baseDeserialize != null) { // base serWorker.Append(serWorker.Create(OpCodes.Ldarg_0)); // reader serWorker.Append(serWorker.Create(OpCodes.Ldarg_1)); // initialState serWorker.Append(serWorker.Create(OpCodes.Ldarg_2)); 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 syncVars) { DeserializeField(syncVar, serWorker, serialize); } serWorker.Append(serWorker.Create(OpCodes.Ret)); // Generates: end if (initialState); serWorker.Append(initialStateLabel); // get dirty bits serWorker.Append(serWorker.Create(OpCodes.Ldarg_1)); serWorker.Append(serWorker.Create(OpCodes.Call, Readers.GetReadFunc(WeaverTypes.Import <ulong>()))); serWorker.Append(serWorker.Create(OpCodes.Stloc_0)); // conditionally read each syncvar // start at number of syncvars in parent int dirtyBit = Weaver.GetSyncVarStart(netBehaviourSubclass.BaseType.FullName); foreach (FieldDefinition syncVar in 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)); DeserializeField(syncVar, serWorker, serialize); serWorker.Append(varLabel); dirtyBit += 1; } serWorker.Append(serWorker.Create(OpCodes.Ret)); netBehaviourSubclass.Methods.Add(serialize); }
static void GenerateDeSerialization(TypeDefinition td) { Weaver.DLog(td, " GenerateDeserialization"); MethodDefinition existingMethod = td.Methods.FirstOrDefault(md => md.Name == "Deserialize"); if (existingMethod != null && !existingMethod.Body.IsEmptyDefault()) { return; } if (td.Fields.Count == 0) { return; } MethodDefinition serializeFunc = existingMethod ?? new MethodDefinition("Deserialize", MethodAttributes.Public | MethodAttributes.Virtual | MethodAttributes.HideBySig, Weaver.voidType); if (existingMethod == null) //only add to new method { serializeFunc.Parameters.Add(new ParameterDefinition("reader", ParameterAttributes.None, Weaver.CurrentAssembly.MainModule.ImportReference(Weaver.NetworkReaderType))); } ILProcessor serWorker = serializeFunc.Body.GetILProcessor(); if (existingMethod != null) { serWorker.Body.Instructions.Clear(); //remove default nop&ret from existing empty interface method } if (!td.IsValueType) //if not struct(IMessageBase), likely same as using else {} here in all cases { // call base MethodReference baseDeserialize = Resolvers.ResolveMethodInParents(td.BaseType, Weaver.CurrentAssembly, "Deserialize"); if (baseDeserialize != null) { serWorker.Append(serWorker.Create(OpCodes.Ldarg_0)); // base serWorker.Append(serWorker.Create(OpCodes.Ldarg_1)); // writer serWorker.Append(serWorker.Create(OpCodes.Call, baseDeserialize)); } } foreach (FieldDefinition field in td.Fields) { if (field.IsStatic || field.IsPrivate || field.IsSpecialName) { continue; } MethodReference readerFunc = Readers.GetReadFunc(field.FieldType); if (readerFunc != null) { serWorker.Append(serWorker.Create(OpCodes.Ldarg_0)); serWorker.Append(serWorker.Create(OpCodes.Ldarg_1)); serWorker.Append(serWorker.Create(OpCodes.Call, readerFunc)); serWorker.Append(serWorker.Create(OpCodes.Stfld, field)); } else { Weaver.Error($"{field} has unsupported type"); return; } } serWorker.Append(serWorker.Create(OpCodes.Ret)); if (existingMethod == null) //only add if not just replaced body { td.Methods.Add(serializeFunc); } }
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)); } }