1114 lines
48 KiB
C#
1114 lines
48 KiB
C#
|
using FishNet.CodeGenerating.Helping.Extension;
|
|||
|
using FishNet.Serializing;
|
|||
|
using MonoFN.Cecil;
|
|||
|
using MonoFN.Cecil.Cil;
|
|||
|
using MonoFN.Cecil.Rocks;
|
|||
|
using SR = System.Reflection;
|
|||
|
using System.Collections.Generic;
|
|||
|
using System;
|
|||
|
using FishNet.CodeGenerating.ILCore;
|
|||
|
using FishNet.CodeGenerating.Extension;
|
|||
|
using FishNet.Utility.Performance;
|
|||
|
using FishNet.Object;
|
|||
|
|
|||
|
namespace FishNet.CodeGenerating.Helping
|
|||
|
{
|
|||
|
|
|||
|
internal class ReaderProcessor : CodegenBase
|
|||
|
{
|
|||
|
|
|||
|
#region Reflection references.
|
|||
|
public TypeDefinition GeneratedReaderClassTypeDef;
|
|||
|
public MethodDefinition GeneratedReaderOnLoadMethodDef;
|
|||
|
public readonly Dictionary<string, MethodReference> InstancedReaderMethods = new Dictionary<string, MethodReference>();
|
|||
|
public readonly Dictionary<string, MethodReference> StaticReaderMethods = new Dictionary<string, MethodReference>();
|
|||
|
public HashSet<TypeReference> AutoPackedMethods = new HashSet<TypeReference>(new TypeReferenceComparer());
|
|||
|
#endregion
|
|||
|
|
|||
|
#region Misc.
|
|||
|
/// <summary>
|
|||
|
/// TypeReferences which have already had delegates made for.
|
|||
|
/// </summary>
|
|||
|
private HashSet<TypeReference> _delegatedTypes = new HashSet<TypeReference>();
|
|||
|
#endregion
|
|||
|
|
|||
|
#region Const.
|
|||
|
/// <summary>
|
|||
|
/// Namespace to use for generated serializers and delegates.
|
|||
|
/// </summary>
|
|||
|
public const string GENERATED_READER_NAMESPACE = WriterProcessor.GENERATED_WRITER_NAMESPACE;
|
|||
|
/// <summary>
|
|||
|
/// Name to use for generated serializers class.
|
|||
|
/// </summary>
|
|||
|
public const string GENERATED_WRITERS_CLASS_NAME = "GeneratedReaders___Internal";
|
|||
|
/// <summary>
|
|||
|
/// Attributes to use for generated serializers class.
|
|||
|
/// </summary>
|
|||
|
public const TypeAttributes GENERATED_TYPE_ATTRIBUTES = (TypeAttributes.BeforeFieldInit | TypeAttributes.Class | TypeAttributes.AnsiClass |
|
|||
|
TypeAttributes.Public | TypeAttributes.AutoClass | TypeAttributes.Abstract | TypeAttributes.Sealed);
|
|||
|
/// <summary>
|
|||
|
/// Name to use for InitializeOnce method.
|
|||
|
/// </summary>
|
|||
|
public const string INITIALIZEONCE_METHOD_NAME = WriterProcessor.INITIALIZEONCE_METHOD_NAME;
|
|||
|
/// <summary>
|
|||
|
/// Attributes to use for InitializeOnce method within generated serializer classes.
|
|||
|
/// </summary>
|
|||
|
public const MethodAttributes INITIALIZEONCE_METHOD_ATTRIBUTES = WriterProcessor.INITIALIZEONCE_METHOD_ATTRIBUTES;
|
|||
|
/// <summary>
|
|||
|
/// Attritbutes to use for generated serializers.
|
|||
|
/// </summary>
|
|||
|
public const MethodAttributes GENERATED_METHOD_ATTRIBUTES = WriterProcessor.GENERATED_METHOD_ATTRIBUTES;
|
|||
|
/// <summary>
|
|||
|
/// Prefix used which all instanced and user created serializers should start with.
|
|||
|
/// </summary>
|
|||
|
internal const string READ_PREFIX = "Read";
|
|||
|
/// <summary>
|
|||
|
/// Class name to use for generated readers.
|
|||
|
/// </summary>
|
|||
|
internal const string GENERATED_READERS_CLASS_NAME = "GeneratedReaders___Internal";
|
|||
|
/// <summary>
|
|||
|
/// Prefix to use for generated readers.
|
|||
|
/// </summary>
|
|||
|
private const string GENERATED_READ_PREFIX = "Read___";
|
|||
|
/// <summary>
|
|||
|
/// Types to exclude from being scanned for auto serialization.
|
|||
|
/// </summary>
|
|||
|
public static System.Type[] EXCLUDED_AUTO_SERIALIZER_TYPES => WriterProcessor.EXCLUDED_AUTO_SERIALIZER_TYPES;
|
|||
|
/// <summary>
|
|||
|
/// Types to exclude from being scanned for auto serialization.
|
|||
|
/// </summary>
|
|||
|
public static string[] EXCLUDED_ASSEMBLY_PREFIXES => WriterProcessor.EXCLUDED_ASSEMBLY_PREFIXES;
|
|||
|
#endregion
|
|||
|
|
|||
|
public override bool ImportReferences() => true;
|
|||
|
|
|||
|
public bool Process()
|
|||
|
{
|
|||
|
GeneralHelper gh = base.GetClass<GeneralHelper>();
|
|||
|
|
|||
|
CreateGeneratedClassData();
|
|||
|
FindInstancedReaders();
|
|||
|
CreateInstancedReaderExtensions();
|
|||
|
|
|||
|
void CreateGeneratedClassData()
|
|||
|
{
|
|||
|
GeneratedReaderClassTypeDef = gh.GetOrCreateClass(out _, ReaderProcessor.GENERATED_TYPE_ATTRIBUTES, ReaderProcessor.GENERATED_READERS_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. */
|
|||
|
GeneratedReaderOnLoadMethodDef = gh.GetOrCreateMethod(GeneratedReaderClassTypeDef, out _, INITIALIZEONCE_METHOD_ATTRIBUTES, INITIALIZEONCE_METHOD_NAME, base.Module.TypeSystem.Void);
|
|||
|
gh.CreateRuntimeInitializeOnLoadMethodAttribute(GeneratedReaderOnLoadMethodDef);
|
|||
|
|
|||
|
ILProcessor ppp = GeneratedReaderOnLoadMethodDef.Body.GetILProcessor();
|
|||
|
ppp.Emit(OpCodes.Ret);
|
|||
|
//GeneratedReaderOnLoadMethodDef.DeclaringType.Methods.Remove(GeneratedReaderOnLoadMethodDef);
|
|||
|
}
|
|||
|
|
|||
|
void FindInstancedReaders()
|
|||
|
{
|
|||
|
Type pooledWriterType = typeof(PooledReader);
|
|||
|
foreach (SR.MethodInfo methodInfo in pooledWriterType.GetMethods())
|
|||
|
{
|
|||
|
if (IsSpecialReadMethod(methodInfo))
|
|||
|
continue;
|
|||
|
bool autoPackMethod;
|
|||
|
if (IsIgnoredReadMethod(methodInfo, out autoPackMethod))
|
|||
|
continue;
|
|||
|
|
|||
|
MethodReference methodRef = base.ImportReference(methodInfo);
|
|||
|
/* TypeReference for the return type
|
|||
|
* of the read method. */
|
|||
|
TypeReference typeRef = base.ImportReference(methodRef.ReturnType);
|
|||
|
|
|||
|
/* If here all checks pass. */
|
|||
|
AddReaderMethod(typeRef, methodRef, true, true);
|
|||
|
if (autoPackMethod)
|
|||
|
AutoPackedMethods.Add(typeRef);
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
return true;
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Returns if a MethodInfo is considered a special write method.
|
|||
|
/// Special read methods have declared references within this class, and will not have extensions made for them.
|
|||
|
/// </summary>
|
|||
|
public bool IsSpecialReadMethod(SR.MethodInfo methodInfo)
|
|||
|
{
|
|||
|
/* Special methods. */
|
|||
|
if (methodInfo.Name == nameof(PooledReader.ReadPackedWhole))
|
|||
|
return true;
|
|||
|
else if (methodInfo.Name == nameof(PooledReader.ReadArray))
|
|||
|
return true;
|
|||
|
else if (methodInfo.Name == nameof(PooledReader.ReadDictionary))
|
|||
|
return true;
|
|||
|
|
|||
|
return false;
|
|||
|
}
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Returns if a read method should be ignored.
|
|||
|
/// </summary>
|
|||
|
public bool IsIgnoredReadMethod(SR.MethodInfo methodInfo, out bool autoPackMethod)
|
|||
|
{
|
|||
|
autoPackMethod = false;
|
|||
|
|
|||
|
if (base.GetClass<GeneralHelper>().CodegenExclude(methodInfo))
|
|||
|
return true;
|
|||
|
//Not long enough to be a write method.
|
|||
|
else if (methodInfo.Name.Length < READ_PREFIX.Length)
|
|||
|
return true;
|
|||
|
//Method name doesn't start with writePrefix.
|
|||
|
else if (methodInfo.Name.Substring(0, READ_PREFIX.Length) != READ_PREFIX)
|
|||
|
return true;
|
|||
|
SR.ParameterInfo[] parameterInfos = methodInfo.GetParameters();
|
|||
|
//Can have at most one parameter for packing.
|
|||
|
if (parameterInfos.Length > 1)
|
|||
|
return true;
|
|||
|
//If has one parameter make sure it's a packing type.
|
|||
|
if (parameterInfos.Length == 1)
|
|||
|
{
|
|||
|
autoPackMethod = (parameterInfos[0].ParameterType == typeof(AutoPackType));
|
|||
|
if (!autoPackMethod)
|
|||
|
return true;
|
|||
|
}
|
|||
|
|
|||
|
return false;
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Adds typeRef, methodDef to instanced or readerMethods.
|
|||
|
/// </summary>
|
|||
|
/// <param name="typeRef"></param>
|
|||
|
/// <param name="methodRef"></param>
|
|||
|
/// <param name="useAdd"></param>
|
|||
|
internal void AddReaderMethod(TypeReference typeRef, MethodReference methodRef, bool instanced, bool useAdd)
|
|||
|
{
|
|||
|
string fullName = typeRef.GetFullnameWithoutBrackets();
|
|||
|
Dictionary<string, MethodReference> dict = (instanced) ?
|
|||
|
InstancedReaderMethods : StaticReaderMethods;
|
|||
|
|
|||
|
if (useAdd)
|
|||
|
dict.Add(fullName, methodRef);
|
|||
|
else
|
|||
|
dict[fullName] = methodRef;
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Creates a Read delegate for readMethodRef and places it within the generated reader/writer constructor.
|
|||
|
/// </summary>
|
|||
|
/// <param name="readMr"></param>
|
|||
|
/// <param name="diagnostics"></param>
|
|||
|
internal void CreateReadDelegate(MethodReference readMr, bool isStatic)
|
|||
|
{
|
|||
|
GeneralHelper gh = base.GetClass<GeneralHelper>();
|
|||
|
ReaderImports ri = base.GetClass<ReaderImports>();
|
|||
|
|
|||
|
if (!isStatic)
|
|||
|
{
|
|||
|
//Supporting Write<T> with types containing generics is more trouble than it's worth.
|
|||
|
if (readMr.IsGenericInstance || readMr.HasGenericParameters)
|
|||
|
return;
|
|||
|
}
|
|||
|
|
|||
|
//Check if ret already exist, if so remove it; ret will be added on again in this method.
|
|||
|
if (GeneratedReaderOnLoadMethodDef.Body.Instructions.Count != 0)
|
|||
|
{
|
|||
|
int lastIndex = (GeneratedReaderOnLoadMethodDef.Body.Instructions.Count - 1);
|
|||
|
if (GeneratedReaderOnLoadMethodDef.Body.Instructions[lastIndex].OpCode == OpCodes.Ret)
|
|||
|
GeneratedReaderOnLoadMethodDef.Body.Instructions.RemoveAt(lastIndex);
|
|||
|
}
|
|||
|
//Check if already exist.
|
|||
|
ILProcessor processor = GeneratedReaderOnLoadMethodDef.Body.GetILProcessor();
|
|||
|
TypeReference dataTypeRef = readMr.ReturnType;
|
|||
|
if (_delegatedTypes.Contains(dataTypeRef))
|
|||
|
{
|
|||
|
base.LogError($"Generic read already created for {dataTypeRef.FullName}.");
|
|||
|
return;
|
|||
|
}
|
|||
|
else
|
|||
|
{
|
|||
|
_delegatedTypes.Add(dataTypeRef);
|
|||
|
}
|
|||
|
|
|||
|
//Create a Func<Reader, T> delegate
|
|||
|
processor.Emit(OpCodes.Ldnull);
|
|||
|
processor.Emit(OpCodes.Ldftn, readMr);
|
|||
|
|
|||
|
GenericInstanceType functionGenericInstance;
|
|||
|
MethodReference functionConstructorInstanceMethodRef;
|
|||
|
bool isAutoPacked = IsAutoPackedType(dataTypeRef);
|
|||
|
|
|||
|
//Generate for autopacktype.
|
|||
|
if (isAutoPacked)
|
|||
|
{
|
|||
|
functionGenericInstance = gh.FunctionT3TypeRef.MakeGenericInstanceType(ri.ReaderTypeRef, base.GetClass<WriterImports>().AutoPackTypeRef, dataTypeRef);
|
|||
|
functionConstructorInstanceMethodRef = gh.FunctionT3ConstructorMethodRef.MakeHostInstanceGeneric(base.Session, functionGenericInstance);
|
|||
|
}
|
|||
|
//Not autopacked.
|
|||
|
else
|
|||
|
{
|
|||
|
functionGenericInstance = gh.FunctionT2TypeRef.MakeGenericInstanceType(ri.ReaderTypeRef, dataTypeRef);
|
|||
|
functionConstructorInstanceMethodRef = gh.FunctionT2ConstructorMethodRef.MakeHostInstanceGeneric(base.Session, functionGenericInstance);
|
|||
|
}
|
|||
|
processor.Emit(OpCodes.Newobj, functionConstructorInstanceMethodRef);
|
|||
|
|
|||
|
//Call delegate to GeneratedReader<T>.Read
|
|||
|
GenericInstanceType genericInstance = ri.GenericReaderTypeRef.MakeGenericInstanceType(dataTypeRef);
|
|||
|
MethodReference genericReadMethodRef = (isAutoPacked) ?
|
|||
|
ri.ReadAutoPackSetMethodRef.MakeHostInstanceGeneric(base.Session, genericInstance) :
|
|||
|
ri.ReadSetMethodRef.MakeHostInstanceGeneric(base.Session, genericInstance);
|
|||
|
processor.Emit(OpCodes.Call, genericReadMethodRef);
|
|||
|
|
|||
|
processor.Emit(OpCodes.Ret);
|
|||
|
}
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Creates reader extension methods for built-in readers.
|
|||
|
/// </summary>
|
|||
|
private void CreateInstancedReaderExtensions()
|
|||
|
{
|
|||
|
if (!FishNetILPP.IsFishNetAssembly(base.Session))
|
|||
|
return;
|
|||
|
|
|||
|
GeneralHelper gh = base.GetClass<GeneralHelper>();
|
|||
|
ReaderProcessor gwh = base.GetClass<ReaderProcessor>();
|
|||
|
|
|||
|
//List<MethodReference> staticReaders = new List<MethodReference>();
|
|||
|
foreach (KeyValuePair<string, MethodReference> item in InstancedReaderMethods)
|
|||
|
{
|
|||
|
MethodReference instancedReadMr = item.Value;
|
|||
|
if (instancedReadMr.ContainsGenericParameter)
|
|||
|
continue;
|
|||
|
|
|||
|
TypeReference returnTr = base.ImportReference(instancedReadMr.ReturnType);
|
|||
|
|
|||
|
MethodDefinition md = new MethodDefinition($"InstancedExtension___{instancedReadMr.Name}",
|
|||
|
WriterProcessor.GENERATED_METHOD_ATTRIBUTES,
|
|||
|
returnTr);
|
|||
|
//Add extension parameter.
|
|||
|
ParameterDefinition readerPd = gh.CreateParameter(md, typeof(Reader), "reader");
|
|||
|
//Add parameters needed by instanced writer.
|
|||
|
List<ParameterDefinition> otherPds = md.CreateParameters(base.Session, instancedReadMr);
|
|||
|
gh.MakeExtensionMethod(md);
|
|||
|
//
|
|||
|
gwh.GeneratedReaderClassTypeDef.Methods.Add(md);
|
|||
|
|
|||
|
ILProcessor processor = md.Body.GetILProcessor();
|
|||
|
//Load writer.
|
|||
|
processor.Emit(OpCodes.Ldarg, readerPd);
|
|||
|
//Load args.
|
|||
|
foreach (ParameterDefinition pd in otherPds)
|
|||
|
processor.Emit(OpCodes.Ldarg, pd);
|
|||
|
//Call instanced.
|
|||
|
processor.Emit(instancedReadMr.GetCallOpCode(base.Session), instancedReadMr);
|
|||
|
processor.Emit(OpCodes.Ret);
|
|||
|
|
|||
|
AddReaderMethod(returnTr, md, false, true);
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Removes typeRef from static/instanced reader methods.
|
|||
|
/// </summary>
|
|||
|
internal void RemoveReaderMethod(TypeReference typeRef, bool instanced)
|
|||
|
{
|
|||
|
string fullName = typeRef.GetFullnameWithoutBrackets();
|
|||
|
Dictionary<string, MethodReference> dict = (instanced) ?
|
|||
|
InstancedReaderMethods : StaticReaderMethods;
|
|||
|
|
|||
|
dict.Remove(fullName);
|
|||
|
}
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Creates read instructions returning instructions and outputing variable of read result.
|
|||
|
/// </summary>
|
|||
|
internal List<Instruction> CreateRead(MethodDefinition methodDef, ParameterDefinition readerParameterDef, TypeReference readTypeRef, out VariableDefinition createdVariableDef)
|
|||
|
{
|
|||
|
ILProcessor processor = methodDef.Body.GetILProcessor();
|
|||
|
List<Instruction> insts = new List<Instruction>();
|
|||
|
MethodReference readMr = GetOrCreateReadMethodReference(readTypeRef);
|
|||
|
if (readMr != null)
|
|||
|
{
|
|||
|
//Make a local variable.
|
|||
|
createdVariableDef = base.GetClass<GeneralHelper>().CreateVariable(methodDef, readTypeRef);
|
|||
|
//pooledReader.ReadBool();
|
|||
|
insts.Add(processor.Create(OpCodes.Ldarg, readerParameterDef));
|
|||
|
//If an auto pack method then insert default value.
|
|||
|
if (AutoPackedMethods.Contains(readTypeRef))
|
|||
|
{
|
|||
|
AutoPackType packType = base.GetClass<GeneralHelper>().GetDefaultAutoPackType(readTypeRef);
|
|||
|
insts.Add(processor.Create(OpCodes.Ldc_I4, (int)packType));
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
TypeReference valueTr = readTypeRef;
|
|||
|
/* 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];
|
|||
|
readMr = readMr.GetMethodReference(base.Session, genericTr);
|
|||
|
}
|
|||
|
|
|||
|
insts.Add(processor.Create(OpCodes.Call, readMr));
|
|||
|
//Store into local variable.
|
|||
|
insts.Add(processor.Create(OpCodes.Stloc, createdVariableDef));
|
|||
|
return insts;
|
|||
|
}
|
|||
|
else
|
|||
|
{
|
|||
|
base.LogError("Reader not found for " + readTypeRef.ToString());
|
|||
|
createdVariableDef = null;
|
|||
|
return null;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Creates a read for fieldRef and populates it into a created variable of class or struct type.
|
|||
|
/// </summary>
|
|||
|
internal bool CreateReadIntoClassOrStruct(MethodDefinition readerMd, ParameterDefinition readerPd, MethodReference readMr, VariableDefinition objectVd, FieldReference valueFr)
|
|||
|
{
|
|||
|
if (readMr != null)
|
|||
|
{
|
|||
|
ILProcessor processor = readerMd.Body.GetILProcessor();
|
|||
|
/* How to load object instance. If it's a structure
|
|||
|
* then it must be loaded by address. Otherwise if
|
|||
|
* class Ldloc can be used. */
|
|||
|
OpCode loadOpCode = (objectVd.VariableType.IsValueType) ?
|
|||
|
OpCodes.Ldloca : OpCodes.Ldloc;
|
|||
|
|
|||
|
/* If generic then find write class for
|
|||
|
* data type. Currently we only support one generic
|
|||
|
* for this. */
|
|||
|
if (valueFr.FieldType.IsGenericInstance)
|
|||
|
{
|
|||
|
GenericInstanceType git = (GenericInstanceType)valueFr.FieldType;
|
|||
|
TypeReference genericTr = git.GenericArguments[0];
|
|||
|
readMr = readMr.GetMethodReference(base.Session, genericTr);
|
|||
|
}
|
|||
|
|
|||
|
processor.Emit(loadOpCode, objectVd);
|
|||
|
//reader.
|
|||
|
processor.Emit(OpCodes.Ldarg, readerPd);
|
|||
|
if (IsAutoPackedType(valueFr.FieldType))
|
|||
|
{
|
|||
|
AutoPackType packType = base.GetClass<GeneralHelper>().GetDefaultAutoPackType(valueFr.FieldType);
|
|||
|
processor.Emit(OpCodes.Ldc_I4, (int)packType);
|
|||
|
}
|
|||
|
//reader.ReadXXXX().
|
|||
|
processor.Emit(OpCodes.Call, readMr);
|
|||
|
//obj.Field = result / reader.ReadXXXX().
|
|||
|
processor.Emit(OpCodes.Stfld, valueFr);
|
|||
|
|
|||
|
return true;
|
|||
|
}
|
|||
|
else
|
|||
|
{
|
|||
|
base.LogError($"Reader not found for {valueFr.FullName}.");
|
|||
|
return false;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Creates a read for fieldRef and populates it into a created variable of class or struct type.
|
|||
|
/// </summary>
|
|||
|
internal bool CreateReadIntoClassOrStruct(MethodDefinition methodDef, ParameterDefinition readerPd, MethodReference readMr, VariableDefinition objectVariableDef, MethodReference setMr, TypeReference readTr)
|
|||
|
{
|
|||
|
if (readMr != null)
|
|||
|
{
|
|||
|
ILProcessor processor = methodDef.Body.GetILProcessor();
|
|||
|
|
|||
|
/* How to load object instance. If it's a structure
|
|||
|
* then it must be loaded by address. Otherwise if
|
|||
|
* class Ldloc can be used. */
|
|||
|
OpCode loadOpCode = (objectVariableDef.VariableType.IsValueType) ?
|
|||
|
OpCodes.Ldloca : OpCodes.Ldloc;
|
|||
|
|
|||
|
/* If generic then find write class for
|
|||
|
* data type. Currently we only support one generic
|
|||
|
* for this. */
|
|||
|
if (readTr.IsGenericInstance)
|
|||
|
{
|
|||
|
GenericInstanceType git = (GenericInstanceType)readTr;
|
|||
|
TypeReference genericTr = git.GenericArguments[0];
|
|||
|
readMr = readMr.GetMethodReference(base.Session, genericTr);
|
|||
|
}
|
|||
|
|
|||
|
processor.Emit(loadOpCode, objectVariableDef);
|
|||
|
//reader.
|
|||
|
processor.Emit(OpCodes.Ldarg, readerPd);
|
|||
|
if (IsAutoPackedType(readTr))
|
|||
|
{
|
|||
|
AutoPackType packType = base.GetClass<GeneralHelper>().GetDefaultAutoPackType(readTr);
|
|||
|
processor.Emit(OpCodes.Ldc_I4, (int)packType);
|
|||
|
}
|
|||
|
//reader.ReadXXXX().
|
|||
|
processor.Emit(OpCodes.Call, readMr);
|
|||
|
//obj.Property = result / reader.ReadXXXX().
|
|||
|
processor.Emit(OpCodes.Call, setMr);
|
|||
|
|
|||
|
return true;
|
|||
|
}
|
|||
|
else
|
|||
|
{
|
|||
|
base.LogError($"Reader not found for {readTr.FullName}.");
|
|||
|
return false;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Creates generic write delegates for all currently known write types.
|
|||
|
/// </summary>
|
|||
|
internal void CreateStaticMethodDelegates()
|
|||
|
{
|
|||
|
foreach (KeyValuePair<string, MethodReference> item in StaticReaderMethods)
|
|||
|
base.GetClass<ReaderProcessor>().CreateReadDelegate(item.Value, true);
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Returns if typeRef has a deserializer.
|
|||
|
/// </summary>
|
|||
|
/// <param name="typeRef"></param>
|
|||
|
/// <param name="createMissing"></param>
|
|||
|
/// <returns></returns>
|
|||
|
internal bool HasDeserializer(TypeReference typeRef, bool createMissing)
|
|||
|
{
|
|||
|
bool result = (GetInstancedReadMethodReference(typeRef) != null) ||
|
|||
|
(GetStaticReadMethodReference(typeRef) != null);
|
|||
|
|
|||
|
if (!result && createMissing)
|
|||
|
{
|
|||
|
if (!base.GetClass<GeneralHelper>().HasNonSerializableAttribute(typeRef.CachedResolve(base.Session)))
|
|||
|
{
|
|||
|
MethodReference methodRef = CreateReader(typeRef);
|
|||
|
result = (methodRef != null);
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
return result;
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Returns if typeRef supports auto packing.
|
|||
|
/// </summary>
|
|||
|
/// <param name="typeRef"></param>
|
|||
|
/// <returns></returns>
|
|||
|
internal bool IsAutoPackedType(TypeReference typeRef)
|
|||
|
{
|
|||
|
return AutoPackedMethods.Contains(typeRef);
|
|||
|
}
|
|||
|
/// <summary>
|
|||
|
/// Creates a null check on the first argument and returns a null object if result indicates to do so.
|
|||
|
/// </summary>
|
|||
|
internal void CreateRetOnNull(ILProcessor processor, ParameterDefinition readerParameterDef, VariableDefinition resultVariableDef, bool useBool)
|
|||
|
{
|
|||
|
Instruction endIf = processor.Create(OpCodes.Nop);
|
|||
|
|
|||
|
if (useBool)
|
|||
|
CreateReadBool(processor, readerParameterDef, resultVariableDef);
|
|||
|
else
|
|||
|
CreateReadPackedWhole(processor, readerParameterDef, resultVariableDef);
|
|||
|
|
|||
|
//If (true or == -1) jmp to endIf. True is null.
|
|||
|
processor.Emit(OpCodes.Ldloc, resultVariableDef);
|
|||
|
if (useBool)
|
|||
|
{
|
|||
|
processor.Emit(OpCodes.Brfalse, endIf);
|
|||
|
}
|
|||
|
else
|
|||
|
{
|
|||
|
//-1
|
|||
|
processor.Emit(OpCodes.Ldc_I4_M1);
|
|||
|
processor.Emit(OpCodes.Bne_Un_S, endIf);
|
|||
|
}
|
|||
|
//Insert null.
|
|||
|
processor.Emit(OpCodes.Ldnull);
|
|||
|
//Exit method.
|
|||
|
processor.Emit(OpCodes.Ret);
|
|||
|
//End of if check.
|
|||
|
processor.Append(endIf);
|
|||
|
}
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Creates a call to WriteBoolean with value.
|
|||
|
/// </summary>
|
|||
|
/// <param name="processor"></param>
|
|||
|
/// <param name="writerParameterDef"></param>
|
|||
|
/// <param name="value"></param>
|
|||
|
internal void CreateReadBool(ILProcessor processor, ParameterDefinition readerParameterDef, VariableDefinition localBoolVariableDef)
|
|||
|
{
|
|||
|
MethodReference readBoolMethodRef = GetReadMethodReference(base.GetClass<GeneralHelper>().GetTypeReference(typeof(bool)));
|
|||
|
processor.Emit(OpCodes.Ldarg, readerParameterDef);
|
|||
|
processor.Emit(readBoolMethodRef.GetCallOpCode(base.Session), readBoolMethodRef);
|
|||
|
processor.Emit(OpCodes.Stloc, localBoolVariableDef);
|
|||
|
}
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Creates a call to WritePackWhole with value.
|
|||
|
/// </summary>
|
|||
|
/// <param name="processor"></param>
|
|||
|
/// <param name="value"></param>
|
|||
|
internal void CreateReadPackedWhole(ILProcessor processor, ParameterDefinition readerParameterDef, VariableDefinition resultVariableDef)
|
|||
|
{
|
|||
|
//Reader.
|
|||
|
processor.Emit(OpCodes.Ldarg, readerParameterDef);
|
|||
|
//Reader.ReadPackedWhole().
|
|||
|
MethodReference readPwMr = base.GetClass<ReaderImports>().Reader_ReadPackedWhole_MethodRef;
|
|||
|
processor.Emit(readPwMr.GetCallOpCode(base.Session), readPwMr);
|
|||
|
processor.Emit(OpCodes.Conv_I4);
|
|||
|
processor.Emit(OpCodes.Stloc, resultVariableDef);
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
#region GetReaderMethodReference.
|
|||
|
/// <summary>
|
|||
|
/// Returns the MethodReference for typeRef.
|
|||
|
/// </summary>
|
|||
|
/// <param name="typeRef"></param>
|
|||
|
/// <returns></returns>
|
|||
|
internal MethodReference GetInstancedReadMethodReference(TypeReference typeRef)
|
|||
|
{
|
|||
|
string fullName = typeRef.GetFullnameWithoutBrackets();
|
|||
|
InstancedReaderMethods.TryGetValue(fullName, out MethodReference methodRef);
|
|||
|
return methodRef;
|
|||
|
}
|
|||
|
/// <summary>
|
|||
|
/// Returns the MethodReference for typeRef.
|
|||
|
/// </summary>
|
|||
|
/// <param name="typeRef"></param>
|
|||
|
/// <returns></returns>
|
|||
|
internal MethodReference GetStaticReadMethodReference(TypeReference typeRef)
|
|||
|
{
|
|||
|
string fullName = typeRef.GetFullnameWithoutBrackets();
|
|||
|
StaticReaderMethods.TryGetValue(fullName, out MethodReference methodRef);
|
|||
|
return methodRef;
|
|||
|
}
|
|||
|
/// <summary>
|
|||
|
/// Returns the MethodReference for typeRef favoring instanced or static. Returns null if not found.
|
|||
|
/// </summary>
|
|||
|
/// <param name="typeRef"></param>
|
|||
|
/// <param name="favorInstanced"></param>
|
|||
|
/// <returns></returns>
|
|||
|
internal MethodReference GetReadMethodReference(TypeReference typeRef)
|
|||
|
{
|
|||
|
MethodReference result;
|
|||
|
bool favorInstanced = false;
|
|||
|
if (favorInstanced)
|
|||
|
{
|
|||
|
result = GetInstancedReadMethodReference(typeRef);
|
|||
|
if (result == null)
|
|||
|
result = GetStaticReadMethodReference(typeRef);
|
|||
|
}
|
|||
|
else
|
|||
|
{
|
|||
|
result = GetStaticReadMethodReference(typeRef);
|
|||
|
if (result == null)
|
|||
|
result = GetInstancedReadMethodReference(typeRef);
|
|||
|
}
|
|||
|
|
|||
|
return result;
|
|||
|
}
|
|||
|
/// <summary>
|
|||
|
/// Returns the MethodReference for typeRef favoring instanced or static.
|
|||
|
/// </summary>
|
|||
|
/// <param name="typeRef"></param>
|
|||
|
/// <param name="favorInstanced"></param>
|
|||
|
/// <returns></returns>
|
|||
|
internal MethodReference GetOrCreateReadMethodReference(TypeReference typeRef)
|
|||
|
{
|
|||
|
#pragma warning disable CS0219
|
|||
|
bool favorInstanced = false;
|
|||
|
#pragma warning restore CS0219
|
|||
|
//Try to get existing writer, if not present make one.
|
|||
|
MethodReference readMethodRef = GetReadMethodReference(typeRef);
|
|||
|
if (readMethodRef == null)
|
|||
|
readMethodRef = CreateReader(typeRef);
|
|||
|
|
|||
|
//If still null then return could not be generated.
|
|||
|
if (readMethodRef == null)
|
|||
|
{
|
|||
|
base.LogError($"Could not create deserializer 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 = GetOrCreateReadMethodReference(item);
|
|||
|
if (result == null)
|
|||
|
{
|
|||
|
base.LogError($"Could not create deserializer for {item.FullName}.");
|
|||
|
return null;
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
return readMethodRef;
|
|||
|
}
|
|||
|
#endregion
|
|||
|
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Generates a reader for objectTypeReference if one does not already exist.
|
|||
|
/// </summary>
|
|||
|
/// <param name="objectTr"></param>
|
|||
|
/// <returns></returns>
|
|||
|
internal MethodReference CreateReader(TypeReference objectTr)
|
|||
|
{
|
|||
|
MethodReference resultMr = null;
|
|||
|
TypeDefinition objectTypeDef;
|
|||
|
|
|||
|
SerializerType serializerType = base.GetClass<GeneratorHelper>().GetSerializerType(objectTr, false, out objectTypeDef);
|
|||
|
if (serializerType != SerializerType.Invalid)
|
|||
|
{
|
|||
|
//Array.
|
|||
|
if (serializerType == SerializerType.Array)
|
|||
|
resultMr = CreateArrayReaderMethodReference(objectTr);
|
|||
|
//Enum.
|
|||
|
else if (serializerType == SerializerType.Enum)
|
|||
|
resultMr = CreateEnumReaderMethodDefinition(objectTr);
|
|||
|
else if (serializerType == SerializerType.Dictionary
|
|||
|
|| serializerType == SerializerType.List
|
|||
|
|| serializerType == SerializerType.ListCache)
|
|||
|
resultMr = CreateGenericCollectionReaderMethodReference(objectTr, serializerType);
|
|||
|
//NetworkBehaviour.
|
|||
|
else if (serializerType == SerializerType.NetworkBehaviour)
|
|||
|
resultMr = GetNetworkBehaviourReaderMethodReference(objectTr);
|
|||
|
//Nullable.
|
|||
|
else if (serializerType == SerializerType.Nullable)
|
|||
|
resultMr = CreateNullableReaderMethodReference(objectTr);
|
|||
|
//Class or struct.
|
|||
|
else if (serializerType == SerializerType.ClassOrStruct)
|
|||
|
resultMr = CreateClassOrStructReaderMethodReference(objectTr);
|
|||
|
}
|
|||
|
|
|||
|
//If was not created.
|
|||
|
if (resultMr == null)
|
|||
|
RemoveFromStaticReaders(objectTr);
|
|||
|
|
|||
|
return resultMr;
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Removes from static writers.
|
|||
|
/// </summary>
|
|||
|
private void RemoveFromStaticReaders(TypeReference tr)
|
|||
|
{
|
|||
|
base.GetClass<ReaderProcessor>().RemoveReaderMethod(tr, false);
|
|||
|
}
|
|||
|
/// <summary>
|
|||
|
/// Adds to static writers.
|
|||
|
/// </summary>
|
|||
|
private void AddToStaticReaders(TypeReference tr, MethodReference mr)
|
|||
|
{
|
|||
|
base.GetClass<ReaderProcessor>().AddReaderMethod(tr, mr.CachedResolve(base.Session), false, true);
|
|||
|
}
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Generates a reader for objectTypeReference if one does not already exist.
|
|||
|
/// </summary>
|
|||
|
/// <param name="objectTr"></param>
|
|||
|
/// <returns></returns>
|
|||
|
private MethodReference CreateEnumReaderMethodDefinition(TypeReference objectTr)
|
|||
|
{
|
|||
|
MethodDefinition createdReaderMd = CreateStaticReaderStubMethodDefinition(objectTr);
|
|||
|
AddToStaticReaders(objectTr, createdReaderMd);
|
|||
|
|
|||
|
ILProcessor processor = createdReaderMd.Body.GetILProcessor();
|
|||
|
|
|||
|
//Get type reference for enum type. eg byte int
|
|||
|
TypeReference underlyingTypeRef = objectTr.CachedResolve(base.Session).GetEnumUnderlyingTypeReference();
|
|||
|
//Get read method for underlying type.
|
|||
|
MethodReference readMethodRef = base.GetClass<ReaderProcessor>().GetOrCreateReadMethodReference(underlyingTypeRef);
|
|||
|
if (readMethodRef == null)
|
|||
|
return null;
|
|||
|
|
|||
|
ParameterDefinition readerParameterDef = createdReaderMd.Parameters[0];
|
|||
|
//reader.ReadXXX().
|
|||
|
processor.Emit(OpCodes.Ldarg, readerParameterDef);
|
|||
|
if (base.GetClass<WriterProcessor>().IsAutoPackedType(underlyingTypeRef))
|
|||
|
processor.Emit(OpCodes.Ldc_I4, (int)AutoPackType.Packed);
|
|||
|
|
|||
|
processor.Emit(OpCodes.Call, readMethodRef);
|
|||
|
|
|||
|
processor.Emit(OpCodes.Ret);
|
|||
|
return base.ImportReference(createdReaderMd);
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Creates a read for a class type which inherits NetworkBehaviour.
|
|||
|
/// </summary>
|
|||
|
/// <param name="objectTr"></param>
|
|||
|
/// <returns></returns>
|
|||
|
private MethodReference GetNetworkBehaviourReaderMethodReference(TypeReference objectTr)
|
|||
|
{
|
|||
|
MethodDefinition createdReaderMd = CreateStaticReaderStubMethodDefinition(objectTr);
|
|||
|
AddToStaticReaders(objectTr, createdReaderMd);
|
|||
|
|
|||
|
ILProcessor processor = createdReaderMd.Body.GetILProcessor();
|
|||
|
TypeReference networkBehaviourTypeRef = base.GetClass<GeneralHelper>().GetTypeReference(typeof(NetworkBehaviour));
|
|||
|
|
|||
|
processor.Emit(OpCodes.Ldarg_0);
|
|||
|
processor.Emit(OpCodes.Call, base.GetClass<ReaderProcessor>().GetReadMethodReference(networkBehaviourTypeRef));
|
|||
|
processor.Emit(OpCodes.Castclass, objectTr);
|
|||
|
processor.Emit(OpCodes.Ret);
|
|||
|
return base.ImportReference(createdReaderMd);
|
|||
|
}
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Creates a writer for an array.
|
|||
|
/// </summary>
|
|||
|
private MethodReference CreateArrayReaderMethodReference(TypeReference objectTr)
|
|||
|
{
|
|||
|
ReaderImports ri = base.GetClass<ReaderImports>();
|
|||
|
TypeReference valueTr = objectTr.GetElementType();
|
|||
|
|
|||
|
//Write not found.
|
|||
|
if (GetOrCreateReadMethodReference(valueTr) == null)
|
|||
|
return null;
|
|||
|
|
|||
|
MethodDefinition createdMd = CreateStaticReaderStubMethodDefinition(objectTr);
|
|||
|
AddToStaticReaders(objectTr, createdMd);
|
|||
|
|
|||
|
//Find instanced writer to use.
|
|||
|
MethodReference instancedWriteMr = ri.Reader_ReadArray_MethodRef;
|
|||
|
//Make generic.
|
|||
|
GenericInstanceMethod writeGim = instancedWriteMr.MakeGenericMethod(new TypeReference[] { valueTr });
|
|||
|
CallInstancedReader(createdMd, writeGim);
|
|||
|
|
|||
|
return base.ImportReference(createdMd);
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Creates a reader for a dictionary.
|
|||
|
/// </summary>
|
|||
|
private MethodReference CreateDictionaryReaderMethodReference(TypeReference objectTr)
|
|||
|
{
|
|||
|
ReaderProcessor rp = base.GetClass<ReaderProcessor>();
|
|||
|
|
|||
|
GenericInstanceType genericInstance = (GenericInstanceType)objectTr;
|
|||
|
base.ImportReference(genericInstance);
|
|||
|
TypeReference keyTr = genericInstance.GenericArguments[0];
|
|||
|
TypeReference valueTr = genericInstance.GenericArguments[1];
|
|||
|
|
|||
|
/* Try to get instanced first for collection element type, if it doesn't exist then try to
|
|||
|
* get/or make a one. */
|
|||
|
MethodReference keyWriteMr = rp.GetOrCreateReadMethodReference(keyTr);
|
|||
|
MethodReference valueWriteMr = rp.GetOrCreateReadMethodReference(valueTr);
|
|||
|
if (keyWriteMr == null || valueWriteMr == null)
|
|||
|
return null;
|
|||
|
|
|||
|
MethodDefinition createdReaderMd = CreateStaticReaderStubMethodDefinition(objectTr);
|
|||
|
AddToStaticReaders(objectTr, createdReaderMd);
|
|||
|
|
|||
|
GenericInstanceMethod readDictGim = base.GetClass<ReaderImports>().Reader_ReadDictionary_MethodRef.MakeGenericMethod(new TypeReference[] { keyTr, valueTr });
|
|||
|
CallInstancedReader(createdReaderMd, readDictGim);
|
|||
|
|
|||
|
return base.ImportReference(createdReaderMd);
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Creates a writer for a variety of generic collections.
|
|||
|
/// </summary>
|
|||
|
private MethodReference CreateGenericCollectionReaderMethodReference(TypeReference objectTr, SerializerType st)
|
|||
|
{
|
|||
|
ReaderImports ri = base.GetClass<ReaderImports>();
|
|||
|
//Make value field generic.
|
|||
|
GenericInstanceType genericInstance = (GenericInstanceType)objectTr;
|
|||
|
base.ImportReference(genericInstance);
|
|||
|
TypeReference valueTr = genericInstance.GenericArguments[0];
|
|||
|
|
|||
|
List<TypeReference> genericArguments = new List<TypeReference>();
|
|||
|
//Make sure all arguments have writers.
|
|||
|
foreach (TypeReference gaTr in genericInstance.GenericArguments)
|
|||
|
{
|
|||
|
//Writer not found.
|
|||
|
if (GetOrCreateReadMethodReference(gaTr) == null)
|
|||
|
{
|
|||
|
base.LogError($"Reader could not be found or created for type {gaTr.FullName}.");
|
|||
|
return null;
|
|||
|
}
|
|||
|
genericArguments.Add(gaTr);
|
|||
|
}
|
|||
|
MethodReference valueWriteMr = GetOrCreateReadMethodReference(valueTr);
|
|||
|
if (valueWriteMr == null)
|
|||
|
return null;
|
|||
|
|
|||
|
MethodDefinition createdMd = CreateStaticReaderStubMethodDefinition(objectTr);
|
|||
|
AddToStaticReaders(objectTr, createdMd);
|
|||
|
|
|||
|
//Find instanced writer to use.
|
|||
|
MethodReference instancedReadMr;
|
|||
|
if (st == SerializerType.Dictionary)
|
|||
|
instancedReadMr = ri.Reader_ReadDictionary_MethodRef;
|
|||
|
else if (st == SerializerType.List)
|
|||
|
instancedReadMr = ri.Reader_ReadList_MethodRef;
|
|||
|
else if (st == SerializerType.ListCache)
|
|||
|
instancedReadMr = ri.Reader_ReadListCache_MethodRef;
|
|||
|
else
|
|||
|
instancedReadMr = null;
|
|||
|
|
|||
|
//Not found.
|
|||
|
if (instancedReadMr == null)
|
|||
|
{
|
|||
|
base.LogError($"Instanced reader not found for SerializerType {st} on object {objectTr.Name}.");
|
|||
|
return null;
|
|||
|
}
|
|||
|
|
|||
|
//Make generic.
|
|||
|
GenericInstanceMethod writeGim = instancedReadMr.MakeGenericMethod(genericArguments.ToArray());
|
|||
|
CallInstancedReader(createdMd, writeGim);
|
|||
|
|
|||
|
return base.ImportReference(createdMd);
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Calls an instanced writer from a static writer.
|
|||
|
/// </summary>
|
|||
|
private void CallInstancedReader(MethodDefinition staticReaderMd, MethodReference instancedReaderMr)
|
|||
|
{
|
|||
|
ParameterDefinition readerPd = staticReaderMd.Parameters[0];
|
|||
|
ILProcessor processor = staticReaderMd.Body.GetILProcessor();
|
|||
|
processor.Emit(OpCodes.Ldarg, readerPd);
|
|||
|
processor.Emit(instancedReaderMr.GetCallOpCode(base.Session), instancedReaderMr);
|
|||
|
processor.Emit(OpCodes.Ret);
|
|||
|
}
|
|||
|
|
|||
|
///// <summary>
|
|||
|
///// Create a reader for a list.
|
|||
|
///// </summary>
|
|||
|
//private MethodReference CreateGenericTypeReader(TypeReference objectTr, SerializerType st)
|
|||
|
//{
|
|||
|
// ReaderProcessor rp = base.GetClass<ReaderProcessor>();
|
|||
|
|
|||
|
// if (st != SerializerType.List && st != SerializerType.ListCache)
|
|||
|
// {
|
|||
|
// base.LogError($"Reader SerializerType {st} is not implemented");
|
|||
|
// return null;
|
|||
|
// }
|
|||
|
|
|||
|
// GenericInstanceType genericInstance = (GenericInstanceType)objectTr;
|
|||
|
// base.ImportReference(genericInstance);
|
|||
|
// TypeReference elementTr = genericInstance.GenericArguments[0];
|
|||
|
|
|||
|
// /* Try to get instanced first for collection element type, if it doesn't exist then try to
|
|||
|
// * get/or make a one. */
|
|||
|
// MethodReference elementReadMr = rp.GetOrCreateReadMethodReference(elementTr);
|
|||
|
// if (elementReadMr == null)
|
|||
|
// return null;
|
|||
|
|
|||
|
// TypeReference readerMethodTr = null;
|
|||
|
// if (st == SerializerType.List)
|
|||
|
// readerMethodTr = base.GetClass<GeneralHelper>().GetTypeReference(typeof(List<>));
|
|||
|
// else if (st == SerializerType.ListCache)
|
|||
|
// readerMethodTr = base.GetClass<GeneralHelper>().GetTypeReference(typeof(ListCache<>));
|
|||
|
|
|||
|
// MethodReference readerMd = rp.GetReadMethodReference(readerMethodTr);
|
|||
|
// MethodDefinition typedReaderMd = CreateStaticReaderStubMethodDefinition(objectTr);
|
|||
|
|
|||
|
// AddToStaticReaders(objectTr, typedReaderMd);
|
|||
|
|
|||
|
// ParameterDefinition readerPd = typedReaderMd.Parameters[0];
|
|||
|
|
|||
|
// //Find add method for list.
|
|||
|
// MethodReference readerGim = readerMd.GetMethodReference(base.Session, elementTr);
|
|||
|
// ILProcessor processor = readerMd.CachedResolve(base.Session).Body.GetILProcessor();
|
|||
|
// processor.Emit(OpCodes.Ldarg, readerPd);
|
|||
|
// processor.Emit(OpCodes.Call, readerGim);
|
|||
|
|
|||
|
// return elementReadMr;
|
|||
|
//}
|
|||
|
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Creates a reader method for a struct or class objectTypeRef.
|
|||
|
/// </summary>
|
|||
|
/// <param name="objectTr"></param>
|
|||
|
/// <returns></returns>
|
|||
|
private MethodReference CreateNullableReaderMethodReference(TypeReference objectTr)
|
|||
|
{
|
|||
|
ReaderProcessor rp = base.GetClass<ReaderProcessor>();
|
|||
|
|
|||
|
GenericInstanceType objectGit = objectTr as GenericInstanceType;
|
|||
|
TypeReference valueTr = objectGit.GenericArguments[0];
|
|||
|
|
|||
|
//Make sure object has a ctor.
|
|||
|
MethodDefinition objectCtorMd = objectTr.GetConstructor(base.Session, 1);
|
|||
|
if (objectCtorMd == null)
|
|||
|
{
|
|||
|
base.LogError($"{objectTr.Name} can't be deserialized because the nullable type does not have a constructor.");
|
|||
|
return null;
|
|||
|
}
|
|||
|
|
|||
|
//Get the reader for the value.
|
|||
|
MethodReference valueReaderMr = rp.GetOrCreateReadMethodReference(valueTr);
|
|||
|
if (valueReaderMr == null)
|
|||
|
return null;
|
|||
|
|
|||
|
TypeDefinition objectTd = objectTr.CachedResolve(base.Session);
|
|||
|
MethodDefinition createdReaderMd = CreateStaticReaderStubMethodDefinition(objectTr);
|
|||
|
AddToStaticReaders(objectTr, createdReaderMd);
|
|||
|
|
|||
|
ILProcessor processor = createdReaderMd.Body.GetILProcessor();
|
|||
|
|
|||
|
ParameterDefinition readerPd = createdReaderMd.Parameters[0];
|
|||
|
// create local for return value
|
|||
|
VariableDefinition resultVd = base.GetClass<GeneralHelper>().CreateVariable(createdReaderMd, objectTr);
|
|||
|
|
|||
|
//Read if null into boolean.
|
|||
|
VariableDefinition nullBoolVd = createdReaderMd.CreateVariable(base.Session, typeof(bool));
|
|||
|
rp.CreateReadBool(processor, readerPd, nullBoolVd);
|
|||
|
|
|||
|
Instruction afterReturnNullInst = processor.Create(OpCodes.Nop);
|
|||
|
processor.Emit(OpCodes.Ldloc, nullBoolVd);
|
|||
|
processor.Emit(OpCodes.Brfalse, afterReturnNullInst);
|
|||
|
//Return a null result.
|
|||
|
base.GetClass<GeneralHelper>().SetVariableDefinitionFromObject(processor, resultVd, objectTd);
|
|||
|
processor.Emit(OpCodes.Ldloc, resultVd);
|
|||
|
processor.Emit(OpCodes.Ret);
|
|||
|
processor.Append(afterReturnNullInst);
|
|||
|
|
|||
|
MethodReference initMr = objectCtorMd.MakeHostInstanceGeneric(base.Session, objectGit);
|
|||
|
processor.Emit(OpCodes.Ldarg, readerPd);
|
|||
|
//If an auto pack method then insert default value.
|
|||
|
if (rp.IsAutoPackedType(valueTr))
|
|||
|
{
|
|||
|
AutoPackType packType = base.GetClass<GeneralHelper>().GetDefaultAutoPackType(valueTr);
|
|||
|
processor.Emit(OpCodes.Ldc_I4, (int)packType);
|
|||
|
}
|
|||
|
processor.Emit(OpCodes.Call, valueReaderMr);
|
|||
|
processor.Emit(OpCodes.Newobj, initMr);
|
|||
|
processor.Emit(OpCodes.Ret);
|
|||
|
|
|||
|
return base.ImportReference(createdReaderMd);
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Creates a reader method for a struct or class objectTypeRef.
|
|||
|
/// </summary>
|
|||
|
/// <param name="objectTr"></param>
|
|||
|
/// <returns></returns>
|
|||
|
private MethodReference CreateClassOrStructReaderMethodReference(TypeReference objectTr)
|
|||
|
{
|
|||
|
MethodDefinition createdReaderMd = CreateStaticReaderStubMethodDefinition(objectTr);
|
|||
|
AddToStaticReaders(objectTr, createdReaderMd);
|
|||
|
|
|||
|
TypeDefinition objectTypeDef = objectTr.CachedResolve(base.Session);
|
|||
|
ILProcessor processor = createdReaderMd.Body.GetILProcessor();
|
|||
|
|
|||
|
ParameterDefinition readerParameterDef = createdReaderMd.Parameters[0];
|
|||
|
// create local for return value
|
|||
|
VariableDefinition objectVariableDef = base.GetClass<GeneralHelper>().CreateVariable(createdReaderMd, objectTr);
|
|||
|
|
|||
|
//If not a value type create a return null check.
|
|||
|
if (!objectTypeDef.IsValueType)
|
|||
|
{
|
|||
|
VariableDefinition nullVariableDef = base.GetClass<GeneralHelper>().CreateVariable(createdReaderMd, typeof(bool));
|
|||
|
//Load packed whole value into sizeVariableDef, exit if null indicator.
|
|||
|
base.GetClass<ReaderProcessor>().CreateRetOnNull(processor, readerParameterDef, nullVariableDef, true);
|
|||
|
}
|
|||
|
|
|||
|
/* If here then not null. */
|
|||
|
//Make a new instance of object type and set to objectVariableDef.
|
|||
|
base.GetClass<GeneralHelper>().SetVariableDefinitionFromObject(processor, objectVariableDef, objectTypeDef);
|
|||
|
if (!ReadFieldsAndProperties(createdReaderMd, readerParameterDef, objectVariableDef, objectTr))
|
|||
|
return null;
|
|||
|
/* //codegen scriptableobjects seem to climb too high up to UnityEngine.Object when
|
|||
|
* creating serializers/deserialized. Make sure this is not possible. */
|
|||
|
|
|||
|
//Load result and return it.
|
|||
|
processor.Emit(OpCodes.Ldloc, objectVariableDef);
|
|||
|
processor.Emit(OpCodes.Ret);
|
|||
|
|
|||
|
return base.ImportReference(createdReaderMd);
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Reads all fields of objectTypeRef.
|
|||
|
/// </summary>
|
|||
|
private bool ReadFieldsAndProperties(MethodDefinition readerMd, ParameterDefinition readerPd, VariableDefinition objectVd, TypeReference objectTr)
|
|||
|
{
|
|||
|
ReaderProcessor rp = base.GetClass<ReaderProcessor>();
|
|||
|
|
|||
|
//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
|
|||
|
, ReaderProcessor.EXCLUDED_AUTO_SERIALIZER_TYPES, ReaderProcessor.EXCLUDED_ASSEMBLY_PREFIXES))
|
|||
|
{
|
|||
|
FieldReference importedFr = base.ImportReference(fieldDef);
|
|||
|
if (GetReadMethod(fieldDef.FieldType, out MethodReference readMr))
|
|||
|
rp.CreateReadIntoClassOrStruct(readerMd, readerPd, readMr, objectVd, importedFr);
|
|||
|
}
|
|||
|
|
|||
|
//Properties.
|
|||
|
foreach (PropertyDefinition propertyDef in objectTr.FindAllSerializableProperties(base.Session
|
|||
|
, ReaderProcessor.EXCLUDED_AUTO_SERIALIZER_TYPES, ReaderProcessor.EXCLUDED_ASSEMBLY_PREFIXES))
|
|||
|
{
|
|||
|
if (GetReadMethod(propertyDef.PropertyType, out MethodReference readMr))
|
|||
|
{
|
|||
|
MethodReference setMr = base.Module.ImportReference(propertyDef.SetMethod);
|
|||
|
rp.CreateReadIntoClassOrStruct(readerMd, readerPd, readMr, objectVd, setMr, propertyDef.PropertyType);
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
//Gets or creates writer method and outputs it. Returns true if method is found or created.
|
|||
|
bool GetReadMethod(TypeReference tr, out MethodReference readMr)
|
|||
|
{
|
|||
|
tr = base.ImportReference(tr);
|
|||
|
readMr = rp.GetOrCreateReadMethodReference(tr);
|
|||
|
return (readMr != null);
|
|||
|
}
|
|||
|
|
|||
|
return true;
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Creates the stub for a new reader method.
|
|||
|
/// </summary>
|
|||
|
/// <param name="objectTypeRef"></param>
|
|||
|
/// <returns></returns>
|
|||
|
public MethodDefinition CreateStaticReaderStubMethodDefinition(TypeReference objectTypeRef, string nameExtension = WriterProcessor.GENERATED_WRITER_NAMESPACE)
|
|||
|
{
|
|||
|
string methodName = $"{GENERATED_READ_PREFIX}{objectTypeRef.FullName}{nameExtension}s";
|
|||
|
// create new reader for this type
|
|||
|
TypeDefinition readerTypeDef = base.GetClass<GeneralHelper>().GetOrCreateClass(out _, GENERATED_TYPE_ATTRIBUTES, GENERATED_READERS_CLASS_NAME, null);
|
|||
|
MethodDefinition readerMethodDef = readerTypeDef.AddMethod(methodName,
|
|||
|
ReaderProcessor.GENERATED_METHOD_ATTRIBUTES,
|
|||
|
objectTypeRef);
|
|||
|
|
|||
|
base.GetClass<GeneralHelper>().CreateParameter(readerMethodDef, base.GetClass<ReaderImports>().Reader_TypeRef, "reader");
|
|||
|
readerMethodDef.Body.InitLocals = true;
|
|||
|
|
|||
|
return readerMethodDef;
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
}
|
|||
|
}
|