feat: Allowing extra base types to be used for SyncLists and other SyncObjects (#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
This commit is contained in:
James Frowen 2020-04-22 08:40:40 +01:00 committed by GitHub
parent ddd4b0b1fc
commit 9bf816a014
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
29 changed files with 685 additions and 55 deletions

View File

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

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: fd67b3f7c2d66074a9bc7a23787e2ffb
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

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

View File

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

View File

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

View File

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

View File

@ -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))
// We cant early return with non classes or Abstract classes
// because we still need to check for embeded types
if (td.IsClass || !td.IsAbstract)
{
SyncListProcessor.Process(td);
modified = true;
}
else if (td.IsDerivedFrom(SyncSetType))
{
SyncListProcessor.Process(td);
modified = true;
}
else if (td.IsDerivedFrom(SyncDictionaryType))
{
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

View File

@ -5,7 +5,43 @@ namespace Mirror.Weaver.Tests
public class WeaverSyncListTests : WeaverTestsBuildFromTestName
{
[Test]
public void SyncListValid()
public void SyncList()
{
Assert.That(CompilationFinishedHook.WeaveFailed, Is.False);
Assert.That(weaverErrors, Is.Empty);
}
[Test]
public void SyncListByteValid()
{
Assert.That(CompilationFinishedHook.WeaveFailed, Is.False);
Assert.That(weaverErrors, Is.Empty);
}
[Test]
public void SyncListGenericAbstractInheritance()
{
Assert.That(CompilationFinishedHook.WeaveFailed, Is.False);
Assert.That(weaverErrors, Is.Empty);
}
[Test]
public void SyncListGenericInheritance()
{
Assert.That(CompilationFinishedHook.WeaveFailed, Is.False);
Assert.That(weaverErrors, Is.Empty);
}
[Test]
public void SyncListGenericInheritanceWithMultipleGeneric()
{
Assert.That(CompilationFinishedHook.WeaveFailed, Is.True);
Assert.That(weaverErrors, Has.Some.Match(@"Mirror\.Weaver error: Could not find generic arguments for Mirror\.SyncList`1 using MirrorTest\.SomeListInt"));
Assert.That(weaverErrors, Has.Some.Match(@"Mirror\.Weaver error: Too many generic argument for MirrorTest\.SomeList`2<System.String,System.Int32>"));
}
[Test]
public void SyncListInheritance()
{
Assert.That(CompilationFinishedHook.WeaveFailed, Is.False);
Assert.That(weaverErrors, Is.Empty);
@ -15,11 +51,116 @@ public void SyncListValid()
public void SyncListMissingParamlessCtor()
{
Assert.That(CompilationFinishedHook.WeaveFailed, Is.True);
Assert.That(weaverErrors, Contains.Item("Mirror.Weaver error: MirrorTest.MirrorTestPlayer/SyncListString2 MirrorTest.MirrorTestPlayer::Foo does not have a default constructor"));
string weaverError = @"Mirror\.Weaver error:";
string fieldType = @"MirrorTest\.SyncListString2 MirrorTest\.SyncListMissingParamlessCtor::Foo";
string errorMessage = @"Can not intialize field because no default constructor was found\. Manually intialize the field \(call the constructor\) or add constructor without Parameter";
Assert.That(weaverErrors, Has.Some.Match($"{weaverError} {fieldType} {errorMessage}"));
}
[Test]
public void SyncListMissingParamlessCtorManuallyInitialized()
{
Assert.That(CompilationFinishedHook.WeaveFailed, Is.False);
Assert.That(weaverErrors, Is.Empty);
}
[Test]
public void SyncListByteValid()
public void SyncListNestedStruct()
{
Assert.That(CompilationFinishedHook.WeaveFailed, Is.False);
Assert.That(weaverErrors, Is.Empty);
}
[Test]
public void SyncListNestedInAbstractClass()
{
Assert.That(CompilationFinishedHook.WeaveFailed, Is.False);
Assert.That(weaverErrors, Is.Empty);
}
[Test]
public void SyncListNestedInAbstractClassWithInvalid()
{
// we need this negative test to make sure that SyncList is being processed
Assert.That(CompilationFinishedHook.WeaveFailed, Is.True);
Assert.That(weaverErrors, Has.Some.Match(@"Mirror\.Weaver error: UnityEngine\.Object MirrorTest\.SomeAbstractClass/MyNestedStruct::target has unsupported type\. Use a type supported by Mirror instead"));
}
[Test]
public void SyncListNestedInStruct()
{
Assert.That(CompilationFinishedHook.WeaveFailed, Is.False);
Assert.That(weaverErrors, Is.Empty);
}
[Test]
public void SyncListNestedInStructWithInvalid()
{
// we need this negative test to make sure that SyncList is being processed
Assert.That(CompilationFinishedHook.WeaveFailed, Is.True);
Assert.That(weaverErrors, Has.Some.Match(@"Mirror\.Weaver error: UnityEngine\.Object MirrorTest\.SomeData::target has unsupported type\. Use a type supported by Mirror instead"));
}
[Test]
public void SyncListStruct()
{
Assert.That(CompilationFinishedHook.WeaveFailed, Is.False);
Assert.That(weaverErrors, Is.Empty);
}
[Test]
public void SyncListStructWithCustomDeserializeOnly()
{
Assert.That(CompilationFinishedHook.WeaveFailed, Is.False);
Assert.That(weaverErrors, Is.Empty);
}
[Test]
public void SyncListStructWithCustomMethods()
{
Assert.That(CompilationFinishedHook.WeaveFailed, Is.False);
Assert.That(weaverErrors, Is.Empty);
}
[Test]
public void SyncListStructWithCustomSerializeOnly()
{
Assert.That(CompilationFinishedHook.WeaveFailed, Is.False);
Assert.That(weaverErrors, Is.Empty);
}
[Test]
public void SyncListErrorForGenericStruct()
{
Assert.That(CompilationFinishedHook.WeaveFailed, Is.True);
string weaverError = @"Mirror\.Weaver error:";
string type = @"MirrorTest\.MyGenericStructList";
string errorMessage = @"Can not create Serialize or Deserialize for generic element\. Override virtual methods with custom Serialize and Deserialize to use MirrorTest.MyGenericStruct`1<System.Single> in SyncList";
Assert.That(weaverErrors, Has.Some.Match($"{weaverError} {type} {errorMessage}"));
}
[Test]
public void SyncListErrorForGenericStructWithCustomDeserializeOnly()
{
Assert.That(CompilationFinishedHook.WeaveFailed, Is.True);
string weaverError = @"Mirror\.Weaver error:";
string type = @"MirrorTest\.MyGenericStructList";
string errorMessage = @"Can not create Serialize or Deserialize for generic element\. Override virtual methods with custom Serialize and Deserialize to use MirrorTest.MyGenericStruct`1<System.Single> in SyncList";
Assert.That(weaverErrors, Has.Some.Match($"{weaverError} {type} {errorMessage}"));
}
[Test]
public void SyncListErrorForGenericStructWithCustomSerializeOnly()
{
Assert.That(CompilationFinishedHook.WeaveFailed, Is.True);
string weaverError = @"Mirror\.Weaver error:";
string type = @"MirrorTest\.MyGenericStructList";
string errorMessage = @"Can not create Serialize or Deserialize for generic element\. Override virtual methods with custom Serialize and Deserialize to use MirrorTest.MyGenericStruct`1<System.Single> in SyncList";
Assert.That(weaverErrors, Has.Some.Match($"{weaverError} {type} {errorMessage}"));
}
[Test]
public void SyncListGenericStructWithCustomMethods()
{
Assert.That(CompilationFinishedHook.WeaveFailed, Is.False);
Assert.That(weaverErrors, Is.Empty);

View File

@ -3,7 +3,7 @@
namespace MirrorTest
{
class MirrorTestPlayer : NetworkBehaviour
class SyncListValid : NetworkBehaviour
{
public SyncListInt Foo;
}

View File

@ -3,7 +3,7 @@
namespace MirrorTest
{
class MirrorTestPlayer : NetworkBehaviour
class SyncListByteValid : NetworkBehaviour
{
class MyByteClass : SyncList<byte> {};

View File

@ -0,0 +1,16 @@
using Mirror;
namespace MirrorTest
{
class SyncListErrorForGenericStruct : NetworkBehaviour
{
MyGenericStructList harpseals;
}
struct MyGenericStruct<T>
{
T genericpotato;
}
class MyGenericStructList : SyncList<MyGenericStruct<float>> { };
}

View File

@ -0,0 +1,22 @@
using Mirror;
namespace MirrorTest
{
class SyncListErrorForGenericStructWithCustomDeserializeOnly : NetworkBehaviour
{
MyGenericStructList harpseals;
}
struct MyGenericStruct<T>
{
public T genericpotato;
}
class MyGenericStructList : SyncList<MyGenericStruct<float>>
{
protected override MyGenericStruct<float> DeserializeItem(NetworkReader reader)
{
return new MyGenericStruct<float>() { genericpotato = reader.ReadSingle() };
}
};
}

View File

@ -0,0 +1,22 @@
using Mirror;
namespace MirrorTest
{
class SyncListErrorForGenericStructWithCustomSerializeOnly : NetworkBehaviour
{
MyGenericStructList harpseals;
}
struct MyGenericStruct<T>
{
public T genericpotato;
}
class MyGenericStructList : SyncList<MyGenericStruct<float>>
{
protected override void SerializeItem(NetworkWriter writer, MyGenericStruct<float> item)
{
writer.WriteSingle(item.genericpotato);
}
};
}

View File

@ -0,0 +1,13 @@
using Mirror;
namespace MirrorTest
{
class SyncListGenericAbstractInheritance : NetworkBehaviour
{
readonly SomeListInt superSyncListString = new SomeListInt();
}
public abstract class SomeList<T> : SyncList<T> { }
public class SomeListInt : SomeList<int> { }
}

View File

@ -0,0 +1,13 @@
using Mirror;
namespace MirrorTest
{
class SyncListGenericInheritance : NetworkBehaviour
{
readonly SomeListInt someList = new SomeListInt();
}
public class SomeList<T> : SyncList<T> { }
public class SomeListInt : SomeList<int> { }
}

View File

@ -0,0 +1,19 @@
using Mirror;
namespace MirrorTest
{
/*
This test will fail
It is hard to know which generic argument we want from `SomeList<string, int>`
So instead give a useful error for this edge case
*/
class SyncListGenericInheritanceWithMultipleGeneric : NetworkBehaviour
{
readonly SomeListInt someList = new SomeListInt();
}
public class SomeList<G, T> : SyncList<T> { }
public class SomeListInt : SomeList<string, int> { }
}

View File

@ -0,0 +1,27 @@
using Mirror;
namespace MirrorTest
{
class SyncListGenericStructWithCustomMethods : NetworkBehaviour
{
MyGenericStructList harpseals;
}
struct MyGenericStruct<T>
{
public T genericpotato;
}
class MyGenericStructList : SyncList<MyGenericStruct<float>>
{
protected override void SerializeItem(NetworkWriter writer, MyGenericStruct<float> item)
{
writer.WriteSingle(item.genericpotato);
}
protected override MyGenericStruct<float> DeserializeItem(NetworkReader reader)
{
return new MyGenericStruct<float>() { genericpotato = reader.ReadSingle() };
}
};
}

View File

@ -0,0 +1,15 @@
using UnityEngine;
using Mirror;
namespace MirrorTest
{
class SyncListInheritance : NetworkBehaviour
{
readonly SuperSyncListString superSyncListString = new SuperSyncListString();
}
public class SuperSyncListString : SyncListString
{
}
}

View File

@ -3,15 +3,15 @@
namespace MirrorTest
{
class MirrorTestPlayer : NetworkBehaviour
class SyncListMissingParamlessCtor : NetworkBehaviour
{
public class SyncListString2 : SyncList<string>
{
public SyncListString2(int phooey) {}
protected override void SerializeItem(NetworkWriter w, string item) {}
protected override string DeserializeItem(NetworkReader r) => "";
}
public SyncListString2 Foo;
}
public class SyncListString2 : SyncList<string>
{
public SyncListString2(int phooey) {}
protected override void SerializeItem(NetworkWriter w, string item) {}
protected override string DeserializeItem(NetworkReader r) => "";
}
}

View File

@ -0,0 +1,17 @@
using UnityEngine;
using Mirror;
namespace MirrorTest
{
class SyncListMissingParamlessCtorManuallyInitialized : NetworkBehaviour
{
public SyncListString2 Foo = new SyncListString2(20);
}
public class SyncListString2 : SyncList<string>
{
public SyncListString2(int phooey) {}
protected override void SerializeItem(NetworkWriter w, string item) {}
protected override string DeserializeItem(NetworkReader r) => "";
}
}

View File

@ -0,0 +1,21 @@
using UnityEngine;
using Mirror;
namespace MirrorTest
{
class SyncListNestedStruct : NetworkBehaviour
{
SomeAbstractClass.MyNestedStructList Foo;
}
public abstract class SomeAbstractClass
{
public struct MyNestedStruct
{
public int potato;
public float floatingpotato;
public double givemetwopotatoes;
}
public class MyNestedStructList : SyncList<MyNestedStruct> { }
}
}

View File

@ -0,0 +1,20 @@
using UnityEngine;
using Mirror;
namespace MirrorTest
{
class SyncListNestedStructWithInvalid : NetworkBehaviour
{
SomeAbstractClass.MyNestedStructList Foo;
}
public abstract class SomeAbstractClass
{
public struct MyNestedStruct
{
public int potato;
public Object target;
}
public class MyNestedStructList : SyncList<MyNestedStruct> { }
}
}

View File

@ -0,0 +1,17 @@
using UnityEngine;
using Mirror;
namespace MirrorTest
{
class SyncListNestedStruct : NetworkBehaviour
{
SomeData.SyncList Foo;
}
public struct SomeData
{
public int usefulNumber;
public class SyncList : Mirror.SyncList<SomeData> { }
}
}

View File

@ -0,0 +1,18 @@
using UnityEngine;
using Mirror;
namespace MirrorTest
{
class SyncListNestedInStructWithInvalid : NetworkBehaviour
{
SomeData.SyncList Foo;
}
public struct SomeData
{
public int usefulNumber;
public Object target;
public class SyncList : Mirror.SyncList<SomeData> { }
}
}

View File

@ -3,15 +3,16 @@
namespace MirrorTest
{
class MirrorTestPlayer : NetworkBehaviour
class SyncListNestedStruct : NetworkBehaviour
{
struct MyStruct
MyNestedStructList Foo;
struct MyNestedStruct
{
int potato;
float floatingpotato;
double givemetwopotatoes;
}
class MyStructClass : SyncList<MyStruct> {};
MyStructClass Foo;
class MyNestedStructList : SyncList<MyNestedStruct> { }
}
}

View File

@ -0,0 +1,17 @@
using UnityEngine;
using Mirror;
namespace MirrorTest
{
class SyncListStruct : NetworkBehaviour
{
MyStructList Foo;
}
struct MyStruct
{
int potato;
float floatingpotato;
double givemetwopotatoes;
}
class MyStructList : SyncList<MyStruct> { }
}

View File

@ -0,0 +1,23 @@
using UnityEngine;
using Mirror;
namespace MirrorTest
{
class SyncListStructWithCustomDeserializeOnly : NetworkBehaviour
{
MyStructList Foo;
}
struct MyStruct
{
int potato;
float floatingpotato;
double givemetwopotatoes;
}
class MyStructList : SyncList<MyStruct>
{
protected override MyStruct DeserializeItem(NetworkReader reader)
{
return new MyStruct() { /* read some stuff here */ };
}
}
}

View File

@ -0,0 +1,28 @@
using UnityEngine;
using Mirror;
namespace MirrorTest
{
class SyncListStructWithCustomMethods : NetworkBehaviour
{
MyStructList Foo;
}
struct MyStruct
{
int potato;
float floatingpotato;
double givemetwopotatoes;
}
class MyStructList : SyncList<MyStruct>
{
protected override void SerializeItem(NetworkWriter writer, MyStruct item)
{
// write some stuff here
}
protected override MyStruct DeserializeItem(NetworkReader reader)
{
return new MyStruct() { /* read some stuff here */ };
}
}
}

View File

@ -0,0 +1,23 @@
using UnityEngine;
using Mirror;
namespace MirrorTest
{
class SyncListStructWithCustomSerializeOnly : NetworkBehaviour
{
MyStructList Foo;
}
struct MyStruct
{
int potato;
float floatingpotato;
double givemetwopotatoes;
}
class MyStructList : SyncList<MyStruct>
{
protected override void SerializeItem(NetworkWriter writer, MyStruct item)
{
// write some stuff here
}
}
}