mirror of
https://github.com/smyalygames/monopoly.git
synced 2025-05-18 14:24:12 +02:00
326 lines
14 KiB
C#
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));
|
|
}
|
|
|
|
}
|
|
}
|
|
}
|