Skip to content

Commit

Permalink
feat: Allowing extra base types to be used for SyncLists and other Sy…
Browse files Browse the repository at this point in the history
…ncObjects (#1729)

* Tests for most uses of sync list

renaming classes for existing test files

* improving error message for Paramless constructor

* More tests for SyncLists

* updating error message

* ignore abstract classes

we dont need to process abstract classes because classes that
inherit from them will be processed instead

* check and error for syncList item being genric

* allowing extra base types for synclist

* checking for nested types in abstract classes

* test for nested types inside structs
  • Loading branch information
James-Frowen committed Apr 22, 2020
1 parent ddd4b0b commit 9bf816a
Show file tree
Hide file tree
Showing 29 changed files with 685 additions and 55 deletions.
110 changes: 110 additions & 0 deletions Assets/Mirror/Editor/Weaver/Processors/GenericArgumentResolver.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
using System.Collections.Generic;
using Mono.CecilX;

namespace Mirror.Weaver
{
public class GenericArgumentResolver
{
readonly Stack<TypeReference> stack = new Stack<TypeReference>();
readonly int maxGenericArgument;

public GenericArgumentResolver(int maxGenericArgument)
{
this.maxGenericArgument = maxGenericArgument;
}

public bool GetGenericFromBaseClass(TypeDefinition td, int genericArgument, TypeReference baseType, out TypeReference itemType)
{
itemType = null;
if (GetGenericBaseType(td, baseType, out GenericInstanceType parent))
{
TypeReference arg = parent.GenericArguments[genericArgument];
if (arg.IsGenericParameter)
{
itemType = FindParameterInStack(genericArgument);
}
else
{
itemType = Weaver.CurrentAssembly.MainModule.ImportReference(arg);
}
}

return itemType != null;
}

TypeReference FindParameterInStack(int genericArgument)
{
while (stack.Count > 0)
{
TypeReference next = stack.Pop();

if (!(next is GenericInstanceType genericType))
{
// if type is not GenericInstanceType something has gone wrong
return null;
}

if (genericType.GenericArguments.Count < genericArgument)
{
// if less than `genericArgument` then we didnt find generic argument
return null;
}

if (genericType.GenericArguments.Count > maxGenericArgument)
{
// if greater than `genericArgument` it is hard to know which generic arg we want
// See SyncListGenericInheritanceWithMultipleGeneric test
Weaver.Error($"Too many generic argument for {next}");
return null;
}

TypeReference genericArg = genericType.GenericArguments[genericArgument];
if (!genericArg.IsGenericParameter)
{
// if not generic, sucessfully found type
return Weaver.CurrentAssembly.MainModule.ImportReference(genericArg);
}
}

// nothing left in stack, something went wrong
return null;
}

bool GetGenericBaseType(TypeDefinition td, TypeReference baseType, out GenericInstanceType found)
{
stack.Clear();
TypeReference parent = td.BaseType;
found = null;

while (parent != null)
{
string parentName = parent.FullName;

// strip generic parameters
int index = parentName.IndexOf('<');
if (index != -1)
{
parentName = parentName.Substring(0, index);
}

if (parentName == baseType.FullName)
{
found = parent as GenericInstanceType;
break;
}
try
{
stack.Push(parent);
parent = parent.Resolve().BaseType;
}
catch (AssemblyResolutionException)
{
// this can happen for plugins.
break;
}
}

return found != null;
}
}
}

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

22 changes: 20 additions & 2 deletions Assets/Mirror/Editor/Weaver/Processors/SyncDictionaryProcessor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,26 @@ static class SyncDictionaryProcessor
/// <param name="td">The synclist class</param>
public static void Process(TypeDefinition td)
{
SyncObjectProcessor.GenerateSerialization(td, 0, "SerializeKey", "DeserializeKey");
SyncObjectProcessor.GenerateSerialization(td, 1, "SerializeItem", "DeserializeItem");
GenericArgumentResolver resolver = new GenericArgumentResolver(2);

if (resolver.GetGenericFromBaseClass(td, 0, Weaver.SyncDictionaryType, out TypeReference keyType))
{
SyncObjectProcessor.GenerateSerialization(td, keyType, "SerializeKey", "DeserializeKey");
}
else
{
Weaver.Error($"Could not find generic arguments for {Weaver.SyncDictionaryType} using {td}");
return;
}

if (resolver.GetGenericFromBaseClass(td, 1, Weaver.SyncDictionaryType, out TypeReference itemType))
{
SyncObjectProcessor.GenerateSerialization(td, itemType, "SerializeItem", "DeserializeItem");
}
else
{
Weaver.Error($"Could not find generic arguments for {Weaver.SyncDictionaryType} using {td}");
}
}
}
}
13 changes: 11 additions & 2 deletions Assets/Mirror/Editor/Weaver/Processors/SyncListProcessor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,18 @@ static class SyncListProcessor
/// Generates serialization methods for synclists
/// </summary>
/// <param name="td">The synclist class</param>
public static void Process(TypeDefinition td)
public static void Process(TypeDefinition td, TypeReference baseType)
{
SyncObjectProcessor.GenerateSerialization(td, 0, "SerializeItem", "DeserializeItem");
GenericArgumentResolver resolver = new GenericArgumentResolver(1);

if (resolver.GetGenericFromBaseClass(td, 0, baseType, out TypeReference itemType))
{
SyncObjectProcessor.GenerateSerialization(td, itemType, "SerializeItem", "DeserializeItem");
}
else
{
Weaver.Error($"Could not find generic arguments for {baseType} using {td}");
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ static void GenerateSyncObjectInstanceInitializer(ILProcessor ctorWorker, FieldD
MethodDefinition ctor = fieldType.Methods.FirstOrDefault(x => x.Name == ".ctor" && !x.HasParameters);
if (ctor == null)
{
Weaver.Error($"{fd} does not have a default constructor");
Weaver.Error($"{fd} Can not intialize field because no default constructor was found. Manually intialize the field (call the constructor) or add constructor without Parameter");
return;
}
MethodReference objectConstructor = Weaver.CurrentAssembly.MainModule.ImportReference(ctor);
Expand Down
35 changes: 19 additions & 16 deletions Assets/Mirror/Editor/Weaver/Processors/SyncObjectProcessor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,19 +10,11 @@ public static class SyncObjectProcessor
/// </summary>
/// <param name="td">The type of the class that needs serialization methods</param>
/// <param name="genericArgument">Which generic argument to serialize, 0 is the first one</param>
/// <param name="baseType">the type that has generic arguments</param>
/// <param name="serializeMethod">The name of the serialize method</param>
/// <param name="deserializeMethod">The name of the deserialize method</param>
public static void GenerateSerialization(TypeDefinition td, int genericArgument, string serializeMethod, string deserializeMethod)
public static void GenerateSerialization(TypeDefinition td, TypeReference itemType, string serializeMethod, string deserializeMethod)
{
// find item type
GenericInstanceType gt = (GenericInstanceType)td.BaseType;
if (gt.GenericArguments.Count <= genericArgument)
{
Weaver.Error($"{td} should have {genericArgument} generic arguments");
return;
}
TypeReference itemType = Weaver.CurrentAssembly.MainModule.ImportReference(gt.GenericArguments[genericArgument]);

Weaver.DLog(td, "SyncObjectProcessor Start item:" + itemType.FullName);

MethodReference writeItemFunc = GenerateSerialization(serializeMethod, td, itemType);
Expand All @@ -47,6 +39,15 @@ static MethodReference GenerateSerialization(string methodName, TypeDefinition t
if (existing != null)
return existing;


// this check needs to happen inside GenerateSerialization because
// we need to check if user has made custom function above
if (itemType.IsGenericInstance)
{
Weaver.Error($"{td} Can not create Serialize or Deserialize for generic element. Override virtual methods with custom Serialize and Deserialize to use {itemType} in SyncList");
return null;
}

MethodDefinition serializeFunc = new MethodDefinition(methodName, MethodAttributes.Public |
MethodAttributes.Virtual |
MethodAttributes.Public |
Expand All @@ -57,12 +58,6 @@ static MethodReference GenerateSerialization(string methodName, TypeDefinition t
serializeFunc.Parameters.Add(new ParameterDefinition("item", ParameterAttributes.None, itemType));
ILProcessor serWorker = serializeFunc.Body.GetILProcessor();

if (itemType.IsGenericInstance)
{
Weaver.Error($"{td} cannot have generic elements {itemType}");
return null;
}

MethodReference writeFunc = Writers.GetWriteFunc(itemType);
if (writeFunc != null)
{
Expand All @@ -88,6 +83,14 @@ static MethodReference GenerateDeserialization(string methodName, TypeDefinition
if (existing != null)
return existing;

// this check needs to happen inside GenerateDeserialization because
// we need to check if user has made custom function above
if (itemType.IsGenericInstance)
{
Weaver.Error($"{td} Can not create Serialize or Deserialize for generic element. Override virtual methods with custom Serialize and Deserialize to use {itemType} in SyncList");
return null;
}

MethodDefinition deserializeFunction = new MethodDefinition(methodName, MethodAttributes.Public |
MethodAttributes.Virtual |
MethodAttributes.Public |
Expand Down
40 changes: 23 additions & 17 deletions Assets/Mirror/Editor/Weaver/Weaver.cs
Original file line number Diff line number Diff line change
Expand Up @@ -406,31 +406,37 @@ static bool WeaveMessage(TypeDefinition td)

static bool WeaveSyncObject(TypeDefinition td)
{
if (!td.IsClass)
return false;

bool modified = false;

// ignore generic classes
// we can not process generic classes
// we give error if a generic syncObject is used in NetworkBehaviour
if (td.HasGenericParameters)
return false;

bool modified = false;
// ignore abstract classes
// we dont need to process abstract classes because classes that
// inherit from them will be processed instead

if (td.IsDerivedFrom(SyncListType))
{
SyncListProcessor.Process(td);
modified = true;
}
else if (td.IsDerivedFrom(SyncSetType))
{
SyncListProcessor.Process(td);
modified = true;
}
else if (td.IsDerivedFrom(SyncDictionaryType))
// We cant early return with non classes or Abstract classes
// because we still need to check for embeded types
if (td.IsClass || !td.IsAbstract)
{
SyncDictionaryProcessor.Process(td);
modified = true;
if (td.IsDerivedFrom(SyncListType))
{
SyncListProcessor.Process(td, SyncListType);
modified = true;
}
else if (td.IsDerivedFrom(SyncSetType))
{
SyncListProcessor.Process(td, SyncSetType);
modified = true;
}
else if (td.IsDerivedFrom(SyncDictionaryType))
{
SyncDictionaryProcessor.Process(td);
modified = true;
}
}

// check for embedded types
Expand Down

0 comments on commit 9bf816a

Please sign in to comment.