mirror of
https://github.com/MirrorNetworking/Mirror.git
synced 2024-11-18 11:00:32 +00:00
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:
parent
8d8cb7eba0
commit
3c0bc28228
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
@ -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
|
||||||
|
@ -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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user