Skip to content

Commit

Permalink
feat: support writing and reading array segments (#918)
Browse files Browse the repository at this point in the history
* Unit tests for writing array segments

* Add tests back for ArraySegment<int>

* Work in progress trying to generate writer

* Avoid boxing

* Weaver can now generate readers and writers for ArraySegment<T>

* Added by accident

* Clarify the loop

* Explain method specializer

* Remove unused method

* Explain loop in reader
  • Loading branch information
paulpach authored and miwarnec committed Jul 30, 2019
1 parent 0df129f commit f9ff443
Show file tree
Hide file tree
Showing 6 changed files with 326 additions and 8 deletions.
25 changes: 25 additions & 0 deletions Assets/Mirror/Editor/Weaver/Extensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -112,5 +112,30 @@ public static bool CanBeResolved(this TypeReference parent)
}
return true;
}


// Given a method of a generic class such as ArraySegment<T>.get_Count,
// and a generic instance such as ArraySegment<int>
// Creates a reference to the specialized method ArraySegment<int>.get_Count;
// Note that calling ArraySegment<T>.get_Count directly gives an invalid IL error
public static MethodReference MakeHostInstanceGeneric(this MethodReference self, GenericInstanceType instanceType)
{

MethodReference reference = new MethodReference(self.Name, self.ReturnType, instanceType)
{
CallingConvention = self.CallingConvention,
HasThis = self.HasThis,
ExplicitThis = self.ExplicitThis
};

foreach (ParameterDefinition parameter in self.Parameters)
reference.Parameters.Add(new ParameterDefinition(parameter.ParameterType));

foreach (GenericParameter generic_parameter in self.GenericParameters)
reference.GenericParameters.Add(new GenericParameter(generic_parameter.Name, reference));

return Weaver.CurrentAssembly.MainModule.ImportReference(reference);
}

}
}
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ static void GenerateSerialization(TypeDefinition td)
if (field.IsStatic || field.IsPrivate || field.IsSpecialName)
continue;

