Skip to content

Commit

Permalink
feat: generate serializers for IMessageBase structs (#1353)
Browse files Browse the repository at this point in the history
* Allow Weaver to add bodies to IMessageBase structs with empty de/serialize methods

* Update Assets/Mirror/Editor/Weaver/Processors/MessageClassProcessor.cs

Co-Authored-By: Paul Pacheco <paulpach@gmail.com>

* applied suggested changes

* adjusted empty method check

* Update Assets/Mirror/Editor/Weaver/Processors/MessageClassProcessor.cs

formatting

Co-Authored-By: vis2k <info@noobtuts.com>

Co-authored-by: Paul Pacheco <paulpach@gmail.com>
Co-authored-by: vis2k <info@noobtuts.com>
  • Loading branch information
3 people committed Dec 27, 2019
1 parent 8d8cb7e commit 3c0bc28
Show file tree
Hide file tree
Showing 3 changed files with 90 additions and 43 deletions.
82 changes: 58 additions & 24 deletions Assets/Mirror/Editor/Weaver/Processors/MessageClassProcessor.cs
Original file line number Diff line number Diff line change
@@ -1,11 +1,19 @@
// this class generates OnSerialize/OnDeserialize when inheriting from MessageBase

using System.Linq;
using Mono.CecilX;
using Mono.CecilX.Cil;

namespace Mirror.Weaver
{
static class MessageClassProcessor
{

static bool IsEmptyDefault(this MethodBody body)
{
return body.Instructions.All(instruction => instruction.OpCode == OpCodes.Nop || instruction.OpCode == OpCodes.Ret);
}

public static void Process(TypeDefinition td)
{
Weaver.DLog(td, "MessageClassProcessor Start");
Expand All @@ -23,10 +31,10 @@ public static void Process(TypeDefinition td)
static void GenerateSerialization(TypeDefinition td)
{
Weaver.DLog(td, " GenerateSerialization");
foreach (MethodDefinition m in td.Methods)
MethodDefinition existingMethod = td.Methods.FirstOrDefault(md=>md.Name == "Serialize");
if (existingMethod != null && !existingMethod.Body.IsEmptyDefault())
{
if (m.Name == "Serialize")
return;
return;
}

if (td.Fields.Count == 0)
Expand All @@ -44,20 +52,30 @@ static void GenerateSerialization(TypeDefinition td)
}
}

MethodDefinition serializeFunc = new MethodDefinition("Serialize",
MethodDefinition serializeFunc = existingMethod ?? new MethodDefinition("Serialize",
MethodAttributes.Public | MethodAttributes.Virtual | MethodAttributes.HideBySig,
Weaver.voidType);

serializeFunc.Parameters.Add(new ParameterDefinition("writer", ParameterAttributes.None, Weaver.CurrentAssembly.MainModule.ImportReference(Weaver.NetworkWriterType)));
if (existingMethod == null) //only add to new method
{
serializeFunc.Parameters.Add(new ParameterDefinition("writer", ParameterAttributes.None, Weaver.CurrentAssembly.MainModule.ImportReference(Weaver.NetworkWriterType)));
}
ILProcessor serWorker = serializeFunc.Body.GetILProcessor();
if (existingMethod != null)
{
serWorker.Body.Instructions.Clear(); //remove default nop&ret from existing empty interface method
}

// call base
MethodReference baseSerialize = Resolvers.ResolveMethodInParents(td.BaseType, Weaver.CurrentAssembly, "Serialize");
if (baseSerialize != null)
if (!td.IsValueType) //if not struct(IMessageBase), likely same as using else {} here in all cases
{
serWorker.Append(serWorker.Create(OpCodes.Ldarg_0)); // base
serWorker.Append(serWorker.Create(OpCodes.Ldarg_1)); // writer
serWorker.Append(serWorker.Create(OpCodes.Call, baseSerialize));
// call base
MethodReference baseSerialize = Resolvers.ResolveMethodInParents(td.BaseType, Weaver.CurrentAssembly, "Serialize");
if (baseSerialize != null)
{
serWorker.Append(serWorker.Create(OpCodes.Ldarg_0)); // base
serWorker.Append(serWorker.Create(OpCodes.Ldarg_1)); // writer
serWorker.Append(serWorker.Create(OpCodes.Call, baseSerialize));
}
}

foreach (FieldDefinition field in td.Fields)
Expand All @@ -81,37 +99,50 @@ static void GenerateSerialization(TypeDefinition td)
}
serWorker.Append(serWorker.Create(OpCodes.Ret));

td.Methods.Add(serializeFunc);
if (existingMethod == null) //only add if not just replaced body
{
td.Methods.Add(serializeFunc);
}
}

static void GenerateDeSerialization(TypeDefinition td)
{
Weaver.DLog(td, " GenerateDeserialization");
foreach (MethodDefinition m in td.Methods)
MethodDefinition existingMethod = td.Methods.FirstOrDefault(md=>md.Name == "Deserialize");
if (existingMethod != null && !existingMethod.Body.IsEmptyDefault())
{
if (m.Name == "Deserialize")
return;
return;
}

if (td.Fields.Count == 0)
{
return;
}

MethodDefinition serializeFunc = new MethodDefinition("Deserialize",
MethodDefinition serializeFunc = existingMethod??new MethodDefinition("Deserialize",
MethodAttributes.Public | MethodAttributes.Virtual | MethodAttributes.HideBySig,
Weaver.voidType);

serializeFunc.Parameters.Add(new ParameterDefinition("reader", ParameterAttributes.None, Weaver.CurrentAssembly.MainModule.ImportReference(Weaver.NetworkReaderType)));
if (existingMethod == null) //only add to new method
{
serializeFunc.Parameters.Add(new ParameterDefinition("reader", ParameterAttributes.None, Weaver.CurrentAssembly.MainModule.ImportReference(Weaver.NetworkReaderType)));
}
ILProcessor serWorker = serializeFunc.Body.GetILProcessor();
if (existingMethod != null)
{
serWorker.Body.Instructions.Clear(); //remove default nop&ret from existing empty interface method
}

// call base
MethodReference baseDeserialize = Resolvers.ResolveMethodInParents(td.BaseType, Weaver.CurrentAssembly, "Deserialize");
if (baseDeserialize != null)
if (!td.IsValueType) //if not struct(IMessageBase), likely same as using else {} here in all cases
{
serWorker.Append(serWorker.Create(OpCodes.Ldarg_0)); // base
serWorker.Append(serWorker.Create(OpCodes.Ldarg_1)); // writer
serWorker.Append(serWorker.Create(OpCodes.Call, baseDeserialize));
// call base
MethodReference baseDeserialize = Resolvers.ResolveMethodInParents(td.BaseType, Weaver.CurrentAssembly, "Deserialize");
if (baseDeserialize != null)
{
serWorker.Append(serWorker.Create(OpCodes.Ldarg_0)); // base
serWorker.Append(serWorker.Create(OpCodes.Ldarg_1)); // writer
serWorker.Append(serWorker.Create(OpCodes.Call, baseDeserialize));
}
}

foreach (FieldDefinition field in td.Fields)
Expand All @@ -135,7 +166,10 @@ static void GenerateDeSerialization(TypeDefinition td)
}
serWorker.Append(serWorker.Create(OpCodes.Ret));

td.Methods.Add(serializeFunc);
if (existingMethod == null) //only add if not just replaced body
{
td.Methods.Add(serializeFunc);
}
}
}
}
24 changes: 5 additions & 19 deletions Assets/Mirror/Editor/Weaver/Weaver.cs
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ class Weaver
public static TypeReference NetworkConnectionType;

