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(WeaverTypes.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(WeaverTypes.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); }
void GenerateConstants() { if (commands.Count == 0 && clientRpcs.Count == 0 && targetRpcs.Count == 0 && eventRpcs.Count == 0 && syncObjects.Count == 0) { return; } Weaver.DLog(netBehaviourSubclass, " GenerateConstants "); // find static constructor MethodDefinition cctor = null; bool cctorFound = false; foreach (MethodDefinition md in netBehaviourSubclass.Methods) { if (md.Name == ".cctor") { cctor = md; cctorFound = true; } } if (cctor != null) { // remove the return opcode from end of function. will add our own later. if (cctor.Body.Instructions.Count != 0) { Instruction ret = cctor.Body.Instructions[cctor.Body.Instructions.Count - 1]; if (ret.OpCode == OpCodes.Ret) { cctor.Body.Instructions.RemoveAt(cctor.Body.Instructions.Count - 1); } else { Weaver.Error($"{netBehaviourSubclass} has invalid class constructor"); return; } } } else { // make one! cctor = new MethodDefinition(".cctor", MethodAttributes.Private | MethodAttributes.HideBySig | MethodAttributes.SpecialName | MethodAttributes.RTSpecialName | MethodAttributes.Static, Weaver.voidType); } // find instance constructor MethodDefinition ctor = null; foreach (MethodDefinition md in netBehaviourSubclass.Methods) { if (md.Name == ".ctor") { ctor = md; 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} has invalid constructor"); return; } break; } } if (ctor == null) { Weaver.Error($"{netBehaviourSubclass} has invalid constructor"); return; } ILProcessor ctorWorker = ctor.Body.GetILProcessor(); ILProcessor cctorWorker = cctor.Body.GetILProcessor(); for (int i = 0; i < commands.Count; ++i) { GenerateRegisterCommandDelegate(cctorWorker, Weaver.registerCommandDelegateReference, commandInvocationFuncs[i], commands[i].Name); } for (int i = 0; i < clientRpcs.Count; ++i) { GenerateRegisterCommandDelegate(cctorWorker, Weaver.registerRpcDelegateReference, clientRpcInvocationFuncs[i], clientRpcs[i].Name); } for (int i = 0; i < targetRpcs.Count; ++i) { GenerateRegisterCommandDelegate(cctorWorker, Weaver.registerRpcDelegateReference, targetRpcInvocationFuncs[i], targetRpcs[i].Name); } for (int i = 0; i < eventRpcs.Count; ++i) { GenerateRegisterCommandDelegate(cctorWorker, Weaver.registerEventDelegateReference, eventRpcInvocationFuncs[i], eventRpcs[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; }
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, 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 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($"{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 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)); } }
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.int32Type); 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); } string functionName = "_WriteList_" + elementType.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, WeaverTypes.voidType); writerFunc.Parameters.Add(new ParameterDefinition("writer", ParameterAttributes.None, Weaver.CurrentAssembly.MainModule.ImportReference(WeaverTypes.NetworkWriterType))); writerFunc.Parameters.Add(new ParameterDefinition("value", ParameterAttributes.None, variable)); writerFunc.Body.Variables.Add(new VariableDefinition(WeaverTypes.int32Type)); writerFunc.Body.Variables.Add(new VariableDefinition(WeaverTypes.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, 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); }
public static void ProcessSyncVars(TypeDefinition td, List <FieldDefinition> syncVars, List <FieldDefinition> syncObjects, Dictionary <FieldDefinition, FieldDefinition> syncVarNetIds) { var 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 var dirtyBitCounter = Weaver.GetSyncVarStart(td.BaseType.FullName); syncVarNetIds.Clear(); // find syncvars foreach (var fd in td.Fields) { foreach (var ca in fd.CustomAttributes) { if (ca.AttributeType.FullName == Weaver.SyncVarType.FullName) { var resolvedField = fd.FieldType.Resolve(); if ((fd.Attributes & FieldAttributes.Static) != 0) { Weaver.Error($"{fd} cannot be static"); return; } if (fd.FieldType.IsArray) { Weaver.Error($"{fd} has invalid type. Use SyncLists instead of arrays"); return; } if (SyncObjectInitializer.ImplementsSyncObject(fd.FieldType)) { Log.Warning($"{fd} has [SyncVar] attribute. SyncLists should not be marked with SyncVar"); break; } syncVars.Add(fd); ProcessSyncVar(td, fd, syncVarNetIds, 1L << dirtyBitCounter); dirtyBitCounter += 1; numSyncVars += 1; if (dirtyBitCounter == SyncVarLimit) { Weaver.Error( $"{td} has too many SyncVars. Consider refactoring your class into multiple components"); return; } break; } } if (fd.FieldType.Resolve().ImplementsInterface(Weaver.SyncObjectType)) { if (fd.IsStatic) { Weaver.Error($"{fd} cannot be static"); return; } syncObjects.Add(fd); } } // add all the new SyncVar __netId fields foreach (var fd in syncVarNetIds.Values) { td.Fields.Add(fd); } Weaver.SetNumSyncVars(td.FullName, numSyncVars); }
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; * // 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) { // 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 (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 // 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, foundMethod)); // Generates: end if (!SyncVarEqual); serWorker.Append(syncVarEqualLabel); } } // [SyncVar] int/float/struct/etc.? /* * Generates code like: * // for hook * int oldValue = a; * 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. // 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 (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 // 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, foundMethod)); // Generates: end if (!SyncVarEqual); serWorker.Append(syncVarEqualLabel); } } }
static MethodDefinition GenerateArrayWriteFunc(TypeReference variable, MethodReference elementWriteFunc) { if (!variable.IsArrayType()) { Weaver.Error($"{variable} is an unsupported type. Jagged and multidimensional arrays are not supported"); 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.uint16Type)); writerFunc.Body.Variables.Add(new VariableDefinition(Weaver.uint16Type)); writerFunc.Body.InitLocals = true; ILProcessor worker = writerFunc.Body.GetILProcessor(); // null check 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_0)); worker.Append(worker.Create(OpCodes.Call, Weaver.NetworkWriteUInt16)); worker.Append(worker.Create(OpCodes.Ret)); // setup array length local variable worker.Append(labelNull); 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.Conv_U2)); worker.Append(worker.Create(OpCodes.Stloc_0)); //write length worker.Append(worker.Create(OpCodes.Ldarg_0)); worker.Append(worker.Create(OpCodes.Ldloc_0)); worker.Append(worker.Create(OpCodes.Call, Weaver.NetworkWriteUInt16)); // start loop 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); 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.Conv_U2)); worker.Append(worker.Create(OpCodes.Stloc_1)); // loop while check 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)); worker.Append(worker.Create(OpCodes.Ret)); return(writerFunc); }
static void GenerateSerialization(TypeDefinition td) { Weaver.DLog(td, " GenerateSerialization"); foreach (MethodDefinition m in td.Methods) { if (m.Name == "Serialize") { 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} has field ${field} that references itself"); return; } } MethodDefinition serializeFunc = new MethodDefinition("Serialize", MethodAttributes.Public | MethodAttributes.Virtual | MethodAttributes.HideBySig, Weaver.voidType); serializeFunc.Parameters.Add(new ParameterDefinition("writer", ParameterAttributes.None, Weaver.CurrentAssembly.MainModule.ImportReference(Weaver.NetworkWriterType))); ILProcessor serWorker = serializeFunc.Body.GetILProcessor(); foreach (FieldDefinition field in td.Fields) { if (field.IsStatic || field.IsPrivate || field.IsSpecialName) { continue; } if (field.FieldType.Resolve().HasGenericParameters&& !field.FieldType.FullName.StartsWith("System.ArraySegment`1", System.StringComparison.Ordinal)) { Weaver.Error($"{field} cannot have generic type {field.FieldType}. Consider creating a class that derives the generic type"); return; } if (field.FieldType.Resolve().IsInterface) { Weaver.Error($"{field} has unsupported type. Use a concrete class instead of interface {field.FieldType}"); return; } MethodReference writeFunc = Writers.GetWriteFunc(field.FieldType); if (writeFunc != null) { serWorker.Append(serWorker.Create(OpCodes.Ldarg_1)); serWorker.Append(serWorker.Create(OpCodes.Ldarg_0)); serWorker.Append(serWorker.Create(OpCodes.Ldfld, field)); serWorker.Append(serWorker.Create(OpCodes.Call, writeFunc)); } else { Weaver.Error($"{field} has unsupported type"); return; } } serWorker.Append(serWorker.Create(OpCodes.Ret)); td.Methods.Add(serializeFunc); }
private static MethodReference GenerateReader(TypeReference variableReference) { // Arrays are special, if we resolve them, we get teh 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 (variableReference.IsArray) { if (variableReference.IsMultidimensionalArray()) { Weaver.Error($"{variableReference.Name} is an unsupported type. Multidimensional arrays are not supported", variableReference); return(null); } return(GenerateReadCollection(variableReference, variableReference.GetElementType(), nameof(NetworkReaderExtensions.ReadArray))); } TypeDefinition variableDefinition = variableReference.Resolve(); if (variableDefinition == null) { Weaver.Error($"{variableReference.Name} is not a supported type", variableReference); return(null); } if (variableDefinition.IsDerivedFrom <UnityEngine.Component>()) { Weaver.Error($"Cannot generate reader for component type {variableReference.Name}. Use a supported type or provide a custom reader", variableReference); return(null); } if (variableReference.Is <UnityEngine.Object>()) { Weaver.Error($"Cannot generate reader for {variableReference.Name}. Use a supported type or provide a custom reader", variableReference); return(null); } if (variableReference.Is <UnityEngine.ScriptableObject>()) { Weaver.Error($"Cannot generate reader for {variableReference.Name}. Use a supported type or provide a custom reader", variableReference); return(null); } if (variableReference.IsByReference) { // error?? Weaver.Error($"Cannot pass type {variableReference.Name} by reference", variableReference); return(null); } if (variableDefinition.HasGenericParameters && !variableDefinition.Is(typeof(ArraySegment <>)) && !variableDefinition.Is(typeof(List <>))) { Weaver.Error($"Cannot generate reader for generic variable {variableReference.Name}. Use a supported type or provide a custom reader", variableReference); return(null); } if (variableDefinition.IsInterface) { Weaver.Error($"Cannot generate reader for interface {variableReference.Name}. Use a supported type or provide a custom reader", variableReference); return(null); } if (variableDefinition.IsAbstract) { Weaver.Error($"Cannot generate reader for abstract class {variableReference.Name}. Use a supported type or provide a custom reader", variableReference); return(null); } if (variableDefinition.IsEnum) { return(GenerateEnumReadFunc(variableReference)); } else if (variableDefinition.Is(typeof(ArraySegment <>))) { return(GenerateArraySegmentReadFunc(variableReference)); } else if (variableDefinition.Is(typeof(List <>))) { GenericInstanceType genericInstance = (GenericInstanceType)variableReference; TypeReference elementType = genericInstance.GenericArguments[0]; return(GenerateReadCollection(variableReference, elementType, nameof(NetworkReaderExtensions.ReadList))); } return(GenerateClassOrStructReadFunction(variableReference)); }
void GenerateConstants() { if (serverRpcs.Count == 0 && clientRpcs.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 < serverRpcs.Count; ++i) { CmdResult cmdResult = serverRpcs[i]; GenerateRegisterServerRpcDelegate(cctorWorker, WeaverTypes.registerServerRpcDelegateReference, serverRpcSkeletonFuncs[i], cmdResult); } for (int i = 0; i < clientRpcs.Count; ++i) { ClientRpcResult clientRpcResult = clientRpcs[i]; GenerateRegisterRemoteDelegate(cctorWorker, WeaverTypes.registerRpcDelegateReference, clientRpcSkeletonFuncs[i], clientRpcResult.method.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 GenerateStructWriterFunction(TypeReference variable, int recursionCount) { 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; } if (field.FieldType.Resolve().HasGenericParameters) { Weaver.Error("WriteReadFunc for " + field.Name + " [" + field.FieldType + "/" + field.FieldType.FullName + "]. Cannot have generic parameters."); return(null); } if (field.FieldType.Resolve().IsInterface) { Weaver.Error("WriteReadFunc for " + field.Name + " [" + field.FieldType + "/" + field.FieldType.FullName + "]. Cannot be an interface."); return(null); } 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("WriteReadFunc for " + field.Name + " type " + field.FieldType + " no supported"); return(null); } } if (fields == 0) { Log.Warning("The class / struct " + variable.Name + " has no public or non-static fields to serialize"); } worker.Append(worker.Create(OpCodes.Ret)); return(writerFunc); }
static bool Weave(string assName, IEnumerable <string> dependencies, IAssemblyResolver assemblyResolver, string unityEngineDLLPath, string mirrorNetDLLPath, string outputDir) { ReaderParameters readParams = Helpers.ReaderParameters(assName, dependencies, assemblyResolver, unityEngineDLLPath, mirrorNetDLLPath); using (CurrentAssembly = AssemblyDefinition.ReadAssembly(assName, readParams)) { SetupTargetTypes(); Readers.Init(CurrentAssembly); Writers.Init(CurrentAssembly); ModuleDefinition moduleDefinition = CurrentAssembly.MainModule; Console.WriteLine("Script Module: {0}", moduleDefinition.Name); // Process each NetworkBehaviour bool didWork = false; // We need to do 2 passes, because SyncListStructs might be referenced from other modules, so we must make sure we generate them first. for (int pass = 0; pass < 2; pass++) { System.Diagnostics.Stopwatch watch = System.Diagnostics.Stopwatch.StartNew(); foreach (TypeDefinition td in moduleDefinition.Types) { if (td.IsClass && td.BaseType.CanBeResolved()) { try { if (pass == 0) { didWork |= CheckSyncList(td); } else { didWork |= CheckNetworkBehaviour(td); didWork |= CheckMessageBase(td); } } catch (Exception ex) { if (CurrentAssembly.MainModule.SymbolReader != null) { CurrentAssembly.MainModule.SymbolReader.Dispose(); } Weaver.Error(ex.Message); throw ex; } } if (WeavingFailed) { if (CurrentAssembly.MainModule.SymbolReader != null) { CurrentAssembly.MainModule.SymbolReader.Dispose(); } return(false); } } watch.Stop(); Console.WriteLine("Pass: "******" took " + watch.ElapsedMilliseconds + " milliseconds"); } if (didWork) { // this must be done for ALL code, not just NetworkBehaviours try { PropertySiteProcessor.ProcessSitesModule(CurrentAssembly.MainModule); } catch (Exception e) { Log.Error("ProcessPropertySites exception: " + e); if (CurrentAssembly.MainModule.SymbolReader != null) { CurrentAssembly.MainModule.SymbolReader.Dispose(); } return(false); } if (WeavingFailed) { //Log.Error("Failed phase II."); if (CurrentAssembly.MainModule.SymbolReader != null) { CurrentAssembly.MainModule.SymbolReader.Dispose(); } return(false); } string dest = Helpers.DestinationFileFor(outputDir, assName); //Console.WriteLine ("Output:" + dest); WriterParameters writeParams = Helpers.GetWriterParameters(readParams); CurrentAssembly.Write(dest, writeParams); } if (CurrentAssembly.MainModule.SymbolReader != null) { CurrentAssembly.MainModule.SymbolReader.Dispose(); } } return(true); }
static void GenerateSerialization(TypeDefinition td) { Weaver.DLog(td, " GenerateSerialization"); foreach (MethodDefinition m in td.Methods) { if (m.Name == "Serialize") { 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("GenerateSerialization for " + td.Name + " [" + field.FullName + "]. [MessageBase] member cannot be self referencing."); return; } } MethodDefinition serializeFunc = new MethodDefinition("Serialize", MethodAttributes.Public | MethodAttributes.Virtual | MethodAttributes.HideBySig, Weaver.voidType); serializeFunc.Parameters.Add(new ParameterDefinition("writer", ParameterAttributes.None, Weaver.CurrentAssembly.MainModule.ImportReference(Weaver.NetworkWriterType))); ILProcessor serWorker = serializeFunc.Body.GetILProcessor(); foreach (FieldDefinition field in td.Fields) { if (field.IsStatic || field.IsPrivate || field.IsSpecialName) { continue; } if (field.FieldType.Resolve().HasGenericParameters) { Weaver.Error("GenerateSerialization for " + td.Name + " [" + field.FieldType + "/" + field.FieldType.FullName + "]. [MessageBase] member cannot have generic parameters."); return; } if (field.FieldType.Resolve().IsInterface) { Weaver.Error("GenerateSerialization for " + td.Name + " [" + field.FieldType + "/" + field.FieldType.FullName + "]. [MessageBase] member cannot be an interface."); return; } MethodReference writeFunc = Writers.GetWriteFunc(field.FieldType); if (writeFunc != null) { serWorker.Append(serWorker.Create(OpCodes.Ldarg_1)); serWorker.Append(serWorker.Create(OpCodes.Ldarg_0)); serWorker.Append(serWorker.Create(OpCodes.Ldfld, field)); serWorker.Append(serWorker.Create(OpCodes.Call, writeFunc)); } else { Weaver.Error("GenerateSerialization for " + td.Name + " unknown type [" + field.FieldType + "/" + field.FieldType.FullName + "]. [MessageBase] member variables must be basic types."); return; } } serWorker.Append(serWorker.Create(OpCodes.Ret)); td.Methods.Add(serializeFunc); }
public static MethodReference GetWriteFunc(TypeReference variable, int recursionCount = 0) { if (writeFuncs.TryGetValue(variable.FullName, out MethodReference foundFunc)) { return(foundFunc); } 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.ScriptableObjectType)) { Weaver.Error($"Cannot generate writer for scriptable object {variable}. 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); } MethodDefinition newWriterFunc; if (variable.IsArray) { newWriterFunc = GenerateArrayWriteFunc(variable, recursionCount); } else 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 = GenerateStructWriterFunction(variable, recursionCount); } if (newWriterFunc == null) { return(null); } RegisterWriteFunc(variable.FullName, newWriterFunc); return(newWriterFunc); }
static MethodDefinition GenerateArraySegmentReadFunc(TypeReference variable, int recursionCount) { GenericInstanceType genericInstance = (GenericInstanceType)variable; TypeReference elementType = genericInstance.GenericArguments[0]; MethodReference elementReadFunc = GetReadFunc(elementType, recursionCount + 1); if (elementReadFunc == null) { Weaver.Error($"Cannot generate reader for ArraySegment because element {elementType.Name} does not have a reader. Use a supported type or provide a custom reader", variable); return(null); } string functionName = "_ReadArraySegment_" + elementType.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(WeaverTypes.NetworkReaderType))); // int lengh readerFunc.Body.Variables.Add(new VariableDefinition(WeaverTypes.int32Type)); // T[] array readerFunc.Body.Variables.Add(new VariableDefinition(elementType.MakeArrayType())); // int i; readerFunc.Body.Variables.Add(new VariableDefinition(WeaverTypes.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(WeaverTypes.int32Type))); worker.Append(worker.Create(OpCodes.Stloc_0)); // T[] array = new int[length] worker.Append(worker.Create(OpCodes.Ldloc_0)); worker.Append(worker.Create(OpCodes.Newarr, elementType)); worker.Append(worker.Create(OpCodes.Stloc_1)); // loop through array and deserialize each element // generates code like this // for (int i=0; i< length ; i++) // { // value[i] = reader.ReadXXX(); // } 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, elementType)); worker.Append(worker.Create(OpCodes.Ldarg_0)); worker.Append(worker.Create(OpCodes.Call, elementReadFunc)); worker.Append(worker.Create(OpCodes.Stobj, elementType)); 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 new ArraySegment<T>(array); worker.Append(worker.Create(OpCodes.Ldloc_1)); worker.Append(worker.Create(OpCodes.Newobj, WeaverTypes.ArraySegmentConstructorReference.MakeHostInstanceGeneric(genericInstance))); 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 MethodDefinition GenerateListReadFunc(TypeReference variable, int recursionCount) { GenericInstanceType genericInstance = (GenericInstanceType)variable; TypeReference elementType = genericInstance.GenericArguments[0]; MethodReference elementReadFunc = GetReadFunc(elementType, recursionCount + 1); if (elementReadFunc == null) { Weaver.Error($"Cannot generate reader for List because element {elementType.Name} does not have a reader. Use a supported type or provide a custom reader", variable); return(null); } string functionName = "_ReadList_" + elementType.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(WeaverTypes.NetworkReaderType))); readerFunc.Body.Variables.Add(new VariableDefinition(WeaverTypes.int32Type)); readerFunc.Body.Variables.Add(new VariableDefinition(variable)); readerFunc.Body.Variables.Add(new VariableDefinition(WeaverTypes.int32Type)); readerFunc.Body.InitLocals = true; ILProcessor worker = readerFunc.Body.GetILProcessor(); // int count = reader.ReadPackedInt32(); worker.Append(worker.Create(OpCodes.Ldarg_0)); worker.Append(worker.Create(OpCodes.Call, GetReadFunc(WeaverTypes.int32Type))); worker.Append(worker.Create(OpCodes.Stloc_0)); // -1 is null list, so if count is less than 0 return null // if (count < 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); // List<T> list = new List<T>(); worker.Append(worker.Create(OpCodes.Newobj, WeaverTypes.ListConstructorReference.MakeHostInstanceGeneric(genericInstance))); worker.Append(worker.Create(OpCodes.Stloc_1)); // loop through array and deserialize each element // generates code like this // for (int i=0; i< length ; i++) // { // list[i] = reader.ReadXXX(); // } 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); MethodReference addItem = WeaverTypes.ListAddReference.MakeHostInstanceGeneric(genericInstance); // list.Add(reader.ReadT()); worker.Append(worker.Create(OpCodes.Ldloc_1)); // list worker.Append(worker.Create(OpCodes.Ldarg_0)); // reader worker.Append(worker.Create(OpCodes.Call, elementReadFunc)); // Read worker.Append(worker.Create(OpCodes.Call, addItem)); // set_Item // end for loop // for loop i++ 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); // for loop i < count 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); }
static void GenerateSerialization(TypeDefinition td) { Weaver.DLog(td, " GenerateSerialization"); MethodDefinition existingMethod = td.Methods.FirstOrDefault(md => md.Name == "Serialize"); if (existingMethod != null && !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} has field ${field} that references itself"); return; } } MethodDefinition serializeFunc = existingMethod ?? new MethodDefinition("Serialize", MethodAttributes.Public | MethodAttributes.Virtual | MethodAttributes.HideBySig, Weaver.voidType); //only add to new method if (existingMethod == null) { serializeFunc.Parameters.Add(new ParameterDefinition("writer", ParameterAttributes.None, Weaver.CurrentAssembly.MainModule.ImportReference(Weaver.NetworkWriterType))); } ILProcessor serWorker = serializeFunc.Body.GetILProcessor(); if (existingMethod != null) { //remove default nop&ret from existing empty interface method serWorker.Body.Instructions.Clear(); } //if not struct(IMessageBase), likely same as using else {} here in all cases if (!td.IsValueType) { // call base MethodReference baseSerialize = Resolvers.ResolveMethodInParents(td.BaseType, Weaver.CurrentAssembly, "Serialize"); if (baseSerialize != null) { // base serWorker.Append(serWorker.Create(OpCodes.Ldarg_0)); // writer serWorker.Append(serWorker.Create(OpCodes.Ldarg_1)); serWorker.Append(serWorker.Create(OpCodes.Call, baseSerialize)); } } foreach (FieldDefinition field in td.Fields) { if (field.IsStatic || field.IsPrivate || field.IsSpecialName) { continue; } MethodReference writeFunc = Writers.GetWriteFunc(field.FieldType); if (writeFunc != null) { serWorker.Append(serWorker.Create(OpCodes.Ldarg_1)); serWorker.Append(serWorker.Create(OpCodes.Ldarg_0)); serWorker.Append(serWorker.Create(OpCodes.Ldfld, field)); serWorker.Append(serWorker.Create(OpCodes.Call, writeFunc)); } else { Weaver.Error($"{field} has unsupported type"); return; } } serWorker.Append(serWorker.Create(OpCodes.Ret)); //only add if not just replaced body if (existingMethod == null) { td.Methods.Add(serializeFunc); } }
static MethodDefinition GenerateStructWriterFunction(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; } if (field.FieldType.Resolve().HasGenericParameters) { Weaver.Error($"{field} has unsupported type. Create a derived class instead of using generics"); return(null); } if (field.FieldType.Resolve().IsInterface) { Weaver.Error($"{field} has unsupported type. Use a concrete class instead of an interface"); return(null); } 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); }
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) { foreach (CustomAttribute ca in fd.CustomAttributes) { if (ca.AttributeType.FullName == Weaver.SyncVarType.FullName) { TypeDefinition resolvedField = fd.FieldType.Resolve(); if (resolvedField.IsDerivedFrom(Weaver.NetworkBehaviourType)) { Weaver.Error($"{fd} has invalid type. SyncVars cannot be NetworkBehaviours"); return; } if (resolvedField.IsDerivedFrom(Weaver.ScriptableObjectType)) { Weaver.Error($"{fd} has invalid type. SyncVars cannot be scriptable objects"); return; } if ((fd.Attributes & FieldAttributes.Static) != 0) { Weaver.Error($"{fd} cannot be static"); return; } if (resolvedField.HasGenericParameters) { Weaver.Error($"{fd} has invalid type. SyncVars cannot have generic parameters"); return; } if (resolvedField.IsInterface) { Weaver.Error($"{fd} has invalid type. Use a concrete type instead of interface {fd.FieldType}"); return; } string fieldModuleName = resolvedField.Module.Name; if (fieldModuleName != Weaver.CurrentAssembly.MainModule.Name && fieldModuleName != Weaver.UnityAssembly.MainModule.Name && fieldModuleName != Weaver.NetAssembly.MainModule.Name && fieldModuleName != Weaver.CorLibModule.Name && fieldModuleName != "System.Runtime.dll" && // this is only for Metro, built-in types are not in corlib on metro fieldModuleName != "netstandard.dll" // handle built-in types when weaving new C#7 compiler assemblies ) { Weaver.Error($"{fd} has invalid type. Use a type defined in the same module {fd.Module}"); return; } if (fd.FieldType.IsArray) { Weaver.Error($"{fd} has invalid type. Use SyncLists instead of arrays"); return; } if (SyncObjectInitializer.ImplementsSyncObject(fd.FieldType)) { Log.Warning($"{fd} has [SyncVar] attribute. SyncLists should not be marked with SyncVar"); break; } syncVars.Add(fd); ProcessSyncVar(td, fd, syncVarNetIds, 1L << dirtyBitCounter); dirtyBitCounter += 1; numSyncVars += 1; if (dirtyBitCounter == SyncVarLimit) { Weaver.Error($"{td} has too many SyncVars. Consider refactoring your class into multiple components"); return; } break; } } if (fd.FieldType.Resolve().ImplementsInterface(Weaver.SyncObjectType)) { if (fd.IsStatic) { Weaver.Error($"{fd} cannot be static"); 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); }
static MethodDefinition GenerateArrayWriteFunc(TypeReference variable, int recursionCount) { if (!variable.IsArrayType()) { Weaver.Error($"{variable.Name} is an unsupported type. Jagged and multidimensional arrays are not supported", variable); return(null); } TypeReference elementType = variable.GetElementType(); MethodReference elementWriteFunc = GetWriteFunc(elementType, recursionCount + 1); MethodReference intWriterFunc = GetWriteFunc(WeaverTypes.int32Type); 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); } 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, WeaverTypes.voidType); writerFunc.Parameters.Add(new ParameterDefinition("writer", ParameterAttributes.None, Weaver.CurrentAssembly.MainModule.ImportReference(WeaverTypes.NetworkWriterType))); writerFunc.Parameters.Add(new ParameterDefinition("value", ParameterAttributes.None, Weaver.CurrentAssembly.MainModule.ImportReference(variable))); writerFunc.Body.Variables.Add(new VariableDefinition(WeaverTypes.int32Type)); writerFunc.Body.Variables.Add(new VariableDefinition(WeaverTypes.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, 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); }
static bool Weave(string assName, IEnumerable <string> dependencies, IAssemblyResolver assemblyResolver, string unityEngineDLLPath, string mirrorNetDLLPath, string outputDir) { using (DefaultAssemblyResolver asmResolver = new DefaultAssemblyResolver()) using (CurrentAssembly = AssemblyDefinition.ReadAssembly(assName, new ReaderParameters { ReadWrite = true, AssemblyResolver = asmResolver })) { asmResolver.AddSearchDirectory(Path.GetDirectoryName(assName)); asmResolver.AddSearchDirectory(Helpers.UnityEngineDLLDirectoryName()); asmResolver.AddSearchDirectory(Path.GetDirectoryName(unityEngineDLLPath)); asmResolver.AddSearchDirectory(Path.GetDirectoryName(mirrorNetDLLPath)); if (dependencies != null) { foreach (string path in dependencies) { asmResolver.AddSearchDirectory(path); } } SetupTargetTypes(); Readers.Init(CurrentAssembly); Writers.Init(CurrentAssembly); ModuleDefinition moduleDefinition = CurrentAssembly.MainModule; Console.WriteLine("Script Module: {0}", moduleDefinition.Name); // Process each NetworkBehaviour bool didWork = false; // We need to do 2 passes, because SyncListStructs might be referenced from other modules, so we must make sure we generate them first. for (int pass = 0; pass < 2; pass++) { System.Diagnostics.Stopwatch watch = System.Diagnostics.Stopwatch.StartNew(); foreach (TypeDefinition td in moduleDefinition.Types) { if (td.IsClass && td.BaseType.CanBeResolved()) { try { if (pass == 0) { didWork |= CheckSyncList(td); } else { didWork |= CheckNetworkBehaviour(td); didWork |= CheckMessageBase(td); } } catch (Exception ex) { Weaver.Error(ex.Message); throw ex; } } if (WeavingFailed) { return(false); } } watch.Stop(); Console.WriteLine("Pass: "******" took " + watch.ElapsedMilliseconds + " milliseconds"); } if (didWork) { // this must be done for ALL code, not just NetworkBehaviours try { PropertySiteProcessor.ProcessSitesModule(CurrentAssembly.MainModule); } catch (Exception e) { Log.Error("ProcessPropertySites exception: " + e); return(false); } if (WeavingFailed) { //Log.Error("Failed phase II."); return(false); } // write to outputDir if specified, otherwise perform in-place write if (outputDir != null) { CurrentAssembly.Write(Helpers.DestinationFileFor(outputDir, assName)); } else { CurrentAssembly.Write(); } } } return(true); }
static MethodDefinition GenerateWriter(TypeReference variableReference, int recursionCount = 0) { // TODO: do we need this check? do we ever receieve types that are "ByReference"s if (variableReference.IsByReference) { // error?? Weaver.Error($"Cannot pass {variableReference.Name} by reference", variableReference); return(null); } // Arrays are special, if we resolve them, we get the element type, // eg int[] resolves to int // therefore process this before checks below if (variableReference.IsArray) { return(GenerateArrayWriteFunc(variableReference, recursionCount)); } // check for collections if (variableReference.IsArraySegment()) { return(GenerateArraySegmentWriteFunc(variableReference, recursionCount)); } if (variableReference.IsList()) { return(GenerateListWriteFunc(variableReference, recursionCount)); } // check for invalid types TypeDefinition variableDefinition = variableReference.Resolve(); if (variableDefinition == null) { Weaver.Error($"{variableReference.Name} is not a supported type. Use a supported type or provide a custom writer", variableReference); return(null); } if (variableDefinition.IsDerivedFrom(WeaverTypes.ComponentType)) { Weaver.Error($"Cannot generate writer for component type {variableReference.Name}. Use a supported type or provide a custom writer", variableReference); return(null); } if (variableReference.FullName == WeaverTypes.ObjectType.FullName) { Weaver.Error($"Cannot generate writer for {variableReference.Name}. Use a supported type or provide a custom writer", variableReference); return(null); } if (variableReference.FullName == WeaverTypes.ScriptableObjectType.FullName) { Weaver.Error($"Cannot generate writer for {variableReference.Name}. Use a supported type or provide a custom writer", variableReference); return(null); } if (variableDefinition.HasGenericParameters) { Weaver.Error($"Cannot generate writer for generic type {variableReference.Name}. Use a supported type or provide a custom writer", variableReference); return(null); } if (variableDefinition.IsInterface) { Weaver.Error($"Cannot generate writer for interface {variableReference.Name}. Use a supported type or provide a custom writer", variableReference); return(null); } if (variableDefinition.IsAbstract) { Weaver.Error($"Cannot generate writer for abstract class {variableReference.Name}. Use a supported type or provide a custom writer", variableReference); return(null); } // generate writer for class/struct return(GenerateClassOrStructWriterFunction(variableReference, recursionCount)); }
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) { foreach (var ca in fd.CustomAttributes) { if (ca.AttributeType.FullName == Weaver.SyncVarType.FullName) { var resolvedField = fd.FieldType.Resolve(); if (resolvedField.IsDerivedFrom(Weaver.NetworkBehaviourType)) { Weaver.Error("SyncVar [" + fd.FullName + "] cannot be derived from NetworkBehaviour."); return; } if (resolvedField.IsDerivedFrom(Weaver.ScriptableObjectType)) { Weaver.Error("SyncVar [" + fd.FullName + "] cannot be derived from ScriptableObject."); return; } if ((fd.Attributes & FieldAttributes.Static) != 0) { Weaver.Error("SyncVar [" + fd.FullName + "] cannot be static."); return; } if (resolvedField.HasGenericParameters) { Weaver.Error("SyncVar [" + fd.FullName + "] cannot have generic parameters."); return; } if (resolvedField.IsInterface) { Weaver.Error("SyncVar [" + fd.FullName + "] cannot be an interface."); return; } var fieldModuleName = resolvedField.Module.Name; if (fieldModuleName != Weaver.CurrentAssembly.MainModule.Name && fieldModuleName != Weaver.UnityAssembly.MainModule.Name && fieldModuleName != Weaver.NetAssembly.MainModule.Name && fieldModuleName != Weaver.CorLibModule.Name && fieldModuleName != "System.Runtime.dll" && // this is only for Metro, built-in types are not in corlib on metro fieldModuleName != "netstandard.dll" // handle built-in types when weaving new C#7 compiler assemblies ) { Weaver.Error("SyncVar [" + fd.FullName + "] from " + resolvedField.Module.ToString() + " cannot be a different module."); return; } if (fd.FieldType.IsArray) { Weaver.Error("SyncVar [" + fd.FullName + "] cannot be an array. Use a SyncList instead."); return; } if (SyncObjectInitializer.ImplementsSyncObject(fd.FieldType)) { Log.Warning(string.Format("Script class [{0}] has [SyncVar] attribute on SyncList field {1}, SyncLists should not be marked with SyncVar.", td.FullName, fd.Name)); break; } syncVars.Add(fd); ProcessSyncVar(td, fd, syncVarNetIds, 1L << dirtyBitCounter); dirtyBitCounter += 1; numSyncVars += 1; if (dirtyBitCounter == k_SyncVarLimit) { Weaver.Error("Script class [" + td.FullName + "] has too many SyncVars (" + k_SyncVarLimit + "). (This could include base classes)"); return; } break; } } if (fd.FieldType.FullName.Contains("Mirror.SyncListStruct")) { Weaver.Error("SyncListStruct member variable [" + fd.FullName + "] must use a dervied class, like \"class MySyncList : SyncListStruct<MyStruct> {}\"."); return; } if (fd.FieldType.Resolve().ImplementsInterface(Weaver.SyncObjectType)) { if (fd.IsStatic) { Weaver.Error("SyncList [" + td.FullName + ":" + fd.FullName + "] cannot be a static"); 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); }
/// <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(Weaver.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); } }
static MethodDefinition GenerateArrayReadFunc(TypeReference variable, int recursionCount) { if (!variable.IsArrayType()) { Weaver.Error($"{variable.Name} is an unsupported type. Jagged and multidimensional arrays are not supported", variable); return(null); } TypeReference elementType = variable.GetElementType(); MethodReference elementReadFunc = GetReadFunc(elementType, recursionCount + 1); if (elementReadFunc == null) { Weaver.Error($"Cannot generate reader for Array because element {elementType.Name} does not have a reader. Use a supported type or provide a custom reader", variable); 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(WeaverTypes.NetworkReaderType))); readerFunc.Body.Variables.Add(new VariableDefinition(WeaverTypes.int32Type)); readerFunc.Body.Variables.Add(new VariableDefinition(variable)); readerFunc.Body.Variables.Add(new VariableDefinition(WeaverTypes.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(WeaverTypes.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); }
void GenerateSerialization() { Weaver.DLog(netBehaviourSubclass, " GenerateSerialization"); foreach (MethodDefinition m in netBehaviourSubclass.Methods) { if (m.Name == "OnSerialize") { return; } } if (syncVars.Count == 0) { // no synvars, no need for custom OnSerialize return; } MethodDefinition serialize = new MethodDefinition("OnSerialize", MethodAttributes.Public | MethodAttributes.Virtual | MethodAttributes.HideBySig, Weaver.boolType); serialize.Parameters.Add(new ParameterDefinition("writer", ParameterAttributes.None, Weaver.CurrentAssembly.MainModule.ImportReference(Weaver.NetworkWriterType))); serialize.Parameters.Add(new ParameterDefinition("forceAll", ParameterAttributes.None, Weaver.boolType)); ILProcessor serWorker = serialize.Body.GetILProcessor(); serialize.Body.InitLocals = true; // loc_0, this local variable is to determine if any variable was dirty VariableDefinition dirtyLocal = new VariableDefinition(Weaver.boolType); serialize.Body.Variables.Add(dirtyLocal); MethodReference baseSerialize = Resolvers.ResolveMethodInParents(netBehaviourSubclass.BaseType, Weaver.CurrentAssembly, "OnSerialize"); if (baseSerialize != null) { serWorker.Append(serWorker.Create(OpCodes.Ldarg_0)); // base serWorker.Append(serWorker.Create(OpCodes.Ldarg_1)); // writer serWorker.Append(serWorker.Create(OpCodes.Ldarg_2)); // forceAll serWorker.Append(serWorker.Create(OpCodes.Call, baseSerialize)); serWorker.Append(serWorker.Create(OpCodes.Stloc_0)); // set dirtyLocal to result of base.OnSerialize() } // Generates: if (forceAll); Instruction initialStateLabel = serWorker.Create(OpCodes.Nop); serWorker.Append(serWorker.Create(OpCodes.Ldarg_2)); // forceAll serWorker.Append(serWorker.Create(OpCodes.Brfalse, initialStateLabel)); foreach (FieldDefinition syncVar in syncVars) { // Generates a writer call for each sync variable serWorker.Append(serWorker.Create(OpCodes.Ldarg_1)); // writer serWorker.Append(serWorker.Create(OpCodes.Ldarg_0)); // this serWorker.Append(serWorker.Create(OpCodes.Ldfld, syncVar)); MethodReference writeFunc = Writers.GetWriteFunc(syncVar.FieldType); if (writeFunc != null) { serWorker.Append(serWorker.Create(OpCodes.Call, writeFunc)); } else { Weaver.Error($"{syncVar} has unsupported type. Use a supported Mirror type instead"); return; } } // always return true if forceAll // Generates: return true serWorker.Append(serWorker.Create(OpCodes.Ldc_I4_1)); serWorker.Append(serWorker.Create(OpCodes.Ret)); // Generates: end if (forceAll); serWorker.Append(initialStateLabel); // write dirty bits before the data fields // Generates: writer.WritePackedUInt64 (base.get_syncVarDirtyBits ()); serWorker.Append(serWorker.Create(OpCodes.Ldarg_1)); // writer serWorker.Append(serWorker.Create(OpCodes.Ldarg_0)); // base serWorker.Append(serWorker.Create(OpCodes.Call, Weaver.NetworkBehaviourDirtyBitsReference)); serWorker.Append(serWorker.Create(OpCodes.Call, Writers.GetWriteFunc(Weaver.uint64Type))); // 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 = serWorker.Create(OpCodes.Nop); // Generates: if ((base.get_syncVarDirtyBits() & 1uL) != 0uL) serWorker.Append(serWorker.Create(OpCodes.Ldarg_0)); // base serWorker.Append(serWorker.Create(OpCodes.Call, Weaver.NetworkBehaviourDirtyBitsReference)); serWorker.Append(serWorker.Create(OpCodes.Ldc_I8, 1L << dirtyBit)); // 8 bytes = long serWorker.Append(serWorker.Create(OpCodes.And)); serWorker.Append(serWorker.Create(OpCodes.Brfalse, varLabel)); // Generates a call to the writer for that field serWorker.Append(serWorker.Create(OpCodes.Ldarg_1)); // writer serWorker.Append(serWorker.Create(OpCodes.Ldarg_0)); // base serWorker.Append(serWorker.Create(OpCodes.Ldfld, syncVar)); MethodReference writeFunc = Writers.GetWriteFunc(syncVar.FieldType); if (writeFunc != null) { serWorker.Append(serWorker.Create(OpCodes.Call, writeFunc)); } else { Weaver.Error($"{syncVar} has unsupported type. Use a supported Mirror type instead"); return; } // something was dirty serWorker.Append(serWorker.Create(OpCodes.Ldc_I4_1)); serWorker.Append(serWorker.Create(OpCodes.Stloc_0)); // set dirtyLocal to true serWorker.Append(varLabel); dirtyBit += 1; } if (Weaver.GenerateLogErrors) { serWorker.Append(serWorker.Create(OpCodes.Ldstr, "Injected Serialize " + netBehaviourSubclass.Name)); serWorker.Append(serWorker.Create(OpCodes.Call, Weaver.logErrorReference)); } // generate: return dirtyLocal serWorker.Append(serWorker.Create(OpCodes.Ldloc_0)); serWorker.Append(serWorker.Create(OpCodes.Ret)); netBehaviourSubclass.Methods.Add(serialize); }
public static MethodReference GetReadFunc(TypeReference variableReference, int recursionCount = 0) { if (readFuncs.TryGetValue(variableReference.FullName, out MethodReference foundFunc)) { return(foundFunc); } MethodDefinition newReaderFunc; // Arrays are special, if we resolve them, we get teh 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 (variableReference.IsArray) { newReaderFunc = GenerateArrayReadFunc(variableReference, recursionCount); if (newReaderFunc != null) { RegisterReadFunc(variableReference.FullName, newReaderFunc); } return(newReaderFunc); } TypeDefinition variableDefinition = variableReference.Resolve(); if (variableDefinition == null) { Weaver.Error($"{variableReference.Name} is not a supported type", variableReference); return(null); } if (variableDefinition.IsDerivedFrom(WeaverTypes.ComponentType)) { Weaver.Error($"Cannot generate reader for component type {variableReference.Name}. Use a supported type or provide a custom reader", variableReference); return(null); } if (variableReference.FullName == WeaverTypes.ObjectType.FullName) { Weaver.Error($"Cannot generate reader for {variableReference.Name}. Use a supported type or provide a custom reader", variableReference); return(null); } if (variableReference.FullName == WeaverTypes.ScriptableObjectType.FullName) { Weaver.Error($"Cannot generate reader for {variableReference.Name}. Use a supported type or provide a custom reader", variableReference); return(null); } if (variableReference.IsByReference) { // error?? Weaver.Error($"Cannot pass type {variableReference.Name} by reference", variableReference); return(null); } if (variableDefinition.HasGenericParameters && !variableDefinition.IsArraySegment() && !variableDefinition.IsList()) { Weaver.Error($"Cannot generate reader for generic variable {variableReference.Name}. Use a supported type or provide a custom reader", variableReference); return(null); } if (variableDefinition.IsInterface) { Weaver.Error($"Cannot generate reader for interface {variableReference.Name}. Use a supported type or provide a custom reader", variableReference); return(null); } if (variableDefinition.IsAbstract) { Weaver.Error($"Cannot generate reader for abstract class {variableReference.Name}. Use a supported type or provide a custom reader", variableReference); return(null); } if (variableDefinition.IsEnum) { return(GetReadFunc(variableDefinition.GetEnumUnderlyingType(), recursionCount)); } else if (variableDefinition.IsArraySegment()) { newReaderFunc = GenerateArraySegmentReadFunc(variableReference, recursionCount); } else if (variableDefinition.IsList()) { newReaderFunc = GenerateListReadFunc(variableReference, recursionCount); } else { newReaderFunc = GenerateClassOrStructReadFunction(variableReference, recursionCount); } if (newReaderFunc == null) { Weaver.Error($"{variableReference.Name} is not a supported type", variableReference); return(null); } RegisterReadFunc(variableReference.FullName, newReaderFunc); return(newReaderFunc); }
static MethodDefinition GenerateStructReadFunction(TypeReference variable, int recursionCount) { if (recursionCount > MaxRecursionCount) { Weaver.Error($"{variable} can't be deserialized because it references itself"); return(null); } if (!Weaver.IsValidTypeToGenerate(variable.Resolve())) { return(null); } string functionName = "_Read" + variable.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); // create local for return value readerFunc.Body.Variables.Add(new VariableDefinition(variable)); readerFunc.Body.InitLocals = true; readerFunc.Parameters.Add(new ParameterDefinition("reader", ParameterAttributes.None, Weaver.CurrentAssembly.MainModule.ImportReference(Weaver.NetworkReaderType))); ILProcessor worker = readerFunc.Body.GetILProcessor(); if (variable.IsValueType) { // structs are created with Initobj worker.Append(worker.Create(OpCodes.Ldloca, 0)); worker.Append(worker.Create(OpCodes.Initobj, variable)); } else { // classes are created with their constructor MethodDefinition ctor = Resolvers.ResolveDefaultPublicCtor(variable); if (ctor == null) { Weaver.Error($"{variable} can't be deserialized bcause i has no default constructor"); return(null); } worker.Append(worker.Create(OpCodes.Newobj, ctor)); worker.Append(worker.Create(OpCodes.Stloc_0)); } uint fields = 0; foreach (FieldDefinition field in variable.Resolve().Fields) { if (field.IsStatic || field.IsPrivate) { continue; } // mismatched ldloca/ldloc for struct/class combinations is invalid IL, which causes crash at runtime OpCode opcode = variable.IsValueType ? OpCodes.Ldloca : OpCodes.Ldloc; worker.Append(worker.Create(opcode, 0)); MethodReference readFunc = GetReadFunc(field.FieldType, recursionCount + 1); if (readFunc != null) { worker.Append(worker.Create(OpCodes.Ldarg_0)); worker.Append(worker.Create(OpCodes.Call, readFunc)); } else { Weaver.Error($"{field} has an unsupported type"); return(null); } worker.Append(worker.Create(OpCodes.Stfld, field)); fields++; } if (fields == 0) { Log.Warning($"{variable} has no public or non-static fields to deserialize"); } worker.Append(worker.Create(OpCodes.Ldloc_0)); worker.Append(worker.Create(OpCodes.Ret)); return(readerFunc); }
public static MethodReference GetReadFunc(TypeReference variable, int recursionCount = 0) { if (recursionCount > MaxRecursionCount) { Weaver.Error("GetReadFunc recursion depth exceeded for " + variable.Name + ". Check for self-referencing member variables."); return(null); } if (readFuncs.TryGetValue(variable.FullName, out MethodReference foundFunc)) { if (foundFunc.ReturnType.IsArray == variable.IsArray) { return(foundFunc); } } TypeDefinition td = variable.Resolve(); if (td == null) { Weaver.Error("GetReadFunc unsupported type " + variable.FullName); return(null); } if (variable.IsByReference) { // error?? Weaver.Error("GetReadFunc variable.IsByReference error."); return(null); } MethodDefinition newReaderFunc; if (variable.IsArray) { TypeReference elementType = variable.GetElementType(); MethodReference elementReadFunc = GetReadFunc(elementType, recursionCount + 1); if (elementReadFunc == null) { return(null); } newReaderFunc = GenerateArrayReadFunc(variable, elementReadFunc); } else { if (td.IsEnum) { return(Weaver.NetworkReaderReadInt32); } newReaderFunc = GenerateStructReadFunction(variable, recursionCount); } if (newReaderFunc == null) { Weaver.Error("GetReadFunc unable to generate function for:" + variable.FullName); return(null); } RegisterReadFunc(variable.FullName, newReaderFunc); return(newReaderFunc); }