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);
}
}
}