Skip to content

Commit

Permalink
breaking: Use SyncLists directly (delete overrides) (#2307)
Browse files Browse the repository at this point in the history
* feat: Use SyncLists directly

Previously,  you had to write an intermediary class to use synclists, syncsets and syncdictionaries.
The weaver would populate that intermediary class with a serialization and deserialization method

This PR gets rid of 90% of the weaver code for synclists.
There is no need to generate these methods anymore.
Instead the lists use `writer.Write<T>` and `read.read<T>` to serialize their Data.

Since there is no code generate in synclists, you can now use the synclists directly instead
of subclassing them.

BEFORE:

```cs
public class MyComponent : NetworkBehaviour {
    // nonsense class to make the weaver happy
    class SyncListData : Synclist<Data> {}

    SyncListData mySyncList;
}
```

AFTER:

```cs
public class MyComponent : NetworkBehaviour {
    Synclist<Data> mySyncList;
}
```

* linting

* feat: Use SyncLists directly (no overrides)

Previously,  you had to write an intermediary class to use synclists and syncdictionaries.

The weaver would populate that intermediary class with a serialization and deserialization method

This PR gets rid of 90% of the weaver code for synclists.
There is no need to generate these methods anymore.
Instead the lists use `writer.Write<T>` and `read.read<T>` to serialize their Data.

Since there is no code generate in synclists, you can now use the synclists directly instead
of subclassing them.

Same as #2305 ,but it removes the deprecated Serialize and Deserialize methods from syncobjects,
This way you get a nice compiler error for the code that no longer works, instead of a warning you might accidentally ignore.

BEFORE:

```cs
public class MyComponent : NetworkBehaviour {
    // nonsense class to make the weaver happy
    class SyncListData : Synclist<Data> {}

    SyncListData mySyncList;
}
```

AFTER:

```cs
public class MyComponent : NetworkBehaviour {
    Synclist<Data> mySyncList;
}
```

BREAKING CHANGE: Serialize and Deserialize methods in synclists don't do anything anymore

* Remove old comment

* Fix compilatio error
  • Loading branch information
paulpach committed Oct 2, 2020
1 parent 49c2f1b commit fb49d19
Show file tree
Hide file tree
Showing 31 changed files with 184 additions and 822 deletions.
36 changes: 0 additions & 36 deletions Assets/Mirror/Editor/Weaver/Extensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -230,42 +230,6 @@ public static MethodDefinition GetMethodInBaseType(this TypeDefinition td, strin
return null;
}

/// <summary>
///
/// </summary>
/// <param name="td"></param>
/// <param name="methodName"></param>
/// <param name="stopAt"></param>
/// <returns></returns>
public static bool HasMethodInBaseType(this TypeDefinition td, string methodName, Type stopAt)
{
TypeDefinition typedef = td;
while (typedef != null)
{
if (typedef.Is(stopAt))
break;

foreach (MethodDefinition md in typedef.Methods)
{
if (md.Name == methodName)
return true;
}

try
{
TypeReference parent = typedef.BaseType;
typedef = parent?.Resolve();
}
catch (AssemblyResolutionException)
{
// this can happen for plugins.
break;
}
}

return false;
}

/// <summary>
/// Finds public fields in type and base type
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,56 +1 @@
using System;
using Mono.CecilX;

namespace Mirror.Weaver
{
public static class GenericArgumentResolver
{
static TypeReference[] GetGenericArguments(TypeReference tr, Type baseType, TypeReference[] genericArguments)
{
if (tr == null)
return null;

TypeReference[] resolvedArguments = new TypeReference[0];

if (tr is GenericInstanceType genericInstance)
{
// this type is a generic instance, for example List<int>
// however, the parameter may be generic itself, for example:
// List<T>. If T is a generic parameter, then look it up in
// from the arguments we got.
resolvedArguments = genericInstance.GenericArguments.ToArray();

for (int i = 0; i< resolvedArguments.Length; i++)
{
TypeReference argument = resolvedArguments[i];
if (argument is GenericParameter genericArgument)
{
resolvedArguments[i] = genericArguments[genericArgument.Position];
}
}
}

if (tr.Is(baseType))
return resolvedArguments;

if (tr.CanBeResolved())
return GetGenericArguments(tr.Resolve().BaseType, baseType, resolvedArguments);

return null;
}

/// <summary>
/// Find out what the arguments are to a generic base type
/// </summary>
/// <param name="td"></param>
/// <param name="baseType"></param>
/// <returns></returns>
public static TypeReference[] GetGenericArguments(TypeDefinition td, Type baseType)
{
if (td == null)
return null;

return GetGenericArguments(td.BaseType, baseType, new TypeReference[] { });
}
}
}
// Removed Oct 1 2020
Original file line number Diff line number Diff line change
@@ -1,28 +1 @@
using Mono.CecilX;

namespace Mirror.Weaver
{
/// <summary>
/// generates OnSerialize/OnDeserialize for SyncLists
/// </summary>
static class SyncDictionaryProcessor
{
/// <summary>
/// Generates serialization methods for synclists
/// </summary>
/// <param name="td">The synclist class</param>
public static void Process(TypeDefinition td)
{
TypeReference []arguments = GenericArgumentResolver.GetGenericArguments(td, typeof(SyncDictionary<,>));
if (arguments != null)
{
SyncObjectProcessor.GenerateSerialization(td, arguments[0], typeof(SyncDictionary<,>), "SerializeKey", "DeserializeKey");
SyncObjectProcessor.GenerateSerialization(td, arguments[1], typeof(SyncDictionary<,>), "SerializeItem", "DeserializeItem");
}
else
{
Weaver.Error($"Could not find generic arguments for SyncDictionary in {td.Name}", td);
}
}
}
}
// Removed Oct 1 2020
30 changes: 1 addition & 29 deletions Assets/Mirror/Editor/Weaver/Processors/SyncListProcessor.cs
Original file line number Diff line number Diff line change
@@ -1,29 +1 @@
using System;
using Mono.CecilX;

namespace Mirror.Weaver
{
/// <summary>
/// generates OnSerialize/OnDeserialize for SyncLists
/// </summary>
static class SyncListProcessor
{
/// <summary>
/// Generates serialization methods for synclists
/// </summary>
/// <param name="td">The synclist class</param>
/// <param name="mirrorBaseType">the base SyncObject td inherits from</param>
public static void Process(TypeDefinition td, Type mirrorBaseType)
{
TypeReference [] arguments = GenericArgumentResolver.GetGenericArguments(td, mirrorBaseType);
if (arguments != null)
{
SyncObjectProcessor.GenerateSerialization(td, arguments[0], mirrorBaseType, "SerializeItem", "DeserializeItem");
}
else
{
Weaver.Error($"Could not find generic arguments for {mirrorBaseType.Name} in {td}", td);
}
}
}
}
// Removed Oct 1 2020
122 changes: 15 additions & 107 deletions Assets/Mirror/Editor/Weaver/Processors/SyncObjectProcessor.cs
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
using System;
using System.Collections.Generic;
using Mono.CecilX;
using Mono.CecilX.Cil;

namespace Mirror.Weaver
{
Expand All @@ -27,11 +25,7 @@ public static List<FieldDefinition> FindSyncObjectsFields(TypeDefinition td)
continue;
}

if (fd.FieldType.Resolve().HasGenericParameters)
{
Weaver.Error($"Cannot use generic SyncObject {fd.Name} directly in NetworkBehaviour. Create a class and inherit from the generic SyncObject instead", fd);
continue;
}
GenerateReadersAndWriters(fd.FieldType);

syncObjects.Add(fd);
}
Expand All @@ -42,114 +36,28 @@ public static List<FieldDefinition> FindSyncObjectsFields(TypeDefinition td)
}