public static TypeReference MessageBaseType;
public static TypeReference IMessageBaseType;
public static TypeReference SyncListType;
public static TypeReference SyncSetType;
public static TypeReference SyncDictionaryType;
Expand Down Expand Up @@ -273,6 +274,7 @@ static void SetupTargetTypes()
NetworkConnectionType = CurrentAssembly.MainModule.ImportReference(NetworkConnectionType);

MessageBaseType = NetAssembly.MainModule.GetType("Mirror.MessageBase");
IMessageBaseType = NetAssembly.MainModule.GetType("Mirror.IMessageBase");
SyncListType = NetAssembly.MainModule.GetType("Mirror.SyncList`1");
SyncSetType = NetAssembly.MainModule.GetType("Mirror.SyncSet`1");
SyncDictionaryType = NetAssembly.MainModule.GetType("Mirror.SyncDictionary`2");
Expand Down Expand Up @@ -385,26 +387,10 @@ static bool CheckMessageBase(TypeDefinition td)

bool didWork = false;

// are ANY parent classes MessageBase
TypeReference parent = td.BaseType;
while (parent != null)
if (td.ImplementsInterface(IMessageBaseType))
{
if (parent.FullName == MessageBaseType.FullName)
{
MessageClassProcessor.Process(td);
didWork = true;
break;
}
try
{
parent = parent.Resolve().BaseType;
}
catch (AssemblyResolutionException)
{
// this can happen for plugins.
//Console.WriteLine("AssemblyResolutionException: "+ ex.ToString());
break;
}
MessageClassProcessor.Process(td);
didWork = true;
}

// check for embedded types
Expand Down
27 changes: 27 additions & 0 deletions Assets/Mirror/Tests/MessageBaseTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,16 @@ public void Serialize(NetworkWriter writer)
}
}

struct WovenTestMessage: IMessageBase
{
public int IntValue;
public string StringValue;
public double DoubleValue;

public void Deserialize(NetworkReader reader) {}
public void Serialize(NetworkWriter writer) {}
}

[TestFixture]
public class MessageBaseTests
{
Expand All @@ -47,5 +57,22 @@ public void Roundtrip()

Assert.AreEqual(1, t.IntValue);
}

[Test]
public void WovenSerializationBodyRoundtrip()
{
NetworkWriter w = new NetworkWriter();
w.Write(new WovenTestMessage{IntValue = 1, StringValue = "2", DoubleValue = 3.3});

byte[] arr = w.ToArray();

NetworkReader r = new NetworkReader(arr);
WovenTestMessage t = new WovenTestMessage();
t.Deserialize(r);

Assert.AreEqual(1, t.IntValue);
Assert.AreEqual("2", t.StringValue);
Assert.AreEqual(3.3, t.DoubleValue);
}
}
}

0 comments on commit 3c0bc28

Please sign in to comment.