using FishNet.CodeGenerating.Helping.Extension; using FishNet.Serializing; using MonoFN.Cecil; using MonoFN.Cecil.Cil; using MonoFN.Cecil.Rocks; using System; using System.Collections.Generic; using UnityEngine; namespace FishNet.CodeGenerating.Helping { internal class GenericWriterHelper { #region Reflection references. private TypeReference _genericWriterTypeRef; private TypeReference _writerTypeRef; private MethodReference _writeGetSetMethodRef; private MethodReference _writeAutoPackGetSetMethodRef; internal TypeReference ActionT2TypeRef; internal TypeReference ActionT3TypeRef; internal MethodReference ActionT2ConstructorMethodRef; internal MethodReference ActionT3ConstructorMethodRef; private TypeDefinition _generatedReaderWriterClassTypeDef; private MethodDefinition _generatedReaderWriterOnLoadMethodDef; private TypeReference _autoPackTypeRef; #endregion #region Misc. /// /// TypeReferences which have already had delegates made for. /// private HashSet _delegatedTypes = new HashSet(); #endregion #region Const. internal const string INITIALIZEONCE_METHOD_NAME = "InitializeOnce"; internal const MethodAttributes INITIALIZEONCE_METHOD_ATTRIBUTES = MethodAttributes.Static; #endregion /// /// Imports references needed by this helper. /// /// /// internal bool ImportReferences() { _genericWriterTypeRef = CodegenSession.ImportReference(typeof(GenericWriter<>)); _writerTypeRef = CodegenSession.ImportReference(typeof(Writer)); ActionT2TypeRef = CodegenSession.ImportReference(typeof(Action<,>)); ActionT3TypeRef = CodegenSession.ImportReference(typeof(Action<,,>)); ActionT2ConstructorMethodRef = CodegenSession.ImportReference(typeof(Action<,>).GetConstructors()[0]); ActionT3ConstructorMethodRef = CodegenSession.ImportReference(typeof(Action<,,>).GetConstructors()[0]); _autoPackTypeRef = CodegenSession.ImportReference(typeof(AutoPackType)); System.Reflection.PropertyInfo writePropertyInfo; writePropertyInfo = typeof(GenericWriter<>).GetProperty(nameof(GenericWriter.Write)); _writeGetSetMethodRef = CodegenSession.ImportReference(writePropertyInfo.GetSetMethod()); writePropertyInfo = typeof(GenericWriter<>).GetProperty(nameof(GenericWriter.WriteAutoPack)); _writeAutoPackGetSetMethodRef = CodegenSession.ImportReference(writePropertyInfo.GetSetMethod()); return true; } /// /// Creates a variant of an instanced write method. /// /// /// internal void CreateInstancedStaticWrite(MethodReference writeMethodRef) { if (_generatedReaderWriterClassTypeDef == null) _generatedReaderWriterClassTypeDef = CodegenSession.GeneralHelper.GetOrCreateClass(out _, WriterGenerator.GENERATED_TYPE_ATTRIBUTES, WriterGenerator.GENERATED_WRITERS_CLASS_NAME, null); MethodDefinition writeMethodDef = writeMethodRef.CachedResolve(); MethodDefinition createdMethodDef = new MethodDefinition($"Static___{writeMethodRef.Name}", (MethodAttributes.Public | MethodAttributes.Static | MethodAttributes.HideBySig), _generatedReaderWriterClassTypeDef.Module.TypeSystem.Void); _generatedReaderWriterClassTypeDef.Methods.Add(createdMethodDef); TypeReference extensionAttributeTypeRef = CodegenSession.ImportReference(typeof(System.Runtime.CompilerServices.ExtensionAttribute)); MethodDefinition constructor = extensionAttributeTypeRef.GetConstructor(); MethodReference extensionAttributeConstructorMethodRef = CodegenSession.ImportReference(constructor); CustomAttribute extensionCustomAttribute = new CustomAttribute(extensionAttributeConstructorMethodRef); createdMethodDef.CustomAttributes.Add(extensionCustomAttribute); /* Add parameters to new method. */ //First add extension. ParameterDefinition extensionParameterDef = CodegenSession.GeneralHelper.CreateParameter(createdMethodDef, typeof(PooledWriter), "pooledWriter", ParameterAttributes.None); //Then other types. ParameterDefinition[] remainingParameterDefs = new ParameterDefinition[writeMethodDef.Parameters.Count]; for (int i = 0; i < writeMethodDef.Parameters.Count; i++) { remainingParameterDefs[i] = CodegenSession.GeneralHelper.CreateParameter(createdMethodDef, writeMethodDef.Parameters[i].ParameterType); _generatedReaderWriterClassTypeDef.Module.ImportReference(remainingParameterDefs[i].ParameterType.CachedResolve()); } ILProcessor processor = createdMethodDef.Body.GetILProcessor(); //Load all parameters. foreach (ParameterDefinition pd in remainingParameterDefs) processor.Emit(OpCodes.Ldarg, pd); //Call instanced method. processor.Emit(OpCodes.Ldarg, extensionParameterDef); processor.Emit(OpCodes.Call, writeMethodRef); processor.Emit(OpCodes.Ret); } /// /// Creates a Write delegate for writeMethodRef and places it within the generated reader/writer constructor. /// /// internal void CreateWriteDelegate(MethodReference writeMethodRef, bool isStatic) { /* If class for generated reader/writers isn't known yet. * It's possible this is the case if the entry being added * now is the first entry. That would mean the class was just * generated. */ bool created; if (_generatedReaderWriterClassTypeDef == null) _generatedReaderWriterClassTypeDef = CodegenSession.GeneralHelper.GetOrCreateClass(out created, WriterGenerator.GENERATED_TYPE_ATTRIBUTES, WriterGenerator.GENERATED_WRITERS_CLASS_NAME, null); /* If constructor isn't set then try to get or create it * and also add it to methods if were created. */ if (_generatedReaderWriterOnLoadMethodDef == null) { _generatedReaderWriterOnLoadMethodDef = CodegenSession.GeneralHelper.GetOrCreateMethod(_generatedReaderWriterClassTypeDef, out created, INITIALIZEONCE_METHOD_ATTRIBUTES, INITIALIZEONCE_METHOD_NAME, CodegenSession.Module.TypeSystem.Void); if (created) CodegenSession.GeneralHelper.CreateRuntimeInitializeOnLoadMethodAttribute(_generatedReaderWriterOnLoadMethodDef); } //Check if ret already exist, if so remove it; ret will be added on again in this method. if (_generatedReaderWriterOnLoadMethodDef.Body.Instructions.Count != 0) { int lastIndex = (_generatedReaderWriterOnLoadMethodDef.Body.Instructions.Count - 1); if (_generatedReaderWriterOnLoadMethodDef.Body.Instructions[lastIndex].OpCode == OpCodes.Ret) _generatedReaderWriterOnLoadMethodDef.Body.Instructions.RemoveAt(lastIndex); } ILProcessor processor = _generatedReaderWriterOnLoadMethodDef.Body.GetILProcessor(); TypeReference dataTypeRef; //Static methods will have the data type as the second parameter (1). if (isStatic) dataTypeRef = writeMethodRef.Parameters[1].ParameterType; else dataTypeRef = writeMethodRef.Parameters[0].ParameterType; //Check if writer already exist. if (_delegatedTypes.Contains(dataTypeRef)) { CodegenSession.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, writeMethodRef); GenericInstanceType actionGenericInstance; MethodReference actionConstructorInstanceMethodRef; bool isAutoPacked = CodegenSession.WriterHelper.IsAutoPackedType(dataTypeRef); //Generate for auto pack type. if (isAutoPacked) { actionGenericInstance = ActionT3TypeRef.MakeGenericInstanceType(_writerTypeRef, dataTypeRef, _autoPackTypeRef); actionConstructorInstanceMethodRef = ActionT3ConstructorMethodRef.MakeHostInstanceGeneric(actionGenericInstance); } //Generate for normal type. else { actionGenericInstance = ActionT2TypeRef.MakeGenericInstanceType(_writerTypeRef, dataTypeRef); actionConstructorInstanceMethodRef = ActionT2ConstructorMethodRef.MakeHostInstanceGeneric(actionGenericInstance); } processor.Emit(OpCodes.Newobj, actionConstructorInstanceMethodRef); //Call delegate to GenericWriter.Write GenericInstanceType genericInstance = _genericWriterTypeRef.MakeGenericInstanceType(dataTypeRef); MethodReference genericrWriteMethodRef = (isAutoPacked) ? _writeAutoPackGetSetMethodRef.MakeHostInstanceGeneric(genericInstance) : _writeGetSetMethodRef.MakeHostInstanceGeneric(genericInstance); processor.Emit(OpCodes.Call, genericrWriteMethodRef); processor.Emit(OpCodes.Ret); } } }