/// <summary>
/// Generates the serialization and deserialization methods for a specified generic argument
/// Generates serialization methods for synclists
/// </summary>
/// <param name="td">The type of the class that needs serialization methods</param>
/// <param name="itemType">generic argument to serialize</param>
/// <param name="td">The synclist class</param>
/// <param name="mirrorBaseType">the base SyncObject td inherits from</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, TypeReference itemType, Type mirrorBaseType, string serializeMethod, string deserializeMethod)
static void GenerateReadersAndWriters(TypeReference tr)
{
Weaver.DLog(td, "SyncObjectProcessor Start item:" + itemType.FullName);

bool success = GenerateSerialization(serializeMethod, td, itemType, mirrorBaseType);
if (Weaver.WeavingFailed)
if (tr is GenericInstanceType genericInstance)
{
return;
}

success |= GenerateDeserialization(deserializeMethod, td, itemType, mirrorBaseType);

if (success)
Weaver.DLog(td, "SyncObjectProcessor Done");
}

// serialization of individual element
static bool GenerateSerialization(string methodName, TypeDefinition td, TypeReference itemType, Type mirrorBaseType)
{
Weaver.DLog(td, " GenerateSerialization");
bool existing = td.HasMethodInBaseType(methodName, mirrorBaseType);
if (existing)
return true;


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

MethodDefinition serializeFunc = new MethodDefinition(methodName, MethodAttributes.Public |
MethodAttributes.Virtual |
MethodAttributes.Public |
MethodAttributes.HideBySig,
WeaverTypes.Import(typeof(void)));

serializeFunc.Parameters.Add(new ParameterDefinition("writer", ParameterAttributes.None, WeaverTypes.Import<NetworkWriter>()));
serializeFunc.Parameters.Add(new ParameterDefinition("item", ParameterAttributes.None, itemType));
ILProcessor worker = serializeFunc.Body.GetILProcessor();

MethodReference writeFunc = Writers.GetWriteFunc(itemType);
if (writeFunc != null)
{
worker.Append(worker.Create(OpCodes.Ldarg_1));
worker.Append(worker.Create(OpCodes.Ldarg_2));
worker.Append(worker.Create(OpCodes.Call, writeFunc));
}
else
{
Weaver.Error($"{td.Name} has sync object generic type {itemType.Name}. Use a type supported by mirror instead", td);
return false;
}
worker.Append(worker.Create(OpCodes.Ret));

td.Methods.Add(serializeFunc);
return true;
}

