diff --git a/Assets/Mirror/Editor/Weaver/Extensions.cs b/Assets/Mirror/Editor/Weaver/Extensions.cs index 2897aaf35..b61bff13a 100644 --- a/Assets/Mirror/Editor/Weaver/Extensions.cs +++ b/Assets/Mirror/Editor/Weaver/Extensions.cs @@ -94,6 +94,24 @@ public static bool IsArrayType(this TypeReference tr) return true; } + public static bool IsArraySegment(this TypeDefinition td) + { + return td.FullName.StartsWith("System.ArraySegment`1", System.StringComparison.Ordinal); + } + public static bool IsArraySegment(this TypeReference td) + { + return td.FullName.StartsWith("System.ArraySegment`1", System.StringComparison.Ordinal); + } + + public static bool IsList(this TypeDefinition td) + { + return td.FullName.StartsWith("System.Collections.Generic.List`1", System.StringComparison.Ordinal); + } + public static bool IsList(this TypeReference td) + { + return td.FullName.StartsWith("System.Collections.Generic.List`1", System.StringComparison.Ordinal); + } + public static bool CanBeResolved(this TypeReference parent) { while (parent != null) diff --git a/Assets/Mirror/Editor/Weaver/Readers.cs b/Assets/Mirror/Editor/Weaver/Readers.cs index a3de67513..65e8fa9fa 100644 --- a/Assets/Mirror/Editor/Weaver/Readers.cs +++ b/Assets/Mirror/Editor/Weaver/Readers.cs @@ -43,13 +43,13 @@ public static MethodReference GetReadFunc(TypeReference variable, int recursionC return newReaderFunc; } - TypeDefinition td = variable.Resolve(); - if (td == null) + TypeDefinition variableType = variable.Resolve(); + if (variableType == null) { Weaver.Error($"{variable.Name} is not a supported type", variable); return null; } - if (td.IsDerivedFrom(WeaverTypes.ComponentType)) + if (variableType.IsDerivedFrom(WeaverTypes.ComponentType)) { Weaver.Error($"Cannot generate reader for component type {variable.Name}. Use a supported type or provide a custom reader", variable); return null; @@ -70,25 +70,29 @@ public static MethodReference GetReadFunc(TypeReference variable, int recursionC Weaver.Error($"Cannot pass type {variable.Name} by reference", variable); return null; } - if (td.HasGenericParameters && !td.FullName.StartsWith("System.ArraySegment`1", System.StringComparison.Ordinal)) + if (variableType.HasGenericParameters && !variableType.IsArraySegment() && !variableType.IsList()) { Weaver.Error($"Cannot generate reader for generic variable {variable.Name}. Use a supported type or provide a custom reader", variable); return null; } - if (td.IsInterface) + if (variableType.IsInterface) { Weaver.Error($"Cannot generate reader for interface {variable.Name}. Use a supported type or provide a custom reader", variable); return null; } - if (td.IsEnum) + if (variableType.IsEnum) { - return GetReadFunc(td.GetEnumUnderlyingType(), recursionCount); + return GetReadFunc(variableType.GetEnumUnderlyingType(), recursionCount); } - else if (variable.FullName.StartsWith("System.ArraySegment`1", System.StringComparison.Ordinal)) + else if (variableType.IsArraySegment()) { newReaderFunc = GenerateArraySegmentReadFunc(variable, recursionCount); } + else if (variableType.IsList()) + { + newReaderFunc = GenerateListReadFunc(variable, recursionCount); + } else { newReaderFunc = GenerateClassOrStructReadFunction(variable, recursionCount); @@ -222,7 +226,7 @@ static MethodDefinition GenerateArraySegmentReadFunc(TypeReference variable, int return null; } - string functionName = "_ReadArraySegment_" + variable.GetElementType().Name + "_"; + string functionName = "_ReadArraySegment_" + elementType.Name + "_"; if (variable.DeclaringType != null) { functionName += variable.DeclaringType.Name; @@ -301,6 +305,111 @@ static MethodDefinition GenerateArraySegmentReadFunc(TypeReference variable, int return readerFunc; } + static MethodDefinition GenerateListReadFunc(TypeReference variable, int recursionCount) + { + GenericInstanceType genericInstance = (GenericInstanceType)variable; + TypeReference elementType = genericInstance.GenericArguments[0]; + + MethodReference elementReadFunc = GetReadFunc(elementType, recursionCount + 1); + if (elementReadFunc == null) + { + Weaver.Error($"Cannot generate reader for List because element {elementType.Name} does not have a reader. Use a supported type or provide a custom reader", variable); + return null; + } + + string functionName = "_ReadList_" + elementType.Name + "_"; + if (variable.DeclaringType != null) + { + functionName += variable.DeclaringType.Name; + } + else + { + functionName += "None"; + } + + // create new reader for this type + MethodDefinition readerFunc = new MethodDefinition(functionName, + MethodAttributes.Public | + MethodAttributes.Static | + MethodAttributes.HideBySig, + variable); + + readerFunc.Parameters.Add(new ParameterDefinition("reader", ParameterAttributes.None, Weaver.CurrentAssembly.MainModule.ImportReference(WeaverTypes.NetworkReaderType))); + + readerFunc.Body.Variables.Add(new VariableDefinition(WeaverTypes.int32Type)); + readerFunc.Body.Variables.Add(new VariableDefinition(variable)); + readerFunc.Body.Variables.Add(new VariableDefinition(WeaverTypes.int32Type)); + readerFunc.Body.InitLocals = true; + + ILProcessor worker = readerFunc.Body.GetILProcessor(); + + // int count = reader.ReadPackedInt32(); + worker.Append(worker.Create(OpCodes.Ldarg_0)); + worker.Append(worker.Create(OpCodes.Call, GetReadFunc(WeaverTypes.int32Type))); + worker.Append(worker.Create(OpCodes.Stloc_0)); + + // -1 is null list, so if count is less than 0 return null + // if (count < 0) { + // return null + // } + worker.Append(worker.Create(OpCodes.Ldloc_0)); + worker.Append(worker.Create(OpCodes.Ldc_I4_0)); + Instruction labelEmptyArray = worker.Create(OpCodes.Nop); + worker.Append(worker.Create(OpCodes.Bge, labelEmptyArray)); + // return null + worker.Append(worker.Create(OpCodes.Ldnull)); + worker.Append(worker.Create(OpCodes.Ret)); + worker.Append(labelEmptyArray); + + // List list = new List(); + worker.Append(worker.Create(OpCodes.Newobj, WeaverTypes.ListConstructorReference.MakeHostInstanceGeneric(genericInstance))); + worker.Append(worker.Create(OpCodes.Stloc_1)); + + // loop through array and deserialize each element + // generates code like this + // for (int i=0; i< length ; i++) + // { + // list[i] = reader.ReadXXX(); + // } + worker.Append(worker.Create(OpCodes.Ldc_I4_0)); + worker.Append(worker.Create(OpCodes.Stloc_2)); + Instruction labelHead = worker.Create(OpCodes.Nop); + worker.Append(worker.Create(OpCodes.Br, labelHead)); + + // loop body + Instruction labelBody = worker.Create(OpCodes.Nop); + worker.Append(labelBody); + + MethodReference addItem = WeaverTypes.ListAddReference.MakeHostInstanceGeneric(genericInstance); + + // list.Add(reader.ReadT()); + worker.Append(worker.Create(OpCodes.Ldloc_1)); // list + worker.Append(worker.Create(OpCodes.Ldarg_0)); // reader + worker.Append(worker.Create(OpCodes.Call, elementReadFunc)); // Read + worker.Append(worker.Create(OpCodes.Call, addItem)); // set_Item + + // end for loop + + // for loop i++ + worker.Append(worker.Create(OpCodes.Ldloc_2)); + worker.Append(worker.Create(OpCodes.Ldc_I4_1)); + worker.Append(worker.Create(OpCodes.Add)); + worker.Append(worker.Create(OpCodes.Stloc_2)); + + // loop while check + worker.Append(labelHead); + // for loop i < count + worker.Append(worker.Create(OpCodes.Ldloc_2)); + worker.Append(worker.Create(OpCodes.Ldloc_0)); + worker.Append(worker.Create(OpCodes.Blt, labelBody)); + + // return value; + worker.Append(worker.Create(OpCodes.Ldloc_1)); + worker.Append(worker.Create(OpCodes.Ret)); + return readerFunc; + } + + static MethodDefinition GenerateClassOrStructReadFunction(TypeReference variable, int recursionCount) { if (recursionCount > MaxRecursionCount) diff --git a/Assets/Mirror/Editor/Weaver/WeaverTypes.cs b/Assets/Mirror/Editor/Weaver/WeaverTypes.cs index 797053df8..b90e02bae 100644 --- a/Assets/Mirror/Editor/Weaver/WeaverTypes.cs +++ b/Assets/Mirror/Editor/Weaver/WeaverTypes.cs @@ -63,6 +63,13 @@ public static class WeaverTypes public static MethodReference ArraySegmentOffsetReference; public static MethodReference ArraySegmentCountReference; + // list + public static TypeReference ListType; + public static MethodReference ListConstructorReference; + public static MethodReference ListCountReference; + public static MethodReference ListGetItemReference; + public static MethodReference ListAddReference; + // system types public static TypeReference voidType; public static TypeReference singleType; @@ -159,6 +166,12 @@ public static void SetupTargetTypes(AssemblyDefinition unityAssembly, AssemblyDe ArraySegmentOffsetReference = Resolvers.ResolveProperty(ArraySegmentType, currentAssembly, "Offset"); ArraySegmentConstructorReference = Resolvers.ResolveMethod(ArraySegmentType, currentAssembly, ".ctor"); + ListType = ImportSystemModuleType(currentAssembly, systemModule, "System.Collections.Generic.List`1"); + ListCountReference = Resolvers.ResolveProperty(ListType, currentAssembly, "Count"); + ListGetItemReference = Resolvers.ResolveMethod(ListType, currentAssembly, "get_Item"); + ListAddReference = Resolvers.ResolveMethod(ListType, currentAssembly, "Add"); + ListConstructorReference = Resolvers.ResolveMethod(ListType, currentAssembly, ".ctor"); + NetworkReaderType = mirrorAssembly.MainModule.GetType("Mirror.NetworkReader"); NetworkWriterType = mirrorAssembly.MainModule.GetType("Mirror.NetworkWriter"); TypeReference pooledNetworkWriterTmp = mirrorAssembly.MainModule.GetType("Mirror.PooledNetworkWriter"); diff --git a/Assets/Mirror/Editor/Weaver/Writers.cs b/Assets/Mirror/Editor/Weaver/Writers.cs index ca0f2daa2..c6858afff 100644 --- a/Assets/Mirror/Editor/Weaver/Writers.cs +++ b/Assets/Mirror/Editor/Weaver/Writers.cs @@ -50,13 +50,14 @@ public static MethodReference GetWriteFunc(TypeReference variable, int recursion Weaver.Error($"Cannot pass {variable.Name} by reference", variable); return null; } - TypeDefinition td = variable.Resolve(); - if (td == null) + + TypeDefinition variableType = variable.Resolve(); + if (variableType == null) { Weaver.Error($"{variable.Name} is not a supported type. Use a supported type or provide a custom writer", variable); return null; } - if (td.IsDerivedFrom(WeaverTypes.ComponentType)) + if (variableType.IsDerivedFrom(WeaverTypes.ComponentType)) { Weaver.Error($"Cannot generate writer for component type {variable.Name}. Use a supported type or provide a custom writer", variable); return null; @@ -71,12 +72,12 @@ public static MethodReference GetWriteFunc(TypeReference variable, int recursion Weaver.Error($"Cannot generate writer for {variable.Name}. Use a supported type or provide a custom writer", variable); return null; } - if (td.HasGenericParameters && !td.FullName.StartsWith("System.ArraySegment`1", System.StringComparison.Ordinal)) + if (variableType.HasGenericParameters && !variableType.IsArraySegment() && !variableType.IsList()) { Weaver.Error($"Cannot generate writer for generic type {variable.Name}. Use a supported type or provide a custom writer", variable); return null; } - if (td.IsInterface) + if (variableType.IsInterface) { Weaver.Error($"Cannot generate writer for interface {variable.Name}. Use a supported type or provide a custom writer", variable); return null; @@ -86,10 +87,14 @@ public static MethodReference GetWriteFunc(TypeReference variable, int recursion { return GetWriteFunc(variable.Resolve().GetEnumUnderlyingType(), recursionCount); } - else if (variable.FullName.StartsWith("System.ArraySegment`1", System.StringComparison.Ordinal)) + else if (variable.IsArraySegment()) { newWriterFunc = GenerateArraySegmentWriteFunc(variable, recursionCount); } + else if (variable.IsList()) + { + newWriterFunc = GenerateListWriteFunc(variable, recursionCount); + } else { newWriterFunc = GenerateClassOrStructWriterFunction(variable, recursionCount); @@ -243,8 +248,10 @@ static MethodDefinition GenerateArrayWriteFunc(TypeReference variable, int recur worker.Append(worker.Create(OpCodes.Call, GetWriteFunc(WeaverTypes.int32Type))); worker.Append(worker.Create(OpCodes.Ret)); - // int length = value.Length; + // else not null worker.Append(labelNull); + + // int length = value.Length; worker.Append(worker.Create(OpCodes.Ldarg_1)); worker.Append(worker.Create(OpCodes.Ldlen)); worker.Append(worker.Create(OpCodes.Stloc_0)); @@ -267,8 +274,8 @@ static MethodDefinition GenerateArrayWriteFunc(TypeReference variable, int recur worker.Append(worker.Create(OpCodes.Ldarg_0)); worker.Append(worker.Create(OpCodes.Ldarg_1)); worker.Append(worker.Create(OpCodes.Ldloc_1)); - worker.Append(worker.Create(OpCodes.Ldelema, variable.GetElementType())); - worker.Append(worker.Create(OpCodes.Ldobj, variable.GetElementType())); + worker.Append(worker.Create(OpCodes.Ldelema, elementType)); + worker.Append(worker.Create(OpCodes.Ldobj, elementType)); worker.Append(worker.Create(OpCodes.Call, elementWriteFunc)); worker.Append(worker.Create(OpCodes.Ldloc_1)); @@ -384,5 +391,115 @@ static MethodDefinition GenerateArraySegmentWriteFunc(TypeReference variable, in return writerFunc; } + static MethodDefinition GenerateListWriteFunc(TypeReference variable, int recursionCount) + { + GenericInstanceType genericInstance = (GenericInstanceType)variable; + TypeReference elementType = genericInstance.GenericArguments[0]; + MethodReference elementWriteFunc = GetWriteFunc(elementType, recursionCount + 1); + + if (elementWriteFunc == null) + { + Weaver.Error($"Cannot generate writer for List because element {elementType.Name} does not have a writer. Use a supported type or provide a custom writer", variable); + return null; + } + + string functionName = "_WriteList_" + elementType.Name + "_"; + if (variable.DeclaringType != null) + { + functionName += variable.DeclaringType.Name; + } + else + { + functionName += "None"; + } + + // create new writer for this type + MethodDefinition writerFunc = new MethodDefinition(functionName, + MethodAttributes.Public | + MethodAttributes.Static | + MethodAttributes.HideBySig, + WeaverTypes.voidType); + + writerFunc.Parameters.Add(new ParameterDefinition("writer", ParameterAttributes.None, Weaver.CurrentAssembly.MainModule.ImportReference(WeaverTypes.NetworkWriterType))); + writerFunc.Parameters.Add(new ParameterDefinition("value", ParameterAttributes.None, variable)); + + writerFunc.Body.Variables.Add(new VariableDefinition(WeaverTypes.int32Type)); + writerFunc.Body.Variables.Add(new VariableDefinition(WeaverTypes.int32Type)); + writerFunc.Body.InitLocals = true; + + ILProcessor worker = writerFunc.Body.GetILProcessor(); + + // if (value == null) + // { + // writer.WritePackedInt32(-1); + // return; + // } + Instruction labelNull = worker.Create(OpCodes.Nop); + worker.Append(worker.Create(OpCodes.Ldarg_1)); + worker.Append(worker.Create(OpCodes.Brtrue, labelNull)); + + worker.Append(worker.Create(OpCodes.Ldarg_0)); + worker.Append(worker.Create(OpCodes.Ldc_I4_M1)); + worker.Append(worker.Create(OpCodes.Call, GetWriteFunc(WeaverTypes.int32Type))); + worker.Append(worker.Create(OpCodes.Ret)); + + // else not null + worker.Append(labelNull); + + MethodReference countref = WeaverTypes.ListCountReference.MakeHostInstanceGeneric(genericInstance); + + // int count = value.Count; + worker.Append(worker.Create(OpCodes.Ldarg_1)); + worker.Append(worker.Create(OpCodes.Call, countref)); + worker.Append(worker.Create(OpCodes.Stloc_0)); + + // writer.WritePackedInt32(count); + worker.Append(worker.Create(OpCodes.Ldarg_0)); + worker.Append(worker.Create(OpCodes.Ldloc_0)); + worker.Append(worker.Create(OpCodes.Call, GetWriteFunc(WeaverTypes.int32Type))); + + // Loop through the List and call the writer for each element. + // generates this: + // for (int i=0; i < count; i++) + // { + // writer.WriteT(value[i]); + // } + worker.Append(worker.Create(OpCodes.Ldc_I4_0)); + worker.Append(worker.Create(OpCodes.Stloc_1)); + Instruction labelHead = worker.Create(OpCodes.Nop); + worker.Append(worker.Create(OpCodes.Br, labelHead)); + + // loop body + Instruction labelBody = worker.Create(OpCodes.Nop); + worker.Append(labelBody); + + MethodReference getItem = WeaverTypes.ListGetItemReference.MakeHostInstanceGeneric(genericInstance); + + // writer.Write(value[i]); + worker.Append(worker.Create(OpCodes.Ldarg_0)); // writer + worker.Append(worker.Create(OpCodes.Ldarg_1)); // value + worker.Append(worker.Create(OpCodes.Ldloc_1)); // i + worker.Append(worker.Create(OpCodes.Call, getItem)); //get_Item + worker.Append(worker.Create(OpCodes.Call, elementWriteFunc)); // Write + + + // end for loop + + // for loop i++ + worker.Append(worker.Create(OpCodes.Ldloc_1)); + worker.Append(worker.Create(OpCodes.Ldc_I4_1)); + worker.Append(worker.Create(OpCodes.Add)); + worker.Append(worker.Create(OpCodes.Stloc_1)); + + worker.Append(labelHead); + // for loop i < count + worker.Append(worker.Create(OpCodes.Ldloc_1)); + worker.Append(worker.Create(OpCodes.Ldloc_0)); + worker.Append(worker.Create(OpCodes.Blt, labelBody)); + + // return + worker.Append(worker.Create(OpCodes.Ret)); + return writerFunc; + } } } diff --git a/Assets/Mirror/Tests/Editor/Generated/CollectionWriterTests.gen.cs b/Assets/Mirror/Tests/Editor/Generated/CollectionWriterTests.gen.cs index 7b9db1d43..d6da98b4b 100644 --- a/Assets/Mirror/Tests/Editor/Generated/CollectionWriterTests.gen.cs +++ b/Assets/Mirror/Tests/Editor/Generated/CollectionWriterTests.gen.cs @@ -1,5 +1,6 @@ // Generated by CollectionWriterGenerator.cs using System; +using System.Collections.Generic; using Mirror.Tests.Generators; using NUnit.Framework; using UnityEngine; @@ -716,4 +717,324 @@ public void SendsData() Assert.That(unpackedCollection.Array[unpackedCollection.Offset + 2].a, Is.EqualTo(new ClassWithNoConstructor { a = 5 }.a)); } } + + public class List_int_Test + { + class Message : MessageBase + { + public List collection; + } + + [Test] + public void SendsNull() + { + Message message = new Message + { + collection = default + }; + + byte[] data = MessagePacker.Pack(message); + + Message unpacked = MessagePacker.Unpack(data); + List unpackedCollection = unpacked.collection; + + Assert.That(unpackedCollection, Is.Null.Or.Empty); + } + + [Test] + public void SendsEmpty() + { + Message message = new Message + { + collection = new List { } + }; + + byte[] data = MessagePacker.Pack(message); + + Message unpacked = MessagePacker.Unpack(data); + List unpackedCollection = unpacked.collection; + + Assert.IsNotNull(unpackedCollection); + Assert.IsEmpty(unpackedCollection); + } + + [Test] + public void SendsData() + { + Message message = new Message + { + collection = new List + { + 3, 4, 5 + } + }; + + byte[] data = MessagePacker.Pack(message); + + Message unpacked = MessagePacker.Unpack(data); + List unpackedCollection = unpacked.collection; + + Assert.IsNotNull(unpackedCollection); + Assert.IsNotEmpty(unpackedCollection); + Assert.That(unpackedCollection[0], Is.EqualTo(3)); + Assert.That(unpackedCollection[1], Is.EqualTo(4)); + Assert.That(unpackedCollection[2], Is.EqualTo(5)); + } + } + + public class List_string_Test + { + class Message : MessageBase + { + public List collection; + } + + [Test] + public void SendsNull() + { + Message message = new Message + { + collection = default + }; + + byte[] data = MessagePacker.Pack(message); + + Message unpacked = MessagePacker.Unpack(data); + List unpackedCollection = unpacked.collection; + + Assert.That(unpackedCollection, Is.Null.Or.Empty); + } + + [Test] + public void SendsEmpty() + { + Message message = new Message + { + collection = new List { } + }; + + byte[] data = MessagePacker.Pack(message); + + Message unpacked = MessagePacker.Unpack(data); + List unpackedCollection = unpacked.collection; + + Assert.IsNotNull(unpackedCollection); + Assert.IsEmpty(unpackedCollection); + } + + [Test] + public void SendsData() + { + Message message = new Message + { + collection = new List + { + "Some", "String", "Value" + } + }; + + byte[] data = MessagePacker.Pack(message); + + Message unpacked = MessagePacker.Unpack(data); + List unpackedCollection = unpacked.collection; + + Assert.IsNotNull(unpackedCollection); + Assert.IsNotEmpty(unpackedCollection); + Assert.That(unpackedCollection[0], Is.EqualTo("Some")); + Assert.That(unpackedCollection[1], Is.EqualTo("String")); + Assert.That(unpackedCollection[2], Is.EqualTo("Value")); + } + } + + public class List_Vector3_Test + { + class Message : MessageBase + { + public List collection; + } + + [Test] + public void SendsNull() + { + Message message = new Message + { + collection = default + }; + + byte[] data = MessagePacker.Pack(message); + + Message unpacked = MessagePacker.Unpack(data); + List unpackedCollection = unpacked.collection; + + Assert.That(unpackedCollection, Is.Null.Or.Empty); + } + + [Test] + public void SendsEmpty() + { + Message message = new Message + { + collection = new List { } + }; + + byte[] data = MessagePacker.Pack(message); + + Message unpacked = MessagePacker.Unpack(data); + List unpackedCollection = unpacked.collection; + + Assert.IsNotNull(unpackedCollection); + Assert.IsEmpty(unpackedCollection); + } + + [Test] + public void SendsData() + { + Message message = new Message + { + collection = new List + { + new Vector3(1, 2, 3), new Vector3(4, 5, 6), new Vector3(7, 8, 9) + } + }; + + byte[] data = MessagePacker.Pack(message); + + Message unpacked = MessagePacker.Unpack(data); + List unpackedCollection = unpacked.collection; + + Assert.IsNotNull(unpackedCollection); + Assert.IsNotEmpty(unpackedCollection); + Assert.That(unpackedCollection[0], Is.EqualTo(new Vector3(1, 2, 3))); + Assert.That(unpackedCollection[1], Is.EqualTo(new Vector3(4, 5, 6))); + Assert.That(unpackedCollection[2], Is.EqualTo(new Vector3(7, 8, 9))); + } + } + + public class List_FloatStringStruct_Test + { + class Message : MessageBase + { + public List collection; + } + + [Test] + public void SendsNull() + { + Message message = new Message + { + collection = default + }; + + byte[] data = MessagePacker.Pack(message); + + Message unpacked = MessagePacker.Unpack(data); + List unpackedCollection = unpacked.collection; + + Assert.That(unpackedCollection, Is.Null.Or.Empty); + } + + [Test] + public void SendsEmpty() + { + Message message = new Message + { + collection = new List { } + }; + + byte[] data = MessagePacker.Pack(message); + + Message unpacked = MessagePacker.Unpack(data); + List unpackedCollection = unpacked.collection; + + Assert.IsNotNull(unpackedCollection); + Assert.IsEmpty(unpackedCollection); + } + + [Test] + public void SendsData() + { + Message message = new Message + { + collection = new List + { + new FloatStringStruct { value = 3, anotherValue = "Some" }, new FloatStringStruct { value = 4, anotherValue = "String" }, new FloatStringStruct { value = 5, anotherValue = "Values" } + } + }; + + byte[] data = MessagePacker.Pack(message); + + Message unpacked = MessagePacker.Unpack(data); + List unpackedCollection = unpacked.collection; + + Assert.IsNotNull(unpackedCollection); + Assert.IsNotEmpty(unpackedCollection); + Assert.That(unpackedCollection[0], Is.EqualTo(new FloatStringStruct { value = 3, anotherValue = "Some" })); + Assert.That(unpackedCollection[1], Is.EqualTo(new FloatStringStruct { value = 4, anotherValue = "String" })); + Assert.That(unpackedCollection[2], Is.EqualTo(new FloatStringStruct { value = 5, anotherValue = "Values" })); + } + } + + public class List_ClassWithNoConstructor_Test + { + class Message : MessageBase + { + public List collection; + } + + [Test] + public void SendsNull() + { + Message message = new Message + { + collection = default + }; + + byte[] data = MessagePacker.Pack(message); + + Message unpacked = MessagePacker.Unpack(data); + List unpackedCollection = unpacked.collection; + + Assert.That(unpackedCollection, Is.Null.Or.Empty); + } + + [Test] + public void SendsEmpty() + { + Message message = new Message + { + collection = new List { } + }; + + byte[] data = MessagePacker.Pack(message); + + Message unpacked = MessagePacker.Unpack(data); + List unpackedCollection = unpacked.collection; + + Assert.IsNotNull(unpackedCollection); + Assert.IsEmpty(unpackedCollection); + } + + [Test] + public void SendsData() + { + Message message = new Message + { + collection = new List + { + new ClassWithNoConstructor { a = 3 }, new ClassWithNoConstructor { a = 4 }, new ClassWithNoConstructor { a = 5 } + } + }; + + byte[] data = MessagePacker.Pack(message); + + Message unpacked = MessagePacker.Unpack(data); + List unpackedCollection = unpacked.collection; + + Assert.IsNotNull(unpackedCollection); + Assert.IsNotEmpty(unpackedCollection); + Assert.That(unpackedCollection[0].a, Is.EqualTo(new ClassWithNoConstructor { a = 3 }.a)); + Assert.That(unpackedCollection[1].a, Is.EqualTo(new ClassWithNoConstructor { a = 4 }.a)); + Assert.That(unpackedCollection[2].a, Is.EqualTo(new ClassWithNoConstructor { a = 5 }.a)); + } + } } \ No newline at end of file diff --git a/Assets/Mirror/Tests/Editor/Generators/CollectionWriterGenerator.cs b/Assets/Mirror/Tests/Editor/Generators/CollectionWriterGenerator.cs index 1fda167ae..a4c7880c1 100644 --- a/Assets/Mirror/Tests/Editor/Generators/CollectionWriterGenerator.cs +++ b/Assets/Mirror/Tests/Editor/Generators/CollectionWriterGenerator.cs @@ -75,8 +75,8 @@ static IEnumerable valuesForType(string elementType) static readonly string[] collectionTypes = new string[] { ArrayType, - //ListType, - ArraySegmentType + ArraySegmentType, + ListType, }; const string NameSpace = BaseNameSpace + ".CollectionWriters"; @@ -86,6 +86,7 @@ static string Main(IEnumerable classes) string mergedClasses = Merge(classes); return $@"// Generated by {nameof(CollectionWriterGenerator)}.cs using System; +using System.Collections.Generic; using Mirror.Tests.Generators; using NUnit.Framework; using UnityEngine; diff --git a/Assets/Mirror/Tests/Editor/Weaver/.WeaverTests.csproj b/Assets/Mirror/Tests/Editor/Weaver/.WeaverTests.csproj index 335272168..51cd314a8 100644 --- a/Assets/Mirror/Tests/Editor/Weaver/.WeaverTests.csproj +++ b/Assets/Mirror/Tests/Editor/Weaver/.WeaverTests.csproj @@ -1,4 +1,4 @@ - + netstandard2.0 _WeaverTests2.csproj @@ -72,13 +72,16 @@ + + + diff --git a/Assets/Mirror/Tests/Editor/Weaver/GeneratedReaderWriter~/CreatesForList.cs b/Assets/Mirror/Tests/Editor/Weaver/GeneratedReaderWriter~/CreatesForList.cs new file mode 100644 index 000000000..12dabd46b --- /dev/null +++ b/Assets/Mirror/Tests/Editor/Weaver/GeneratedReaderWriter~/CreatesForList.cs @@ -0,0 +1,14 @@ +using System.Collections.Generic; +using Mirror; + +namespace GeneratedReaderWriter.CreatesForList +{ + public class CreatesForList : NetworkBehaviour + { + [ClientRpc] + public void RpcDoSomething(List data) + { + // empty + } + } +} diff --git a/Assets/Mirror/Tests/Editor/Weaver/GeneratedReaderWriter~/CreatesForStructList.cs b/Assets/Mirror/Tests/Editor/Weaver/GeneratedReaderWriter~/CreatesForStructList.cs new file mode 100644 index 000000000..7b21dbd04 --- /dev/null +++ b/Assets/Mirror/Tests/Editor/Weaver/GeneratedReaderWriter~/CreatesForStructList.cs @@ -0,0 +1,22 @@ +using System.Collections.Generic; +using Mirror; +using UnityEngine; + +namespace GeneratedReaderWriter.CreatesForStructList +{ + public class CreatesForStructList : NetworkBehaviour + { + [ClientRpc] + public void RpcDoSomething(List data) + { + // empty + } + } + + public struct MyStruct + { + public int someValue; + public Vector3 anotherValue; + } +} + diff --git a/Assets/Mirror/Tests/Editor/Weaver/GeneratedReaderWriter~/GivesErrorForInvalidListType.cs b/Assets/Mirror/Tests/Editor/Weaver/GeneratedReaderWriter~/GivesErrorForInvalidListType.cs new file mode 100644 index 000000000..8dfaeab11 --- /dev/null +++ b/Assets/Mirror/Tests/Editor/Weaver/GeneratedReaderWriter~/GivesErrorForInvalidListType.cs @@ -0,0 +1,15 @@ +using System.Collections.Generic; +using Mirror; +using UnityEngine; + +namespace GeneratedReaderWriter.GivesErrorForInvalidListType +{ + public class GivesErrorForInvalidListType : NetworkBehaviour + { + [ClientRpc] + public void RpcDoSomething(List data) + { + // empty + } + } +} diff --git a/Assets/Mirror/Tests/Editor/Weaver/WeaverGeneratedReaderWriterTests.cs b/Assets/Mirror/Tests/Editor/Weaver/WeaverGeneratedReaderWriterTests.cs index ecfa84d36..4a4eecadd 100644 --- a/Assets/Mirror/Tests/Editor/Weaver/WeaverGeneratedReaderWriterTests.cs +++ b/Assets/Mirror/Tests/Editor/Weaver/WeaverGeneratedReaderWriterTests.cs @@ -179,5 +179,24 @@ public void GivesErrorForInvalidArraySegmentType() Assert.That(weaverErrors, Contains.Item("Cannot generate writer for ArraySegment because element MonoBehaviour does not have a writer. Use a supported type or provide a custom writer (at System.ArraySegment`1)")); Assert.That(weaverErrors, Contains.Item("Cannot generate reader for ArraySegment because element MonoBehaviour does not have a reader. Use a supported type or provide a custom reader (at System.ArraySegment`1)")); } + + [Test] + public void CreatesForList() + { + Assert.That(weaverErrors, Is.Empty); + } + + [Test] + public void CreatesForStructList() + { + Assert.That(weaverErrors, Is.Empty); + } + + [Test] + public void GivesErrorForInvalidListType() + { + Assert.That(weaverErrors, Contains.Item("Cannot generate writer for List because element MonoBehaviour does not have a writer. Use a supported type or provide a custom writer (at System.Collections.Generic.List`1)")); + Assert.That(weaverErrors, Contains.Item("Cannot generate reader for List because element MonoBehaviour does not have a reader. Use a supported type or provide a custom reader (at System.Collections.Generic.List`1)")); + } } }