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 // this class generates OnSerialize/OnDeserialize when inheriting from MessageBase
using System.Linq;
using Mono.CecilX; using Mono.CecilX;
using Mono.CecilX.Cil; using Mono.CecilX.Cil;
@ -6,6 +8,12 @@ namespace Mirror.Weaver
{ {
static class MessageClassProcessor 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) public static void Process(TypeDefinition td)
{ {
Weaver.DLog(td, "MessageClassProcessor Start"); Weaver.DLog(td, "MessageClassProcessor Start");
@ -23,9 +31,9 @@ public static void Process(TypeDefinition td)
static void GenerateSerialization(TypeDefinition td) static void GenerateSerialization(TypeDefinition td)
{ {
Weaver.DLog(td, " GenerateSerialization"); 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;
} }
@ -44,13 +52,22 @@ static void GenerateSerialization(TypeDefinition td)
} }
} }
MethodDefinition serializeFunc = new MethodDefinition("Serialize", MethodDefinition serializeFunc = existingMethod ?? new MethodDefinition("Serialize",
MethodAttributes.Public | MethodAttributes.Virtual | MethodAttributes.HideBySig, MethodAttributes.Public | MethodAttributes.Virtual | MethodAttributes.HideBySig,
Weaver.voidType); Weaver.voidType);
if (existingMethod == null) //only add to new method
{
serializeFunc.Parameters.Add(new ParameterDefinition("writer", ParameterAttributes.None, Weaver.CurrentAssembly.MainModule.ImportReference(Weaver.NetworkWriterType))); serializeFunc.Parameters.Add(new ParameterDefinition("writer", ParameterAttributes.None, Weaver.CurrentAssembly.MainModule.ImportReference(Weaver.NetworkWriterType)));
}
ILProcessor serWorker = serializeFunc.Body.GetILProcessor(); 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 // call base
MethodReference baseSerialize = Resolvers.ResolveMethodInParents(td.BaseType, Weaver.CurrentAssembly, "Serialize"); MethodReference baseSerialize = Resolvers.ResolveMethodInParents(td.BaseType, Weaver.CurrentAssembly, "Serialize");
if (baseSerialize != null) if (baseSerialize != null)
@ -59,6 +76,7 @@ static void GenerateSerialization(TypeDefinition td)
serWorker.Append(serWorker.Create(OpCodes.Ldarg_1)); // writer serWorker.Append(serWorker.Create(OpCodes.Ldarg_1)); // writer
serWorker.Append(serWorker.Create(OpCodes.Call, baseSerialize)); serWorker.Append(serWorker.Create(OpCodes.Call, baseSerialize));
} }
}
foreach (FieldDefinition field in td.Fields) foreach (FieldDefinition field in td.Fields)
{ {
@ -81,15 +99,18 @@ static void GenerateSerialization(TypeDefinition td)
} }
serWorker.Append(serWorker.Create(OpCodes.Ret)); serWorker.Append(serWorker.Create(OpCodes.Ret));
if (existingMethod == null) //only add if not just replaced body
{
td.Methods.Add(serializeFunc); td.Methods.Add(serializeFunc);
} }
}
static void GenerateDeSerialization(TypeDefinition td) static void GenerateDeSerialization(TypeDefinition td)
{ {
Weaver.DLog(td, " GenerateDeserialization"); 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;
} }
@ -98,13 +119,22 @@ static void GenerateDeSerialization(TypeDefinition td)
return; return;
} }
MethodDefinition serializeFunc = new MethodDefinition("Deserialize", MethodDefinition serializeFunc = existingMethod??new MethodDefinition("Deserialize",
MethodAttributes.Public | MethodAttributes.Virtual | MethodAttributes.HideBySig, MethodAttributes.Public | MethodAttributes.Virtual | MethodAttributes.HideBySig,
Weaver.voidType); Weaver.voidType);
if (existingMethod == null) //only add to new method
{
serializeFunc.Parameters.Add(new ParameterDefinition("reader", ParameterAttributes.None, Weaver.CurrentAssembly.MainModule.ImportReference(Weaver.NetworkReaderType))); serializeFunc.Parameters.Add(new ParameterDefinition("reader", ParameterAttributes.None, Weaver.CurrentAssembly.MainModule.ImportReference(Weaver.NetworkReaderType)));
}
ILProcessor serWorker = serializeFunc.Body.GetILProcessor(); 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 // call base
MethodReference baseDeserialize = Resolvers.ResolveMethodInParents(td.BaseType, Weaver.CurrentAssembly, "Deserialize"); MethodReference baseDeserialize = Resolvers.ResolveMethodInParents(td.BaseType, Weaver.CurrentAssembly, "Deserialize");
if (baseDeserialize != null) if (baseDeserialize != null)
@ -113,6 +143,7 @@ static void GenerateDeSerialization(TypeDefinition td)
serWorker.Append(serWorker.Create(OpCodes.Ldarg_1)); // writer serWorker.Append(serWorker.Create(OpCodes.Ldarg_1)); // writer
serWorker.Append(serWorker.Create(OpCodes.Call, baseDeserialize)); serWorker.Append(serWorker.Create(OpCodes.Call, baseDeserialize));
} }
}
foreach (FieldDefinition field in td.Fields) foreach (FieldDefinition field in td.Fields)
{ {
@ -135,7 +166,10 @@ static void GenerateDeSerialization(TypeDefinition td)
} }
serWorker.Append(serWorker.Create(OpCodes.Ret)); serWorker.Append(serWorker.Create(OpCodes.Ret));
if (existingMethod == null) //only add if not just replaced body
{
td.Methods.Add(serializeFunc); td.Methods.Add(serializeFunc);
} }
} }
}
} }

