feat: generate serializers for IMessageBase structs (#1353)

* 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>
This commit is contained in:
Julien Heimann 2019-12-27 17:12:28 +01:00 committed by Paul Pacheco
parent 8d8cb7eba0
commit 3c0bc28228
3 changed files with 94 additions and 47 deletions

View File

@ -1,4 +1,6 @@
// this class generates OnSerialize/OnDeserialize when inheriting from MessageBase
using System.Linq;
using Mono.CecilX;
using Mono.CecilX.Cil;
@ -6,6 +8,12 @@ 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");
@ -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)
@ -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)));
ILProcessor serWorker = serializeFunc.Body.GetILProcessor();
// call base
MethodReference baseSerialize = Resolvers.ResolveMethodInParents(td.BaseType, Weaver.CurrentAssembly, "Serialize");
if (baseSerialize != null)
if (existingMethod == null) //only add to new method
{
serWorker.Append(serWorker.Create(OpCodes.Ldarg_0)); // base
serWorker.Append(serWorker.Create(OpCodes.Ldarg_1)); // writer
serWorker.Append(serWorker.Create(OpCodes.Call, baseSerialize));
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
}
if (!td.IsValueType) //if not struct(IMessageBase), likely same as using else {} here in all cases
{
// 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)
@ -81,16 +99,19 @@ 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)
@ -98,20 +119,30 @@ static void GenerateDeSerialization(TypeDefinition td)
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)));
ILProcessor serWorker = serializeFunc.Body.GetILProcessor();
// call base
MethodReference baseDeserialize = Resolvers.ResolveMethodInParents(td.BaseType, Weaver.CurrentAssembly, "Deserialize");
if (baseDeserialize != null)
if (existingMethod == null) //only add to new method
{
serWorker.Append(serWorker.Create(OpCodes.Ldarg_0)); // base
serWorker.Append(serWorker.Create(OpCodes.Ldarg_1)); // writer
serWorker.Append(serWorker.Create(OpCodes.Call, baseDeserialize));
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
}
if (!td.IsValueType) //if not struct(IMessageBase), likely same as using else {} here in all cases
{
// 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)
@ -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);
}
}
}
}

View File

@ -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;
@ -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");
@ -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

View File

@ -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
{
@ -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);
}
}
}