fishbait/UnityProject/Assets/FishNet/CodeGenerating/Helpers/WriterHelper.cs
NIMFER bf403a8f97 Ducktaped together a FishNet version of the Transport
Ducktaped together a FishNet version of the Transport, now I need to edit the LoadBalancer and Server so it connects and actually trades information
2022-08-14 03:55:25 +02:00

557 lines
No EOL
24 KiB
C#

using FishNet.CodeGenerating.Helping.Extension;
using FishNet.CodeGenerating.ILCore;
using FishNet.Object;
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 WriterHelper
{
#region Reflection references.
private MethodReference WriterPool_GetWriter_MethodRef;
private MethodReference Writer_WritePackedWhole_MethodRef;
internal TypeReference PooledWriter_TypeRef;
internal TypeReference Writer_TypeRef;
internal readonly Dictionary<TypeReference, MethodReference> _instancedWriterMethods = new Dictionary<TypeReference, MethodReference>(new TypeReferenceComparer());
private readonly Dictionary<TypeReference, MethodReference> _staticWriterMethods = new Dictionary<TypeReference, MethodReference>(new TypeReferenceComparer());
private HashSet<TypeReference> _autoPackedMethods = new HashSet<TypeReference>(new TypeReferenceComparer());
private MethodReference PooledWriter_Dispose_MethodRef;
internal MethodReference Writer_WriteDictionary_MethodRef;
internal TypeReference NetworkBehaviour_TypeRef;
#endregion
#region Const.
internal const string WRITE_PREFIX = "Write";
/// <summary>
/// Types to exclude from being scanned for auto serialization.
/// </summary>
public static readonly System.Type[] EXCLUDED_AUTO_SERIALIZER_TYPES = new System.Type[]
{
typeof(NetworkBehaviour)
};
/// <summary>
/// Types within assemblies which begin with these prefixes will not have serializers created for them.
/// </summary>
public static readonly string[] EXCLUDED_ASSEMBLY_PREFIXES = new string[]
{
"UnityEngine."
};
#endregion
/// <summary>
/// Imports references needed by this helper.
/// </summary>
/// <param name="moduleDef"></param>
/// <returns></returns>
internal bool ImportReferences()
{
PooledWriter_TypeRef = CodegenSession.ImportReference(typeof(PooledWriter));
Writer_TypeRef = CodegenSession.ImportReference(typeof(Writer));
NetworkBehaviour_TypeRef = CodegenSession.ImportReference(typeof(NetworkBehaviour));
//WriterPool.GetWriter
Type writerPoolType = typeof(WriterPool);
foreach (var methodInfo in writerPoolType.GetMethods())
{
if (methodInfo.Name == nameof(WriterPool.GetWriter))
WriterPool_GetWriter_MethodRef = CodegenSession.ImportReference(methodInfo);
}
Type pooledWriterType = typeof(PooledWriter);
foreach (MethodInfo methodInfo in pooledWriterType.GetMethods())
{
/* Special methods. */
//Write.Dispose.
if (methodInfo.Name == nameof(PooledWriter.Dispose))
{
PooledWriter_Dispose_MethodRef = CodegenSession.ImportReference(methodInfo);
continue;
}
//WritePackedWhole.
else if (methodInfo.Name == nameof(PooledWriter.WritePackedWhole))
{
Writer_WritePackedWhole_MethodRef = CodegenSession.ImportReference(methodInfo);
continue;
}
//WriteDictionary.
else if (methodInfo.Name == nameof(PooledWriter.WriteDictionary))
{
Writer_WriteDictionary_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 < WRITE_PREFIX.Length)
continue;
//Method name doesn't start with writePrefix.
else if (methodInfo.Name.Substring(0, WRITE_PREFIX.Length) != WRITE_PREFIX)
continue;
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)
continue;
/* If two parameters make sure the second parameter
* is a pack parameter. */
bool autoPackMethod = false;
if (parameterInfos.Length == 2)
{
autoPackMethod = (parameterInfos[1].ParameterType == typeof(AutoPackType));
if (!autoPackMethod)
continue;
}
//First parameter is generic; these are not supported.
if (parameterInfos[0].ParameterType.IsGenericParameter)
continue;
/* TypeReference for the first parameter in the write method.
* The first parameter will always be the type written. */
TypeReference typeRef = CodegenSession.ImportReference(parameterInfos[0].ParameterType);
/* If here all checks pass. */
MethodReference methodRef = CodegenSession.ImportReference(methodInfo);
AddWriterMethod(typeRef, methodRef, true, true);
if (autoPackMethod)
_autoPackedMethods.Add(typeRef);
}
Type writerExtensionsType = typeof(WriterExtensions);
foreach (MethodInfo methodInfo in writerExtensionsType.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 < WRITE_PREFIX.Length)
continue;
//Method name doesn't start with writePrefix.
if (methodInfo.Name.Substring(0, WRITE_PREFIX.Length) != WRITE_PREFIX)
continue;
ParameterInfo[] parameterInfos = methodInfo.GetParameters();
/* No parameters or more than 3 parameters. Most extension Write methods
* will have only 2 parameter but some will have 3 if
* there is a pack option. */
if (parameterInfos.Length < 2 || parameterInfos.Length > 3)
continue;
/* If 3 parameters make sure the 3rd parameter
* is a pack parameter. */
bool autoPackMethod = false;
if (parameterInfos.Length == 3)
{
autoPackMethod = (parameterInfos[2].ParameterType == typeof(AutoPackType));
if (!autoPackMethod)
continue;
}
//First parameter is generic; these are not supported.
if (parameterInfos[1].ParameterType.IsGenericParameter)
continue;
/* TypeReference for the second parameter in the write method.
* The first parameter will always be the type written. */
TypeReference typeRef = CodegenSession.ImportReference(parameterInfos[1].ParameterType);
/* If here all checks pass. */
MethodReference methodRef = CodegenSession.ImportReference(methodInfo);
AddWriterMethod(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 _staticWriterMethods)
{
if (FishNetILPP.CODEGEN_THIS_NAMESPACE.Length == 0 || item.Key.FullName.Contains(FishNetILPP.CODEGEN_THIS_NAMESPACE))
{
CodegenSession.GenericWriterHelper.CreateWriteDelegate(item.Value, true);
modified = true;
}
}
return modified;
}
/// <summary>
/// Returns if typeRef has a serializer.
/// </summary>
/// <param name="typeRef"></param>
/// <returns></returns>
internal bool HasSerializer(TypeReference typeRef, bool createMissing)
{
bool result = (GetInstancedWriteMethodReference(typeRef) != null) ||
(GetStaticWriteMethodReference(typeRef) != null);
if (!result && createMissing)
{
if (!CodegenSession.GeneralHelper.HasNonSerializableAttribute(typeRef.CachedResolve()))
{
MethodReference methodRef = CodegenSession.WriterGenerator.CreateWriter(typeRef);
result = (methodRef != null);
}
}
return result;
}
#region GetWriterMethodReference.
/// <summary>
/// Returns the MethodReference for typeRef.
/// </summary>
/// <param name="typeRef"></param>
/// <returns></returns>
internal MethodReference GetInstancedWriteMethodReference(TypeReference typeRef)
{
_instancedWriterMethods.TryGetValue(typeRef, out MethodReference methodRef);
return methodRef;
}
/// <summary>
/// Returns the MethodReference for typeRef.
/// </summary>
/// <param name="typeRef"></param>
/// <returns></returns>
internal MethodReference GetStaticWriteMethodReference(TypeReference typeRef)
{
_staticWriterMethods.TryGetValue(typeRef, out MethodReference methodRef);
return methodRef;
}
/// <summary>
/// Returns the MethodReference for typeRef favoring instanced or static.
/// </summary>
/// <param name="typeRef"></param>
/// <param name="favorInstanced"></param>
/// <returns></returns>
internal MethodReference GetFavoredWriteMethodReference(TypeReference typeRef, bool favorInstanced)
{
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;
}
/// <summary>
/// Gets the write MethodRef for typeRef, or tries to create it if not present.
/// </summary>
/// <param name="typeRef"></param>
/// <returns></returns>
internal MethodReference GetOrCreateFavoredWriteMethodReference(TypeReference typeRef, bool favorInstanced)
{
//Try to get existing writer, if not present make one.
MethodReference writeMethodRef = GetFavoredWriteMethodReference(typeRef, favorInstanced);
if (writeMethodRef == null)
writeMethodRef = CodegenSession.WriterGenerator.CreateWriter(typeRef);
if (writeMethodRef == null)
CodegenSession.LogError($"Could not create serializer for {typeRef.FullName}.");
return writeMethodRef;
}
#endregion
/// <summary>
/// Adds typeRef, methodDef to InstancedWriterMethods.
/// </summary>
/// <param name="typeRef"></param>
/// <param name="methodRef"></param>
/// <param name="useAdd"></param>
internal void AddWriterMethod(TypeReference typeRef, MethodReference methodRef, bool instanced, bool useAdd)
{
Dictionary<TypeReference, MethodReference> dict = (instanced) ?
_instancedWriterMethods : _staticWriterMethods;
if (useAdd)
dict.Add(typeRef, methodRef);
else
dict[typeRef] = methodRef;
}
/// <summary>
/// Removes typeRef from Static or InstancedWriterMethods.
/// </summary>
internal void RemoveWriterMethod(TypeReference typeRef, bool instanced)
{
Dictionary<TypeReference, MethodReference> dict = (instanced) ?
_instancedWriterMethods : _staticWriterMethods;
dict.Remove(typeRef);
}
/// <summary>
/// Creates a PooledWriter within the body/ and returns its variable index.
/// EG: PooledWriter writer = WriterPool.GetWriter();
/// </summary>
internal VariableDefinition CreatePooledWriter(MethodDefinition methodDef)
{
VariableDefinition resultVd;
List<Instruction> insts = CreatePooledWriter(methodDef, out resultVd);
ILProcessor processor = methodDef.Body.GetILProcessor();
processor.Add(insts);
return resultVd;
}
/// <summary>
/// Creates a PooledWriter within the body/ and returns its variable index.
/// EG: PooledWriter writer = WriterPool.GetWriter();
/// </summary>
/// <param name="processor"></param>
/// <param name="methodDef"></param>
/// <returns></returns>
internal List<Instruction> CreatePooledWriter(MethodDefinition methodDef, out VariableDefinition resultVd)
{
List<Instruction> insts = new List<Instruction>();
ILProcessor processor = methodDef.Body.GetILProcessor();
resultVd = CodegenSession.GeneralHelper.CreateVariable(methodDef, PooledWriter_TypeRef);
//Get a pooled writer from WriterPool and assign it to added PooledWriter.
insts.Add(processor.Create(OpCodes.Call, WriterPool_GetWriter_MethodRef));
insts.Add(processor.Create(OpCodes.Stloc, resultVd));
return insts;
}
/// <summary>
/// Calls Dispose on a PooledWriter.
/// EG: writer.Dispose();
/// </summary>
/// <param name="processor"></param>
/// <param name="writerDefinition"></param>
internal List<Instruction> DisposePooledWriter(MethodDefinition methodDef, VariableDefinition writerDefinition)
{
List<Instruction> insts = new List<Instruction>();
ILProcessor processor = methodDef.Body.GetILProcessor();
insts.Add(processor.Create(OpCodes.Ldloc, writerDefinition));
insts.Add(processor.Create(OpCodes.Callvirt, PooledWriter_Dispose_MethodRef));
return insts;
}
/// <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 second argument using a boolean.
/// </summary>
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
/// <summary>
/// Creates a call to WritePackWhole with value.
/// </summary>
/// <param name="processor"></param>
/// <param name="value"></param>
internal void CreateWritePackedWhole(ILProcessor processor, ParameterDefinition writerParameterDef, int value)
{
//Create local int and set it to value.
VariableDefinition intVariableDef = CodegenSession.GeneralHelper.CreateVariable(processor.Body.Method, typeof(int));
CodegenSession.GeneralHelper.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(OpCodes.Callvirt, Writer_WritePackedWhole_MethodRef);
}
/// <summary>
/// Creates a call to WritePackWhole with value.
/// </summary>
/// <param name="processor"></param>
/// <param name="value"></param>
internal void CreateWritePackedWhole(ILProcessor processor, ParameterDefinition writerParameterDef, VariableDefinition value)
{
//Writer.
processor.Emit(OpCodes.Ldarg, writerParameterDef);
//Writer.WritePackedWhole(value).
processor.Emit(OpCodes.Ldloc, value);
processor.Emit(OpCodes.Conv_U8);
processor.Emit(OpCodes.Callvirt, Writer_WritePackedWhole_MethodRef);
}
#endregion
/// <summary>
/// Creates a call to WriteBoolean with value.
/// </summary>
/// <param name="processor"></param>
/// <param name="writerParameterDef"></param>
/// <param name="value"></param>
internal void CreateWriteBool(ILProcessor processor, ParameterDefinition writerParameterDef, bool value)
{
MethodReference writeBoolMethodRef = GetFavoredWriteMethodReference(CodegenSession.GeneralHelper.GetTypeReference(typeof(bool)), true);
processor.Emit(OpCodes.Ldarg, writerParameterDef);
int intValue = (value) ? 1 : 0;
processor.Emit(OpCodes.Ldc_I4, intValue);
processor.Emit(OpCodes.Callvirt, writeBoolMethodRef);
}
/// <summary>
/// Creates a Write call on a PooledWriter variable for parameterDef.
/// EG: writer.WriteBool(xxxxx);
/// </summary>
internal List<Instruction> CreateWriteInstructions(MethodDefinition methodDef, object pooledWriterDef, ParameterDefinition valueParameterDef, MethodReference writeMethodRef)
{
List<Instruction> insts = new List<Instruction>();
ILProcessor processor = methodDef.Body.GetILProcessor();
if (writeMethodRef != 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
{
CodegenSession.LogError($"{pooledWriterDef.GetType().FullName} is not a valid writerDef. Type must be VariableDefinition or ParameterDefinition.");
return new List<Instruction>();
}
insts.Add(processor.Create(OpCodes.Ldarg, valueParameterDef));
//If an auto pack method then insert default value.
if (_autoPackedMethods.Contains(valueParameterDef.ParameterType))
{
AutoPackType packType = CodegenSession.GeneralHelper.GetDefaultAutoPackType(valueParameterDef.ParameterType);
insts.Add(processor.Create(OpCodes.Ldc_I4, (int)packType));
}
insts.Add(processor.Create(OpCodes.Call, writeMethodRef));
return insts;
}
else
{
CodegenSession.LogError($"Writer not found for {valueParameterDef.ParameterType.FullName}.");
return new List<Instruction>();
}
}
/// <summary>
/// Creates a Write call on a PooledWriter variable for parameterDef.
/// EG: writer.WriteBool(xxxxx);
/// </summary>
internal void CreateWrite(MethodDefinition methodDef, object writerDef, ParameterDefinition valuePd, MethodReference writeMr)
{
List<Instruction> insts = CreateWriteInstructions(methodDef, writerDef, valuePd, writeMr);
ILProcessor processor = methodDef.Body.GetILProcessor();
processor.Add(insts);
}
/// <summary>
/// Creates a Write call to a writer.
/// EG: StaticClass.WriteBool(xxxxx);
/// </summary>
/// <param name="processor"></param>
/// <param name="fieldDef"></param>
internal void CreateWrite(MethodDefinition writerMd, ParameterDefinition valuePd, FieldDefinition fieldDef, MethodReference writeMr)
{
if (writeMr != null)
{
ILProcessor processor = writerMd.Body.GetILProcessor();
ParameterDefinition writerPd = writerMd.Parameters[0];
FieldReference fieldRef = CodegenSession.GeneralHelper.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 = CodegenSession.GeneralHelper.GetDefaultAutoPackType(fieldDef.FieldType);
processor.Emit(OpCodes.Ldc_I4, (int)packType);
}
processor.Emit(OpCodes.Call, writeMr);
}
else
{
CodegenSession.LogError($"Writer not found for {fieldDef.FieldType.FullName}.");
}
}
/// <summary>
/// Creates a Write call to a writer.
/// EG: StaticClass.WriteBool(xxxxx);
/// </summary>
/// <param name="processor"></param>
/// <param name="propertyDef"></param>
internal void CreateWrite(MethodDefinition writerMd, ParameterDefinition valuePd, MethodReference getMr, MethodReference writeMr)
{
TypeReference returnTr = getMr.ReturnType;
if (writeMr != null)
{
ILProcessor processor = writerMd.Body.GetILProcessor();
ParameterDefinition writerPd = writerMd.Parameters[0];
processor.Emit(OpCodes.Ldarg, writerPd);
processor.Emit(OpCodes.Ldarg, valuePd);
processor.Emit(OpCodes.Call, getMr);
//If an auto pack method then insert default value.
if (_autoPackedMethods.Contains(returnTr))
{
AutoPackType packType = CodegenSession.GeneralHelper.GetDefaultAutoPackType(returnTr);
processor.Emit(OpCodes.Ldc_I4, (int)packType);
}
processor.Emit(OpCodes.Call, writeMr);
}
else
{
CodegenSession.LogError($"Writer not found for {returnTr.FullName}.");
}
}
}
}