using FishNet.CodeGenerating.Extension; using FishNet.CodeGenerating.Helping.Extension; using FishNet.CodeGenerating.ILCore; using FishNet.Object; using FishNet.Serializing; using FishNet.Utility.Performance; using MonoFN.Cecil; using MonoFN.Cecil.Cil; using MonoFN.Cecil.Rocks; using System; using System.Collections.Generic; using SR = System.Reflection; using UnityDebug = UnityEngine.Debug; namespace FishNet.CodeGenerating.Helping { internal class WriterProcessor : CodegenBase { #region Reflection references. public readonly Dictionary InstancedWriterMethods = new Dictionary(); public readonly Dictionary StaticWriterMethods = new Dictionary(); public HashSet AutoPackedMethods = new HashSet(new TypeReferenceComparer()); public TypeDefinition GeneratedWriterClassTypeDef; public MethodDefinition GeneratedWriterOnLoadMethodDef; #endregion #region Misc. /// /// TypeReferences which have already had delegates made for. /// private HashSet _delegatedTypes = new HashSet(); #endregion #region Const. /// /// Namespace to use for generated serializers and delegates. /// public const string GENERATED_WRITER_NAMESPACE = "FishNet.Serializing.Generated"; /// /// Name to use for generated serializers class. /// public const string GENERATED_WRITERS_CLASS_NAME = "GeneratedWriters___Internal"; /// /// Attributes to use for generated serializers class. /// public const TypeAttributes GENERATED_TYPE_ATTRIBUTES = (TypeAttributes.BeforeFieldInit | TypeAttributes.Class | TypeAttributes.AnsiClass | TypeAttributes.Public | TypeAttributes.AutoClass | TypeAttributes.Abstract | TypeAttributes.Sealed); /// /// Name to use for InitializeOnce method. /// public const string INITIALIZEONCE_METHOD_NAME = "InitializeOnce"; /// /// Attributes to use for InitializeOnce method within generated serializer classes. /// public const MethodAttributes INITIALIZEONCE_METHOD_ATTRIBUTES = (MethodAttributes.Static | MethodAttributes.Private | MethodAttributes.HideBySig); /// /// Attritbutes to use for generated serializers. /// public const MethodAttributes GENERATED_METHOD_ATTRIBUTES = (MethodAttributes.Static | MethodAttributes.Public | MethodAttributes.HideBySig); /// /// Prefix all built-in and user created write methods should begin with. /// internal const string WRITE_PREFIX = "Write"; /// /// Prefix all built-in and user created write methods should begin with. /// internal const string GENERATED_WRITE_PREFIX = "Write___"; /// /// Types to exclude from being scanned for auto serialization. /// public static readonly System.Type[] EXCLUDED_AUTO_SERIALIZER_TYPES = new System.Type[] { typeof(NetworkBehaviour) }; /// /// Types within assemblies which begin with these prefixes will not have serializers created for them. /// public static readonly string[] EXCLUDED_ASSEMBLY_PREFIXES = new string[] { "UnityEngine." }; #endregion public override bool ImportReferences() => true; /// /// Processes data. To be used after everything else has called ImportReferences. /// /// public bool Process() { GeneralHelper gh = base.GetClass(); CreateGeneratedClassData(); FindInstancedWriters(); CreateInstancedWriterExtensions(); //Creates class for generated writers, and init on load method. void CreateGeneratedClassData() { GeneratedWriterClassTypeDef = gh.GetOrCreateClass(out _, GENERATED_TYPE_ATTRIBUTES, GENERATED_WRITERS_CLASS_NAME, null, WriterProcessor.GENERATED_WRITER_NAMESPACE); /* If constructor isn't set then try to get or create it * and also add it to methods if were created. */ GeneratedWriterOnLoadMethodDef = gh.GetOrCreateMethod(GeneratedWriterClassTypeDef, out _, INITIALIZEONCE_METHOD_ATTRIBUTES, INITIALIZEONCE_METHOD_NAME, base.Module.TypeSystem.Void); ILProcessor pp = GeneratedWriterOnLoadMethodDef.Body.GetILProcessor(); pp.Emit(OpCodes.Ret); gh.CreateRuntimeInitializeOnLoadMethodAttribute(GeneratedWriterOnLoadMethodDef); } //Finds all instanced writers and autopack types. void FindInstancedWriters() { Type pooledWriterType = typeof(PooledWriter); foreach (SR.MethodInfo methodInfo in pooledWriterType.GetMethods()) { if (IsSpecialWriteMethod(methodInfo)) continue; bool autoPackMethod; if (IsIgnoredWriteMethod(methodInfo, out autoPackMethod)) continue; MethodReference methodRef = base.ImportReference(methodInfo); /* TypeReference for the first parameter in the write method. * The first parameter will always be the type written. */ TypeReference typeRef = base.ImportReference(methodRef.Parameters[0].ParameterType); /* If here all checks pass. */ AddWriterMethod(typeRef, methodRef, true, true); if (autoPackMethod) AutoPackedMethods.Add(typeRef); } } return true; } /// /// Returns if a MethodInfo is considered a special write method. /// Special write methods have declared references within this class, and will not have extensions made for them. /// public bool IsSpecialWriteMethod(SR.MethodInfo methodInfo) { /* Special methods. */ if (methodInfo.Name == nameof(PooledWriter.Dispose)) return true; else if (methodInfo.Name == nameof(PooledWriter.WritePackedWhole)) return true; else if (methodInfo.Name == nameof(PooledWriter.WriteDictionary)) return true; else if (methodInfo.Name == nameof(PooledWriter.WriteList)) return true; return false; } /// /// Returns if a write method should be ignored. /// public bool IsIgnoredWriteMethod(SR.MethodInfo methodInfo, out bool autoPackMethod) { autoPackMethod = false; if (base.GetClass().CodegenExclude(methodInfo)) return true; //Not long enough to be a write method. else if (methodInfo.Name.Length < WRITE_PREFIX.Length) return true; //Method name doesn't start with writePrefix. else if (methodInfo.Name.Substring(0, WRITE_PREFIX.Length) != WRITE_PREFIX) return true; SR.ParameterInfo[] parameterInfos = methodInfo.GetParameters(); /* No parameters or more than 2 parameters. Most Write methods * will have only 1 parameter but some will have 2 if * there is a pack option. */ if (parameterInfos.Length < 1 || parameterInfos.Length > 2) return true; /* If two parameters make sure the second parameter * is a pack parameter. */ if (parameterInfos.Length == 2) { autoPackMethod = (parameterInfos[1].ParameterType == typeof(AutoPackType)); if (!autoPackMethod) return true; } return false; } /// /// Creates writer extension methods for built-in writers. /// private void CreateInstancedWriterExtensions() { //return; if (!FishNetILPP.IsFishNetAssembly(base.Session)) return; GeneralHelper gh = base.GetClass(); WriterProcessor gwh = base.GetClass(); //List staticReaders = new List(); foreach (KeyValuePair item in InstancedWriterMethods) { MethodReference instancedWriteMr = item.Value; if (instancedWriteMr.HasGenericParameters) continue; TypeReference valueTr = instancedWriteMr.Parameters[0].ParameterType; MethodDefinition md = new MethodDefinition($"InstancedExtension___{instancedWriteMr.Name}", WriterProcessor.GENERATED_METHOD_ATTRIBUTES, base.Module.TypeSystem.Void); //Add extension parameter. ParameterDefinition writerPd = gh.CreateParameter(md, typeof(Writer), "writer"); //Add parameters needed by instanced writer. List otherPds = md.CreateParameters(base.Session, instancedWriteMr); gh.MakeExtensionMethod(md); // gwh.GeneratedWriterClassTypeDef.Methods.Add(md); ILProcessor processor = md.Body.GetILProcessor(); //Load writer. processor.Emit(OpCodes.Ldarg, writerPd); //Load args. foreach (ParameterDefinition pd in otherPds) processor.Emit(OpCodes.Ldarg, pd); //Call instanced. processor.Emit(instancedWriteMr.GetCallOpCode(base.Session), instancedWriteMr); processor.Emit(OpCodes.Ret); AddWriterMethod(valueTr, md, false, true); } } /// /// Adds typeRef, methodDef to Instanced or Static write methods. /// public void AddWriterMethod(TypeReference typeRef, MethodReference methodRef, bool instanced, bool useAdd) { Dictionary dict = (instanced) ? InstancedWriterMethods : StaticWriterMethods; string fullName = typeRef.GetFullnameWithoutBrackets(); if (useAdd) dict.Add(fullName, methodRef); else dict[fullName] = methodRef; } /// /// Removes typeRef from Instanced or Static write methods. /// internal void RemoveWriterMethod(TypeReference typeRef, bool instanced) { Dictionary dict = (instanced) ? InstancedWriterMethods : StaticWriterMethods; dict.Remove(typeRef.FullName); } /// /// Returns if typeRef supports auto packing. /// public bool IsAutoPackedType(TypeReference typeRef) { return AutoPackedMethods.Contains(typeRef); } /// /// Creates Write delegates for known static methods. /// public void CreateStaticMethodDelegates() { foreach (KeyValuePair item in StaticWriterMethods) base.GetClass().CreateStaticMethodWriteDelegate(item.Value); } /// /// Creates a Write delegate for writeMethodRef and places it within the generated reader/writer constructor. /// /// private void CreateStaticMethodWriteDelegate(MethodReference writeMr) { GeneralHelper gh = base.GetClass(); WriterImports wi = base.GetClass(); //Check if ret already exist, if so remove it; ret will be added on again in this method. if (GeneratedWriterOnLoadMethodDef.Body.Instructions.Count != 0) { int lastIndex = (GeneratedWriterOnLoadMethodDef.Body.Instructions.Count - 1); if (GeneratedWriterOnLoadMethodDef.Body.Instructions[lastIndex].OpCode == OpCodes.Ret) GeneratedWriterOnLoadMethodDef.Body.Instructions.RemoveAt(lastIndex); } ILProcessor processor = GeneratedWriterOnLoadMethodDef.Body.GetILProcessor(); TypeReference dataTypeRef; dataTypeRef = writeMr.Parameters[1].ParameterType; //Check if writer already exist. if (_delegatedTypes.Contains(dataTypeRef)) { base.LogError($"Generic write already created for {dataTypeRef.FullName}."); return; } else { _delegatedTypes.Add(dataTypeRef); } /* Create a Action delegate. * May also be Action delegate * for packed types. */ processor.Emit(OpCodes.Ldnull); processor.Emit(OpCodes.Ldftn, writeMr); GenericInstanceType actionGenericInstance; MethodReference actionConstructorInstanceMethodRef; bool isAutoPacked = base.GetClass().IsAutoPackedType(dataTypeRef); //Generate for auto pack type. if (isAutoPacked) { actionGenericInstance = gh.ActionT3_TypeRef.MakeGenericInstanceType(wi.WriterTypeRef, dataTypeRef, base.GetClass().AutoPackTypeRef); actionConstructorInstanceMethodRef = gh.ActionT3Constructor_MethodRef.MakeHostInstanceGeneric(base.Session, actionGenericInstance); } //Generate for normal type. else { actionGenericInstance = gh.ActionT2_TypeRef.MakeGenericInstanceType(wi.WriterTypeRef, dataTypeRef); actionConstructorInstanceMethodRef = gh.ActionT2Constructor_MethodRef.MakeHostInstanceGeneric(base.Session, actionGenericInstance); } processor.Emit(OpCodes.Newobj, actionConstructorInstanceMethodRef); //Call delegate to GenericWriter.Write GenericInstanceType genericInstance = wi.GenericWriterTypeRef.MakeGenericInstanceType(dataTypeRef); MethodReference genericrWriteMethodRef = (isAutoPacked) ? wi.WriteAutoPackGetSetMethodRef.MakeHostInstanceGeneric(base.Session, genericInstance) : wi.WriteGetSetMethodRef.MakeHostInstanceGeneric(base.Session, genericInstance); processor.Emit(OpCodes.Call, genericrWriteMethodRef); processor.Emit(OpCodes.Ret); } /// /// Returns if typeRef has a serializer. /// /// /// internal bool HasSerializer(TypeReference typeRef, bool createMissing) { bool result = (GetInstancedWriteMethodReference(typeRef) != null) || (GetStaticWriteMethodReference(typeRef) != null); if (!result && createMissing) { if (!base.GetClass().HasNonSerializableAttribute(typeRef.CachedResolve(base.Session))) { MethodReference methodRef = CreateWriter(typeRef); result = (methodRef != null); } } return result; } #region GetWriterMethodReference. /// /// Returns the MethodReference for typeRef. /// /// /// internal MethodReference GetInstancedWriteMethodReference(TypeReference typeRef) { string fullName = typeRef.GetFullnameWithoutBrackets(); InstancedWriterMethods.TryGetValue(fullName, out MethodReference methodRef); return methodRef; } /// /// Returns the MethodReference for typeRef. /// /// /// internal MethodReference GetStaticWriteMethodReference(TypeReference typeRef) { string fullName = typeRef.GetFullnameWithoutBrackets(); StaticWriterMethods.TryGetValue(fullName, out MethodReference methodRef); return methodRef; } /// /// Returns the MethodReference for typeRef favoring instanced or static. /// /// /// /// internal MethodReference GetWriteMethodReference(TypeReference typeRef) { bool favorInstanced = false; MethodReference result; if (favorInstanced) { result = GetInstancedWriteMethodReference(typeRef); if (result == null) result = GetStaticWriteMethodReference(typeRef); } else { result = GetStaticWriteMethodReference(typeRef); if (result == null) result = GetInstancedWriteMethodReference(typeRef); } return result; } /// /// Gets the write MethodRef for typeRef, or tries to create it if not present. /// /// /// internal MethodReference GetOrCreateWriteMethodReference(TypeReference typeRef) { #pragma warning disable CS0219 bool favorInstanced = false; #pragma warning restore CS0219 //Try to get existing writer, if not present make one. MethodReference writeMethodRef = GetWriteMethodReference(typeRef); if (writeMethodRef == null) writeMethodRef = CreateWriter(typeRef); //If still null then return could not be generated. if (writeMethodRef == null) { base.LogError($"Could not create serializer for {typeRef.FullName}."); } //Otherwise, check if generic and create writes for generic pararameters. else if (typeRef.IsGenericInstance) { GenericInstanceType git = (GenericInstanceType)typeRef; foreach (TypeReference item in git.GenericArguments) { MethodReference result = GetOrCreateWriteMethodReference(item); if (result == null) { base.LogError($"Could not create serializer for {item.FullName}."); return null; } } } return writeMethodRef; } #endregion /// /// Creates a PooledWriter within the body/ and returns its variable index. /// EG: PooledWriter writer = WriterPool.GetWriter(); /// internal VariableDefinition CreatePooledWriter(MethodDefinition methodDef, int length) { VariableDefinition resultVd; List insts = CreatePooledWriter(methodDef, length, out resultVd); ILProcessor processor = methodDef.Body.GetILProcessor(); processor.Add(insts); return resultVd; } /// /// Creates a PooledWriter within the body/ and returns its variable index. /// EG: PooledWriter writer = WriterPool.GetWriter(); /// /// /// /// internal List CreatePooledWriter(MethodDefinition methodDef, int length, out VariableDefinition resultVd) { WriterImports wi = base.GetClass(); List insts = new List(); ILProcessor processor = methodDef.Body.GetILProcessor(); resultVd = base.GetClass().CreateVariable(methodDef, wi.PooledWriter_TypeRef); //If length is specified then pass in length. if (length > 0) { insts.Add(processor.Create(OpCodes.Ldc_I4, length)); insts.Add(processor.Create(OpCodes.Call, wi.WriterPool_GetWriterLength_MethodRef)); } //Use parameter-less method if no length. else { insts.Add(processor.Create(OpCodes.Call, wi.WriterPool_GetWriter_MethodRef)); } //Set value to variable definition. insts.Add(processor.Create(OpCodes.Stloc, resultVd)); return insts; } /// /// Calls Dispose on a PooledWriter. /// EG: writer.Dispose(); /// /// /// internal List DisposePooledWriter(MethodDefinition methodDef, VariableDefinition writerDefinition) { WriterImports wi = base.GetClass(); List insts = new List(); ILProcessor processor = methodDef.Body.GetILProcessor(); insts.Add(processor.Create(OpCodes.Ldloc, writerDefinition)); insts.Add(processor.Create(wi.PooledWriter_Dispose_MethodRef.GetCallOpCode(base.Session), wi.PooledWriter_Dispose_MethodRef)); return insts; } /// /// Creates a null check on the second argument using a boolean. /// internal void CreateRetOnNull(ILProcessor processor, ParameterDefinition writerParameterDef, ParameterDefinition checkedParameterDef, bool useBool) { Instruction endIf = processor.Create(OpCodes.Nop); //If (value) jmp to endIf. processor.Emit(OpCodes.Ldarg, checkedParameterDef); processor.Emit(OpCodes.Brtrue, endIf); //writer.WriteBool / writer.WritePackedWhole if (useBool) CreateWriteBool(processor, writerParameterDef, true); else CreateWritePackedWhole(processor, writerParameterDef, -1); //Exit method. processor.Emit(OpCodes.Ret); //End of if check. processor.Append(endIf); } #region CreateWritePackWhole /// /// Creates a call to WritePackWhole with value. /// /// /// internal void CreateWritePackedWhole(ILProcessor processor, ParameterDefinition writerParameterDef, int value) { WriterImports wi = base.GetClass(); //Create local int and set it to value. VariableDefinition intVariableDef = base.GetClass().CreateVariable(processor.Body.Method, typeof(int)); base.GetClass().SetVariableDefinitionFromInt(processor, intVariableDef, value); //Writer. processor.Emit(OpCodes.Ldarg, writerParameterDef); //Writer.WritePackedWhole(value). processor.Emit(OpCodes.Ldloc, intVariableDef); processor.Emit(OpCodes.Conv_U8); processor.Emit(wi.Writer_WritePackedWhole_MethodRef.GetCallOpCode(base.Session), wi.Writer_WritePackedWhole_MethodRef); } /// /// Creates a call to WritePackWhole with value. /// /// /// internal void CreateWritePackedWhole(ILProcessor processor, ParameterDefinition writerParameterDef, VariableDefinition value) { WriterImports wi = base.GetClass(); //Writer. processor.Emit(OpCodes.Ldarg, writerParameterDef); //Writer.WritePackedWhole(value). processor.Emit(OpCodes.Ldloc, value); processor.Emit(OpCodes.Conv_U8); processor.Emit(wi.Writer_WritePackedWhole_MethodRef.GetCallOpCode(base.Session), wi.Writer_WritePackedWhole_MethodRef); } #endregion /// /// Creates a call to WriteBoolean with value. /// /// /// /// internal void CreateWriteBool(ILProcessor processor, ParameterDefinition writerParameterDef, bool value) { MethodReference writeBoolMethodRef = GetWriteMethodReference(base.GetClass().GetTypeReference(typeof(bool))); processor.Emit(OpCodes.Ldarg, writerParameterDef); int intValue = (value) ? 1 : 0; processor.Emit(OpCodes.Ldc_I4, intValue); processor.Emit(writeBoolMethodRef.GetCallOpCode(base.Session), writeBoolMethodRef); } /// /// Creates a Write call on a PooledWriter variable for parameterDef. /// EG: writer.WriteBool(xxxxx); /// internal List CreateWriteInstructions(MethodDefinition methodDef, object pooledWriterDef, ParameterDefinition valueParameterDef, MethodReference writeMr) { List insts = new List(); ILProcessor processor = methodDef.Body.GetILProcessor(); if (writeMr != null) { if (pooledWriterDef is VariableDefinition) { insts.Add(processor.Create(OpCodes.Ldloc, (VariableDefinition)pooledWriterDef)); } else if (pooledWriterDef is ParameterDefinition) { insts.Add(processor.Create(OpCodes.Ldarg, (ParameterDefinition)pooledWriterDef)); } else { base.LogError($"{pooledWriterDef.GetType().FullName} is not a valid writerDef. Type must be VariableDefinition or ParameterDefinition."); return new List(); } insts.Add(processor.Create(OpCodes.Ldarg, valueParameterDef)); //If an auto pack method then insert default value. if (AutoPackedMethods.Contains(valueParameterDef.ParameterType)) { AutoPackType packType = base.GetClass().GetDefaultAutoPackType(valueParameterDef.ParameterType); insts.Add(processor.Create(OpCodes.Ldc_I4, (int)packType)); } TypeReference valueTr = valueParameterDef.ParameterType; /* If generic then find write class for * data type. Currently we only support one generic * for this. */ if (valueTr.IsGenericInstance) { GenericInstanceType git = (GenericInstanceType)valueTr; TypeReference genericTr = git.GenericArguments[0]; writeMr = writeMr.GetMethodReference(base.Session, genericTr); } insts.Add(processor.Create(OpCodes.Call, writeMr)); return insts; } else { base.LogError($"Writer not found for {valueParameterDef.ParameterType.FullName}."); return new List(); } } /// /// Creates a Write call on a PooledWriter variable for parameterDef. /// EG: writer.WriteBool(xxxxx); /// internal void CreateWrite(MethodDefinition methodDef, object writerDef, ParameterDefinition valuePd, MethodReference writeMr) { List insts = CreateWriteInstructions(methodDef, writerDef, valuePd, writeMr); ILProcessor processor = methodDef.Body.GetILProcessor(); processor.Add(insts); } /// /// Creates a Write call to a writer. /// EG: StaticClass.WriteBool(xxxxx); /// /// /// internal void CreateWrite(MethodDefinition writerMd, ParameterDefinition valuePd, FieldDefinition fieldDef, MethodReference writeMr) { if (writeMr != null) { ILProcessor processor = writerMd.Body.GetILProcessor(); ParameterDefinition writerPd = writerMd.Parameters[0]; /* If generic then find write class for * data type. Currently we only support one generic * for this. */ if (fieldDef.FieldType.IsGenericInstance) { GenericInstanceType git = (GenericInstanceType)fieldDef.FieldType; TypeReference genericTr = git.GenericArguments[0]; writeMr = writeMr.GetMethodReference(base.Session, genericTr); } FieldReference fieldRef = base.GetClass().GetFieldReference(fieldDef); processor.Emit(OpCodes.Ldarg, writerPd); processor.Emit(OpCodes.Ldarg, valuePd); processor.Emit(OpCodes.Ldfld, fieldRef); //If an auto pack method then insert default value. if (AutoPackedMethods.Contains(fieldDef.FieldType)) { AutoPackType packType = base.GetClass().GetDefaultAutoPackType(fieldDef.FieldType); processor.Emit(OpCodes.Ldc_I4, (int)packType); } processor.Emit(OpCodes.Call, writeMr); } else { base.LogError($"Writer not found for {fieldDef.FieldType.FullName}."); } } /// /// Creates a Write call to a writer. /// EG: StaticClass.WriteBool(xxxxx); /// /// /// internal void CreateWrite(MethodDefinition writerMd, ParameterDefinition valuePd, MethodReference getMr, MethodReference writeMr) { TypeReference returnTr = base.ImportReference(getMr.ReturnType); if (writeMr != null) { ILProcessor processor = writerMd.Body.GetILProcessor(); ParameterDefinition writerPd = writerMd.Parameters[0]; /* If generic then find write class for * data type. Currently we only support one generic * for this. */ if (returnTr.IsGenericInstance) { GenericInstanceType git = (GenericInstanceType)returnTr; TypeReference genericTr = git.GenericArguments[0]; writeMr = writeMr.GetMethodReference(base.Session, genericTr); } processor.Emit(OpCodes.Ldarg, writerPd); OpCode ldArgOC0 = (valuePd.ParameterType.IsValueType) ? OpCodes.Ldarga : OpCodes.Ldarg; processor.Emit(ldArgOC0, valuePd); processor.Emit(OpCodes.Call, getMr); //If an auto pack method then insert default value. if (AutoPackedMethods.Contains(returnTr)) { AutoPackType packType = base.GetClass().GetDefaultAutoPackType(returnTr); processor.Emit(OpCodes.Ldc_I4, (int)packType); } processor.Emit(OpCodes.Call, writeMr); } else { base.LogError($"Writer not found for {returnTr.FullName}."); } } #region TypeReference writer generators. /// /// Generates a writer for objectTypeReference if one does not already exist. /// /// /// internal MethodReference CreateWriter(TypeReference objectTr) { MethodReference methodRefResult = null; TypeDefinition objectTd; SerializerType serializerType = base.GetClass().GetSerializerType(objectTr, true, out objectTd); if (serializerType != SerializerType.Invalid) { //Array. if (serializerType == SerializerType.Array) methodRefResult = CreateArrayWriterMethodReference(objectTr); //Enum. else if (serializerType == SerializerType.Enum) methodRefResult = CreateEnumWriterMethodDefinition(objectTr); //Dictionary, List, ListCache else if (serializerType == SerializerType.Dictionary || serializerType == SerializerType.List || serializerType == SerializerType.ListCache) methodRefResult = CreateGenericCollectionWriterMethodReference(objectTr, serializerType); //NetworkBehaviour. else if (serializerType == SerializerType.NetworkBehaviour) methodRefResult = CreateNetworkBehaviourWriterMethodReference(objectTd); //Nullable type. else if (serializerType == SerializerType.Nullable) methodRefResult = CreateNullableWriterMethodReference(objectTr, objectTd); //Class or struct. else if (serializerType == SerializerType.ClassOrStruct) methodRefResult = CreateClassOrStructWriterMethodDefinition(objectTr); } //If was not created. if (methodRefResult == null) RemoveFromStaticWriters(objectTr); return methodRefResult; } /// /// Removes from static writers. /// private void RemoveFromStaticWriters(TypeReference tr) { base.GetClass().RemoveWriterMethod(tr, false); } /// /// Adds to static writers. /// private void AddToStaticWriters(TypeReference tr, MethodReference mr) { base.GetClass().AddWriterMethod(tr, mr.CachedResolve(base.Session), false, true); } /// /// Adds a write for a NetworkBehaviour class type to WriterMethods. /// /// private MethodReference CreateNetworkBehaviourWriterMethodReference(TypeReference objectTr) { ObjectHelper oh = base.GetClass(); objectTr = base.ImportReference(objectTr.Resolve()); //All NetworkBehaviour types will simply WriteNetworkBehaviour/ReadNetworkBehaviour. //Create generated reader/writer class. This class holds all generated reader/writers. base.GetClass().GetOrCreateClass(out _, GENERATED_TYPE_ATTRIBUTES, GENERATED_WRITERS_CLASS_NAME, null); MethodDefinition createdWriterMd = CreateStaticWriterStubMethodDefinition(objectTr); AddToStaticWriters(objectTr, createdWriterMd); ILProcessor processor = createdWriterMd.Body.GetILProcessor(); MethodReference writeMethodRef = base.GetClass().GetOrCreateWriteMethodReference(oh.NetworkBehaviour_TypeRef); //Get parameters for method. ParameterDefinition writerParameterDef = createdWriterMd.Parameters[0]; ParameterDefinition classParameterDef = createdWriterMd.Parameters[1]; //Load parameters as arguments. processor.Emit(OpCodes.Ldarg, writerParameterDef); processor.Emit(OpCodes.Ldarg, classParameterDef); //writer.WriteNetworkBehaviour(arg1); processor.Emit(OpCodes.Call, writeMethodRef); processor.Emit(OpCodes.Ret); return base.ImportReference(createdWriterMd); } /// /// Gets the length of a collection and writes the value to a variable. /// private void CreateCollectionLength(ILProcessor processor, ParameterDefinition collectionParameterDef, VariableDefinition storeVariableDef) { processor.Emit(OpCodes.Ldarg, collectionParameterDef); processor.Emit(OpCodes.Ldlen); processor.Emit(OpCodes.Conv_I4); processor.Emit(OpCodes.Stloc, storeVariableDef); } /// /// Creates a writer for a class or struct of objectTypeRef. /// /// /// private MethodReference CreateNullableWriterMethodReference(TypeReference objectTr, TypeDefinition objectTd) { WriterProcessor wh = base.GetClass(); GenericInstanceType objectGit = objectTr as GenericInstanceType; TypeReference valueTr = objectGit.GenericArguments[0]; //Get the writer for the value. MethodReference valueWriterMr = wh.GetOrCreateWriteMethodReference(valueTr); if (valueWriterMr == null) return null; MethodDefinition tmpMd; tmpMd = objectTd.GetMethod("get_Value"); MethodReference genericGetValueMr = tmpMd.MakeHostInstanceGeneric(base.Session, objectGit); tmpMd = objectTd.GetMethod("get_HasValue"); MethodReference genericHasValueMr = tmpMd.MakeHostInstanceGeneric(base.Session, objectGit); /* Stubs generate Method(Writer writer, T value). */ MethodDefinition createdWriterMd = CreateStaticWriterStubMethodDefinition(objectTr); AddToStaticWriters(objectTr, createdWriterMd); ILProcessor processor = createdWriterMd.Body.GetILProcessor(); //Value parameter. ParameterDefinition valuePd = createdWriterMd.Parameters[1]; ParameterDefinition writerPd = createdWriterMd.Parameters[0]; //Have to write a new ret on null because nullables use hasValue for null checks. Instruction afterNullRetInst = processor.Create(OpCodes.Nop); processor.Emit(OpCodes.Ldarga, valuePd); processor.Emit(OpCodes.Call, genericHasValueMr); processor.Emit(OpCodes.Brtrue_S, afterNullRetInst); wh.CreateWriteBool(processor, writerPd, true); processor.Emit(OpCodes.Ret); processor.Append(afterNullRetInst); //Code will only execute here and below if not null. wh.CreateWriteBool(processor, writerPd, false); processor.Emit(OpCodes.Ldarg, writerPd); processor.Emit(OpCodes.Ldarga, valuePd); processor.Emit(OpCodes.Call, genericGetValueMr); //If an auto pack method then insert default value. if (wh.IsAutoPackedType(valueTr)) { AutoPackType packType = base.GetClass().GetDefaultAutoPackType(valueTr); processor.Emit(OpCodes.Ldc_I4, (int)packType); } processor.Emit(OpCodes.Call, valueWriterMr); processor.Emit(OpCodes.Ret); return base.ImportReference(createdWriterMd); } /// /// Creates a writer for a class or struct of objectTypeRef. /// /// /// private MethodReference CreateClassOrStructWriterMethodDefinition(TypeReference objectTr) { WriterProcessor wh = base.GetClass(); /*Stubs generate Method(Writer writer, T value). */ MethodDefinition createdWriterMd = CreateStaticWriterStubMethodDefinition(objectTr); AddToStaticWriters(objectTr, createdWriterMd); ILProcessor processor = createdWriterMd.Body.GetILProcessor(); //If not a value type then add a null check. if (!objectTr.CachedResolve(base.Session).IsValueType) { ParameterDefinition writerPd = createdWriterMd.Parameters[0]; wh.CreateRetOnNull(processor, writerPd, createdWriterMd.Parameters[1], true); //Code will only execute here and below if not null. wh.CreateWriteBool(processor, writerPd, false); } //Write all fields for the class or struct. ParameterDefinition valueParameterDef = createdWriterMd.Parameters[1]; if (!WriteFieldsAndProperties(createdWriterMd, valueParameterDef, objectTr)) return null; processor.Emit(OpCodes.Ret); return base.ImportReference(createdWriterMd); } /// /// Find all fields in type and write them /// /// /// /// false if fail private bool WriteFieldsAndProperties(MethodDefinition generatedWriteMd, ParameterDefinition valuePd, TypeReference objectTr) { WriterProcessor wh = base.GetClass(); //This probably isn't needed but I'm too afraid to remove it. if (objectTr.Module != base.Module) objectTr = base.ImportReference(objectTr.CachedResolve(base.Session)); //Fields foreach (FieldDefinition fieldDef in objectTr.FindAllSerializableFields(base.Session))//, WriterHelper.EXCLUDED_AUTO_SERIALIZER_TYPES)) { TypeReference tr; if (fieldDef.FieldType.IsGenericInstance) { GenericInstanceType genericTr = (GenericInstanceType)fieldDef.FieldType; tr = genericTr.GenericArguments[0]; } else { tr = fieldDef.FieldType; } if (GetWriteMethod(fieldDef.FieldType, out MethodReference writeMr)) wh.CreateWrite(generatedWriteMd, valuePd, fieldDef, writeMr); } //Properties. foreach (PropertyDefinition propertyDef in objectTr.FindAllSerializableProperties(base.Session , WriterProcessor.EXCLUDED_AUTO_SERIALIZER_TYPES, WriterProcessor.EXCLUDED_ASSEMBLY_PREFIXES)) { if (GetWriteMethod(propertyDef.PropertyType, out MethodReference writerMr)) { MethodReference getMr = base.Module.ImportReference(propertyDef.GetMethod); wh.CreateWrite(generatedWriteMd, valuePd, getMr, writerMr); } } //Gets or creates writer method and outputs it. Returns true if method is found or created. bool GetWriteMethod(TypeReference tr, out MethodReference writeMr) { tr = base.ImportReference(tr); writeMr = wh.GetOrCreateWriteMethodReference(tr); return (writeMr != null); } return true; } /// /// Creates a writer for an enum. /// /// /// private MethodReference CreateEnumWriterMethodDefinition(TypeReference enumTr) { WriterProcessor wh = base.GetClass(); MethodDefinition createdWriterMd = CreateStaticWriterStubMethodDefinition(enumTr); AddToStaticWriters(enumTr, createdWriterMd); ILProcessor processor = createdWriterMd.Body.GetILProcessor(); //Element type for enum. EG: byte int ect TypeReference underlyingTypeRef = enumTr.CachedResolve(base.Session).GetEnumUnderlyingTypeReference(); //Method to write that type. MethodReference underlyingWriterMethodRef = wh.GetOrCreateWriteMethodReference(underlyingTypeRef); if (underlyingWriterMethodRef == null) return null; ParameterDefinition writerParameterDef = createdWriterMd.Parameters[0]; ParameterDefinition valueParameterDef = createdWriterMd.Parameters[1]; //Push writer and value into call. processor.Emit(OpCodes.Ldarg, writerParameterDef); processor.Emit(OpCodes.Ldarg, valueParameterDef); if (wh.IsAutoPackedType(underlyingTypeRef)) processor.Emit(OpCodes.Ldc_I4, (int)AutoPackType.Packed); //writer.WriteXXX(value) processor.Emit(OpCodes.Call, underlyingWriterMethodRef); processor.Emit(OpCodes.Ret); return base.ImportReference(createdWriterMd); } /// /// Calls an instanced writer from a static writer. /// private void CallInstancedWriter(MethodDefinition staticWriterMd, MethodReference instancedWriterMr) { ParameterDefinition writerPd = staticWriterMd.Parameters[0]; ParameterDefinition valuePd = staticWriterMd.Parameters[1]; ILProcessor processor = staticWriterMd.Body.GetILProcessor(); processor.Emit(OpCodes.Ldarg, writerPd); processor.Emit(OpCodes.Ldarg, valuePd); processor.Emit(instancedWriterMr.GetCallOpCode(base.Session), instancedWriterMr); processor.Emit(OpCodes.Ret); } /// /// Creates a writer for an array. /// private MethodReference CreateArrayWriterMethodReference(TypeReference objectTr) { WriterImports wi = base.GetClass(); TypeReference valueTr = objectTr.GetElementType(); //Write not found. if (GetOrCreateWriteMethodReference(valueTr) == null) return null; MethodDefinition createdMd = CreateStaticWriterStubMethodDefinition(objectTr); AddToStaticWriters(objectTr, createdMd); //Find instanced writer to use. MethodReference instancedWriteMr = wi.Writer_WriteArray_MethodRef; //Make generic. GenericInstanceMethod writeGim = instancedWriteMr.MakeGenericMethod(new TypeReference[] { valueTr }); CallInstancedWriter(createdMd, writeGim); return base.ImportReference(createdMd); } /// /// Creates a writer for a variety of generic collections. /// private MethodReference CreateGenericCollectionWriterMethodReference(TypeReference objectTr, SerializerType st) { WriterImports wi = base.GetClass(); //Make value field generic. GenericInstanceType genericInstance = (GenericInstanceType)objectTr; base.ImportReference(genericInstance); TypeReference valueTr = genericInstance.GenericArguments[0]; List genericArguments = new List(); //Make sure all arguments have writers. foreach (TypeReference gaTr in genericInstance.GenericArguments) { MethodReference mr = GetOrCreateWriteMethodReference(gaTr); //Writer not found. if (mr == null) { base.LogError($"Writer could not be found or created for type {gaTr.FullName}."); return null; } genericArguments.Add(gaTr); } MethodReference valueWriteMr = GetOrCreateWriteMethodReference(valueTr); if (valueWriteMr == null) return null; MethodDefinition createdMd = CreateStaticWriterStubMethodDefinition(objectTr); AddToStaticWriters(objectTr, createdMd); //Find instanced writer to use. MethodReference instancedWriteMr; if (st == SerializerType.Dictionary) instancedWriteMr = wi.Writer_WriteDictionary_MethodRef; else if (st == SerializerType.List) instancedWriteMr = wi.Writer_WriteList_MethodRef; else if (st == SerializerType.ListCache) instancedWriteMr = wi.Writer_WriteListCache_MethodRef; else instancedWriteMr = null; //Not found. if (instancedWriteMr == null) { base.LogError($"Instanced writer not found for SerializerType {st} on object {objectTr.Name}."); return null; } //Make generic. GenericInstanceMethod writeGim = instancedWriteMr.MakeGenericMethod(genericArguments.ToArray()); CallInstancedWriter(createdMd, writeGim); return base.ImportReference(createdMd); } /// /// Creates a method definition stub for objectTypeRef. /// /// /// public MethodDefinition CreateStaticWriterStubMethodDefinition(TypeReference objectTypeRef, string nameExtension = WriterProcessor.GENERATED_WRITER_NAMESPACE) { string methodName = $"{GENERATED_WRITE_PREFIX}{objectTypeRef.FullName}{nameExtension}"; // create new writer for this type TypeDefinition writerTypeDef = base.GetClass().GetOrCreateClass(out _, GENERATED_TYPE_ATTRIBUTES, GENERATED_WRITERS_CLASS_NAME, null); MethodDefinition writerMethodDef = writerTypeDef.AddMethod(methodName, MethodAttributes.Public | MethodAttributes.Static | MethodAttributes.HideBySig); base.GetClass().CreateParameter(writerMethodDef, base.GetClass().Writer_TypeRef, "writer"); base.GetClass().CreateParameter(writerMethodDef, objectTypeRef, "value"); base.GetClass().MakeExtensionMethod(writerMethodDef); writerMethodDef.Body.InitLocals = true; return writerMethodDef; } #endregion } }