View File

@ -48,6 +48,7 @@ class Weaver
public static TypeReference NetworkConnectionType; public static TypeReference NetworkConnectionType;
public static TypeReference MessageBaseType; public static TypeReference MessageBaseType;
public static TypeReference IMessageBaseType;
public static TypeReference SyncListType; public static TypeReference SyncListType;
public static TypeReference SyncSetType; public static TypeReference SyncSetType;
public static TypeReference SyncDictionaryType; public static TypeReference SyncDictionaryType;
@ -273,6 +274,7 @@ static void SetupTargetTypes()
NetworkConnectionType = CurrentAssembly.MainModule.ImportReference(NetworkConnectionType); NetworkConnectionType = CurrentAssembly.MainModule.ImportReference(NetworkConnectionType);
MessageBaseType = NetAssembly.MainModule.GetType("Mirror.MessageBase"); MessageBaseType = NetAssembly.MainModule.GetType("Mirror.MessageBase");
IMessageBaseType = NetAssembly.MainModule.GetType("Mirror.IMessageBase");
SyncListType = NetAssembly.MainModule.GetType("Mirror.SyncList`1"); SyncListType = NetAssembly.MainModule.GetType("Mirror.SyncList`1");
SyncSetType = NetAssembly.MainModule.GetType("Mirror.SyncSet`1"); SyncSetType = NetAssembly.MainModule.GetType("Mirror.SyncSet`1");
SyncDictionaryType = NetAssembly.MainModule.GetType("Mirror.SyncDictionary`2"); SyncDictionaryType = NetAssembly.MainModule.GetType("Mirror.SyncDictionary`2");
@ -385,26 +387,10 @@ static bool CheckMessageBase(TypeDefinition td)
bool didWork = false; bool didWork = false;
// are ANY parent classes MessageBase if (td.ImplementsInterface(IMessageBaseType))
TypeReference parent = td.BaseType;
while (parent != null)
{
if (parent.FullName == MessageBaseType.FullName)
{ {
MessageClassProcessor.Process(td); MessageClassProcessor.Process(td);
didWork = true; didWork = true;
break;
}
try
{
parent = parent.Resolve().BaseType;
}
catch (AssemblyResolutionException)
{
// this can happen for plugins.
//Console.WriteLine("AssemblyResolutionException: "+ ex.ToString());
break;
}
} }
// check for embedded types // 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] [TestFixture]
public class MessageBaseTests public class MessageBaseTests
{ {
@ -47,5 +57,22 @@ public void Roundtrip()
Assert.AreEqual(1, t.IntValue); 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);
}
} }
} }