if (field.FieldType.Resolve().HasGenericParameters && field.FieldType.FullName != "System.ArraySegment`1<System.Byte>")
if (field.FieldType.Resolve().HasGenericParameters && !field.FieldType.FullName.StartsWith("System.ArraySegment`1", System.StringComparison.Ordinal))
{
Weaver.Error($"{field} cannot have generic type {field.FieldType}. Consider creating a class that derives the generic type");
return;
Expand Down
98 changes: 98 additions & 0 deletions Assets/Mirror/Editor/Weaver/Readers.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
using System.Collections.Generic;
using Mono.CecilX;
using Mono.CecilX.Cil;
using Mono.CecilX.Rocks;


namespace Mirror.Weaver
Expand Down Expand Up @@ -86,6 +87,10 @@ public static MethodReference GetReadFunc(TypeReference variable, int recursionC
{
return GetReadFunc(td.GetEnumUnderlyingType(), recursionCount);
}
else if (variable.FullName.StartsWith("System.ArraySegment`1", System.StringComparison.Ordinal))
{
newReaderFunc = GenerateArraySegmentReadFunc(variable, recursionCount);
}
else
{
newReaderFunc = GenerateStructReadFunction(variable, recursionCount);
Expand Down Expand Up @@ -209,6 +214,99 @@ static MethodDefinition GenerateArrayReadFunc(TypeReference variable, int recurs
return readerFunc;
}

static MethodDefinition GenerateArraySegmentReadFunc(TypeReference variable, int recursionCount)
{
GenericInstanceType genericInstance = (GenericInstanceType)variable;
TypeReference elementType = genericInstance.GenericArguments[0];

MethodReference elementReadFunc = GetReadFunc(elementType, recursionCount + 1);
if (elementReadFunc == null)
{
return null;
}

string functionName = "_ReadArraySegment_" + variable.GetElementType().Name + "_";
if (variable.DeclaringType != null)
{
functionName += variable.DeclaringType.Name;
}
else
{
functionName += "None";
}

// create new reader for this type
MethodDefinition readerFunc = new MethodDefinition(functionName,
MethodAttributes.Public |
MethodAttributes.Static |
MethodAttributes.HideBySig,
variable);

readerFunc.Parameters.Add(new ParameterDefinition("reader", ParameterAttributes.None, Weaver.CurrentAssembly.MainModule.ImportReference(Weaver.NetworkReaderType)));

// int lengh
readerFunc.Body.Variables.Add(new VariableDefinition(Weaver.int32Type));
// T[] array
readerFunc.Body.Variables.Add(new VariableDefinition(elementType.MakeArrayType()));
// int i;
readerFunc.Body.Variables.Add(new VariableDefinition(Weaver.int32Type));
readerFunc.Body.InitLocals = true;

ILProcessor worker = readerFunc.Body.GetILProcessor();

// int length = reader.ReadPackedInt32();
worker.Append(worker.Create(OpCodes.Ldarg_0));
worker.Append(worker.Create(OpCodes.Call, Weaver.NetworkReaderReadPackedInt32));
worker.Append(worker.Create(OpCodes.Stloc_0));

// T[] array = new int[length]
worker.Append(worker.Create(OpCodes.Ldloc_0));
worker.Append(worker.Create(OpCodes.Newarr, elementType));
worker.Append(worker.Create(OpCodes.Stloc_1));


// loop through array and deserialize each element
// generates code like this
// for (int i=0; i< length ; i++)
// {
// value[i] = reader.ReadXXX();
// }
worker.Append(worker.Create(OpCodes.Ldc_I4_0));
worker.Append(worker.Create(OpCodes.Stloc_2));
Instruction labelHead = worker.Create(OpCodes.Nop);
worker.Append(worker.Create(OpCodes.Br, labelHead));

// loop body
Instruction labelBody = worker.Create(OpCodes.Nop);
worker.Append(labelBody);
{
// value[i] = reader.ReadT();
worker.Append(worker.Create(OpCodes.Ldloc_1));
worker.Append(worker.Create(OpCodes.Ldloc_2));
worker.Append(worker.Create(OpCodes.Ldelema, elementType));
worker.Append(worker.Create(OpCodes.Ldarg_0));
worker.Append(worker.Create(OpCodes.Call, elementReadFunc));
worker.Append(worker.Create(OpCodes.Stobj, elementType));
}

worker.Append(worker.Create(OpCodes.Ldloc_2));
worker.Append(worker.Create(OpCodes.Ldc_I4_1));
worker.Append(worker.Create(OpCodes.Add));
worker.Append(worker.Create(OpCodes.Stloc_2));

// loop while check
worker.Append(labelHead);
worker.Append(worker.Create(OpCodes.Ldloc_2));
worker.Append(worker.Create(OpCodes.Ldloc_0));
worker.Append(worker.Create(OpCodes.Blt, labelBody));

// return new ArraySegment<T>(array);
worker.Append(worker.Create(OpCodes.Ldloc_1));
worker.Append(worker.Create(OpCodes.Newobj, Weaver.ArraySegmentConstructorReference.MakeHostInstanceGeneric(genericInstance)));
worker.Append(worker.Create(OpCodes.Ret));
return readerFunc;
}

static MethodDefinition GenerateStructReadFunction(TypeReference variable, int recursionCount)
{
if (recursionCount > MaxRecursionCount)
Expand Down
14 changes: 14 additions & 0 deletions Assets/Mirror/Editor/Weaver/Weaver.cs
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,13 @@ class Weaver
public static TypeReference SyncObjectType;
public static MethodReference InitSyncObjectReference;

// array segment
public static TypeReference ArraySegmentType;
public static MethodReference ArraySegmentConstructorReference;
public static MethodReference ArraySegmentArrayReference;
public static MethodReference ArraySegmentOffsetReference;
public static MethodReference ArraySegmentCountReference;

// system types
public static TypeReference voidType;
public static TypeReference singleType;
Expand Down Expand Up @@ -290,6 +297,13 @@ static void SetupTargetTypes()
IEnumeratorType = ImportCorLibType("System.Collections.IEnumerator");
guidType = ImportCorLibType("System.Guid");

ArraySegmentType = ImportCorLibType("System.ArraySegment`1");
ArraySegmentArrayReference = Resolvers.ResolveProperty(ArraySegmentType, CurrentAssembly, "Array");
ArraySegmentCountReference = Resolvers.ResolveProperty(ArraySegmentType, CurrentAssembly, "Count");
ArraySegmentOffsetReference = Resolvers.ResolveProperty(ArraySegmentType, CurrentAssembly, "Offset");
ArraySegmentConstructorReference = Resolvers.ResolveMethod(ArraySegmentType, CurrentAssembly, ".ctor");


NetworkReaderType = NetAssembly.MainModule.GetType("Mirror.NetworkReader");
TypeDefinition NetworkReaderDef = NetworkReaderType.Resolve();

Expand Down
101 changes: 101 additions & 0 deletions Assets/Mirror/Editor/Weaver/Writers.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
using System.Collections.Generic;
using Mono.CecilX;
using Mono.CecilX.Cil;
using Mono.CecilX.Rocks;

namespace Mirror.Weaver
{
Expand Down Expand Up @@ -79,6 +80,10 @@ public static MethodReference GetWriteFunc(TypeReference variable, int recursion
{
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);
Expand Down Expand Up @@ -280,5 +285,101 @@ static MethodDefinition GenerateArrayWriteFunc(TypeReference variable, int recur
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, Weaver.NetworkWriterWritePackedInt32));

// 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;
}

}
}

0 comments on commit f9ff443

Please sign in to comment.