static MethodDefinition GenerateArrayReadFunc(TypeReference variable, int recursionCount) { if (!variable.IsArrayType()) { Weaver.Error($"{variable} is an unsupported type. Jagged and multidimensional arrays are not supported"); return(null); } TypeReference elementType = variable.GetElementType(); MethodReference elementReadFunc = GetReadFunc(elementType, recursionCount + 1); if (elementReadFunc == null) { return(null); } string functionName = "_ReadArray" + variable.GetElementType().Name + "_"; if (variable.DeclaringType != null) { functionName += variable.DeclaringType.Name; } else { functionName += "None"; } // create new reader for this type MethodDefinition readerFunc = new MethodDefinition(functionName, MethodAttributes.Public | MethodAttributes.Static | MethodAttributes.HideBySig, variable); readerFunc.Parameters.Add(new ParameterDefinition("reader", ParameterAttributes.None, Weaver.CurrentAssembly.MainModule.ImportReference(Weaver.NetworkReaderType))); readerFunc.Body.Variables.Add(new VariableDefinition(Weaver.int32Type)); readerFunc.Body.Variables.Add(new VariableDefinition(variable)); readerFunc.Body.Variables.Add(new VariableDefinition(Weaver.int32Type)); readerFunc.Body.InitLocals = true; ILProcessor worker = readerFunc.Body.GetILProcessor(); // int length = reader.ReadPackedInt32(); worker.Append(worker.Create(OpCodes.Ldarg_0)); worker.Append(worker.Create(OpCodes.Call, GetReadFunc(Weaver.int32Type))); worker.Append(worker.Create(OpCodes.Stloc_0)); // if (length < 0) { // return null // } worker.Append(worker.Create(OpCodes.Ldloc_0)); worker.Append(worker.Create(OpCodes.Ldc_I4_0)); Instruction labelEmptyArray = worker.Create(OpCodes.Nop); worker.Append(worker.Create(OpCodes.Bge, labelEmptyArray)); // return null worker.Append(worker.Create(OpCodes.Ldnull)); worker.Append(worker.Create(OpCodes.Ret)); worker.Append(labelEmptyArray); // T value = new T[length]; worker.Append(worker.Create(OpCodes.Ldloc_0)); worker.Append(worker.Create(OpCodes.Newarr, variable.GetElementType())); worker.Append(worker.Create(OpCodes.Stloc_1)); // for (int i=0; i< length ; i++) { worker.Append(worker.Create(OpCodes.Ldc_I4_0)); worker.Append(worker.Create(OpCodes.Stloc_2)); Instruction labelHead = worker.Create(OpCodes.Nop); worker.Append(worker.Create(OpCodes.Br, labelHead)); // loop body Instruction labelBody = worker.Create(OpCodes.Nop); worker.Append(labelBody); // value[i] = reader.ReadT(); worker.Append(worker.Create(OpCodes.Ldloc_1)); worker.Append(worker.Create(OpCodes.Ldloc_2)); worker.Append(worker.Create(OpCodes.Ldelema, variable.GetElementType())); worker.Append(worker.Create(OpCodes.Ldarg_0)); worker.Append(worker.Create(OpCodes.Call, elementReadFunc)); worker.Append(worker.Create(OpCodes.Stobj, variable.GetElementType())); worker.Append(worker.Create(OpCodes.Ldloc_2)); worker.Append(worker.Create(OpCodes.Ldc_I4_1)); worker.Append(worker.Create(OpCodes.Add)); worker.Append(worker.Create(OpCodes.Stloc_2)); // loop while check worker.Append(labelHead); worker.Append(worker.Create(OpCodes.Ldloc_2)); worker.Append(worker.Create(OpCodes.Ldloc_0)); worker.Append(worker.Create(OpCodes.Blt, labelBody)); // return value; worker.Append(worker.Create(OpCodes.Ldloc_1)); worker.Append(worker.Create(OpCodes.Ret)); return(readerFunc); }
public static MethodReference GetWriteFunc(TypeReference variable, int recursionCount = 0) { if (writeFuncs.TryGetValue(variable.FullName, out MethodReference foundFunc)) { return(foundFunc); } MethodDefinition newWriterFunc; // Arrays are special, if we resolve them, we get the element type, // so the following ifs might choke on it for scriptable objects // or other objects that require a custom serializer // thus check if it is an array and skip all the checks. if (variable.IsArray) { newWriterFunc = GenerateArrayWriteFunc(variable, recursionCount); RegisterWriteFunc(variable.FullName, newWriterFunc); return(newWriterFunc); } if (variable.IsByReference) { // error?? Weaver.Error($"Cannot pass {variable} by reference"); return(null); } TypeDefinition td = variable.Resolve(); if (td == null) { Weaver.Error($"{variable} is not a supported type. Use a supported type or provide a custom writer"); return(null); } if (td.IsDerivedFrom(Weaver.ComponentType)) { Weaver.Error($"Cannot generate writer for component type {variable}. Use a supported type or provide a custom writer"); return(null); } if (td.HasGenericParameters && !td.FullName.StartsWith("System.ArraySegment`1", System.StringComparison.Ordinal)) { Weaver.Error($"Cannot generate writer for generic type {variable}. Use a concrete type or provide a custom writer"); return(null); } if (td.IsInterface) { Weaver.Error($"Cannot generate writer for interface {variable}. Use a concrete type or provide a custom writer"); return(null); } if (variable.Resolve().IsEnum) { return(GetWriteFunc(variable.Resolve().GetEnumUnderlyingType(), recursionCount)); } else if (variable.FullName.StartsWith("System.ArraySegment`1", System.StringComparison.Ordinal)) { newWriterFunc = GenerateArraySegmentWriteFunc(variable, recursionCount); } else { newWriterFunc = GenerateClassOrStructWriterFunction(variable, recursionCount); } if (newWriterFunc == null) { return(null); } RegisterWriteFunc(variable.FullName, newWriterFunc); return(newWriterFunc); }
static void GenerateSerialization(TypeDefinition td) { Weaver.DLog(td, " GenerateSerialization"); MethodDefinition existingMethod = td.GetMethodWith1Arg("Serialize", WeaverTypes.Import <Mirror.NetworkWriter>()); // do nothing if method exists and is abstract or not empty if (existingMethod != null && (existingMethod.IsAbstract || !existingMethod.Body.IsEmptyDefault())) { return; } if (td.Fields.Count == 0) { return; } // check for self-referencing types foreach (FieldDefinition field in td.Fields) { if (field.FieldType.FullName == td.FullName) { Weaver.Error($"{td.Name} has field {field.Name} that references itself", field); return; } } MethodDefinition serializeFunc = existingMethod ?? new MethodDefinition("Serialize", MethodAttributes.Public | MethodAttributes.Virtual | MethodAttributes.HideBySig, WeaverTypes.Import(typeof(void))); //only add to new method if (existingMethod == null) { serializeFunc.Parameters.Add(new ParameterDefinition("writer", ParameterAttributes.None, WeaverTypes.Import <Mirror.NetworkWriter>())); } ILProcessor worker = serializeFunc.Body.GetILProcessor(); if (existingMethod != null) { //remove default nop&ret from existing empty interface method worker.Body.Instructions.Clear(); } // if it is not a struct, call base if (!td.IsValueType) { // call base CallBase(td, worker, "Serialize"); } foreach (FieldDefinition field in td.Fields) { if (field.IsStatic || field.IsPrivate || field.IsSpecialName) { continue; } CallWriter(worker, field); } worker.Append(worker.Create(OpCodes.Ret)); //only add if not just replaced body if (existingMethod == null) { td.Methods.Add(serializeFunc); } }
static MethodDefinition GenerateClassOrStructWriterFunction(TypeReference variable, int recursionCount) { if (recursionCount > MaxRecursionCount) { Weaver.Error($"{variable} can't be serialized because it references itself"); return(null); } if (!Weaver.IsValidTypeToGenerate(variable.Resolve())) { return(null); } string functionName = "_Write" + variable.Name + "_"; if (variable.DeclaringType != null) { functionName += variable.DeclaringType.Name; } else { functionName += "None"; } // create new writer for this type MethodDefinition writerFunc = new MethodDefinition(functionName, MethodAttributes.Public | MethodAttributes.Static | MethodAttributes.HideBySig, Weaver.voidType); writerFunc.Parameters.Add(new ParameterDefinition("writer", ParameterAttributes.None, Weaver.CurrentAssembly.MainModule.ImportReference(Weaver.NetworkWriterType))); writerFunc.Parameters.Add(new ParameterDefinition("value", ParameterAttributes.None, Weaver.CurrentAssembly.MainModule.ImportReference(variable))); ILProcessor worker = writerFunc.Body.GetILProcessor(); uint fields = 0; foreach (FieldDefinition field in variable.Resolve().Fields) { if (field.IsStatic || field.IsPrivate) { continue; } MethodReference writeFunc = GetWriteFunc(field.FieldType, recursionCount + 1); if (writeFunc != null) { fields++; worker.Append(worker.Create(OpCodes.Ldarg_0)); worker.Append(worker.Create(OpCodes.Ldarg_1)); worker.Append(worker.Create(OpCodes.Ldfld, field)); worker.Append(worker.Create(OpCodes.Call, writeFunc)); } else { Weaver.Error($"{field} has unsupported type. Use a type supported by Mirror instead"); return(null); } } if (fields == 0) { Log.Warning($" {variable} has no no public or non-static fields to serialize"); } worker.Append(worker.Create(OpCodes.Ret)); return(writerFunc); }
static MethodDefinition GenerateArrayWriteFunc(TypeReference variable, int recursionCount) { if (!variable.IsArrayType()) { Weaver.Error($"{variable} is an unsupported type. Jagged and multidimensional arrays are not supported"); return(null); } TypeReference elementType = variable.GetElementType(); MethodReference elementWriteFunc = GetWriteFunc(elementType, recursionCount + 1); if (elementWriteFunc == null) { return(null); } string functionName = "_WriteArray" + variable.GetElementType().Name + "_"; if (variable.DeclaringType != null) { functionName += variable.DeclaringType.Name; } else { functionName += "None"; } // create new writer for this type MethodDefinition writerFunc = new MethodDefinition(functionName, MethodAttributes.Public | MethodAttributes.Static | MethodAttributes.HideBySig, Weaver.voidType); writerFunc.Parameters.Add(new ParameterDefinition("writer", ParameterAttributes.None, Weaver.CurrentAssembly.MainModule.ImportReference(Weaver.NetworkWriterType))); writerFunc.Parameters.Add(new ParameterDefinition("value", ParameterAttributes.None, Weaver.CurrentAssembly.MainModule.ImportReference(variable))); writerFunc.Body.Variables.Add(new VariableDefinition(Weaver.int32Type)); writerFunc.Body.Variables.Add(new VariableDefinition(Weaver.int32Type)); writerFunc.Body.InitLocals = true; ILProcessor worker = writerFunc.Body.GetILProcessor(); // if (value == null) // { // writer.WritePackedInt32(-1); // return; // } Instruction labelNull = worker.Create(OpCodes.Nop); worker.Append(worker.Create(OpCodes.Ldarg_1)); worker.Append(worker.Create(OpCodes.Brtrue, labelNull)); worker.Append(worker.Create(OpCodes.Ldarg_0)); worker.Append(worker.Create(OpCodes.Ldc_I4_M1)); worker.Append(worker.Create(OpCodes.Call, GetWriteFunc(Weaver.int32Type))); worker.Append(worker.Create(OpCodes.Ret)); // int length = value.Length; worker.Append(labelNull); worker.Append(worker.Create(OpCodes.Ldarg_1)); worker.Append(worker.Create(OpCodes.Ldlen)); worker.Append(worker.Create(OpCodes.Stloc_0)); // writer.WritePackedInt32(length); worker.Append(worker.Create(OpCodes.Ldarg_0)); worker.Append(worker.Create(OpCodes.Ldloc_0)); worker.Append(worker.Create(OpCodes.Call, GetWriteFunc(Weaver.int32Type))); // for (int i=0; i< value.length; i++) { worker.Append(worker.Create(OpCodes.Ldc_I4_0)); worker.Append(worker.Create(OpCodes.Stloc_1)); Instruction labelHead = worker.Create(OpCodes.Nop); worker.Append(worker.Create(OpCodes.Br, labelHead)); // loop body Instruction labelBody = worker.Create(OpCodes.Nop); worker.Append(labelBody); // writer.Write(value[i]); worker.Append(worker.Create(OpCodes.Ldarg_0)); worker.Append(worker.Create(OpCodes.Ldarg_1)); worker.Append(worker.Create(OpCodes.Ldloc_1)); worker.Append(worker.Create(OpCodes.Ldelema, variable.GetElementType())); worker.Append(worker.Create(OpCodes.Ldobj, variable.GetElementType())); worker.Append(worker.Create(OpCodes.Call, elementWriteFunc)); worker.Append(worker.Create(OpCodes.Ldloc_1)); worker.Append(worker.Create(OpCodes.Ldc_I4_1)); worker.Append(worker.Create(OpCodes.Add)); worker.Append(worker.Create(OpCodes.Stloc_1)); // end for loop worker.Append(labelHead); worker.Append(worker.Create(OpCodes.Ldloc_1)); worker.Append(worker.Create(OpCodes.Ldarg_1)); worker.Append(worker.Create(OpCodes.Ldlen)); worker.Append(worker.Create(OpCodes.Conv_I4)); worker.Append(worker.Create(OpCodes.Blt, labelBody)); // return worker.Append(worker.Create(OpCodes.Ret)); return(writerFunc); }
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; } MethodDefinition serialize = new MethodDefinition(DeserializeMethodName, MethodAttributes.Public | MethodAttributes.Virtual | MethodAttributes.HideBySig, WeaverTypes.Import(typeof(void))); serialize.Parameters.Add(new ParameterDefinition("reader", ParameterAttributes.None, WeaverTypes.Import <Mirror.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; VariableDefinition 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; } if (Weaver.GenerateLogErrors) { serWorker.Append(serWorker.Create(OpCodes.Ldstr, "Injected Deserialize " + netBehaviourSubclass.Name)); serWorker.Append(serWorker.Create(OpCodes.Call, WeaverTypes.logErrorReference)); } serWorker.Append(serWorker.Create(OpCodes.Ret)); netBehaviourSubclass.Methods.Add(serialize); }
public static void ProcessSyncVars(TypeDefinition td, List <FieldDefinition> syncVars, List <FieldDefinition> syncObjects, Dictionary <FieldDefinition, FieldDefinition> syncVarNetIds) { int numSyncVars = 0; // the mapping of dirtybits to sync-vars is implicit in the order of the fields here. this order is recorded in m_replacementProperties. // start assigning syncvars at the place the base class stopped, if any int dirtyBitCounter = Weaver.GetSyncVarStart(td.BaseType.FullName); syncVarNetIds.Clear(); // find syncvars foreach (FieldDefinition fd in td.Fields) { if (fd.HasCustomAttribute(Weaver.SyncVarType)) { if ((fd.Attributes & FieldAttributes.Static) != 0) { Weaver.Error($"{fd.Name} cannot be static", fd); return; } if (fd.FieldType.IsArray) { Weaver.Error($"{fd.Name} has invalid type. Use SyncLists instead of arrays", fd); return; } if (SyncObjectInitializer.ImplementsSyncObject(fd.FieldType)) { Weaver.Warning($"{fd.Name} has [SyncVar] attribute. SyncLists should not be marked with SyncVar", fd); } else { syncVars.Add(fd); ProcessSyncVar(td, fd, syncVarNetIds, 1L << dirtyBitCounter); dirtyBitCounter += 1; numSyncVars += 1; if (dirtyBitCounter == SyncVarLimit) { Weaver.Error($"{td.Name} has too many SyncVars. Consider refactoring your class into multiple components", td); return; } } } if (fd.FieldType.Resolve().ImplementsInterface(Weaver.SyncObjectType)) { if (fd.IsStatic) { Weaver.Error($"{fd.Name} cannot be static", fd); return; } if (fd.FieldType.Resolve().HasGenericParameters) { Weaver.Error($"Cannot use generic SyncObject {fd.Name} directly in NetworkBehaviour. Create a class and inherit from the generic SyncObject instead", fd); return; } syncObjects.Add(fd); } } // add all the new SyncVar __netId fields foreach (FieldDefinition fd in syncVarNetIds.Values) { td.Fields.Add(fd); } Weaver.SetNumSyncVars(td.FullName, numSyncVars); }
public NetworkBehaviourProcessor(TypeDefinition td) { Weaver.DLog(td, "NetworkBehaviourProcessor"); netBehaviourSubclass = td; }
/// <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); } }
void GenerateSerialization() { Weaver.DLog(netBehaviourSubclass, " GenerateSerialization"); const string SerializeMethodName = "SerializeSyncVars"; if (netBehaviourSubclass.GetMethod(SerializeMethodName) != null) { return; } if (syncVars.Count == 0) { // no synvars, no need for custom OnSerialize return; } MethodDefinition serialize = new MethodDefinition(SerializeMethodName, MethodAttributes.Public | MethodAttributes.Virtual | MethodAttributes.HideBySig, WeaverTypes.Import <bool>()); serialize.Parameters.Add(new ParameterDefinition("writer", ParameterAttributes.None, WeaverTypes.Import <Mirror.NetworkWriter>())); serialize.Parameters.Add(new ParameterDefinition("forceAll", ParameterAttributes.None, WeaverTypes.Import <bool>())); ILProcessor worker = serialize.Body.GetILProcessor(); serialize.Body.InitLocals = true; // loc_0, this local variable is to determine if any variable was dirty VariableDefinition dirtyLocal = new VariableDefinition(WeaverTypes.Import <bool>()); serialize.Body.Variables.Add(dirtyLocal); MethodReference baseSerialize = Resolvers.TryResolveMethodInParents(netBehaviourSubclass.BaseType, Weaver.CurrentAssembly, SerializeMethodName); if (baseSerialize != null) { // base worker.Append(worker.Create(OpCodes.Ldarg_0)); // writer worker.Append(worker.Create(OpCodes.Ldarg_1)); // forceAll worker.Append(worker.Create(OpCodes.Ldarg_2)); worker.Append(worker.Create(OpCodes.Call, baseSerialize)); // set dirtyLocal to result of base.OnSerialize() worker.Append(worker.Create(OpCodes.Stloc_0)); } // Generates: if (forceAll); Instruction initialStateLabel = worker.Create(OpCodes.Nop); // forceAll worker.Append(worker.Create(OpCodes.Ldarg_2)); worker.Append(worker.Create(OpCodes.Brfalse, initialStateLabel)); foreach (FieldDefinition syncVar in syncVars) { // Generates a writer call for each sync variable // writer worker.Append(worker.Create(OpCodes.Ldarg_1)); // this worker.Append(worker.Create(OpCodes.Ldarg_0)); worker.Append(worker.Create(OpCodes.Ldfld, syncVar)); MethodReference writeFunc = Writers.GetWriteFunc(syncVar.FieldType); if (writeFunc != null) { worker.Append(worker.Create(OpCodes.Call, writeFunc)); } else { Weaver.Error($"{syncVar.Name} has unsupported type. Use a supported Mirror type instead", syncVar); return; } } // always return true if forceAll // Generates: return true worker.Append(worker.Create(OpCodes.Ldc_I4_1)); worker.Append(worker.Create(OpCodes.Ret)); // Generates: end if (forceAll); worker.Append(initialStateLabel); // write dirty bits before the data fields // Generates: writer.WritePackedUInt64 (base.get_syncVarDirtyBits ()); // writer worker.Append(worker.Create(OpCodes.Ldarg_1)); // base worker.Append(worker.Create(OpCodes.Ldarg_0)); worker.Append(worker.Create(OpCodes.Call, WeaverTypes.NetworkBehaviourDirtyBitsReference)); MethodReference writeUint64Func = Writers.GetWriteFunc(WeaverTypes.Import <ulong>()); worker.Append(worker.Create(OpCodes.Call, writeUint64Func)); // generate a writer call for any dirty variable in this class // start at number of syncvars in parent int dirtyBit = Weaver.GetSyncVarStart(netBehaviourSubclass.BaseType.FullName); foreach (FieldDefinition syncVar in syncVars) { Instruction varLabel = worker.Create(OpCodes.Nop); // Generates: if ((base.get_syncVarDirtyBits() & 1uL) != 0uL) // base worker.Append(worker.Create(OpCodes.Ldarg_0)); worker.Append(worker.Create(OpCodes.Call, WeaverTypes.NetworkBehaviourDirtyBitsReference)); // 8 bytes = long worker.Append(worker.Create(OpCodes.Ldc_I8, 1L << dirtyBit)); worker.Append(worker.Create(OpCodes.And)); worker.Append(worker.Create(OpCodes.Brfalse, varLabel)); // Generates a call to the writer for that field // writer worker.Append(worker.Create(OpCodes.Ldarg_1)); // base worker.Append(worker.Create(OpCodes.Ldarg_0)); worker.Append(worker.Create(OpCodes.Ldfld, syncVar)); MethodReference writeFunc = Writers.GetWriteFunc(syncVar.FieldType); if (writeFunc != null) { worker.Append(worker.Create(OpCodes.Call, writeFunc)); } else { Weaver.Error($"{syncVar.Name} has unsupported type. Use a supported Mirror type instead", syncVar); return; } // something was dirty worker.Append(worker.Create(OpCodes.Ldc_I4_1)); // set dirtyLocal to true worker.Append(worker.Create(OpCodes.Stloc_0)); worker.Append(varLabel); dirtyBit += 1; } if (Weaver.GenerateLogErrors) { worker.Append(worker.Create(OpCodes.Ldstr, "Injected Serialize " + netBehaviourSubclass.Name)); worker.Append(worker.Create(OpCodes.Call, WeaverTypes.logErrorReference)); } // generate: return dirtyLocal worker.Append(worker.Create(OpCodes.Ldloc_0)); worker.Append(worker.Create(OpCodes.Ret)); netBehaviourSubclass.Methods.Add(serialize); }
void GenerateConstants() { if (commands.Count == 0 && clientRpcs.Count == 0 && targetRpcs.Count == 0 && syncObjects.Count == 0) { return; } Weaver.DLog(netBehaviourSubclass, " GenerateConstants "); // find static constructor MethodDefinition cctor = netBehaviourSubclass.GetMethod(".cctor"); bool cctorFound = cctor != null; if (cctor != null) { // remove the return opcode from end of function. will add our own later. if (cctor.Body.Instructions.Count != 0) { Instruction retInstr = cctor.Body.Instructions[cctor.Body.Instructions.Count - 1]; if (retInstr.OpCode == OpCodes.Ret) { cctor.Body.Instructions.RemoveAt(cctor.Body.Instructions.Count - 1); } else { Weaver.Error($"{netBehaviourSubclass.Name} has invalid class constructor", cctor); return; } } } else { // make one! cctor = new MethodDefinition(".cctor", MethodAttributes.Private | MethodAttributes.HideBySig | MethodAttributes.SpecialName | MethodAttributes.RTSpecialName | MethodAttributes.Static, WeaverTypes.Import(typeof(void))); } // find instance constructor MethodDefinition ctor = netBehaviourSubclass.GetMethod(".ctor"); if (ctor == null) { Weaver.Error($"{netBehaviourSubclass.Name} has invalid constructor", netBehaviourSubclass); return; } Instruction ret = ctor.Body.Instructions[ctor.Body.Instructions.Count - 1]; if (ret.OpCode == OpCodes.Ret) { ctor.Body.Instructions.RemoveAt(ctor.Body.Instructions.Count - 1); } else { Weaver.Error($"{netBehaviourSubclass.Name} has invalid constructor", ctor); return; } // TODO: find out if the order below matters. If it doesn't split code below into 2 functions ILProcessor ctorWorker = ctor.Body.GetILProcessor(); ILProcessor cctorWorker = cctor.Body.GetILProcessor(); for (int i = 0; i < commands.Count; ++i) { CmdResult cmdResult = commands[i]; GenerateRegisterCommandDelegate(cctorWorker, WeaverTypes.registerCommandDelegateReference, commandInvocationFuncs[i], cmdResult); } for (int i = 0; i < clientRpcs.Count; ++i) { ClientRpcResult clientRpcResult = clientRpcs[i]; GenerateRegisterRemoteDelegate(cctorWorker, WeaverTypes.registerRpcDelegateReference, clientRpcInvocationFuncs[i], clientRpcResult.method.Name); } for (int i = 0; i < targetRpcs.Count; ++i) { GenerateRegisterRemoteDelegate(cctorWorker, WeaverTypes.registerRpcDelegateReference, targetRpcInvocationFuncs[i], targetRpcs[i].Name); } foreach (FieldDefinition fd in syncObjects) { SyncObjectInitializer.GenerateSyncObjectInitializer(ctorWorker, fd); } cctorWorker.Append(cctorWorker.Create(OpCodes.Ret)); if (!cctorFound) { netBehaviourSubclass.Methods.Add(cctor); } // finish ctor ctorWorker.Append(ctorWorker.Create(OpCodes.Ret)); // in case class had no cctor, it might have BeforeFieldInit, so injected cctor would be called too late netBehaviourSubclass.Attributes &= ~TypeAttributes.BeforeFieldInit; }
static MethodDefinition GenerateListWriteFunc(TypeReference variable, int recursionCount) { GenericInstanceType genericInstance = (GenericInstanceType)variable; TypeReference elementType = genericInstance.GenericArguments[0]; MethodReference elementWriteFunc = GetWriteFunc(elementType, recursionCount + 1); MethodReference intWriterFunc = GetWriteFunc(WeaverTypes.Import <int>()); // need this null check till later PR when GetWriteFunc throws exception instead if (elementWriteFunc == null) { Weaver.Error($"Cannot generate writer for List because element {elementType.Name} does not have a writer. Use a supported type or provide a custom writer", variable); return(null); } MethodDefinition writerFunc = GenerateWriterFunc(variable); writerFunc.Body.Variables.Add(new VariableDefinition(WeaverTypes.Import <int>())); writerFunc.Body.Variables.Add(new VariableDefinition(WeaverTypes.Import <int>())); writerFunc.Body.InitLocals = true; ILProcessor worker = writerFunc.Body.GetILProcessor(); // if (value == null) // { // writer.WritePackedInt32(-1); // return; // } Instruction labelNull = worker.Create(OpCodes.Nop); worker.Append(worker.Create(OpCodes.Ldarg_1)); worker.Append(worker.Create(OpCodes.Brtrue, labelNull)); worker.Append(worker.Create(OpCodes.Ldarg_0)); worker.Append(worker.Create(OpCodes.Ldc_I4_M1)); worker.Append(worker.Create(OpCodes.Call, intWriterFunc)); worker.Append(worker.Create(OpCodes.Ret)); // else not null worker.Append(labelNull); MethodReference countref = WeaverTypes.ListCountReference.MakeHostInstanceGeneric(genericInstance); // int count = value.Count; worker.Append(worker.Create(OpCodes.Ldarg_1)); worker.Append(worker.Create(OpCodes.Call, countref)); worker.Append(worker.Create(OpCodes.Stloc_0)); // writer.WritePackedInt32(count); worker.Append(worker.Create(OpCodes.Ldarg_0)); worker.Append(worker.Create(OpCodes.Ldloc_0)); worker.Append(worker.Create(OpCodes.Call, intWriterFunc)); // Loop through the List<T> and call the writer for each element. // generates this: // for (int i=0; i < count; i++) // { // writer.WriteT(value[i]); // } worker.Append(worker.Create(OpCodes.Ldc_I4_0)); worker.Append(worker.Create(OpCodes.Stloc_1)); Instruction labelHead = worker.Create(OpCodes.Nop); worker.Append(worker.Create(OpCodes.Br, labelHead)); // loop body Instruction labelBody = worker.Create(OpCodes.Nop); worker.Append(labelBody); MethodReference getItem = WeaverTypes.ListGetItemReference.MakeHostInstanceGeneric(genericInstance); // writer.Write(value[i]); worker.Append(worker.Create(OpCodes.Ldarg_0)); // writer worker.Append(worker.Create(OpCodes.Ldarg_1)); // value worker.Append(worker.Create(OpCodes.Ldloc_1)); // i worker.Append(worker.Create(OpCodes.Call, getItem)); //get_Item worker.Append(worker.Create(OpCodes.Call, elementWriteFunc)); // Write // end for loop // for loop i++ worker.Append(worker.Create(OpCodes.Ldloc_1)); worker.Append(worker.Create(OpCodes.Ldc_I4_1)); worker.Append(worker.Create(OpCodes.Add)); worker.Append(worker.Create(OpCodes.Stloc_1)); worker.Append(labelHead); // for loop i < count worker.Append(worker.Create(OpCodes.Ldloc_1)); worker.Append(worker.Create(OpCodes.Ldloc_0)); worker.Append(worker.Create(OpCodes.Blt, labelBody)); // return worker.Append(worker.Create(OpCodes.Ret)); return(writerFunc); }
static MethodDefinition GenerateArraySegmentWriteFunc(TypeReference variable, int recursionCount) { GenericInstanceType genericInstance = (GenericInstanceType)variable; TypeReference elementType = genericInstance.GenericArguments[0]; MethodReference elementWriteFunc = GetWriteFunc(elementType, recursionCount + 1); MethodReference intWriterFunc = GetWriteFunc(WeaverTypes.Import <int>()); // need this null check till later PR when GetWriteFunc throws exception instead if (elementWriteFunc == null) { Weaver.Error($"Cannot generate writer for ArraySegment because element {elementType.Name} does not have a writer. Use a supported type or provide a custom writer", variable); return(null); } MethodDefinition writerFunc = GenerateWriterFunc(variable); writerFunc.Body.Variables.Add(new VariableDefinition(WeaverTypes.Import <int>())); writerFunc.Body.Variables.Add(new VariableDefinition(WeaverTypes.Import <int>())); ILProcessor worker = writerFunc.Body.GetILProcessor(); MethodReference countref = WeaverTypes.ArraySegmentCountReference.MakeHostInstanceGeneric(genericInstance); // int length = value.Count; worker.Append(worker.Create(OpCodes.Ldarga_S, (byte)1)); worker.Append(worker.Create(OpCodes.Call, countref)); worker.Append(worker.Create(OpCodes.Stloc_0)); // writer.WritePackedInt32(length); worker.Append(worker.Create(OpCodes.Ldarg_0)); worker.Append(worker.Create(OpCodes.Ldloc_0)); worker.Append(worker.Create(OpCodes.Call, intWriterFunc)); // Loop through the ArraySegment<T> and call the writer for each element. // generates this: // for (int i=0; i< length; i++) // { // writer.Write(value.Array[i + value.Offset]); // } worker.Append(worker.Create(OpCodes.Ldc_I4_0)); worker.Append(worker.Create(OpCodes.Stloc_1)); Instruction labelHead = worker.Create(OpCodes.Nop); worker.Append(worker.Create(OpCodes.Br, labelHead)); // loop body Instruction labelBody = worker.Create(OpCodes.Nop); worker.Append(labelBody); // writer.Write(value.Array[i + value.Offset]); worker.Append(worker.Create(OpCodes.Ldarg_0)); worker.Append(worker.Create(OpCodes.Ldarga_S, (byte)1)); worker.Append(worker.Create(OpCodes.Call, WeaverTypes.ArraySegmentArrayReference.MakeHostInstanceGeneric(genericInstance))); worker.Append(worker.Create(OpCodes.Ldloc_1)); worker.Append(worker.Create(OpCodes.Ldarga_S, (byte)1)); worker.Append(worker.Create(OpCodes.Call, WeaverTypes.ArraySegmentOffsetReference.MakeHostInstanceGeneric(genericInstance))); worker.Append(worker.Create(OpCodes.Add)); worker.Append(worker.Create(OpCodes.Ldelema, elementType)); worker.Append(worker.Create(OpCodes.Ldobj, elementType)); worker.Append(worker.Create(OpCodes.Call, elementWriteFunc)); worker.Append(worker.Create(OpCodes.Ldloc_1)); worker.Append(worker.Create(OpCodes.Ldc_I4_1)); worker.Append(worker.Create(OpCodes.Add)); worker.Append(worker.Create(OpCodes.Stloc_1)); // end for loop worker.Append(labelHead); worker.Append(worker.Create(OpCodes.Ldloc_1)); worker.Append(worker.Create(OpCodes.Ldloc_0)); worker.Append(worker.Create(OpCodes.Blt, labelBody)); // return worker.Append(worker.Create(OpCodes.Ret)); return(writerFunc); }
static MethodDefinition GenerateArrayWriteFunc(TypeReference variable, int recursionCount) { if (!variable.IsArrayType()) { throw new GenerateWriterException($"{variable.Name} is an unsupported type. Jagged and multidimensional arrays are not supported", variable); } TypeReference elementType = variable.GetElementType(); MethodReference elementWriteFunc = GetWriteFunc(elementType, recursionCount + 1); MethodReference intWriterFunc = GetWriteFunc(WeaverTypes.Import <int>()); // need this null check till later PR when GetWriteFunc throws exception instead if (elementWriteFunc == null) { Weaver.Error($"Cannot generate writer for Array because element {elementType.Name} does not have a writer. Use a supported type or provide a custom writer", variable); return(null); } MethodDefinition writerFunc = GenerateWriterFunc(variable); writerFunc.Body.Variables.Add(new VariableDefinition(WeaverTypes.Import <int>())); writerFunc.Body.Variables.Add(new VariableDefinition(WeaverTypes.Import <int>())); ILProcessor worker = writerFunc.Body.GetILProcessor(); // if (value == null) // { // writer.WritePackedInt32(-1); // return; // } Instruction labelNull = worker.Create(OpCodes.Nop); worker.Append(worker.Create(OpCodes.Ldarg_1)); worker.Append(worker.Create(OpCodes.Brtrue, labelNull)); worker.Append(worker.Create(OpCodes.Ldarg_0)); worker.Append(worker.Create(OpCodes.Ldc_I4_M1)); worker.Append(worker.Create(OpCodes.Call, intWriterFunc)); worker.Append(worker.Create(OpCodes.Ret)); // else not null worker.Append(labelNull); // int length = value.Length; worker.Append(worker.Create(OpCodes.Ldarg_1)); worker.Append(worker.Create(OpCodes.Ldlen)); worker.Append(worker.Create(OpCodes.Stloc_0)); // writer.WritePackedInt32(length); worker.Append(worker.Create(OpCodes.Ldarg_0)); worker.Append(worker.Create(OpCodes.Ldloc_0)); worker.Append(worker.Create(OpCodes.Call, intWriterFunc)); // for (int i=0; i< value.length; i++) { worker.Append(worker.Create(OpCodes.Ldc_I4_0)); worker.Append(worker.Create(OpCodes.Stloc_1)); Instruction labelHead = worker.Create(OpCodes.Nop); worker.Append(worker.Create(OpCodes.Br, labelHead)); // loop body Instruction labelBody = worker.Create(OpCodes.Nop); worker.Append(labelBody); // writer.Write(value[i]); worker.Append(worker.Create(OpCodes.Ldarg_0)); worker.Append(worker.Create(OpCodes.Ldarg_1)); worker.Append(worker.Create(OpCodes.Ldloc_1)); worker.Append(worker.Create(OpCodes.Ldelema, elementType)); worker.Append(worker.Create(OpCodes.Ldobj, elementType)); worker.Append(worker.Create(OpCodes.Call, elementWriteFunc)); worker.Append(worker.Create(OpCodes.Ldloc_1)); worker.Append(worker.Create(OpCodes.Ldc_I4_1)); worker.Append(worker.Create(OpCodes.Add)); worker.Append(worker.Create(OpCodes.Stloc_1)); // end for loop worker.Append(labelHead); worker.Append(worker.Create(OpCodes.Ldloc_1)); worker.Append(worker.Create(OpCodes.Ldarg_1)); worker.Append(worker.Create(OpCodes.Ldlen)); worker.Append(worker.Create(OpCodes.Conv_I4)); worker.Append(worker.Create(OpCodes.Blt, labelBody)); // return worker.Append(worker.Create(OpCodes.Ret)); return(writerFunc); }