static bool GenerateDeserialization(string methodName, TypeDefinition td, TypeReference itemType, Type mirrorBaseType)
{
Weaver.DLog(td, " GenerateDeserialization");
bool existing = td.HasMethodInBaseType(methodName, mirrorBaseType);
if (existing)
return true;

// this check needs to happen inside GenerateDeserialization because
// we need to check if user has made custom function above
if (itemType.IsGenericInstance)
{
Weaver.Error($"Can not create Serialize or Deserialize for generic element in {td.Name}. Override virtual methods with custom Serialize and Deserialize to use {itemType.Name} in SyncList", td);
return false;
foreach (TypeReference argument in genericInstance.GenericArguments)
{
if (!argument.IsGenericParameter)
{
Readers.GetReadFunc(argument);
Writers.GetWriteFunc(argument);
}
}
}

MethodDefinition deserializeFunction = new MethodDefinition(methodName, MethodAttributes.Public |
MethodAttributes.Virtual |
MethodAttributes.Public |
MethodAttributes.HideBySig,
itemType);

deserializeFunction.Parameters.Add(new ParameterDefinition("reader", ParameterAttributes.None, WeaverTypes.Import<NetworkReader>()));

ILProcessor worker = deserializeFunction.Body.GetILProcessor();

MethodReference readerFunc = Readers.GetReadFunc(itemType);
if (readerFunc != null)
{
worker.Append(worker.Create(OpCodes.Ldarg_1));
worker.Append(worker.Create(OpCodes.Call, readerFunc));
worker.Append(worker.Create(OpCodes.Ret));
}
else
if (tr != null)
{
Weaver.Error($"{td.Name} has sync object generic type {itemType.Name}. Use a type supported by mirror instead", td);
return false;
GenerateReadersAndWriters(tr.Resolve().BaseType);
}

td.Methods.Add(deserializeFunction);
return true;
}
}
}
54 changes: 0 additions & 54 deletions Assets/Mirror/Editor/Weaver/Weaver.cs
Original file line number Diff line number Diff line change
Expand Up @@ -179,67 +179,13 @@ static bool WeaveMessage(TypeDefinition td)
return modified;
}

static bool WeaveSyncObject(TypeDefinition td)
{
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;

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

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

// check for embedded types
foreach (TypeDefinition embedded in td.NestedTypes)
{
modified |= WeaveSyncObject(embedded);
}

return modified;
}

static bool WeaveModule(ModuleDefinition moduleDefinition)
{
try
{
bool modified = false;

// We need to do 2 passes, because SyncListStructs might be referenced from other modules, so we must make sure we generate them first.
System.Diagnostics.Stopwatch watch = System.Diagnostics.Stopwatch.StartNew();
foreach (TypeDefinition td in moduleDefinition.Types)
{
if (td.IsClass && td.BaseType.CanBeResolved())
{
modified |= WeaveSyncObject(td);
}
}
watch.Stop();
Console.WriteLine("Weave sync objects took " + watch.ElapsedMilliseconds + " milliseconds");

watch.Start();
foreach (TypeDefinition td in moduleDefinition.Types)
Expand Down

0 comments on commit fb49d19

Please sign in to comment.