2020-11-30 08:12:07 +00:00

326 lines
14 KiB
C#

using System;
using System.Collections.Generic;
using Mono.CecilX;
using Mono.CecilX.Cil;
using Mono.CecilX.Rocks;
namespace Mirror.Weaver
{
public static class Readers
{
static Dictionary<string, MethodReference> readFuncs;
public static void Init()
{
readFuncs = new Dictionary<string, MethodReference>();
}
internal static void Register(TypeReference dataType, MethodReference methodReference)
{
readFuncs[dataType.FullName] = methodReference;
}
public static MethodReference GetReadFunc(TypeReference variableReference)
{
if (readFuncs.TryGetValue(variableReference.FullName, out MethodReference foundFunc))
{
return foundFunc;
}
// Arrays are special, if we resolve them, we get teh element type,
// so the following ifs might choke on it for scriptable objects
// or other objects that require a custom serializer
// thus check if it is an array and skip all the checks.
if (variableReference.IsArray)
{
if (variableReference.IsMultidimensionalArray())
{
Weaver.Error($"{variableReference.Name} is an unsupported type. Multidimensional arrays are not supported", variableReference);
return null;
}
return GenerateReadCollection(variableReference, variableReference.GetElementType(), nameof(NetworkReaderExtensions.ReadArray));
}
TypeDefinition variableDefinition = variableReference.Resolve();
if (variableDefinition == null)
{
Weaver.Error($"{variableReference.Name} is not a supported type", variableReference);
return null;
}
if (variableDefinition.IsDerivedFrom<UnityEngine.Component>())
{
Weaver.Error($"Cannot generate reader for component type {variableReference.Name}. Use a supported type or provide a custom reader", variableReference);
return null;
}
if (variableReference.Is<UnityEngine.Object>())
{
Weaver.Error($"Cannot generate reader for {variableReference.Name}. Use a supported type or provide a custom reader", variableReference);
return null;
}
if (variableReference.Is<UnityEngine.ScriptableObject>())
{
Weaver.Error($"Cannot generate reader for {variableReference.Name}. Use a supported type or provide a custom reader", variableReference);
return null;
}
if (variableReference.IsByReference)
{
// error??
Weaver.Error($"Cannot pass type {variableReference.Name} by reference", variableReference);
return null;
}
if (variableDefinition.HasGenericParameters && !variableDefinition.Is(typeof(ArraySegment<>)) && !variableDefinition.Is(typeof(List<>)))
{
Weaver.Error($"Cannot generate reader for generic variable {variableReference.Name}. Use a supported type or provide a custom reader", variableReference);
return null;
}
if (variableDefinition.IsInterface)
{
Weaver.Error($"Cannot generate reader for interface {variableReference.Name}. Use a supported type or provide a custom reader", variableReference);
return null;
}
if (variableDefinition.IsAbstract)
{
Weaver.Error($"Cannot generate reader for abstract class {variableReference.Name}. Use a supported type or provide a custom reader", variableReference);
return null;
}
if (variableDefinition.IsEnum)
{
return GenerateEnumReadFunc(variableReference);
}
else if (variableDefinition.Is(typeof(ArraySegment<>)))
{
return GenerateArraySegmentReadFunc(variableReference);
}
else if (variableDefinition.Is(typeof(List<>)))
{
GenericInstanceType genericInstance = (GenericInstanceType)variableReference;
TypeReference elementType = genericInstance.GenericArguments[0];
return GenerateReadCollection(variableReference, elementType, nameof(NetworkReaderExtensions.ReadList));
}
return GenerateClassOrStructReadFunction(variableReference);
}
static void RegisterReadFunc(TypeReference typeReference, MethodDefinition newReaderFunc)
{
readFuncs[typeReference.FullName] = newReaderFunc;
Weaver.WeaveLists.generateContainerClass.Methods.Add(newReaderFunc);
}
static MethodDefinition GenerateEnumReadFunc(TypeReference variable)
{
MethodDefinition readerFunc = GenerateReaderFunction(variable);
ILProcessor worker = readerFunc.Body.GetILProcessor();
worker.Append(worker.Create(OpCodes.Ldarg_0));
TypeReference underlyingType = variable.Resolve().GetEnumUnderlyingType();
MethodReference underlyingFunc = GetReadFunc(underlyingType);
worker.Append(worker.Create(OpCodes.Call, underlyingFunc));
worker.Append(worker.Create(OpCodes.Ret));
return readerFunc;
}
static MethodDefinition GenerateArraySegmentReadFunc(TypeReference variable)
{
GenericInstanceType genericInstance = (GenericInstanceType)variable;
TypeReference elementType = genericInstance.GenericArguments[0];
MethodDefinition readerFunc = GenerateReaderFunction(variable);
ILProcessor worker = readerFunc.Body.GetILProcessor();
// $array = reader.Read<[T]>()
ArrayType arrayType = elementType.MakeArrayType();
worker.Append(worker.Create(OpCodes.Ldarg_0));
worker.Append(worker.Create(OpCodes.Call, GetReadFunc(arrayType)));
// return new ArraySegment<T>($array);
worker.Append(worker.Create(OpCodes.Newobj, WeaverTypes.ArraySegmentConstructorReference.MakeHostInstanceGeneric(genericInstance)));
worker.Append(worker.Create(OpCodes.Ret));
return readerFunc;
}
private static MethodDefinition GenerateReaderFunction(TypeReference variable)
{
string functionName = "_Read_" + variable.FullName;
// create new reader for this type
MethodDefinition readerFunc = new MethodDefinition(functionName,
MethodAttributes.Public |
MethodAttributes.Static |
MethodAttributes.HideBySig,
Weaver.CurrentAssembly.MainModule.ImportReference(variable));
readerFunc.Parameters.Add(new ParameterDefinition("reader", ParameterAttributes.None, WeaverTypes.Import<NetworkReader>()));
readerFunc.Body.InitLocals = true;
RegisterReadFunc(variable, readerFunc);
return readerFunc;
}
static MethodDefinition GenerateReadCollection(TypeReference variable, TypeReference elementType, string readerFunction)
{
MethodDefinition readerFunc = GenerateReaderFunction(variable);
// generate readers for the element
GetReadFunc(elementType);
ModuleDefinition module = Weaver.CurrentAssembly.MainModule;
TypeReference readerExtensions = module.ImportReference(typeof(NetworkReaderExtensions));
MethodReference listReader = Resolvers.ResolveMethod(readerExtensions, Weaver.CurrentAssembly, readerFunction);
GenericInstanceMethod methodRef = new GenericInstanceMethod(listReader);
methodRef.GenericArguments.Add(elementType);
// generates
// return reader.ReadList<T>();
ILProcessor worker = readerFunc.Body.GetILProcessor();
worker.Append(worker.Create(OpCodes.Ldarg_0)); // reader
worker.Append(worker.Create(OpCodes.Call, methodRef)); // Read
worker.Append(worker.Create(OpCodes.Ret));
return readerFunc;
}
static MethodDefinition GenerateClassOrStructReadFunction(TypeReference variable)
{
MethodDefinition readerFunc = GenerateReaderFunction(variable);
// create local for return value
readerFunc.Body.Variables.Add(new VariableDefinition(variable));
ILProcessor worker = readerFunc.Body.GetILProcessor();
TypeDefinition td = variable.Resolve();
if (!td.IsValueType)
GenerateNullCheck(worker);
CreateNew(variable, worker, td);
ReadAllFields(variable, worker);
worker.Append(worker.Create(OpCodes.Ldloc_0));
worker.Append(worker.Create(OpCodes.Ret));
return readerFunc;
}
private static void GenerateNullCheck(ILProcessor worker)
{
// if (!reader.ReadBoolean()) {
// return null;
// }
worker.Append(worker.Create(OpCodes.Ldarg_0));
worker.Append(worker.Create(OpCodes.Call, GetReadFunc(WeaverTypes.Import<bool>())));
Instruction labelEmptyArray = worker.Create(OpCodes.Nop);
worker.Append(worker.Create(OpCodes.Brtrue, labelEmptyArray));
// return null
worker.Append(worker.Create(OpCodes.Ldnull));
worker.Append(worker.Create(OpCodes.Ret));
worker.Append(labelEmptyArray);
}
// Initialize the local variable with a new instance
static void CreateNew(TypeReference variable, ILProcessor worker, TypeDefinition td)
{
if (variable.IsValueType)
{
// structs are created with Initobj
worker.Append(worker.Create(OpCodes.Ldloca, 0));
worker.Append(worker.Create(OpCodes.Initobj, variable));
}
else if (td.IsDerivedFrom<UnityEngine.ScriptableObject>())
{
GenericInstanceMethod genericInstanceMethod = new GenericInstanceMethod(WeaverTypes.ScriptableObjectCreateInstanceMethod);
genericInstanceMethod.GenericArguments.Add(variable);
worker.Append(worker.Create(OpCodes.Call, genericInstanceMethod));
worker.Append(worker.Create(OpCodes.Stloc_0));
}
else
{
// classes are created with their constructor
MethodDefinition ctor = Resolvers.ResolveDefaultPublicCtor(variable);
if (ctor == null)
{
Weaver.Error($"{variable.Name} can't be deserialized because it has no default constructor", variable);
return;
}
MethodReference ctorRef = Weaver.CurrentAssembly.MainModule.ImportReference(ctor);
worker.Append(worker.Create(OpCodes.Newobj, ctorRef));
worker.Append(worker.Create(OpCodes.Stloc_0));
}
}
static void ReadAllFields(TypeReference variable, ILProcessor worker)
{
uint fields = 0;
foreach (FieldDefinition field in variable.FindAllPublicFields())
{
// mismatched ldloca/ldloc for struct/class combinations is invalid IL, which causes crash at runtime
OpCode opcode = variable.IsValueType ? OpCodes.Ldloca : OpCodes.Ldloc;
worker.Append(worker.Create(opcode, 0));
MethodReference readFunc = GetReadFunc(field.FieldType);
if (readFunc != null)
{
worker.Append(worker.Create(OpCodes.Ldarg_0));
worker.Append(worker.Create(OpCodes.Call, readFunc));
}
else
{
Weaver.Error($"{field.Name} has an unsupported type", field);
}
FieldReference fieldRef = Weaver.CurrentAssembly.MainModule.ImportReference(field);
worker.Append(worker.Create(OpCodes.Stfld, fieldRef));
fields++;
}
}
/// <summary>
/// Save a delegate for each one of the readers into <see cref="Reader{T}.read"/>
/// </summary>
/// <param name="worker"></param>
internal static void InitializeReaders(ILProcessor worker)
{
ModuleDefinition module = Weaver.CurrentAssembly.MainModule;
TypeReference genericReaderClassRef = module.ImportReference(typeof(Reader<>));
System.Reflection.FieldInfo fieldInfo = typeof(Reader<>).GetField(nameof(Reader<object>.read));
FieldReference fieldRef = module.ImportReference(fieldInfo);
TypeReference networkReaderRef = module.ImportReference(typeof(NetworkReader));
TypeReference funcRef = module.ImportReference(typeof(Func<,>));
MethodReference funcConstructorRef = module.ImportReference(typeof(Func<,>).GetConstructors()[0]);
foreach (MethodReference readFunc in readFuncs.Values)
{
TypeReference dataType = readFunc.ReturnType;
// create a Func<NetworkReader, T> delegate
worker.Append(worker.Create(OpCodes.Ldnull));
worker.Append(worker.Create(OpCodes.Ldftn, readFunc));
GenericInstanceType funcGenericInstance = funcRef.MakeGenericInstanceType(networkReaderRef, dataType);
MethodReference funcConstructorInstance = funcConstructorRef.MakeHostInstanceGeneric(funcGenericInstance);
worker.Append(worker.Create(OpCodes.Newobj, funcConstructorInstance));
// save it in Writer<T>.write
GenericInstanceType genericInstance = genericReaderClassRef.MakeGenericInstanceType(dataType);
FieldReference specializedField = fieldRef.SpecializeField(genericInstance);
worker.Append(worker.Create(OpCodes.Stsfld, specializedField));
}
}
}
}