diff --git a/com.unity.netcode.gameobjects/CHANGELOG.md b/com.unity.netcode.gameobjects/CHANGELOG.md index 3458c1cb43..d83f32aef9 100644 --- a/com.unity.netcode.gameobjects/CHANGELOG.md +++ b/com.unity.netcode.gameobjects/CHANGELOG.md @@ -10,6 +10,7 @@ Additional documentation and release notes are available at [Multiplayer Documen ### Added +- Added support for serializing `NativeArray<>` and `NativeList<>` in `FastBufferReader`/`FastBufferWriter`, `BufferSerializer`, `NetworkVariable`, and RPCs. (#2375) - The location of the automatically-created default network prefab list can now be configured (#2544) - Added: Message size limits (max single message and max fragmented message) can now be set using NetworkManager.SetMaxSingleMessageSize() and NetworkManager.SetMaxFragmentedMessageSize() for transports that don't work with the default values (#2530) - Added `NetworkObject.SpawnWithObservers` property (default is true) that when set to false will spawn a `NetworkObject` with no observers and will not be spawned on any client until `NetworkObject.NetworkShow` is invoked. (#2568) diff --git a/com.unity.netcode.gameobjects/Editor/CodeGen/NetworkBehaviourILPP.cs b/com.unity.netcode.gameobjects/Editor/CodeGen/NetworkBehaviourILPP.cs index baac903d31..13306322d2 100644 --- a/com.unity.netcode.gameobjects/Editor/CodeGen/NetworkBehaviourILPP.cs +++ b/com.unity.netcode.gameobjects/Editor/CodeGen/NetworkBehaviourILPP.cs @@ -18,6 +18,8 @@ namespace Unity.Netcode.Editor.CodeGen internal sealed class NetworkBehaviourILPP : ILPPInterface { private const string k_ReadValueMethodName = nameof(FastBufferReader.ReadValueSafe); + private const string k_ReadValueInPlaceMethodName = nameof(FastBufferReader.ReadValueSafeInPlace); + private const string k_ReadValueTempMethodName = nameof(FastBufferReader.ReadValueSafeTemp); private const string k_WriteValueMethodName = nameof(FastBufferWriter.WriteValueSafe); public override ILPPInterface GetInstance() => this; @@ -182,7 +184,70 @@ private void CreateNetworkVariableTypeInitializers(AssemblyDefinition assembly) GenericInstanceMethod serializeMethod = null; GenericInstanceMethod equalityMethod; - if (type.IsValueType) + + if (type.Resolve().FullName == "Unity.Collections.NativeArray`1") + { + var wrappedType = ((GenericInstanceType)type).GenericArguments[0]; + if (IsSpecialCaseType(wrappedType) || wrappedType.HasInterface(typeof(INetworkSerializeByMemcpy).FullName) || wrappedType.Resolve().IsEnum || IsMemcpyableType(wrappedType)) + { + serializeMethod = new GenericInstanceMethod(m_NetworkVariableSerializationTypes_InitializeSerializer_UnmanagedByMemcpyArray_MethodRef); + } + else if (wrappedType.HasInterface(typeof(INetworkSerializable).FullName)) + { + serializeMethod = new GenericInstanceMethod(m_NetworkVariableSerializationTypes_InitializeSerializer_UnmanagedINetworkSerializableArray_MethodRef); + } + else if (wrappedType.HasInterface(CodeGenHelpers.IUTF8Bytes_FullName) && wrappedType.HasInterface(k_INativeListBool_FullName)) + { + serializeMethod = new GenericInstanceMethod(m_NetworkVariableSerializationTypes_InitializeSerializer_FixedStringArray_MethodRef); + } + + if (wrappedType.HasInterface(typeof(IEquatable<>).FullName + "<" + wrappedType.FullName + ">")) + { + equalityMethod = new GenericInstanceMethod(m_NetworkVariableSerializationTypes_InitializeEqualityChecker_UnmanagedIEquatableArray_MethodRef); + } + else + { + equalityMethod = new GenericInstanceMethod(m_NetworkVariableSerializationTypes_InitializeEqualityChecker_UnmanagedValueEqualsArray_MethodRef); + } + + if (serializeMethod != null) + { + serializeMethod.GenericArguments.Add(wrappedType); + } + equalityMethod.GenericArguments.Add(wrappedType); + } + else if (type.Resolve().FullName == "Unity.Collections.NativeList`1") + { + var wrappedType = ((GenericInstanceType)type).GenericArguments[0]; + if (IsSpecialCaseType(wrappedType) || wrappedType.HasInterface(typeof(INetworkSerializeByMemcpy).FullName) || wrappedType.Resolve().IsEnum || IsMemcpyableType(wrappedType)) + { + serializeMethod = new GenericInstanceMethod(m_NetworkVariableSerializationTypes_InitializeSerializer_UnmanagedByMemcpyList_MethodRef); + } + else if (wrappedType.HasInterface(typeof(INetworkSerializable).FullName)) + { + serializeMethod = new GenericInstanceMethod(m_NetworkVariableSerializationTypes_InitializeSerializer_UnmanagedINetworkSerializableList_MethodRef); + } + else if (wrappedType.HasInterface(CodeGenHelpers.IUTF8Bytes_FullName) && wrappedType.HasInterface(k_INativeListBool_FullName)) + { + serializeMethod = new GenericInstanceMethod(m_NetworkVariableSerializationTypes_InitializeSerializer_FixedStringList_MethodRef); + } + + if (wrappedType.HasInterface(typeof(IEquatable<>).FullName + "<" + wrappedType.FullName + ">")) + { + equalityMethod = new GenericInstanceMethod(m_NetworkVariableSerializationTypes_InitializeEqualityChecker_UnmanagedIEquatableList_MethodRef); + } + else + { + equalityMethod = new GenericInstanceMethod(m_NetworkVariableSerializationTypes_InitializeEqualityChecker_UnmanagedValueEqualsList_MethodRef); + } + + if (serializeMethod != null) + { + serializeMethod.GenericArguments.Add(wrappedType); + } + equalityMethod.GenericArguments.Add(wrappedType); + } + else if (type.IsValueType) { if (type.HasInterface(typeof(INetworkSerializeByMemcpy).FullName) || type.Resolve().IsEnum || IsMemcpyableType(type)) { @@ -205,6 +270,12 @@ private void CreateNetworkVariableTypeInitializers(AssemblyDefinition assembly) { equalityMethod = new GenericInstanceMethod(m_NetworkVariableSerializationTypes_InitializeEqualityChecker_UnmanagedValueEquals_MethodRef); } + + if (serializeMethod != null) + { + serializeMethod.GenericArguments.Add(type); + } + equalityMethod.GenericArguments.Add(type); } else { @@ -236,14 +307,18 @@ private void CreateNetworkVariableTypeInitializers(AssemblyDefinition assembly) { equalityMethod = new GenericInstanceMethod(m_NetworkVariableSerializationTypes_InitializeEqualityChecker_ManagedClassEquals_MethodRef); } + + if (serializeMethod != null) + { + serializeMethod.GenericArguments.Add(type); + } + equalityMethod.GenericArguments.Add(type); } if (serializeMethod != null) { - serializeMethod.GenericArguments.Add(type); instructions.Add(processor.Create(OpCodes.Call, m_MainModule.ImportReference(serializeMethod))); } - equalityMethod.GenericArguments.Add(type); instructions.Add(processor.Create(OpCodes.Call, m_MainModule.ImportReference(equalityMethod))); } @@ -291,12 +366,22 @@ private void CreateNetworkVariableTypeInitializers(AssemblyDefinition assembly) private FieldReference m_ServerRpcParams_Receive_SenderClientId_FieldRef; private TypeReference m_ClientRpcParams_TypeRef; private MethodReference m_NetworkVariableSerializationTypes_InitializeSerializer_UnmanagedByMemcpy_MethodRef; + private MethodReference m_NetworkVariableSerializationTypes_InitializeSerializer_UnmanagedByMemcpyArray_MethodRef; + private MethodReference m_NetworkVariableSerializationTypes_InitializeSerializer_UnmanagedByMemcpyList_MethodRef; private MethodReference m_NetworkVariableSerializationTypes_InitializeSerializer_UnmanagedINetworkSerializable_MethodRef; + private MethodReference m_NetworkVariableSerializationTypes_InitializeSerializer_UnmanagedINetworkSerializableArray_MethodRef; + private MethodReference m_NetworkVariableSerializationTypes_InitializeSerializer_UnmanagedINetworkSerializableList_MethodRef; private MethodReference m_NetworkVariableSerializationTypes_InitializeSerializer_ManagedINetworkSerializable_MethodRef; private MethodReference m_NetworkVariableSerializationTypes_InitializeSerializer_FixedString_MethodRef; + private MethodReference m_NetworkVariableSerializationTypes_InitializeSerializer_FixedStringArray_MethodRef; + private MethodReference m_NetworkVariableSerializationTypes_InitializeSerializer_FixedStringList_MethodRef; private MethodReference m_NetworkVariableSerializationTypes_InitializeEqualityChecker_ManagedIEquatable_MethodRef; private MethodReference m_NetworkVariableSerializationTypes_InitializeEqualityChecker_UnmanagedIEquatable_MethodRef; + private MethodReference m_NetworkVariableSerializationTypes_InitializeEqualityChecker_UnmanagedIEquatableArray_MethodRef; + private MethodReference m_NetworkVariableSerializationTypes_InitializeEqualityChecker_UnmanagedIEquatableList_MethodRef; private MethodReference m_NetworkVariableSerializationTypes_InitializeEqualityChecker_UnmanagedValueEquals_MethodRef; + private MethodReference m_NetworkVariableSerializationTypes_InitializeEqualityChecker_UnmanagedValueEqualsArray_MethodRef; + private MethodReference m_NetworkVariableSerializationTypes_InitializeEqualityChecker_UnmanagedValueEqualsList_MethodRef; private MethodReference m_NetworkVariableSerializationTypes_InitializeEqualityChecker_ManagedClassEquals_MethodRef; private MethodReference m_ExceptionCtorMethodReference; @@ -316,6 +401,8 @@ private void CreateNetworkVariableTypeInitializers(AssemblyDefinition assembly) private MethodReference m_ByteUnpacker_ReadValueBitPacked_Long_MethodRef; private MethodReference m_ByteUnpacker_ReadValueBitPacked_ULong_MethodRef; + private MethodReference m_NetworkBehaviour_createNativeList_MethodRef; + private TypeReference m_FastBufferWriter_TypeRef; private readonly Dictionary m_FastBufferWriter_WriteValue_MethodRefs = new Dictionary(); private readonly List m_FastBufferWriter_ExtensionMethodRefs = new List(); @@ -381,6 +468,7 @@ private void CreateNetworkVariableTypeInitializers(AssemblyDefinition assembly) private const string k_NetworkBehaviour_beginSendClientRpc = nameof(NetworkBehaviour.__beginSendClientRpc); private const string k_NetworkBehaviour_endSendClientRpc = nameof(NetworkBehaviour.__endSendClientRpc); private const string k_NetworkBehaviour___initializeVariables = nameof(NetworkBehaviour.__initializeVariables); + private const string k_NetworkBehaviour_createNativeList = nameof(NetworkBehaviour.__createNativeList); private const string k_NetworkBehaviour_NetworkManager = nameof(NetworkBehaviour.NetworkManager); private const string k_NetworkBehaviour_OwnerClientId = nameof(NetworkBehaviour.OwnerClientId); private const string k_NetworkBehaviour___nameNetworkVariable = nameof(NetworkBehaviour.__nameNetworkVariable); @@ -587,6 +675,9 @@ private bool ImportReferences(ModuleDefinition moduleDefinition) case k_NetworkBehaviour_endSendClientRpc: m_NetworkBehaviour_endSendClientRpc_MethodRef = moduleDefinition.ImportReference(methodDef); break; + case k_NetworkBehaviour_createNativeList: + m_NetworkBehaviour_createNativeList_MethodRef = moduleDefinition.ImportReference(methodDef); + break; case k_NetworkBehaviour___nameNetworkVariable: m_NetworkBehaviour___nameNetworkVariable_MethodRef = moduleDefinition.ImportReference(methodDef); break; @@ -742,24 +833,54 @@ private bool ImportReferences(ModuleDefinition moduleDefinition) case nameof(NetworkVariableSerializationTypes.InitializeSerializer_UnmanagedByMemcpy): m_NetworkVariableSerializationTypes_InitializeSerializer_UnmanagedByMemcpy_MethodRef = method; break; + case nameof(NetworkVariableSerializationTypes.InitializeSerializer_UnmanagedByMemcpyArray): + m_NetworkVariableSerializationTypes_InitializeSerializer_UnmanagedByMemcpyArray_MethodRef = method; + break; + case nameof(NetworkVariableSerializationTypes.InitializeSerializer_UnmanagedByMemcpyList): + m_NetworkVariableSerializationTypes_InitializeSerializer_UnmanagedByMemcpyList_MethodRef = method; + break; case nameof(NetworkVariableSerializationTypes.InitializeSerializer_UnmanagedINetworkSerializable): m_NetworkVariableSerializationTypes_InitializeSerializer_UnmanagedINetworkSerializable_MethodRef = method; break; + case nameof(NetworkVariableSerializationTypes.InitializeSerializer_UnmanagedINetworkSerializableArray): + m_NetworkVariableSerializationTypes_InitializeSerializer_UnmanagedINetworkSerializableArray_MethodRef = method; + break; + case nameof(NetworkVariableSerializationTypes.InitializeSerializer_UnmanagedINetworkSerializableList): + m_NetworkVariableSerializationTypes_InitializeSerializer_UnmanagedINetworkSerializableList_MethodRef = method; + break; case nameof(NetworkVariableSerializationTypes.InitializeSerializer_ManagedINetworkSerializable): m_NetworkVariableSerializationTypes_InitializeSerializer_ManagedINetworkSerializable_MethodRef = method; break; case nameof(NetworkVariableSerializationTypes.InitializeSerializer_FixedString): m_NetworkVariableSerializationTypes_InitializeSerializer_FixedString_MethodRef = method; break; + case nameof(NetworkVariableSerializationTypes.InitializeSerializer_FixedStringArray): + m_NetworkVariableSerializationTypes_InitializeSerializer_FixedStringArray_MethodRef = method; + break; + case nameof(NetworkVariableSerializationTypes.InitializeSerializer_FixedStringList): + m_NetworkVariableSerializationTypes_InitializeSerializer_FixedStringList_MethodRef = method; + break; case nameof(NetworkVariableSerializationTypes.InitializeEqualityChecker_ManagedIEquatable): m_NetworkVariableSerializationTypes_InitializeEqualityChecker_ManagedIEquatable_MethodRef = method; break; case nameof(NetworkVariableSerializationTypes.InitializeEqualityChecker_UnmanagedIEquatable): m_NetworkVariableSerializationTypes_InitializeEqualityChecker_UnmanagedIEquatable_MethodRef = method; break; + case nameof(NetworkVariableSerializationTypes.InitializeEqualityChecker_UnmanagedIEquatableArray): + m_NetworkVariableSerializationTypes_InitializeEqualityChecker_UnmanagedIEquatableArray_MethodRef = method; + break; + case nameof(NetworkVariableSerializationTypes.InitializeEqualityChecker_UnmanagedIEquatableList): + m_NetworkVariableSerializationTypes_InitializeEqualityChecker_UnmanagedIEquatableList_MethodRef = method; + break; case nameof(NetworkVariableSerializationTypes.InitializeEqualityChecker_UnmanagedValueEquals): m_NetworkVariableSerializationTypes_InitializeEqualityChecker_UnmanagedValueEquals_MethodRef = method; break; + case nameof(NetworkVariableSerializationTypes.InitializeEqualityChecker_UnmanagedValueEqualsArray): + m_NetworkVariableSerializationTypes_InitializeEqualityChecker_UnmanagedValueEqualsArray_MethodRef = method; + break; + case nameof(NetworkVariableSerializationTypes.InitializeEqualityChecker_UnmanagedValueEqualsList): + m_NetworkVariableSerializationTypes_InitializeEqualityChecker_UnmanagedValueEqualsList_MethodRef = method; + break; case nameof(NetworkVariableSerializationTypes.InitializeEqualityChecker_ManagedClassEquals): m_NetworkVariableSerializationTypes_InitializeEqualityChecker_ManagedClassEquals_MethodRef = method; break; @@ -1216,28 +1337,44 @@ private MethodReference GetFastBufferWriterWriteMethod(string name, TypeReferenc continue; } - var checkType = paramType.Resolve(); + var checkType = paramType; if (paramType.IsArray) { checkType = ((ArrayType)paramType).ElementType.Resolve(); } - if ((parameters[0].ParameterType.Resolve() == checkType || - (parameters[0].ParameterType.Resolve() == checkType.MakeByReferenceType().Resolve() && parameters[0].IsIn))) + if (!method.HasGenericParameters) { - return method; - } + if (!paramType.IsGenericInstance && (parameters[0].ParameterType.Resolve() == checkType || + (parameters[0].ParameterType.Resolve() == checkType.MakeByReferenceType().Resolve() && parameters[0].IsIn))) + { + return method; + } - if (parameters[0].ParameterType == paramType || - (parameters[0].ParameterType == paramType.MakeByReferenceType() && parameters[0].IsIn)) - { - return method; + if (parameters[0].ParameterType == paramType || parameters[0].ParameterType.FullName == paramType.FullName || + (parameters[0].ParameterType == paramType.MakeByReferenceType() && parameters[0].IsIn)) + { + return method; + } } - - if (method.HasGenericParameters && method.GenericParameters.Count == 1) + else if (method.GenericParameters.Count == 1) { + var resolved = method.Parameters[0].ParameterType.Resolve(); + if (resolved != null && resolved != paramType.Resolve()) + { + continue; + } if (method.GenericParameters[0].HasConstraints) { + if (paramType.IsGenericInstance && (paramType.Resolve().FullName == "Unity.Collections.NativeList`1" || paramType.Resolve().FullName == "Unity.Collections.NativeArray`1")) + { + if (method.Parameters[0].ParameterType.Resolve() != paramType.Resolve()) + { + continue; + } + var instanceType = (GenericInstanceType)paramType; + checkType = instanceType.GenericArguments[0]; + } var meetsConstraints = true; foreach (var constraint in method.GenericParameters[0].Constraints) { @@ -1270,7 +1407,13 @@ private MethodReference GetFastBufferWriterWriteMethod(string name, TypeReferenc if (meetsConstraints) { var instanceMethod = new GenericInstanceMethod(method); - if (paramType.IsArray) + + if (paramType.IsGenericInstance && (paramType.Resolve().FullName == "Unity.Collections.NativeList`1" || paramType.Resolve().FullName == "Unity.Collections.NativeArray`1")) + { + var wrappedType = ((GenericInstanceType)paramType).GenericArguments[0]; + instanceMethod.GenericArguments.Add(wrappedType); + } + else if (paramType.IsArray) { instanceMethod.GenericArguments.Add(((ArrayType)paramType).ElementType); } @@ -1379,9 +1522,9 @@ private MethodReference GetFastBufferReaderReadMethod(string name, TypeReference continue; } - if (!parameters[0].IsOut) + if (!parameters[0].IsOut && !parameters[0].ParameterType.IsByReference) { - return null; + continue; } var methodParam = ((ByReferenceType)parameters[0].ParameterType).ElementType; @@ -1391,24 +1534,52 @@ private MethodReference GetFastBufferReaderReadMethod(string name, TypeReference continue; } - var checkType = paramType.Resolve(); + var checkType = (TypeReference)paramType.Resolve(); if (paramType.IsArray) { checkType = ((ArrayType)paramType).ElementType.Resolve(); } - if (methodParam.Resolve() == checkType.Resolve() || methodParam.Resolve() == checkType.MakeByReferenceType().Resolve()) + if (!method.HasGenericParameters) { - return method; - } + if (!paramType.IsGenericInstance && (methodParam.Resolve() == checkType.Resolve() || methodParam.Resolve() == checkType.MakeByReferenceType().Resolve())) + { + return method; + } - if (methodParam.Resolve() == paramType || methodParam.Resolve() == paramType.MakeByReferenceType().Resolve()) - { - return method; + if (methodParam.Resolve() == paramType || methodParam.FullName == paramType.FullName) + { + return method; + } } - - if (method.HasGenericParameters && method.GenericParameters.Count == 1) + else if (method.GenericParameters.Count == 1) { + var resolved = method.Parameters[0].ParameterType.Resolve(); + if (resolved != null && resolved != paramType.Resolve()) + { + continue; + } + if (paramType.IsGenericInstance && (paramType.Resolve().FullName == "Unity.Collections.NativeList`1" || paramType.Resolve().FullName == "Unity.Collections.NativeArray`1")) + { + if (method.Name == "OnSendGlobalCounterClientRpc") + { + m_Diagnostics.AddWarning( + $"{method}: {method.Parameters[0].ParameterType} | {paramType}" + ); + } + if (method.Parameters[0].ParameterType.Resolve() != paramType.Resolve()) + { + if (method.Name == "OnSendGlobalCounterClientRpc") + { + m_Diagnostics.AddWarning( + $"{method}: Not suitable" + ); + } + continue; + } + var instanceType = (GenericInstanceType)paramType; + checkType = instanceType.GenericArguments[0]; + } if (method.GenericParameters[0].HasConstraints) { var meetsConstraints = true; @@ -1445,7 +1616,12 @@ private MethodReference GetFastBufferReaderReadMethod(string name, TypeReference if (meetsConstraints) { var instanceMethod = new GenericInstanceMethod(method); - if (paramType.IsArray) + if (paramType.IsGenericInstance && (paramType.Resolve().FullName == "Unity.Collections.NativeList`1" || paramType.Resolve().FullName == "Unity.Collections.NativeArray`1")) + { + var wrappedType = ((GenericInstanceType)paramType).GenericArguments[0]; + instanceMethod.GenericArguments.Add(wrappedType); + } + else if (paramType.IsArray) { instanceMethod.GenericArguments.Add(((ArrayType)paramType).ElementType); } @@ -1453,7 +1629,6 @@ private MethodReference GetFastBufferReaderReadMethod(string name, TypeReference { instanceMethod.GenericArguments.Add(paramType); } - return instanceMethod; } } @@ -1515,7 +1690,19 @@ private bool GetReadMethodForParameter(TypeReference paramType, out MethodRefere } } - var typeMethod = GetFastBufferReaderReadMethod(k_ReadValueMethodName, paramType); + MethodReference typeMethod; + if (paramType.Resolve().FullName == "Unity.Collections.NativeList`1") + { + typeMethod = GetFastBufferReaderReadMethod(k_ReadValueInPlaceMethodName, paramType); + } + else if (paramType.Resolve().FullName == "Unity.Collections.NativeArray`1") + { + typeMethod = GetFastBufferReaderReadMethod(k_ReadValueTempMethodName, paramType); + } + else + { + typeMethod = GetFastBufferReaderReadMethod(k_ReadValueMethodName, paramType); + } if (typeMethod != null) { methodRef = m_MainModule.ImportReference(typeMethod); @@ -1855,7 +2042,7 @@ private void InjectWriteAndCallBlocks(MethodDefinition methodDefinition, CustomA } else { - m_Diagnostics.AddError(methodDefinition, $"{methodDefinition.Name} - Don't know how to serialize {paramType.Name}. RPC parameter types must either implement {nameof(INetworkSerializeByMemcpy)} or {nameof(INetworkSerializable)}. If this type is external and you are sure its memory layout makes it serializable by memcpy, you can replace {paramType} with {typeof(ForceNetworkSerializeByMemcpy<>).Name}<{paramType}>, or you can create extension methods for {nameof(FastBufferReader)}.{nameof(FastBufferReader.ReadValueSafe)}(this {nameof(FastBufferReader)}, out {paramType}) and {nameof(FastBufferWriter)}.{nameof(FastBufferWriter.WriteValueSafe)}(this {nameof(FastBufferWriter)}, in {paramType}) to define serialization for this type."); + m_Diagnostics.AddError(methodDefinition, $"{methodDefinition.Name} - Don't know how to serialize {paramType}. RPC parameter types must either implement {nameof(INetworkSerializeByMemcpy)} or {nameof(INetworkSerializable)}. If this type is external and you are sure its memory layout makes it serializable by memcpy, you can replace {paramType} with {typeof(ForceNetworkSerializeByMemcpy<>).Name}<{paramType}>, or you can create extension methods for {nameof(FastBufferReader)}.{nameof(FastBufferReader.ReadValueSafe)}(this {nameof(FastBufferReader)}, out {paramType}) and {nameof(FastBufferWriter)}.{nameof(FastBufferWriter.WriteValueSafe)}(this {nameof(FastBufferWriter)}, in {paramType}) to define serialization for this type."); continue; } @@ -2243,6 +2430,26 @@ private MethodDefinition GenerateStaticHandler(MethodDefinition methodDefinition processor.Emit(OpCodes.Brfalse, jumpInstruction); } + if (paramType.IsGenericInstance && paramType.Resolve().FullName == "Unity.Collections.NativeList`1") + { + // var list = NetworkBehaviour.__createNativeList(); + + // This simplifies things - easier to call __createNativeList() and have the implementation in C# + // than to try to actually construct a NativeList in IL. This is also more future-proof. + + // Unlike other types, NativeList<> calls ReadValueSafeInPlace instead of ReadValueSafe. + // FastBufferReader doesn't support a non-in-place deserializer for NativeList in order to + // avoid users using it without realizing the allocation overhead that would cost. In-place + // is more efficient when an existing value exists, and when it doesn't, it's easy to create one, + // which is what we do here. + + var method = new GenericInstanceMethod(m_NetworkBehaviour_createNativeList_MethodRef); + var genericParam = (GenericInstanceType)paramType; + method.GenericArguments.Add(genericParam.GenericArguments[0]); + processor.Emit(OpCodes.Call, method); + processor.Emit(OpCodes.Stloc, localIndex); + } + var foundMethodRef = GetReadMethodForParameter(paramType, out var methodRef); if (foundMethodRef) { @@ -2295,7 +2502,7 @@ private MethodDefinition GenerateStaticHandler(MethodDefinition methodDefinition } else { - m_Diagnostics.AddError(methodDefinition, $"{methodDefinition.Name} - Don't know how to serialize {paramType.Name}. RPC parameter types must either implement {nameof(INetworkSerializeByMemcpy)} or {nameof(INetworkSerializable)}. If this type is external and you are sure its memory layout makes it serializable by memcpy, you can replace {paramType} with {typeof(ForceNetworkSerializeByMemcpy<>).Name}<{paramType}>, or you can create extension methods for {nameof(FastBufferReader)}.{nameof(FastBufferReader.ReadValueSafe)}(this {nameof(FastBufferReader)}, out {paramType}) and {nameof(FastBufferWriter)}.{nameof(FastBufferWriter.WriteValueSafe)}(this {nameof(FastBufferWriter)}, in {paramType}) to define serialization for this type."); + m_Diagnostics.AddError(methodDefinition, $"{methodDefinition.Name} - Don't know how to deserialize {paramType}. RPC parameter types must either implement {nameof(INetworkSerializeByMemcpy)} or {nameof(INetworkSerializable)}. If this type is external and you are sure its memory layout makes it serializable by memcpy, you can replace {paramType} with {typeof(ForceNetworkSerializeByMemcpy<>).Name}<{paramType}>, or you can create extension methods for {nameof(FastBufferReader)}.{nameof(FastBufferReader.ReadValueSafe)}(this {nameof(FastBufferReader)}, out {paramType}) and {nameof(FastBufferWriter)}.{nameof(FastBufferWriter.WriteValueSafe)}(this {nameof(FastBufferWriter)}, in {paramType}) to define serialization for this type."); continue; } diff --git a/com.unity.netcode.gameobjects/Editor/CodeGen/RuntimeAccessModifiersILPP.cs b/com.unity.netcode.gameobjects/Editor/CodeGen/RuntimeAccessModifiersILPP.cs index 0d9d2d0230..73166648d8 100644 --- a/com.unity.netcode.gameobjects/Editor/CodeGen/RuntimeAccessModifiersILPP.cs +++ b/com.unity.netcode.gameobjects/Editor/CodeGen/RuntimeAccessModifiersILPP.cs @@ -123,7 +123,10 @@ private void ProcessNetworkBehaviour(TypeDefinition typeDefinition) if (methodDefinition.Name == nameof(NetworkBehaviour.__beginSendServerRpc) || methodDefinition.Name == nameof(NetworkBehaviour.__endSendServerRpc) || methodDefinition.Name == nameof(NetworkBehaviour.__beginSendClientRpc) || - methodDefinition.Name == nameof(NetworkBehaviour.__endSendClientRpc) || methodDefinition.Name == nameof(NetworkBehaviour.__initializeVariables) || methodDefinition.Name == nameof(NetworkBehaviour.__nameNetworkVariable)) + methodDefinition.Name == nameof(NetworkBehaviour.__endSendClientRpc) || + methodDefinition.Name == nameof(NetworkBehaviour.__initializeVariables) || + methodDefinition.Name == nameof(NetworkBehaviour.__nameNetworkVariable) || + methodDefinition.Name == nameof(NetworkBehaviour.__createNativeList)) { methodDefinition.IsFamily = true; } diff --git a/com.unity.netcode.gameobjects/Runtime/Core/NetworkBehaviour.cs b/com.unity.netcode.gameobjects/Runtime/Core/NetworkBehaviour.cs index 8f02fe1563..782ecba650 100644 --- a/com.unity.netcode.gameobjects/Runtime/Core/NetworkBehaviour.cs +++ b/com.unity.netcode.gameobjects/Runtime/Core/NetworkBehaviour.cs @@ -275,6 +275,14 @@ internal void __endSendClientRpc(ref FastBufferWriter bufferWriter, uint rpcMeth #endif } +#pragma warning disable IDE1006 // disable naming rule violation check + // RuntimeAccessModifiersILPP will make this `protected` + internal static NativeList __createNativeList() where T : unmanaged +#pragma warning restore IDE1006 // restore naming rule violation check + { + return new NativeList(Allocator.Temp); + } + internal string GenerateObserverErrorMessage(ClientRpcParams clientRpcParams, ulong targetClientId) { var containerNameHoldingId = clientRpcParams.Send.TargetClientIds != null ? nameof(ClientRpcParams.Send.TargetClientIds) : nameof(ClientRpcParams.Send.TargetClientIdsNativeArray); diff --git a/com.unity.netcode.gameobjects/Runtime/NetworkVariable/NetworkVariable.cs b/com.unity.netcode.gameobjects/Runtime/NetworkVariable/NetworkVariable.cs index 538d6f31af..505df391b9 100644 --- a/com.unity.netcode.gameobjects/Runtime/NetworkVariable/NetworkVariable.cs +++ b/com.unity.netcode.gameobjects/Runtime/NetworkVariable/NetworkVariable.cs @@ -33,6 +33,13 @@ public NetworkVariable(T value = default, : base(readPerm, writePerm) { m_InternalValue = value; + // Since we start with IsDirty = true, this doesn't need to be duplicated + // right away. It won't get read until after ResetDirty() is called, and + // the duplicate will be made there. Avoiding calling + // NetworkVariableSerialization.Duplicate() is important because calling + // it in the constructor might not give users enough time to set the + // DuplicateValue callback if they're using UserNetworkVariableSerialization + m_PreviousValue = default; } /// @@ -41,6 +48,11 @@ public NetworkVariable(T value = default, [SerializeField] private protected T m_InternalValue; + private protected T m_PreviousValue; + + private bool m_HasPreviousValue; + private bool m_IsDisposed; + /// /// The value of the NetworkVariable container /// @@ -61,9 +73,83 @@ public virtual T Value } Set(value); + m_IsDisposed = false; } } + internal ref T RefValue() + { + return ref m_InternalValue; + } + + public override void Dispose() + { + if (m_IsDisposed) + { + return; + } + + m_IsDisposed = true; + if (m_InternalValue is IDisposable internalValueDisposable) + { + internalValueDisposable.Dispose(); + } + + m_InternalValue = default; + if (m_HasPreviousValue && m_PreviousValue is IDisposable previousValueDisposable) + { + m_HasPreviousValue = false; + previousValueDisposable.Dispose(); + } + + m_PreviousValue = default; + } + + ~NetworkVariable() + { + Dispose(); + } + + /// + /// Gets Whether or not the container is dirty + /// + /// Whether or not the container is dirty + public override bool IsDirty() + { + // For most cases we can use the dirty flag. + // This doesn't work for cases where we're wrapping more complex types + // like INetworkSerializable, NativeList, NativeArray, etc. + // Changes to the values in those types don't call the Value.set method, + // so we can't catch those changes and need to compare the current value + // against the previous one. + if (base.IsDirty()) + { + return true; + } + + // Cache the dirty value so we don't perform this again if we already know we're dirty + // Unfortunately we can't cache the NOT dirty state, because that might change + // in between to checks... but the DIRTY state won't change until ResetDirty() + // is called. + var dirty = !NetworkVariableSerialization.AreEqual(ref m_PreviousValue, ref m_InternalValue); + SetDirty(dirty); + return dirty; + } + + /// + /// Resets the dirty state and marks the variable as synced / clean + /// + public override void ResetDirty() + { + base.ResetDirty(); + // Resetting the dirty value declares that the current value is not dirty + // Therefore, we set the m_PreviousValue field to a duplicate of the current + // field, so that our next dirty check is made against the current "not dirty" + // value. + m_HasPreviousValue = true; + NetworkVariableSerialization.Serializer.Duplicate(m_InternalValue, ref m_PreviousValue); + } + /// /// Sets the , marks the dirty, and invokes the callback /// if there are subscribers to that event. diff --git a/com.unity.netcode.gameobjects/Runtime/NetworkVariable/NetworkVariableSerialization.cs b/com.unity.netcode.gameobjects/Runtime/NetworkVariable/NetworkVariableSerialization.cs index 463f50244d..59f951999a 100644 --- a/com.unity.netcode.gameobjects/Runtime/NetworkVariable/NetworkVariableSerialization.cs +++ b/com.unity.netcode.gameobjects/Runtime/NetworkVariable/NetworkVariableSerialization.cs @@ -20,6 +20,8 @@ internal interface INetworkVariableSerializer // of it to pass it as a ref parameter. public void Write(FastBufferWriter writer, ref T value); public void Read(FastBufferReader reader, ref T value); + internal void ReadWithAllocator(FastBufferReader reader, out T value, Allocator allocator); + public void Duplicate(in T value, ref T duplicatedValue); } /// @@ -35,6 +37,16 @@ public void Read(FastBufferReader reader, ref short value) { ByteUnpacker.ReadValueBitPacked(reader, out value); } + + void INetworkVariableSerializer.ReadWithAllocator(FastBufferReader reader, out short value, Allocator allocator) + { + throw new NotImplementedException(); + } + + public void Duplicate(in short value, ref short duplicatedValue) + { + duplicatedValue = value; + } } /// @@ -50,6 +62,16 @@ public void Read(FastBufferReader reader, ref ushort value) { ByteUnpacker.ReadValueBitPacked(reader, out value); } + + void INetworkVariableSerializer.ReadWithAllocator(FastBufferReader reader, out ushort value, Allocator allocator) + { + throw new NotImplementedException(); + } + + public void Duplicate(in ushort value, ref ushort duplicatedValue) + { + duplicatedValue = value; + } } /// @@ -65,6 +87,16 @@ public void Read(FastBufferReader reader, ref int value) { ByteUnpacker.ReadValueBitPacked(reader, out value); } + + void INetworkVariableSerializer.ReadWithAllocator(FastBufferReader reader, out int value, Allocator allocator) + { + throw new NotImplementedException(); + } + + public void Duplicate(in int value, ref int duplicatedValue) + { + duplicatedValue = value; + } } /// @@ -80,6 +112,16 @@ public void Read(FastBufferReader reader, ref uint value) { ByteUnpacker.ReadValueBitPacked(reader, out value); } + + void INetworkVariableSerializer.ReadWithAllocator(FastBufferReader reader, out uint value, Allocator allocator) + { + throw new NotImplementedException(); + } + + public void Duplicate(in uint value, ref uint duplicatedValue) + { + duplicatedValue = value; + } } /// @@ -95,6 +137,16 @@ public void Read(FastBufferReader reader, ref long value) { ByteUnpacker.ReadValueBitPacked(reader, out value); } + + void INetworkVariableSerializer.ReadWithAllocator(FastBufferReader reader, out long value, Allocator allocator) + { + throw new NotImplementedException(); + } + + public void Duplicate(in long value, ref long duplicatedValue) + { + duplicatedValue = value; + } } /// @@ -110,6 +162,16 @@ public void Read(FastBufferReader reader, ref ulong value) { ByteUnpacker.ReadValueBitPacked(reader, out value); } + + void INetworkVariableSerializer.ReadWithAllocator(FastBufferReader reader, out ulong value, Allocator allocator) + { + throw new NotImplementedException(); + } + + public void Duplicate(in ulong value, ref ulong duplicatedValue) + { + duplicatedValue = value; + } } /// @@ -130,6 +192,80 @@ public void Read(FastBufferReader reader, ref T value) { reader.ReadUnmanagedSafe(out value); } + + void INetworkVariableSerializer.ReadWithAllocator(FastBufferReader reader, out T value, Allocator allocator) + { + throw new NotImplementedException(); + } + + public void Duplicate(in T value, ref T duplicatedValue) + { + duplicatedValue = value; + } + } + + internal class UnmanagedArraySerializer : INetworkVariableSerializer> where T : unmanaged + { + public void Write(FastBufferWriter writer, ref NativeArray value) + { + writer.WriteUnmanagedSafe(value); + } + public void Read(FastBufferReader reader, ref NativeArray value) + { + value.Dispose(); + reader.ReadUnmanagedSafe(out value, Allocator.Persistent); + } + + void INetworkVariableSerializer>.ReadWithAllocator(FastBufferReader reader, out NativeArray value, Allocator allocator) + { + reader.ReadUnmanagedSafe(out value, allocator); + } + + public void Duplicate(in NativeArray value, ref NativeArray duplicatedValue) + { + if (!duplicatedValue.IsCreated || duplicatedValue.Length != value.Length) + { + if (duplicatedValue.IsCreated) + { + duplicatedValue.Dispose(); + } + + duplicatedValue = new NativeArray(value.Length, Allocator.Persistent, NativeArrayOptions.UninitializedMemory); + } + + duplicatedValue.CopyFrom(value); + } + } + + internal class UnmanagedListSerializer : INetworkVariableSerializer> where T : unmanaged + { + public void Write(FastBufferWriter writer, ref NativeList value) + { + writer.WriteUnmanagedSafe(value); + } + public void Read(FastBufferReader reader, ref NativeList value) + { + reader.ReadUnmanagedSafeInPlace(ref value); + } + + void INetworkVariableSerializer>.ReadWithAllocator(FastBufferReader reader, out NativeList value, Allocator allocator) + { + throw new NotImplementedException(); + } + + public void Duplicate(in NativeList value, ref NativeList duplicatedValue) + { + if (!duplicatedValue.IsCreated) + { + duplicatedValue = new NativeList(value.Length, Allocator.Persistent); + } + else if (value.Length != duplicatedValue.Length) + { + duplicatedValue.ResizeUninitialized(value.Length); + } + + duplicatedValue.CopyFrom(value); + } } /// @@ -146,6 +282,88 @@ public void Read(FastBufferReader reader, ref T value) { reader.ReadValueSafeInPlace(ref value); } + + void INetworkVariableSerializer.ReadWithAllocator(FastBufferReader reader, out T value, Allocator allocator) + { + throw new NotImplementedException(); + } + + public void Duplicate(in T value, ref T duplicatedValue) + { + duplicatedValue = value; + } + } + + /// + /// Serializer for FixedStrings + /// + /// + internal class FixedStringArraySerializer : INetworkVariableSerializer> where T : unmanaged, INativeList, IUTF8Bytes + { + public void Write(FastBufferWriter writer, ref NativeArray value) + { + writer.WriteValueSafe(value); + } + public void Read(FastBufferReader reader, ref NativeArray value) + { + value.Dispose(); + reader.ReadValueSafe(out value, Allocator.Persistent); + } + + void INetworkVariableSerializer>.ReadWithAllocator(FastBufferReader reader, out NativeArray value, Allocator allocator) + { + reader.ReadValueSafe(out value, allocator); + } + + public void Duplicate(in NativeArray value, ref NativeArray duplicatedValue) + { + if (!duplicatedValue.IsCreated || duplicatedValue.Length != value.Length) + { + if (duplicatedValue.IsCreated) + { + duplicatedValue.Dispose(); + } + + duplicatedValue = new NativeArray(value.Length, Allocator.Persistent, NativeArrayOptions.UninitializedMemory); + } + + duplicatedValue.CopyFrom(value); + } + } + + /// + /// Serializer for FixedStrings + /// + /// + internal class FixedStringListSerializer : INetworkVariableSerializer> where T : unmanaged, INativeList, IUTF8Bytes + { + public void Write(FastBufferWriter writer, ref NativeList value) + { + writer.WriteValueSafe(value); + } + public void Read(FastBufferReader reader, ref NativeList value) + { + reader.ReadValueSafeInPlace(ref value); + } + + void INetworkVariableSerializer>.ReadWithAllocator(FastBufferReader reader, out NativeList value, Allocator allocator) + { + throw new NotImplementedException(); + } + + public void Duplicate(in NativeList value, ref NativeList duplicatedValue) + { + if (!duplicatedValue.IsCreated) + { + duplicatedValue = new NativeList(value.Length, Allocator.Persistent); + } + else if (value.Length != duplicatedValue.Length) + { + duplicatedValue.ResizeUninitialized(value.Length); + } + + duplicatedValue.CopyFrom(value); + } } /// @@ -163,7 +381,88 @@ public void Read(FastBufferReader reader, ref T value) { var bufferSerializer = new BufferSerializer(new BufferSerializerReader(reader)); value.NetworkSerialize(bufferSerializer); + } + void INetworkVariableSerializer.ReadWithAllocator(FastBufferReader reader, out T value, Allocator allocator) + { + throw new NotImplementedException(); + } + + public void Duplicate(in T value, ref T duplicatedValue) + { + duplicatedValue = value; + } + } + + /// + /// Serializer for unmanaged INetworkSerializable types + /// + /// + internal class UnmanagedNetworkSerializableArraySerializer : INetworkVariableSerializer> where T : unmanaged, INetworkSerializable + { + public void Write(FastBufferWriter writer, ref NativeArray value) + { + writer.WriteNetworkSerializable(value); + } + public void Read(FastBufferReader reader, ref NativeArray value) + { + value.Dispose(); + reader.ReadNetworkSerializable(out value, Allocator.Persistent); + } + + void INetworkVariableSerializer>.ReadWithAllocator(FastBufferReader reader, out NativeArray value, Allocator allocator) + { + reader.ReadNetworkSerializable(out value, allocator); + } + + public void Duplicate(in NativeArray value, ref NativeArray duplicatedValue) + { + if (!duplicatedValue.IsCreated || duplicatedValue.Length != value.Length) + { + if (duplicatedValue.IsCreated) + { + duplicatedValue.Dispose(); + } + + duplicatedValue = new NativeArray(value.Length, Allocator.Persistent, NativeArrayOptions.UninitializedMemory); + } + + duplicatedValue.CopyFrom(value); + } + } + + /// + /// Serializer for unmanaged INetworkSerializable types + /// + /// + internal class UnmanagedNetworkSerializableListSerializer : INetworkVariableSerializer> where T : unmanaged, INetworkSerializable + { + public void Write(FastBufferWriter writer, ref NativeList value) + { + writer.WriteNetworkSerializable(value); + } + public void Read(FastBufferReader reader, ref NativeList value) + { + reader.ReadNetworkSerializableInPlace(ref value); + } + + void INetworkVariableSerializer>.ReadWithAllocator(FastBufferReader reader, out NativeList value, Allocator allocator) + { + throw new NotImplementedException(); + } + + public void Duplicate(in NativeList value, ref NativeList duplicatedValue) + { + if (!duplicatedValue.IsCreated) + { + duplicatedValue = new NativeList(value.Length, Allocator.Persistent); + } + else if (value.Length != duplicatedValue.Length) + { + duplicatedValue.ResizeUninitialized(value.Length); + } + + duplicatedValue.CopyFrom(value); } } @@ -201,6 +500,21 @@ public void Read(FastBufferReader reader, ref T value) value.NetworkSerialize(bufferSerializer); } } + + void INetworkVariableSerializer.ReadWithAllocator(FastBufferReader reader, out T value, Allocator allocator) + { + throw new NotImplementedException(); + } + + public void Duplicate(in T value, ref T duplicatedValue) + { + using var writer = new FastBufferWriter(256, Allocator.Temp); + var refValue = value; + Write(writer, ref refValue); + + using var reader = new FastBufferReader(writer, Allocator.None); + Read(reader, ref duplicatedValue); + } } /// @@ -227,14 +541,26 @@ public class UserNetworkVariableSerialization public delegate void ReadValueDelegate(FastBufferReader reader, out T value); /// - /// The delegate handler declaration + /// The read value delegate handler definition + /// + /// The to read the value of type `T` + /// The value of type `T` to be read + public delegate void DuplicateValueDelegate(in T value, ref T duplicatedValue); + + /// + /// Callback to write a value /// public static WriteValueDelegate WriteValue; /// - /// The delegate handler declaration + /// Callback to read a value /// public static ReadValueDelegate ReadValue; + + /// + /// Callback to create a duplicate of a value, used to check for dirty status. + /// + public static DuplicateValueDelegate DuplicateValue; } /// @@ -250,20 +576,34 @@ internal class FallbackSerializer : INetworkVariableSerializer { public void Write(FastBufferWriter writer, ref T value) { - if (UserNetworkVariableSerialization.ReadValue == null || UserNetworkVariableSerialization.WriteValue == null) + if (UserNetworkVariableSerialization.ReadValue == null || UserNetworkVariableSerialization.WriteValue == null || UserNetworkVariableSerialization.DuplicateValue == null) { - throw new ArgumentException($"Type {typeof(T).FullName} is not supported by {typeof(NetworkVariable<>).Name}. If this is a type you can change, then either implement {nameof(INetworkSerializable)} or mark it as serializable by memcpy by adding {nameof(INetworkSerializeByMemcpy)} to its interface list. If not, assign serialization code to {nameof(UserNetworkVariableSerialization)}.{nameof(UserNetworkVariableSerialization.WriteValue)} and {nameof(UserNetworkVariableSerialization)}.{nameof(UserNetworkVariableSerialization.ReadValue)}, or if it's serializable by memcpy (contains no pointers), wrap it in {typeof(ForceNetworkSerializeByMemcpy<>).Name}."); + throw new ArgumentException($"Type {typeof(T).FullName} is not supported by {typeof(NetworkVariable<>).Name}. If this is a type you can change, then either implement {nameof(INetworkSerializable)} or mark it as serializable by memcpy by adding {nameof(INetworkSerializeByMemcpy)} to its interface list. If not, assign serialization code to {nameof(UserNetworkVariableSerialization)}.{nameof(UserNetworkVariableSerialization.WriteValue)}, {nameof(UserNetworkVariableSerialization)}.{nameof(UserNetworkVariableSerialization.ReadValue)}, and {nameof(UserNetworkVariableSerialization)}.{nameof(UserNetworkVariableSerialization.DuplicateValue)}, or if it's serializable by memcpy (contains no pointers), wrap it in {typeof(ForceNetworkSerializeByMemcpy<>).Name}."); } UserNetworkVariableSerialization.WriteValue(writer, value); } public void Read(FastBufferReader reader, ref T value) { - if (UserNetworkVariableSerialization.ReadValue == null || UserNetworkVariableSerialization.WriteValue == null) + if (UserNetworkVariableSerialization.ReadValue == null || UserNetworkVariableSerialization.WriteValue == null || UserNetworkVariableSerialization.DuplicateValue == null) { - throw new ArgumentException($"Type {typeof(T).FullName} is not supported by {typeof(NetworkVariable<>).Name}. If this is a type you can change, then either implement {nameof(INetworkSerializable)} or mark it as serializable by memcpy by adding {nameof(INetworkSerializeByMemcpy)} to its interface list. If not, assign serialization code to {nameof(UserNetworkVariableSerialization)}.{nameof(UserNetworkVariableSerialization.WriteValue)} and {nameof(UserNetworkVariableSerialization)}.{nameof(UserNetworkVariableSerialization.ReadValue)}, or if it's serializable by memcpy (contains no pointers), wrap it in {typeof(ForceNetworkSerializeByMemcpy<>).Name}."); + throw new ArgumentException($"Type {typeof(T).FullName} is not supported by {typeof(NetworkVariable<>).Name}. If this is a type you can change, then either implement {nameof(INetworkSerializable)} or mark it as serializable by memcpy by adding {nameof(INetworkSerializeByMemcpy)} to its interface list. If not, assign serialization code to {nameof(UserNetworkVariableSerialization)}.{nameof(UserNetworkVariableSerialization.WriteValue)}, {nameof(UserNetworkVariableSerialization)}.{nameof(UserNetworkVariableSerialization.ReadValue)}, and {nameof(UserNetworkVariableSerialization)}.{nameof(UserNetworkVariableSerialization.DuplicateValue)}, or if it's serializable by memcpy (contains no pointers), wrap it in {typeof(ForceNetworkSerializeByMemcpy<>).Name}."); } UserNetworkVariableSerialization.ReadValue(reader, out value); } + + void INetworkVariableSerializer.ReadWithAllocator(FastBufferReader reader, out T value, Allocator allocator) + { + throw new NotImplementedException(); + } + + public void Duplicate(in T value, ref T duplicatedValue) + { + if (UserNetworkVariableSerialization.ReadValue == null || UserNetworkVariableSerialization.WriteValue == null || UserNetworkVariableSerialization.DuplicateValue == null) + { + throw new ArgumentException($"Type {typeof(T).FullName} is not supported by {typeof(NetworkVariable<>).Name}. If this is a type you can change, then either implement {nameof(INetworkSerializable)} or mark it as serializable by memcpy by adding {nameof(INetworkSerializeByMemcpy)} to its interface list. If not, assign serialization code to {nameof(UserNetworkVariableSerialization)}.{nameof(UserNetworkVariableSerialization.WriteValue)}, {nameof(UserNetworkVariableSerialization)}.{nameof(UserNetworkVariableSerialization.ReadValue)}, and {nameof(UserNetworkVariableSerialization)}.{nameof(UserNetworkVariableSerialization.DuplicateValue)}, or if it's serializable by memcpy (contains no pointers), wrap it in {typeof(ForceNetworkSerializeByMemcpy<>).Name}."); + } + UserNetworkVariableSerialization.DuplicateValue(value, ref duplicatedValue); + } } /// @@ -309,6 +649,24 @@ public static void InitializeSerializer_UnmanagedByMemcpy() where T : unmanag NetworkVariableSerialization.Serializer = new UnmanagedTypeSerializer(); } + /// + /// Registeres an unmanaged type that will be serialized by a direct memcpy into a buffer + /// + /// + public static void InitializeSerializer_UnmanagedByMemcpyArray() where T : unmanaged + { + NetworkVariableSerialization>.Serializer = new UnmanagedArraySerializer(); + } + + /// + /// Registeres an unmanaged type that will be serialized by a direct memcpy into a buffer + /// + /// + public static void InitializeSerializer_UnmanagedByMemcpyList() where T : unmanaged + { + NetworkVariableSerialization>.Serializer = new UnmanagedListSerializer(); + } + /// /// Registers an unmanaged type that implements INetworkSerializable and will be serialized through a call to /// NetworkSerialize @@ -319,6 +677,26 @@ public static void InitializeSerializer_UnmanagedINetworkSerializable() where NetworkVariableSerialization.Serializer = new UnmanagedNetworkSerializableSerializer(); } + /// + /// Registers an unmanaged type that implements INetworkSerializable and will be serialized through a call to + /// NetworkSerialize + /// + /// + public static void InitializeSerializer_UnmanagedINetworkSerializableArray() where T : unmanaged, INetworkSerializable + { + NetworkVariableSerialization>.Serializer = new UnmanagedNetworkSerializableArraySerializer(); + } + + /// + /// Registers an unmanaged type that implements INetworkSerializable and will be serialized through a call to + /// NetworkSerialize + /// + /// + public static void InitializeSerializer_UnmanagedINetworkSerializableList() where T : unmanaged, INetworkSerializable + { + NetworkVariableSerialization>.Serializer = new UnmanagedNetworkSerializableListSerializer(); + } + /// /// Registers a managed type that implements INetworkSerializable and will be serialized through a call to /// NetworkSerialize @@ -339,6 +717,26 @@ public static void InitializeSerializer_FixedString() where T : unmanaged, IN NetworkVariableSerialization.Serializer = new FixedStringSerializer(); } + /// + /// Registers a FixedString type that will be serialized through FastBufferReader/FastBufferWriter's FixedString + /// serializers + /// + /// + public static void InitializeSerializer_FixedStringArray() where T : unmanaged, INativeList, IUTF8Bytes + { + NetworkVariableSerialization>.Serializer = new FixedStringArraySerializer(); + } + + /// + /// Registers a FixedString type that will be serialized through FastBufferReader/FastBufferWriter's FixedString + /// serializers + /// + /// + public static void InitializeSerializer_FixedStringList() where T : unmanaged, INativeList, IUTF8Bytes + { + NetworkVariableSerialization>.Serializer = new FixedStringListSerializer(); + } + /// /// Registers a managed type that will be checked for equality using T.Equals() /// @@ -357,6 +755,24 @@ public static void InitializeEqualityChecker_UnmanagedIEquatable() where T : NetworkVariableSerialization.AreEqual = NetworkVariableSerialization.EqualityEquals; } + /// + /// Registers an unmanaged type that will be checked for equality using T.Equals() + /// + /// + public static void InitializeEqualityChecker_UnmanagedIEquatableArray() where T : unmanaged, IEquatable + { + NetworkVariableSerialization>.AreEqual = NetworkVariableSerialization.EqualityEqualsArray; + } + + /// + /// Registers an unmanaged type that will be checked for equality using T.Equals() + /// + /// + public static void InitializeEqualityChecker_UnmanagedIEquatableList() where T : unmanaged, IEquatable + { + NetworkVariableSerialization>.AreEqual = NetworkVariableSerialization.EqualityEqualsList; + } + /// /// Registers an unmanaged type that will be checked for equality using memcmp and only considered /// equal if they are bitwise equivalent in memory @@ -367,6 +783,26 @@ public static void InitializeEqualityChecker_UnmanagedValueEquals() where T : NetworkVariableSerialization.AreEqual = NetworkVariableSerialization.ValueEquals; } + /// + /// Registers an unmanaged type that will be checked for equality using memcmp and only considered + /// equal if they are bitwise equivalent in memory + /// + /// + public static void InitializeEqualityChecker_UnmanagedValueEqualsArray() where T : unmanaged + { + NetworkVariableSerialization>.AreEqual = NetworkVariableSerialization.ValueEqualsArray; + } + + /// + /// Registers an unmanaged type that will be checked for equality using memcmp and only considered + /// equal if they are bitwise equivalent in memory + /// + /// + public static void InitializeEqualityChecker_UnmanagedValueEqualsList() where T : unmanaged + { + NetworkVariableSerialization>.AreEqual = NetworkVariableSerialization.ValueEqualsList; + } + /// /// Registers a managed type that will be checked for equality using the == operator /// @@ -405,6 +841,56 @@ internal static unsafe bool ValueEquals(ref TValueType a, ref TValue return UnsafeUtility.MemCmp(aptr, bptr, sizeof(TValueType)) == 0; } + // Compares two values of the same unmanaged type by underlying memory + // Ignoring any overridden value checks + // Size is fixed + internal static unsafe bool ValueEqualsList(ref NativeList a, ref NativeList b) where TValueType : unmanaged + { + if (a.IsCreated != b.IsCreated) + { + return false; + } + + if (!a.IsCreated) + { + return true; + } + + if (a.Length != b.Length) + { + return false; + } + + var aptr = (TValueType*)a.GetUnsafePtr(); + var bptr = (TValueType*)b.GetUnsafePtr(); + return UnsafeUtility.MemCmp(aptr, bptr, sizeof(TValueType) * a.Length) == 0; + } + + // Compares two values of the same unmanaged type by underlying memory + // Ignoring any overridden value checks + // Size is fixed + internal static unsafe bool ValueEqualsArray(ref NativeArray a, ref NativeArray b) where TValueType : unmanaged + { + if (a.IsCreated != b.IsCreated) + { + return false; + } + + if (!a.IsCreated) + { + return true; + } + + if (a.Length != b.Length) + { + return false; + } + + var aptr = (TValueType*)a.GetUnsafePtr(); + var bptr = (TValueType*)b.GetUnsafePtr(); + return UnsafeUtility.MemCmp(aptr, bptr, sizeof(TValueType) * a.Length) == 0; + } + internal static bool EqualityEqualsObject(ref TValueType a, ref TValueType b) where TValueType : class, IEquatable { if (a == null) @@ -425,6 +911,72 @@ internal static bool EqualityEquals(ref TValueType a, ref TValueType return a.Equals(b); } + // Compares two values of the same unmanaged type by underlying memory + // Ignoring any overridden value checks + // Size is fixed + internal static unsafe bool EqualityEqualsList(ref NativeList a, ref NativeList b) where TValueType : unmanaged, IEquatable + { + if (a.IsCreated != b.IsCreated) + { + return false; + } + + if (!a.IsCreated) + { + return true; + } + + if (a.Length != b.Length) + { + return false; + } + + var aptr = (TValueType*)a.GetUnsafePtr(); + var bptr = (TValueType*)b.GetUnsafePtr(); + for (var i = 0; i < a.Length; ++i) + { + if (!EqualityEquals(ref aptr[i], ref bptr[i])) + { + return false; + } + } + + return true; + } + + // Compares two values of the same unmanaged type by underlying memory + // Ignoring any overridden value checks + // Size is fixed + internal static unsafe bool EqualityEqualsArray(ref NativeArray a, ref NativeArray b) where TValueType : unmanaged, IEquatable + { + if (a.IsCreated != b.IsCreated) + { + return false; + } + + if (!a.IsCreated) + { + return true; + } + + if (a.Length != b.Length) + { + return false; + } + + var aptr = (TValueType*)a.GetUnsafePtr(); + var bptr = (TValueType*)b.GetUnsafePtr(); + for (var i = 0; i < a.Length; ++i) + { + if (!EqualityEquals(ref aptr[i], ref bptr[i])) + { + return false; + } + } + + return true; + } + internal static bool ClassEquals(ref TValueType a, ref TValueType b) where TValueType : class { return a == b; diff --git a/com.unity.netcode.gameobjects/Runtime/Serialization/BufferSerializer.cs b/com.unity.netcode.gameobjects/Runtime/Serialization/BufferSerializer.cs index 4afd4579b5..d6e1ca0cc4 100644 --- a/com.unity.netcode.gameobjects/Runtime/Serialization/BufferSerializer.cs +++ b/com.unity.netcode.gameobjects/Runtime/Serialization/BufferSerializer.cs @@ -124,6 +124,23 @@ public FastBufferWriter GetFastBufferWriter() /// The type being serialized public void SerializeValue(ref T[] value, FastBufferWriter.ForStructs unused = default) where T : unmanaged, INetworkSerializeByMemcpy => m_Implementation.SerializeValue(ref value); + /// + /// Read or write a NativeArray of struct values implementing ISerializeByMemcpy + /// + /// The values to read/write + /// The allocator to use to construct the resulting NativeArray when reading + /// An unused parameter used for enabling overload resolution based on generic constraints + /// The type being serialized + public void SerializeValue(ref NativeArray value, Allocator allocator, FastBufferWriter.ForGeneric unused = default) where T : unmanaged => m_Implementation.SerializeValue(ref value, allocator); + + /// + /// Read or write a NativeList of struct values implementing ISerializeByMemcpy + /// + /// The values to read/write + /// An unused parameter used for enabling overload resolution based on generic constraints + /// The type being serialized + public void SerializeValue(ref NativeList value, FastBufferWriter.ForGeneric unused = default) where T : unmanaged => m_Implementation.SerializeValue(ref value); + /// /// Read or write a struct or class value implementing INetworkSerializable /// @@ -266,6 +283,7 @@ public FastBufferWriter GetFastBufferWriter() // Those two are necessary to serialize FixedStrings efficiently // - otherwise we'd just be memcpy'ing the whole thing even if // most of it isn't used. + /// /// Read or write a FixedString value /// @@ -275,6 +293,25 @@ public FastBufferWriter GetFastBufferWriter() public void SerializeValue(ref T value, FastBufferWriter.ForFixedStrings unused = default) where T : unmanaged, INativeList, IUTF8Bytes => m_Implementation.SerializeValue(ref value); + /// + /// Read or write a NativeArray of FixedString values + /// + /// The network serializable type + /// The values to read/write + /// The allocator to use to construct the resulting NativeArray when reading + /// An unused parameter used for enabling overload resolution of FixedStrings + public void SerializeValue(ref NativeArray value, Allocator allocator) + where T : unmanaged, INativeList, IUTF8Bytes => m_Implementation.SerializeValue(ref value, allocator); + + /// + /// Read or write a NativeList of FixedString values + /// + /// The network serializable type + /// The values to read/write + /// An unused parameter used for enabling overload resolution of FixedStrings + public void SerializeValue(ref NativeList value) + where T : unmanaged, INativeList, IUTF8Bytes => m_Implementation.SerializeValue(ref value); + /// /// Read or write a NetworkSerializable value. /// SerializeValue() is the preferred method to do this - this is provided for backward compatibility only. @@ -381,6 +418,29 @@ public bool PreCheck(int amount) /// An unused parameter used for enabling overload resolution of structs public void SerializeValuePreChecked(ref T[] value, FastBufferWriter.ForStructs unused = default) where T : unmanaged, INetworkSerializeByMemcpy => m_Implementation.SerializeValuePreChecked(ref value); + /// + /// Serialize a NativeArray of structs, "pre-checked", which skips buffer checks. + /// In debug and editor builds, a check is made to ensure you've called "PreCheck" before + /// calling this. In release builds, calling this without calling "PreCheck" may read or write + /// past the end of the buffer, which will cause memory corruption and undefined behavior. + /// + /// The network serializable types in an array + /// The values to read/write + /// The allocator to use to construct the resulting NativeArray when reading + /// An unused parameter used for enabling overload resolution of structs + public void SerializeValuePreChecked(ref NativeArray value, Allocator allocator, FastBufferWriter.ForGeneric unused = default) where T : unmanaged => m_Implementation.SerializeValuePreChecked(ref value, allocator); + + /// + /// Serialize a NativeList of structs, "pre-checked", which skips buffer checks. + /// In debug and editor builds, a check is made to ensure you've called "PreCheck" before + /// calling this. In release builds, calling this without calling "PreCheck" may read or write + /// past the end of the buffer, which will cause memory corruption and undefined behavior. + /// + /// The network serializable types in an array + /// The values to read/write + /// An unused parameter used for enabling overload resolution of structs + public void SerializeValuePreChecked(ref NativeList value, FastBufferWriter.ForGeneric unused = default) where T : unmanaged => m_Implementation.SerializeValuePreChecked(ref value); + /// /// Serialize a Vector2, "pre-checked", which skips buffer checks. /// In debug and editor builds, a check is made to ensure you've called "PreCheck" before @@ -463,7 +523,7 @@ public bool PreCheck(int amount) public void SerializeValuePreChecked(ref Vector4 value) => m_Implementation.SerializeValuePreChecked(ref value); /// - /// Serialize a Vector4Array, "pre-checked", which skips buffer checks. + /// Serialize a Vector4 array, "pre-checked", which skips buffer checks. /// In debug and editor builds, a check is made to ensure you've called "PreCheck" before /// calling this. In release builds, calling this without calling "PreCheck" may read or write /// past the end of the buffer, which will cause memory corruption and undefined behavior. diff --git a/com.unity.netcode.gameobjects/Runtime/Serialization/BufferSerializerReader.cs b/com.unity.netcode.gameobjects/Runtime/Serialization/BufferSerializerReader.cs index 271f357165..30482342d8 100644 --- a/com.unity.netcode.gameobjects/Runtime/Serialization/BufferSerializerReader.cs +++ b/com.unity.netcode.gameobjects/Runtime/Serialization/BufferSerializerReader.cs @@ -30,34 +30,52 @@ public FastBufferWriter GetFastBufferWriter() public void SerializeValue(ref byte value) => m_Reader.ReadByteSafe(out value); public void SerializeValue(ref T value, FastBufferWriter.ForPrimitives unused = default) where T : unmanaged, IComparable, IConvertible, IComparable, IEquatable => m_Reader.ReadValueSafe(out value); public void SerializeValue(ref T[] value, FastBufferWriter.ForPrimitives unused = default) where T : unmanaged, IComparable, IConvertible, IComparable, IEquatable => m_Reader.ReadValueSafe(out value); + public void SerializeValue(ref T value, FastBufferWriter.ForEnums unused = default) where T : unmanaged, Enum => m_Reader.ReadValueSafe(out value); public void SerializeValue(ref T[] value, FastBufferWriter.ForEnums unused = default) where T : unmanaged, Enum => m_Reader.ReadValueSafe(out value); + public void SerializeValue(ref T value, FastBufferWriter.ForStructs unused = default) where T : unmanaged, INetworkSerializeByMemcpy => m_Reader.ReadValueSafe(out value); public void SerializeValue(ref T[] value, FastBufferWriter.ForStructs unused = default) where T : unmanaged, INetworkSerializeByMemcpy => m_Reader.ReadValueSafe(out value); + public void SerializeValue(ref NativeArray value, Allocator allocator, FastBufferWriter.ForGeneric unused = default) where T : unmanaged => m_Reader.ReadValueSafe(out value, allocator); + public void SerializeValue(ref NativeList value, FastBufferWriter.ForGeneric unused = default) where T : unmanaged => m_Reader.ReadValueSafeInPlace(ref value); + public void SerializeValue(ref T value, FastBufferWriter.ForNetworkSerializable unused = default) where T : INetworkSerializable, new() => m_Reader.ReadNetworkSerializableInPlace(ref value); public void SerializeValue(ref T[] value, FastBufferWriter.ForNetworkSerializable unused = default) where T : INetworkSerializable, new() => m_Reader.ReadValue(out value); public void SerializeValue(ref T value, FastBufferWriter.ForFixedStrings unused = default) where T : unmanaged, INativeList, IUTF8Bytes => m_Reader.ReadValueSafe(out value); + public void SerializeValue(ref NativeArray value, Allocator allocator) where T : unmanaged, INativeList, IUTF8Bytes => m_Reader.ReadValueSafe(out value, allocator); + + public void SerializeValue(ref NativeList value) where T : unmanaged, INativeList, IUTF8Bytes => m_Reader.ReadValueSafeInPlace(ref value); + public void SerializeValue(ref Vector2 value) => m_Reader.ReadValueSafe(out value); public void SerializeValue(ref Vector2[] value) => m_Reader.ReadValueSafe(out value); + public void SerializeValue(ref Vector3 value) => m_Reader.ReadValueSafe(out value); public void SerializeValue(ref Vector3[] value) => m_Reader.ReadValueSafe(out value); + public void SerializeValue(ref Vector2Int value) => m_Reader.ReadValueSafe(out value); public void SerializeValue(ref Vector2Int[] value) => m_Reader.ReadValueSafe(out value); + public void SerializeValue(ref Vector3Int value) => m_Reader.ReadValueSafe(out value); public void SerializeValue(ref Vector3Int[] value) => m_Reader.ReadValueSafe(out value); + public void SerializeValue(ref Vector4 value) => m_Reader.ReadValueSafe(out value); public void SerializeValue(ref Vector4[] value) => m_Reader.ReadValueSafe(out value); + public void SerializeValue(ref Quaternion value) => m_Reader.ReadValueSafe(out value); public void SerializeValue(ref Quaternion[] value) => m_Reader.ReadValueSafe(out value); + public void SerializeValue(ref Color value) => m_Reader.ReadValueSafe(out value); public void SerializeValue(ref Color[] value) => m_Reader.ReadValueSafe(out value); + public void SerializeValue(ref Color32 value) => m_Reader.ReadValueSafe(out value); public void SerializeValue(ref Color32[] value) => m_Reader.ReadValueSafe(out value); + public void SerializeValue(ref Ray value) => m_Reader.ReadValueSafe(out value); public void SerializeValue(ref Ray[] value) => m_Reader.ReadValueSafe(out value); + public void SerializeValue(ref Ray2D value) => m_Reader.ReadValueSafe(out value); public void SerializeValue(ref Ray2D[] value) => m_Reader.ReadValueSafe(out value); @@ -72,30 +90,46 @@ public bool PreCheck(int amount) public void SerializeValuePreChecked(ref byte value) => m_Reader.ReadByte(out value); public void SerializeValuePreChecked(ref T value, FastBufferWriter.ForPrimitives unused = default) where T : unmanaged, IComparable, IConvertible, IComparable, IEquatable => m_Reader.ReadValue(out value); public void SerializeValuePreChecked(ref T[] value, FastBufferWriter.ForPrimitives unused = default) where T : unmanaged, IComparable, IConvertible, IComparable, IEquatable => m_Reader.ReadValue(out value); + public void SerializeValuePreChecked(ref T value, FastBufferWriter.ForEnums unused = default) where T : unmanaged, Enum => m_Reader.ReadValue(out value); public void SerializeValuePreChecked(ref T[] value, FastBufferWriter.ForEnums unused = default) where T : unmanaged, Enum => m_Reader.ReadValue(out value); + public void SerializeValuePreChecked(ref T value, FastBufferWriter.ForStructs unused = default) where T : unmanaged, INetworkSerializeByMemcpy => m_Reader.ReadValue(out value); public void SerializeValuePreChecked(ref T[] value, FastBufferWriter.ForStructs unused = default) where T : unmanaged, INetworkSerializeByMemcpy => m_Reader.ReadValue(out value); + public void SerializeValuePreChecked(ref NativeArray value, Allocator allocator, FastBufferWriter.ForGeneric unused = default) where T : unmanaged => m_Reader.ReadValue(out value, allocator); + + public void SerializeValuePreChecked(ref NativeList value, FastBufferWriter.ForGeneric unused = default) where T : unmanaged => m_Reader.ReadValueInPlace(ref value); + public void SerializeValuePreChecked(ref T value, FastBufferWriter.ForFixedStrings unused = default) where T : unmanaged, INativeList, IUTF8Bytes => m_Reader.ReadValue(out value); + public void SerializeValuePreChecked(ref Vector2 value) => m_Reader.ReadValue(out value); public void SerializeValuePreChecked(ref Vector2[] value) => m_Reader.ReadValue(out value); + public void SerializeValuePreChecked(ref Vector3 value) => m_Reader.ReadValue(out value); public void SerializeValuePreChecked(ref Vector3[] value) => m_Reader.ReadValue(out value); + public void SerializeValuePreChecked(ref Vector2Int value) => m_Reader.ReadValue(out value); public void SerializeValuePreChecked(ref Vector2Int[] value) => m_Reader.ReadValue(out value); + public void SerializeValuePreChecked(ref Vector3Int value) => m_Reader.ReadValue(out value); public void SerializeValuePreChecked(ref Vector3Int[] value) => m_Reader.ReadValue(out value); + public void SerializeValuePreChecked(ref Vector4 value) => m_Reader.ReadValue(out value); public void SerializeValuePreChecked(ref Vector4[] value) => m_Reader.ReadValue(out value); + public void SerializeValuePreChecked(ref Quaternion value) => m_Reader.ReadValue(out value); public void SerializeValuePreChecked(ref Quaternion[] value) => m_Reader.ReadValue(out value); + public void SerializeValuePreChecked(ref Color value) => m_Reader.ReadValue(out value); public void SerializeValuePreChecked(ref Color[] value) => m_Reader.ReadValue(out value); + public void SerializeValuePreChecked(ref Color32 value) => m_Reader.ReadValue(out value); public void SerializeValuePreChecked(ref Color32[] value) => m_Reader.ReadValue(out value); + public void SerializeValuePreChecked(ref Ray value) => m_Reader.ReadValue(out value); public void SerializeValuePreChecked(ref Ray[] value) => m_Reader.ReadValue(out value); + public void SerializeValuePreChecked(ref Ray2D value) => m_Reader.ReadValue(out value); public void SerializeValuePreChecked(ref Ray2D[] value) => m_Reader.ReadValue(out value); } diff --git a/com.unity.netcode.gameobjects/Runtime/Serialization/BufferSerializerWriter.cs b/com.unity.netcode.gameobjects/Runtime/Serialization/BufferSerializerWriter.cs index 7c3bd1dd0f..915c60db68 100644 --- a/com.unity.netcode.gameobjects/Runtime/Serialization/BufferSerializerWriter.cs +++ b/com.unity.netcode.gameobjects/Runtime/Serialization/BufferSerializerWriter.cs @@ -30,33 +30,50 @@ public FastBufferWriter GetFastBufferWriter() public void SerializeValue(ref byte value) => m_Writer.WriteByteSafe(value); public void SerializeValue(ref T value, FastBufferWriter.ForPrimitives unused = default) where T : unmanaged, IComparable, IConvertible, IComparable, IEquatable => m_Writer.WriteValueSafe(value); public void SerializeValue(ref T[] value, FastBufferWriter.ForPrimitives unused = default) where T : unmanaged, IComparable, IConvertible, IComparable, IEquatable => m_Writer.WriteValueSafe(value); + public void SerializeValue(ref T value, FastBufferWriter.ForEnums unused = default) where T : unmanaged, Enum => m_Writer.WriteValueSafe(value); public void SerializeValue(ref T[] value, FastBufferWriter.ForEnums unused = default) where T : unmanaged, Enum => m_Writer.WriteValueSafe(value); + public void SerializeValue(ref T value, FastBufferWriter.ForStructs unused = default) where T : unmanaged, INetworkSerializeByMemcpy => m_Writer.WriteValueSafe(value); public void SerializeValue(ref T[] value, FastBufferWriter.ForStructs unused = default) where T : unmanaged, INetworkSerializeByMemcpy => m_Writer.WriteValueSafe(value); + public void SerializeValue(ref NativeArray value, Allocator allocator, FastBufferWriter.ForGeneric unused = default) where T : unmanaged => m_Writer.WriteValueSafe(value); + public void SerializeValue(ref NativeList value, FastBufferWriter.ForGeneric unused = default) where T : unmanaged => m_Writer.WriteValueSafe(value); + public void SerializeValue(ref T value, FastBufferWriter.ForNetworkSerializable unused = default) where T : INetworkSerializable, new() => m_Writer.WriteValue(value); public void SerializeValue(ref T[] value, FastBufferWriter.ForNetworkSerializable unused = default) where T : INetworkSerializable, new() => m_Writer.WriteValue(value); + public void SerializeValue(ref T value, FastBufferWriter.ForFixedStrings unused = default) where T : unmanaged, INativeList, IUTF8Bytes => m_Writer.WriteValueSafe(value); + public void SerializeValue(ref NativeArray value, Allocator allocator) where T : unmanaged, INativeList, IUTF8Bytes => m_Writer.WriteValueSafe(value); + public void SerializeValue(ref NativeList value) where T : unmanaged, INativeList, IUTF8Bytes => m_Writer.WriteValueSafe(value); public void SerializeValue(ref Vector2 value) => m_Writer.WriteValueSafe(value); public void SerializeValue(ref Vector2[] value) => m_Writer.WriteValueSafe(value); + public void SerializeValue(ref Vector3 value) => m_Writer.WriteValueSafe(value); public void SerializeValue(ref Vector3[] value) => m_Writer.WriteValueSafe(value); + public void SerializeValue(ref Vector2Int value) => m_Writer.WriteValueSafe(value); public void SerializeValue(ref Vector2Int[] value) => m_Writer.WriteValueSafe(value); + public void SerializeValue(ref Vector3Int value) => m_Writer.WriteValueSafe(value); public void SerializeValue(ref Vector3Int[] value) => m_Writer.WriteValueSafe(value); + public void SerializeValue(ref Vector4 value) => m_Writer.WriteValueSafe(value); public void SerializeValue(ref Vector4[] value) => m_Writer.WriteValueSafe(value); + public void SerializeValue(ref Quaternion value) => m_Writer.WriteValueSafe(value); public void SerializeValue(ref Quaternion[] value) => m_Writer.WriteValueSafe(value); + public void SerializeValue(ref Color value) => m_Writer.WriteValueSafe(value); public void SerializeValue(ref Color[] value) => m_Writer.WriteValueSafe(value); + public void SerializeValue(ref Color32 value) => m_Writer.WriteValueSafe(value); public void SerializeValue(ref Color32[] value) => m_Writer.WriteValueSafe(value); + public void SerializeValue(ref Ray value) => m_Writer.WriteValueSafe(value); public void SerializeValue(ref Ray[] value) => m_Writer.WriteValueSafe(value); + public void SerializeValue(ref Ray2D value) => m_Writer.WriteValueSafe(value); public void SerializeValue(ref Ray2D[] value) => m_Writer.WriteValueSafe(value); @@ -77,29 +94,42 @@ public bool PreCheck(int amount) public void SerializeValuePreChecked(ref T value, FastBufferWriter.ForEnums unused = default) where T : unmanaged, Enum => m_Writer.WriteValue(value); public void SerializeValuePreChecked(ref T[] value, FastBufferWriter.ForEnums unused = default) where T : unmanaged, Enum => m_Writer.WriteValue(value); + public void SerializeValuePreChecked(ref T value, FastBufferWriter.ForStructs unused = default) where T : unmanaged, INetworkSerializeByMemcpy => m_Writer.WriteValue(value); public void SerializeValuePreChecked(ref T[] value, FastBufferWriter.ForStructs unused = default) where T : unmanaged, INetworkSerializeByMemcpy => m_Writer.WriteValue(value); + public void SerializeValuePreChecked(ref NativeArray value, Allocator allocator, FastBufferWriter.ForGeneric unused = default) where T : unmanaged => m_Writer.WriteValue(value); + public void SerializeValuePreChecked(ref NativeList value, FastBufferWriter.ForGeneric unused = default) where T : unmanaged => m_Writer.WriteValue(value); + public void SerializeValuePreChecked(ref T value, FastBufferWriter.ForFixedStrings unused = default) where T : unmanaged, INativeList, IUTF8Bytes => m_Writer.WriteValue(value); public void SerializeValuePreChecked(ref Vector2 value) => m_Writer.WriteValue(value); public void SerializeValuePreChecked(ref Vector2[] value) => m_Writer.WriteValue(value); + public void SerializeValuePreChecked(ref Vector3 value) => m_Writer.WriteValue(value); public void SerializeValuePreChecked(ref Vector3[] value) => m_Writer.WriteValue(value); + public void SerializeValuePreChecked(ref Vector2Int value) => m_Writer.WriteValue(value); public void SerializeValuePreChecked(ref Vector2Int[] value) => m_Writer.WriteValue(value); + public void SerializeValuePreChecked(ref Vector3Int value) => m_Writer.WriteValue(value); public void SerializeValuePreChecked(ref Vector3Int[] value) => m_Writer.WriteValue(value); + public void SerializeValuePreChecked(ref Vector4 value) => m_Writer.WriteValue(value); public void SerializeValuePreChecked(ref Vector4[] value) => m_Writer.WriteValue(value); + public void SerializeValuePreChecked(ref Quaternion value) => m_Writer.WriteValue(value); public void SerializeValuePreChecked(ref Quaternion[] value) => m_Writer.WriteValue(value); + public void SerializeValuePreChecked(ref Color value) => m_Writer.WriteValue(value); public void SerializeValuePreChecked(ref Color[] value) => m_Writer.WriteValue(value); + public void SerializeValuePreChecked(ref Color32 value) => m_Writer.WriteValue(value); public void SerializeValuePreChecked(ref Color32[] value) => m_Writer.WriteValue(value); + public void SerializeValuePreChecked(ref Ray value) => m_Writer.WriteValue(value); public void SerializeValuePreChecked(ref Ray[] value) => m_Writer.WriteValue(value); + public void SerializeValuePreChecked(ref Ray2D value) => m_Writer.WriteValue(value); public void SerializeValuePreChecked(ref Ray2D[] value) => m_Writer.WriteValue(value); } diff --git a/com.unity.netcode.gameobjects/Runtime/Serialization/FastBufferReader.cs b/com.unity.netcode.gameobjects/Runtime/Serialization/FastBufferReader.cs index 2f62a65974..834719c701 100644 --- a/com.unity.netcode.gameobjects/Runtime/Serialization/FastBufferReader.cs +++ b/com.unity.netcode.gameobjects/Runtime/Serialization/FastBufferReader.cs @@ -62,7 +62,7 @@ internal unsafe void CommitBitwiseReads(int amount) private static unsafe ReaderHandle* CreateHandle(byte* buffer, int length, int offset, Allocator copyAllocator, Allocator internalAllocator) { - ReaderHandle* readerHandle = null; + ReaderHandle* readerHandle; if (copyAllocator == Allocator.None) { readerHandle = (ReaderHandle*)UnsafeUtility.Malloc(sizeof(ReaderHandle), UnsafeUtility.AlignOf(), internalAllocator); @@ -461,6 +461,40 @@ public unsafe byte[] ToArray() } } + /// + /// Read a NativeArray of INetworkSerializables + /// + /// INetworkSerializable instance + /// The allocator to use to construct the resulting NativeArray + /// the array to read the values of type `T` into + /// + public void ReadNetworkSerializable(out NativeArray value, Allocator allocator) where T : unmanaged, INetworkSerializable + { + ReadValueSafe(out int size); + value = new NativeArray(size, allocator); + for (var i = 0; i < size; ++i) + { + ReadNetworkSerializable(out T item); + value[i] = item; + } + } + + /// + /// Read a NativeList of INetworkSerializables + /// + /// INetworkSerializable instance + /// the array to read the values of type `T` into + /// + public void ReadNetworkSerializableInPlace(ref NativeList value) where T : unmanaged, INetworkSerializable + { + ReadValueSafe(out int size); + value.Resize(size, NativeArrayOptions.UninitializedMemory); + for (var i = 0; i < size; ++i) + { + ReadNetworkSerializable(out value.ElementAt(i)); + } + } + /// /// Read an INetworkSerializable in-place, without constructing a new one /// Note that this will NOT check for null before calling NetworkSerialize @@ -757,6 +791,42 @@ internal unsafe void ReadUnmanagedSafe(out T[] value) where T : unmanaged ReadBytesSafe(bytes, sizeInBytes); } } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal unsafe void ReadUnmanaged(out NativeArray value, Allocator allocator) where T : unmanaged + { + ReadUnmanaged(out int sizeInTs); + int sizeInBytes = sizeInTs * sizeof(T); + value = new NativeArray(sizeInTs, allocator); + byte* bytes = (byte*)value.GetUnsafePtr(); + ReadBytes(bytes, sizeInBytes); + } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal unsafe void ReadUnmanagedSafe(out NativeArray value, Allocator allocator) where T : unmanaged + { + ReadUnmanagedSafe(out int sizeInTs); + int sizeInBytes = sizeInTs * sizeof(T); + value = new NativeArray(sizeInTs, allocator); + byte* bytes = (byte*)value.GetUnsafePtr(); + ReadBytesSafe(bytes, sizeInBytes); + } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal unsafe void ReadUnmanagedInPlace(ref NativeList value) where T : unmanaged + { + ReadUnmanaged(out int sizeInTs); + int sizeInBytes = sizeInTs * sizeof(T); + value.Resize(sizeInTs, NativeArrayOptions.UninitializedMemory); + byte* bytes = (byte*)value.GetUnsafePtr(); + ReadBytes(bytes, sizeInBytes); + } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal unsafe void ReadUnmanagedSafeInPlace(ref NativeList value) where T : unmanaged + { + ReadUnmanagedSafe(out int sizeInTs); + int sizeInBytes = sizeInTs * sizeof(T); + value.Resize(sizeInTs, NativeArrayOptions.UninitializedMemory); + byte* bytes = (byte*)value.GetUnsafePtr(); + ReadBytesSafe(bytes, sizeInBytes); + } /// /// Read a NetworkSerializable value @@ -800,6 +870,19 @@ internal unsafe void ReadUnmanagedSafe(out T[] value) where T : unmanaged [MethodImpl(MethodImplOptions.AggressiveInlining)] public void ReadValueSafe(out T[] value, FastBufferWriter.ForNetworkSerializable unused = default) where T : INetworkSerializable, new() => ReadNetworkSerializable(out value); + /// + /// Read a NetworkSerializable NativeArray + /// + /// "Safe" version - automatically performs bounds checking. Less efficient than bounds checking + /// for multiple reads at once by calling TryBeginRead. + /// + /// The type being serialized + /// The values to read + /// The allocator to use to construct the resulting NativeArray + /// An unused parameter used for enabling overload resolution based on generic constraints + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void ReadValueSafe(out NativeArray value, Allocator allocator, FastBufferWriter.ForNetworkSerializable unused = default) where T : unmanaged, INetworkSerializable => ReadNetworkSerializable(out value, allocator); + /// /// Read a struct @@ -819,6 +902,70 @@ internal unsafe void ReadUnmanagedSafe(out T[] value) where T : unmanaged [MethodImpl(MethodImplOptions.AggressiveInlining)] public void ReadValue(out T[] value, FastBufferWriter.ForStructs unused = default) where T : unmanaged, INetworkSerializeByMemcpy => ReadUnmanaged(out value); + /// + /// Read a struct NativeArray + /// + /// The type being serialized + /// The values to read + /// The allocator to use to construct the resulting NativeArray + /// An unused parameter used for enabling overload resolution based on generic constraints + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void ReadValue(out NativeArray value, Allocator allocator, FastBufferWriter.ForGeneric unused = default) where T : unmanaged + { + if (typeof(INetworkSerializable).IsAssignableFrom(typeof(T))) + { + // This calls WriteNetworkSerializable in a way that doesn't require + // any boxing. + NetworkVariableSerialization>.Serializer.ReadWithAllocator(this, out value, allocator); + } + else + { + ReadUnmanaged(out value, allocator); + } + } + + /// + /// Read a struct NativeArray using a Temp allocator. Equivalent to ReadValue(out value, Allocator.Temp) + /// + /// The type being serialized + /// The values to read + /// An unused parameter used for enabling overload resolution based on generic constraints + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void ReadValueTemp(out NativeArray value, FastBufferWriter.ForGeneric unused = default) where T : unmanaged + { + if (typeof(INetworkSerializable).IsAssignableFrom(typeof(T))) + { + // This calls WriteNetworkSerializable in a way that doesn't require + // any boxing. + NetworkVariableSerialization>.Serializer.ReadWithAllocator(this, out value, Allocator.Temp); + } + else + { + ReadUnmanaged(out value, Allocator.Temp); + } + } + + /// + /// Read a struct NativeList + /// + /// The type being serialized + /// The values to read + /// An unused parameter used for enabling overload resolution based on generic constraints + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void ReadValueInPlace(ref NativeList value, FastBufferWriter.ForGeneric unused = default) where T : unmanaged + { + if (typeof(INetworkSerializable).IsAssignableFrom(typeof(T))) + { + // This calls WriteNetworkSerializable in a way that doesn't require + // any boxing. + NetworkVariableSerialization>.Serializer.Read(this, ref value); + } + else + { + ReadUnmanagedInPlace(ref value); + } + } + /// /// Read a struct /// @@ -843,6 +990,79 @@ internal unsafe void ReadUnmanagedSafe(out T[] value) where T : unmanaged [MethodImpl(MethodImplOptions.AggressiveInlining)] public void ReadValueSafe(out T[] value, FastBufferWriter.ForStructs unused = default) where T : unmanaged, INetworkSerializeByMemcpy => ReadUnmanagedSafe(out value); + /// + /// Read a struct NativeArray + /// + /// "Safe" version - automatically performs bounds checking. Less efficient than bounds checking + /// for multiple reads at once by calling TryBeginRead. + /// + /// The type being serialized + /// The values to read + /// The allocator to use to construct the resulting NativeArray + /// An unused parameter used for enabling overload resolution based on generic constraints + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void ReadValueSafe(out NativeArray value, Allocator allocator, FastBufferWriter.ForGeneric unused = default) where T : unmanaged + { + if (typeof(INetworkSerializable).IsAssignableFrom(typeof(T))) + { + // This calls WriteNetworkSerializable in a way that doesn't require + // any boxing. + NetworkVariableSerialization>.Serializer.ReadWithAllocator(this, out value, allocator); + } + else + { + ReadUnmanagedSafe(out value, allocator); + } + } + + /// + /// Read a struct NativeArray using a Temp allocator. Equivalent to ReadValueSafe(out value, Allocator.Temp) + /// + /// "Safe" version - automatically performs bounds checking. Less efficient than bounds checking + /// for multiple reads at once by calling TryBeginRead. + /// + /// The type being serialized + /// The values to read + /// An unused parameter used for enabling overload resolution based on generic constraints + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void ReadValueSafeTemp(out NativeArray value, FastBufferWriter.ForGeneric unused = default) where T : unmanaged + { + if (typeof(INetworkSerializable).IsAssignableFrom(typeof(T))) + { + // This calls WriteNetworkSerializable in a way that doesn't require + // any boxing. + NetworkVariableSerialization>.Serializer.ReadWithAllocator(this, out value, Allocator.Temp); + } + else + { + ReadUnmanagedSafe(out value, Allocator.Temp); + } + } + + /// + /// Read a struct NativeList + /// + /// "Safe" version - automatically performs bounds checking. Less efficient than bounds checking + /// for multiple reads at once by calling TryBeginRead. + /// + /// The type being serialized + /// The values to read + /// An unused parameter used for enabling overload resolution based on generic constraints + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void ReadValueSafeInPlace(ref NativeList value, FastBufferWriter.ForGeneric unused = default) where T : unmanaged + { + if (typeof(INetworkSerializable).IsAssignableFrom(typeof(T))) + { + // This calls WriteNetworkSerializable in a way that doesn't require + // any boxing. + NetworkVariableSerialization>.Serializer.Read(this, ref value); + } + else + { + ReadUnmanagedSafeInPlace(ref value); + } + } + /// /// Read a primitive value (int, bool, etc) /// Accepts any value that implements the given interfaces, but is not guaranteed to work correctly @@ -880,7 +1100,7 @@ internal unsafe void ReadUnmanagedSafe(out T[] value) where T : unmanaged public void ReadValueSafe(out T value, FastBufferWriter.ForPrimitives unused = default) where T : unmanaged, IComparable, IConvertible, IComparable, IEquatable => ReadUnmanagedSafe(out value); /// - /// Read a primitive value (int, bool, etc) + /// Read a primitive value (int, bool, etc) array /// Accepts any value that implements the given interfaces, but is not guaranteed to work correctly /// on values that are not primitives. /// @@ -936,6 +1156,7 @@ internal unsafe void ReadUnmanagedSafe(out T[] value) where T : unmanaged [MethodImpl(MethodImplOptions.AggressiveInlining)] public void ReadValueSafe(out T[] value, FastBufferWriter.ForEnums unused = default) where T : unmanaged, Enum => ReadUnmanagedSafe(out value); + /// /// Read a Vector2 /// @@ -1346,5 +1567,92 @@ public unsafe void ReadValueSafeInPlace(ref T value, FastBufferWriter.ForFixe value.Length = length; ReadBytesSafe(value.GetUnsafePtr(), length); } + + /// + /// Read a FixedString NativeArray. + /// + /// "Safe" version - automatically performs bounds checking. Less efficient than bounds checking + /// for multiple reads at once by calling TryBeginRead. + /// + /// the value to read + /// The allocator to use to construct the resulting NativeArray + /// An unused parameter used for enabling overload resolution based on generic constraints + /// The type being serialized + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public unsafe void ReadValueSafe(out NativeArray value, Allocator allocator) + where T : unmanaged, INativeList, IUTF8Bytes + { + ReadUnmanagedSafe(out int length); + value = new NativeArray(length, allocator); + var ptr = (T*)value.GetUnsafePtr(); + for (var i = 0; i < length; ++i) + { + ReadValueSafeInPlace(ref ptr[i]); + } + } + + /// + /// Read a FixedString NativeArray using a Temp allocator. Equivalent to ReadValueSafe(out value, Allocator.Temp) + /// + /// "Safe" version - automatically performs bounds checking. Less efficient than bounds checking + /// for multiple reads at once by calling TryBeginRead. + /// + /// the value to read + /// An unused parameter used for enabling overload resolution based on generic constraints + /// The type being serialized + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public unsafe void ReadValueSafeTemp(out NativeArray value) + where T : unmanaged, INativeList, IUTF8Bytes + { + ReadUnmanagedSafe(out int length); + value = new NativeArray(length, Allocator.Temp); + var ptr = (T*)value.GetUnsafePtr(); + for (var i = 0; i < length; ++i) + { + ReadValueSafeInPlace(ref ptr[i]); + } + } + + /// + /// Read a FixedString NativeArray using a Temp allocator. Equivalent to ReadValueSafe(out value, Allocator.Temp) + /// + /// "Safe" version - automatically performs bounds checking. Less efficient than bounds checking + /// for multiple reads at once by calling TryBeginRead. + /// + /// the value to read + /// An unused parameter used for enabling overload resolution based on generic constraints + /// The type being serialized + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void ReadValueSafe(out T[] value, FastBufferWriter.ForFixedStrings unused = default) + where T : unmanaged, INativeList, IUTF8Bytes + { + ReadUnmanagedSafe(out int length); + value = new T[length]; + for (var i = 0; i < length; ++i) + { + ReadValueSafeInPlace(ref value[i]); + } + } + + /// + /// Read a FixedString NativeList. + /// + /// "Safe" version - automatically performs bounds checking. Less efficient than bounds checking + /// for multiple reads at once by calling TryBeginRead. + /// + /// the value to read + /// An unused parameter used for enabling overload resolution based on generic constraints + /// The type being serialized + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void ReadValueSafeInPlace(ref NativeList value) + where T : unmanaged, INativeList, IUTF8Bytes + { + ReadUnmanagedSafe(out int length); + value.Resize(length, NativeArrayOptions.UninitializedMemory); + for (var i = 0; i < length; ++i) + { + ReadValueSafeInPlace(ref value.ElementAt(i)); + } + } } } diff --git a/com.unity.netcode.gameobjects/Runtime/Serialization/FastBufferWriter.cs b/com.unity.netcode.gameobjects/Runtime/Serialization/FastBufferWriter.cs index 6cb133f278..ef0ba881fd 100644 --- a/com.unity.netcode.gameobjects/Runtime/Serialization/FastBufferWriter.cs +++ b/com.unity.netcode.gameobjects/Runtime/Serialization/FastBufferWriter.cs @@ -452,6 +452,40 @@ public void WriteNetworkSerializable(T[] array, int count = -1, int offset = } } + /// + /// Write a NativeArray of INetworkSerializables + /// + /// The value to write + /// + /// + /// + public void WriteNetworkSerializable(NativeArray array, int count = -1, int offset = 0) where T : unmanaged, INetworkSerializable + { + int sizeInTs = count != -1 ? count : array.Length - offset; + WriteValueSafe(sizeInTs); + foreach (var item in array) + { + WriteNetworkSerializable(item); + } + } + + /// + /// Write a NativeList of INetworkSerializables + /// + /// The value to write + /// + /// + /// + public void WriteNetworkSerializable(NativeList array, int count = -1, int offset = 0) where T : unmanaged, INetworkSerializable + { + int sizeInTs = count != -1 ? count : array.Length - offset; + WriteValueSafe(sizeInTs); + foreach (var item in array) + { + WriteNetworkSerializable(item); + } + } + /// /// Writes a string /// @@ -536,6 +570,38 @@ public static unsafe int GetWriteSize(T[] array, int count = -1, int offset = return sizeof(int) + sizeInBytes; } + /// + /// Get the required size to write a NativeArray + /// + /// The array to write + /// The amount of elements to write + /// Where in the array to start + /// + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static unsafe int GetWriteSize(NativeArray array, int count = -1, int offset = 0) where T : unmanaged + { + int sizeInTs = count != -1 ? count : array.Length - offset; + int sizeInBytes = sizeInTs * sizeof(T); + return sizeof(int) + sizeInBytes; + } + + /// + /// Get the required size to write a NativeList + /// + /// The array to write + /// The amount of elements to write + /// Where in the array to start + /// + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static unsafe int GetWriteSize(NativeList array, int count = -1, int offset = 0) where T : unmanaged + { + int sizeInTs = count != -1 ? count : array.Length - offset; + int sizeInBytes = sizeInTs * sizeof(T); + return sizeof(int) + sizeInBytes; + } + /// /// Write a partial value. The specified number of bytes is written from the value and the rest is ignored. /// @@ -680,6 +746,32 @@ public unsafe void WriteBytes(byte[] value, int size = -1, int offset = 0) } } + /// + /// Write multiple bytes to the stream + /// + /// Value to write + /// Number of bytes to write + /// Offset into the buffer to begin writing + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public unsafe void WriteBytes(NativeArray value, int size = -1, int offset = 0) + { + byte* ptr = (byte*)value.GetUnsafePtr(); + WriteBytes(ptr, size == -1 ? value.Length : size, offset); + } + + /// + /// Write multiple bytes to the stream + /// + /// Value to write + /// Number of bytes to write + /// Offset into the buffer to begin writing + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public unsafe void WriteBytes(NativeList value, int size = -1, int offset = 0) + { + byte* ptr = (byte*)value.GetUnsafePtr(); + WriteBytes(ptr, size == -1 ? value.Length : size, offset); + } + /// /// Write multiple bytes to the stream /// @@ -698,6 +790,32 @@ public unsafe void WriteBytesSafe(byte[] value, int size = -1, int offset = 0) } } + /// + /// Write multiple bytes to the stream + /// + /// Value to write + /// Number of bytes to write + /// Offset into the buffer to begin writing + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public unsafe void WriteBytesSafe(NativeArray value, int size = -1, int offset = 0) + { + byte* ptr = (byte*)value.GetUnsafePtr(); + WriteBytesSafe(ptr, size == -1 ? value.Length : size, offset); + } + + /// + /// Write multiple bytes to the stream + /// + /// Value to write + /// Number of bytes to write + /// Offset into the buffer to begin writing + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public unsafe void WriteBytesSafe(NativeList value, int size = -1, int offset = 0) + { + byte* ptr = (byte*)value.GetUnsafePtr(); + WriteBytesSafe(ptr, size == -1 ? value.Length : size, offset); + } + /// /// Copy the contents of this writer into another writer. /// The contents will be copied from the beginning of this writer to its current position. @@ -749,6 +867,42 @@ public static int GetWriteSize(in T value) return value.Length + sizeof(int); } + /// + /// Get the write size for an array of FixedStrings + /// + /// + /// + /// + public static int GetWriteSize(in NativeArray value) + where T : unmanaged, INativeList, IUTF8Bytes + { + var size = sizeof(int); + foreach (var item in value) + { + size += sizeof(int) + item.Length; + } + + return size; + } + + /// + /// Get the write size for an array of FixedStrings + /// + /// + /// + /// + public static int GetWriteSize(in NativeList value) + where T : unmanaged, INativeList, IUTF8Bytes + { + var size = sizeof(int); + foreach (var item in value) + { + size += sizeof(int) + item.Length; + } + + return size; + } + /// /// Get the size required to write an unmanaged value of type T /// @@ -799,6 +953,48 @@ internal unsafe void WriteUnmanagedSafe(T[] value) where T : unmanaged } } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal unsafe void WriteUnmanaged(NativeArray value) where T : unmanaged + { + WriteUnmanaged(value.Length); + var ptr = (T*)value.GetUnsafePtr(); + { + byte* bytes = (byte*)ptr; + WriteBytes(bytes, sizeof(T) * value.Length); + } + } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal unsafe void WriteUnmanagedSafe(NativeArray value) where T : unmanaged + { + WriteUnmanagedSafe(value.Length); + var ptr = (T*)value.GetUnsafePtr(); + { + byte* bytes = (byte*)ptr; + WriteBytesSafe(bytes, sizeof(T) * value.Length); + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal unsafe void WriteUnmanaged(NativeList value) where T : unmanaged + { + WriteUnmanaged(value.Length); + var ptr = (T*)value.GetUnsafePtr(); + { + byte* bytes = (byte*)ptr; + WriteBytes(bytes, sizeof(T) * value.Length); + } + } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal unsafe void WriteUnmanagedSafe(NativeList value) where T : unmanaged + { + WriteUnmanagedSafe(value.Length); + var ptr = (T*)value.GetUnsafePtr(); + { + byte* bytes = (byte*)ptr; + WriteBytesSafe(bytes, sizeof(T) * value.Length); + } + } + /// /// This empty struct exists to allow overloading WriteValue based on generic constraints. /// At the bytecode level, constraints aren't included in the method signature, so if multiple @@ -869,6 +1065,20 @@ public struct ForFixedStrings } + /// + /// This empty struct exists to allow overloading WriteValue based on generic constraints. + /// At the bytecode level, constraints aren't included in the method signature, so if multiple + /// methods exist with the same signature, it causes a compile error because they would end up + /// being emitted as the same method, even if the constraints are different. + /// Adding an empty struct with a default value gives them different signatures in the bytecode, + /// which then allows the compiler to do overload resolution based on the generic constraints + /// without the user having to pass the struct in themselves. + /// + public struct ForGeneric + { + + } + /// /// Write a NetworkSerializable value /// @@ -929,6 +1139,48 @@ public struct ForFixedStrings [MethodImpl(MethodImplOptions.AggressiveInlining)] public void WriteValue(T[] value, ForStructs unused = default) where T : unmanaged, INetworkSerializeByMemcpy => WriteUnmanaged(value); + /// + /// Write a struct NativeArray + /// + /// The values to write + /// An unused parameter used for enabling overload resolution based on generic constraints + /// The type being serialized + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void WriteValue(NativeArray value, ForGeneric unused = default) where T : unmanaged + { + if (typeof(INetworkSerializable).IsAssignableFrom(typeof(T))) + { + // This calls WriteNetworkSerializable in a way that doesn't require + // any boxing. + NetworkVariableSerialization>.Serializer.Write(this, ref value); + } + else + { + WriteUnmanaged(value); + } + } + + /// + /// Write a struct NativeList + /// + /// The values to write + /// An unused parameter used for enabling overload resolution based on generic constraints + /// The type being serialized + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void WriteValue(NativeList value, ForGeneric unused = default) where T : unmanaged + { + if (typeof(INetworkSerializable).IsAssignableFrom(typeof(T))) + { + // This calls WriteNetworkSerializable in a way that doesn't require + // any boxing. + NetworkVariableSerialization>.Serializer.Write(this, ref value); + } + else + { + WriteUnmanaged(value); + } + } + /// /// Write a struct /// @@ -953,6 +1205,54 @@ public struct ForFixedStrings [MethodImpl(MethodImplOptions.AggressiveInlining)] public void WriteValueSafe(T[] value, ForStructs unused = default) where T : unmanaged, INetworkSerializeByMemcpy => WriteUnmanagedSafe(value); + /// + /// Write a struct NativeArray + /// + /// "Safe" version - automatically performs bounds checking. Less efficient than bounds checking + /// for multiple writes at once by calling TryBeginWrite. + /// + /// The values to write + /// An unused parameter used for enabling overload resolution based on generic constraints + /// The type being serialized + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void WriteValueSafe(NativeArray value, ForGeneric unused = default) where T : unmanaged + { + if (typeof(INetworkSerializable).IsAssignableFrom(typeof(T))) + { + // This calls WriteNetworkSerializable in a way that doesn't require + // any boxing. + NetworkVariableSerialization>.Serializer.Write(this, ref value); + } + else + { + WriteUnmanagedSafe(value); + } + } + + /// + /// Write a struct NativeList + /// + /// "Safe" version - automatically performs bounds checking. Less efficient than bounds checking + /// for multiple writes at once by calling TryBeginWrite. + /// + /// The values to write + /// An unused parameter used for enabling overload resolution based on generic constraints + /// The type being serialized + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void WriteValueSafe(NativeList value, ForGeneric unused = default) where T : unmanaged + { + if (typeof(INetworkSerializable).IsAssignableFrom(typeof(T))) + { + // This calls WriteNetworkSerializable in a way that doesn't require + // any boxing. + NetworkVariableSerialization>.Serializer.Write(this, ref value); + } + else + { + WriteUnmanagedSafe(value); + } + } + /// /// Write a primitive value (int, bool, etc) /// Accepts any value that implements the given interfaces, but is not guaranteed to work correctly @@ -1185,7 +1485,6 @@ public struct ForFixedStrings [MethodImpl(MethodImplOptions.AggressiveInlining)] public void WriteValue(Ray2D[] value) => WriteUnmanaged(value); - /// /// Write a Vector2 /// @@ -1415,6 +1714,63 @@ public unsafe void WriteValue(in T value, ForFixedStrings unused = default) } } + /// + /// Write an array of FixedString values. Writes only the part of each string that's actually used. + /// When calling TryBeginWrite, ensure you calculate the write size correctly (preferably by calling + /// FastBufferWriter.GetWriteSize()) + /// + /// the value to write + /// An unused parameter used for enabling overload resolution based on generic constraints + /// The type being serialized + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void WriteValue(T[] value, ForFixedStrings unused = default) + where T : unmanaged, INativeList, IUTF8Bytes + { + WriteUnmanaged(value.Length); + foreach (var str in value) + { + WriteValue(str); + } + } + + /// + /// Write a NativeArray of FixedString values. Writes only the part of each string that's actually used. + /// When calling TryBeginWrite, ensure you calculate the write size correctly (preferably by calling + /// FastBufferWriter.GetWriteSize()) + /// + /// the value to write + /// An unused parameter used for enabling overload resolution based on generic constraints + /// The type being serialized + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void WriteValue(in NativeArray value, ForFixedStrings unused = default) + where T : unmanaged, INativeList, IUTF8Bytes + { + WriteUnmanaged(value.Length); + foreach (var str in value) + { + WriteValue(str); + } + } + + /// + /// Write a NativeList of FixedString values. Writes only the part of each string that's actually used. + /// When calling TryBeginWrite, ensure you calculate the write size correctly (preferably by calling + /// FastBufferWriter.GetWriteSize()) + /// + /// the value to write + /// An unused parameter used for enabling overload resolution based on generic constraints + /// The type being serialized + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void WriteValue(in NativeList value, ForFixedStrings unused = default) + where T : unmanaged, INativeList, IUTF8Bytes + { + WriteUnmanaged(value.Length); + foreach (var str in value) + { + WriteValue(str); + } + } + /// /// Write a FixedString value. Writes only the part of the string that's actually used. @@ -1435,5 +1791,74 @@ public void WriteValueSafe(in T value, ForFixedStrings unused = default) } WriteValue(value); } + + /// + /// Write a NativeArray of FixedString values. Writes only the part of each string that's actually used. + /// "Safe" version - automatically performs bounds checking. Less efficient than bounds checking + /// for multiple writes at once by calling TryBeginWrite. + /// + /// the value to write + /// An unused parameter used for enabling overload resolution based on generic constraints + /// The type being serialized + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void WriteValueSafe(T[] value, ForFixedStrings unused = default) + where T : unmanaged, INativeList, IUTF8Bytes + { + if (!TryBeginWriteInternal(GetWriteSize(value))) + { + throw new OverflowException("Writing past the end of the buffer"); + } + WriteUnmanaged(value.Length); + foreach (var str in value) + { + WriteValue(str); + } + } + + /// + /// Write a NativeArray of FixedString values. Writes only the part of each string that's actually used. + /// "Safe" version - automatically performs bounds checking. Less efficient than bounds checking + /// for multiple writes at once by calling TryBeginWrite. + /// + /// the value to write + /// An unused parameter used for enabling overload resolution based on generic constraints + /// The type being serialized + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void WriteValueSafe(in NativeArray value) + where T : unmanaged, INativeList, IUTF8Bytes + { + if (!TryBeginWriteInternal(GetWriteSize(value))) + { + throw new OverflowException("Writing past the end of the buffer"); + } + WriteUnmanaged(value.Length); + foreach (var str in value) + { + WriteValue(str); + } + } + + /// + /// Write a NativeList of FixedString values. Writes only the part of each string that's actually used. + /// "Safe" version - automatically performs bounds checking. Less efficient than bounds checking + /// for multiple writes at once by calling TryBeginWrite. + /// + /// the value to write + /// An unused parameter used for enabling overload resolution based on generic constraints + /// The type being serialized + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void WriteValueSafe(in NativeList value) + where T : unmanaged, INativeList, IUTF8Bytes + { + if (!TryBeginWriteInternal(GetWriteSize(value))) + { + throw new OverflowException("Writing past the end of the buffer"); + } + WriteUnmanaged(value.Length); + foreach (var str in value) + { + WriteValue(str); + } + } } } diff --git a/com.unity.netcode.gameobjects/Runtime/Serialization/IReaderWriter.cs b/com.unity.netcode.gameobjects/Runtime/Serialization/IReaderWriter.cs index b469a6143f..9e89c25b89 100644 --- a/com.unity.netcode.gameobjects/Runtime/Serialization/IReaderWriter.cs +++ b/com.unity.netcode.gameobjects/Runtime/Serialization/IReaderWriter.cs @@ -96,6 +96,23 @@ public interface IReaderWriter /// The type being serialized void SerializeValue(ref T[] value, FastBufferWriter.ForStructs unused = default) where T : unmanaged, INetworkSerializeByMemcpy; + /// + /// Read or write a NativeArray of struct values implementing ISerializeByMemcpy + /// + /// The values to read/write + /// The allocator to use to construct the resulting NativeArray when reading + /// An unused parameter used for enabling overload resolution based on generic constraints + /// The type being serialized + void SerializeValue(ref NativeArray value, Allocator allocator, FastBufferWriter.ForGeneric unused = default) where T : unmanaged; + + /// + /// Read or write a NativeList of struct values implementing ISerializeByMemcpy + /// + /// The values to read/write + /// An unused parameter used for enabling overload resolution based on generic constraints + /// The type being serialized + void SerializeValue(ref NativeList value, FastBufferWriter.ForGeneric unused = default) where T : unmanaged; + /// /// Read or write a struct or class value implementing INetworkSerializable /// @@ -121,6 +138,25 @@ public interface IReaderWriter void SerializeValue(ref T value, FastBufferWriter.ForFixedStrings unused = default) where T : unmanaged, INativeList, IUTF8Bytes; + /// + /// Read or write NativeArray of FixedString values + /// + /// The value to read/write + /// The allocator to use to construct the resulting NativeArray when reading + /// An unused parameter used for enabling overload resolution based on generic constraints + /// The type being serialized + void SerializeValue(ref NativeArray value, Allocator allocator) + where T : unmanaged, INativeList, IUTF8Bytes; + + /// + /// Read or write a NativeList of FixedString values + /// + /// The value to read/write + /// An unused parameter used for enabling overload resolution based on generic constraints + /// The type being serialized + void SerializeValue(ref NativeList value) + where T : unmanaged, INativeList, IUTF8Bytes; + /// /// Read or write a Vector2 value /// @@ -344,6 +380,29 @@ void SerializeValue(ref T value, FastBufferWriter.ForFixedStrings unused = de /// An unused parameter that can be used for enabling overload resolution based on generic constraints void SerializeValuePreChecked(ref T[] value, FastBufferWriter.ForStructs unused = default) where T : unmanaged, INetworkSerializeByMemcpy; + /// + /// Serialize a NativeArray of structs, "pre-checked", which skips buffer checks. + /// In debug and editor builds, a check is made to ensure you've called "PreCheck" before + /// calling this. In release builds, calling this without calling "PreCheck" may read or write + /// past the end of the buffer, which will cause memory corruption and undefined behavior. + /// + /// The type being serialized + /// The values to read/write + /// The allocator to use to construct the resulting NativeArray when reading + /// An unused parameter that can be used for enabling overload resolution based on generic constraints + void SerializeValuePreChecked(ref NativeArray value, Allocator allocator, FastBufferWriter.ForGeneric unused = default) where T : unmanaged; + + /// + /// Serialize a NativeList of structs, "pre-checked", which skips buffer checks. + /// In debug and editor builds, a check is made to ensure you've called "PreCheck" before + /// calling this. In release builds, calling this without calling "PreCheck" may read or write + /// past the end of the buffer, which will cause memory corruption and undefined behavior. + /// + /// The type being serialized + /// The values to read/write + /// An unused parameter that can be used for enabling overload resolution based on generic constraints + void SerializeValuePreChecked(ref NativeList value, FastBufferWriter.ForGeneric unused = default) where T : unmanaged; + /// /// Serialize a FixedString, "pre-checked", which skips buffer checks. /// In debug and editor builds, a check is made to ensure you've called "PreCheck" before @@ -438,7 +497,7 @@ void SerializeValuePreChecked(ref T value, FastBufferWriter.ForFixedStrings u void SerializeValuePreChecked(ref Vector4 value); /// - /// Serialize a Vector4Array, "pre-checked", which skips buffer checks. + /// Serialize a Vector4 array, "pre-checked", which skips buffer checks. /// In debug and editor builds, a check is made to ensure you've called "PreCheck" before /// calling this. In release builds, calling this without calling "PreCheck" may read or write /// past the end of the buffer, which will cause memory corruption and undefined behavior. diff --git a/com.unity.netcode.gameobjects/Tests/Editor/Serialization/BaseFastBufferReaderWriterTest.cs b/com.unity.netcode.gameobjects/Tests/Editor/Serialization/BaseFastBufferReaderWriterTest.cs index 0f0229b197..bd62cbadf1 100644 --- a/com.unity.netcode.gameobjects/Tests/Editor/Serialization/BaseFastBufferReaderWriterTest.cs +++ b/com.unity.netcode.gameobjects/Tests/Editor/Serialization/BaseFastBufferReaderWriterTest.cs @@ -1,5 +1,6 @@ using System; using NUnit.Framework; +using Unity.Collections; using UnityEngine; using Random = System.Random; @@ -11,50 +12,50 @@ protected enum ByteEnum : byte { A, B, - C - }; + C = byte.MaxValue + } protected enum SByteEnum : sbyte { A, B, - C - }; + C = sbyte.MaxValue + } protected enum ShortEnum : short { A, B, - C - }; + C = short.MaxValue + } protected enum UShortEnum : ushort { A, B, - C - }; + C = ushort.MaxValue + } protected enum IntEnum : int { A, B, - C - }; + C = int.MaxValue + } protected enum UIntEnum : uint { A, B, - C - }; + C = uint.MaxValue + } protected enum LongEnum : long { A, B, - C - }; + C = long.MaxValue + } protected enum ULongEnum : ulong { A, B, - C - }; + C = ulong.MaxValue + } protected struct TestStruct : INetworkSerializeByMemcpy { @@ -85,23 +86,30 @@ public enum WriteType protected abstract void RunTypeArrayTestSafe(T[] valueToTest) where T : unmanaged; + protected abstract void RunTypeNativeArrayTest(NativeArray valueToTest) where T : unmanaged; + + protected abstract void RunTypeNativeArrayTestSafe(NativeArray valueToTest) where T : unmanaged; + + protected abstract void RunTypeNativeListTest(NativeList valueToTest) where T : unmanaged; + + protected abstract void RunTypeNativeListTestSafe(NativeList valueToTest) where T : unmanaged; + + private Random m_Random = new Random(); protected TestStruct GetTestStruct() { - var random = new Random(); - var testStruct = new TestStruct { - A = (byte)random.Next(), - B = (short)random.Next(), - C = (ushort)random.Next(), - D = random.Next(), - E = (uint)random.Next(), - F = ((long)random.Next() << 32) + random.Next(), - G = ((ulong)random.Next() << 32) + (ulong)random.Next(), + A = (byte)m_Random.Next(), + B = (short)m_Random.Next(), + C = (ushort)m_Random.Next(), + D = m_Random.Next(), + E = (uint)m_Random.Next(), + F = ((long)m_Random.Next() << 32) + m_Random.Next(), + G = ((ulong)m_Random.Next() << 32) + (ulong)m_Random.Next(), H = true, I = '\u263a', - J = (float)random.NextDouble(), - K = random.NextDouble(), + J = (float)m_Random.NextDouble(), + K = m_Random.NextDouble(), }; return testStruct; @@ -600,5 +608,693 @@ void RunTypeTestLocal(T[] val, WriteType wt) where T : unmanaged Assert.Fail("No type handler was provided for this type in the test!"); } } + + public void BaseNativeArrayTypeTest(Type testType, WriteType writeType) + { + var random = new Random(); + void RunTypeTestLocal(NativeArray val, WriteType wt) where T : unmanaged + { + switch (wt) + { + case WriteType.WriteDirect: + RunTypeNativeArrayTest(val); + break; + case WriteType.WriteSafe: + RunTypeNativeArrayTestSafe(val); + break; + } + } + + if (testType == typeof(byte)) + { + RunTypeTestLocal(new NativeArray(new[]{ + (byte) random.Next(), + (byte) random.Next(), + (byte) random.Next(), + (byte) random.Next(), + (byte) random.Next(), + (byte) random.Next(), + (byte) random.Next() + }, Allocator.Temp), writeType); + } + else if (testType == typeof(sbyte)) + { + RunTypeTestLocal(new NativeArray(new[]{ + (sbyte) random.Next(), + (sbyte) random.Next(), + (sbyte) random.Next(), + (sbyte) random.Next(), + (sbyte) random.Next(), + (sbyte) random.Next(), + (sbyte) random.Next() + }, Allocator.Temp), writeType); + } + else if (testType == typeof(short)) + { + RunTypeTestLocal(new NativeArray(new[]{ + (short) random.Next(), + (short) random.Next(), + (short) random.Next(), + (short) random.Next(), + (short) random.Next() + }, Allocator.Temp), writeType); + } + else if (testType == typeof(ushort)) + { + RunTypeTestLocal(new NativeArray(new[]{ + (ushort) random.Next(), + (ushort) random.Next(), + (ushort) random.Next(), + (ushort) random.Next(), + (ushort) random.Next(), + (ushort) random.Next() + }, Allocator.Temp), writeType); + } + else if (testType == typeof(int)) + { + RunTypeTestLocal(new NativeArray(new[]{ + random.Next(), + random.Next(), + random.Next(), + random.Next(), + random.Next(), + random.Next(), + random.Next(), + random.Next() + }, Allocator.Temp), writeType); + } + else if (testType == typeof(uint)) + { + RunTypeTestLocal(new NativeArray(new[]{ + (uint) random.Next(), + (uint) random.Next(), + (uint) random.Next(), + (uint) random.Next(), + (uint) random.Next(), + (uint) random.Next() + }, Allocator.Temp), writeType); + } + else if (testType == typeof(long)) + { + RunTypeTestLocal(new NativeArray(new[]{ + ((long)random.Next() << 32) + random.Next(), + ((long)random.Next() << 32) + random.Next(), + ((long)random.Next() << 32) + random.Next(), + ((long)random.Next() << 32) + random.Next() + }, Allocator.Temp), writeType); + } + else if (testType == typeof(ulong)) + { + RunTypeTestLocal(new NativeArray(new[]{ + ((ulong)random.Next() << 32) + (ulong)random.Next(), + ((ulong)random.Next() << 32) + (ulong)random.Next(), + ((ulong)random.Next() << 32) + (ulong)random.Next(), + ((ulong)random.Next() << 32) + (ulong)random.Next(), + ((ulong)random.Next() << 32) + (ulong)random.Next(), + ((ulong)random.Next() << 32) + (ulong)random.Next(), + ((ulong)random.Next() << 32) + (ulong)random.Next(), + ((ulong)random.Next() << 32) + (ulong)random.Next(), + ((ulong)random.Next() << 32) + (ulong)random.Next() + }, Allocator.Temp), writeType); + } + else if (testType == typeof(bool)) + { + RunTypeTestLocal(new NativeArray(new[]{ + true, + false, + true, + true, + false, + false, + true, + false, + true + }, Allocator.Temp), writeType); + } + else if (testType == typeof(char)) + { + RunTypeTestLocal(new NativeArray(new[]{ + 'a', + '\u263a' + }, Allocator.Temp), writeType); + } + else if (testType == typeof(float)) + { + RunTypeTestLocal(new NativeArray(new[]{ + (float)random.NextDouble(), + (float)random.NextDouble(), + (float)random.NextDouble(), + (float)random.NextDouble(), + (float)random.NextDouble(), + (float)random.NextDouble(), + (float)random.NextDouble(), + (float)random.NextDouble(), + (float)random.NextDouble() + }, Allocator.Temp), writeType); + } + else if (testType == typeof(double)) + { + RunTypeTestLocal(new NativeArray(new[]{ + random.NextDouble(), + random.NextDouble(), + random.NextDouble(), + random.NextDouble(), + random.NextDouble(), + random.NextDouble(), + random.NextDouble(), + random.NextDouble(), + random.NextDouble() + }, Allocator.Temp), writeType); + } + else if (testType == typeof(ByteEnum)) + { + RunTypeTestLocal(new NativeArray(new[]{ + ByteEnum.C, + ByteEnum.A, + ByteEnum.B + }, Allocator.Temp), writeType); + } + else if (testType == typeof(SByteEnum)) + { + RunTypeTestLocal(new NativeArray(new[]{ + SByteEnum.C, + SByteEnum.A, + SByteEnum.B + }, Allocator.Temp), writeType); + } + else if (testType == typeof(ShortEnum)) + { + RunTypeTestLocal(new NativeArray(new[]{ + ShortEnum.C, + ShortEnum.A, + ShortEnum.B + }, Allocator.Temp), writeType); + } + else if (testType == typeof(UShortEnum)) + { + RunTypeTestLocal(new NativeArray(new[]{ + UShortEnum.C, + UShortEnum.A, + UShortEnum.B + }, Allocator.Temp), writeType); + } + else if (testType == typeof(IntEnum)) + { + RunTypeTestLocal(new NativeArray(new[]{ + IntEnum.C, + IntEnum.A, + IntEnum.B + }, Allocator.Temp), writeType); + } + else if (testType == typeof(UIntEnum)) + { + RunTypeTestLocal(new NativeArray(new[]{ + UIntEnum.C, + UIntEnum.A, + UIntEnum.B + }, Allocator.Temp), writeType); + } + else if (testType == typeof(LongEnum)) + { + RunTypeTestLocal(new NativeArray(new[]{ + LongEnum.C, + LongEnum.A, + LongEnum.B + }, Allocator.Temp), writeType); + } + else if (testType == typeof(ULongEnum)) + { + RunTypeTestLocal(new NativeArray(new[]{ + ULongEnum.C, + ULongEnum.A, + ULongEnum.B + }, Allocator.Temp), writeType); + } + else if (testType == typeof(Vector2)) + { + RunTypeTestLocal(new NativeArray(new[]{ + new Vector2((float) random.NextDouble(), (float) random.NextDouble()), + new Vector2((float) random.NextDouble(), (float) random.NextDouble()), + new Vector2((float) random.NextDouble(), (float) random.NextDouble()), + }, Allocator.Temp), writeType); + } + else if (testType == typeof(Vector3)) + { + RunTypeTestLocal(new NativeArray(new[]{ + new Vector3((float) random.NextDouble(), (float) random.NextDouble(), (float) random.NextDouble()), + new Vector3((float) random.NextDouble(), (float) random.NextDouble(), (float) random.NextDouble()), + new Vector3((float) random.NextDouble(), (float) random.NextDouble(), (float) random.NextDouble()), + }, Allocator.Temp), writeType); + } + else if (testType == typeof(Vector2Int)) + { + RunTypeTestLocal(new NativeArray(new[]{ + new Vector2Int((int) random.NextDouble(), (int) random.NextDouble()), + new Vector2Int((int) random.NextDouble(), (int) random.NextDouble()), + new Vector2Int((int) random.NextDouble(), (int) random.NextDouble()), + }, Allocator.Temp), writeType); + } + else if (testType == typeof(Vector3Int)) + { + RunTypeTestLocal(new NativeArray(new[]{ + new Vector3Int((int) random.NextDouble(), (int) random.NextDouble(), (int) random.NextDouble()), + new Vector3Int((int) random.NextDouble(), (int) random.NextDouble(), (int) random.NextDouble()), + new Vector3Int((int) random.NextDouble(), (int) random.NextDouble(), (int) random.NextDouble()), + }, Allocator.Temp), writeType); + } + else if (testType == typeof(Vector4)) + { + RunTypeTestLocal(new NativeArray(new[]{ + new Vector4((float) random.NextDouble(), (float) random.NextDouble(), (float) random.NextDouble(), + (float) random.NextDouble()), + new Vector4((float) random.NextDouble(), (float) random.NextDouble(), (float) random.NextDouble(), + (float) random.NextDouble()), + new Vector4((float) random.NextDouble(), (float) random.NextDouble(), (float) random.NextDouble(), + (float) random.NextDouble()), + }, Allocator.Temp), writeType); + } + else if (testType == typeof(Quaternion)) + { + RunTypeTestLocal(new NativeArray(new[]{ + new Quaternion((float) random.NextDouble(), (float) random.NextDouble(), + (float) random.NextDouble(), (float) random.NextDouble()), + new Quaternion((float) random.NextDouble(), (float) random.NextDouble(), + (float) random.NextDouble(), (float) random.NextDouble()), + new Quaternion((float) random.NextDouble(), (float) random.NextDouble(), + (float) random.NextDouble(), (float) random.NextDouble()), + }, Allocator.Temp), writeType); + } + else if (testType == typeof(Color)) + { + RunTypeTestLocal(new NativeArray(new[]{ + new Color((float) random.NextDouble(), (float) random.NextDouble(), (float) random.NextDouble(), + (float) random.NextDouble()), + new Color((float) random.NextDouble(), (float) random.NextDouble(), (float) random.NextDouble(), + (float) random.NextDouble()), + new Color((float) random.NextDouble(), (float) random.NextDouble(), (float) random.NextDouble(), + (float) random.NextDouble()), + }, Allocator.Temp), writeType); + } + else if (testType == typeof(Color32)) + { + RunTypeTestLocal(new NativeArray(new[]{ + new Color32((byte) random.Next(), (byte) random.Next(), (byte) random.Next(), (byte) random.Next()), + new Color32((byte) random.Next(), (byte) random.Next(), (byte) random.Next(), (byte) random.Next()), + new Color32((byte) random.Next(), (byte) random.Next(), (byte) random.Next(), (byte) random.Next()), + }, Allocator.Temp), writeType); + } + else if (testType == typeof(Ray)) + { + RunTypeTestLocal(new NativeArray(new[]{ + new Ray( + new Vector3((float) random.NextDouble(), (float) random.NextDouble(), + (float) random.NextDouble()), + new Vector3((float) random.NextDouble(), (float) random.NextDouble(), + (float) random.NextDouble())), + new Ray( + new Vector3((float) random.NextDouble(), (float) random.NextDouble(), + (float) random.NextDouble()), + new Vector3((float) random.NextDouble(), (float) random.NextDouble(), + (float) random.NextDouble())), + new Ray( + new Vector3((float) random.NextDouble(), (float) random.NextDouble(), + (float) random.NextDouble()), + new Vector3((float) random.NextDouble(), (float) random.NextDouble(), + (float) random.NextDouble())), + }, Allocator.Temp), writeType); + } + else if (testType == typeof(Ray2D)) + { + RunTypeTestLocal(new NativeArray(new[]{ + new Ray2D( + new Vector2((float) random.NextDouble(), (float) random.NextDouble()), + new Vector2((float) random.NextDouble(), (float) random.NextDouble())), + new Ray2D( + new Vector2((float) random.NextDouble(), (float) random.NextDouble()), + new Vector2((float) random.NextDouble(), (float) random.NextDouble())), + new Ray2D( + new Vector2((float) random.NextDouble(), (float) random.NextDouble()), + new Vector2((float) random.NextDouble(), (float) random.NextDouble())), + }, Allocator.Temp), writeType); + } + else if (testType == typeof(TestStruct)) + { + RunTypeTestLocal(new NativeArray(new[] { + GetTestStruct(), + GetTestStruct(), + GetTestStruct(), + }, Allocator.Temp), writeType); + } + else + { + Assert.Fail("No type handler was provided for this type in the test!"); + } + } + public void BaseNativeListTypeTest(Type testType, WriteType writeType) + { + var random = new Random(); + void RunTypeTestLocal(NativeArray val, WriteType wt) where T : unmanaged + { + var lst = new NativeList(val.Length, Allocator.Temp); + foreach (var item in val) + { + lst.Add(item); + } + switch (wt) + { + case WriteType.WriteDirect: + RunTypeNativeListTest(lst); + break; + case WriteType.WriteSafe: + RunTypeNativeListTestSafe(lst); + break; + } + } + + if (testType == typeof(byte)) + { + RunTypeTestLocal(new NativeArray(new[]{ + (byte) random.Next(), + (byte) random.Next(), + (byte) random.Next(), + (byte) random.Next(), + (byte) random.Next(), + (byte) random.Next(), + (byte) random.Next() + }, Allocator.Temp), writeType); + } + else if (testType == typeof(sbyte)) + { + RunTypeTestLocal(new NativeArray(new[]{ + (sbyte) random.Next(), + (sbyte) random.Next(), + (sbyte) random.Next(), + (sbyte) random.Next(), + (sbyte) random.Next(), + (sbyte) random.Next(), + (sbyte) random.Next() + }, Allocator.Temp), writeType); + } + else if (testType == typeof(short)) + { + RunTypeTestLocal(new NativeArray(new[]{ + (short) random.Next(), + (short) random.Next(), + (short) random.Next(), + (short) random.Next(), + (short) random.Next() + }, Allocator.Temp), writeType); + } + else if (testType == typeof(ushort)) + { + RunTypeTestLocal(new NativeArray(new[]{ + (ushort) random.Next(), + (ushort) random.Next(), + (ushort) random.Next(), + (ushort) random.Next(), + (ushort) random.Next(), + (ushort) random.Next() + }, Allocator.Temp), writeType); + } + else if (testType == typeof(int)) + { + RunTypeTestLocal(new NativeArray(new[]{ + random.Next(), + random.Next(), + random.Next(), + random.Next(), + random.Next(), + random.Next(), + random.Next(), + random.Next() + }, Allocator.Temp), writeType); + } + else if (testType == typeof(uint)) + { + RunTypeTestLocal(new NativeArray(new[]{ + (uint) random.Next(), + (uint) random.Next(), + (uint) random.Next(), + (uint) random.Next(), + (uint) random.Next(), + (uint) random.Next() + }, Allocator.Temp), writeType); + } + else if (testType == typeof(long)) + { + RunTypeTestLocal(new NativeArray(new[]{ + ((long)random.Next() << 32) + random.Next(), + ((long)random.Next() << 32) + random.Next(), + ((long)random.Next() << 32) + random.Next(), + ((long)random.Next() << 32) + random.Next() + }, Allocator.Temp), writeType); + } + else if (testType == typeof(ulong)) + { + RunTypeTestLocal(new NativeArray(new[]{ + ((ulong)random.Next() << 32) + (ulong)random.Next(), + ((ulong)random.Next() << 32) + (ulong)random.Next(), + ((ulong)random.Next() << 32) + (ulong)random.Next(), + ((ulong)random.Next() << 32) + (ulong)random.Next(), + ((ulong)random.Next() << 32) + (ulong)random.Next(), + ((ulong)random.Next() << 32) + (ulong)random.Next(), + ((ulong)random.Next() << 32) + (ulong)random.Next(), + ((ulong)random.Next() << 32) + (ulong)random.Next(), + ((ulong)random.Next() << 32) + (ulong)random.Next() + }, Allocator.Temp), writeType); + } + else if (testType == typeof(bool)) + { + RunTypeTestLocal(new NativeArray(new[]{ + true, + false, + true, + true, + false, + false, + true, + false, + true + }, Allocator.Temp), writeType); + } + else if (testType == typeof(char)) + { + RunTypeTestLocal(new NativeArray(new[]{ + 'a', + '\u263a' + }, Allocator.Temp), writeType); + } + else if (testType == typeof(float)) + { + RunTypeTestLocal(new NativeArray(new[]{ + (float)random.NextDouble(), + (float)random.NextDouble(), + (float)random.NextDouble(), + (float)random.NextDouble(), + (float)random.NextDouble(), + (float)random.NextDouble(), + (float)random.NextDouble(), + (float)random.NextDouble(), + (float)random.NextDouble() + }, Allocator.Temp), writeType); + } + else if (testType == typeof(double)) + { + RunTypeTestLocal(new NativeArray(new[]{ + random.NextDouble(), + random.NextDouble(), + random.NextDouble(), + random.NextDouble(), + random.NextDouble(), + random.NextDouble(), + random.NextDouble(), + random.NextDouble(), + random.NextDouble() + }, Allocator.Temp), writeType); + } + else if (testType == typeof(ByteEnum)) + { + RunTypeTestLocal(new NativeArray(new[]{ + ByteEnum.C, + ByteEnum.A, + ByteEnum.B + }, Allocator.Temp), writeType); + } + else if (testType == typeof(SByteEnum)) + { + RunTypeTestLocal(new NativeArray(new[]{ + SByteEnum.C, + SByteEnum.A, + SByteEnum.B + }, Allocator.Temp), writeType); + } + else if (testType == typeof(ShortEnum)) + { + RunTypeTestLocal(new NativeArray(new[]{ + ShortEnum.C, + ShortEnum.A, + ShortEnum.B + }, Allocator.Temp), writeType); + } + else if (testType == typeof(UShortEnum)) + { + RunTypeTestLocal(new NativeArray(new[]{ + UShortEnum.C, + UShortEnum.A, + UShortEnum.B + }, Allocator.Temp), writeType); + } + else if (testType == typeof(IntEnum)) + { + RunTypeTestLocal(new NativeArray(new[]{ + IntEnum.C, + IntEnum.A, + IntEnum.B + }, Allocator.Temp), writeType); + } + else if (testType == typeof(UIntEnum)) + { + RunTypeTestLocal(new NativeArray(new[]{ + UIntEnum.C, + UIntEnum.A, + UIntEnum.B + }, Allocator.Temp), writeType); + } + else if (testType == typeof(LongEnum)) + { + RunTypeTestLocal(new NativeArray(new[]{ + LongEnum.C, + LongEnum.A, + LongEnum.B + }, Allocator.Temp), writeType); + } + else if (testType == typeof(ULongEnum)) + { + RunTypeTestLocal(new NativeArray(new[]{ + ULongEnum.C, + ULongEnum.A, + ULongEnum.B + }, Allocator.Temp), writeType); + } + else if (testType == typeof(Vector2)) + { + RunTypeTestLocal(new NativeArray(new[]{ + new Vector2((float) random.NextDouble(), (float) random.NextDouble()), + new Vector2((float) random.NextDouble(), (float) random.NextDouble()), + new Vector2((float) random.NextDouble(), (float) random.NextDouble()), + }, Allocator.Temp), writeType); + } + else if (testType == typeof(Vector3)) + { + RunTypeTestLocal(new NativeArray(new[]{ + new Vector3((float) random.NextDouble(), (float) random.NextDouble(), (float) random.NextDouble()), + new Vector3((float) random.NextDouble(), (float) random.NextDouble(), (float) random.NextDouble()), + new Vector3((float) random.NextDouble(), (float) random.NextDouble(), (float) random.NextDouble()), + }, Allocator.Temp), writeType); + } + else if (testType == typeof(Vector2Int)) + { + RunTypeTestLocal(new NativeArray(new[]{ + new Vector2Int((int) random.NextDouble(), (int) random.NextDouble()), + new Vector2Int((int) random.NextDouble(), (int) random.NextDouble()), + new Vector2Int((int) random.NextDouble(), (int) random.NextDouble()), + }, Allocator.Temp), writeType); + } + else if (testType == typeof(Vector3Int)) + { + RunTypeTestLocal(new NativeArray(new[]{ + new Vector3Int((int) random.NextDouble(), (int) random.NextDouble(), (int) random.NextDouble()), + new Vector3Int((int) random.NextDouble(), (int) random.NextDouble(), (int) random.NextDouble()), + new Vector3Int((int) random.NextDouble(), (int) random.NextDouble(), (int) random.NextDouble()), + }, Allocator.Temp), writeType); + } + else if (testType == typeof(Vector4)) + { + RunTypeTestLocal(new NativeArray(new[]{ + new Vector4((float) random.NextDouble(), (float) random.NextDouble(), (float) random.NextDouble(), + (float) random.NextDouble()), + new Vector4((float) random.NextDouble(), (float) random.NextDouble(), (float) random.NextDouble(), + (float) random.NextDouble()), + new Vector4((float) random.NextDouble(), (float) random.NextDouble(), (float) random.NextDouble(), + (float) random.NextDouble()), + }, Allocator.Temp), writeType); + } + else if (testType == typeof(Quaternion)) + { + RunTypeTestLocal(new NativeArray(new[]{ + new Quaternion((float) random.NextDouble(), (float) random.NextDouble(), + (float) random.NextDouble(), (float) random.NextDouble()), + new Quaternion((float) random.NextDouble(), (float) random.NextDouble(), + (float) random.NextDouble(), (float) random.NextDouble()), + new Quaternion((float) random.NextDouble(), (float) random.NextDouble(), + (float) random.NextDouble(), (float) random.NextDouble()), + }, Allocator.Temp), writeType); + } + else if (testType == typeof(Color)) + { + RunTypeTestLocal(new NativeArray(new[]{ + new Color((float) random.NextDouble(), (float) random.NextDouble(), (float) random.NextDouble(), + (float) random.NextDouble()), + new Color((float) random.NextDouble(), (float) random.NextDouble(), (float) random.NextDouble(), + (float) random.NextDouble()), + new Color((float) random.NextDouble(), (float) random.NextDouble(), (float) random.NextDouble(), + (float) random.NextDouble()), + }, Allocator.Temp), writeType); + } + else if (testType == typeof(Color32)) + { + RunTypeTestLocal(new NativeArray(new[]{ + new Color32((byte) random.Next(), (byte) random.Next(), (byte) random.Next(), (byte) random.Next()), + new Color32((byte) random.Next(), (byte) random.Next(), (byte) random.Next(), (byte) random.Next()), + new Color32((byte) random.Next(), (byte) random.Next(), (byte) random.Next(), (byte) random.Next()), + }, Allocator.Temp), writeType); + } + else if (testType == typeof(Ray)) + { + RunTypeTestLocal(new NativeArray(new[]{ + new Ray( + new Vector3((float) random.NextDouble(), (float) random.NextDouble(), + (float) random.NextDouble()), + new Vector3((float) random.NextDouble(), (float) random.NextDouble(), + (float) random.NextDouble())), + new Ray( + new Vector3((float) random.NextDouble(), (float) random.NextDouble(), + (float) random.NextDouble()), + new Vector3((float) random.NextDouble(), (float) random.NextDouble(), + (float) random.NextDouble())), + new Ray( + new Vector3((float) random.NextDouble(), (float) random.NextDouble(), + (float) random.NextDouble()), + new Vector3((float) random.NextDouble(), (float) random.NextDouble(), + (float) random.NextDouble())), + }, Allocator.Temp), writeType); + } + else if (testType == typeof(Ray2D)) + { + RunTypeTestLocal(new NativeArray(new[]{ + new Ray2D( + new Vector2((float) random.NextDouble(), (float) random.NextDouble()), + new Vector2((float) random.NextDouble(), (float) random.NextDouble())), + new Ray2D( + new Vector2((float) random.NextDouble(), (float) random.NextDouble()), + new Vector2((float) random.NextDouble(), (float) random.NextDouble())), + new Ray2D( + new Vector2((float) random.NextDouble(), (float) random.NextDouble()), + new Vector2((float) random.NextDouble(), (float) random.NextDouble())), + }, Allocator.Temp), writeType); + } + else if (testType == typeof(TestStruct)) + { + RunTypeTestLocal(new NativeArray(new[] { + GetTestStruct(), + GetTestStruct(), + GetTestStruct(), + }, Allocator.Temp), writeType); + } + else + { + Assert.Fail("No type handler was provided for this type in the test!"); + } + } } } diff --git a/com.unity.netcode.gameobjects/Tests/Editor/Serialization/FastBufferReaderTests.cs b/com.unity.netcode.gameobjects/Tests/Editor/Serialization/FastBufferReaderTests.cs index e1dcbbaee8..96dd429e2f 100644 --- a/com.unity.netcode.gameobjects/Tests/Editor/Serialization/FastBufferReaderTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Editor/Serialization/FastBufferReaderTests.cs @@ -69,6 +69,10 @@ private void RunWriteMethod(string methodName, FastBufferWriter writer, in T { continue; } + if (candidateMethod.GetParameters()[0].ParameterType.IsGenericType) + { + continue; + } try { method = candidateMethod.MakeGenericMethod(typeof(T)); @@ -110,6 +114,92 @@ private void RunWriteMethod(string methodName, FastBufferWriter writer, in T[ { continue; } + if (candidateMethod.GetParameters()[0].ParameterType.IsGenericType) + { + continue; + } + try + { + method = candidateMethod.MakeGenericMethod(typeof(T)); + break; + } + catch (ArgumentException) + { + continue; + } + } + } + } + + Assert.NotNull(method); + + object[] args = new object[method.GetParameters().Length]; + args[0] = value; + for (var i = 1; i < args.Length; ++i) + { + args[i] = method.GetParameters()[i].DefaultValue; + } + method.Invoke(writer, args); + } + + private void RunWriteMethod(string methodName, FastBufferWriter writer, in NativeArray value) where T : unmanaged + { + MethodInfo method = typeof(FastBufferWriter).GetMethod(methodName, new[] { typeof(NativeArray) }); + if (method == null) + { + foreach (var candidateMethod in typeof(FastBufferWriter).GetMethods()) + { + if (candidateMethod.Name == methodName && candidateMethod.IsGenericMethodDefinition) + { + if (candidateMethod.GetParameters().Length == 0 || (candidateMethod.GetParameters().Length > 1 && !candidateMethod.GetParameters()[1].HasDefaultValue)) + { + continue; + } + if (!candidateMethod.GetParameters()[0].ParameterType.Name.Contains("NativeArray")) + { + continue; + } + try + { + method = candidateMethod.MakeGenericMethod(typeof(T)); + break; + } + catch (ArgumentException) + { + continue; + } + } + } + } + + Assert.NotNull(method); + + object[] args = new object[method.GetParameters().Length]; + args[0] = value; + for (var i = 1; i < args.Length; ++i) + { + args[i] = method.GetParameters()[i].DefaultValue; + } + method.Invoke(writer, args); + } + + private void RunWriteMethod(string methodName, FastBufferWriter writer, in NativeList value) where T : unmanaged + { + MethodInfo method = typeof(FastBufferWriter).GetMethod(methodName, new[] { typeof(NativeList) }); + if (method == null) + { + foreach (var candidateMethod in typeof(FastBufferWriter).GetMethods()) + { + if (candidateMethod.Name == methodName && candidateMethod.IsGenericMethodDefinition) + { + if (candidateMethod.GetParameters().Length == 0 || (candidateMethod.GetParameters().Length > 1 && !candidateMethod.GetParameters()[1].HasDefaultValue)) + { + continue; + } + if (!candidateMethod.GetParameters()[0].ParameterType.Name.Contains("NativeList")) + { + continue; + } try { method = candidateMethod.MakeGenericMethod(typeof(T)); @@ -151,6 +241,10 @@ private void RunReadMethod(string methodName, FastBufferReader reader, out T { continue; } + if (candidateMethod.GetParameters()[0].ParameterType.IsGenericType) + { + continue; + } try { method = candidateMethod.MakeGenericMethod(typeof(T)); @@ -203,6 +297,10 @@ private void RunReadMethod(string methodName, FastBufferReader reader, out T[ { continue; } + if (candidateMethod.GetParameters()[0].ParameterType.IsGenericType) + { + continue; + } try { method = candidateMethod.MakeGenericMethod(typeof(T)); @@ -230,6 +328,110 @@ private void RunReadMethod(string methodName, FastBufferReader reader, out T[ value = (T[])args[0]; } + private void RunReadMethod(string methodName, FastBufferReader reader, out NativeArray value) where T : unmanaged + { + MethodInfo method = null; + + try + { + method = typeof(FastBufferReader).GetMethod(methodName, new[] { typeof(NativeArray).MakeByRefType(), typeof(Allocator) }); + } + catch (AmbiguousMatchException) + { + // skip. + } + if (method == null) + { + foreach (var candidateMethod in typeof(FastBufferReader).GetMethods()) + { + if (candidateMethod.Name == methodName && candidateMethod.IsGenericMethodDefinition) + { + if (candidateMethod.GetParameters().Length < 2 || (candidateMethod.GetParameters().Length > 2 && !candidateMethod.GetParameters()[2].HasDefaultValue)) + { + continue; + } + if (!candidateMethod.GetParameters()[0].ParameterType.Name.Contains("NativeArray")) + { + continue; + } + try + { + method = candidateMethod.MakeGenericMethod(typeof(T)); + break; + } + catch (ArgumentException) + { + continue; + } + } + } + } + + Assert.NotNull(method); + + value = new NativeArray(); + + object[] args = new object[method.GetParameters().Length]; + args[0] = value; + args[1] = Allocator.Temp; + for (var i = 2; i < args.Length; ++i) + { + args[i] = method.GetParameters()[i].DefaultValue; + } + method.Invoke(reader, args); + value = (NativeArray)args[0]; + } + + private void RunReadMethod(string methodName, FastBufferReader reader, ref NativeList value) where T : unmanaged + { + MethodInfo method = null; + + try + { + method = typeof(FastBufferReader).GetMethod(methodName, new[] { typeof(NativeList).MakeByRefType() }); + } + catch (AmbiguousMatchException) + { + // skip. + } + if (method == null) + { + foreach (var candidateMethod in typeof(FastBufferReader).GetMethods()) + { + if (candidateMethod.Name == methodName && candidateMethod.IsGenericMethodDefinition) + { + if (candidateMethod.GetParameters().Length == 0 || (candidateMethod.GetParameters().Length > 1 && !candidateMethod.GetParameters()[1].HasDefaultValue)) + { + continue; + } + if (!candidateMethod.GetParameters()[0].ParameterType.Name.Contains("NativeList")) + { + continue; + } + try + { + method = candidateMethod.MakeGenericMethod(typeof(T)); + break; + } + catch (ArgumentException) + { + continue; + } + } + } + } + + Assert.NotNull(method); + + object[] args = new object[method.GetParameters().Length]; + args[0] = value; + for (var i = 1; i < args.Length; ++i) + { + args[i] = method.GetParameters()[i].DefaultValue; + } + method.Invoke(reader, args); + } + protected override unsafe void RunTypeTest(T valueToTest) { var writeSize = FastBufferWriter.GetWriteSize(valueToTest); @@ -288,6 +490,26 @@ private void VerifyArrayEquality(T[] value, T[] compareValue, int offset) whe } } + private void VerifyArrayEquality(NativeArray value, NativeArray compareValue, int offset) where T : unmanaged + { + Assert.AreEqual(value.Length, compareValue.Length); + + for (var i = 0; i < value.Length; ++i) + { + Assert.AreEqual(value[i], compareValue[i]); + } + } + + private void VerifyArrayEquality(NativeList value, NativeList compareValue, int offset) where T : unmanaged + { + Assert.AreEqual(value.Length, compareValue.Length); + + for (var i = 0; i < value.Length; ++i) + { + Assert.AreEqual(value[i], compareValue[i]); + } + } + protected override unsafe void RunTypeArrayTest(T[] valueToTest) { var writeSize = FastBufferWriter.GetWriteSize(valueToTest); @@ -340,6 +562,112 @@ protected override unsafe void RunTypeArrayTestSafe(T[] valueToTest) } } + protected override unsafe void RunTypeNativeArrayTest(NativeArray valueToTest) + { + var writeSize = FastBufferWriter.GetWriteSize(valueToTest); + var writer = new FastBufferWriter(writeSize + 2, Allocator.Temp); + using (writer) + { + Assert.AreEqual(sizeof(int) + sizeof(T) * valueToTest.Length, writeSize); + Assert.IsTrue(writer.TryBeginWrite(writeSize + 2), "Writer denied write permission"); + + RunWriteMethod(nameof(FastBufferWriter.WriteValue), writer, valueToTest); + + WriteCheckBytes(writer, writeSize); + + var reader = new FastBufferReader(writer, Allocator.Temp); + using (reader) + { + VerifyPositionAndLength(reader, writer.Length); + + Assert.IsTrue(reader.TryBeginRead(writeSize)); + RunReadMethod(nameof(FastBufferReader.ReadValue), reader, out NativeArray result); + VerifyArrayEquality(valueToTest, result, 0); + + VerifyCheckBytes(reader, writeSize); + } + } + } + + protected override unsafe void RunTypeNativeArrayTestSafe(NativeArray valueToTest) + { + var writeSize = FastBufferWriter.GetWriteSize(valueToTest); + var writer = new FastBufferWriter(writeSize + 2, Allocator.Temp); + using (writer) + { + Assert.AreEqual(sizeof(int) + sizeof(T) * valueToTest.Length, writeSize); + + RunWriteMethod(nameof(FastBufferWriter.WriteValueSafe), writer, valueToTest); + + WriteCheckBytes(writer, writeSize); + + var reader = new FastBufferReader(writer, Allocator.Temp); + using (reader) + { + VerifyPositionAndLength(reader, writer.Length); + + RunReadMethod(nameof(FastBufferReader.ReadValueSafe), reader, out NativeArray result); + VerifyArrayEquality(valueToTest, result, 0); + + VerifyCheckBytes(reader, writeSize); + } + } + } + + protected override unsafe void RunTypeNativeListTest(NativeList valueToTest) + { + var writeSize = FastBufferWriter.GetWriteSize(valueToTest); + var writer = new FastBufferWriter(writeSize + 2, Allocator.Temp); + using (writer) + { + Assert.AreEqual(sizeof(int) + sizeof(T) * valueToTest.Length, writeSize); + Assert.IsTrue(writer.TryBeginWrite(writeSize + 2), "Writer denied write permission"); + + RunWriteMethod(nameof(FastBufferWriter.WriteValue), writer, valueToTest); + + WriteCheckBytes(writer, writeSize); + + var reader = new FastBufferReader(writer, Allocator.Temp); + using (reader) + { + VerifyPositionAndLength(reader, writer.Length); + + Assert.IsTrue(reader.TryBeginRead(writeSize)); + var result = new NativeList(Allocator.Temp); + RunReadMethod(nameof(FastBufferReader.ReadValueInPlace), reader, ref result); + VerifyArrayEquality(valueToTest, result, 0); + + VerifyCheckBytes(reader, writeSize); + } + } + } + + protected override unsafe void RunTypeNativeListTestSafe(NativeList valueToTest) + { + var writeSize = FastBufferWriter.GetWriteSize(valueToTest); + var writer = new FastBufferWriter(writeSize + 2, Allocator.Temp); + using (writer) + { + Assert.AreEqual(sizeof(int) + sizeof(T) * valueToTest.Length, writeSize); + + RunWriteMethod(nameof(FastBufferWriter.WriteValueSafe), writer, valueToTest); + + WriteCheckBytes(writer, writeSize); + + var reader = new FastBufferReader(writer, Allocator.Temp); + using (reader) + { + VerifyPositionAndLength(reader, writer.Length); + + var result = new NativeList(Allocator.Temp); + RunReadMethod(nameof(FastBufferReader.ReadValueSafeInPlace), reader, ref result); + VerifyArrayEquality(valueToTest, result, 0); + + VerifyCheckBytes(reader, writeSize); + } + } + } + [Test] public void GivenFastBufferWriterContainingValue_WhenReadingUnmanagedType_ValueMatchesWhatWasWritten( [Values(typeof(byte), typeof(sbyte), typeof(short), typeof(ushort), typeof(int), typeof(uint), @@ -368,6 +696,34 @@ public void GivenFastBufferWriterContainingValue_WhenReadingArrayOfUnmanagedElem BaseArrayTypeTest(testType, writeType); } + [Test] + public void GivenFastBufferWriterContainingValue_WhenReadingNativeArrayOfUnmanagedElementType_ValueMatchesWhatWasWritten( + [Values(typeof(byte), typeof(sbyte), typeof(short), typeof(ushort), typeof(int), typeof(uint), + typeof(long), typeof(ulong), typeof(bool), typeof(char), typeof(float), typeof(double), + typeof(ByteEnum), typeof(SByteEnum), typeof(ShortEnum), typeof(UShortEnum), typeof(IntEnum), + typeof(UIntEnum), typeof(LongEnum), typeof(ULongEnum), typeof(Vector2), typeof(Vector3), + typeof(Vector2Int), typeof(Vector3Int), typeof(Vector4), typeof(Quaternion), typeof(Color), + typeof(Color32), typeof(Ray), typeof(Ray2D), typeof(TestStruct))] + Type testType, + [Values] WriteType writeType) + { + BaseNativeArrayTypeTest(testType, writeType); + } + + [Test] + public void GivenFastBufferWriterContainingValue_WhenReadingNativeListOfUnmanagedElementType_ValueMatchesWhatWasWritten( + [Values(typeof(byte), typeof(sbyte), typeof(short), typeof(ushort), typeof(int), typeof(uint), + typeof(long), typeof(ulong), typeof(bool), typeof(char), typeof(float), typeof(double), + typeof(ByteEnum), typeof(SByteEnum), typeof(ShortEnum), typeof(UShortEnum), typeof(IntEnum), + typeof(UIntEnum), typeof(LongEnum), typeof(ULongEnum), typeof(Vector2), typeof(Vector3), + typeof(Vector2Int), typeof(Vector3Int), typeof(Vector4), typeof(Quaternion), typeof(Color), + typeof(Color32), typeof(Ray), typeof(Ray2D), typeof(TestStruct))] + Type testType, + [Values] WriteType writeType) + { + BaseNativeListTypeTest(testType, writeType); + } + public unsafe void RunFixedStringTest(T fixedStringValue, int numBytesWritten, WriteType writeType) where T : unmanaged, INativeList, IUTF8Bytes { diff --git a/com.unity.netcode.gameobjects/Tests/Editor/Serialization/FastBufferWriterTests.cs b/com.unity.netcode.gameobjects/Tests/Editor/Serialization/FastBufferWriterTests.cs index ac924c938e..0b2bc67591 100644 --- a/com.unity.netcode.gameobjects/Tests/Editor/Serialization/FastBufferWriterTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Editor/Serialization/FastBufferWriterTests.cs @@ -2,6 +2,7 @@ using System.Reflection; using NUnit.Framework; using Unity.Collections; +using Unity.Collections.LowLevel.Unsafe; using UnityEngine; using Random = System.Random; @@ -80,6 +81,11 @@ private void RunMethod(string methodName, FastBufferWriter writer, in T value { continue; } + + if (candidateMethod.GetParameters()[0].ParameterType.IsGenericType) + { + continue; + } try { method = candidateMethod.MakeGenericMethod(typeof(T)); @@ -121,6 +127,92 @@ private void RunMethod(string methodName, FastBufferWriter writer, in T[] val { continue; } + if (candidateMethod.GetParameters()[0].ParameterType.IsGenericType) + { + continue; + } + try + { + method = candidateMethod.MakeGenericMethod(typeof(T)); + break; + } + catch (ArgumentException) + { + continue; + } + } + } + } + + Assert.NotNull(method); + + object[] args = new object[method.GetParameters().Length]; + args[0] = value; + for (var i = 1; i < args.Length; ++i) + { + args[i] = method.GetParameters()[i].DefaultValue; + } + method.Invoke(writer, args); + } + + private void RunMethod(string methodName, FastBufferWriter writer, in NativeArray value) where T : unmanaged + { + MethodInfo method = typeof(FastBufferWriter).GetMethod(methodName, new[] { typeof(NativeArray) }); + if (method == null) + { + foreach (var candidateMethod in typeof(FastBufferWriter).GetMethods()) + { + if (candidateMethod.Name == methodName && candidateMethod.IsGenericMethodDefinition) + { + if (candidateMethod.GetParameters().Length == 0 || (candidateMethod.GetParameters().Length > 1 && !candidateMethod.GetParameters()[1].HasDefaultValue)) + { + continue; + } + if (!candidateMethod.GetParameters()[0].ParameterType.Name.Contains("NativeArray")) + { + continue; + } + try + { + method = candidateMethod.MakeGenericMethod(typeof(T)); + break; + } + catch (ArgumentException) + { + continue; + } + } + } + } + + Assert.NotNull(method); + + object[] args = new object[method.GetParameters().Length]; + args[0] = value; + for (var i = 1; i < args.Length; ++i) + { + args[i] = method.GetParameters()[i].DefaultValue; + } + method.Invoke(writer, args); + } + + private void RunMethod(string methodName, FastBufferWriter writer, in NativeList value) where T : unmanaged + { + MethodInfo method = typeof(FastBufferWriter).GetMethod(methodName, new[] { typeof(NativeList) }); + if (method == null) + { + foreach (var candidateMethod in typeof(FastBufferWriter).GetMethods()) + { + if (candidateMethod.Name == methodName && candidateMethod.IsGenericMethodDefinition) + { + if (candidateMethod.GetParameters().Length == 0 || (candidateMethod.GetParameters().Length > 1 && !candidateMethod.GetParameters()[1].HasDefaultValue)) + { + continue; + } + if (!candidateMethod.GetParameters()[0].ParameterType.Name.Contains("NativeList")) + { + continue; + } try { method = candidateMethod.MakeGenericMethod(typeof(T)); @@ -199,6 +291,32 @@ private unsafe void VerifyArrayEquality(T[] value, byte* unsafePtr, int offse } } + private unsafe void VerifyArrayEquality(NativeArray value, byte* unsafePtr, int offset) where T : unmanaged + { + int* sizeValue = (int*)(unsafePtr + offset); + Assert.AreEqual(value.Length, *sizeValue); + + var asTPointer = (T*)value.GetUnsafePtr(); + var underlyingTArray = (T*)(unsafePtr + sizeof(int) + offset); + for (var i = 0; i < value.Length; ++i) + { + Assert.AreEqual(asTPointer[i], underlyingTArray[i]); + } + } + + private unsafe void VerifyArrayEquality(NativeList value, byte* unsafePtr, int offset) where T : unmanaged + { + int* sizeValue = (int*)(unsafePtr + offset); + Assert.AreEqual(value.Length, *sizeValue); + + var asTPointer = (T*)value.GetUnsafePtr(); + var underlyingTArray = (T*)(unsafePtr + sizeof(int) + offset); + for (var i = 0; i < value.Length; ++i) + { + Assert.AreEqual(asTPointer[i], underlyingTArray[i]); + } + } + protected override unsafe void RunTypeArrayTest(T[] valueToTest) { var writeSize = FastBufferWriter.GetWriteSize(valueToTest); @@ -242,6 +360,94 @@ protected override unsafe void RunTypeArrayTestSafe(T[] valueToTest) } } + + protected override unsafe void RunTypeNativeArrayTest(NativeArray valueToTest) + { + var writeSize = FastBufferWriter.GetWriteSize(valueToTest); + var writer = new FastBufferWriter(writeSize + 2, Allocator.Temp); + using (writer) + { + + Assert.AreEqual(sizeof(int) + sizeof(T) * valueToTest.Length, writeSize); + Assert.IsTrue(writer.TryBeginWrite(writeSize + 2), "Writer denied write permission"); + + RunMethod(nameof(FastBufferWriter.WriteValue), writer, valueToTest); + VerifyPositionAndLength(writer, writeSize); + + WriteCheckBytes(writer, writeSize); + + VerifyArrayEquality(valueToTest, writer.GetUnsafePtr(), 0); + + var underlyingArray = writer.ToArray(); + VerifyCheckBytes(underlyingArray, writeSize); + } + } + + protected override unsafe void RunTypeNativeArrayTestSafe(NativeArray valueToTest) + { + var writeSize = FastBufferWriter.GetWriteSize(valueToTest); + var writer = new FastBufferWriter(writeSize + 2, Allocator.Temp); + using (writer) + { + + Assert.AreEqual(sizeof(int) + sizeof(T) * valueToTest.Length, writeSize); + + RunMethod(nameof(FastBufferWriter.WriteValueSafe), writer, valueToTest); + VerifyPositionAndLength(writer, writeSize); + + WriteCheckBytes(writer, writeSize); + + VerifyArrayEquality(valueToTest, writer.GetUnsafePtr(), 0); + + var underlyingArray = writer.ToArray(); + VerifyCheckBytes(underlyingArray, writeSize); + } + } + + + protected override unsafe void RunTypeNativeListTest(NativeList valueToTest) + { + var writeSize = FastBufferWriter.GetWriteSize(valueToTest); + var writer = new FastBufferWriter(writeSize + 2, Allocator.Temp); + using (writer) + { + + Assert.AreEqual(sizeof(int) + sizeof(T) * valueToTest.Length, writeSize); + Assert.IsTrue(writer.TryBeginWrite(writeSize + 2), "Writer denied write permission"); + + RunMethod(nameof(FastBufferWriter.WriteValue), writer, valueToTest); + VerifyPositionAndLength(writer, writeSize); + + WriteCheckBytes(writer, writeSize); + + VerifyArrayEquality(valueToTest, writer.GetUnsafePtr(), 0); + + var underlyingArray = writer.ToArray(); + VerifyCheckBytes(underlyingArray, writeSize); + } + } + + protected override unsafe void RunTypeNativeListTestSafe(NativeList valueToTest) + { + var writeSize = FastBufferWriter.GetWriteSize(valueToTest); + var writer = new FastBufferWriter(writeSize + 2, Allocator.Temp); + using (writer) + { + + Assert.AreEqual(sizeof(int) + sizeof(T) * valueToTest.Length, writeSize); + + RunMethod(nameof(FastBufferWriter.WriteValueSafe), writer, valueToTest); + VerifyPositionAndLength(writer, writeSize); + + WriteCheckBytes(writer, writeSize); + + VerifyArrayEquality(valueToTest, writer.GetUnsafePtr(), 0); + + var underlyingArray = writer.ToArray(); + VerifyCheckBytes(underlyingArray, writeSize); + } + } + [Test, Description("Tests")] public void WhenWritingUnmanagedType_ValueIsWrittenCorrectly( [Values(typeof(byte), typeof(sbyte), typeof(short), typeof(ushort), typeof(int), typeof(uint), @@ -270,6 +476,34 @@ public void WhenWritingArrayOfUnmanagedElementType_ArrayIsWrittenCorrectly( BaseArrayTypeTest(testType, writeType); } + [Test] + public void WhenWritingNativeArrayOfUnmanagedElementType_NativeArrayIsWrittenCorrectly( + [Values(typeof(byte), typeof(sbyte), typeof(short), typeof(ushort), typeof(int), typeof(uint), + typeof(long), typeof(ulong), typeof(bool), typeof(char), typeof(float), typeof(double), + typeof(ByteEnum), typeof(SByteEnum), typeof(ShortEnum), typeof(UShortEnum), typeof(IntEnum), + typeof(UIntEnum), typeof(LongEnum), typeof(ULongEnum), typeof(Vector2), typeof(Vector3), + typeof(Vector2Int), typeof(Vector3Int), typeof(Vector4), typeof(Quaternion), typeof(Color), + typeof(Color32), typeof(Ray), typeof(Ray2D), typeof(TestStruct))] + Type testType, + [Values] WriteType writeType) + { + BaseNativeArrayTypeTest(testType, writeType); + } + + [Test] + public void WhenWritingNativeListOfUnmanagedElementType_NativeListIsWrittenCorrectly( + [Values(typeof(byte), typeof(sbyte), typeof(short), typeof(ushort), typeof(int), typeof(uint), + typeof(long), typeof(ulong), typeof(bool), typeof(char), typeof(float), typeof(double), + typeof(ByteEnum), typeof(SByteEnum), typeof(ShortEnum), typeof(UShortEnum), typeof(IntEnum), + typeof(UIntEnum), typeof(LongEnum), typeof(ULongEnum), typeof(Vector2), typeof(Vector3), + typeof(Vector2Int), typeof(Vector3Int), typeof(Vector4), typeof(Quaternion), typeof(Color), + typeof(Color32), typeof(Ray), typeof(Ray2D), typeof(TestStruct))] + Type testType, + [Values] WriteType writeType) + { + BaseNativeListTypeTest(testType, writeType); + } + [TestCase(false, WriteType.WriteDirect)] [TestCase(false, WriteType.WriteSafe)] [TestCase(true, WriteType.WriteDirect)] diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/Components/NetworkVariableTestComponent.cs b/com.unity.netcode.gameobjects/Tests/Runtime/Components/NetworkVariableTestComponent.cs index 9e3c686d7d..468600f8b3 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/Components/NetworkVariableTestComponent.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/Components/NetworkVariableTestComponent.cs @@ -6,10 +6,19 @@ namespace Unity.Netcode.RuntimeTests { + public class EmbeddedManagedNetworkSerializableType : INetworkSerializable + { + public int Int; + public void NetworkSerialize(BufferSerializer serializer) where T : IReaderWriter + { + serializer.SerializeValue(ref Int); + } + } public class ManagedNetworkSerializableType : INetworkSerializable, IEquatable { public string Str = ""; public int[] Ints = Array.Empty(); + public EmbeddedManagedNetworkSerializableType Embedded = new EmbeddedManagedNetworkSerializableType(); public int InMemoryValue; public void NetworkSerialize(BufferSerializer serializer) where T : IReaderWriter @@ -28,6 +37,8 @@ public void NetworkSerialize(BufferSerializer serializer) where T : IReade serializer.SerializeValue(ref val); Ints[i] = val; } + + serializer.SerializeValue(ref Embedded); } public bool Equals(ManagedNetworkSerializableType other) @@ -60,6 +71,11 @@ public bool Equals(ManagedNetworkSerializableType other) } } + if (Embedded.Int != other.Embedded.Int) + { + return false; + } + return true; } @@ -280,7 +296,8 @@ private void InitializeTest() m_NetworkVariableManaged = new NetworkVariable(new ManagedNetworkSerializableType { Str = "1234567890", - Ints = new[] { 1, 2, 3, 4, 5 } + Ints = new[] { 1, 2, 3, 4, 5 }, + Embedded = new EmbeddedManagedNetworkSerializableType { Int = 6 } }); // Use this nifty class: NetworkVariableHelper @@ -386,7 +403,8 @@ public void AssertAllValuesAreCorrect() Assert.IsTrue(m_NetworkVariableManaged.Value.Equals(new ManagedNetworkSerializableType { Str = "ManagedNetworkSerializableType", - Ints = new[] { 1000, 2000, 3000, 4000, 5000, 6000, 7000, 8000, 9000, 10000 } + Ints = new[] { 1000, 2000, 3000, 4000, 5000, 6000, 7000, 8000, 9000, 10000 }, + Embedded = new EmbeddedManagedNetworkSerializableType { Int = 20000 } })); } @@ -433,7 +451,8 @@ private void Update() m_NetworkVariableManaged.Value = new ManagedNetworkSerializableType { Str = "ManagedNetworkSerializableType", - Ints = new[] { 1000, 2000, 3000, 4000, 5000, 6000, 7000, 8000, 9000, 10000 } + Ints = new[] { 1000, 2000, 3000, 4000, 5000, 6000, 7000, 8000, 9000, 10000 }, + Embedded = new EmbeddedManagedNetworkSerializableType { Int = 20000 } }; //Set the timeout (i.e. how long we will wait for all NetworkVariables to have registered their changes) diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkVariableTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkVariableTests.cs index 616ac26420..7e4ac35851 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkVariableTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkVariableTests.cs @@ -34,12 +34,200 @@ public struct TemplatedValueOnlyReferencedByNetworkVariableSubclass : INetwor public T Value; } + public enum ByteEnum : byte + { + A, + B, + C = byte.MaxValue + } + public enum SByteEnum : sbyte + { + A, + B, + C = sbyte.MaxValue + } + public enum ShortEnum : short + { + A, + B, + C = short.MaxValue + } + public enum UShortEnum : ushort + { + A, + B, + C = ushort.MaxValue + } + public enum IntEnum : int + { + A, + B, + C = int.MaxValue + } + public enum UIntEnum : uint + { + A, + B, + C = uint.MaxValue + } + public enum LongEnum : long + { + A, + B, + C = long.MaxValue + } + public enum ULongEnum : ulong + { + A, + B, + C = ulong.MaxValue + } + + public struct NetworkVariableTestStruct : INetworkSerializeByMemcpy + { + public byte A; + public short B; + public ushort C; + public int D; + public uint E; + public long F; + public ulong G; + public bool H; + public char I; + public float J; + public double K; + + private static System.Random s_Random = new System.Random(); + + public static NetworkVariableTestStruct GetTestStruct() + { + var testStruct = new NetworkVariableTestStruct + { + A = (byte)s_Random.Next(), + B = (short)s_Random.Next(), + C = (ushort)s_Random.Next(), + D = s_Random.Next(), + E = (uint)s_Random.Next(), + F = ((long)s_Random.Next() << 32) + s_Random.Next(), + G = ((ulong)s_Random.Next() << 32) + (ulong)s_Random.Next(), + H = true, + I = '\u263a', + J = (float)s_Random.NextDouble(), + K = s_Random.NextDouble(), + }; + + return testStruct; + } + } + // The ILPP code for NetworkVariables to determine how to serialize them relies on them existing as fields of a NetworkBehaviour to find them. // Some of the tests below create NetworkVariables on the stack, so this class is here just to make sure the relevant types are all accounted for. public class NetVarILPPClassForTests : NetworkBehaviour { + public NetworkVariable ByteVar; + public NetworkVariable> ByteArrayVar; + public NetworkVariable> ByteListVar; + public NetworkVariable SbyteVar; + public NetworkVariable> SbyteArrayVar; + public NetworkVariable> SbyteListVar; + public NetworkVariable ShortVar; + public NetworkVariable> ShortArrayVar; + public NetworkVariable> ShortListVar; + public NetworkVariable UshortVar; + public NetworkVariable> UshortArrayVar; + public NetworkVariable> UshortListVar; + public NetworkVariable IntVar; + public NetworkVariable> IntArrayVar; + public NetworkVariable> IntListVar; + public NetworkVariable UintVar; + public NetworkVariable> UintArrayVar; + public NetworkVariable> UintListVar; + public NetworkVariable LongVar; + public NetworkVariable> LongArrayVar; + public NetworkVariable> LongListVar; + public NetworkVariable UlongVar; + public NetworkVariable> UlongArrayVar; + public NetworkVariable> UlongListVar; + public NetworkVariable BoolVar; + public NetworkVariable> BoolArrayVar; + public NetworkVariable> BoolListVar; + public NetworkVariable CharVar; + public NetworkVariable> CharArrayVar; + public NetworkVariable> CharListVar; + public NetworkVariable FloatVar; + public NetworkVariable> FloatArrayVar; + public NetworkVariable> FloatListVar; + public NetworkVariable DoubleVar; + public NetworkVariable> DoubleArrayVar; + public NetworkVariable> DoubleListVar; + public NetworkVariable ByteEnumVar; + public NetworkVariable> ByteEnumArrayVar; + public NetworkVariable> ByteEnumListVar; + public NetworkVariable SByteEnumVar; + public NetworkVariable> SByteEnumArrayVar; + public NetworkVariable> SByteEnumListVar; + public NetworkVariable ShortEnumVar; + public NetworkVariable> ShortEnumArrayVar; + public NetworkVariable> ShortEnumListVar; + public NetworkVariable UShortEnumVar; + public NetworkVariable> UShortEnumArrayVar; + public NetworkVariable> UShortEnumListVar; + public NetworkVariable IntEnumVar; + public NetworkVariable> IntEnumArrayVar; + public NetworkVariable> IntEnumListVar; + public NetworkVariable UIntEnumVar; + public NetworkVariable> UIntEnumArrayVar; + public NetworkVariable> UIntEnumListVar; + public NetworkVariable LongEnumVar; + public NetworkVariable> LongEnumArrayVar; + public NetworkVariable> LongEnumListVar; + public NetworkVariable ULongEnumVar; + public NetworkVariable> ULongEnumArrayVar; + public NetworkVariable> ULongEnumListVar; + public NetworkVariable Vector2Var; + public NetworkVariable> Vector2ArrayVar; + public NetworkVariable> Vector2ListVar; + public NetworkVariable Vector3Var; + public NetworkVariable> Vector3ArrayVar; + public NetworkVariable> Vector3ListVar; + public NetworkVariable Vector2IntVar; + public NetworkVariable> Vector2IntArrayVar; + public NetworkVariable> Vector2IntListVar; + public NetworkVariable Vector3IntVar; + public NetworkVariable> Vector3IntArrayVar; + public NetworkVariable> Vector3IntListVar; + public NetworkVariable Vector4Var; + public NetworkVariable> Vector4ArrayVar; + public NetworkVariable> Vector4ListVar; + public NetworkVariable QuaternionVar; + public NetworkVariable> QuaternionArrayVar; + public NetworkVariable> QuaternionListVar; + public NetworkVariable ColorVar; + public NetworkVariable> ColorArrayVar; + public NetworkVariable> ColorListVar; + public NetworkVariable Color32Var; + public NetworkVariable> Color32ArrayVar; + public NetworkVariable> Color32ListVar; + public NetworkVariable RayVar; + public NetworkVariable> RayArrayVar; + public NetworkVariable> RayListVar; + public NetworkVariable Ray2DVar; + public NetworkVariable> Ray2DArrayVar; + public NetworkVariable> Ray2DListVar; + public NetworkVariable TestStructVar; + public NetworkVariable> TestStructArrayVar; + public NetworkVariable> TestStructListVar; + + public NetworkVariable FixedStringVar; + public NetworkVariable> FixedStringArrayVar; + public NetworkVariable> FixedStringListVar; + public NetworkVariable UnmanagedNetworkSerializableTypeVar; + public NetworkVariable> UnmanagedNetworkSerializableListVar; + public NetworkVariable> UnmanagedNetworkSerializableArrayVar; + public NetworkVariable ManagedNetworkSerializableTypeVar; + public NetworkVariable StringVar; public NetworkVariable GuidVar; public NetworkVariableSubclass> SubclassVar; @@ -60,7 +248,7 @@ public class ClassHavingNetworkBehaviour : IntermediateNetworkBehavior { @@ -1062,6 +1250,7 @@ public void TestUnsupportedManagedTypesThrowExceptions() // Just making sure these are null, just in case. UserNetworkVariableSerialization.ReadValue = null; UserNetworkVariableSerialization.WriteValue = null; + UserNetworkVariableSerialization.DuplicateValue = null; Assert.Throws(() => { variable.WriteField(writer); @@ -1084,6 +1273,10 @@ public void TestUnsupportedManagedTypesWithUserSerializationDoNotThrowExceptions { writer.WriteValueSafe(value); }; + UserNetworkVariableSerialization.DuplicateValue = (in string a, ref string b) => + { + b = string.Copy(a); + }; try { using var writer = new FastBufferWriter(1024, Allocator.Temp); @@ -1099,6 +1292,7 @@ public void TestUnsupportedManagedTypesWithUserSerializationDoNotThrowExceptions { UserNetworkVariableSerialization.ReadValue = null; UserNetworkVariableSerialization.WriteValue = null; + UserNetworkVariableSerialization.DuplicateValue = null; } } @@ -1111,6 +1305,7 @@ public void TestUnsupportedUnmanagedTypesThrowExceptions() // Just making sure these are null, just in case. UserNetworkVariableSerialization.ReadValue = null; UserNetworkVariableSerialization.WriteValue = null; + UserNetworkVariableSerialization.DuplicateValue = null; Assert.Throws(() => { variable.WriteField(writer); @@ -1151,6 +1346,10 @@ public void TestUnsupportedUnmanagedTypesWithUserSerializationDoNotThrowExceptio var tmpValue = new ForceNetworkSerializeByMemcpy(value); writer.WriteValueSafe(tmpValue); }; + UserNetworkVariableSerialization.DuplicateValue = (in Guid a, ref Guid b) => + { + b = a; + }; try { using var writer = new FastBufferWriter(1024, Allocator.Temp); @@ -1167,6 +1366,775 @@ public void TestUnsupportedUnmanagedTypesWithUserSerializationDoNotThrowExceptio { UserNetworkVariableSerialization.ReadValue = null; UserNetworkVariableSerialization.WriteValue = null; + UserNetworkVariableSerialization.DuplicateValue = null; + } + } + + private void TestValueType(T testValue, T changedValue) where T : unmanaged + { + var serverVariable = new NetworkVariable(testValue); + var clientVariable = new NetworkVariable(); + using var writer = new FastBufferWriter(1024, Allocator.Temp); + serverVariable.WriteField(writer); + + Assert.IsFalse(NetworkVariableSerialization.AreEqual(ref serverVariable.RefValue(), ref clientVariable.RefValue())); + + using var reader = new FastBufferReader(writer, Allocator.None); + clientVariable.ReadField(reader); + + Assert.IsTrue(NetworkVariableSerialization.AreEqual(ref serverVariable.RefValue(), ref clientVariable.RefValue())); + + serverVariable.Value = changedValue; + Assert.IsFalse(NetworkVariableSerialization.AreEqual(ref serverVariable.RefValue(), ref clientVariable.RefValue())); + + writer.Seek(0); + + serverVariable.WriteDelta(writer); + + Assert.IsFalse(NetworkVariableSerialization.AreEqual(ref serverVariable.RefValue(), ref clientVariable.RefValue())); + + using var reader2 = new FastBufferReader(writer, Allocator.None); + clientVariable.ReadDelta(reader2, false); + Assert.IsTrue(NetworkVariableSerialization.AreEqual(ref serverVariable.RefValue(), ref clientVariable.RefValue())); + } + + private void TestValueTypeNativeArray(NativeArray testValue, NativeArray changedValue) where T : unmanaged + { + var serverVariable = new NetworkVariable>(testValue); + var clientVariable = new NetworkVariable>(new NativeArray(1, Allocator.Persistent)); + using var writer = new FastBufferWriter(1024, Allocator.Temp); + serverVariable.WriteField(writer); + + Assert.IsFalse(NetworkVariableSerialization>.AreEqual(ref serverVariable.RefValue(), ref clientVariable.RefValue())); + + using var reader = new FastBufferReader(writer, Allocator.None); + clientVariable.ReadField(reader); + + Assert.IsTrue(NetworkVariableSerialization>.AreEqual(ref serverVariable.RefValue(), ref clientVariable.RefValue())); + + serverVariable.Dispose(); + serverVariable.Value = changedValue; + Assert.IsFalse(NetworkVariableSerialization>.AreEqual(ref serverVariable.RefValue(), ref clientVariable.RefValue())); + + writer.Seek(0); + + serverVariable.WriteDelta(writer); + + Assert.IsFalse(NetworkVariableSerialization>.AreEqual(ref serverVariable.RefValue(), ref clientVariable.RefValue())); + + using var reader2 = new FastBufferReader(writer, Allocator.None); + clientVariable.ReadDelta(reader2, false); + Assert.IsTrue(NetworkVariableSerialization>.AreEqual(ref serverVariable.RefValue(), ref clientVariable.RefValue())); + + serverVariable.ResetDirty(); + Assert.IsFalse(serverVariable.IsDirty()); + var cachedValue = changedValue[0]; + changedValue[0] = testValue[0]; + Assert.IsTrue(serverVariable.IsDirty()); + serverVariable.ResetDirty(); + Assert.IsFalse(serverVariable.IsDirty()); + changedValue[0] = cachedValue; + Assert.IsTrue(serverVariable.IsDirty()); + + + serverVariable.Dispose(); + clientVariable.Dispose(); + } + + private void TestValueTypeNativeList(NativeList testValue, NativeList changedValue) where T : unmanaged + { + var serverVariable = new NetworkVariable>(testValue); + var inPlaceList = new NativeList(1, Allocator.Temp); + var clientVariable = new NetworkVariable>(inPlaceList); + using var writer = new FastBufferWriter(1024, Allocator.Temp); + serverVariable.WriteField(writer); + + Assert.IsFalse(NetworkVariableSerialization>.AreEqual(ref serverVariable.RefValue(), ref clientVariable.RefValue())); + // Lists are deserialized in place so this should ALWAYS be true. Checking it every time to make sure! + Assert.IsTrue(NetworkVariableSerialization>.AreEqual(ref clientVariable.RefValue(), ref inPlaceList)); + + using var reader = new FastBufferReader(writer, Allocator.None); + clientVariable.ReadField(reader); + + Assert.IsTrue(NetworkVariableSerialization>.AreEqual(ref serverVariable.RefValue(), ref clientVariable.RefValue())); + // Lists are deserialized in place so this should ALWAYS be true. Checking it every time to make sure! + Assert.IsTrue(NetworkVariableSerialization>.AreEqual(ref clientVariable.RefValue(), ref inPlaceList)); + + serverVariable.Dispose(); + serverVariable.Value = changedValue; + Assert.IsFalse(NetworkVariableSerialization>.AreEqual(ref serverVariable.RefValue(), ref clientVariable.RefValue())); + // Lists are deserialized in place so this should ALWAYS be true. Checking it every time to make sure! + Assert.IsTrue(NetworkVariableSerialization>.AreEqual(ref clientVariable.RefValue(), ref inPlaceList)); + + writer.Seek(0); + + serverVariable.WriteDelta(writer); + + Assert.IsFalse(NetworkVariableSerialization>.AreEqual(ref serverVariable.RefValue(), ref clientVariable.RefValue())); + // Lists are deserialized in place so this should ALWAYS be true. Checking it every time to make sure! + Assert.IsTrue(NetworkVariableSerialization>.AreEqual(ref clientVariable.RefValue(), ref inPlaceList)); + + using var reader2 = new FastBufferReader(writer, Allocator.None); + clientVariable.ReadDelta(reader2, false); + Assert.IsTrue(NetworkVariableSerialization>.AreEqual(ref serverVariable.RefValue(), ref clientVariable.RefValue())); + // Lists are deserialized in place so this should ALWAYS be true. Checking it every time to make sure! + Assert.IsTrue(NetworkVariableSerialization>.AreEqual(ref clientVariable.RefValue(), ref inPlaceList)); + + serverVariable.ResetDirty(); + Assert.IsFalse(serverVariable.IsDirty()); + serverVariable.Value.Clear(); + Assert.IsTrue(serverVariable.IsDirty()); + + serverVariable.ResetDirty(); + + Assert.IsFalse(serverVariable.IsDirty()); + serverVariable.Value.Add(default); + Assert.IsTrue(serverVariable.IsDirty()); + + serverVariable.Dispose(); + clientVariable.Dispose(); + } + + [Test] + public void WhenSerializingAndDeserializingValueTypeNetworkVariables_ValuesAreSerializedCorrectly( + + [Values(typeof(byte), typeof(sbyte), typeof(short), typeof(ushort), typeof(int), typeof(uint), + typeof(long), typeof(ulong), typeof(bool), typeof(char), typeof(float), typeof(double), + typeof(ByteEnum), typeof(SByteEnum), typeof(ShortEnum), typeof(UShortEnum), typeof(IntEnum), + typeof(UIntEnum), typeof(LongEnum), typeof(ULongEnum), typeof(Vector2), typeof(Vector3), + typeof(Vector2Int), typeof(Vector3Int), typeof(Vector4), typeof(Quaternion), typeof(Color), + typeof(Color32), typeof(Ray), typeof(Ray2D), typeof(NetworkVariableTestStruct), typeof(FixedString32Bytes))] + Type testType) + { + if (testType == typeof(byte)) + { + TestValueType(byte.MinValue + 5, byte.MaxValue); + } + else if (testType == typeof(sbyte)) + { + TestValueType(sbyte.MinValue + 5, sbyte.MaxValue); + } + else if (testType == typeof(short)) + { + TestValueType(short.MinValue + 5, short.MaxValue); + } + else if (testType == typeof(ushort)) + { + TestValueType(ushort.MinValue + 5, ushort.MaxValue); + } + else if (testType == typeof(int)) + { + TestValueType(int.MinValue + 5, int.MaxValue); + } + else if (testType == typeof(uint)) + { + TestValueType(uint.MinValue + 5, uint.MaxValue); + } + else if (testType == typeof(long)) + { + TestValueType(long.MinValue + 5, long.MaxValue); + } + else if (testType == typeof(ulong)) + { + TestValueType(ulong.MinValue + 5, ulong.MaxValue); + } + else if (testType == typeof(bool)) + { + TestValueType(true, false); + } + else if (testType == typeof(char)) + { + TestValueType('z', ' '); + } + else if (testType == typeof(float)) + { + TestValueType(float.MinValue + 5.12345678f, float.MaxValue); + } + else if (testType == typeof(double)) + { + TestValueType(double.MinValue + 5.12345678, double.MaxValue); + } + else if (testType == typeof(ByteEnum)) + { + TestValueType(ByteEnum.B, ByteEnum.C); + } + else if (testType == typeof(SByteEnum)) + { + TestValueType(SByteEnum.B, SByteEnum.C); + } + else if (testType == typeof(ShortEnum)) + { + TestValueType(ShortEnum.B, ShortEnum.C); + } + else if (testType == typeof(UShortEnum)) + { + TestValueType(UShortEnum.B, UShortEnum.C); + } + else if (testType == typeof(IntEnum)) + { + TestValueType(IntEnum.B, IntEnum.C); + } + else if (testType == typeof(UIntEnum)) + { + TestValueType(UIntEnum.B, UIntEnum.C); + } + else if (testType == typeof(LongEnum)) + { + TestValueType(LongEnum.B, LongEnum.C); + } + else if (testType == typeof(ULongEnum)) + { + TestValueType(ULongEnum.B, ULongEnum.C); + } + else if (testType == typeof(Vector2)) + { + TestValueType( + new Vector2(5, 10), + new Vector2(15, 20)); + } + else if (testType == typeof(Vector3)) + { + TestValueType( + new Vector3(5, 10, 15), + new Vector3(20, 25, 30)); + } + else if (testType == typeof(Vector2Int)) + { + TestValueType( + new Vector2Int(5, 10), + new Vector2Int(15, 20)); + } + else if (testType == typeof(Vector3Int)) + { + TestValueType( + new Vector3Int(5, 10, 15), + new Vector3Int(20, 25, 30)); + } + else if (testType == typeof(Vector4)) + { + TestValueType( + new Vector4(5, 10, 15, 20), + new Vector4(25, 30, 35, 40)); + } + else if (testType == typeof(Quaternion)) + { + TestValueType( + new Quaternion(5, 10, 15, 20), + new Quaternion(25, 30, 35, 40)); + } + else if (testType == typeof(Color)) + { + TestValueType( + new Color(1, 0, 0), + new Color(0, 1, 1)); + } + else if (testType == typeof(Color32)) + { + TestValueType( + new Color32(255, 0, 0, 128), + new Color32(0, 255, 255, 255)); + } + else if (testType == typeof(Ray)) + { + TestValueType( + new Ray(new Vector3(0, 1, 2), new Vector3(3, 4, 5)), + new Ray(new Vector3(6, 7, 8), new Vector3(9, 10, 11))); + } + else if (testType == typeof(Ray2D)) + { + TestValueType( + new Ray2D(new Vector2(0, 1), new Vector2(2, 3)), + new Ray2D(new Vector2(4, 5), new Vector2(6, 7))); + } + else if (testType == typeof(NetworkVariableTestStruct)) + { + TestValueType(NetworkVariableTestStruct.GetTestStruct(), NetworkVariableTestStruct.GetTestStruct()); + } + else if (testType == typeof(FixedString32Bytes)) + { + TestValueType(new FixedString32Bytes("foobar"), new FixedString32Bytes("12345678901234567890123456789")); + } + } + + [Test] + public void WhenSerializingAndDeserializingValueTypeNativeArrayNetworkVariables_ValuesAreSerializedCorrectly( + + [Values(typeof(byte), typeof(sbyte), typeof(short), typeof(ushort), typeof(int), typeof(uint), + typeof(long), typeof(ulong), typeof(bool), typeof(char), typeof(float), typeof(double), + typeof(ByteEnum), typeof(SByteEnum), typeof(ShortEnum), typeof(UShortEnum), typeof(IntEnum), + typeof(UIntEnum), typeof(LongEnum), typeof(ULongEnum), typeof(Vector2), typeof(Vector3), + typeof(Vector2Int), typeof(Vector3Int), typeof(Vector4), typeof(Quaternion), typeof(Color), + typeof(Color32), typeof(Ray), typeof(Ray2D), typeof(NetworkVariableTestStruct), typeof(FixedString32Bytes))] + Type testType) + { + if (testType == typeof(byte)) + { + TestValueTypeNativeArray( + new NativeArray(new byte[] { byte.MinValue + 5, byte.MaxValue }, Allocator.Temp), + new NativeArray(new byte[] { 0, byte.MinValue + 10, byte.MaxValue - 10 }, Allocator.Temp)); + } + else if (testType == typeof(sbyte)) + { + TestValueTypeNativeArray( + new NativeArray(new sbyte[] { sbyte.MinValue + 5, sbyte.MaxValue }, Allocator.Temp), + new NativeArray(new sbyte[] { 0, sbyte.MinValue + 10, sbyte.MaxValue - 10 }, Allocator.Temp)); + } + else if (testType == typeof(short)) + { + TestValueTypeNativeArray( + new NativeArray(new short[] { short.MinValue + 5, short.MaxValue }, Allocator.Temp), + new NativeArray(new short[] { 0, short.MinValue + 10, short.MaxValue - 10 }, Allocator.Temp)); + } + else if (testType == typeof(ushort)) + { + TestValueTypeNativeArray( + new NativeArray(new ushort[] { ushort.MinValue + 5, ushort.MaxValue }, Allocator.Temp), + new NativeArray(new ushort[] { 0, ushort.MinValue + 10, ushort.MaxValue - 10 }, Allocator.Temp)); + } + else if (testType == typeof(int)) + { + TestValueTypeNativeArray( + new NativeArray(new int[] { int.MinValue + 5, int.MaxValue }, Allocator.Temp), + new NativeArray(new int[] { 0, int.MinValue + 10, int.MaxValue - 10 }, Allocator.Temp)); + } + else if (testType == typeof(uint)) + { + TestValueTypeNativeArray( + new NativeArray(new uint[] { uint.MinValue + 5, uint.MaxValue }, Allocator.Temp), + new NativeArray(new uint[] { 0, uint.MinValue + 10, uint.MaxValue - 10 }, Allocator.Temp)); + } + else if (testType == typeof(long)) + { + TestValueTypeNativeArray( + new NativeArray(new long[] { long.MinValue + 5, long.MaxValue }, Allocator.Temp), + new NativeArray(new long[] { 0, long.MinValue + 10, long.MaxValue - 10 }, Allocator.Temp)); + } + else if (testType == typeof(ulong)) + { + TestValueTypeNativeArray( + new NativeArray(new ulong[] { ulong.MinValue + 5, ulong.MaxValue }, Allocator.Temp), + new NativeArray(new ulong[] { 0, ulong.MinValue + 10, ulong.MaxValue - 10 }, Allocator.Temp)); + } + else if (testType == typeof(bool)) + { + TestValueTypeNativeArray( + new NativeArray(new bool[] { true, false, true }, Allocator.Temp), + new NativeArray(new bool[] { false, true, false, true, false }, Allocator.Temp)); + } + else if (testType == typeof(char)) + { + TestValueTypeNativeArray( + new NativeArray(new char[] { 'z', ' ', '?' }, Allocator.Temp), + new NativeArray(new char[] { 'n', 'e', 'w', ' ', 'v', 'a', 'l', 'u', 'e' }, Allocator.Temp)); + } + else if (testType == typeof(float)) + { + TestValueTypeNativeArray( + new NativeArray(new float[] { float.MinValue + 5.12345678f, float.MaxValue }, Allocator.Temp), + new NativeArray(new float[] { 0, float.MinValue + 10.987654321f, float.MaxValue - 10.135792468f }, Allocator.Temp)); + } + else if (testType == typeof(double)) + { + TestValueTypeNativeArray( + new NativeArray(new double[] { double.MinValue + 5.12345678, double.MaxValue }, Allocator.Temp), + new NativeArray(new double[] { 0, double.MinValue + 10.987654321, double.MaxValue - 10.135792468 }, Allocator.Temp)); + } + else if (testType == typeof(ByteEnum)) + { + TestValueTypeNativeArray( + new NativeArray(new ByteEnum[] { ByteEnum.C, ByteEnum.B, ByteEnum.A }, Allocator.Temp), + new NativeArray(new ByteEnum[] { ByteEnum.B, ByteEnum.C, ByteEnum.B, ByteEnum.A, ByteEnum.C }, Allocator.Temp)); + } + else if (testType == typeof(SByteEnum)) + { + TestValueTypeNativeArray( + new NativeArray(new SByteEnum[] { SByteEnum.C, SByteEnum.B, SByteEnum.A }, Allocator.Temp), + new NativeArray(new SByteEnum[] { SByteEnum.B, SByteEnum.C, SByteEnum.B, SByteEnum.A, SByteEnum.C }, Allocator.Temp)); + } + else if (testType == typeof(ShortEnum)) + { + TestValueTypeNativeArray( + new NativeArray(new ShortEnum[] { ShortEnum.C, ShortEnum.B, ShortEnum.A }, Allocator.Temp), + new NativeArray(new ShortEnum[] { ShortEnum.B, ShortEnum.C, ShortEnum.B, ShortEnum.A, ShortEnum.C }, Allocator.Temp)); + } + else if (testType == typeof(UShortEnum)) + { + TestValueTypeNativeArray( + new NativeArray(new UShortEnum[] { UShortEnum.C, UShortEnum.B, UShortEnum.A }, Allocator.Temp), + new NativeArray(new UShortEnum[] { UShortEnum.B, UShortEnum.C, UShortEnum.B, UShortEnum.A, UShortEnum.C }, Allocator.Temp)); + } + else if (testType == typeof(IntEnum)) + { + TestValueTypeNativeArray( + new NativeArray(new IntEnum[] { IntEnum.C, IntEnum.B, IntEnum.A }, Allocator.Temp), + new NativeArray(new IntEnum[] { IntEnum.B, IntEnum.C, IntEnum.B, IntEnum.A, IntEnum.C }, Allocator.Temp)); + } + else if (testType == typeof(UIntEnum)) + { + TestValueTypeNativeArray( + new NativeArray(new UIntEnum[] { UIntEnum.C, UIntEnum.B, UIntEnum.A }, Allocator.Temp), + new NativeArray(new UIntEnum[] { UIntEnum.B, UIntEnum.C, UIntEnum.B, UIntEnum.A, UIntEnum.C }, Allocator.Temp)); + } + else if (testType == typeof(LongEnum)) + { + TestValueTypeNativeArray( + new NativeArray(new LongEnum[] { LongEnum.C, LongEnum.B, LongEnum.A }, Allocator.Temp), + new NativeArray(new LongEnum[] { LongEnum.B, LongEnum.C, LongEnum.B, LongEnum.A, LongEnum.C }, Allocator.Temp)); + } + else if (testType == typeof(ULongEnum)) + { + TestValueTypeNativeArray( + new NativeArray(new ULongEnum[] { ULongEnum.C, ULongEnum.B, ULongEnum.A }, Allocator.Temp), + new NativeArray(new ULongEnum[] { ULongEnum.B, ULongEnum.C, ULongEnum.B, ULongEnum.A, ULongEnum.C }, Allocator.Temp)); + } + else if (testType == typeof(Vector2)) + { + TestValueTypeNativeArray( + new NativeArray(new Vector2[] { new Vector2(5, 10), new Vector2(15, 20) }, Allocator.Temp), + new NativeArray(new Vector2[] { new Vector2(25, 30), new Vector2(35, 40), new Vector2(45, 50) }, Allocator.Temp)); + } + else if (testType == typeof(Vector3)) + { + TestValueTypeNativeArray( + new NativeArray(new Vector3[] { new Vector3(5, 10, 15), new Vector3(20, 25, 30) }, Allocator.Temp), + new NativeArray(new Vector3[] { new Vector3(35, 40, 45), new Vector3(50, 55, 60), new Vector3(65, 70, 75) }, Allocator.Temp)); + } + else if (testType == typeof(Vector2Int)) + { + TestValueTypeNativeArray( + new NativeArray(new Vector2Int[] { new Vector2Int(5, 10), new Vector2Int(15, 20) }, Allocator.Temp), + new NativeArray(new Vector2Int[] { new Vector2Int(25, 30), new Vector2Int(35, 40), new Vector2Int(45, 50) }, Allocator.Temp)); + } + else if (testType == typeof(Vector3Int)) + { + TestValueTypeNativeArray( + new NativeArray(new Vector3Int[] { new Vector3Int(5, 10, 15), new Vector3Int(20, 25, 30) }, Allocator.Temp), + new NativeArray(new Vector3Int[] { new Vector3Int(35, 40, 45), new Vector3Int(50, 55, 60), new Vector3Int(65, 70, 75) }, Allocator.Temp)); + } + else if (testType == typeof(Vector4)) + { + TestValueTypeNativeArray( + new NativeArray(new Vector4[] { new Vector4(5, 10, 15, 20), new Vector4(25, 30, 35, 40) }, Allocator.Temp), + new NativeArray(new Vector4[] { new Vector4(45, 50, 55, 60), new Vector4(65, 70, 75, 80), new Vector4(85, 90, 95, 100) }, Allocator.Temp)); + } + else if (testType == typeof(Quaternion)) + { + TestValueTypeNativeArray( + new NativeArray(new Quaternion[] { new Quaternion(5, 10, 15, 20), new Quaternion(25, 30, 35, 40) }, Allocator.Temp), + new NativeArray(new Quaternion[] { new Quaternion(45, 50, 55, 60), new Quaternion(65, 70, 75, 80), new Quaternion(85, 90, 95, 100) }, Allocator.Temp)); + } + else if (testType == typeof(Color)) + { + TestValueTypeNativeArray( + new NativeArray(new Color[] { new Color(.5f, .10f, .15f), new Color(.20f, .25f, .30f) }, Allocator.Temp), + new NativeArray(new Color[] { new Color(.35f, .40f, .45f), new Color(.50f, .55f, .60f), new Color(.65f, .70f, .75f) }, Allocator.Temp)); + } + else if (testType == typeof(Color32)) + { + TestValueTypeNativeArray( + new NativeArray(new Color32[] { new Color32(5, 10, 15, 20), new Color32(25, 30, 35, 40) }, Allocator.Temp), + new NativeArray(new Color32[] { new Color32(45, 50, 55, 60), new Color32(65, 70, 75, 80), new Color32(85, 90, 95, 100) }, Allocator.Temp)); + } + else if (testType == typeof(Ray)) + { + TestValueTypeNativeArray( + new NativeArray(new Ray[] + { + new Ray(new Vector3(0, 1, 2), new Vector3(3, 4, 5)), + new Ray(new Vector3(6, 7, 8), new Vector3(9, 10, 11)), + }, Allocator.Temp), + new NativeArray(new Ray[] + { + new Ray(new Vector3(12, 13, 14), new Vector3(15, 16, 17)), + new Ray(new Vector3(18, 19, 20), new Vector3(21, 22, 23)), + new Ray(new Vector3(24, 25, 26), new Vector3(27, 28, 29)), + }, Allocator.Temp)); + } + else if (testType == typeof(Ray2D)) + { + TestValueTypeNativeArray( + new NativeArray(new Ray2D[] + { + new Ray2D(new Vector2(0, 1), new Vector2(3, 4)), + new Ray2D(new Vector2(6, 7), new Vector2(9, 10)), + }, Allocator.Temp), + new NativeArray(new Ray2D[] + { + new Ray2D(new Vector2(12, 13), new Vector2(15, 16)), + new Ray2D(new Vector2(18, 19), new Vector2(21, 22)), + new Ray2D(new Vector2(24, 25), new Vector2(27, 28)), + }, Allocator.Temp)); + } + else if (testType == typeof(NetworkVariableTestStruct)) + { + TestValueTypeNativeArray( + new NativeArray(new NetworkVariableTestStruct[] + { + NetworkVariableTestStruct.GetTestStruct(), + NetworkVariableTestStruct.GetTestStruct() + }, Allocator.Temp), + new NativeArray(new NetworkVariableTestStruct[] + { + NetworkVariableTestStruct.GetTestStruct(), + NetworkVariableTestStruct.GetTestStruct(), + NetworkVariableTestStruct.GetTestStruct() + }, Allocator.Temp)); + } + else if (testType == typeof(FixedString32Bytes)) + { + TestValueTypeNativeArray( + new NativeArray(new FixedString32Bytes[] + { + new FixedString32Bytes("foobar"), + new FixedString32Bytes("12345678901234567890123456789") + }, Allocator.Temp), + new NativeArray(new FixedString32Bytes[] + { + new FixedString32Bytes("BazQux"), + new FixedString32Bytes("98765432109876543210987654321"), + new FixedString32Bytes("FixedString32Bytes") + }, Allocator.Temp)); + } + } + + [Test] + public void WhenSerializingAndDeserializingValueTypeNativeListNetworkVariables_ValuesAreSerializedCorrectly( + + [Values(typeof(byte), typeof(sbyte), typeof(short), typeof(ushort), typeof(int), typeof(uint), + typeof(long), typeof(ulong), typeof(bool), typeof(char), typeof(float), typeof(double), + typeof(ByteEnum), typeof(SByteEnum), typeof(ShortEnum), typeof(UShortEnum), typeof(IntEnum), + typeof(UIntEnum), typeof(LongEnum), typeof(ULongEnum), typeof(Vector2), typeof(Vector3), + typeof(Vector2Int), typeof(Vector3Int), typeof(Vector4), typeof(Quaternion), typeof(Color), + typeof(Color32), typeof(Ray), typeof(Ray2D), typeof(NetworkVariableTestStruct), typeof(FixedString32Bytes))] + Type testType) + { + if (testType == typeof(byte)) + { + TestValueTypeNativeList( + new NativeList(Allocator.Temp) { byte.MinValue + 5, byte.MaxValue }, + new NativeList(Allocator.Temp) { 0, byte.MinValue + 10, byte.MaxValue - 10 }); + } + else if (testType == typeof(sbyte)) + { + TestValueTypeNativeList( + new NativeList(Allocator.Temp) { sbyte.MinValue + 5, sbyte.MaxValue }, + new NativeList(Allocator.Temp) { 0, sbyte.MinValue + 10, sbyte.MaxValue - 10 }); + } + else if (testType == typeof(short)) + { + TestValueTypeNativeList( + new NativeList(Allocator.Temp) { short.MinValue + 5, short.MaxValue }, + new NativeList(Allocator.Temp) { 0, short.MinValue + 10, short.MaxValue - 10 }); + } + else if (testType == typeof(ushort)) + { + TestValueTypeNativeList( + new NativeList(Allocator.Temp) { ushort.MinValue + 5, ushort.MaxValue }, + new NativeList(Allocator.Temp) { 0, ushort.MinValue + 10, ushort.MaxValue - 10 }); + } + else if (testType == typeof(int)) + { + TestValueTypeNativeList( + new NativeList(Allocator.Temp) { int.MinValue + 5, int.MaxValue }, + new NativeList(Allocator.Temp) { 0, int.MinValue + 10, int.MaxValue - 10 }); + } + else if (testType == typeof(uint)) + { + TestValueTypeNativeList( + new NativeList(Allocator.Temp) { uint.MinValue + 5, uint.MaxValue }, + new NativeList(Allocator.Temp) { 0, uint.MinValue + 10, uint.MaxValue - 10 }); + } + else if (testType == typeof(long)) + { + TestValueTypeNativeList( + new NativeList(Allocator.Temp) { long.MinValue + 5, long.MaxValue }, + new NativeList(Allocator.Temp) { 0, long.MinValue + 10, long.MaxValue - 10 }); + } + else if (testType == typeof(ulong)) + { + TestValueTypeNativeList( + new NativeList(Allocator.Temp) { ulong.MinValue + 5, ulong.MaxValue }, + new NativeList(Allocator.Temp) { 0, ulong.MinValue + 10, ulong.MaxValue - 10 }); + } + else if (testType == typeof(bool)) + { + TestValueTypeNativeList( + new NativeList(Allocator.Temp) { true, false, true }, + new NativeList(Allocator.Temp) { false, true, false, true, false }); + } + else if (testType == typeof(char)) + { + TestValueTypeNativeList( + new NativeList(Allocator.Temp) { 'z', ' ', '?' }, + new NativeList(Allocator.Temp) { 'n', 'e', 'w', ' ', 'v', 'a', 'l', 'u', 'e' }); + } + else if (testType == typeof(float)) + { + TestValueTypeNativeList( + new NativeList(Allocator.Temp) { float.MinValue + 5.12345678f, float.MaxValue }, + new NativeList(Allocator.Temp) { 0, float.MinValue + 10.987654321f, float.MaxValue - 10.135792468f }); + } + else if (testType == typeof(double)) + { + TestValueTypeNativeList( + new NativeList(Allocator.Temp) { double.MinValue + 5.12345678, double.MaxValue }, + new NativeList(Allocator.Temp) { 0, double.MinValue + 10.987654321, double.MaxValue - 10.135792468 }); + } + else if (testType == typeof(ByteEnum)) + { + TestValueTypeNativeList( + new NativeList(Allocator.Temp) { ByteEnum.C, ByteEnum.B, ByteEnum.A }, + new NativeList(Allocator.Temp) { ByteEnum.B, ByteEnum.C, ByteEnum.B, ByteEnum.A, ByteEnum.C }); + } + else if (testType == typeof(SByteEnum)) + { + TestValueTypeNativeList( + new NativeList(Allocator.Temp) { SByteEnum.C, SByteEnum.B, SByteEnum.A }, + new NativeList(Allocator.Temp) { SByteEnum.B, SByteEnum.C, SByteEnum.B, SByteEnum.A, SByteEnum.C }); + } + else if (testType == typeof(ShortEnum)) + { + TestValueTypeNativeList( + new NativeList(Allocator.Temp) { ShortEnum.C, ShortEnum.B, ShortEnum.A }, + new NativeList(Allocator.Temp) { ShortEnum.B, ShortEnum.C, ShortEnum.B, ShortEnum.A, ShortEnum.C }); + } + else if (testType == typeof(UShortEnum)) + { + TestValueTypeNativeList( + new NativeList(Allocator.Temp) { UShortEnum.C, UShortEnum.B, UShortEnum.A }, + new NativeList(Allocator.Temp) { UShortEnum.B, UShortEnum.C, UShortEnum.B, UShortEnum.A, UShortEnum.C }); + } + else if (testType == typeof(IntEnum)) + { + TestValueTypeNativeList( + new NativeList(Allocator.Temp) { IntEnum.C, IntEnum.B, IntEnum.A }, + new NativeList(Allocator.Temp) { IntEnum.B, IntEnum.C, IntEnum.B, IntEnum.A, IntEnum.C }); + } + else if (testType == typeof(UIntEnum)) + { + TestValueTypeNativeList( + new NativeList(Allocator.Temp) { UIntEnum.C, UIntEnum.B, UIntEnum.A }, + new NativeList(Allocator.Temp) { UIntEnum.B, UIntEnum.C, UIntEnum.B, UIntEnum.A, UIntEnum.C }); + } + else if (testType == typeof(LongEnum)) + { + TestValueTypeNativeList( + new NativeList(Allocator.Temp) { LongEnum.C, LongEnum.B, LongEnum.A }, + new NativeList(Allocator.Temp) { LongEnum.B, LongEnum.C, LongEnum.B, LongEnum.A, LongEnum.C }); + } + else if (testType == typeof(ULongEnum)) + { + TestValueTypeNativeList( + new NativeList(Allocator.Temp) { ULongEnum.C, ULongEnum.B, ULongEnum.A }, + new NativeList(Allocator.Temp) { ULongEnum.B, ULongEnum.C, ULongEnum.B, ULongEnum.A, ULongEnum.C }); + } + else if (testType == typeof(Vector2)) + { + TestValueTypeNativeList( + new NativeList(Allocator.Temp) { new Vector2(5, 10), new Vector2(15, 20) }, + new NativeList(Allocator.Temp) { new Vector2(25, 30), new Vector2(35, 40), new Vector2(45, 50) }); + } + else if (testType == typeof(Vector3)) + { + TestValueTypeNativeList( + new NativeList(Allocator.Temp) { new Vector3(5, 10, 15), new Vector3(20, 25, 30) }, + new NativeList(Allocator.Temp) { new Vector3(35, 40, 45), new Vector3(50, 55, 60), new Vector3(65, 70, 75) }); + } + else if (testType == typeof(Vector2Int)) + { + TestValueTypeNativeList( + new NativeList(Allocator.Temp) { new Vector2Int(5, 10), new Vector2Int(15, 20) }, + new NativeList(Allocator.Temp) { new Vector2Int(25, 30), new Vector2Int(35, 40), new Vector2Int(45, 50) }); + } + else if (testType == typeof(Vector3Int)) + { + TestValueTypeNativeList( + new NativeList(Allocator.Temp) { new Vector3Int(5, 10, 15), new Vector3Int(20, 25, 30) }, + new NativeList(Allocator.Temp) { new Vector3Int(35, 40, 45), new Vector3Int(50, 55, 60), new Vector3Int(65, 70, 75) }); + } + else if (testType == typeof(Vector4)) + { + TestValueTypeNativeList( + new NativeList(Allocator.Temp) { new Vector4(5, 10, 15, 20), new Vector4(25, 30, 35, 40) }, + new NativeList(Allocator.Temp) { new Vector4(45, 50, 55, 60), new Vector4(65, 70, 75, 80), new Vector4(85, 90, 95, 100) }); + } + else if (testType == typeof(Quaternion)) + { + TestValueTypeNativeList( + new NativeList(Allocator.Temp) { new Quaternion(5, 10, 15, 20), new Quaternion(25, 30, 35, 40) }, + new NativeList(Allocator.Temp) { new Quaternion(45, 50, 55, 60), new Quaternion(65, 70, 75, 80), new Quaternion(85, 90, 95, 100) }); + } + else if (testType == typeof(Color)) + { + TestValueTypeNativeList( + new NativeList(Allocator.Temp) { new Color(.5f, .10f, .15f), new Color(.20f, .25f, .30f) }, + new NativeList(Allocator.Temp) { new Color(.35f, .40f, .45f), new Color(.50f, .55f, .60f), new Color(.65f, .70f, .75f) }); + } + else if (testType == typeof(Color32)) + { + TestValueTypeNativeList( + new NativeList(Allocator.Temp) { new Color32(5, 10, 15, 20), new Color32(25, 30, 35, 40) }, + new NativeList(Allocator.Temp) { new Color32(45, 50, 55, 60), new Color32(65, 70, 75, 80), new Color32(85, 90, 95, 100) }); + } + else if (testType == typeof(Ray)) + { + TestValueTypeNativeList( + new NativeList(Allocator.Temp) + { + new Ray(new Vector3(0, 1, 2), new Vector3(3, 4, 5)), + new Ray(new Vector3(6, 7, 8), new Vector3(9, 10, 11)), + }, + new NativeList(Allocator.Temp) + { + new Ray(new Vector3(12, 13, 14), new Vector3(15, 16, 17)), + new Ray(new Vector3(18, 19, 20), new Vector3(21, 22, 23)), + new Ray(new Vector3(24, 25, 26), new Vector3(27, 28, 29)), + }); + } + else if (testType == typeof(Ray2D)) + { + TestValueTypeNativeList( + new NativeList(Allocator.Temp) + { + new Ray2D(new Vector2(0, 1), new Vector2(3, 4)), + new Ray2D(new Vector2(6, 7), new Vector2(9, 10)), + }, + new NativeList(Allocator.Temp) + { + new Ray2D(new Vector2(12, 13), new Vector2(15, 16)), + new Ray2D(new Vector2(18, 19), new Vector2(21, 22)), + new Ray2D(new Vector2(24, 25), new Vector2(27, 28)), + }); + } + else if (testType == typeof(NetworkVariableTestStruct)) + { + TestValueTypeNativeList( + new NativeList(Allocator.Temp) + { + NetworkVariableTestStruct.GetTestStruct(), + NetworkVariableTestStruct.GetTestStruct() + }, + new NativeList(Allocator.Temp) + { + NetworkVariableTestStruct.GetTestStruct(), + NetworkVariableTestStruct.GetTestStruct(), + NetworkVariableTestStruct.GetTestStruct() + }); + } + else if (testType == typeof(FixedString32Bytes)) + { + TestValueTypeNativeList( + new NativeList(Allocator.Temp) + { + new FixedString32Bytes("foobar"), + new FixedString32Bytes("12345678901234567890123456789") + }, + new NativeList(Allocator.Temp) + { + new FixedString32Bytes("BazQux"), + new FixedString32Bytes("98765432109876543210987654321"), + new FixedString32Bytes("FixedString32Bytes") + }); } } @@ -1179,7 +2147,8 @@ public void TestManagedINetworkSerializableNetworkVariablesDeserializeInPlace() { InMemoryValue = 1, Ints = new[] { 2, 3, 4 }, - Str = "five" + Str = "five", + Embedded = new EmbeddedManagedNetworkSerializableType { Int = 6 } } }; @@ -1188,11 +2157,13 @@ public void TestManagedINetworkSerializableNetworkVariablesDeserializeInPlace() Assert.AreEqual(1, variable.Value.InMemoryValue); Assert.AreEqual(new[] { 2, 3, 4 }, variable.Value.Ints); Assert.AreEqual("five", variable.Value.Str); + Assert.AreEqual(6, variable.Value.Embedded.Int); variable.Value = new ManagedNetworkSerializableType { InMemoryValue = 10, Ints = new[] { 20, 30, 40, 50 }, - Str = "sixty" + Str = "sixty", + Embedded = new EmbeddedManagedNetworkSerializableType { Int = 60 } }; using var reader = new FastBufferReader(writer, Allocator.None); @@ -1200,6 +2171,7 @@ public void TestManagedINetworkSerializableNetworkVariablesDeserializeInPlace() Assert.AreEqual(10, variable.Value.InMemoryValue, "In-memory value was not the same - in-place deserialization should not change this"); Assert.AreEqual(new[] { 2, 3, 4 }, variable.Value.Ints, "Ints were not correctly deserialized"); Assert.AreEqual("five", variable.Value.Str, "Str was not correctly deserialized"); + Assert.AreEqual(6, variable.Value.Embedded.Int, "Embedded int was not correctly deserialized"); } [Test] diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkVariableUserSerializableTypesTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkVariableUserSerializableTypesTests.cs index bb0c228b88..4cc8811202 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkVariableUserSerializableTypesTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkVariableUserSerializableTypesTests.cs @@ -143,6 +143,10 @@ public IEnumerator WhenUsingAUserSerializableNetworkVariableWithUserSerializatio value = new MyTypeOne(); reader.ReadValueSafe(out value.Value); }; + UserNetworkVariableSerialization.DuplicateValue = (in MyTypeOne value, ref MyTypeOne duplicatedValue) => + { + duplicatedValue = value; + }; var serverObject = SpawnObject(m_WorkingPrefab, m_ServerNetworkManager); var serverNetworkObject = serverObject.GetComponent(); @@ -172,6 +176,10 @@ public IEnumerator WhenUsingAUserSerializableNetworkVariableWithUserSerializatio { UserNetworkVariableSerialization.WriteValue = NetworkVariableUserSerializableTypesTestsExtensionMethods.WriteValueSafe; UserNetworkVariableSerialization.ReadValue = NetworkVariableUserSerializableTypesTestsExtensionMethods.ReadValueSafe; + UserNetworkVariableSerialization.DuplicateValue = (in MyTypeTwo value, ref MyTypeTwo duplicatedValue) => + { + duplicatedValue = value; + }; var serverObject = SpawnObject(m_ExtensionMethodPrefab, m_ServerNetworkManager); var serverNetworkObject = serverObject.GetComponent(); diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/RpcTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/RpcTests.cs index a5a02a568a..cb5f16d31b 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/RpcTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/RpcTests.cs @@ -4,9 +4,9 @@ using NUnit.Framework; using Unity.Collections; using Unity.Netcode.TestHelpers.Runtime; -using UnityEngine; using UnityEngine.TestTools; using Debug = UnityEngine.Debug; +using Vector3 = UnityEngine.Vector3; namespace Unity.Netcode.RuntimeTests { @@ -15,7 +15,8 @@ public class RpcTests : NetcodeIntegrationTest public class RpcTestNB : NetworkBehaviour { public event Action OnServer_Rpc; - public event Action OnTypedServer_Rpc; + public event Action, ServerRpcParams> OnNativeListServer_Rpc; + public event Action, FixedString32Bytes> OnTypedServer_Rpc; public event Action OnClient_Rpc; [ServerRpc] @@ -24,6 +25,13 @@ public void MyServerRpc(ulong clientId, ServerRpcParams param = default) OnServer_Rpc(clientId, param); } + [ServerRpc] + public void MyNativeListServerRpc(NativeList clientId, ServerRpcParams param = default) + { + OnNativeListServer_Rpc(clientId, param); + } + + [ClientRpc] public void MyClientRpc() { @@ -31,9 +39,9 @@ public void MyClientRpc() } [ServerRpc] - public void MyTypedServerRpc(Vector3 param1, Vector3[] param2, FixedString32Bytes param3) + public void MyTypedServerRpc(Vector3 param1, Vector3[] param2, NativeList param3, FixedString32Bytes param4) { - OnTypedServer_Rpc(param1, param2, param3); + OnTypedServer_Rpc(param1, param2, param3, param4); } } @@ -61,6 +69,11 @@ public IEnumerator TestRpcs() var vector3 = new Vector3(1, 2, 3); Vector3[] vector3s = new[] { new Vector3(4, 5, 6), new Vector3(7, 8, 9) }; + using var vector3sNativeList = new NativeList(Allocator.Persistent) + { + new Vector3(10, 11, 12), + new Vector3(13, 14, 15) + }; localClienRpcTestNB.OnClient_Rpc += () => { @@ -90,14 +103,17 @@ public IEnumerator TestRpcs() var str = new FixedString32Bytes("abcdefg"); - serverClientRpcTestNB.OnTypedServer_Rpc += (param1, param2, param3) => + serverClientRpcTestNB.OnTypedServer_Rpc += (param1, param2, param3, param4) => { Debug.Log("TypedServerRpc received on server object"); Assert.AreEqual(param1, vector3); Assert.AreEqual(param2.Length, vector3s.Length); Assert.AreEqual(param2[0], vector3s[0]); Assert.AreEqual(param2[1], vector3s[1]); - Assert.AreEqual(param3, str); + Assert.AreEqual(param3.Length, vector3s.Length); + Assert.AreEqual(param3[0], vector3sNativeList[0]); + Assert.AreEqual(param3[1], vector3sNativeList[1]); + Assert.AreEqual(param4, str); hasReceivedTypedServerRpc = true; }; @@ -105,7 +121,7 @@ public IEnumerator TestRpcs() localClienRpcTestNB.MyServerRpc(m_ClientNetworkManagers[0].LocalClientId); // Send TypedServerRpc - localClienRpcTestNB.MyTypedServerRpc(vector3, vector3s, str); + localClienRpcTestNB.MyTypedServerRpc(vector3, vector3s, vector3sNativeList, str); // Send ClientRpc serverClientRpcTestNB.MyClientRpc(); diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/RpcTypeSerializationTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/RpcTypeSerializationTests.cs new file mode 100644 index 0000000000..0014f1ddb7 --- /dev/null +++ b/com.unity.netcode.gameobjects/Tests/Runtime/RpcTypeSerializationTests.cs @@ -0,0 +1,1919 @@ +using System; +using System.Collections; +using System.Linq; +using NUnit.Framework; +using Unity.Collections; +using Unity.Netcode.TestHelpers.Runtime; +using UnityEngine; +using UnityEngine.TestTools; +using Quaternion = UnityEngine.Quaternion; +using Vector2 = UnityEngine.Vector2; +using Vector3 = UnityEngine.Vector3; +using Vector4 = UnityEngine.Vector4; + + +namespace Unity.Netcode.RuntimeTests +{ + public class RpcTypeSerializationTests : NetcodeIntegrationTest + { + public RpcTypeSerializationTests() + { + m_UseHost = false; + } + + public class RpcTestNB : NetworkBehaviour + { + public delegate void OnReceivedDelegate(object obj); + public OnReceivedDelegate OnReceived; + + [ClientRpc] + public void ByteClientRpc(byte value) + { + OnReceived(value); + } + + [ClientRpc] + public void ByteArrayClientRpc(byte[] value) + { + OnReceived(value); + } + + [ClientRpc] + public void ByteNativeArrayClientRpc(NativeArray value) + { + OnReceived(value); + } + + [ClientRpc] + public void ByteNativeListClientRpc(NativeList value) + { + OnReceived(value); + } + + [ClientRpc] + public void SbyteClientRpc(sbyte value) + { + OnReceived(value); + } + + [ClientRpc] + public void SbyteArrayClientRpc(sbyte[] value) + { + OnReceived(value); + } + + [ClientRpc] + public void SbyteNativeArrayClientRpc(NativeArray value) + { + OnReceived(value); + } + + [ClientRpc] + public void SbyteNativeListClientRpc(NativeList value) + { + OnReceived(value); + } + + [ClientRpc] + public void ShortClientRpc(short value) + { + OnReceived(value); + } + + [ClientRpc] + public void ShortArrayClientRpc(short[] value) + { + OnReceived(value); + } + + [ClientRpc] + public void ShortNativeArrayClientRpc(NativeArray value) + { + OnReceived(value); + } + + [ClientRpc] + public void ShortNativeListClientRpc(NativeList value) + { + OnReceived(value); + } + + [ClientRpc] + public void UshortClientRpc(ushort value) + { + OnReceived(value); + } + + [ClientRpc] + public void UshortArrayClientRpc(ushort[] value) + { + OnReceived(value); + } + + [ClientRpc] + public void UshortNativeArrayClientRpc(NativeArray value) + { + OnReceived(value); + } + + [ClientRpc] + public void UshortNativeListClientRpc(NativeList value) + { + OnReceived(value); + } + + [ClientRpc] + public void IntClientRpc(int value) + { + OnReceived(value); + } + + [ClientRpc] + public void IntArrayClientRpc(int[] value) + { + OnReceived(value); + } + + [ClientRpc] + public void IntNativeArrayClientRpc(NativeArray value) + { + OnReceived(value); + } + + [ClientRpc] + public void IntNativeListClientRpc(NativeList value) + { + OnReceived(value); + } + + [ClientRpc] + public void UintClientRpc(uint value) + { + OnReceived(value); + } + + [ClientRpc] + public void UintArrayClientRpc(uint[] value) + { + OnReceived(value); + } + + [ClientRpc] + public void UintNativeArrayClientRpc(NativeArray value) + { + OnReceived(value); + } + + [ClientRpc] + public void UintNativeListClientRpc(NativeList value) + { + OnReceived(value); + } + + [ClientRpc] + public void LongClientRpc(long value) + { + OnReceived(value); + } + + [ClientRpc] + public void LongArrayClientRpc(long[] value) + { + OnReceived(value); + } + + [ClientRpc] + public void LongNativeArrayClientRpc(NativeArray value) + { + OnReceived(value); + } + + [ClientRpc] + public void LongNativeListClientRpc(NativeList value) + { + OnReceived(value); + } + + [ClientRpc] + public void UlongClientRpc(ulong value) + { + OnReceived(value); + } + + [ClientRpc] + public void UlongArrayClientRpc(ulong[] value) + { + OnReceived(value); + } + + [ClientRpc] + public void UlongNativeArrayClientRpc(NativeArray value) + { + OnReceived(value); + } + + [ClientRpc] + public void UlongNativeListClientRpc(NativeList value) + { + OnReceived(value); + } + + [ClientRpc] + public void BoolClientRpc(bool value) + { + OnReceived(value); + } + + [ClientRpc] + public void BoolArrayClientRpc(bool[] value) + { + OnReceived(value); + } + + [ClientRpc] + public void BoolNativeArrayClientRpc(NativeArray value) + { + OnReceived(value); + } + + [ClientRpc] + public void BoolNativeListClientRpc(NativeList value) + { + OnReceived(value); + } + + [ClientRpc] + public void CharClientRpc(char value) + { + OnReceived(value); + } + + [ClientRpc] + public void CharArrayClientRpc(char[] value) + { + OnReceived(value); + } + + [ClientRpc] + public void CharNativeArrayClientRpc(NativeArray value) + { + OnReceived(value); + } + + [ClientRpc] + public void CharNativeListClientRpc(NativeList value) + { + OnReceived(value); + } + + [ClientRpc] + public void FloatClientRpc(float value) + { + OnReceived(value); + } + + [ClientRpc] + public void FloatArrayClientRpc(float[] value) + { + OnReceived(value); + } + + [ClientRpc] + public void FloatNativeArrayClientRpc(NativeArray value) + { + OnReceived(value); + } + + [ClientRpc] + public void FloatNativeListClientRpc(NativeList value) + { + OnReceived(value); + } + + [ClientRpc] + public void DoubleClientRpc(double value) + { + OnReceived(value); + } + + [ClientRpc] + public void DoubleArrayClientRpc(double[] value) + { + OnReceived(value); + } + + [ClientRpc] + public void DoubleNativeArrayClientRpc(NativeArray value) + { + OnReceived(value); + } + + [ClientRpc] + public void DoubleNativeListClientRpc(NativeList value) + { + OnReceived(value); + } + + [ClientRpc] + public void ByteEnumClientRpc(ByteEnum value) + { + OnReceived(value); + } + + [ClientRpc] + public void ByteEnumArrayClientRpc(ByteEnum[] value) + { + OnReceived(value); + } + + [ClientRpc] + public void ByteEnumNativeArrayClientRpc(NativeArray value) + { + OnReceived(value); + } + + [ClientRpc] + public void ByteEnumNativeListClientRpc(NativeList value) + { + OnReceived(value); + } + + [ClientRpc] + public void SByteEnumClientRpc(SByteEnum value) + { + OnReceived(value); + } + + [ClientRpc] + public void SByteEnumArrayClientRpc(SByteEnum[] value) + { + OnReceived(value); + } + + [ClientRpc] + public void SByteEnumNativeArrayClientRpc(NativeArray value) + { + OnReceived(value); + } + + [ClientRpc] + public void SByteEnumNativeListClientRpc(NativeList value) + { + OnReceived(value); + } + + [ClientRpc] + public void ShortEnumClientRpc(ShortEnum value) + { + OnReceived(value); + } + + [ClientRpc] + public void ShortEnumArrayClientRpc(ShortEnum[] value) + { + OnReceived(value); + } + + [ClientRpc] + public void ShortEnumNativeArrayClientRpc(NativeArray value) + { + OnReceived(value); + } + + [ClientRpc] + public void ShortEnumNativeListClientRpc(NativeList value) + { + OnReceived(value); + } + + [ClientRpc] + public void UShortEnumClientRpc(UShortEnum value) + { + OnReceived(value); + } + + [ClientRpc] + public void UShortEnumArrayClientRpc(UShortEnum[] value) + { + OnReceived(value); + } + + [ClientRpc] + public void UShortEnumNativeArrayClientRpc(NativeArray value) + { + OnReceived(value); + } + + [ClientRpc] + public void UShortEnumNativeListClientRpc(NativeList value) + { + OnReceived(value); + } + + [ClientRpc] + public void IntEnumClientRpc(IntEnum value) + { + OnReceived(value); + } + + [ClientRpc] + public void IntEnumArrayClientRpc(IntEnum[] value) + { + OnReceived(value); + } + + [ClientRpc] + public void IntEnumNativeArrayClientRpc(NativeArray value) + { + OnReceived(value); + } + + [ClientRpc] + public void IntEnumNativeListClientRpc(NativeList value) + { + OnReceived(value); + } + + [ClientRpc] + public void UIntEnumClientRpc(UIntEnum value) + { + OnReceived(value); + } + + [ClientRpc] + public void UIntEnumArrayClientRpc(UIntEnum[] value) + { + OnReceived(value); + } + + [ClientRpc] + public void UIntEnumNativeArrayClientRpc(NativeArray value) + { + OnReceived(value); + } + + [ClientRpc] + public void UIntEnumNativeListClientRpc(NativeList value) + { + OnReceived(value); + } + + [ClientRpc] + public void LongEnumClientRpc(LongEnum value) + { + OnReceived(value); + } + + [ClientRpc] + public void LongEnumArrayClientRpc(LongEnum[] value) + { + OnReceived(value); + } + + [ClientRpc] + public void LongEnumNativeArrayClientRpc(NativeArray value) + { + OnReceived(value); + } + + [ClientRpc] + public void LongEnumNativeListClientRpc(NativeList value) + { + OnReceived(value); + } + + [ClientRpc] + public void ULongEnumClientRpc(ULongEnum value) + { + OnReceived(value); + } + + [ClientRpc] + public void ULongEnumArrayClientRpc(ULongEnum[] value) + { + OnReceived(value); + } + + [ClientRpc] + public void ULongEnumNativeArrayClientRpc(NativeArray value) + { + OnReceived(value); + } + + [ClientRpc] + public void ULongEnumNativeListClientRpc(NativeList value) + { + OnReceived(value); + } + + [ClientRpc] + public void Vector2ClientRpc(Vector2 value) + { + OnReceived(value); + } + + [ClientRpc] + public void Vector2ArrayClientRpc(Vector2[] value) + { + OnReceived(value); + } + + [ClientRpc] + public void Vector2NativeArrayClientRpc(NativeArray value) + { + OnReceived(value); + } + + [ClientRpc] + public void Vector2NativeListClientRpc(NativeList value) + { + OnReceived(value); + } + + [ClientRpc] + public void Vector3ClientRpc(Vector3 value) + { + OnReceived(value); + } + + [ClientRpc] + public void Vector3ArrayClientRpc(Vector3[] value) + { + OnReceived(value); + } + + [ClientRpc] + public void Vector3NativeArrayClientRpc(NativeArray value) + { + OnReceived(value); + } + + [ClientRpc] + public void Vector3NativeListClientRpc(NativeList value) + { + OnReceived(value); + } + + [ClientRpc] + public void Vector2IntClientRpc(Vector2Int value) + { + OnReceived(value); + } + + [ClientRpc] + public void Vector2IntArrayClientRpc(Vector2Int[] value) + { + OnReceived(value); + } + + [ClientRpc] + public void Vector2IntNativeArrayClientRpc(NativeArray value) + { + OnReceived(value); + } + + [ClientRpc] + public void Vector2IntNativeListClientRpc(NativeList value) + { + OnReceived(value); + } + + [ClientRpc] + public void Vector3IntClientRpc(Vector3Int value) + { + OnReceived(value); + } + + [ClientRpc] + public void Vector3IntArrayClientRpc(Vector3Int[] value) + { + OnReceived(value); + } + + [ClientRpc] + public void Vector3IntNativeArrayClientRpc(NativeArray value) + { + OnReceived(value); + } + + [ClientRpc] + public void Vector3IntNativeListClientRpc(NativeList value) + { + OnReceived(value); + } + + [ClientRpc] + public void Vector4ClientRpc(Vector4 value) + { + OnReceived(value); + } + + [ClientRpc] + public void Vector4ArrayClientRpc(Vector4[] value) + { + OnReceived(value); + } + + [ClientRpc] + public void Vector4NativeArrayClientRpc(NativeArray value) + { + OnReceived(value); + } + + [ClientRpc] + public void Vector4NativeListClientRpc(NativeList value) + { + OnReceived(value); + } + + [ClientRpc] + public void QuaternionClientRpc(Quaternion value) + { + OnReceived(value); + } + + [ClientRpc] + public void QuaternionArrayClientRpc(Quaternion[] value) + { + OnReceived(value); + } + + [ClientRpc] + public void QuaternionNativeArrayClientRpc(NativeArray value) + { + OnReceived(value); + } + + [ClientRpc] + public void QuaternionNativeListClientRpc(NativeList value) + { + OnReceived(value); + } + + [ClientRpc] + public void ColorClientRpc(Color value) + { + OnReceived(value); + } + + [ClientRpc] + public void ColorArrayClientRpc(Color[] value) + { + OnReceived(value); + } + + [ClientRpc] + public void ColorNativeArrayClientRpc(NativeArray value) + { + OnReceived(value); + } + + [ClientRpc] + public void ColorNativeListClientRpc(NativeList value) + { + OnReceived(value); + } + + [ClientRpc] + public void Color32ClientRpc(Color32 value) + { + OnReceived(value); + } + + [ClientRpc] + public void Color32ArrayClientRpc(Color32[] value) + { + OnReceived(value); + } + + [ClientRpc] + public void Color32NativeArrayClientRpc(NativeArray value) + { + OnReceived(value); + } + + [ClientRpc] + public void Color32NativeListClientRpc(NativeList value) + { + OnReceived(value); + } + + [ClientRpc] + public void RayClientRpc(Ray value) + { + OnReceived(value); + } + + [ClientRpc] + public void RayArrayClientRpc(Ray[] value) + { + OnReceived(value); + } + + [ClientRpc] + public void RayNativeArrayClientRpc(NativeArray value) + { + OnReceived(value); + } + + [ClientRpc] + public void RayNativeListClientRpc(NativeList value) + { + OnReceived(value); + } + + [ClientRpc] + public void Ray2DClientRpc(Ray2D value) + { + OnReceived(value); + } + + [ClientRpc] + public void Ray2DArrayClientRpc(Ray2D[] value) + { + OnReceived(value); + } + + [ClientRpc] + public void Ray2DNativeArrayClientRpc(NativeArray value) + { + OnReceived(value); + } + + [ClientRpc] + public void Ray2DNativeListClientRpc(NativeList value) + { + OnReceived(value); + } + + [ClientRpc] + public void NetworkVariableTestStructClientRpc(NetworkVariableTestStruct value) + { + OnReceived(value); + } + + [ClientRpc] + public void NetworkVariableTestStructArrayClientRpc(NetworkVariableTestStruct[] value) + { + OnReceived(value); + } + + [ClientRpc] + public void NetworkVariableTestStructNativeArrayClientRpc(NativeArray value) + { + OnReceived(value); + } + + [ClientRpc] + public void NetworkVariableTestStructNativeListClientRpc(NativeList value) + { + OnReceived(value); + } + + [ClientRpc] + public void FixedString32BytesClientRpc(FixedString32Bytes value) + { + OnReceived(value); + } + + [ClientRpc] + public void FixedString32BytesArrayClientRpc(FixedString32Bytes[] value) + { + OnReceived(value); + } + + [ClientRpc] + public void FixedString32BytesNativeArrayClientRpc(NativeArray value) + { + OnReceived(value); + } + + [ClientRpc] + public void FixedString32BytesNativeListClientRpc(NativeList value) + { + OnReceived(value); + } + } + + protected override int NumberOfClients => 1; + + protected override void OnCreatePlayerPrefab() + { + m_PlayerPrefab.AddComponent(); + } + + public IEnumerator TestValueType(T firstTest, T secondTest) where T : unmanaged + { + var methods = typeof(RpcTestNB).GetMethods(); + foreach (var method in methods) + { + var parms = method.GetParameters(); + if (parms.Length != 1) + { + continue; + } + if (parms[0].ParameterType == typeof(T) && method.Name.EndsWith("ClientRpc")) + { + object receivedValue = null; + + // This is the *SERVER VERSION* of the *CLIENT PLAYER* RpcTestNB component + var serverObject = m_PlayerNetworkObjects[m_ServerNetworkManager.LocalClientId][m_ClientNetworkManagers[0].LocalClientId].GetComponent(); + + // This is the *CLIENT VERSION* of the *CLIENT PLAYER* RpcTestNB component + var clientObject = m_PlayerNetworkObjects[m_ClientNetworkManagers[0].LocalClientId][m_ClientNetworkManagers[0].LocalClientId].GetComponent(); + + clientObject.OnReceived = o => + { + receivedValue = o; + Debug.Log($"Received value {o}"); + }; + Debug.Log($"Sending first RPC with {firstTest}"); + method.Invoke(serverObject, new object[] { firstTest }); + + yield return WaitForMessageReceived(m_ClientNetworkManagers.ToList()); + + Assert.IsNotNull(receivedValue); + + Assert.AreEqual(receivedValue.GetType(), typeof(T)); + var value = (T)receivedValue; + Assert.IsTrue(NetworkVariableSerialization.AreEqual(ref value, ref firstTest)); + + receivedValue = null; + + Debug.Log($"Sending second RPC with {secondTest}"); + method.Invoke(serverObject, new object[] { secondTest }); + + yield return WaitForMessageReceived(m_ClientNetworkManagers.ToList()); + + Assert.IsNotNull(receivedValue); + + Assert.AreEqual(receivedValue.GetType(), typeof(T)); + value = (T)receivedValue; + Assert.IsTrue(NetworkVariableSerialization.AreEqual(ref value, ref secondTest)); + yield break; + } + } + Assert.Fail($"Could not find RPC function for {typeof(T).Name}"); + } + + public IEnumerator TestValueTypeArray(T[] firstTest, T[] secondTest) where T : unmanaged + { + var methods = typeof(RpcTestNB).GetMethods(); + foreach (var method in methods) + { + var parms = method.GetParameters(); + if (parms.Length != 1) + { + continue; + } + if (parms[0].ParameterType == typeof(T[]) && method.Name.EndsWith("ClientRpc")) + { + object receivedValue = null; + + // This is the *SERVER VERSION* of the *CLIENT PLAYER* RpcTestNB component + var serverObject = m_PlayerNetworkObjects[m_ServerNetworkManager.LocalClientId][m_ClientNetworkManagers[0].LocalClientId].GetComponent(); + + // This is the *CLIENT VERSION* of the *CLIENT PLAYER* RpcTestNB component + var clientObject = m_PlayerNetworkObjects[m_ClientNetworkManagers[0].LocalClientId][m_ClientNetworkManagers[0].LocalClientId].GetComponent(); + + clientObject.OnReceived = o => + { + receivedValue = o; + Debug.Log($"Received value {o}"); + }; + Debug.Log($"Sending first RPC with {firstTest}"); + method.Invoke(serverObject, new object[] { firstTest }); + + yield return WaitForMessageReceived(m_ClientNetworkManagers.ToList()); + + Assert.IsNotNull(receivedValue); + + Assert.AreEqual(receivedValue.GetType(), typeof(T[])); + var value = (T[])receivedValue; + Assert.AreEqual(value, firstTest); + + receivedValue = null; + + Debug.Log($"Sending second RPC with {secondTest}"); + method.Invoke(serverObject, new object[] { secondTest }); + + yield return WaitForMessageReceived(m_ClientNetworkManagers.ToList()); + + Assert.IsNotNull(receivedValue); + + Assert.AreEqual(receivedValue.GetType(), typeof(T[])); + value = (T[])receivedValue; + Assert.AreEqual(value, secondTest); + + method.Invoke(serverObject, new object[] { null }); + + yield return WaitForMessageReceived(m_ClientNetworkManagers.ToList()); + + Assert.IsNull(receivedValue); + yield break; + } + } + Assert.Fail($"Could not find RPC function for {typeof(T).Name}"); + } + + public IEnumerator TestValueTypeNativeArray(NativeArray firstTest, NativeArray secondTest) where T : unmanaged + { + var methods = typeof(RpcTestNB).GetMethods(); + foreach (var method in methods) + { + var parms = method.GetParameters(); + if (parms.Length != 1) + { + continue; + } + if (parms[0].ParameterType == typeof(NativeArray) && method.Name.EndsWith("ClientRpc")) + { + var receivedValue = new NativeArray(); + + // This is the *SERVER VERSION* of the *CLIENT PLAYER* RpcTestNB component + var serverObject = m_PlayerNetworkObjects[m_ServerNetworkManager.LocalClientId][m_ClientNetworkManagers[0].LocalClientId].GetComponent(); + + // This is the *CLIENT VERSION* of the *CLIENT PLAYER* RpcTestNB component + var clientObject = m_PlayerNetworkObjects[m_ClientNetworkManagers[0].LocalClientId][m_ClientNetworkManagers[0].LocalClientId].GetComponent(); + + clientObject.OnReceived = o => + { + Assert.AreEqual(o.GetType(), typeof(NativeArray)); + var oAsArray = (NativeArray)o; + receivedValue = new NativeArray(oAsArray, Allocator.Persistent); + Debug.Log($"Received value {o}"); + }; + Debug.Log($"Sending first RPC with {firstTest}"); + method.Invoke(serverObject, new object[] { firstTest }); + + yield return WaitForMessageReceived(m_ClientNetworkManagers.ToList()); + + Assert.IsTrue(receivedValue.IsCreated); + + Assert.IsTrue(NetworkVariableSerialization>.AreEqual(ref receivedValue, ref firstTest)); + receivedValue.Dispose(); + firstTest.Dispose(); + + receivedValue = new NativeArray(); + + Debug.Log($"Sending second RPC with {secondTest}"); + method.Invoke(serverObject, new object[] { secondTest }); + + yield return WaitForMessageReceived(m_ClientNetworkManagers.ToList()); + + Assert.IsTrue(receivedValue.IsCreated); + + Assert.IsTrue(NetworkVariableSerialization>.AreEqual(ref receivedValue, ref secondTest)); + receivedValue.Dispose(); + secondTest.Dispose(); + yield break; + } + } + Assert.Fail($"Could not find RPC function for {typeof(T).Name}"); + } + + public IEnumerator TestValueTypeNativeList(NativeList firstTest, NativeList secondTest) where T : unmanaged + { + var methods = typeof(RpcTestNB).GetMethods(); + foreach (var method in methods) + { + var parms = method.GetParameters(); + if (parms.Length != 1) + { + continue; + } + if (parms[0].ParameterType == typeof(NativeList) && method.Name.EndsWith("ClientRpc")) + { + var receivedValue = new NativeList(); + + // This is the *SERVER VERSION* of the *CLIENT PLAYER* RpcTestNB component + var serverObject = m_PlayerNetworkObjects[m_ServerNetworkManager.LocalClientId][m_ClientNetworkManagers[0].LocalClientId].GetComponent(); + + // This is the *CLIENT VERSION* of the *CLIENT PLAYER* RpcTestNB component + var clientObject = m_PlayerNetworkObjects[m_ClientNetworkManagers[0].LocalClientId][m_ClientNetworkManagers[0].LocalClientId].GetComponent(); + + clientObject.OnReceived = o => + { + Assert.AreEqual(o.GetType(), typeof(NativeList)); + var oAsList = (NativeList)o; + receivedValue = new NativeList(oAsList.Length, Allocator.Persistent); + foreach (var item in oAsList) + { + receivedValue.Add(item); + } + Debug.Log($"Received value {o}"); + }; + Debug.Log($"Sending first RPC with {firstTest}"); + method.Invoke(serverObject, new object[] { firstTest }); + + yield return WaitForMessageReceived(m_ClientNetworkManagers.ToList()); + + Assert.IsTrue(receivedValue.IsCreated); + + Assert.IsTrue(NetworkVariableSerialization>.AreEqual(ref receivedValue, ref firstTest)); + receivedValue.Dispose(); + firstTest.Dispose(); + + receivedValue = new NativeList(); + + Debug.Log($"Sending second RPC with {secondTest}"); + method.Invoke(serverObject, new object[] { secondTest }); + + yield return WaitForMessageReceived(m_ClientNetworkManagers.ToList()); + + Assert.IsTrue(receivedValue.IsCreated); + + Assert.IsTrue(NetworkVariableSerialization>.AreEqual(ref receivedValue, ref secondTest)); + receivedValue.Dispose(); + secondTest.Dispose(); + yield break; + } + } + Assert.Fail($"Could not find RPC function for {typeof(T).Name}"); + } + + [UnityTest] + public IEnumerator WhenSendingAValueTypeOverAnRpc_ValuesAreSerializedCorrectly( + + [Values(typeof(byte), typeof(sbyte), typeof(short), typeof(ushort), typeof(int), typeof(uint), + typeof(long), typeof(ulong), typeof(bool), typeof(char), typeof(float), typeof(double), + typeof(ByteEnum), typeof(SByteEnum), typeof(ShortEnum), typeof(UShortEnum), typeof(IntEnum), + typeof(UIntEnum), typeof(LongEnum), typeof(ULongEnum), typeof(Vector2), typeof(Vector3), + typeof(Vector2Int), typeof(Vector3Int), typeof(Vector4), typeof(Quaternion), typeof(Color), + typeof(Color32), typeof(Ray), typeof(Ray2D), typeof(NetworkVariableTestStruct), typeof(FixedString32Bytes))] + Type testType) + { + if (testType == typeof(byte)) + { + yield return TestValueType(byte.MinValue + 5, byte.MaxValue); + } + else if (testType == typeof(sbyte)) + { + yield return TestValueType(sbyte.MinValue + 5, sbyte.MaxValue); + } + else if (testType == typeof(short)) + { + yield return TestValueType(short.MinValue + 5, short.MaxValue); + } + else if (testType == typeof(ushort)) + { + yield return TestValueType(ushort.MinValue + 5, ushort.MaxValue); + } + else if (testType == typeof(int)) + { + yield return TestValueType(int.MinValue + 5, int.MaxValue); + } + else if (testType == typeof(uint)) + { + yield return TestValueType(uint.MinValue + 5, uint.MaxValue); + } + else if (testType == typeof(long)) + { + yield return TestValueType(long.MinValue + 5, long.MaxValue); + } + else if (testType == typeof(ulong)) + { + yield return TestValueType(ulong.MinValue + 5, ulong.MaxValue); + } + else if (testType == typeof(bool)) + { + yield return TestValueType(true, false); + } + else if (testType == typeof(char)) + { + yield return TestValueType('z', ' '); + } + else if (testType == typeof(float)) + { + yield return TestValueType(float.MinValue + 5.12345678f, float.MaxValue); + } + else if (testType == typeof(double)) + { + yield return TestValueType(double.MinValue + 5.12345678, double.MaxValue); + } + else if (testType == typeof(ByteEnum)) + { + yield return TestValueType(ByteEnum.B, ByteEnum.C); + } + else if (testType == typeof(SByteEnum)) + { + yield return TestValueType(SByteEnum.B, SByteEnum.C); + } + else if (testType == typeof(ShortEnum)) + { + yield return TestValueType(ShortEnum.B, ShortEnum.C); + } + else if (testType == typeof(UShortEnum)) + { + yield return TestValueType(UShortEnum.B, UShortEnum.C); + } + else if (testType == typeof(IntEnum)) + { + yield return TestValueType(IntEnum.B, IntEnum.C); + } + else if (testType == typeof(UIntEnum)) + { + yield return TestValueType(UIntEnum.B, UIntEnum.C); + } + else if (testType == typeof(LongEnum)) + { + yield return TestValueType(LongEnum.B, LongEnum.C); + } + else if (testType == typeof(ULongEnum)) + { + yield return TestValueType(ULongEnum.B, ULongEnum.C); + } + else if (testType == typeof(Vector2)) + { + yield return TestValueType( + new Vector2(5, 10), + new Vector2(15, 20)); + } + else if (testType == typeof(Vector3)) + { + yield return TestValueType( + new Vector3(5, 10, 15), + new Vector3(20, 25, 30)); + } + else if (testType == typeof(Vector2Int)) + { + yield return TestValueType( + new Vector2Int(5, 10), + new Vector2Int(15, 20)); + } + else if (testType == typeof(Vector3Int)) + { + yield return TestValueType( + new Vector3Int(5, 10, 15), + new Vector3Int(20, 25, 30)); + } + else if (testType == typeof(Vector4)) + { + yield return TestValueType( + new Vector4(5, 10, 15, 20), + new Vector4(25, 30, 35, 40)); + } + else if (testType == typeof(Quaternion)) + { + yield return TestValueType( + new Quaternion(5, 10, 15, 20), + new Quaternion(25, 30, 35, 40)); + } + else if (testType == typeof(Color)) + { + yield return TestValueType( + new Color(1, 0, 0), + new Color(0, 1, 1)); + } + else if (testType == typeof(Color32)) + { + yield return TestValueType( + new Color32(255, 0, 0, 128), + new Color32(0, 255, 255, 255)); + } + else if (testType == typeof(Ray)) + { + yield return TestValueType( + new Ray(new Vector3(0, 1, 2), new Vector3(3, 4, 5)), + new Ray(new Vector3(6, 7, 8), new Vector3(9, 10, 11))); + } + else if (testType == typeof(Ray2D)) + { + yield return TestValueType( + new Ray2D(new Vector2(0, 1), new Vector2(2, 3)), + new Ray2D(new Vector2(4, 5), new Vector2(6, 7))); + } + else if (testType == typeof(NetworkVariableTestStruct)) + { + yield return TestValueType(NetworkVariableTestStruct.GetTestStruct(), NetworkVariableTestStruct.GetTestStruct()); + } + else if (testType == typeof(FixedString32Bytes)) + { + yield return TestValueType(new FixedString32Bytes("foobar"), new FixedString32Bytes("12345678901234567890123456789")); + } + } + + [UnityTest] + public IEnumerator WhenSendingAnArrayOfValueTypesOverAnRpc_ValuesAreSerializedCorrectly( + + [Values(typeof(byte), typeof(sbyte), typeof(short), typeof(ushort), typeof(int), typeof(uint), + typeof(long), typeof(ulong), typeof(bool), typeof(char), typeof(float), typeof(double), + typeof(ByteEnum), typeof(SByteEnum), typeof(ShortEnum), typeof(UShortEnum), typeof(IntEnum), + typeof(UIntEnum), typeof(LongEnum), typeof(ULongEnum), typeof(Vector2), typeof(Vector3), + typeof(Vector2Int), typeof(Vector3Int), typeof(Vector4), typeof(Quaternion), typeof(Color), + typeof(Color32), typeof(Ray), typeof(Ray2D), typeof(NetworkVariableTestStruct), typeof(FixedString32Bytes))] + Type testType) + { + if (testType == typeof(byte)) + { + yield return TestValueTypeArray( + new byte[] { byte.MinValue + 5, byte.MaxValue }, + new byte[] { 0, byte.MinValue + 10, byte.MaxValue - 10 }); + } + else if (testType == typeof(sbyte)) + { + yield return TestValueTypeArray( + new sbyte[] { sbyte.MinValue + 5, sbyte.MaxValue }, + new sbyte[] { 0, sbyte.MinValue + 10, sbyte.MaxValue - 10 }); + } + else if (testType == typeof(short)) + { + yield return TestValueTypeArray( + new short[] { short.MinValue + 5, short.MaxValue }, + new short[] { 0, short.MinValue + 10, short.MaxValue - 10 }); + } + else if (testType == typeof(ushort)) + { + yield return TestValueTypeArray( + new ushort[] { ushort.MinValue + 5, ushort.MaxValue }, + new ushort[] { 0, ushort.MinValue + 10, ushort.MaxValue - 10 }); + } + else if (testType == typeof(int)) + { + yield return TestValueTypeArray( + new int[] { int.MinValue + 5, int.MaxValue }, + new int[] { 0, int.MinValue + 10, int.MaxValue - 10 }); + } + else if (testType == typeof(uint)) + { + yield return TestValueTypeArray( + new uint[] { uint.MinValue + 5, uint.MaxValue }, + new uint[] { 0, uint.MinValue + 10, uint.MaxValue - 10 }); + } + else if (testType == typeof(long)) + { + yield return TestValueTypeArray( + new long[] { long.MinValue + 5, long.MaxValue }, + new long[] { 0, long.MinValue + 10, long.MaxValue - 10 }); + } + else if (testType == typeof(ulong)) + { + yield return TestValueTypeArray( + new ulong[] { ulong.MinValue + 5, ulong.MaxValue }, + new ulong[] { 0, ulong.MinValue + 10, ulong.MaxValue - 10 }); + } + else if (testType == typeof(bool)) + { + yield return TestValueTypeArray( + new bool[] { true, false, true }, + new bool[] { false, true, false, true, false }); + } + else if (testType == typeof(char)) + { + yield return TestValueTypeArray( + new char[] { 'z', ' ', '?' }, + new char[] { 'n', 'e', 'w', ' ', 'v', 'a', 'l', 'u', 'e' }); + } + else if (testType == typeof(float)) + { + yield return TestValueTypeArray( + new float[] { float.MinValue + 5.12345678f, float.MaxValue }, + new float[] { 0, float.MinValue + 10.987654321f, float.MaxValue - 10.135792468f }); + } + else if (testType == typeof(double)) + { + yield return TestValueTypeArray( + new double[] { double.MinValue + 5.12345678, double.MaxValue }, + new double[] { 0, double.MinValue + 10.987654321, double.MaxValue - 10.135792468 }); + } + else if (testType == typeof(ByteEnum)) + { + yield return TestValueTypeArray( + new ByteEnum[] { ByteEnum.C, ByteEnum.B, ByteEnum.A }, + new ByteEnum[] { ByteEnum.B, ByteEnum.C, ByteEnum.B, ByteEnum.A, ByteEnum.C }); + } + else if (testType == typeof(SByteEnum)) + { + yield return TestValueTypeArray( + new SByteEnum[] { SByteEnum.C, SByteEnum.B, SByteEnum.A }, + new SByteEnum[] { SByteEnum.B, SByteEnum.C, SByteEnum.B, SByteEnum.A, SByteEnum.C }); + } + else if (testType == typeof(ShortEnum)) + { + yield return TestValueTypeArray( + new ShortEnum[] { ShortEnum.C, ShortEnum.B, ShortEnum.A }, + new ShortEnum[] { ShortEnum.B, ShortEnum.C, ShortEnum.B, ShortEnum.A, ShortEnum.C }); + } + else if (testType == typeof(UShortEnum)) + { + yield return TestValueTypeArray( + new UShortEnum[] { UShortEnum.C, UShortEnum.B, UShortEnum.A }, + new UShortEnum[] { UShortEnum.B, UShortEnum.C, UShortEnum.B, UShortEnum.A, UShortEnum.C }); + } + else if (testType == typeof(IntEnum)) + { + yield return TestValueTypeArray( + new IntEnum[] { IntEnum.C, IntEnum.B, IntEnum.A }, + new IntEnum[] { IntEnum.B, IntEnum.C, IntEnum.B, IntEnum.A, IntEnum.C }); + } + else if (testType == typeof(UIntEnum)) + { + yield return TestValueTypeArray( + new UIntEnum[] { UIntEnum.C, UIntEnum.B, UIntEnum.A }, + new UIntEnum[] { UIntEnum.B, UIntEnum.C, UIntEnum.B, UIntEnum.A, UIntEnum.C }); + } + else if (testType == typeof(LongEnum)) + { + yield return TestValueTypeArray( + new LongEnum[] { LongEnum.C, LongEnum.B, LongEnum.A }, + new LongEnum[] { LongEnum.B, LongEnum.C, LongEnum.B, LongEnum.A, LongEnum.C }); + } + else if (testType == typeof(ULongEnum)) + { + yield return TestValueTypeArray( + new ULongEnum[] { ULongEnum.C, ULongEnum.B, ULongEnum.A }, + new ULongEnum[] { ULongEnum.B, ULongEnum.C, ULongEnum.B, ULongEnum.A, ULongEnum.C }); + } + else if (testType == typeof(Vector2)) + { + yield return TestValueTypeArray( + new Vector2[] { new Vector2(5, 10), new Vector2(15, 20) }, + new Vector2[] { new Vector2(25, 30), new Vector2(35, 40), new Vector2(45, 50) }); + } + else if (testType == typeof(Vector3)) + { + yield return TestValueTypeArray( + new Vector3[] { new Vector3(5, 10, 15), new Vector3(20, 25, 30) }, + new Vector3[] { new Vector3(35, 40, 45), new Vector3(50, 55, 60), new Vector3(65, 70, 75) }); + } + else if (testType == typeof(Vector2Int)) + { + yield return TestValueTypeArray( + new Vector2Int[] { new Vector2Int(5, 10), new Vector2Int(15, 20) }, + new Vector2Int[] { new Vector2Int(25, 30), new Vector2Int(35, 40), new Vector2Int(45, 50) }); + } + else if (testType == typeof(Vector3Int)) + { + yield return TestValueTypeArray( + new Vector3Int[] { new Vector3Int(5, 10, 15), new Vector3Int(20, 25, 30) }, + new Vector3Int[] { new Vector3Int(35, 40, 45), new Vector3Int(50, 55, 60), new Vector3Int(65, 70, 75) }); + } + else if (testType == typeof(Vector4)) + { + yield return TestValueTypeArray( + new Vector4[] { new Vector4(5, 10, 15, 20), new Vector4(25, 30, 35, 40) }, + new Vector4[] { new Vector4(45, 50, 55, 60), new Vector4(65, 70, 75, 80), new Vector4(85, 90, 95, 100) }); + } + else if (testType == typeof(Quaternion)) + { + yield return TestValueTypeArray( + new Quaternion[] { new Quaternion(5, 10, 15, 20), new Quaternion(25, 30, 35, 40) }, + new Quaternion[] { new Quaternion(45, 50, 55, 60), new Quaternion(65, 70, 75, 80), new Quaternion(85, 90, 95, 100) }); + } + else if (testType == typeof(Color)) + { + yield return TestValueTypeArray( + new Color[] { new Color(.5f, .10f, .15f), new Color(.20f, .25f, .30f) }, + new Color[] { new Color(.35f, .40f, .45f), new Color(.50f, .55f, .60f), new Color(.65f, .70f, .75f) }); + } + else if (testType == typeof(Color32)) + { + yield return TestValueTypeArray( + new Color32[] { new Color32(5, 10, 15, 20), new Color32(25, 30, 35, 40) }, + new Color32[] { new Color32(45, 50, 55, 60), new Color32(65, 70, 75, 80), new Color32(85, 90, 95, 100) }); + } + else if (testType == typeof(Ray)) + { + yield return TestValueTypeArray( + new Ray[] + { + new Ray(new Vector3(0, 1, 2), new Vector3(3, 4, 5)), + new Ray(new Vector3(6, 7, 8), new Vector3(9, 10, 11)), + }, + new Ray[] + { + new Ray(new Vector3(12, 13, 14), new Vector3(15, 16, 17)), + new Ray(new Vector3(18, 19, 20), new Vector3(21, 22, 23)), + new Ray(new Vector3(24, 25, 26), new Vector3(27, 28, 29)), + }); + } + else if (testType == typeof(Ray2D)) + { + yield return TestValueTypeArray( + new Ray2D[] + { + new Ray2D(new Vector2(0, 1), new Vector2(3, 4)), + new Ray2D(new Vector2(6, 7), new Vector2(9, 10)), + }, + new Ray2D[] + { + new Ray2D(new Vector2(12, 13), new Vector2(15, 16)), + new Ray2D(new Vector2(18, 19), new Vector2(21, 22)), + new Ray2D(new Vector2(24, 25), new Vector2(27, 28)), + }); + } + else if (testType == typeof(NetworkVariableTestStruct)) + { + yield return TestValueTypeArray( + new NetworkVariableTestStruct[] + { + NetworkVariableTestStruct.GetTestStruct(), + NetworkVariableTestStruct.GetTestStruct() + }, + new NetworkVariableTestStruct[] + { + NetworkVariableTestStruct.GetTestStruct(), + NetworkVariableTestStruct.GetTestStruct(), + NetworkVariableTestStruct.GetTestStruct() + }); + } + else if (testType == typeof(FixedString32Bytes)) + { + yield return TestValueTypeArray( + new FixedString32Bytes[] + { + new FixedString32Bytes("foobar"), + new FixedString32Bytes("12345678901234567890123456789") + }, + new FixedString32Bytes[] + { + new FixedString32Bytes("BazQux"), + new FixedString32Bytes("98765432109876543210987654321"), + new FixedString32Bytes("FixedString32Bytes") + }); + } + } + + [UnityTest] + public IEnumerator WhenSendingANativeArrayOfValueTypesOverAnRpc_ValuesAreSerializedCorrectly( + + [Values(typeof(byte), typeof(sbyte), typeof(short), typeof(ushort), typeof(int), typeof(uint), + typeof(long), typeof(ulong), typeof(bool), typeof(char), typeof(float), typeof(double), + typeof(ByteEnum), typeof(SByteEnum), typeof(ShortEnum), typeof(UShortEnum), typeof(IntEnum), + typeof(UIntEnum), typeof(LongEnum), typeof(ULongEnum), typeof(Vector2), typeof(Vector3), + typeof(Vector2Int), typeof(Vector3Int), typeof(Vector4), typeof(Quaternion), typeof(Color), + typeof(Color32), typeof(Ray), typeof(Ray2D), typeof(NetworkVariableTestStruct), typeof(FixedString32Bytes))] + Type testType) + { + if (testType == typeof(byte)) + { + yield return TestValueTypeNativeArray( + new NativeArray(new byte[] { byte.MinValue + 5, byte.MaxValue }, Allocator.Persistent), + new NativeArray(new byte[] { 0, byte.MinValue + 10, byte.MaxValue - 10 }, Allocator.Persistent)); + } + else if (testType == typeof(sbyte)) + { + yield return TestValueTypeNativeArray( + new NativeArray(new sbyte[] { sbyte.MinValue + 5, sbyte.MaxValue }, Allocator.Persistent), + new NativeArray(new sbyte[] { 0, sbyte.MinValue + 10, sbyte.MaxValue - 10 }, Allocator.Persistent)); + } + else if (testType == typeof(short)) + { + yield return TestValueTypeNativeArray( + new NativeArray(new short[] { short.MinValue + 5, short.MaxValue }, Allocator.Persistent), + new NativeArray(new short[] { 0, short.MinValue + 10, short.MaxValue - 10 }, Allocator.Persistent)); + } + else if (testType == typeof(ushort)) + { + yield return TestValueTypeNativeArray( + new NativeArray(new ushort[] { ushort.MinValue + 5, ushort.MaxValue }, Allocator.Persistent), + new NativeArray(new ushort[] { 0, ushort.MinValue + 10, ushort.MaxValue - 10 }, Allocator.Persistent)); + } + else if (testType == typeof(int)) + { + yield return TestValueTypeNativeArray( + new NativeArray(new int[] { int.MinValue + 5, int.MaxValue }, Allocator.Persistent), + new NativeArray(new int[] { 0, int.MinValue + 10, int.MaxValue - 10 }, Allocator.Persistent)); + } + else if (testType == typeof(uint)) + { + yield return TestValueTypeNativeArray( + new NativeArray(new uint[] { uint.MinValue + 5, uint.MaxValue }, Allocator.Persistent), + new NativeArray(new uint[] { 0, uint.MinValue + 10, uint.MaxValue - 10 }, Allocator.Persistent)); + } + else if (testType == typeof(long)) + { + yield return TestValueTypeNativeArray( + new NativeArray(new long[] { long.MinValue + 5, long.MaxValue }, Allocator.Persistent), + new NativeArray(new long[] { 0, long.MinValue + 10, long.MaxValue - 10 }, Allocator.Persistent)); + } + else if (testType == typeof(ulong)) + { + yield return TestValueTypeNativeArray( + new NativeArray(new ulong[] { ulong.MinValue + 5, ulong.MaxValue }, Allocator.Persistent), + new NativeArray(new ulong[] { 0, ulong.MinValue + 10, ulong.MaxValue - 10 }, Allocator.Persistent)); + } + else if (testType == typeof(bool)) + { + yield return TestValueTypeNativeArray( + new NativeArray(new bool[] { true, false, true }, Allocator.Persistent), + new NativeArray(new bool[] { false, true, false, true, false }, Allocator.Persistent)); + } + else if (testType == typeof(char)) + { + yield return TestValueTypeNativeArray( + new NativeArray(new char[] { 'z', ' ', '?' }, Allocator.Persistent), + new NativeArray(new char[] { 'n', 'e', 'w', ' ', 'v', 'a', 'l', 'u', 'e' }, Allocator.Persistent)); + } + else if (testType == typeof(float)) + { + yield return TestValueTypeNativeArray( + new NativeArray(new float[] { float.MinValue + 5.12345678f, float.MaxValue }, Allocator.Persistent), + new NativeArray(new float[] { 0, float.MinValue + 10.987654321f, float.MaxValue - 10.135792468f }, Allocator.Persistent)); + } + else if (testType == typeof(double)) + { + yield return TestValueTypeNativeArray( + new NativeArray(new double[] { double.MinValue + 5.12345678, double.MaxValue }, Allocator.Persistent), + new NativeArray(new double[] { 0, double.MinValue + 10.987654321, double.MaxValue - 10.135792468 }, Allocator.Persistent)); + } + else if (testType == typeof(ByteEnum)) + { + yield return TestValueTypeNativeArray( + new NativeArray(new ByteEnum[] { ByteEnum.C, ByteEnum.B, ByteEnum.A }, Allocator.Persistent), + new NativeArray(new ByteEnum[] { ByteEnum.B, ByteEnum.C, ByteEnum.B, ByteEnum.A, ByteEnum.C }, Allocator.Persistent)); + } + else if (testType == typeof(SByteEnum)) + { + yield return TestValueTypeNativeArray( + new NativeArray(new SByteEnum[] { SByteEnum.C, SByteEnum.B, SByteEnum.A }, Allocator.Persistent), + new NativeArray(new SByteEnum[] { SByteEnum.B, SByteEnum.C, SByteEnum.B, SByteEnum.A, SByteEnum.C }, Allocator.Persistent)); + } + else if (testType == typeof(ShortEnum)) + { + yield return TestValueTypeNativeArray( + new NativeArray(new ShortEnum[] { ShortEnum.C, ShortEnum.B, ShortEnum.A }, Allocator.Persistent), + new NativeArray(new ShortEnum[] { ShortEnum.B, ShortEnum.C, ShortEnum.B, ShortEnum.A, ShortEnum.C }, Allocator.Persistent)); + } + else if (testType == typeof(UShortEnum)) + { + yield return TestValueTypeNativeArray( + new NativeArray(new UShortEnum[] { UShortEnum.C, UShortEnum.B, UShortEnum.A }, Allocator.Persistent), + new NativeArray(new UShortEnum[] { UShortEnum.B, UShortEnum.C, UShortEnum.B, UShortEnum.A, UShortEnum.C }, Allocator.Persistent)); + } + else if (testType == typeof(IntEnum)) + { + yield return TestValueTypeNativeArray( + new NativeArray(new IntEnum[] { IntEnum.C, IntEnum.B, IntEnum.A }, Allocator.Persistent), + new NativeArray(new IntEnum[] { IntEnum.B, IntEnum.C, IntEnum.B, IntEnum.A, IntEnum.C }, Allocator.Persistent)); + } + else if (testType == typeof(UIntEnum)) + { + yield return TestValueTypeNativeArray( + new NativeArray(new UIntEnum[] { UIntEnum.C, UIntEnum.B, UIntEnum.A }, Allocator.Persistent), + new NativeArray(new UIntEnum[] { UIntEnum.B, UIntEnum.C, UIntEnum.B, UIntEnum.A, UIntEnum.C }, Allocator.Persistent)); + } + else if (testType == typeof(LongEnum)) + { + yield return TestValueTypeNativeArray( + new NativeArray(new LongEnum[] { LongEnum.C, LongEnum.B, LongEnum.A }, Allocator.Persistent), + new NativeArray(new LongEnum[] { LongEnum.B, LongEnum.C, LongEnum.B, LongEnum.A, LongEnum.C }, Allocator.Persistent)); + } + else if (testType == typeof(ULongEnum)) + { + yield return TestValueTypeNativeArray( + new NativeArray(new ULongEnum[] { ULongEnum.C, ULongEnum.B, ULongEnum.A }, Allocator.Persistent), + new NativeArray(new ULongEnum[] { ULongEnum.B, ULongEnum.C, ULongEnum.B, ULongEnum.A, ULongEnum.C }, Allocator.Persistent)); + } + else if (testType == typeof(Vector2)) + { + yield return TestValueTypeNativeArray( + new NativeArray(new Vector2[] { new Vector2(5, 10), new Vector2(15, 20) }, Allocator.Persistent), + new NativeArray(new Vector2[] { new Vector2(25, 30), new Vector2(35, 40), new Vector2(45, 50) }, Allocator.Persistent)); + } + else if (testType == typeof(Vector3)) + { + yield return TestValueTypeNativeArray( + new NativeArray(new Vector3[] { new Vector3(5, 10, 15), new Vector3(20, 25, 30) }, Allocator.Persistent), + new NativeArray(new Vector3[] { new Vector3(35, 40, 45), new Vector3(50, 55, 60), new Vector3(65, 70, 75) }, Allocator.Persistent)); + } + else if (testType == typeof(Vector2Int)) + { + yield return TestValueTypeNativeArray( + new NativeArray(new Vector2Int[] { new Vector2Int(5, 10), new Vector2Int(15, 20) }, Allocator.Persistent), + new NativeArray(new Vector2Int[] { new Vector2Int(25, 30), new Vector2Int(35, 40), new Vector2Int(45, 50) }, Allocator.Persistent)); + } + else if (testType == typeof(Vector3Int)) + { + yield return TestValueTypeNativeArray( + new NativeArray(new Vector3Int[] { new Vector3Int(5, 10, 15), new Vector3Int(20, 25, 30) }, Allocator.Persistent), + new NativeArray(new Vector3Int[] { new Vector3Int(35, 40, 45), new Vector3Int(50, 55, 60), new Vector3Int(65, 70, 75) }, Allocator.Persistent)); + } + else if (testType == typeof(Vector4)) + { + yield return TestValueTypeNativeArray( + new NativeArray(new Vector4[] { new Vector4(5, 10, 15, 20), new Vector4(25, 30, 35, 40) }, Allocator.Persistent), + new NativeArray(new Vector4[] { new Vector4(45, 50, 55, 60), new Vector4(65, 70, 75, 80), new Vector4(85, 90, 95, 100) }, Allocator.Persistent)); + } + else if (testType == typeof(Quaternion)) + { + yield return TestValueTypeNativeArray( + new NativeArray(new Quaternion[] { new Quaternion(5, 10, 15, 20), new Quaternion(25, 30, 35, 40) }, Allocator.Persistent), + new NativeArray(new Quaternion[] { new Quaternion(45, 50, 55, 60), new Quaternion(65, 70, 75, 80), new Quaternion(85, 90, 95, 100) }, Allocator.Persistent)); + } + else if (testType == typeof(Color)) + { + yield return TestValueTypeNativeArray( + new NativeArray(new Color[] { new Color(.5f, .10f, .15f), new Color(.20f, .25f, .30f) }, Allocator.Persistent), + new NativeArray(new Color[] { new Color(.35f, .40f, .45f), new Color(.50f, .55f, .60f), new Color(.65f, .70f, .75f) }, Allocator.Persistent)); + } + else if (testType == typeof(Color32)) + { + yield return TestValueTypeNativeArray( + new NativeArray(new Color32[] { new Color32(5, 10, 15, 20), new Color32(25, 30, 35, 40) }, Allocator.Persistent), + new NativeArray(new Color32[] { new Color32(45, 50, 55, 60), new Color32(65, 70, 75, 80), new Color32(85, 90, 95, 100) }, Allocator.Persistent)); + } + else if (testType == typeof(Ray)) + { + yield return TestValueTypeNativeArray( + new NativeArray(new Ray[] + { + new Ray(new Vector3(0, 1, 2), new Vector3(3, 4, 5)), + new Ray(new Vector3(6, 7, 8), new Vector3(9, 10, 11)), + }, Allocator.Persistent), + new NativeArray(new Ray[] + { + new Ray(new Vector3(12, 13, 14), new Vector3(15, 16, 17)), + new Ray(new Vector3(18, 19, 20), new Vector3(21, 22, 23)), + new Ray(new Vector3(24, 25, 26), new Vector3(27, 28, 29)), + }, Allocator.Persistent)); + } + else if (testType == typeof(Ray2D)) + { + yield return TestValueTypeNativeArray( + new NativeArray(new Ray2D[] + { + new Ray2D(new Vector2(0, 1), new Vector2(3, 4)), + new Ray2D(new Vector2(6, 7), new Vector2(9, 10)), + }, Allocator.Persistent), + new NativeArray(new Ray2D[] + { + new Ray2D(new Vector2(12, 13), new Vector2(15, 16)), + new Ray2D(new Vector2(18, 19), new Vector2(21, 22)), + new Ray2D(new Vector2(24, 25), new Vector2(27, 28)), + }, Allocator.Persistent)); + } + else if (testType == typeof(NetworkVariableTestStruct)) + { + yield return TestValueTypeNativeArray( + new NativeArray(new NetworkVariableTestStruct[] + { + NetworkVariableTestStruct.GetTestStruct(), + NetworkVariableTestStruct.GetTestStruct() + }, Allocator.Persistent), + new NativeArray(new NetworkVariableTestStruct[] + { + NetworkVariableTestStruct.GetTestStruct(), + NetworkVariableTestStruct.GetTestStruct(), + NetworkVariableTestStruct.GetTestStruct() + }, Allocator.Persistent)); + } + else if (testType == typeof(FixedString32Bytes)) + { + yield return TestValueTypeNativeArray( + new NativeArray(new FixedString32Bytes[] + { + new FixedString32Bytes("foobar"), + new FixedString32Bytes("12345678901234567890123456789") + }, Allocator.Persistent), + new NativeArray(new FixedString32Bytes[] + { + new FixedString32Bytes("BazQux"), + new FixedString32Bytes("98765432109876543210987654321"), + new FixedString32Bytes("FixedString32Bytes") + }, Allocator.Persistent)); + } + } + + [UnityTest] + public IEnumerator WhenSendingANativeListOfValueTypesOverAnRpc_ValuesAreSerializedCorrectly( + + [Values(typeof(byte), typeof(sbyte), typeof(short), typeof(ushort), typeof(int), typeof(uint), + typeof(long), typeof(ulong), typeof(bool), typeof(char), typeof(float), typeof(double), + typeof(ByteEnum), typeof(SByteEnum), typeof(ShortEnum), typeof(UShortEnum), typeof(IntEnum), + typeof(UIntEnum), typeof(LongEnum), typeof(ULongEnum), typeof(Vector2), typeof(Vector3), + typeof(Vector2Int), typeof(Vector3Int), typeof(Vector4), typeof(Quaternion), typeof(Color), + typeof(Color32), typeof(Ray), typeof(Ray2D), typeof(NetworkVariableTestStruct), typeof(FixedString32Bytes))] + Type testType) + { + if (testType == typeof(byte)) + { + yield return TestValueTypeNativeList( + new NativeList(Allocator.Persistent) { byte.MinValue + 5, byte.MaxValue }, + new NativeList(Allocator.Persistent) { 0, byte.MinValue + 10, byte.MaxValue - 10 }); + } + else if (testType == typeof(sbyte)) + { + yield return TestValueTypeNativeList( + new NativeList(Allocator.Persistent) { sbyte.MinValue + 5, sbyte.MaxValue }, + new NativeList(Allocator.Persistent) { 0, sbyte.MinValue + 10, sbyte.MaxValue - 10 }); + } + else if (testType == typeof(short)) + { + yield return TestValueTypeNativeList( + new NativeList(Allocator.Persistent) { short.MinValue + 5, short.MaxValue }, + new NativeList(Allocator.Persistent) { 0, short.MinValue + 10, short.MaxValue - 10 }); + } + else if (testType == typeof(ushort)) + { + yield return TestValueTypeNativeList( + new NativeList(Allocator.Persistent) { ushort.MinValue + 5, ushort.MaxValue }, + new NativeList(Allocator.Persistent) { 0, ushort.MinValue + 10, ushort.MaxValue - 10 }); + } + else if (testType == typeof(int)) + { + yield return TestValueTypeNativeList( + new NativeList(Allocator.Persistent) { int.MinValue + 5, int.MaxValue }, + new NativeList(Allocator.Persistent) { 0, int.MinValue + 10, int.MaxValue - 10 }); + } + else if (testType == typeof(uint)) + { + yield return TestValueTypeNativeList( + new NativeList(Allocator.Persistent) { uint.MinValue + 5, uint.MaxValue }, + new NativeList(Allocator.Persistent) { 0, uint.MinValue + 10, uint.MaxValue - 10 }); + } + else if (testType == typeof(long)) + { + yield return TestValueTypeNativeList( + new NativeList(Allocator.Persistent) { long.MinValue + 5, long.MaxValue }, + new NativeList(Allocator.Persistent) { 0, long.MinValue + 10, long.MaxValue - 10 }); + } + else if (testType == typeof(ulong)) + { + yield return TestValueTypeNativeList( + new NativeList(Allocator.Persistent) { ulong.MinValue + 5, ulong.MaxValue }, + new NativeList(Allocator.Persistent) { 0, ulong.MinValue + 10, ulong.MaxValue - 10 }); + } + else if (testType == typeof(bool)) + { + yield return TestValueTypeNativeList( + new NativeList(Allocator.Persistent) { true, false, true }, + new NativeList(Allocator.Persistent) { false, true, false, true, false }); + } + else if (testType == typeof(char)) + { + yield return TestValueTypeNativeList( + new NativeList(Allocator.Persistent) { 'z', ' ', '?' }, + new NativeList(Allocator.Persistent) { 'n', 'e', 'w', ' ', 'v', 'a', 'l', 'u', 'e' }); + } + else if (testType == typeof(float)) + { + yield return TestValueTypeNativeList( + new NativeList(Allocator.Persistent) { float.MinValue + 5.12345678f, float.MaxValue }, + new NativeList(Allocator.Persistent) { 0, float.MinValue + 10.987654321f, float.MaxValue - 10.135792468f }); + } + else if (testType == typeof(double)) + { + yield return TestValueTypeNativeList( + new NativeList(Allocator.Persistent) { double.MinValue + 5.12345678, double.MaxValue }, + new NativeList(Allocator.Persistent) { 0, double.MinValue + 10.987654321, double.MaxValue - 10.135792468 }); + } + else if (testType == typeof(ByteEnum)) + { + yield return TestValueTypeNativeList( + new NativeList(Allocator.Persistent) { ByteEnum.C, ByteEnum.B, ByteEnum.A }, + new NativeList(Allocator.Persistent) { ByteEnum.B, ByteEnum.C, ByteEnum.B, ByteEnum.A, ByteEnum.C }); + } + else if (testType == typeof(SByteEnum)) + { + yield return TestValueTypeNativeList( + new NativeList(Allocator.Persistent) { SByteEnum.C, SByteEnum.B, SByteEnum.A }, + new NativeList(Allocator.Persistent) { SByteEnum.B, SByteEnum.C, SByteEnum.B, SByteEnum.A, SByteEnum.C }); + } + else if (testType == typeof(ShortEnum)) + { + yield return TestValueTypeNativeList( + new NativeList(Allocator.Persistent) { ShortEnum.C, ShortEnum.B, ShortEnum.A }, + new NativeList(Allocator.Persistent) { ShortEnum.B, ShortEnum.C, ShortEnum.B, ShortEnum.A, ShortEnum.C }); + } + else if (testType == typeof(UShortEnum)) + { + yield return TestValueTypeNativeList( + new NativeList(Allocator.Persistent) { UShortEnum.C, UShortEnum.B, UShortEnum.A }, + new NativeList(Allocator.Persistent) { UShortEnum.B, UShortEnum.C, UShortEnum.B, UShortEnum.A, UShortEnum.C }); + } + else if (testType == typeof(IntEnum)) + { + yield return TestValueTypeNativeList( + new NativeList(Allocator.Persistent) { IntEnum.C, IntEnum.B, IntEnum.A }, + new NativeList(Allocator.Persistent) { IntEnum.B, IntEnum.C, IntEnum.B, IntEnum.A, IntEnum.C }); + } + else if (testType == typeof(UIntEnum)) + { + yield return TestValueTypeNativeList( + new NativeList(Allocator.Persistent) { UIntEnum.C, UIntEnum.B, UIntEnum.A }, + new NativeList(Allocator.Persistent) { UIntEnum.B, UIntEnum.C, UIntEnum.B, UIntEnum.A, UIntEnum.C }); + } + else if (testType == typeof(LongEnum)) + { + yield return TestValueTypeNativeList( + new NativeList(Allocator.Persistent) { LongEnum.C, LongEnum.B, LongEnum.A }, + new NativeList(Allocator.Persistent) { LongEnum.B, LongEnum.C, LongEnum.B, LongEnum.A, LongEnum.C }); + } + else if (testType == typeof(ULongEnum)) + { + yield return TestValueTypeNativeList( + new NativeList(Allocator.Persistent) { ULongEnum.C, ULongEnum.B, ULongEnum.A }, + new NativeList(Allocator.Persistent) { ULongEnum.B, ULongEnum.C, ULongEnum.B, ULongEnum.A, ULongEnum.C }); + } + else if (testType == typeof(Vector2)) + { + yield return TestValueTypeNativeList( + new NativeList(Allocator.Persistent) { new Vector2(5, 10), new Vector2(15, 20) }, + new NativeList(Allocator.Persistent) { new Vector2(25, 30), new Vector2(35, 40), new Vector2(45, 50) }); + } + else if (testType == typeof(Vector3)) + { + yield return TestValueTypeNativeList( + new NativeList(Allocator.Persistent) { new Vector3(5, 10, 15), new Vector3(20, 25, 30) }, + new NativeList(Allocator.Persistent) { new Vector3(35, 40, 45), new Vector3(50, 55, 60), new Vector3(65, 70, 75) }); + } + else if (testType == typeof(Vector2Int)) + { + yield return TestValueTypeNativeList( + new NativeList(Allocator.Persistent) { new Vector2Int(5, 10), new Vector2Int(15, 20) }, + new NativeList(Allocator.Persistent) { new Vector2Int(25, 30), new Vector2Int(35, 40), new Vector2Int(45, 50) }); + } + else if (testType == typeof(Vector3Int)) + { + yield return TestValueTypeNativeList( + new NativeList(Allocator.Persistent) { new Vector3Int(5, 10, 15), new Vector3Int(20, 25, 30) }, + new NativeList(Allocator.Persistent) { new Vector3Int(35, 40, 45), new Vector3Int(50, 55, 60), new Vector3Int(65, 70, 75) }); + } + else if (testType == typeof(Vector4)) + { + yield return TestValueTypeNativeList( + new NativeList(Allocator.Persistent) { new Vector4(5, 10, 15, 20), new Vector4(25, 30, 35, 40) }, + new NativeList(Allocator.Persistent) { new Vector4(45, 50, 55, 60), new Vector4(65, 70, 75, 80), new Vector4(85, 90, 95, 100) }); + } + else if (testType == typeof(Quaternion)) + { + yield return TestValueTypeNativeList( + new NativeList(Allocator.Persistent) { new Quaternion(5, 10, 15, 20), new Quaternion(25, 30, 35, 40) }, + new NativeList(Allocator.Persistent) { new Quaternion(45, 50, 55, 60), new Quaternion(65, 70, 75, 80), new Quaternion(85, 90, 95, 100) }); + } + else if (testType == typeof(Color)) + { + yield return TestValueTypeNativeList( + new NativeList(Allocator.Persistent) { new Color(.5f, .10f, .15f), new Color(.20f, .25f, .30f) }, + new NativeList(Allocator.Persistent) { new Color(.35f, .40f, .45f), new Color(.50f, .55f, .60f), new Color(.65f, .70f, .75f) }); + } + else if (testType == typeof(Color32)) + { + yield return TestValueTypeNativeList( + new NativeList(Allocator.Persistent) { new Color32(5, 10, 15, 20), new Color32(25, 30, 35, 40) }, + new NativeList(Allocator.Persistent) { new Color32(45, 50, 55, 60), new Color32(65, 70, 75, 80), new Color32(85, 90, 95, 100) }); + } + else if (testType == typeof(Ray)) + { + yield return TestValueTypeNativeList( + new NativeList(Allocator.Persistent) + { + new Ray(new Vector3(0, 1, 2), new Vector3(3, 4, 5)), + new Ray(new Vector3(6, 7, 8), new Vector3(9, 10, 11)), + }, + new NativeList(Allocator.Persistent) + { + new Ray(new Vector3(12, 13, 14), new Vector3(15, 16, 17)), + new Ray(new Vector3(18, 19, 20), new Vector3(21, 22, 23)), + new Ray(new Vector3(24, 25, 26), new Vector3(27, 28, 29)), + }); + } + else if (testType == typeof(Ray2D)) + { + yield return TestValueTypeNativeList( + new NativeList(Allocator.Persistent) + { + new Ray2D(new Vector2(0, 1), new Vector2(3, 4)), + new Ray2D(new Vector2(6, 7), new Vector2(9, 10)), + }, + new NativeList(Allocator.Persistent) + { + new Ray2D(new Vector2(12, 13), new Vector2(15, 16)), + new Ray2D(new Vector2(18, 19), new Vector2(21, 22)), + new Ray2D(new Vector2(24, 25), new Vector2(27, 28)), + }); + } + else if (testType == typeof(NetworkVariableTestStruct)) + { + yield return TestValueTypeNativeList( + new NativeList(Allocator.Persistent) + { + NetworkVariableTestStruct.GetTestStruct(), + NetworkVariableTestStruct.GetTestStruct() + }, + new NativeList(Allocator.Persistent) + { + NetworkVariableTestStruct.GetTestStruct(), + NetworkVariableTestStruct.GetTestStruct(), + NetworkVariableTestStruct.GetTestStruct() + }); + } + else if (testType == typeof(FixedString32Bytes)) + { + yield return TestValueTypeNativeList( + new NativeList(Allocator.Persistent) + { + new FixedString32Bytes("foobar"), + new FixedString32Bytes("12345678901234567890123456789") + }, + new NativeList(Allocator.Persistent) + { + new FixedString32Bytes("BazQux"), + new FixedString32Bytes("98765432109876543210987654321"), + new FixedString32Bytes("FixedString32Bytes") + }); + } + } + } +} diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/RpcTypeSerializationTests.cs.meta b/com.unity.netcode.gameobjects/Tests/Runtime/RpcTypeSerializationTests.cs.meta new file mode 100644 index 0000000000..a0a88afc7c --- /dev/null +++ b/com.unity.netcode.gameobjects/Tests/Runtime/RpcTypeSerializationTests.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: fd1b9e10285844d48c86d1930652d22c +timeCreated: 1672959718 \ No newline at end of file diff --git a/testproject/Assets/Tests/Runtime/Animation/Resources/AnimatorObject.prefab b/testproject/Assets/Tests/Runtime/Animation/Resources/AnimatorObject.prefab index cd34d363d9..b5fb402cf2 100644 --- a/testproject/Assets/Tests/Runtime/Animation/Resources/AnimatorObject.prefab +++ b/testproject/Assets/Tests/Runtime/Animation/Resources/AnimatorObject.prefab @@ -29,7 +29,6 @@ Transform: m_LocalRotation: {x: -0, y: -0, z: -0, w: 1} m_LocalPosition: {x: -305, y: -240, z: 0} m_LocalScale: {x: 10, y: 10, z: 10} - m_ConstrainProportionsScale: 0 m_Children: - {fileID: 3456315205079358758} - {fileID: 8794063308444682358} @@ -55,7 +54,6 @@ MeshRenderer: m_CastShadows: 1 m_ReceiveShadows: 1 m_DynamicOccludee: 1 - m_StaticShadowCaster: 0 m_MotionVectors: 1 m_LightProbeUsage: 1 m_ReflectionProbeUsage: 1 @@ -128,7 +126,6 @@ Transform: m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} m_LocalPosition: {x: 0, y: 0, z: 0.505} m_LocalScale: {x: 0.1, y: 0.1, z: 0.1} - m_ConstrainProportionsScale: 0 m_Children: [] m_Father: {fileID: 5566730610036911080} m_RootOrder: 1 @@ -152,7 +149,6 @@ MeshRenderer: m_CastShadows: 1 m_ReceiveShadows: 1 m_DynamicOccludee: 1 - m_StaticShadowCaster: 0 m_MotionVectors: 1 m_LightProbeUsage: 1 m_ReflectionProbeUsage: 1 @@ -225,7 +221,6 @@ Transform: m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} m_LocalPosition: {x: 0, y: 0, z: -0.514} m_LocalScale: {x: 0.1, y: 0.1, z: 0.1} - m_ConstrainProportionsScale: 0 m_Children: [] m_Father: {fileID: 5566730610036911080} m_RootOrder: 0 @@ -249,7 +244,6 @@ MeshRenderer: m_CastShadows: 1 m_ReceiveShadows: 1 m_DynamicOccludee: 1 - m_StaticShadowCaster: 0 m_MotionVectors: 1 m_LightProbeUsage: 1 m_ReflectionProbeUsage: 1 @@ -324,7 +318,6 @@ Transform: m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} m_LocalPosition: {x: 320, y: 240, z: 0} m_LocalScale: {x: 1, y: 1, z: 1} - m_ConstrainProportionsScale: 0 m_Children: - {fileID: 5566730610036911080} m_Father: {fileID: 0} @@ -344,11 +337,14 @@ MonoBehaviour: m_EditorClassIdentifier: GlobalObjectIdHash: 951099334 AlwaysReplicateAsRoot: 0 + SynchronizeTransform: 1 + ActiveSceneSynchronization: 0 + SceneMigrationSynchronization: 1 DontDestroyWithOwner: 0 AutoObjectParentSync: 1 --- !u!95 &6515743261518512780 Animator: - serializedVersion: 4 + serializedVersion: 3 m_ObjectHideFlags: 0 m_CorrespondingSourceObject: {fileID: 0} m_PrefabInstance: {fileID: 0} @@ -361,7 +357,6 @@ Animator: m_UpdateMode: 0 m_ApplyRootMotion: 0 m_LinearVelocityBlending: 0 - m_StabilizeFeet: 0 m_WarningMessage: m_HasTransformHierarchy: 1 m_AllowConstantClipSamplingOptimization: 1 @@ -379,37 +374,43 @@ MonoBehaviour: m_Name: m_EditorClassIdentifier: TransitionStateInfoList: - - Layer: 0 + - IsCrossFadeExit: 0 + Layer: 0 OriginatingState: 2081823275 DestinationState: -570305638 TransitionDuration: 0.30227518 TriggerNameHash: 1033918907 TransitionIndex: 1 - - Layer: 0 + - IsCrossFadeExit: 0 + Layer: 0 OriginatingState: 2081823275 DestinationState: -1509639022 TransitionDuration: 0.25 TriggerNameHash: 1033918907 TransitionIndex: 2 - - Layer: 1 + - IsCrossFadeExit: 0 + Layer: 1 OriginatingState: 2081823275 DestinationState: -623385122 TransitionDuration: 2.2832582 TriggerNameHash: -623385122 TransitionIndex: 0 - - Layer: 3 + - IsCrossFadeExit: 0 + Layer: 3 OriginatingState: 2081823275 DestinationState: -1829531531 TransitionDuration: 0.25 TriggerNameHash: 1080829965 TransitionIndex: 0 - - Layer: 3 + - IsCrossFadeExit: 0 + Layer: 3 OriginatingState: 2081823275 DestinationState: 705160537 TransitionDuration: 0.25 TriggerNameHash: 1080829965 TransitionIndex: 1 - - Layer: 3 + - IsCrossFadeExit: 0 + Layer: 3 OriginatingState: 2081823275 DestinationState: 801385362 TransitionDuration: 0.25 diff --git a/testproject/Assets/Tests/Runtime/Animation/Resources/OwnerAnimatorObject.prefab b/testproject/Assets/Tests/Runtime/Animation/Resources/OwnerAnimatorObject.prefab index 33c0960ba1..a5fe03a9af 100644 --- a/testproject/Assets/Tests/Runtime/Animation/Resources/OwnerAnimatorObject.prefab +++ b/testproject/Assets/Tests/Runtime/Animation/Resources/OwnerAnimatorObject.prefab @@ -29,7 +29,6 @@ Transform: m_LocalRotation: {x: -0, y: -0, z: -0, w: 1} m_LocalPosition: {x: -305, y: -240, z: 0} m_LocalScale: {x: 10, y: 10, z: 10} - m_ConstrainProportionsScale: 0 m_Children: - {fileID: 3456315205079358758} - {fileID: 8794063308444682358} @@ -55,7 +54,6 @@ MeshRenderer: m_CastShadows: 1 m_ReceiveShadows: 1 m_DynamicOccludee: 1 - m_StaticShadowCaster: 0 m_MotionVectors: 1 m_LightProbeUsage: 1 m_ReflectionProbeUsage: 1 @@ -128,7 +126,6 @@ Transform: m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} m_LocalPosition: {x: 0, y: 0, z: 0.505} m_LocalScale: {x: 0.1, y: 0.1, z: 0.1} - m_ConstrainProportionsScale: 0 m_Children: [] m_Father: {fileID: 5566730610036911080} m_RootOrder: 1 @@ -152,7 +149,6 @@ MeshRenderer: m_CastShadows: 1 m_ReceiveShadows: 1 m_DynamicOccludee: 1 - m_StaticShadowCaster: 0 m_MotionVectors: 1 m_LightProbeUsage: 1 m_ReflectionProbeUsage: 1 @@ -225,7 +221,6 @@ Transform: m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} m_LocalPosition: {x: 0, y: 0, z: -0.514} m_LocalScale: {x: 0.1, y: 0.1, z: 0.1} - m_ConstrainProportionsScale: 0 m_Children: [] m_Father: {fileID: 5566730610036911080} m_RootOrder: 0 @@ -249,7 +244,6 @@ MeshRenderer: m_CastShadows: 1 m_ReceiveShadows: 1 m_DynamicOccludee: 1 - m_StaticShadowCaster: 0 m_MotionVectors: 1 m_LightProbeUsage: 1 m_ReflectionProbeUsage: 1 @@ -324,7 +318,6 @@ Transform: m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} m_LocalPosition: {x: 320, y: 240, z: 0} m_LocalScale: {x: 1, y: 1, z: 1} - m_ConstrainProportionsScale: 0 m_Children: - {fileID: 5566730610036911080} m_Father: {fileID: 0} @@ -344,11 +337,14 @@ MonoBehaviour: m_EditorClassIdentifier: GlobalObjectIdHash: 951099334 AlwaysReplicateAsRoot: 0 + SynchronizeTransform: 1 + ActiveSceneSynchronization: 0 + SceneMigrationSynchronization: 1 DontDestroyWithOwner: 0 AutoObjectParentSync: 1 --- !u!95 &6515743261518512780 Animator: - serializedVersion: 4 + serializedVersion: 3 m_ObjectHideFlags: 0 m_CorrespondingSourceObject: {fileID: 0} m_PrefabInstance: {fileID: 0} @@ -361,7 +357,6 @@ Animator: m_UpdateMode: 0 m_ApplyRootMotion: 0 m_LinearVelocityBlending: 0 - m_StabilizeFeet: 0 m_WarningMessage: m_HasTransformHierarchy: 1 m_AllowConstantClipSamplingOptimization: 1 @@ -379,37 +374,43 @@ MonoBehaviour: m_Name: m_EditorClassIdentifier: TransitionStateInfoList: - - Layer: 0 + - IsCrossFadeExit: 0 + Layer: 0 OriginatingState: 2081823275 DestinationState: -570305638 TransitionDuration: 0.30227518 TriggerNameHash: 1033918907 TransitionIndex: 1 - - Layer: 0 + - IsCrossFadeExit: 0 + Layer: 0 OriginatingState: 2081823275 DestinationState: -1509639022 TransitionDuration: 0.25 TriggerNameHash: 1033918907 TransitionIndex: 2 - - Layer: 1 + - IsCrossFadeExit: 0 + Layer: 1 OriginatingState: 2081823275 DestinationState: -623385122 TransitionDuration: 2.2832582 TriggerNameHash: -623385122 TransitionIndex: 0 - - Layer: 3 + - IsCrossFadeExit: 0 + Layer: 3 OriginatingState: 2081823275 DestinationState: -1829531531 TransitionDuration: 0.25 TriggerNameHash: 1080829965 TransitionIndex: 0 - - Layer: 3 + - IsCrossFadeExit: 0 + Layer: 3 OriginatingState: 2081823275 DestinationState: 705160537 TransitionDuration: 0.25 TriggerNameHash: 1080829965 TransitionIndex: 1 - - Layer: 3 + - IsCrossFadeExit: 0 + Layer: 3 OriginatingState: 2081823275 DestinationState: 801385362 TransitionDuration: 0.25