1
Fork 0
mirror of https://github.com/Steffo99/better-tee.git synced 2024-11-22 23:34:18 +00:00
better-tee/Assets/Packages/Mirror/Editor/Weaver/Writers.cs
2019-09-16 00:28:36 +02:00

350 lines
14 KiB
C#

using System.Collections.Generic;
using Mono.CecilX;
using Mono.CecilX.Cil;
namespace Mirror.Weaver
{
public static class Writers
{
const int MaxRecursionCount = 128;
static Dictionary<string, MethodReference> writeFuncs;
public static void Init()
{
writeFuncs = new Dictionary<string, MethodReference>();
}
public static void Register(TypeReference dataType, MethodReference methodReference)
{
writeFuncs[dataType.FullName] = methodReference;
}
public static MethodReference GetWriteFunc(TypeReference variable, int recursionCount = 0)
{
if (writeFuncs.TryGetValue(variable.FullName, out MethodReference foundFunc))
{
return foundFunc;
}
if (variable.IsByReference)
{
// error??
Weaver.Error($"{variable} has unsupported type. Use one of Mirror supported types instead");
return null;
}
MethodDefinition newWriterFunc;
if (variable.IsArray)
{
newWriterFunc = GenerateArrayWriteFunc(variable, recursionCount);
}
else if (variable.Resolve().IsEnum)
{
return GetWriteFunc(variable.Resolve().GetEnumUnderlyingType(), recursionCount);
}
else if (variable.FullName.StartsWith("System.ArraySegment`1", System.StringComparison.Ordinal))
{
newWriterFunc = GenerateArraySegmentWriteFunc(variable, recursionCount);
}
else
{
newWriterFunc = GenerateStructWriterFunction(variable, recursionCount);
}
if (newWriterFunc == null)
{
return null;
}
RegisterWriteFunc(variable.FullName, newWriterFunc);
return newWriterFunc;
}
static void RegisterWriteFunc(string name, MethodDefinition newWriterFunc)
{
writeFuncs[name] = newWriterFunc;
Weaver.WeaveLists.generatedWriteFunctions.Add(newWriterFunc);
Weaver.ConfirmGeneratedCodeClass();
Weaver.WeaveLists.generateContainerClass.Methods.Add(newWriterFunc);
}
static MethodDefinition GenerateStructWriterFunction(TypeReference variable, int recursionCount)
{
if (recursionCount > MaxRecursionCount)
{
Weaver.Error($"{variable} can't be serialized because it references itself");
return null;
}
if (!Weaver.IsValidTypeToGenerate(variable.Resolve()))
{
return null;
}
string functionName = "_Write" + variable.Name + "_";
if (variable.DeclaringType != null)
{
functionName += variable.DeclaringType.Name;
}
else
{
functionName += "None";
}
// create new writer for this type
MethodDefinition writerFunc = new MethodDefinition(functionName,
MethodAttributes.Public |
MethodAttributes.Static |
MethodAttributes.HideBySig,
Weaver.voidType);
writerFunc.Parameters.Add(new ParameterDefinition("writer", ParameterAttributes.None, Weaver.CurrentAssembly.MainModule.ImportReference(Weaver.NetworkWriterType)));
writerFunc.Parameters.Add(new ParameterDefinition("value", ParameterAttributes.None, Weaver.CurrentAssembly.MainModule.ImportReference(variable)));
ILProcessor worker = writerFunc.Body.GetILProcessor();
uint fields = 0;
foreach (FieldDefinition field in variable.Resolve().Fields)
{
if (field.IsStatic || field.IsPrivate)
continue;
if (field.FieldType.Resolve().HasGenericParameters)
{
Weaver.Error($"{field} has unsupported type. Create a derived class instead of using generics");
return null;
}
if (field.FieldType.Resolve().IsInterface)
{
Weaver.Error($"{field} has unsupported type. Use a concrete class instead of an interface");
return null;
}
MethodReference writeFunc = GetWriteFunc(field.FieldType, recursionCount + 1);
if (writeFunc != null)
{
fields++;
worker.Append(worker.Create(OpCodes.Ldarg_0));
worker.Append(worker.Create(OpCodes.Ldarg_1));
worker.Append(worker.Create(OpCodes.Ldfld, field));
worker.Append(worker.Create(OpCodes.Call, writeFunc));
}
else
{
Weaver.Error($"{field} has unsupported type. Use a type supported by Mirror instead");
return null;
}
}
if (fields == 0)
{
Log.Warning($" {variable} has no no public or non-static fields to serialize");
}
worker.Append(worker.Create(OpCodes.Ret));
return writerFunc;
}
static MethodDefinition GenerateArrayWriteFunc(TypeReference variable, int recursionCount)
{
if (!variable.IsArrayType())
{
Weaver.Error($"{variable} is an unsupported type. Jagged and multidimensional arrays are not supported");
return null;
}
TypeReference elementType = variable.GetElementType();
MethodReference elementWriteFunc = GetWriteFunc(elementType, recursionCount + 1);
if (elementWriteFunc == null)
{
return null;
}
string functionName = "_WriteArray" + variable.GetElementType().Name + "_";
if (variable.DeclaringType != null)
{
functionName += variable.DeclaringType.Name;
}
else
{
functionName += "None";
}
// create new writer for this type
MethodDefinition writerFunc = new MethodDefinition(functionName,
MethodAttributes.Public |
MethodAttributes.Static |
MethodAttributes.HideBySig,
Weaver.voidType);
writerFunc.Parameters.Add(new ParameterDefinition("writer", ParameterAttributes.None, Weaver.CurrentAssembly.MainModule.ImportReference(Weaver.NetworkWriterType)));
writerFunc.Parameters.Add(new ParameterDefinition("value", ParameterAttributes.None, Weaver.CurrentAssembly.MainModule.ImportReference(variable)));
writerFunc.Body.Variables.Add(new VariableDefinition(Weaver.int32Type));
writerFunc.Body.Variables.Add(new VariableDefinition(Weaver.int32Type));
writerFunc.Body.InitLocals = true;
ILProcessor worker = writerFunc.Body.GetILProcessor();
// if (value == null)
// {
// writer.WritePackedInt32(-1);
// return;
// }
Instruction labelNull = worker.Create(OpCodes.Nop);
worker.Append(worker.Create(OpCodes.Ldarg_1));
worker.Append(worker.Create(OpCodes.Brtrue, labelNull));
worker.Append(worker.Create(OpCodes.Ldarg_0));
worker.Append(worker.Create(OpCodes.Ldc_I4_M1));
worker.Append(worker.Create(OpCodes.Call, GetWriteFunc(Weaver.int32Type)));
worker.Append(worker.Create(OpCodes.Ret));
// int length = value.Length;
worker.Append(labelNull);
worker.Append(worker.Create(OpCodes.Ldarg_1));
worker.Append(worker.Create(OpCodes.Ldlen));
worker.Append(worker.Create(OpCodes.Stloc_0));
// writer.WritePackedInt32(length);
worker.Append(worker.Create(OpCodes.Ldarg_0));
worker.Append(worker.Create(OpCodes.Ldloc_0));
worker.Append(worker.Create(OpCodes.Call, GetWriteFunc(Weaver.int32Type)));
// for (int i=0; i< value.length; i++) {
worker.Append(worker.Create(OpCodes.Ldc_I4_0));
worker.Append(worker.Create(OpCodes.Stloc_1));
Instruction labelHead = worker.Create(OpCodes.Nop);
worker.Append(worker.Create(OpCodes.Br, labelHead));
// loop body
Instruction labelBody = worker.Create(OpCodes.Nop);
worker.Append(labelBody);
// writer.Write(value[i]);
worker.Append(worker.Create(OpCodes.Ldarg_0));
worker.Append(worker.Create(OpCodes.Ldarg_1));
worker.Append(worker.Create(OpCodes.Ldloc_1));
worker.Append(worker.Create(OpCodes.Ldelema, variable.GetElementType()));
worker.Append(worker.Create(OpCodes.Ldobj, variable.GetElementType()));
worker.Append(worker.Create(OpCodes.Call, elementWriteFunc));
worker.Append(worker.Create(OpCodes.Ldloc_1));
worker.Append(worker.Create(OpCodes.Ldc_I4_1));
worker.Append(worker.Create(OpCodes.Add));
worker.Append(worker.Create(OpCodes.Stloc_1));
// end for loop
worker.Append(labelHead);
worker.Append(worker.Create(OpCodes.Ldloc_1));
worker.Append(worker.Create(OpCodes.Ldarg_1));
worker.Append(worker.Create(OpCodes.Ldlen));
worker.Append(worker.Create(OpCodes.Conv_I4));
worker.Append(worker.Create(OpCodes.Blt, labelBody));
// return
worker.Append(worker.Create(OpCodes.Ret));
return writerFunc;
}
static MethodDefinition GenerateArraySegmentWriteFunc(TypeReference variable, int recursionCount)
{
GenericInstanceType genericInstance = (GenericInstanceType)variable;
TypeReference elementType = genericInstance.GenericArguments[0];
MethodReference elementWriteFunc = GetWriteFunc(elementType, recursionCount + 1);
if (elementWriteFunc == null)
{
return null;
}
string functionName = "_WriteArraySegment_" + elementType.Name + "_";
if (variable.DeclaringType != null)
{
functionName += variable.DeclaringType.Name;
}
else
{
functionName += "None";
}
// create new writer for this type
MethodDefinition writerFunc = new MethodDefinition(functionName,
MethodAttributes.Public |
MethodAttributes.Static |
MethodAttributes.HideBySig,
Weaver.voidType);
writerFunc.Parameters.Add(new ParameterDefinition("writer", ParameterAttributes.None, Weaver.CurrentAssembly.MainModule.ImportReference(Weaver.NetworkWriterType)));
writerFunc.Parameters.Add(new ParameterDefinition("value", ParameterAttributes.None, variable));
writerFunc.Body.Variables.Add(new VariableDefinition(Weaver.int32Type));
writerFunc.Body.Variables.Add(new VariableDefinition(Weaver.int32Type));
writerFunc.Body.InitLocals = true;
ILProcessor worker = writerFunc.Body.GetILProcessor();
MethodReference countref = Weaver.ArraySegmentCountReference.MakeHostInstanceGeneric(genericInstance);
// int length = value.Count;
worker.Append(worker.Create(OpCodes.Ldarga_S, (byte)1));
worker.Append(worker.Create(OpCodes.Call, countref));
worker.Append(worker.Create(OpCodes.Stloc_0));
// writer.WritePackedInt32(length);
worker.Append(worker.Create(OpCodes.Ldarg_0));
worker.Append(worker.Create(OpCodes.Ldloc_0));
worker.Append(worker.Create(OpCodes.Call, GetWriteFunc(Weaver.int32Type)));
// Loop through the ArraySegment<T> and call the writer for each element.
// generates this:
// for (int i=0; i< length; i++)
// {
// writer.Write(value.Array[i + value.Offset]);
// }
worker.Append(worker.Create(OpCodes.Ldc_I4_0));
worker.Append(worker.Create(OpCodes.Stloc_1));
Instruction labelHead = worker.Create(OpCodes.Nop);
worker.Append(worker.Create(OpCodes.Br, labelHead));
// loop body
Instruction labelBody = worker.Create(OpCodes.Nop);
worker.Append(labelBody);
{
// writer.Write(value.Array[i + value.Offset]);
worker.Append(worker.Create(OpCodes.Ldarg_0));
worker.Append(worker.Create(OpCodes.Ldarga_S, (byte)1));
worker.Append(worker.Create(OpCodes.Call, Weaver.ArraySegmentArrayReference.MakeHostInstanceGeneric(genericInstance)));
worker.Append(worker.Create(OpCodes.Ldloc_1));
worker.Append(worker.Create(OpCodes.Ldarga_S, (byte)1));
worker.Append(worker.Create(OpCodes.Call, Weaver.ArraySegmentOffsetReference.MakeHostInstanceGeneric(genericInstance)));
worker.Append(worker.Create(OpCodes.Add));
worker.Append(worker.Create(OpCodes.Ldelema, elementType));
worker.Append(worker.Create(OpCodes.Ldobj, elementType));
worker.Append(worker.Create(OpCodes.Call, elementWriteFunc));
}
worker.Append(worker.Create(OpCodes.Ldloc_1));
worker.Append(worker.Create(OpCodes.Ldc_I4_1));
worker.Append(worker.Create(OpCodes.Add));
worker.Append(worker.Create(OpCodes.Stloc_1));
// end for loop
worker.Append(labelHead);
worker.Append(worker.Create(OpCodes.Ldloc_1));
worker.Append(worker.Create(OpCodes.Ldloc_0));
worker.Append(worker.Create(OpCodes.Blt, labelBody));
// return
worker.Append(worker.Create(OpCodes.Ret));
return writerFunc;
}
}
}