fishbait/FishBait-UnityProject/Assets/FishNet/CodeGenerating/Helpers/ReaderHelper.cs
2022-08-14 14:51:58 +02:00

478 lines
No EOL
20 KiB
C#

using FishNet.CodeGenerating.Helping.Extension;
using FishNet.CodeGenerating.ILCore;
using FishNet.Connection;
using FishNet.Serializing;
using MonoFN.Cecil;
using MonoFN.Cecil.Cil;
using System;
using System.Collections.Generic;
using System.Reflection;
using UnityEngine;
namespace FishNet.CodeGenerating.Helping
{
internal class ReaderHelper
{
#region Reflection references.
internal TypeReference PooledReader_TypeRef;
internal TypeReference Reader_TypeRef;
internal TypeReference NetworkConnection_TypeRef;
internal MethodReference PooledReader_ReadNetworkBehaviour_MethodRef;
private readonly Dictionary<TypeReference, MethodReference> _instancedReaderMethods = new Dictionary<TypeReference, MethodReference>(new TypeReferenceComparer());
private readonly Dictionary<TypeReference, MethodReference> _staticReaderMethods = new Dictionary<TypeReference, MethodReference>(new TypeReferenceComparer());
private HashSet<TypeReference> _autoPackedMethods = new HashSet<TypeReference>(new TypeReferenceComparer());
private MethodReference Reader_ReadPackedWhole_MethodRef;
internal MethodReference Reader_ReadDictionary_MethodRef;
internal MethodReference Reader_ReadToCollection_MethodRef;
#endregion
#region Const.
internal const string READ_PREFIX = "Read";
/// <summary>
/// Types to exclude from being scanned for auto serialization.
/// </summary>
public static System.Type[] EXCLUDED_AUTO_SERIALIZER_TYPES => WriterHelper.EXCLUDED_AUTO_SERIALIZER_TYPES;
/// <summary>
/// Types to exclude from being scanned for auto serialization.
/// </summary>
public static string[] EXCLUDED_ASSEMBLY_PREFIXES => WriterHelper.EXCLUDED_ASSEMBLY_PREFIXES;
#endregion
/// <summary>
/// Imports references needed by this helper.
/// </summary>
/// <param name="moduleDef"></param>
/// <returns></returns>
internal bool ImportReferences()
{
PooledReader_TypeRef = CodegenSession.ImportReference(typeof(PooledReader));
Reader_TypeRef = CodegenSession.ImportReference(typeof(Reader));
NetworkConnection_TypeRef = CodegenSession.ImportReference(typeof(NetworkConnection));
Type pooledReaderType = typeof(PooledReader);
foreach (MethodInfo methodInfo in pooledReaderType.GetMethods())
{
/* Special methods. */
//ReadPackedWhole.
if (methodInfo.Name == nameof(PooledReader.ReadPackedWhole))
{
Reader_ReadPackedWhole_MethodRef = CodegenSession.ImportReference(methodInfo);
continue;
}
//ReadToCollection.
else if (methodInfo.Name == nameof(PooledReader.ReadArray))
{
Reader_ReadToCollection_MethodRef = CodegenSession.ImportReference(methodInfo);
continue;
}
//ReadDictionary.
else if (methodInfo.Name == nameof(PooledReader.ReadDictionary))
{
Reader_ReadDictionary_MethodRef = CodegenSession.ImportReference(methodInfo);
continue;
}
else if (CodegenSession.GeneralHelper.CodegenExclude(methodInfo))
continue;
//Generic methods are not supported.
else if (methodInfo.IsGenericMethod)
continue;
//Not long enough to be a write method.
else if (methodInfo.Name.Length < READ_PREFIX.Length)
continue;
//Method name doesn't start with writePrefix.
else if (methodInfo.Name.Substring(0, READ_PREFIX.Length) != READ_PREFIX)
continue;
ParameterInfo[] parameterInfos = methodInfo.GetParameters();
//Can have at most one parameter for packing.
if (parameterInfos.Length > 1)
continue;
//If has one parameter make sure it's a packing type.
bool autoPackMethod = false;
if (parameterInfos.Length == 1)
{
autoPackMethod = (parameterInfos[0].ParameterType == typeof(AutoPackType));
if (!autoPackMethod)
continue;
}
/* TypeReference for the return type
* of the read method. */
TypeReference typeRef = CodegenSession.ImportReference(methodInfo.ReturnType);
MethodReference methodRef = CodegenSession.ImportReference(methodInfo);
/* If here all checks pass. */
AddReaderMethod(typeRef, methodRef, true, true);
if (autoPackMethod)
_autoPackedMethods.Add(typeRef);
}
Type readerExtensionsType = typeof(ReaderExtensions);
foreach (MethodInfo methodInfo in readerExtensionsType.GetMethods())
{
if (CodegenSession.GeneralHelper.CodegenExclude(methodInfo))
continue;
//Generic methods are not supported.
if (methodInfo.IsGenericMethod)
continue;
//Not static.
if (!methodInfo.IsStatic)
continue;
//Not long enough to be a write method.
if (methodInfo.Name.Length < READ_PREFIX.Length)
continue;
//Method name doesn't start with writePrefix.
if (methodInfo.Name.Substring(0, READ_PREFIX.Length) != READ_PREFIX)
continue;
ParameterInfo[] parameterInfos = methodInfo.GetParameters();
//Can have at most one parameter for packing.
if (parameterInfos.Length > 2)
continue;
//If has 2 parameters make sure it's a packing type.
bool autoPackMethod = false;
if (parameterInfos.Length == 2)
{
autoPackMethod = (parameterInfos[1].ParameterType == typeof(AutoPackType));
if (!autoPackMethod)
continue;
}
/* TypeReference for the return type
* of the read method. */
TypeReference typeRef = CodegenSession.ImportReference(methodInfo.ReturnType);
MethodReference methodRef = CodegenSession.ImportReference(methodInfo);
/* If here all checks pass. */
AddReaderMethod(typeRef, methodRef, false, true);
}
return true;
}
/// <summary>
/// Creates generic write delegates for all currently known write types.
/// </summary>
internal bool CreateGenericDelegates()
{
bool modified = false;
/* Only write statics. This will include extensions and generated. */
foreach (KeyValuePair<TypeReference, MethodReference> item in _staticReaderMethods)
{
if (FishNetILPP.CODEGEN_THIS_NAMESPACE.Length == 0 || item.Key.FullName.Contains(FishNetILPP.CODEGEN_THIS_NAMESPACE))
{
CodegenSession.GenericReaderHelper.CreateReadDelegate(item.Value);
modified = true;
}
}
return modified;
}
/// <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 (!CodegenSession.GeneralHelper.HasNonSerializableAttribute(typeRef.CachedResolve()))
{
MethodReference methodRef = CodegenSession.ReaderGenerator.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 = GetFavoredReadMethodReference(CodegenSession.GeneralHelper.GetTypeReference(typeof(bool)), true);
processor.Emit(OpCodes.Ldarg, readerParameterDef);
processor.Emit(OpCodes.Callvirt, 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().
processor.Emit(OpCodes.Callvirt, Reader_ReadPackedWhole_MethodRef);
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)
{
_instancedReaderMethods.TryGetValue(typeRef, out MethodReference methodRef);
return methodRef;
}
/// <summary>
/// Returns the MethodReference for typeRef.
/// </summary>
/// <param name="typeRef"></param>
/// <returns></returns>
internal MethodReference GetStaticReadMethodReference(TypeReference typeRef)
{
_staticReaderMethods.TryGetValue(typeRef, 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 GetFavoredReadMethodReference(TypeReference typeRef, bool favorInstanced)
{
MethodReference result;
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 GetOrCreateFavoredReadMethodReference(TypeReference typeRef, bool favorInstanced)
{
//Try to get existing writer, if not present make one.
MethodReference readMethodRef = GetFavoredReadMethodReference(typeRef, favorInstanced);
if (readMethodRef == null)
readMethodRef = CodegenSession.ReaderGenerator.CreateReader(typeRef);
if (readMethodRef == null)
CodegenSession.LogError($"Could not create deserializer for {typeRef.FullName}.");
return readMethodRef;
}
#endregion
/// <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)
{
Dictionary<TypeReference, MethodReference> dict = (instanced) ?
_instancedReaderMethods : _staticReaderMethods;
if (useAdd)
dict.Add(typeRef, methodRef);
else
dict[typeRef] = methodRef;
}
/// <summary>
/// Removes typeRef from static/instanced reader methods.
/// </summary>
internal void RemoveReaderMethod(TypeReference typeRef, bool instanced)
{
Dictionary<TypeReference, MethodReference> dict = (instanced) ?
_instancedReaderMethods : _staticReaderMethods;
dict.Remove(typeRef);
}
/// <summary>
/// Creates read instructions returning instructions and outputing variable of read result.
/// </summary>
/// <param name="processor"></param>
/// <param name="methodDef"></param>
/// <param name="readerParameterDef"></param>
/// <param name="readTypeRef"></param>
/// <param name="diagnostics"></param>
/// <returns></returns>
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 readerMethodRef = GetFavoredReadMethodReference(readTypeRef, true);
if (readerMethodRef != null)
{
//Make a local variable.
createdVariableDef = CodegenSession.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 = CodegenSession.GeneralHelper.GetDefaultAutoPackType(readTypeRef);
insts.Add(processor.Create(OpCodes.Ldc_I4, (int)packType));
}
insts.Add(processor.Create(OpCodes.Call, readerMethodRef));
//Store into local variable.
insts.Add(processor.Create(OpCodes.Stloc, createdVariableDef));
return insts;
}
else
{
CodegenSession.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;
processor.Emit(loadOpCode, objectVd);
//reader.
processor.Emit(OpCodes.Ldarg, readerPd);
if (IsAutoPackedType(valueFr.FieldType))
{
AutoPackType packType = CodegenSession.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
{
CodegenSession.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;
processor.Emit(loadOpCode, objectVariableDef);
//reader.
processor.Emit(OpCodes.Ldarg, readerPd);
if (IsAutoPackedType(readTr))
{
AutoPackType packType = CodegenSession.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
{
CodegenSession.LogError($"Reader not found for {readTr.FullName}.");
return false;
}
}
}
}