diff --git a/com.unity.netcode.gameobjects/Components/NetworkAnimator.cs b/com.unity.netcode.gameobjects/Components/NetworkAnimator.cs index a1f16d86de..eb01597d64 100644 --- a/com.unity.netcode.gameobjects/Components/NetworkAnimator.cs +++ b/com.unity.netcode.gameobjects/Components/NetworkAnimator.cs @@ -76,7 +76,7 @@ public bool SetTrigger(int key) return TriggerParameters.Add(key); } - public void NetworkSerialize(NetworkSerializer serializer) + public void NetworkSerialize(BufferSerializer serializer) where T : IBufferSerializerImplementation { SerializeIntParameters(serializer); SerializeFloatParameters(serializer); @@ -85,28 +85,28 @@ public void NetworkSerialize(NetworkSerializer serializer) SerializeAnimatorLayerStates(serializer); } - private void SerializeAnimatorLayerStates(NetworkSerializer serializer) + private void SerializeAnimatorLayerStates(BufferSerializer serializer) where T : IBufferSerializerImplementation { - int layerCount = serializer.IsReading ? 0 : LayerStates.Length; - serializer.Serialize(ref layerCount); + int layerCount = serializer.IsReader ? 0 : LayerStates.Length; + serializer.SerializeValue(ref layerCount); - if (serializer.IsReading && LayerStates.Length != layerCount) + if (serializer.IsReader && LayerStates.Length != layerCount) { LayerStates = new LayerState[layerCount]; } for (int paramIndex = 0; paramIndex < layerCount; paramIndex++) { - var stateHash = serializer.IsReading ? 0 : LayerStates[paramIndex].StateHash; - serializer.Serialize(ref stateHash); + var stateHash = serializer.IsReader ? 0 : LayerStates[paramIndex].StateHash; + serializer.SerializeValue(ref stateHash); - var layerWeight = serializer.IsReading ? 0 : LayerStates[paramIndex].LayerWeight; - serializer.Serialize(ref layerWeight); + var layerWeight = serializer.IsReader ? 0 : LayerStates[paramIndex].LayerWeight; + serializer.SerializeValue(ref layerWeight); - var normalizedStateTime = serializer.IsReading ? 0 : LayerStates[paramIndex].NormalizedStateTime; - serializer.Serialize(ref normalizedStateTime); + var normalizedStateTime = serializer.IsReader ? 0 : LayerStates[paramIndex].NormalizedStateTime; + serializer.SerializeValue(ref normalizedStateTime); - if (serializer.IsReading) + if (serializer.IsReader) { LayerStates[paramIndex] = new LayerState() { @@ -118,103 +118,103 @@ private void SerializeAnimatorLayerStates(NetworkSerializer serializer) } } - private void SerializeTriggerParameters(NetworkSerializer serializer) + private void SerializeTriggerParameters(BufferSerializer serializer) where T : IBufferSerializerImplementation { - int paramCount = serializer.IsReading ? 0 : TriggerParameters.Count; - serializer.Serialize(ref paramCount); + int paramCount = serializer.IsReader ? 0 : TriggerParameters.Count; + serializer.SerializeValue(ref paramCount); - var paramArray = serializer.IsReading ? new int[paramCount] : TriggerParameters.ToArray(); + var paramArray = serializer.IsReader ? new int[paramCount] : TriggerParameters.ToArray(); for (int i = 0; i < paramCount; i++) { - var paramId = serializer.IsReading ? 0 : paramArray[i]; - serializer.Serialize(ref paramId); + var paramId = serializer.IsReader ? 0 : paramArray[i]; + serializer.SerializeValue(ref paramId); - if (serializer.IsReading) + if (serializer.IsReader) { paramArray[i] = paramId; } } - if (serializer.IsReading) + if (serializer.IsReader) { TriggerParameters = new HashSet(paramArray); } } - private void SerializeBoolParameters(NetworkSerializer serializer) + private void SerializeBoolParameters(BufferSerializer serializer) where T : IBufferSerializerImplementation { - int paramCount = serializer.IsReading ? 0 : BoolParameters.Count; - serializer.Serialize(ref paramCount); + int paramCount = serializer.IsReader ? 0 : BoolParameters.Count; + serializer.SerializeValue(ref paramCount); - var paramArray = serializer.IsReading ? new KeyValuePair[paramCount] : BoolParameters.ToArray(); + var paramArray = serializer.IsReader ? new KeyValuePair[paramCount] : BoolParameters.ToArray(); for (int paramIndex = 0; paramIndex < paramCount; paramIndex++) { - var paramId = serializer.IsReading ? 0 : paramArray[paramIndex].Key; - serializer.Serialize(ref paramId); + var paramId = serializer.IsReader ? 0 : paramArray[paramIndex].Key; + serializer.SerializeValue(ref paramId); - var paramBool = serializer.IsReading ? false : paramArray[paramIndex].Value; - serializer.Serialize(ref paramBool); + var paramBool = serializer.IsReader ? false : paramArray[paramIndex].Value; + serializer.SerializeValue(ref paramBool); - if (serializer.IsReading) + if (serializer.IsReader) { paramArray[paramIndex] = new KeyValuePair(paramId, paramBool); } } - if (serializer.IsReading) + if (serializer.IsReader) { BoolParameters = paramArray.ToDictionary(pair => pair.Key, pair => pair.Value); } } - private void SerializeFloatParameters(NetworkSerializer serializer) + private void SerializeFloatParameters(BufferSerializer serializer) where T : IBufferSerializerImplementation { - int paramCount = serializer.IsReading ? 0 : FloatParameters.Count; - serializer.Serialize(ref paramCount); + int paramCount = serializer.IsReader ? 0 : FloatParameters.Count; + serializer.SerializeValue(ref paramCount); - var paramArray = serializer.IsReading ? new KeyValuePair[paramCount] : FloatParameters.ToArray(); + var paramArray = serializer.IsReader ? new KeyValuePair[paramCount] : FloatParameters.ToArray(); for (int paramIndex = 0; paramIndex < paramCount; paramIndex++) { - var paramId = serializer.IsReading ? 0 : paramArray[paramIndex].Key; - serializer.Serialize(ref paramId); + var paramId = serializer.IsReader ? 0 : paramArray[paramIndex].Key; + serializer.SerializeValue(ref paramId); - var paramFloat = serializer.IsReading ? 0 : paramArray[paramIndex].Value; - serializer.Serialize(ref paramFloat); + var paramFloat = serializer.IsReader ? 0 : paramArray[paramIndex].Value; + serializer.SerializeValue(ref paramFloat); - if (serializer.IsReading) + if (serializer.IsReader) { paramArray[paramIndex] = new KeyValuePair(paramId, paramFloat); } } - if (serializer.IsReading) + if (serializer.IsReader) { FloatParameters = paramArray.ToDictionary(pair => pair.Key, pair => pair.Value); } } - private void SerializeIntParameters(NetworkSerializer serializer) + private void SerializeIntParameters(BufferSerializer serializer) where T : IBufferSerializerImplementation { - int paramCount = serializer.IsReading ? 0 : IntParameters.Count; - serializer.Serialize(ref paramCount); + int paramCount = serializer.IsReader ? 0 : IntParameters.Count; + serializer.SerializeValue(ref paramCount); - var paramArray = serializer.IsReading ? new KeyValuePair[paramCount] : IntParameters.ToArray(); + var paramArray = serializer.IsReader ? new KeyValuePair[paramCount] : IntParameters.ToArray(); for (int paramIndex = 0; paramIndex < paramCount; paramIndex++) { - var paramId = serializer.IsReading ? 0 : paramArray[paramIndex].Key; - serializer.Serialize(ref paramId); + var paramId = serializer.IsReader ? 0 : paramArray[paramIndex].Key; + serializer.SerializeValue(ref paramId); - var paramInt = serializer.IsReading ? 0 : paramArray[paramIndex].Value; - serializer.Serialize(ref paramInt); + var paramInt = serializer.IsReader ? 0 : paramArray[paramIndex].Value; + serializer.SerializeValue(ref paramInt); - if (serializer.IsReading) + if (serializer.IsReader) { paramArray[paramIndex] = new KeyValuePair(paramId, paramInt); } } - if (serializer.IsReading) + if (serializer.IsReader) { IntParameters = paramArray.ToDictionary(pair => pair.Key, pair => pair.Value); } diff --git a/com.unity.netcode.gameobjects/Components/NetworkTransform.cs b/com.unity.netcode.gameobjects/Components/NetworkTransform.cs index 214091cbb5..b23991aabe 100644 --- a/com.unity.netcode.gameobjects/Components/NetworkTransform.cs +++ b/com.unity.netcode.gameobjects/Components/NetworkTransform.cs @@ -82,48 +82,48 @@ public bool HasScaleZ public float RotAngleX, RotAngleY, RotAngleZ; public float ScaleX, ScaleY, ScaleZ; - public void NetworkSerialize(NetworkSerializer serializer) + public void NetworkSerialize(BufferSerializer serializer) where T : IBufferSerializerImplementation { // InLocalSpace + HasXXX Bits - serializer.Serialize(ref Bitset); + serializer.SerializeValue(ref Bitset); // Position Values if (HasPositionX) { - serializer.Serialize(ref PositionX); + serializer.SerializeValue(ref PositionX); } if (HasPositionY) { - serializer.Serialize(ref PositionY); + serializer.SerializeValue(ref PositionY); } if (HasPositionZ) { - serializer.Serialize(ref PositionZ); + serializer.SerializeValue(ref PositionZ); } // RotAngle Values if (HasRotAngleX) { - serializer.Serialize(ref RotAngleX); + serializer.SerializeValue(ref RotAngleX); } if (HasRotAngleY) { - serializer.Serialize(ref RotAngleY); + serializer.SerializeValue(ref RotAngleY); } if (HasRotAngleZ) { - serializer.Serialize(ref RotAngleZ); + serializer.SerializeValue(ref RotAngleZ); } // Scale Values if (HasScaleX) { - serializer.Serialize(ref ScaleX); + serializer.SerializeValue(ref ScaleX); } if (HasScaleY) { - serializer.Serialize(ref ScaleY); + serializer.SerializeValue(ref ScaleY); } if (HasScaleZ) { - serializer.Serialize(ref ScaleZ); + serializer.SerializeValue(ref ScaleZ); } } } diff --git a/com.unity.netcode.gameobjects/Editor/CodeGen/CodeGenHelpers.cs b/com.unity.netcode.gameobjects/Editor/CodeGen/CodeGenHelpers.cs index 3bf61d2523..5a271bfb74 100644 --- a/com.unity.netcode.gameobjects/Editor/CodeGen/CodeGenHelpers.cs +++ b/com.unity.netcode.gameobjects/Editor/CodeGen/CodeGenHelpers.cs @@ -17,6 +17,7 @@ internal static class CodeGenHelpers public const string RuntimeAssemblyName = "Unity.Netcode.Runtime"; public static readonly string NetworkBehaviour_FullName = typeof(NetworkBehaviour).FullName; + public static readonly string INetworkMessage_FullName = typeof(INetworkMessage).FullName; public static readonly string ServerRpcAttribute_FullName = typeof(ServerRpcAttribute).FullName; public static readonly string ClientRpcAttribute_FullName = typeof(ClientRpcAttribute).FullName; public static readonly string ServerRpcParams_FullName = typeof(ServerRpcParams).FullName; @@ -264,9 +265,9 @@ public static void AddError(this List diagnostics, SequencePo }); } - public static AssemblyDefinition AssemblyDefinitionFor(ICompiledAssembly compiledAssembly) + public static AssemblyDefinition AssemblyDefinitionFor(ICompiledAssembly compiledAssembly, out PostProcessorAssemblyResolver assemblyResolver) { - var assemblyResolver = new PostProcessorAssemblyResolver(compiledAssembly); + assemblyResolver = new PostProcessorAssemblyResolver(compiledAssembly); var readerParameters = new ReaderParameters { SymbolStream = new MemoryStream(compiledAssembly.InMemoryAssembly.PdbData), diff --git a/com.unity.netcode.gameobjects/Editor/CodeGen/INetworkMessageILPP.cs b/com.unity.netcode.gameobjects/Editor/CodeGen/INetworkMessageILPP.cs new file mode 100644 index 0000000000..01a59f6ef9 --- /dev/null +++ b/com.unity.netcode.gameobjects/Editor/CodeGen/INetworkMessageILPP.cs @@ -0,0 +1,128 @@ +using System; +using System.IO; +using System.Linq; +using System.Collections.Generic; +using System.Reflection; +using System.Runtime.CompilerServices; +using Mono.Cecil; +using Mono.Cecil.Cil; +using Mono.Cecil.Rocks; +using Unity.Collections; +using Unity.CompilationPipeline.Common.Diagnostics; +using Unity.CompilationPipeline.Common.ILPostProcessing; +using UnityEngine; +using UnityEngine.UIElements; +using MethodAttributes = Mono.Cecil.MethodAttributes; +using ParameterAttributes = Mono.Cecil.ParameterAttributes; +using ILPPInterface = Unity.CompilationPipeline.Common.ILPostProcessing.ILPostProcessor; + +namespace Unity.Netcode.Editor.CodeGen +{ + + internal sealed class INetworkMessageILPP : ILPPInterface + { + public override ILPPInterface GetInstance() => this; + + public override bool WillProcess(ICompiledAssembly compiledAssembly) => compiledAssembly.References.Any(filePath => Path.GetFileNameWithoutExtension(filePath) == CodeGenHelpers.RuntimeAssemblyName); + + private readonly List m_Diagnostics = new List(); + + public override ILPostProcessResult Process(ICompiledAssembly compiledAssembly) + { + if (!WillProcess(compiledAssembly)) + { + return null; + } + + + m_Diagnostics.Clear(); + + // read + var assemblyDefinition = CodeGenHelpers.AssemblyDefinitionFor(compiledAssembly, out var resolver); + if (assemblyDefinition == null) + { + m_Diagnostics.AddError($"Cannot read assembly definition: {compiledAssembly.Name}"); + return null; + } + + // process + var mainModule = assemblyDefinition.MainModule; + if (mainModule != null) + { + if (ImportReferences(mainModule)) + { + // process `INetworkMessage` types + mainModule.GetTypes() + .Where(t => t.HasInterface(CodeGenHelpers.INetworkMessage_FullName)) + .ToList() + .ForEach(b => ProcessINetworkMessage(b)); + } + else + { + m_Diagnostics.AddError($"Cannot import references into main module: {mainModule.Name}"); + } + } + else + { + m_Diagnostics.AddError($"Cannot get main module from assembly definition: {compiledAssembly.Name}"); + } + + // write + var pe = new MemoryStream(); + var pdb = new MemoryStream(); + + var writerParameters = new WriterParameters + { + SymbolWriterProvider = new PortablePdbWriterProvider(), + SymbolStream = pdb, + WriteSymbols = true + }; + + assemblyDefinition.Write(pe, writerParameters); + + return new ILPostProcessResult(new InMemoryAssembly(pe.ToArray(), pdb.ToArray()), m_Diagnostics); + } + + + private TypeReference m_FastBufferReader_TypeRef; + private TypeReference m_NetworkContext_TypeRef; + + private bool ImportReferences(ModuleDefinition moduleDefinition) + { + m_FastBufferReader_TypeRef = moduleDefinition.ImportReference(typeof(FastBufferReader)); + m_NetworkContext_TypeRef = moduleDefinition.ImportReference(typeof(NetworkContext)); + + return true; + } + + private void ProcessINetworkMessage(TypeDefinition typeDefinition) + { + var foundAValidMethod = false; + SequencePoint typeSequence = null; + foreach (var method in typeDefinition.Methods) + { + var resolved = method.Resolve(); + var methodSequence = resolved.DebugInformation.SequencePoints.FirstOrDefault(); + if (typeSequence == null || methodSequence.StartLine < typeSequence.StartLine) + { + typeSequence = methodSequence; + } + if (resolved.IsStatic && resolved.IsPublic && resolved.Name == "Receive" && resolved.Parameters.Count == 2 + && resolved.Parameters[0].ParameterType.IsByReference + && resolved.Parameters[0].ParameterType.GetElementType().Resolve() == + m_FastBufferReader_TypeRef.Resolve() + && resolved.Parameters[1].ParameterType.Resolve() == m_NetworkContext_TypeRef.Resolve() + && resolved.ReturnType == resolved.Module.TypeSystem.Void) + { + foundAValidMethod = true; + break; + } + } + + if (!foundAValidMethod) + { + m_Diagnostics.AddError(typeSequence, $"Class {typeDefinition.FullName} does not implement required function: `public static void Receive(ref FastBufferReader, NetworkContext)`"); + } + } + } +} diff --git a/com.unity.netcode.gameobjects/Editor/CodeGen/INetworkMessageILPP.cs.meta b/com.unity.netcode.gameobjects/Editor/CodeGen/INetworkMessageILPP.cs.meta new file mode 100644 index 0000000000..47c5ba2801 --- /dev/null +++ b/com.unity.netcode.gameobjects/Editor/CodeGen/INetworkMessageILPP.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: a754504752d649bb8131df8756bd764e +timeCreated: 1631666359 \ No newline at end of file diff --git a/com.unity.netcode.gameobjects/Editor/CodeGen/NetworkBehaviourILPP.cs b/com.unity.netcode.gameobjects/Editor/CodeGen/NetworkBehaviourILPP.cs index 1231c23dee..57b3689d37 100644 --- a/com.unity.netcode.gameobjects/Editor/CodeGen/NetworkBehaviourILPP.cs +++ b/com.unity.netcode.gameobjects/Editor/CodeGen/NetworkBehaviourILPP.cs @@ -3,9 +3,12 @@ using System.Linq; using System.Collections.Generic; using System.Reflection; +using System.Runtime.CompilerServices; using Mono.Cecil; using Mono.Cecil.Cil; using Mono.Cecil.Rocks; +using Mono.Collections.Generic; +using Unity.Collections; using Unity.CompilationPipeline.Common.Diagnostics; using Unity.CompilationPipeline.Common.ILPostProcessing; using UnityEngine; @@ -15,6 +18,7 @@ namespace Unity.Netcode.Editor.CodeGen { + internal sealed class NetworkBehaviourILPP : ILPPInterface { public override ILPPInterface GetInstance() => this; @@ -30,10 +34,11 @@ public override ILPostProcessResult Process(ICompiledAssembly compiledAssembly) return null; } + m_Diagnostics.Clear(); // read - var assemblyDefinition = CodeGenHelpers.AssemblyDefinitionFor(compiledAssembly); + var assemblyDefinition = CodeGenHelpers.AssemblyDefinitionFor(compiledAssembly, out m_AssemblyResolver); if (assemblyDefinition == null) { m_Diagnostics.AddError($"Cannot read assembly definition: {compiledAssembly.Name}"); @@ -44,13 +49,21 @@ public override ILPostProcessResult Process(ICompiledAssembly compiledAssembly) var mainModule = assemblyDefinition.MainModule; if (mainModule != null) { + m_MainModule = mainModule; if (ImportReferences(mainModule)) { // process `NetworkBehaviour` types - mainModule.GetTypes() - .Where(t => t.IsSubclassOf(CodeGenHelpers.NetworkBehaviour_FullName)) - .ToList() - .ForEach(b => ProcessNetworkBehaviour(b, compiledAssembly.Defines)); + try + { + mainModule.GetTypes() + .Where(t => t.IsSubclassOf(CodeGenHelpers.NetworkBehaviour_FullName)) + .ToList() + .ForEach(b => ProcessNetworkBehaviour(b, compiledAssembly.Defines)); + } + catch (Exception e) + { + m_Diagnostics.AddError((e.ToString() + e.StackTrace.ToString()).Replace("\n", "|").Replace("\r", "|")); + } } else { @@ -78,6 +91,9 @@ public override ILPostProcessResult Process(ICompiledAssembly compiledAssembly) return new ILPostProcessResult(new InMemoryAssembly(pe.ToArray(), pdb.ToArray()), m_Diagnostics); } + private ModuleDefinition m_MainModule; + private PostProcessorAssemblyResolver m_AssemblyResolver; + private MethodReference m_Debug_LogError_MethodRef; private TypeReference m_NetworkManager_TypeRef; private MethodReference m_NetworkManager_getLocalClientId_MethodRef; @@ -91,10 +107,8 @@ public override ILPostProcessResult Process(ICompiledAssembly compiledAssembly) private FieldReference m_NetworkManager_rpc_name_table_FieldRef; private MethodReference m_NetworkManager_rpc_name_table_Add_MethodRef; private TypeReference m_NetworkBehaviour_TypeRef; - private MethodReference m_NetworkBehaviour_BeginSendServerRpc_MethodRef; - private MethodReference m_NetworkBehaviour_EndSendServerRpc_MethodRef; - private MethodReference m_NetworkBehaviour_BeginSendClientRpc_MethodRef; - private MethodReference m_NetworkBehaviour_EndSendClientRpc_MethodRef; + private MethodReference m_NetworkBehaviour_SendServerRpc_MethodRef; + private MethodReference m_NetworkBehaviour_SendClientRpc_MethodRef; private FieldReference m_NetworkBehaviour_rpc_exec_stage_FieldRef; private MethodReference m_NetworkBehaviour_getNetworkManager_MethodRef; private MethodReference m_NetworkBehaviour_getOwnerClientId_MethodRef; @@ -106,49 +120,16 @@ public override ILPostProcessResult Process(ICompiledAssembly compiledAssembly) private FieldReference m_ServerRpcParams_Receive_FieldRef; private FieldReference m_ServerRpcParams_Receive_SenderClientId_FieldRef; private TypeReference m_ClientRpcParams_TypeRef; - private TypeReference m_NetworkSerializer_TypeRef; - private MethodReference m_NetworkSerializer_SerializeBool_MethodRef; - private MethodReference m_NetworkSerializer_SerializeChar_MethodRef; - private MethodReference m_NetworkSerializer_SerializeSbyte_MethodRef; - private MethodReference m_NetworkSerializer_SerializeByte_MethodRef; - private MethodReference m_NetworkSerializer_SerializeShort_MethodRef; - private MethodReference m_NetworkSerializer_SerializeUshort_MethodRef; - private MethodReference m_NetworkSerializer_SerializeInt_MethodRef; - private MethodReference m_NetworkSerializer_SerializeUint_MethodRef; - private MethodReference m_NetworkSerializer_SerializeLong_MethodRef; - private MethodReference m_NetworkSerializer_SerializeUlong_MethodRef; - private MethodReference m_NetworkSerializer_SerializeFloat_MethodRef; - private MethodReference m_NetworkSerializer_SerializeDouble_MethodRef; - private MethodReference m_NetworkSerializer_SerializeString_MethodRef; - private MethodReference m_NetworkSerializer_SerializeColor_MethodRef; - private MethodReference m_NetworkSerializer_SerializeColor32_MethodRef; - private MethodReference m_NetworkSerializer_SerializeVector2_MethodRef; - private MethodReference m_NetworkSerializer_SerializeVector3_MethodRef; - private MethodReference m_NetworkSerializer_SerializeVector4_MethodRef; - private MethodReference m_NetworkSerializer_SerializeQuaternion_MethodRef; - private MethodReference m_NetworkSerializer_SerializeRay_MethodRef; - private MethodReference m_NetworkSerializer_SerializeRay2D_MethodRef; - private MethodReference m_NetworkSerializer_SerializeBoolArray_MethodRef; - private MethodReference m_NetworkSerializer_SerializeCharArray_MethodRef; - private MethodReference m_NetworkSerializer_SerializeSbyteArray_MethodRef; - private MethodReference m_NetworkSerializer_SerializeByteArray_MethodRef; - private MethodReference m_NetworkSerializer_SerializeShortArray_MethodRef; - private MethodReference m_NetworkSerializer_SerializeUshortArray_MethodRef; - private MethodReference m_NetworkSerializer_SerializeIntArray_MethodRef; - private MethodReference m_NetworkSerializer_SerializeUintArray_MethodRef; - private MethodReference m_NetworkSerializer_SerializeLongArray_MethodRef; - private MethodReference m_NetworkSerializer_SerializeUlongArray_MethodRef; - private MethodReference m_NetworkSerializer_SerializeFloatArray_MethodRef; - private MethodReference m_NetworkSerializer_SerializeDoubleArray_MethodRef; - private MethodReference m_NetworkSerializer_SerializeStringArray_MethodRef; - private MethodReference m_NetworkSerializer_SerializeColorArray_MethodRef; - private MethodReference m_NetworkSerializer_SerializeColor32Array_MethodRef; - private MethodReference m_NetworkSerializer_SerializeVector2Array_MethodRef; - private MethodReference m_NetworkSerializer_SerializeVector3Array_MethodRef; - private MethodReference m_NetworkSerializer_SerializeVector4Array_MethodRef; - private MethodReference m_NetworkSerializer_SerializeQuaternionArray_MethodRef; - private MethodReference m_NetworkSerializer_SerializeRayArray_MethodRef; - private MethodReference m_NetworkSerializer_SerializeRay2DArray_MethodRef; + + private TypeReference m_FastBufferWriter_TypeRef; + private MethodReference m_FastBufferWriter_Constructor; + private MethodReference m_FastBufferWriter_Dispose; + private Dictionary m_FastBufferWriter_WriteValue_MethodRefs = new Dictionary(); + private List m_FastBufferWriter_ExtensionMethodRefs = new List(); + + private TypeReference m_FastBufferReader_TypeRef; + private Dictionary m_FastBufferReader_ReadValue_MethodRefs = new Dictionary(); + private List m_FastBufferReader_ExtensionMethodRefs = new List(); private const string k_Debug_LogError = nameof(Debug.LogError); private const string k_NetworkManager_LocalClientId = nameof(NetworkManager.LocalClientId); @@ -160,11 +141,9 @@ public override ILPostProcessResult Process(ICompiledAssembly compiledAssembly) private const string k_NetworkManager_rpc_func_table = nameof(NetworkManager.__rpc_func_table); private const string k_NetworkManager_rpc_name_table = nameof(NetworkManager.__rpc_name_table); - private const string k_NetworkBehaviour_BeginSendServerRpc = nameof(NetworkBehaviour.__beginSendServerRpc); - private const string k_NetworkBehaviour_EndSendServerRpc = nameof(NetworkBehaviour.__endSendServerRpc); - private const string k_NetworkBehaviour_BeginSendClientRpc = nameof(NetworkBehaviour.__beginSendClientRpc); - private const string k_NetworkBehaviour_EndSendClientRpc = nameof(NetworkBehaviour.__endSendClientRpc); private const string k_NetworkBehaviour_rpc_exec_stage = nameof(NetworkBehaviour.__rpc_exec_stage); + private const string k_NetworkBehaviour_SendServerRpc = nameof(NetworkBehaviour.SendServerRpc); + private const string k_NetworkBehaviour_SendClientRpc = nameof(NetworkBehaviour.SendClientRpc); private const string k_NetworkBehaviour_NetworkManager = nameof(NetworkBehaviour.NetworkManager); private const string k_NetworkBehaviour_OwnerClientId = nameof(NetworkBehaviour.OwnerClientId); @@ -253,17 +232,11 @@ private bool ImportReferences(ModuleDefinition moduleDefinition) { switch (methodInfo.Name) { - case k_NetworkBehaviour_BeginSendServerRpc: - m_NetworkBehaviour_BeginSendServerRpc_MethodRef = moduleDefinition.ImportReference(methodInfo); - break; - case k_NetworkBehaviour_EndSendServerRpc: - m_NetworkBehaviour_EndSendServerRpc_MethodRef = moduleDefinition.ImportReference(methodInfo); + case k_NetworkBehaviour_SendServerRpc: + m_NetworkBehaviour_SendServerRpc_MethodRef = moduleDefinition.ImportReference(methodInfo); break; - case k_NetworkBehaviour_BeginSendClientRpc: - m_NetworkBehaviour_BeginSendClientRpc_MethodRef = moduleDefinition.ImportReference(methodInfo); - break; - case k_NetworkBehaviour_EndSendClientRpc: - m_NetworkBehaviour_EndSendClientRpc_MethodRef = moduleDefinition.ImportReference(methodInfo); + case k_NetworkBehaviour_SendClientRpc: + m_NetworkBehaviour_SendClientRpc_MethodRef = moduleDefinition.ImportReference(methodInfo); break; } } @@ -278,7 +251,7 @@ private bool ImportReferences(ModuleDefinition moduleDefinition) } } - var networkHandlerDelegateType = typeof(Action); + var networkHandlerDelegateType = typeof(NetworkManager.RpcReceive); m_NetworkHandlerDelegateCtor_MethodRef = moduleDefinition.ImportReference(networkHandlerDelegateType.GetConstructor(new[] { typeof(object), typeof(IntPtr) })); var rpcParamsType = typeof(__RpcParams); @@ -321,196 +294,74 @@ private bool ImportReferences(ModuleDefinition moduleDefinition) var clientRpcParamsType = typeof(ClientRpcParams); m_ClientRpcParams_TypeRef = moduleDefinition.ImportReference(clientRpcParamsType); - var networkSerializerType = typeof(NetworkSerializer); - m_NetworkSerializer_TypeRef = moduleDefinition.ImportReference(networkSerializerType); - foreach (var methodInfo in networkSerializerType.GetMethods()) + var fastBufferWriterType = typeof(FastBufferWriter); + m_FastBufferWriter_TypeRef = moduleDefinition.ImportReference(fastBufferWriterType); + + m_FastBufferWriter_Constructor = moduleDefinition.ImportReference( + fastBufferWriterType.GetConstructor(new[] { typeof(int), typeof(Allocator), typeof(int) })); + m_FastBufferWriter_Dispose = moduleDefinition.ImportReference(fastBufferWriterType.GetMethod("Dispose")); + + var fastBufferReaderType = typeof(FastBufferReader); + m_FastBufferReader_TypeRef = moduleDefinition.ImportReference(fastBufferReaderType); + + // Find all extension methods for FastBufferReader and FastBufferWriter to enable user-implemented + // methods to be called. + var assemblies = new List(); + assemblies.Add(m_MainModule.Assembly); + foreach (var reference in m_MainModule.AssemblyReferences) { - if (methodInfo.Name != nameof(NetworkSerializer.Serialize)) - { - continue; - } + assemblies.Add(m_AssemblyResolver.Resolve(reference)); + } - var methodParams = methodInfo.GetParameters(); - if (methodParams.Length != 1) + var extensionConstructor = + moduleDefinition.ImportReference(typeof(ExtensionAttribute).GetConstructor(new Type[] { })); + foreach (var assembly in assemblies) + { + foreach (var module in assembly.Modules) { - continue; - } + foreach (var type in module.Types) + { + var resolvedType = type.Resolve(); + if (!resolvedType.IsSealed || !resolvedType.IsAbstract || resolvedType.IsNested) + { + continue; + } + foreach (var method in type.Methods) + { + if (!method.IsStatic) + { + continue; + } - var paramType = methodParams[0].ParameterType; - if (paramType.IsByRef == false) - { - continue; - } + var isExtension = false; - var paramTypeName = paramType.Name; + foreach (var attr in method.CustomAttributes) + { + if (attr.Constructor.Resolve() == extensionConstructor.Resolve()) + { + isExtension = true; + } + } - if (paramTypeName == typeof(bool).MakeByRefType().Name) - { - m_NetworkSerializer_SerializeBool_MethodRef = moduleDefinition.ImportReference(methodInfo); - } - else if (paramTypeName == typeof(char).MakeByRefType().Name) - { - m_NetworkSerializer_SerializeChar_MethodRef = moduleDefinition.ImportReference(methodInfo); - } - else if (paramTypeName == typeof(sbyte).MakeByRefType().Name) - { - m_NetworkSerializer_SerializeSbyte_MethodRef = moduleDefinition.ImportReference(methodInfo); - } - else if (paramTypeName == typeof(byte).MakeByRefType().Name) - { - m_NetworkSerializer_SerializeByte_MethodRef = moduleDefinition.ImportReference(methodInfo); - } - else if (paramTypeName == typeof(short).MakeByRefType().Name) - { - m_NetworkSerializer_SerializeShort_MethodRef = moduleDefinition.ImportReference(methodInfo); - } - else if (paramTypeName == typeof(ushort).MakeByRefType().Name) - { - m_NetworkSerializer_SerializeUshort_MethodRef = moduleDefinition.ImportReference(methodInfo); - } - else if (paramTypeName == typeof(int).MakeByRefType().Name) - { - m_NetworkSerializer_SerializeInt_MethodRef = moduleDefinition.ImportReference(methodInfo); - } - else if (paramTypeName == typeof(uint).MakeByRefType().Name) - { - m_NetworkSerializer_SerializeUint_MethodRef = moduleDefinition.ImportReference(methodInfo); - } - else if (paramTypeName == typeof(long).MakeByRefType().Name) - { - m_NetworkSerializer_SerializeLong_MethodRef = moduleDefinition.ImportReference(methodInfo); - } - else if (paramTypeName == typeof(ulong).MakeByRefType().Name) - { - m_NetworkSerializer_SerializeUlong_MethodRef = moduleDefinition.ImportReference(methodInfo); - } - else if (paramTypeName == typeof(float).MakeByRefType().Name) - { - m_NetworkSerializer_SerializeFloat_MethodRef = moduleDefinition.ImportReference(methodInfo); - } - else if (paramTypeName == typeof(double).MakeByRefType().Name) - { - m_NetworkSerializer_SerializeDouble_MethodRef = moduleDefinition.ImportReference(methodInfo); - } - else if (paramTypeName == typeof(string).MakeByRefType().Name) - { - m_NetworkSerializer_SerializeString_MethodRef = moduleDefinition.ImportReference(methodInfo); - } - else if (paramTypeName == typeof(Color).MakeByRefType().Name) - { - m_NetworkSerializer_SerializeColor_MethodRef = moduleDefinition.ImportReference(methodInfo); - } - else if (paramTypeName == typeof(Color32).MakeByRefType().Name) - { - m_NetworkSerializer_SerializeColor32_MethodRef = moduleDefinition.ImportReference(methodInfo); - } - else if (paramTypeName == typeof(Vector2).MakeByRefType().Name) - { - m_NetworkSerializer_SerializeVector2_MethodRef = moduleDefinition.ImportReference(methodInfo); - } - else if (paramTypeName == typeof(Vector3).MakeByRefType().Name) - { - m_NetworkSerializer_SerializeVector3_MethodRef = moduleDefinition.ImportReference(methodInfo); - } - else if (paramTypeName == typeof(Vector4).MakeByRefType().Name) - { - m_NetworkSerializer_SerializeVector4_MethodRef = moduleDefinition.ImportReference(methodInfo); - } - else if (paramTypeName == typeof(Quaternion).MakeByRefType().Name) - { - m_NetworkSerializer_SerializeQuaternion_MethodRef = moduleDefinition.ImportReference(methodInfo); - } - else if (paramTypeName == typeof(Ray).MakeByRefType().Name) - { - m_NetworkSerializer_SerializeRay_MethodRef = moduleDefinition.ImportReference(methodInfo); - } - else if (paramTypeName == typeof(Ray2D).MakeByRefType().Name) - { - m_NetworkSerializer_SerializeRay2D_MethodRef = moduleDefinition.ImportReference(methodInfo); - } - else if (paramTypeName == typeof(bool[]).MakeByRefType().Name) - { - m_NetworkSerializer_SerializeBoolArray_MethodRef = moduleDefinition.ImportReference(methodInfo); - } - else if (paramTypeName == typeof(char[]).MakeByRefType().Name) - { - m_NetworkSerializer_SerializeCharArray_MethodRef = moduleDefinition.ImportReference(methodInfo); - } - else if (paramTypeName == typeof(sbyte[]).MakeByRefType().Name) - { - m_NetworkSerializer_SerializeSbyteArray_MethodRef = moduleDefinition.ImportReference(methodInfo); - } - else if (paramTypeName == typeof(byte[]).MakeByRefType().Name) - { - m_NetworkSerializer_SerializeByteArray_MethodRef = moduleDefinition.ImportReference(methodInfo); - } - else if (paramTypeName == typeof(short[]).MakeByRefType().Name) - { - m_NetworkSerializer_SerializeShortArray_MethodRef = moduleDefinition.ImportReference(methodInfo); - } - else if (paramTypeName == typeof(ushort[]).MakeByRefType().Name) - { - m_NetworkSerializer_SerializeUshortArray_MethodRef = moduleDefinition.ImportReference(methodInfo); - } - else if (paramTypeName == typeof(int[]).MakeByRefType().Name) - { - m_NetworkSerializer_SerializeIntArray_MethodRef = moduleDefinition.ImportReference(methodInfo); - } - else if (paramTypeName == typeof(uint[]).MakeByRefType().Name) - { - m_NetworkSerializer_SerializeUintArray_MethodRef = moduleDefinition.ImportReference(methodInfo); - } - else if (paramTypeName == typeof(long[]).MakeByRefType().Name) - { - m_NetworkSerializer_SerializeLongArray_MethodRef = moduleDefinition.ImportReference(methodInfo); - } - else if (paramTypeName == typeof(ulong[]).MakeByRefType().Name) - { - m_NetworkSerializer_SerializeUlongArray_MethodRef = moduleDefinition.ImportReference(methodInfo); - } - else if (paramTypeName == typeof(float[]).MakeByRefType().Name) - { - m_NetworkSerializer_SerializeFloatArray_MethodRef = moduleDefinition.ImportReference(methodInfo); - } - else if (paramTypeName == typeof(double[]).MakeByRefType().Name) - { - m_NetworkSerializer_SerializeDoubleArray_MethodRef = moduleDefinition.ImportReference(methodInfo); - } - else if (paramTypeName == typeof(string[]).MakeByRefType().Name) - { - m_NetworkSerializer_SerializeStringArray_MethodRef = moduleDefinition.ImportReference(methodInfo); - } - else if (paramTypeName == typeof(Color[]).MakeByRefType().Name) - { - m_NetworkSerializer_SerializeColorArray_MethodRef = moduleDefinition.ImportReference(methodInfo); - } - else if (paramTypeName == typeof(Color32[]).MakeByRefType().Name) - { - m_NetworkSerializer_SerializeColor32Array_MethodRef = moduleDefinition.ImportReference(methodInfo); - } - else if (paramTypeName == typeof(Vector2[]).MakeByRefType().Name) - { - m_NetworkSerializer_SerializeVector2Array_MethodRef = moduleDefinition.ImportReference(methodInfo); - } - else if (paramTypeName == typeof(Vector3[]).MakeByRefType().Name) - { - m_NetworkSerializer_SerializeVector3Array_MethodRef = moduleDefinition.ImportReference(methodInfo); - } - else if (paramTypeName == typeof(Vector4[]).MakeByRefType().Name) - { - m_NetworkSerializer_SerializeVector4Array_MethodRef = moduleDefinition.ImportReference(methodInfo); - } - else if (paramTypeName == typeof(Quaternion[]).MakeByRefType().Name) - { - m_NetworkSerializer_SerializeQuaternionArray_MethodRef = moduleDefinition.ImportReference(methodInfo); - } - else if (paramTypeName == typeof(Ray[]).MakeByRefType().Name) - { - m_NetworkSerializer_SerializeRayArray_MethodRef = moduleDefinition.ImportReference(methodInfo); - } - else if (paramTypeName == typeof(Ray2D[]).MakeByRefType().Name) - { - m_NetworkSerializer_SerializeRay2DArray_MethodRef = moduleDefinition.ImportReference(methodInfo); + if (!isExtension) + { + continue; + } + + var parameters = method.Parameters; + + if (parameters.Count == 2 + && parameters[0].ParameterType.Resolve() == m_FastBufferWriter_TypeRef.MakeByReferenceType().Resolve()) + { + m_FastBufferWriter_ExtensionMethodRefs.Add(m_MainModule.ImportReference(method)); + } + else if (parameters.Count == 2 + && parameters[0].ParameterType.Resolve() == m_FastBufferReader_TypeRef.MakeByReferenceType().Resolve()) + { + m_FastBufferReader_ExtensionMethodRefs.Add(m_MainModule.ImportReference(method)); + } + } + } } } @@ -613,6 +464,39 @@ private void ProcessNetworkBehaviour(TypeDefinition typeDefinition, string[] ass typeDefinition.Methods.Add(newGetTypeNameMethod); } + + // Weird behavior from Cecil: When importing a reference to a specific implementation of a generic + // method, it's importing the main module as a reference into itself. This causes Unity to have issues + // when attempting to iterate the assemblies to discover unit tests, as it goes into infinite recursion + // and eventually hits a stack overflow. I wasn't able to find any way to stop Cecil from importing the module + // into itself, so at the end of it all, we're just going to go back and remove it again. + var moduleName = m_MainModule.Name; + if (moduleName.EndsWith(".dll") || moduleName.EndsWith(".exe")) + { + moduleName = moduleName.Substring(0, moduleName.Length - 4); + } + + foreach (var reference in m_MainModule.AssemblyReferences) + { + var referenceName = reference.Name.Split(',')[0]; + if (referenceName.EndsWith(".dll") || referenceName.EndsWith(".exe")) + { + referenceName = referenceName.Substring(0, referenceName.Length - 4); + } + + if (moduleName == referenceName) + { + try + { + m_MainModule.AssemblyReferences.Remove(reference); + break; + } + catch (Exception e) + { + // + } + } + } } private CustomAttribute CheckAndGetRpcAttribute(MethodDefinition methodDefinition) @@ -681,34 +565,233 @@ private CustomAttribute CheckAndGetRpcAttribute(MethodDefinition methodDefinitio return null; } + // Checks for IsSerializable are moved to later as the check is now done by dynamically seeing if any valid + // serializer OR extension method exists for it. + return rpcAttribute; + } - int paramCount = methodDefinition.Parameters.Count; - for (int paramIndex = 0; paramIndex < paramCount; ++paramIndex) + private MethodReference GetWriteMethodViaSystemReflection(string name, TypeReference paramType) + { + foreach (var method in m_FastBufferWriter_TypeRef.Resolve().Methods) { - var paramDef = methodDefinition.Parameters[paramIndex]; - var paramType = paramDef.ParameterType; + if (method.Name == name) + { + var parameters = method.Parameters; + + if (parameters.Count == 0 || (parameters.Count > 1 && !parameters[1].IsOptional)) + { + continue; + } + + if (parameters[0].ParameterType.IsArray != paramType.IsArray) + { + continue; + } - // Serializable - if (paramType.IsSerializable()) + var checkType = paramType.Resolve(); + if (paramType.IsArray) + { + checkType = paramType.GetElementType().Resolve(); + } + + if ( + (parameters[0].ParameterType.Resolve() == checkType + || (parameters[0].ParameterType.Resolve() == checkType.MakeByReferenceType().Resolve() && parameters[0].IsIn))) + { + return method; + } + if (method.HasGenericParameters && method.GenericParameters.Count == 1) + { + if (method.GenericParameters[0].HasConstraints) + { + foreach (var constraint in method.GenericParameters[0].Constraints) + { + var resolvedConstraint = constraint.Resolve(); + + if ( + (resolvedConstraint.IsInterface && + checkType.HasInterface(resolvedConstraint.FullName)) + || (resolvedConstraint.IsClass && + checkType.Resolve().IsSubclassOf(resolvedConstraint.FullName))) + { + var instanceMethod = new GenericInstanceMethod(method); + instanceMethod.GenericArguments.Add(checkType); + return instanceMethod; + } + } + } + } + } + } + + return null; + } + + private bool GetWriteMethodForParameter(TypeReference paramType, out MethodReference methodRef) + { + var assemblyQualifiedName = paramType.FullName + ", " + paramType.Resolve().Module.Assembly.FullName; + var foundMethodRef = m_FastBufferWriter_WriteValue_MethodRefs.TryGetValue(assemblyQualifiedName, out methodRef); + + if (!foundMethodRef) + { + foreach (var method in m_FastBufferWriter_ExtensionMethodRefs) { - continue; + var parameters = method.Resolve().Parameters; + + if (method.Name == "WriteValueSafe") + { + if (parameters[1].IsIn) + { + if (parameters[1].ParameterType.Resolve() == paramType.MakeByReferenceType().Resolve() + && ((ByReferenceType)parameters[1].ParameterType).ElementType.IsArray == paramType.IsArray) + { + methodRef = method; + m_FastBufferWriter_WriteValue_MethodRefs[assemblyQualifiedName] = methodRef; + return true; + } + } + else + { + + if (parameters[1].ParameterType.Resolve() == paramType.Resolve() + && parameters[1].ParameterType.IsArray == paramType.IsArray) + { + methodRef = method; + m_FastBufferWriter_WriteValue_MethodRefs[assemblyQualifiedName] = methodRef; + return true; + } + } + } } - // ServerRpcParams - if (paramType.FullName == CodeGenHelpers.ServerRpcParams_FullName && isServerRpc && paramIndex == paramCount - 1) + + /*var systemType = Type.GetType(paramType.FullName); + if (systemType == null) { - continue; + systemType = Type.GetType(assemblyQualifiedName); + if (systemType == null) + { + throw new Exception("Couldn't find type for " + paramType.FullName + ", " + + paramType.Resolve().Module.Assembly.FullName); + } + }*/ + // Try NetworkSerializable first because INetworkSerializable may also be valid for WriteValueSafe + // and that would cause boxing if so. + var typeMethod = GetWriteMethodViaSystemReflection("WriteNetworkSerializable", paramType); + if (typeMethod == null) + { + typeMethod = GetWriteMethodViaSystemReflection("WriteValueSafe", paramType); } - // ClientRpcParams - if (paramType.FullName == CodeGenHelpers.ClientRpcParams_FullName && !isServerRpc && paramIndex == paramCount - 1) + if (typeMethod != null) { - continue; + methodRef = m_MainModule.ImportReference(typeMethod); + m_FastBufferWriter_WriteValue_MethodRefs[assemblyQualifiedName] = methodRef; + foundMethodRef = true; + } + } + + return foundMethodRef; + } + private MethodReference GetReadMethodViaSystemReflection(string name, TypeReference paramType) + { + foreach (var method in m_FastBufferReader_TypeRef.Resolve().Methods) + { + var paramTypeDef = paramType.Resolve(); + if (method.Name == name) + { + var parameters = method.Parameters; + + if (parameters.Count == 0 || (parameters.Count > 1 && !parameters[1].IsOptional)) + { + continue; + } + + if (!parameters[0].IsOut) + { + return null; + } + + var methodParam = ((ByReferenceType) parameters[0].ParameterType).ElementType; + + if (methodParam.IsArray != paramType.IsArray) + { + continue; + } + + var checkType = paramType.Resolve(); + if (paramType.IsArray) + { + checkType = paramType.GetElementType().Resolve(); + } + + if (methodParam.Resolve() == checkType.Resolve() || methodParam.Resolve() == checkType.MakeByReferenceType().Resolve()) + { + return method; + } + if (method.HasGenericParameters && method.GenericParameters.Count == 1) + { + if (method.GenericParameters[0].HasConstraints) + { + foreach (var constraint in method.GenericParameters[0].Constraints) + { + var resolvedConstraint = constraint.Resolve(); + + if ( + (resolvedConstraint.IsInterface && + checkType.HasInterface(resolvedConstraint.FullName)) + || (resolvedConstraint.IsClass && + checkType.Resolve().IsSubclassOf(resolvedConstraint.FullName))) + { + var instanceMethod = new GenericInstanceMethod(method); + instanceMethod.GenericArguments.Add(checkType); + return instanceMethod; + } + } + } + } + } + } + + return null; + } + + private bool GetReadMethodForParameter(TypeReference paramType, out MethodReference methodRef) + { + var assemblyQualifiedName = paramType.FullName + ", " + paramType.Resolve().Module.Assembly.FullName; + + var foundMethodRef = m_FastBufferReader_ReadValue_MethodRefs.TryGetValue(assemblyQualifiedName, out methodRef); + if (!foundMethodRef) + { + foreach (var method in m_FastBufferReader_ExtensionMethodRefs) + { + var parameters = method.Resolve().Parameters; + if ( + method.Name == "ReadValueSafe" + && parameters[1].IsOut + && parameters[1].ParameterType.Resolve() == paramType.MakeByReferenceType().Resolve() + && ((ByReferenceType)parameters[1].ParameterType).ElementType.IsArray == paramType.IsArray) + { + methodRef = method; + m_FastBufferReader_ReadValue_MethodRefs[assemblyQualifiedName] = methodRef; + return true; + } } - m_Diagnostics.AddError(methodDefinition, $"RPC method parameter does not support serialization: {paramType.FullName}"); - rpcAttribute = null; + // Try NetworkSerializable first because INetworkSerializable may also be valid for ReadValueSafe + // and that would cause boxing if so. + var typeMethod = GetReadMethodViaSystemReflection("ReadNetworkSerializable", paramType); + if (typeMethod == null) + { + typeMethod = GetReadMethodViaSystemReflection("ReadValueSafe", paramType); + } + if (typeMethod != null) + { + methodRef = m_MainModule.ImportReference(typeMethod); + m_FastBufferReader_ReadValue_MethodRefs[assemblyQualifiedName] = methodRef; + foundMethodRef = true; + } } - return rpcAttribute; + return foundMethodRef; } private void InjectWriteAndCallBlocks(MethodDefinition methodDefinition, CustomAttribute rpcAttribute, uint rpcMethodId) @@ -743,8 +826,9 @@ private void InjectWriteAndCallBlocks(MethodDefinition methodDefinition, CustomA methodDefinition.Body.Variables.Add(new VariableDefinition(m_NetworkManager_TypeRef)); int netManLocIdx = methodDefinition.Body.Variables.Count - 1; // NetworkSerializer serializer; - methodDefinition.Body.Variables.Add(new VariableDefinition(m_NetworkSerializer_TypeRef)); + methodDefinition.Body.Variables.Add(new VariableDefinition(m_FastBufferWriter_TypeRef)); int serializerLocIdx = methodDefinition.Body.Variables.Count - 1; + // XXXRpcParams if (!hasRpcParams) { @@ -813,7 +897,8 @@ private void InjectWriteAndCallBlocks(MethodDefinition methodDefinition, CustomA instructions.Add(processor.Create(OpCodes.Ldarg_0)); instructions.Add(processor.Create(OpCodes.Call, m_NetworkBehaviour_getOwnerClientId_MethodRef)); instructions.Add(processor.Create(OpCodes.Ldloc, netManLocIdx)); - instructions.Add(processor.Create(OpCodes.Callvirt, m_NetworkManager_getLocalClientId_MethodRef)); + instructions.Add( + processor.Create(OpCodes.Callvirt, m_NetworkManager_getLocalClientId_MethodRef)); instructions.Add(processor.Create(OpCodes.Ceq)); instructions.Add(processor.Create(OpCodes.Ldc_I4, 0)); instructions.Add(processor.Create(OpCodes.Ceq)); @@ -831,7 +916,8 @@ private void InjectWriteAndCallBlocks(MethodDefinition methodDefinition, CustomA instructions.Add(processor.Create(OpCodes.Brfalse, logNextInstr)); // Debug.LogError(...); - instructions.Add(processor.Create(OpCodes.Ldstr, "Only the owner can invoke a ServerRpc that requires ownership!")); + instructions.Add(processor.Create(OpCodes.Ldstr, + "Only the owner can invoke a ServerRpc that requires ownership!")); instructions.Add(processor.Create(OpCodes.Call, m_Debug_LogError_MethodRef)); instructions.Add(logNextInstr); @@ -839,1786 +925,337 @@ private void InjectWriteAndCallBlocks(MethodDefinition methodDefinition, CustomA instructions.Add(roReturnInstr); instructions.Add(roLastInstr); } + } - // var serializer = BeginSendServerRpc(rpcMethodId, serverRpcParams, rpcDelivery); - instructions.Add(processor.Create(OpCodes.Ldarg_0)); - - // rpcMethodId - instructions.Add(processor.Create(OpCodes.Ldc_I4, unchecked((int)rpcMethodId))); - - // rpcParams - instructions.Add(hasRpcParams ? processor.Create(OpCodes.Ldarg, paramCount) : processor.Create(OpCodes.Ldloc, rpcParamsIdx)); + // var writer = new FastBufferWriter(1300, Allocator.Temp, 65536); + instructions.Add(processor.Create(OpCodes.Ldloca, serializerLocIdx)); + instructions.Add(processor.Create(OpCodes.Ldc_I4, 1300)); + instructions.Add(processor.Create(OpCodes.Ldc_I4_2)); + instructions.Add(processor.Create(OpCodes.Ldc_I4, 65536)); + instructions.Add(processor.Create(OpCodes.Call, m_FastBufferWriter_Constructor)); - // rpcDelivery - instructions.Add(processor.Create(OpCodes.Ldc_I4, (int)rpcDelivery)); - - // BeginSendServerRpc - instructions.Add(processor.Create(OpCodes.Call, m_NetworkBehaviour_BeginSendServerRpc_MethodRef)); - instructions.Add(processor.Create(OpCodes.Stloc, serializerLocIdx)); - } - else - { - // ClientRpc - // var serializer = BeginSendClientRpc(rpcMethodId, clientRpcParams, rpcDelivery); - instructions.Add(processor.Create(OpCodes.Ldarg_0)); - - // rpcMethodId - instructions.Add(processor.Create(OpCodes.Ldc_I4, unchecked((int)rpcMethodId))); - - // rpcParams - instructions.Add(hasRpcParams ? processor.Create(OpCodes.Ldarg, paramCount) : processor.Create(OpCodes.Ldloc, rpcParamsIdx)); - - // rpcDelivery - instructions.Add(processor.Create(OpCodes.Ldc_I4, (int)rpcDelivery)); - - // BeginSendClientRpc - instructions.Add(processor.Create(OpCodes.Call, m_NetworkBehaviour_BeginSendClientRpc_MethodRef)); - instructions.Add(processor.Create(OpCodes.Stloc, serializerLocIdx)); - } - - // if (serializer != null) - instructions.Add(processor.Create(OpCodes.Ldloc, serializerLocIdx)); - instructions.Add(processor.Create(OpCodes.Brfalse, endInstr)); + var firstInstruction = processor.Create(OpCodes.Nop); + instructions.Add(firstInstruction); // write method parameters into stream - for (int paramIndex = 0; paramIndex < paramCount; ++paramIndex) - { - var paramDef = methodDefinition.Parameters[paramIndex]; - var paramType = paramDef.ParameterType; - - // C# primitives (+arrays) - - if (paramType == typeSystem.Boolean) - { - instructions.Add(processor.Create(OpCodes.Ldloc, serializerLocIdx)); - instructions.Add(processor.Create(OpCodes.Ldarga, paramIndex + 1)); - instructions.Add(processor.Create(OpCodes.Callvirt, m_NetworkSerializer_SerializeBool_MethodRef)); - continue; - } - - if (paramType.IsArray && paramType.GetElementType() == typeSystem.Boolean) - { - instructions.Add(processor.Create(OpCodes.Ldloc, serializerLocIdx)); - instructions.Add(processor.Create(OpCodes.Ldarga, paramIndex + 1)); - instructions.Add(processor.Create(OpCodes.Callvirt, m_NetworkSerializer_SerializeBoolArray_MethodRef)); - continue; - } - - if (paramType == typeSystem.Char) - { - instructions.Add(processor.Create(OpCodes.Ldloc, serializerLocIdx)); - instructions.Add(processor.Create(OpCodes.Ldarga, paramIndex + 1)); - instructions.Add(processor.Create(OpCodes.Callvirt, m_NetworkSerializer_SerializeChar_MethodRef)); - continue; - } - - if (paramType.IsArray && paramType.GetElementType() == typeSystem.Char) - { - instructions.Add(processor.Create(OpCodes.Ldloc, serializerLocIdx)); - instructions.Add(processor.Create(OpCodes.Ldarga, paramIndex + 1)); - instructions.Add(processor.Create(OpCodes.Callvirt, m_NetworkSerializer_SerializeCharArray_MethodRef)); - continue; - } - - if (paramType == typeSystem.SByte) - { - instructions.Add(processor.Create(OpCodes.Ldloc, serializerLocIdx)); - instructions.Add(processor.Create(OpCodes.Ldarga, paramIndex + 1)); - instructions.Add(processor.Create(OpCodes.Callvirt, m_NetworkSerializer_SerializeSbyte_MethodRef)); - continue; - } - - if (paramType.IsArray && paramType.GetElementType() == typeSystem.SByte) - { - instructions.Add(processor.Create(OpCodes.Ldloc, serializerLocIdx)); - instructions.Add(processor.Create(OpCodes.Ldarga, paramIndex + 1)); - instructions.Add(processor.Create(OpCodes.Callvirt, m_NetworkSerializer_SerializeSbyteArray_MethodRef)); - continue; - } - - if (paramType == typeSystem.Byte) - { - instructions.Add(processor.Create(OpCodes.Ldloc, serializerLocIdx)); - instructions.Add(processor.Create(OpCodes.Ldarga, paramIndex + 1)); - instructions.Add(processor.Create(OpCodes.Callvirt, m_NetworkSerializer_SerializeByte_MethodRef)); - continue; - } - - if (paramType.IsArray && paramType.GetElementType() == typeSystem.Byte) - { - instructions.Add(processor.Create(OpCodes.Ldloc, serializerLocIdx)); - instructions.Add(processor.Create(OpCodes.Ldarga, paramIndex + 1)); - instructions.Add(processor.Create(OpCodes.Callvirt, m_NetworkSerializer_SerializeByteArray_MethodRef)); - continue; - } - - if (paramType == typeSystem.Int16) - { - instructions.Add(processor.Create(OpCodes.Ldloc, serializerLocIdx)); - instructions.Add(processor.Create(OpCodes.Ldarga, paramIndex + 1)); - instructions.Add(processor.Create(OpCodes.Callvirt, m_NetworkSerializer_SerializeShort_MethodRef)); - continue; - } - - if (paramType.IsArray && paramType.GetElementType() == typeSystem.Int16) - { - instructions.Add(processor.Create(OpCodes.Ldloc, serializerLocIdx)); - instructions.Add(processor.Create(OpCodes.Ldarga, paramIndex + 1)); - instructions.Add(processor.Create(OpCodes.Callvirt, m_NetworkSerializer_SerializeShortArray_MethodRef)); - continue; - } - - if (paramType == typeSystem.UInt16) - { - instructions.Add(processor.Create(OpCodes.Ldloc, serializerLocIdx)); - instructions.Add(processor.Create(OpCodes.Ldarga, paramIndex + 1)); - instructions.Add(processor.Create(OpCodes.Callvirt, m_NetworkSerializer_SerializeUshort_MethodRef)); - continue; - } - - if (paramType.IsArray && paramType.GetElementType() == typeSystem.UInt16) - { - instructions.Add(processor.Create(OpCodes.Ldloc, serializerLocIdx)); - instructions.Add(processor.Create(OpCodes.Ldarga, paramIndex + 1)); - instructions.Add(processor.Create(OpCodes.Callvirt, m_NetworkSerializer_SerializeUshortArray_MethodRef)); - continue; - } - - if (paramType == typeSystem.Int32) - { - instructions.Add(processor.Create(OpCodes.Ldloc, serializerLocIdx)); - instructions.Add(processor.Create(OpCodes.Ldarga, paramIndex + 1)); - instructions.Add(processor.Create(OpCodes.Callvirt, m_NetworkSerializer_SerializeInt_MethodRef)); - continue; - } - - if (paramType.IsArray && paramType.GetElementType() == typeSystem.Int32) - { - instructions.Add(processor.Create(OpCodes.Ldloc, serializerLocIdx)); - instructions.Add(processor.Create(OpCodes.Ldarga, paramIndex + 1)); - instructions.Add(processor.Create(OpCodes.Callvirt, m_NetworkSerializer_SerializeIntArray_MethodRef)); - continue; - } - - if (paramType == typeSystem.UInt32) - { - instructions.Add(processor.Create(OpCodes.Ldloc, serializerLocIdx)); - instructions.Add(processor.Create(OpCodes.Ldarga, paramIndex + 1)); - instructions.Add(processor.Create(OpCodes.Callvirt, m_NetworkSerializer_SerializeUint_MethodRef)); - continue; - } - - if (paramType.IsArray && paramType.GetElementType() == typeSystem.UInt32) - { - instructions.Add(processor.Create(OpCodes.Ldloc, serializerLocIdx)); - instructions.Add(processor.Create(OpCodes.Ldarga, paramIndex + 1)); - instructions.Add(processor.Create(OpCodes.Callvirt, m_NetworkSerializer_SerializeUintArray_MethodRef)); - continue; - } - - if (paramType == typeSystem.Int64) - { - instructions.Add(processor.Create(OpCodes.Ldloc, serializerLocIdx)); - instructions.Add(processor.Create(OpCodes.Ldarga, paramIndex + 1)); - instructions.Add(processor.Create(OpCodes.Callvirt, m_NetworkSerializer_SerializeLong_MethodRef)); - continue; - } - - if (paramType.IsArray && paramType.GetElementType() == typeSystem.Int64) - { - instructions.Add(processor.Create(OpCodes.Ldloc, serializerLocIdx)); - instructions.Add(processor.Create(OpCodes.Ldarga, paramIndex + 1)); - instructions.Add(processor.Create(OpCodes.Callvirt, m_NetworkSerializer_SerializeLongArray_MethodRef)); - continue; - } - - if (paramType == typeSystem.UInt64) - { - instructions.Add(processor.Create(OpCodes.Ldloc, serializerLocIdx)); - instructions.Add(processor.Create(OpCodes.Ldarga, paramIndex + 1)); - instructions.Add(processor.Create(OpCodes.Callvirt, m_NetworkSerializer_SerializeUlong_MethodRef)); - continue; - } - - if (paramType.IsArray && paramType.GetElementType() == typeSystem.UInt64) - { - instructions.Add(processor.Create(OpCodes.Ldloc, serializerLocIdx)); - instructions.Add(processor.Create(OpCodes.Ldarga, paramIndex + 1)); - instructions.Add(processor.Create(OpCodes.Callvirt, m_NetworkSerializer_SerializeUlongArray_MethodRef)); - continue; - } - - if (paramType == typeSystem.Single) - { - instructions.Add(processor.Create(OpCodes.Ldloc, serializerLocIdx)); - instructions.Add(processor.Create(OpCodes.Ldarga, paramIndex + 1)); - instructions.Add(processor.Create(OpCodes.Callvirt, m_NetworkSerializer_SerializeFloat_MethodRef)); - continue; - } - - if (paramType.IsArray && paramType.GetElementType() == typeSystem.Single) - { - instructions.Add(processor.Create(OpCodes.Ldloc, serializerLocIdx)); - instructions.Add(processor.Create(OpCodes.Ldarga, paramIndex + 1)); - instructions.Add(processor.Create(OpCodes.Callvirt, m_NetworkSerializer_SerializeFloatArray_MethodRef)); - continue; - } - - if (paramType == typeSystem.Double) - { - instructions.Add(processor.Create(OpCodes.Ldloc, serializerLocIdx)); - instructions.Add(processor.Create(OpCodes.Ldarga, paramIndex + 1)); - instructions.Add(processor.Create(OpCodes.Callvirt, m_NetworkSerializer_SerializeDouble_MethodRef)); - continue; - } - - if (paramType.IsArray && paramType.GetElementType() == typeSystem.Double) - { - instructions.Add(processor.Create(OpCodes.Ldloc, serializerLocIdx)); - instructions.Add(processor.Create(OpCodes.Ldarga, paramIndex + 1)); - instructions.Add(processor.Create(OpCodes.Callvirt, m_NetworkSerializer_SerializeDoubleArray_MethodRef)); - continue; - } - - if (paramType == typeSystem.String) - { - instructions.Add(processor.Create(OpCodes.Ldloc, serializerLocIdx)); - instructions.Add(processor.Create(OpCodes.Ldarga, paramIndex + 1)); - instructions.Add(processor.Create(OpCodes.Callvirt, m_NetworkSerializer_SerializeString_MethodRef)); - continue; - } - - if (paramType.IsArray && paramType.GetElementType() == typeSystem.String) - { - instructions.Add(processor.Create(OpCodes.Ldloc, serializerLocIdx)); - instructions.Add(processor.Create(OpCodes.Ldarga, paramIndex + 1)); - instructions.Add(processor.Create(OpCodes.Callvirt, m_NetworkSerializer_SerializeStringArray_MethodRef)); - continue; - } - - // Unity primitives (+arrays) - - if (paramType.FullName == CodeGenHelpers.UnityColor_FullName) - { - instructions.Add(processor.Create(OpCodes.Ldloc, serializerLocIdx)); - instructions.Add(processor.Create(OpCodes.Ldarga, paramIndex + 1)); - instructions.Add(processor.Create(OpCodes.Callvirt, m_NetworkSerializer_SerializeColor_MethodRef)); - continue; - } - - if (paramType.IsArray && paramType.GetElementType().FullName == CodeGenHelpers.UnityColor_FullName) - { - instructions.Add(processor.Create(OpCodes.Ldloc, serializerLocIdx)); - instructions.Add(processor.Create(OpCodes.Ldarga, paramIndex + 1)); - instructions.Add(processor.Create(OpCodes.Callvirt, m_NetworkSerializer_SerializeColorArray_MethodRef)); - continue; - } - - if (paramType.FullName == CodeGenHelpers.UnityColor32_FullName) - { - instructions.Add(processor.Create(OpCodes.Ldloc, serializerLocIdx)); - instructions.Add(processor.Create(OpCodes.Ldarga, paramIndex + 1)); - instructions.Add(processor.Create(OpCodes.Callvirt, m_NetworkSerializer_SerializeColor32_MethodRef)); - continue; - } - - if (paramType.IsArray && paramType.GetElementType().FullName == CodeGenHelpers.UnityColor32_FullName) - { - instructions.Add(processor.Create(OpCodes.Ldloc, serializerLocIdx)); - instructions.Add(processor.Create(OpCodes.Ldarga, paramIndex + 1)); - instructions.Add(processor.Create(OpCodes.Callvirt, m_NetworkSerializer_SerializeColor32Array_MethodRef)); - continue; - } - - if (paramType.FullName == CodeGenHelpers.UnityVector2_FullName) - { - instructions.Add(processor.Create(OpCodes.Ldloc, serializerLocIdx)); - instructions.Add(processor.Create(OpCodes.Ldarga, paramIndex + 1)); - instructions.Add(processor.Create(OpCodes.Callvirt, m_NetworkSerializer_SerializeVector2_MethodRef)); - continue; - } - - if (paramType.IsArray && paramType.GetElementType().FullName == CodeGenHelpers.UnityVector2_FullName) - { - instructions.Add(processor.Create(OpCodes.Ldloc, serializerLocIdx)); - instructions.Add(processor.Create(OpCodes.Ldarga, paramIndex + 1)); - instructions.Add(processor.Create(OpCodes.Callvirt, m_NetworkSerializer_SerializeVector2Array_MethodRef)); - continue; - } - - if (paramType.FullName == CodeGenHelpers.UnityVector3_FullName) - { - instructions.Add(processor.Create(OpCodes.Ldloc, serializerLocIdx)); - instructions.Add(processor.Create(OpCodes.Ldarga, paramIndex + 1)); - instructions.Add(processor.Create(OpCodes.Callvirt, m_NetworkSerializer_SerializeVector3_MethodRef)); - continue; - } - - if (paramType.IsArray && paramType.GetElementType().FullName == CodeGenHelpers.UnityVector3_FullName) - { - instructions.Add(processor.Create(OpCodes.Ldloc, serializerLocIdx)); - instructions.Add(processor.Create(OpCodes.Ldarga, paramIndex + 1)); - instructions.Add(processor.Create(OpCodes.Callvirt, m_NetworkSerializer_SerializeVector3Array_MethodRef)); - continue; - } - - if (paramType.FullName == CodeGenHelpers.UnityVector4_FullName) - { - instructions.Add(processor.Create(OpCodes.Ldloc, serializerLocIdx)); - instructions.Add(processor.Create(OpCodes.Ldarga, paramIndex + 1)); - instructions.Add(processor.Create(OpCodes.Callvirt, m_NetworkSerializer_SerializeVector4_MethodRef)); - continue; - } - - if (paramType.IsArray && paramType.GetElementType().FullName == CodeGenHelpers.UnityVector4_FullName) - { - instructions.Add(processor.Create(OpCodes.Ldloc, serializerLocIdx)); - instructions.Add(processor.Create(OpCodes.Ldarga, paramIndex + 1)); - instructions.Add(processor.Create(OpCodes.Callvirt, m_NetworkSerializer_SerializeVector4Array_MethodRef)); - continue; - } - - if (paramType.FullName == CodeGenHelpers.UnityQuaternion_FullName) - { - instructions.Add(processor.Create(OpCodes.Ldloc, serializerLocIdx)); - instructions.Add(processor.Create(OpCodes.Ldarga, paramIndex + 1)); - instructions.Add(processor.Create(OpCodes.Callvirt, m_NetworkSerializer_SerializeQuaternion_MethodRef)); - continue; - } - - if (paramType.IsArray && paramType.GetElementType().FullName == CodeGenHelpers.UnityQuaternion_FullName) - { - instructions.Add(processor.Create(OpCodes.Ldloc, serializerLocIdx)); - instructions.Add(processor.Create(OpCodes.Ldarga, paramIndex + 1)); - instructions.Add(processor.Create(OpCodes.Callvirt, m_NetworkSerializer_SerializeQuaternionArray_MethodRef)); - continue; - } - - if (paramType.FullName == CodeGenHelpers.UnityRay_FullName) - { - instructions.Add(processor.Create(OpCodes.Ldloc, serializerLocIdx)); - instructions.Add(processor.Create(OpCodes.Ldarga, paramIndex + 1)); - instructions.Add(processor.Create(OpCodes.Callvirt, m_NetworkSerializer_SerializeRay_MethodRef)); - continue; - } - - if (paramType.IsArray && paramType.GetElementType().FullName == CodeGenHelpers.UnityRay_FullName) - { - instructions.Add(processor.Create(OpCodes.Ldloc, serializerLocIdx)); - instructions.Add(processor.Create(OpCodes.Ldarga, paramIndex + 1)); - instructions.Add(processor.Create(OpCodes.Callvirt, m_NetworkSerializer_SerializeRayArray_MethodRef)); - continue; - } - - if (paramType.FullName == CodeGenHelpers.UnityRay2D_FullName) - { - instructions.Add(processor.Create(OpCodes.Ldloc, serializerLocIdx)); - instructions.Add(processor.Create(OpCodes.Ldarga, paramIndex + 1)); - instructions.Add(processor.Create(OpCodes.Callvirt, m_NetworkSerializer_SerializeRay2D_MethodRef)); - continue; - } - - if (paramType.IsArray && paramType.GetElementType().FullName == CodeGenHelpers.UnityRay2D_FullName) - { - instructions.Add(processor.Create(OpCodes.Ldloc, serializerLocIdx)); - instructions.Add(processor.Create(OpCodes.Ldarga, paramIndex + 1)); - instructions.Add(processor.Create(OpCodes.Callvirt, m_NetworkSerializer_SerializeRay2DArray_MethodRef)); - continue; - } - - // Enum - - { - var paramEnumIntType = paramType.GetEnumAsInt(); - if (paramEnumIntType != null) - { - if (paramEnumIntType == typeSystem.Int32) - { - methodDefinition.Body.Variables.Add(new VariableDefinition(typeSystem.Int32)); - int localIndex = methodDefinition.Body.Variables.Count - 1; - - instructions.Add(processor.Create(OpCodes.Ldarg, paramIndex + 1)); - instructions.Add(processor.Create(OpCodes.Stloc, localIndex)); - - instructions.Add(processor.Create(OpCodes.Ldloc, serializerLocIdx)); - instructions.Add(processor.Create(OpCodes.Ldloca, localIndex)); - instructions.Add(processor.Create(OpCodes.Callvirt, m_NetworkSerializer_SerializeInt_MethodRef)); - continue; - } - - if (paramEnumIntType == typeSystem.UInt32) - { - methodDefinition.Body.Variables.Add(new VariableDefinition(typeSystem.UInt32)); - int localIndex = methodDefinition.Body.Variables.Count - 1; - - instructions.Add(processor.Create(OpCodes.Ldarg, paramIndex + 1)); - instructions.Add(processor.Create(OpCodes.Stloc, localIndex)); - - instructions.Add(processor.Create(OpCodes.Ldloc, serializerLocIdx)); - instructions.Add(processor.Create(OpCodes.Ldloca, localIndex)); - instructions.Add(processor.Create(OpCodes.Callvirt, m_NetworkSerializer_SerializeUint_MethodRef)); - continue; - } - - if (paramEnumIntType == typeSystem.Byte) - { - methodDefinition.Body.Variables.Add(new VariableDefinition(typeSystem.Byte)); - int localIndex = methodDefinition.Body.Variables.Count - 1; - - instructions.Add(processor.Create(OpCodes.Ldarg, paramIndex + 1)); - instructions.Add(processor.Create(OpCodes.Stloc, localIndex)); - - instructions.Add(processor.Create(OpCodes.Ldloc, serializerLocIdx)); - instructions.Add(processor.Create(OpCodes.Ldloca, localIndex)); - instructions.Add(processor.Create(OpCodes.Callvirt, m_NetworkSerializer_SerializeByte_MethodRef)); - continue; - } - - if (paramEnumIntType == typeSystem.SByte) - { - methodDefinition.Body.Variables.Add(new VariableDefinition(typeSystem.SByte)); - int localIndex = methodDefinition.Body.Variables.Count - 1; - - instructions.Add(processor.Create(OpCodes.Ldarg, paramIndex + 1)); - instructions.Add(processor.Create(OpCodes.Stloc, localIndex)); - - instructions.Add(processor.Create(OpCodes.Ldloc, serializerLocIdx)); - instructions.Add(processor.Create(OpCodes.Ldloca, localIndex)); - instructions.Add(processor.Create(OpCodes.Callvirt, m_NetworkSerializer_SerializeSbyte_MethodRef)); - continue; - } - - if (paramEnumIntType == typeSystem.Int16) - { - methodDefinition.Body.Variables.Add(new VariableDefinition(typeSystem.Int16)); - int localIndex = methodDefinition.Body.Variables.Count - 1; - - instructions.Add(processor.Create(OpCodes.Ldarg, paramIndex + 1)); - instructions.Add(processor.Create(OpCodes.Stloc, localIndex)); - - instructions.Add(processor.Create(OpCodes.Ldloc, serializerLocIdx)); - instructions.Add(processor.Create(OpCodes.Ldloca, localIndex)); - instructions.Add(processor.Create(OpCodes.Callvirt, m_NetworkSerializer_SerializeShort_MethodRef)); - continue; - } - - if (paramEnumIntType == typeSystem.UInt16) - { - methodDefinition.Body.Variables.Add(new VariableDefinition(typeSystem.UInt16)); - int localIndex = methodDefinition.Body.Variables.Count - 1; - - instructions.Add(processor.Create(OpCodes.Ldarg, paramIndex + 1)); - instructions.Add(processor.Create(OpCodes.Stloc, localIndex)); - - instructions.Add(processor.Create(OpCodes.Ldloc, serializerLocIdx)); - instructions.Add(processor.Create(OpCodes.Ldloca, localIndex)); - instructions.Add(processor.Create(OpCodes.Callvirt, m_NetworkSerializer_SerializeUshort_MethodRef)); - continue; - } - - if (paramEnumIntType == typeSystem.Int64) - { - methodDefinition.Body.Variables.Add(new VariableDefinition(typeSystem.Int64)); - int localIndex = methodDefinition.Body.Variables.Count - 1; - - instructions.Add(processor.Create(OpCodes.Ldarg, paramIndex + 1)); - instructions.Add(processor.Create(OpCodes.Stloc, localIndex)); - - instructions.Add(processor.Create(OpCodes.Ldloc, serializerLocIdx)); - instructions.Add(processor.Create(OpCodes.Ldloca, localIndex)); - instructions.Add(processor.Create(OpCodes.Callvirt, m_NetworkSerializer_SerializeLong_MethodRef)); - continue; - } - - if (paramEnumIntType == typeSystem.UInt64) - { - methodDefinition.Body.Variables.Add(new VariableDefinition(typeSystem.UInt64)); - int localIndex = methodDefinition.Body.Variables.Count - 1; - - instructions.Add(processor.Create(OpCodes.Ldarg, paramIndex + 1)); - instructions.Add(processor.Create(OpCodes.Stloc, localIndex)); - - instructions.Add(processor.Create(OpCodes.Ldloc, serializerLocIdx)); - instructions.Add(processor.Create(OpCodes.Ldloca, localIndex)); - instructions.Add(processor.Create(OpCodes.Callvirt, m_NetworkSerializer_SerializeUlong_MethodRef)); - continue; - } - } - } - - // Enum array - - if (paramType.IsArray) - { - var paramElemEnumIntType = paramType.GetElementType().GetEnumAsInt(); - if (paramElemEnumIntType != null) - { - methodDefinition.Body.Variables.Add(new VariableDefinition(typeSystem.Int32)); - int arrLenLocalIndex = methodDefinition.Body.Variables.Count - 1; - - var endifInstr = processor.Create(OpCodes.Nop); - var arrLenInstr = processor.Create(OpCodes.Nop); - - instructions.Add(processor.Create(OpCodes.Ldarg, paramIndex + 1)); - instructions.Add(processor.Create(OpCodes.Brtrue, arrLenInstr)); - instructions.Add(processor.Create(OpCodes.Ldc_I4_M1)); - instructions.Add(processor.Create(OpCodes.Br, endifInstr)); - instructions.Add(arrLenInstr); - instructions.Add(processor.Create(OpCodes.Ldarg, paramIndex + 1)); - instructions.Add(processor.Create(OpCodes.Ldlen)); - instructions.Add(processor.Create(OpCodes.Conv_I4)); - instructions.Add(endifInstr); - instructions.Add(processor.Create(OpCodes.Stloc, arrLenLocalIndex)); - - instructions.Add(processor.Create(OpCodes.Ldloc, serializerLocIdx)); - instructions.Add(processor.Create(OpCodes.Ldloca, arrLenLocalIndex)); - instructions.Add(processor.Create(OpCodes.Callvirt, m_NetworkSerializer_SerializeInt_MethodRef)); - - methodDefinition.Body.Variables.Add(new VariableDefinition(typeSystem.Int32)); - int counterLocalIndex = methodDefinition.Body.Variables.Count - 1; - - var forBodyInstr = processor.Create(OpCodes.Nop); - var forCheckInstr = processor.Create(OpCodes.Nop); - - instructions.Add(processor.Create(OpCodes.Ldc_I4_0)); - instructions.Add(processor.Create(OpCodes.Stloc, counterLocalIndex)); - instructions.Add(processor.Create(OpCodes.Br, forCheckInstr)); - instructions.Add(forBodyInstr); - - if (paramElemEnumIntType == typeSystem.Int32) - { - methodDefinition.Body.Variables.Add(new VariableDefinition(typeSystem.Int32)); - int enumValLocalIndex = methodDefinition.Body.Variables.Count - 1; - - instructions.Add(processor.Create(OpCodes.Ldarg, paramIndex + 1)); - instructions.Add(processor.Create(OpCodes.Ldloc, counterLocalIndex)); - instructions.Add(processor.Create(OpCodes.Ldelem_I4)); - instructions.Add(processor.Create(OpCodes.Stloc, enumValLocalIndex)); - - instructions.Add(processor.Create(OpCodes.Ldloc, serializerLocIdx)); - instructions.Add(processor.Create(OpCodes.Ldloca, enumValLocalIndex)); - instructions.Add(processor.Create(OpCodes.Callvirt, m_NetworkSerializer_SerializeInt_MethodRef)); - } - else if (paramElemEnumIntType == typeSystem.UInt32) - { - methodDefinition.Body.Variables.Add(new VariableDefinition(typeSystem.UInt32)); - int enumValLocalIndex = methodDefinition.Body.Variables.Count - 1; - - instructions.Add(processor.Create(OpCodes.Ldarg, paramIndex + 1)); - instructions.Add(processor.Create(OpCodes.Ldloc, counterLocalIndex)); - instructions.Add(processor.Create(OpCodes.Ldelem_U4)); - instructions.Add(processor.Create(OpCodes.Stloc, enumValLocalIndex)); - - instructions.Add(processor.Create(OpCodes.Ldloc, serializerLocIdx)); - instructions.Add(processor.Create(OpCodes.Ldloca, enumValLocalIndex)); - instructions.Add(processor.Create(OpCodes.Callvirt, m_NetworkSerializer_SerializeUint_MethodRef)); - } - else if (paramElemEnumIntType == typeSystem.Byte) - { - methodDefinition.Body.Variables.Add(new VariableDefinition(typeSystem.Byte)); - int enumValLocalIndex = methodDefinition.Body.Variables.Count - 1; - - instructions.Add(processor.Create(OpCodes.Ldarg, paramIndex + 1)); - instructions.Add(processor.Create(OpCodes.Ldloc, counterLocalIndex)); - instructions.Add(processor.Create(OpCodes.Ldelem_U1)); - instructions.Add(processor.Create(OpCodes.Stloc, enumValLocalIndex)); - - instructions.Add(processor.Create(OpCodes.Ldloc, serializerLocIdx)); - instructions.Add(processor.Create(OpCodes.Ldloca, enumValLocalIndex)); - instructions.Add(processor.Create(OpCodes.Callvirt, m_NetworkSerializer_SerializeByte_MethodRef)); - } - else if (paramElemEnumIntType == typeSystem.SByte) - { - methodDefinition.Body.Variables.Add(new VariableDefinition(typeSystem.SByte)); - int enumValLocalIndex = methodDefinition.Body.Variables.Count - 1; - - instructions.Add(processor.Create(OpCodes.Ldarg, paramIndex + 1)); - instructions.Add(processor.Create(OpCodes.Ldloc, counterLocalIndex)); - instructions.Add(processor.Create(OpCodes.Ldelem_I1)); - instructions.Add(processor.Create(OpCodes.Stloc, enumValLocalIndex)); - - instructions.Add(processor.Create(OpCodes.Ldloc, serializerLocIdx)); - instructions.Add(processor.Create(OpCodes.Ldloca, enumValLocalIndex)); - instructions.Add(processor.Create(OpCodes.Callvirt, m_NetworkSerializer_SerializeSbyte_MethodRef)); - } - else if (paramElemEnumIntType == typeSystem.Int16) - { - methodDefinition.Body.Variables.Add(new VariableDefinition(typeSystem.Int16)); - int enumValLocalIndex = methodDefinition.Body.Variables.Count - 1; - - instructions.Add(processor.Create(OpCodes.Ldarg, paramIndex + 1)); - instructions.Add(processor.Create(OpCodes.Ldloc, counterLocalIndex)); - instructions.Add(processor.Create(OpCodes.Ldelem_I2)); - instructions.Add(processor.Create(OpCodes.Stloc, enumValLocalIndex)); - - instructions.Add(processor.Create(OpCodes.Ldloc, serializerLocIdx)); - instructions.Add(processor.Create(OpCodes.Ldloca, enumValLocalIndex)); - instructions.Add(processor.Create(OpCodes.Callvirt, m_NetworkSerializer_SerializeShort_MethodRef)); - } - else if (paramElemEnumIntType == typeSystem.UInt16) - { - methodDefinition.Body.Variables.Add(new VariableDefinition(typeSystem.UInt16)); - int enumValLocalIndex = methodDefinition.Body.Variables.Count - 1; - - instructions.Add(processor.Create(OpCodes.Ldarg, paramIndex + 1)); - instructions.Add(processor.Create(OpCodes.Ldloc, counterLocalIndex)); - instructions.Add(processor.Create(OpCodes.Ldelem_U2)); - instructions.Add(processor.Create(OpCodes.Stloc, enumValLocalIndex)); - - instructions.Add(processor.Create(OpCodes.Ldloc, serializerLocIdx)); - instructions.Add(processor.Create(OpCodes.Ldloca, enumValLocalIndex)); - instructions.Add(processor.Create(OpCodes.Callvirt, m_NetworkSerializer_SerializeUshort_MethodRef)); - } - else if (paramElemEnumIntType == typeSystem.Int64) - { - methodDefinition.Body.Variables.Add(new VariableDefinition(typeSystem.Int64)); - int enumValLocalIndex = methodDefinition.Body.Variables.Count - 1; - - instructions.Add(processor.Create(OpCodes.Ldarg, paramIndex + 1)); - instructions.Add(processor.Create(OpCodes.Ldloc, counterLocalIndex)); - instructions.Add(processor.Create(OpCodes.Ldelem_I8)); - instructions.Add(processor.Create(OpCodes.Stloc, enumValLocalIndex)); - - instructions.Add(processor.Create(OpCodes.Ldloc, serializerLocIdx)); - instructions.Add(processor.Create(OpCodes.Ldloca, enumValLocalIndex)); - instructions.Add(processor.Create(OpCodes.Callvirt, m_NetworkSerializer_SerializeLong_MethodRef)); - } - else if (paramElemEnumIntType == typeSystem.UInt64) - { - methodDefinition.Body.Variables.Add(new VariableDefinition(typeSystem.UInt64)); - int enumValLocalIndex = methodDefinition.Body.Variables.Count - 1; - - instructions.Add(processor.Create(OpCodes.Ldarg, paramIndex + 1)); - instructions.Add(processor.Create(OpCodes.Ldloc, counterLocalIndex)); - instructions.Add(processor.Create(OpCodes.Ldelem_I8)); - instructions.Add(processor.Create(OpCodes.Stloc, enumValLocalIndex)); - - instructions.Add(processor.Create(OpCodes.Ldloc, serializerLocIdx)); - instructions.Add(processor.Create(OpCodes.Ldloca, enumValLocalIndex)); - instructions.Add(processor.Create(OpCodes.Callvirt, m_NetworkSerializer_SerializeUlong_MethodRef)); - } - - instructions.Add(processor.Create(OpCodes.Ldloc, counterLocalIndex)); - instructions.Add(processor.Create(OpCodes.Ldc_I4_1)); - instructions.Add(processor.Create(OpCodes.Add)); - instructions.Add(processor.Create(OpCodes.Stloc, counterLocalIndex)); - instructions.Add(forCheckInstr); - instructions.Add(processor.Create(OpCodes.Ldloc, counterLocalIndex)); - instructions.Add(processor.Create(OpCodes.Ldloc, arrLenLocalIndex)); - instructions.Add(processor.Create(OpCodes.Clt)); - instructions.Add(processor.Create(OpCodes.Brtrue, forBodyInstr)); - - continue; - } - } - - // INetworkSerializable - - if (paramType.HasInterface(CodeGenHelpers.INetworkSerializable_FullName)) - { - var paramTypeDef = paramType.Resolve(); - var paramTypeNetworkSerialize_MethodDef = paramTypeDef.Methods.FirstOrDefault(m => m.Name == CodeGenHelpers.INetworkSerializable_NetworkSerialize_Name); - var paramTypeNetworkSerialize_MethodRef = methodDefinition.Module.ImportReference(paramTypeNetworkSerialize_MethodDef); - if (paramTypeNetworkSerialize_MethodRef != null) - { - if (paramType.IsValueType) - { - // struct (pass by value) - instructions.Add(processor.Create(OpCodes.Ldarga, paramIndex + 1)); - instructions.Add(processor.Create(OpCodes.Ldloc, serializerLocIdx)); - instructions.Add(processor.Create(OpCodes.Call, paramTypeNetworkSerialize_MethodRef)); - } - else - { - // class (pass by reference) - methodDefinition.Body.Variables.Add(new VariableDefinition(typeSystem.Boolean)); - int isSetLocalIndex = methodDefinition.Body.Variables.Count - 1; - - instructions.Add(processor.Create(OpCodes.Ldarg, paramIndex + 1)); - instructions.Add(processor.Create(OpCodes.Ldnull)); - instructions.Add(processor.Create(OpCodes.Cgt_Un)); - instructions.Add(processor.Create(OpCodes.Stloc, isSetLocalIndex)); - - instructions.Add(processor.Create(OpCodes.Ldloc, serializerLocIdx)); - instructions.Add(processor.Create(OpCodes.Ldloca, isSetLocalIndex)); - instructions.Add(processor.Create(OpCodes.Callvirt, m_NetworkSerializer_SerializeBool_MethodRef)); - - var notSetInstr = processor.Create(OpCodes.Nop); - - instructions.Add(processor.Create(OpCodes.Ldloc, isSetLocalIndex)); - instructions.Add(processor.Create(OpCodes.Brfalse, notSetInstr)); - - instructions.Add(processor.Create(OpCodes.Ldarg, paramIndex + 1)); - instructions.Add(processor.Create(OpCodes.Ldloc, serializerLocIdx)); - instructions.Add(processor.Create(OpCodes.Callvirt, paramTypeNetworkSerialize_MethodRef)); - - instructions.Add(notSetInstr); - } - - continue; - } - } - - // INetworkSerializable[] - if (paramType.IsArray && paramType.GetElementType().HasInterface(CodeGenHelpers.INetworkSerializable_FullName)) - { - var paramElemType = paramType.GetElementType(); - var paramElemTypeDef = paramElemType.Resolve(); - var paramElemNetworkSerialize_MethodDef = paramElemTypeDef.Methods.FirstOrDefault(m => m.Name == CodeGenHelpers.INetworkSerializable_NetworkSerialize_Name); - var paramElemNetworkSerialize_MethodRef = methodDefinition.Module.ImportReference(paramElemNetworkSerialize_MethodDef); - if (paramElemNetworkSerialize_MethodRef != null) - { - methodDefinition.Body.Variables.Add(new VariableDefinition(typeSystem.Int32)); - int arrLenLocalIndex = methodDefinition.Body.Variables.Count - 1; - - var endifInstr = processor.Create(OpCodes.Nop); - var arrLenInstr = processor.Create(OpCodes.Nop); - - instructions.Add(processor.Create(OpCodes.Ldarg, paramIndex + 1)); - instructions.Add(processor.Create(OpCodes.Brtrue, arrLenInstr)); - instructions.Add(processor.Create(OpCodes.Ldc_I4_M1)); - instructions.Add(processor.Create(OpCodes.Br, endifInstr)); - instructions.Add(arrLenInstr); - instructions.Add(processor.Create(OpCodes.Ldarg, paramIndex + 1)); - instructions.Add(processor.Create(OpCodes.Ldlen)); - instructions.Add(processor.Create(OpCodes.Conv_I4)); - instructions.Add(endifInstr); - instructions.Add(processor.Create(OpCodes.Stloc, arrLenLocalIndex)); - - instructions.Add(processor.Create(OpCodes.Ldloc, serializerLocIdx)); - instructions.Add(processor.Create(OpCodes.Ldloca, arrLenLocalIndex)); - instructions.Add(processor.Create(OpCodes.Callvirt, m_NetworkSerializer_SerializeInt_MethodRef)); - - methodDefinition.Body.Variables.Add(new VariableDefinition(typeSystem.Int32)); - int counterLocalIndex = methodDefinition.Body.Variables.Count - 1; - - var forBodyInstr = processor.Create(OpCodes.Nop); - var forCheckInstr = processor.Create(OpCodes.Nop); - - instructions.Add(processor.Create(OpCodes.Ldc_I4_0)); - instructions.Add(processor.Create(OpCodes.Stloc, counterLocalIndex)); - instructions.Add(processor.Create(OpCodes.Br, forCheckInstr)); - instructions.Add(forBodyInstr); - - if (paramElemType.IsValueType) - { - // struct (pass by value) - instructions.Add(processor.Create(OpCodes.Ldarg, paramIndex + 1)); - instructions.Add(processor.Create(OpCodes.Ldloc, counterLocalIndex)); - instructions.Add(processor.Create(OpCodes.Ldelema, paramElemType)); - instructions.Add(processor.Create(OpCodes.Ldloc, serializerLocIdx)); - instructions.Add(processor.Create(OpCodes.Call, paramElemNetworkSerialize_MethodRef)); - } - else - { - // class (pass by reference) - methodDefinition.Body.Variables.Add(new VariableDefinition(typeSystem.Boolean)); - int isSetLocalIndex = methodDefinition.Body.Variables.Count - 1; - - instructions.Add(processor.Create(OpCodes.Ldarg, paramIndex + 1)); - instructions.Add(processor.Create(OpCodes.Ldloc, counterLocalIndex)); - instructions.Add(processor.Create(OpCodes.Ldelem_Ref)); - instructions.Add(processor.Create(OpCodes.Ldnull)); - instructions.Add(processor.Create(OpCodes.Cgt_Un)); - instructions.Add(processor.Create(OpCodes.Stloc, isSetLocalIndex)); - - instructions.Add(processor.Create(OpCodes.Ldloc, serializerLocIdx)); - instructions.Add(processor.Create(OpCodes.Ldloca, isSetLocalIndex)); - instructions.Add(processor.Create(OpCodes.Callvirt, m_NetworkSerializer_SerializeBool_MethodRef)); - - var notSetInstr = processor.Create(OpCodes.Nop); - - instructions.Add(processor.Create(OpCodes.Ldloc, isSetLocalIndex)); - instructions.Add(processor.Create(OpCodes.Brfalse, notSetInstr)); - - instructions.Add(processor.Create(OpCodes.Ldarg, paramIndex + 1)); - instructions.Add(processor.Create(OpCodes.Ldloc, counterLocalIndex)); - instructions.Add(processor.Create(OpCodes.Ldelem_Ref)); - instructions.Add(processor.Create(OpCodes.Ldloc, serializerLocIdx)); - instructions.Add(processor.Create(OpCodes.Callvirt, paramElemNetworkSerialize_MethodRef)); - - instructions.Add(notSetInstr); - } - - instructions.Add(processor.Create(OpCodes.Ldloc, counterLocalIndex)); - instructions.Add(processor.Create(OpCodes.Ldc_I4_1)); - instructions.Add(processor.Create(OpCodes.Add)); - instructions.Add(processor.Create(OpCodes.Stloc, counterLocalIndex)); - instructions.Add(forCheckInstr); - instructions.Add(processor.Create(OpCodes.Ldloc, counterLocalIndex)); - instructions.Add(processor.Create(OpCodes.Ldloc, arrLenLocalIndex)); - instructions.Add(processor.Create(OpCodes.Clt)); - instructions.Add(processor.Create(OpCodes.Brtrue, forBodyInstr)); - - continue; - } - } - } - - instructions.Add(endInstr); - - // EndSendServerRpc(serializer, rpcMethodId, serverRpcParams, rpcDelivery) -> ServerRpc - // EndSendClientRpc(serializer, rpcMethodId, clientRpcParams, rpcDelivery) -> ClientRpc - if (isServerRpc) - { - // ServerRpc - // EndSendServerRpc(serializer, rpcMethodId, serverRpcParams, rpcDelivery); - instructions.Add(processor.Create(OpCodes.Ldarg_0)); - - // serializer - instructions.Add(processor.Create(OpCodes.Ldloc, serializerLocIdx)); - - // rpcMethodId - instructions.Add(processor.Create(OpCodes.Ldc_I4, unchecked((int)rpcMethodId))); - - if (hasRpcParams) - { - // rpcParams - instructions.Add(processor.Create(OpCodes.Ldarg, paramCount)); - } - else - { - // default - instructions.Add(processor.Create(OpCodes.Ldloc, rpcParamsIdx)); - } - - // rpcDelivery - instructions.Add(processor.Create(OpCodes.Ldc_I4, (int)rpcDelivery)); - - // EndSendServerRpc - instructions.Add(processor.Create(OpCodes.Call, m_NetworkBehaviour_EndSendServerRpc_MethodRef)); - } - else - { - // ClientRpc - // EndSendClientRpc(serializer, rpcMethodId, clientRpcParams, rpcDelivery); - instructions.Add(processor.Create(OpCodes.Ldarg_0)); - - // serializer - instructions.Add(processor.Create(OpCodes.Ldloc, serializerLocIdx)); - - // rpcMethodId - instructions.Add(processor.Create(OpCodes.Ldc_I4, unchecked((int)rpcMethodId))); - - if (hasRpcParams) - { - // rpcParams - instructions.Add(processor.Create(OpCodes.Ldarg, paramCount)); - } - else - { - // default - instructions.Add(processor.Create(OpCodes.Ldloc, rpcParamsIdx)); - } - - // rpcDelivery - instructions.Add(processor.Create(OpCodes.Ldc_I4, (int)rpcDelivery)); - - // EndSendClientRpc - instructions.Add(processor.Create(OpCodes.Call, m_NetworkBehaviour_EndSendClientRpc_MethodRef)); - } - - instructions.Add(lastInstr); - } - - { - var returnInstr = processor.Create(OpCodes.Ret); - var lastInstr = processor.Create(OpCodes.Nop); - - // if (__rpc_exec_stage == __RpcExecStage.Server) -> ServerRpc - // if (__rpc_exec_stage == __RpcExecStage.Client) -> ClientRpc - instructions.Add(processor.Create(OpCodes.Ldarg_0)); - instructions.Add(processor.Create(OpCodes.Ldfld, m_NetworkBehaviour_rpc_exec_stage_FieldRef)); - instructions.Add(processor.Create(OpCodes.Ldc_I4, (int)(isServerRpc ? NetworkBehaviour.__RpcExecStage.Server : NetworkBehaviour.__RpcExecStage.Client))); - instructions.Add(processor.Create(OpCodes.Ceq)); - instructions.Add(processor.Create(OpCodes.Brfalse, returnInstr)); - - // if (networkManager.IsServer || networkManager.IsHost) -> ServerRpc - // if (networkManager.IsClient || networkManager.IsHost) -> ClientRpc - instructions.Add(processor.Create(OpCodes.Ldloc, netManLocIdx)); - instructions.Add(processor.Create(OpCodes.Callvirt, isServerRpc ? m_NetworkManager_getIsServer_MethodRef : m_NetworkManager_getIsClient_MethodRef)); - instructions.Add(processor.Create(OpCodes.Brtrue, lastInstr)); - instructions.Add(processor.Create(OpCodes.Ldloc, netManLocIdx)); - instructions.Add(processor.Create(OpCodes.Callvirt, m_NetworkManager_getIsHost_MethodRef)); - instructions.Add(processor.Create(OpCodes.Brtrue, lastInstr)); - - instructions.Add(returnInstr); - instructions.Add(lastInstr); - } - - instructions.Reverse(); - instructions.ForEach(instruction => processor.Body.Instructions.Insert(0, instruction)); - } - - private MethodDefinition GenerateStaticHandler(MethodDefinition methodDefinition, CustomAttribute rpcAttribute) - { - var typeSystem = methodDefinition.Module.TypeSystem; - var nhandler = new MethodDefinition( - $"{methodDefinition.Name}__nhandler", - MethodAttributes.Private | MethodAttributes.Static | MethodAttributes.HideBySig, - methodDefinition.Module.TypeSystem.Void); - nhandler.Parameters.Add(new ParameterDefinition("target", ParameterAttributes.None, m_NetworkBehaviour_TypeRef)); - nhandler.Parameters.Add(new ParameterDefinition("serializer", ParameterAttributes.None, m_NetworkSerializer_TypeRef)); - nhandler.Parameters.Add(new ParameterDefinition("rpcParams", ParameterAttributes.None, m_RpcParams_TypeRef)); - - var processor = nhandler.Body.GetILProcessor(); - var isServerRpc = rpcAttribute.AttributeType.FullName == CodeGenHelpers.ServerRpcAttribute_FullName; - var requireOwnership = true; // default value MUST be = `ServerRpcAttribute.RequireOwnership` - foreach (var attrField in rpcAttribute.Fields) - { - switch (attrField.Name) - { - case k_ServerRpcAttribute_RequireOwnership: - requireOwnership = attrField.Argument.Type == typeSystem.Boolean && (bool)attrField.Argument.Value; - break; - } - } - - nhandler.Body.InitLocals = true; - // NetworkManager networkManager; - nhandler.Body.Variables.Add(new VariableDefinition(m_NetworkManager_TypeRef)); - int netManLocIdx = nhandler.Body.Variables.Count - 1; - - { - var returnInstr = processor.Create(OpCodes.Ret); - var lastInstr = processor.Create(OpCodes.Nop); - - // networkManager = this.NetworkManager; - processor.Emit(OpCodes.Ldarg_0); - processor.Emit(OpCodes.Call, m_NetworkBehaviour_getNetworkManager_MethodRef); - processor.Emit(OpCodes.Stloc, netManLocIdx); - - // if (networkManager == null || !networkManager.IsListening) return; - processor.Emit(OpCodes.Ldloc, netManLocIdx); - processor.Emit(OpCodes.Brfalse, returnInstr); - processor.Emit(OpCodes.Ldloc, netManLocIdx); - processor.Emit(OpCodes.Callvirt, m_NetworkManager_getIsListening_MethodRef); - processor.Emit(OpCodes.Brtrue, lastInstr); - - processor.Append(returnInstr); - processor.Append(lastInstr); - } - - if (isServerRpc && requireOwnership) - { - var roReturnInstr = processor.Create(OpCodes.Ret); - var roLastInstr = processor.Create(OpCodes.Nop); - - // if (rpcParams.Server.Receive.SenderClientId != target.OwnerClientId) { ... } return; - processor.Emit(OpCodes.Ldarg_2); - processor.Emit(OpCodes.Ldfld, m_RpcParams_Server_FieldRef); - processor.Emit(OpCodes.Ldfld, m_ServerRpcParams_Receive_FieldRef); - processor.Emit(OpCodes.Ldfld, m_ServerRpcParams_Receive_SenderClientId_FieldRef); - processor.Emit(OpCodes.Ldarg_0); - processor.Emit(OpCodes.Call, m_NetworkBehaviour_getOwnerClientId_MethodRef); - processor.Emit(OpCodes.Ceq); - processor.Emit(OpCodes.Ldc_I4, 0); - processor.Emit(OpCodes.Ceq); - processor.Emit(OpCodes.Brfalse, roLastInstr); - - var logNextInstr = processor.Create(OpCodes.Nop); - - // if (LogLevel.Normal > networkManager.LogLevel) - processor.Emit(OpCodes.Ldloc, netManLocIdx); - processor.Emit(OpCodes.Ldfld, m_NetworkManager_LogLevel_FieldRef); - processor.Emit(OpCodes.Ldc_I4, (int)LogLevel.Normal); - processor.Emit(OpCodes.Cgt); - processor.Emit(OpCodes.Ldc_I4, 0); - processor.Emit(OpCodes.Ceq); - processor.Emit(OpCodes.Brfalse, logNextInstr); - - // Debug.LogError(...); - processor.Emit(OpCodes.Ldstr, "Only the owner can invoke a ServerRpc that requires ownership!"); - processor.Emit(OpCodes.Call, m_Debug_LogError_MethodRef); - - processor.Append(logNextInstr); - - processor.Append(roReturnInstr); - processor.Append(roLastInstr); - } - - // read method parameters from stream - int paramCount = methodDefinition.Parameters.Count; - int[] paramLocalMap = new int[paramCount]; - for (int paramIndex = 0; paramIndex < paramCount; ++paramIndex) - { - var paramDef = methodDefinition.Parameters[paramIndex]; - var paramType = paramDef.ParameterType; - - // local variable - nhandler.Body.Variables.Add(new VariableDefinition(paramType)); - int localIndex = nhandler.Body.Variables.Count - 1; - paramLocalMap[paramIndex] = localIndex; - - // C# primitives (+arrays) - - if (paramType == typeSystem.Boolean) - { - processor.Emit(OpCodes.Ldarg_1); - processor.Emit(OpCodes.Ldloca, localIndex); - processor.Emit(OpCodes.Callvirt, m_NetworkSerializer_SerializeBool_MethodRef); - continue; - } - - if (paramType.IsArray && paramType.GetElementType() == typeSystem.Boolean) - { - processor.Emit(OpCodes.Ldarg_1); - processor.Emit(OpCodes.Ldloca, localIndex); - processor.Emit(OpCodes.Callvirt, m_NetworkSerializer_SerializeBoolArray_MethodRef); - continue; - } - - if (paramType == typeSystem.Char) - { - processor.Emit(OpCodes.Ldarg_1); - processor.Emit(OpCodes.Ldloca, localIndex); - processor.Emit(OpCodes.Callvirt, m_NetworkSerializer_SerializeChar_MethodRef); - continue; - } - - if (paramType.IsArray && paramType.GetElementType() == typeSystem.Char) - { - processor.Emit(OpCodes.Ldarg_1); - processor.Emit(OpCodes.Ldloca, localIndex); - processor.Emit(OpCodes.Callvirt, m_NetworkSerializer_SerializeCharArray_MethodRef); - continue; - } - - if (paramType == typeSystem.SByte) - { - processor.Emit(OpCodes.Ldarg_1); - processor.Emit(OpCodes.Ldloca, localIndex); - processor.Emit(OpCodes.Callvirt, m_NetworkSerializer_SerializeSbyte_MethodRef); - continue; - } - - if (paramType.IsArray && paramType.GetElementType() == typeSystem.SByte) - { - processor.Emit(OpCodes.Ldarg_1); - processor.Emit(OpCodes.Ldloca, localIndex); - processor.Emit(OpCodes.Callvirt, m_NetworkSerializer_SerializeSbyteArray_MethodRef); - continue; - } - - if (paramType == typeSystem.Byte) - { - processor.Emit(OpCodes.Ldarg_1); - processor.Emit(OpCodes.Ldloca, localIndex); - processor.Emit(OpCodes.Callvirt, m_NetworkSerializer_SerializeByte_MethodRef); - continue; - } - - if (paramType.IsArray && paramType.GetElementType() == typeSystem.Byte) - { - processor.Emit(OpCodes.Ldarg_1); - processor.Emit(OpCodes.Ldloca, localIndex); - processor.Emit(OpCodes.Callvirt, m_NetworkSerializer_SerializeByteArray_MethodRef); - continue; - } - - if (paramType == typeSystem.Int16) - { - processor.Emit(OpCodes.Ldarg_1); - processor.Emit(OpCodes.Ldloca, localIndex); - processor.Emit(OpCodes.Callvirt, m_NetworkSerializer_SerializeShort_MethodRef); - continue; - } - - if (paramType.IsArray && paramType.GetElementType() == typeSystem.Int16) - { - processor.Emit(OpCodes.Ldarg_1); - processor.Emit(OpCodes.Ldloca, localIndex); - processor.Emit(OpCodes.Callvirt, m_NetworkSerializer_SerializeShortArray_MethodRef); - continue; - } - - if (paramType == typeSystem.UInt16) - { - processor.Emit(OpCodes.Ldarg_1); - processor.Emit(OpCodes.Ldloca, localIndex); - processor.Emit(OpCodes.Callvirt, m_NetworkSerializer_SerializeUshort_MethodRef); - continue; - } - - if (paramType.IsArray && paramType.GetElementType() == typeSystem.UInt16) - { - processor.Emit(OpCodes.Ldarg_1); - processor.Emit(OpCodes.Ldloca, localIndex); - processor.Emit(OpCodes.Callvirt, m_NetworkSerializer_SerializeUshortArray_MethodRef); - continue; - } - - if (paramType == typeSystem.Int32) - { - processor.Emit(OpCodes.Ldarg_1); - processor.Emit(OpCodes.Ldloca, localIndex); - processor.Emit(OpCodes.Callvirt, m_NetworkSerializer_SerializeInt_MethodRef); - continue; - } - - if (paramType.IsArray && paramType.GetElementType() == typeSystem.Int32) - { - processor.Emit(OpCodes.Ldarg_1); - processor.Emit(OpCodes.Ldloca, localIndex); - processor.Emit(OpCodes.Callvirt, m_NetworkSerializer_SerializeIntArray_MethodRef); - continue; - } - - if (paramType == typeSystem.UInt32) - { - processor.Emit(OpCodes.Ldarg_1); - processor.Emit(OpCodes.Ldloca, localIndex); - processor.Emit(OpCodes.Callvirt, m_NetworkSerializer_SerializeUint_MethodRef); - continue; - } - - if (paramType.IsArray && paramType.GetElementType() == typeSystem.UInt32) - { - processor.Emit(OpCodes.Ldarg_1); - processor.Emit(OpCodes.Ldloca, localIndex); - processor.Emit(OpCodes.Callvirt, m_NetworkSerializer_SerializeUintArray_MethodRef); - continue; - } - - if (paramType == typeSystem.Int64) - { - processor.Emit(OpCodes.Ldarg_1); - processor.Emit(OpCodes.Ldloca, localIndex); - processor.Emit(OpCodes.Callvirt, m_NetworkSerializer_SerializeLong_MethodRef); - continue; - } - - if (paramType.IsArray && paramType.GetElementType() == typeSystem.Int64) - { - processor.Emit(OpCodes.Ldarg_1); - processor.Emit(OpCodes.Ldloca, localIndex); - processor.Emit(OpCodes.Callvirt, m_NetworkSerializer_SerializeLongArray_MethodRef); - continue; - } - - if (paramType == typeSystem.UInt64) - { - processor.Emit(OpCodes.Ldarg_1); - processor.Emit(OpCodes.Ldloca, localIndex); - processor.Emit(OpCodes.Callvirt, m_NetworkSerializer_SerializeUlong_MethodRef); - continue; - } - - if (paramType.IsArray && paramType.GetElementType() == typeSystem.UInt64) - { - processor.Emit(OpCodes.Ldarg_1); - processor.Emit(OpCodes.Ldloca, localIndex); - processor.Emit(OpCodes.Callvirt, m_NetworkSerializer_SerializeUlongArray_MethodRef); - continue; - } - - if (paramType == typeSystem.Single) - { - processor.Emit(OpCodes.Ldarg_1); - processor.Emit(OpCodes.Ldloca, localIndex); - processor.Emit(OpCodes.Callvirt, m_NetworkSerializer_SerializeFloat_MethodRef); - continue; - } - - if (paramType.IsArray && paramType.GetElementType() == typeSystem.Single) - { - processor.Emit(OpCodes.Ldarg_1); - processor.Emit(OpCodes.Ldloca, localIndex); - processor.Emit(OpCodes.Callvirt, m_NetworkSerializer_SerializeFloatArray_MethodRef); - continue; - } - - if (paramType == typeSystem.Double) - { - processor.Emit(OpCodes.Ldarg_1); - processor.Emit(OpCodes.Ldloca, localIndex); - processor.Emit(OpCodes.Callvirt, m_NetworkSerializer_SerializeDouble_MethodRef); - continue; - } - - if (paramType.IsArray && paramType.GetElementType() == typeSystem.Double) - { - processor.Emit(OpCodes.Ldarg_1); - processor.Emit(OpCodes.Ldloca, localIndex); - processor.Emit(OpCodes.Callvirt, m_NetworkSerializer_SerializeDoubleArray_MethodRef); - continue; - } - - if (paramType == typeSystem.String) - { - processor.Emit(OpCodes.Ldarg_1); - processor.Emit(OpCodes.Ldloca, localIndex); - processor.Emit(OpCodes.Callvirt, m_NetworkSerializer_SerializeString_MethodRef); - continue; - } - - if (paramType.IsArray && paramType.GetElementType() == typeSystem.String) - { - processor.Emit(OpCodes.Ldarg_1); - processor.Emit(OpCodes.Ldloca, localIndex); - processor.Emit(OpCodes.Callvirt, m_NetworkSerializer_SerializeStringArray_MethodRef); - continue; - } - - // Unity primitives (+arrays) - - if (paramType.FullName == CodeGenHelpers.UnityColor_FullName) - { - processor.Emit(OpCodes.Ldarg_1); - processor.Emit(OpCodes.Ldloca, localIndex); - processor.Emit(OpCodes.Callvirt, m_NetworkSerializer_SerializeColor_MethodRef); - continue; - } - - if (paramType.IsArray && paramType.GetElementType().FullName == CodeGenHelpers.UnityColor_FullName) - { - processor.Emit(OpCodes.Ldarg_1); - processor.Emit(OpCodes.Ldloca, localIndex); - processor.Emit(OpCodes.Callvirt, m_NetworkSerializer_SerializeColorArray_MethodRef); - continue; - } - - if (paramType.FullName == CodeGenHelpers.UnityColor32_FullName) - { - processor.Emit(OpCodes.Ldarg_1); - processor.Emit(OpCodes.Ldloca, localIndex); - processor.Emit(OpCodes.Callvirt, m_NetworkSerializer_SerializeColor32_MethodRef); - continue; - } - - if (paramType.IsArray && paramType.GetElementType().FullName == CodeGenHelpers.UnityColor32_FullName) - { - processor.Emit(OpCodes.Ldarg_1); - processor.Emit(OpCodes.Ldloca, localIndex); - processor.Emit(OpCodes.Callvirt, m_NetworkSerializer_SerializeColor32Array_MethodRef); - continue; - } - - if (paramType.FullName == CodeGenHelpers.UnityVector2_FullName) - { - processor.Emit(OpCodes.Ldarg_1); - processor.Emit(OpCodes.Ldloca, localIndex); - processor.Emit(OpCodes.Callvirt, m_NetworkSerializer_SerializeVector2_MethodRef); - continue; - } - - if (paramType.IsArray && paramType.GetElementType().FullName == CodeGenHelpers.UnityVector2_FullName) - { - processor.Emit(OpCodes.Ldarg_1); - processor.Emit(OpCodes.Ldloca, localIndex); - processor.Emit(OpCodes.Callvirt, m_NetworkSerializer_SerializeVector2Array_MethodRef); - continue; - } - - if (paramType.FullName == CodeGenHelpers.UnityVector3_FullName) - { - processor.Emit(OpCodes.Ldarg_1); - processor.Emit(OpCodes.Ldloca, localIndex); - processor.Emit(OpCodes.Callvirt, m_NetworkSerializer_SerializeVector3_MethodRef); - continue; - } - - if (paramType.IsArray && paramType.GetElementType().FullName == CodeGenHelpers.UnityVector3_FullName) - { - processor.Emit(OpCodes.Ldarg_1); - processor.Emit(OpCodes.Ldloca, localIndex); - processor.Emit(OpCodes.Callvirt, m_NetworkSerializer_SerializeVector3Array_MethodRef); - continue; - } - - if (paramType.FullName == CodeGenHelpers.UnityVector4_FullName) - { - processor.Emit(OpCodes.Ldarg_1); - processor.Emit(OpCodes.Ldloca, localIndex); - processor.Emit(OpCodes.Callvirt, m_NetworkSerializer_SerializeVector4_MethodRef); - continue; - } - - if (paramType.IsArray && paramType.GetElementType().FullName == CodeGenHelpers.UnityVector4_FullName) - { - processor.Emit(OpCodes.Ldarg_1); - processor.Emit(OpCodes.Ldloca, localIndex); - processor.Emit(OpCodes.Callvirt, m_NetworkSerializer_SerializeVector4Array_MethodRef); - continue; - } - - if (paramType.FullName == CodeGenHelpers.UnityQuaternion_FullName) - { - processor.Emit(OpCodes.Ldarg_1); - processor.Emit(OpCodes.Ldloca, localIndex); - processor.Emit(OpCodes.Callvirt, m_NetworkSerializer_SerializeQuaternion_MethodRef); - continue; - } - - if (paramType.IsArray && paramType.GetElementType().FullName == CodeGenHelpers.UnityQuaternion_FullName) - { - processor.Emit(OpCodes.Ldarg_1); - processor.Emit(OpCodes.Ldloca, localIndex); - processor.Emit(OpCodes.Callvirt, m_NetworkSerializer_SerializeQuaternionArray_MethodRef); - continue; - } - - if (paramType.FullName == CodeGenHelpers.UnityRay_FullName) - { - processor.Emit(OpCodes.Ldarg_1); - processor.Emit(OpCodes.Ldloca, localIndex); - processor.Emit(OpCodes.Callvirt, m_NetworkSerializer_SerializeRay_MethodRef); - continue; - } - - if (paramType.IsArray && paramType.GetElementType().FullName == CodeGenHelpers.UnityRay_FullName) - { - processor.Emit(OpCodes.Ldarg_1); - processor.Emit(OpCodes.Ldloca, localIndex); - processor.Emit(OpCodes.Callvirt, m_NetworkSerializer_SerializeRayArray_MethodRef); - continue; - } - - if (paramType.FullName == CodeGenHelpers.UnityRay2D_FullName) - { - processor.Emit(OpCodes.Ldarg_1); - processor.Emit(OpCodes.Ldloca, localIndex); - processor.Emit(OpCodes.Callvirt, m_NetworkSerializer_SerializeRay2D_MethodRef); - continue; - } - - if (paramType.IsArray && paramType.GetElementType().FullName == CodeGenHelpers.UnityRay2D_FullName) - { - processor.Emit(OpCodes.Ldarg_1); - processor.Emit(OpCodes.Ldloca, localIndex); - processor.Emit(OpCodes.Callvirt, m_NetworkSerializer_SerializeRay2DArray_MethodRef); - continue; - } - - // Enum - - { - var paramEnumIntType = paramType.GetEnumAsInt(); - if (paramEnumIntType != null) - { - if (paramEnumIntType == typeSystem.Int32) - { - nhandler.Body.Variables.Add(new VariableDefinition(typeSystem.Int32)); - int enumLocalIndex = nhandler.Body.Variables.Count - 1; - - processor.Emit(OpCodes.Ldarg_1); - processor.Emit(OpCodes.Ldloca, enumLocalIndex); - processor.Emit(OpCodes.Callvirt, m_NetworkSerializer_SerializeInt_MethodRef); - - processor.Emit(OpCodes.Ldloc, enumLocalIndex); - processor.Emit(OpCodes.Stloc, localIndex); - continue; - } - - if (paramEnumIntType == typeSystem.UInt32) - { - nhandler.Body.Variables.Add(new VariableDefinition(typeSystem.UInt32)); - int enumLocalIndex = nhandler.Body.Variables.Count - 1; - - processor.Emit(OpCodes.Ldarg_1); - processor.Emit(OpCodes.Ldloca, enumLocalIndex); - processor.Emit(OpCodes.Callvirt, m_NetworkSerializer_SerializeUint_MethodRef); - - processor.Emit(OpCodes.Ldloc, enumLocalIndex); - processor.Emit(OpCodes.Stloc, localIndex); - continue; - } - - if (paramEnumIntType == typeSystem.Byte) - { - nhandler.Body.Variables.Add(new VariableDefinition(typeSystem.Byte)); - int enumLocalIndex = nhandler.Body.Variables.Count - 1; - - processor.Emit(OpCodes.Ldarg_1); - processor.Emit(OpCodes.Ldloca, enumLocalIndex); - processor.Emit(OpCodes.Callvirt, m_NetworkSerializer_SerializeByte_MethodRef); - - processor.Emit(OpCodes.Ldloc, enumLocalIndex); - processor.Emit(OpCodes.Stloc, localIndex); - continue; - } - - if (paramEnumIntType == typeSystem.SByte) - { - nhandler.Body.Variables.Add(new VariableDefinition(typeSystem.SByte)); - int enumLocalIndex = nhandler.Body.Variables.Count - 1; - - processor.Emit(OpCodes.Ldarg_1); - processor.Emit(OpCodes.Ldloca, enumLocalIndex); - processor.Emit(OpCodes.Callvirt, m_NetworkSerializer_SerializeSbyte_MethodRef); - - processor.Emit(OpCodes.Ldloc, enumLocalIndex); - processor.Emit(OpCodes.Stloc, localIndex); - continue; - } - - if (paramEnumIntType == typeSystem.Int16) - { - nhandler.Body.Variables.Add(new VariableDefinition(typeSystem.Int16)); - int enumLocalIndex = nhandler.Body.Variables.Count - 1; - - processor.Emit(OpCodes.Ldarg_1); - processor.Emit(OpCodes.Ldloca, enumLocalIndex); - processor.Emit(OpCodes.Callvirt, m_NetworkSerializer_SerializeShort_MethodRef); - - processor.Emit(OpCodes.Ldloc, enumLocalIndex); - processor.Emit(OpCodes.Stloc, localIndex); - continue; - } - - if (paramEnumIntType == typeSystem.UInt16) - { - nhandler.Body.Variables.Add(new VariableDefinition(typeSystem.UInt16)); - int enumLocalIndex = nhandler.Body.Variables.Count - 1; - - processor.Emit(OpCodes.Ldarg_1); - processor.Emit(OpCodes.Ldloca, enumLocalIndex); - processor.Emit(OpCodes.Callvirt, m_NetworkSerializer_SerializeUshort_MethodRef); - - processor.Emit(OpCodes.Ldloc, enumLocalIndex); - processor.Emit(OpCodes.Stloc, localIndex); - continue; - } - - if (paramEnumIntType == typeSystem.Int64) - { - nhandler.Body.Variables.Add(new VariableDefinition(typeSystem.Int64)); - int enumLocalIndex = nhandler.Body.Variables.Count - 1; - - processor.Emit(OpCodes.Ldarg_1); - processor.Emit(OpCodes.Ldloca, enumLocalIndex); - processor.Emit(OpCodes.Callvirt, m_NetworkSerializer_SerializeLong_MethodRef); - - processor.Emit(OpCodes.Ldloc, enumLocalIndex); - processor.Emit(OpCodes.Stloc, localIndex); - continue; - } - - if (paramEnumIntType == typeSystem.UInt64) - { - nhandler.Body.Variables.Add(new VariableDefinition(typeSystem.UInt64)); - int enumLocalIndex = nhandler.Body.Variables.Count - 1; - - processor.Emit(OpCodes.Ldarg_1); - processor.Emit(OpCodes.Ldloca, enumLocalIndex); - processor.Emit(OpCodes.Callvirt, m_NetworkSerializer_SerializeUlong_MethodRef); - - processor.Emit(OpCodes.Ldloc, enumLocalIndex); - processor.Emit(OpCodes.Stloc, localIndex); - continue; - } - } - } - - // Enum array - - if (paramType.IsArray) + for (int paramIndex = 0; paramIndex < paramCount; ++paramIndex) { - var paramElemEnumIntType = paramType.GetElementType().GetEnumAsInt(); - if (paramElemEnumIntType != null) + var paramDef = methodDefinition.Parameters[paramIndex]; + var paramType = paramDef.ParameterType; + // ServerRpcParams + if (paramType.FullName == CodeGenHelpers.ServerRpcParams_FullName && isServerRpc && paramIndex == paramCount - 1) { - nhandler.Body.Variables.Add(new VariableDefinition(typeSystem.Int32)); - int arrLenLocalIndex = nhandler.Body.Variables.Count - 1; - - processor.Emit(OpCodes.Ldarg_1); - processor.Emit(OpCodes.Ldloca, arrLenLocalIndex); - processor.Emit(OpCodes.Callvirt, m_NetworkSerializer_SerializeInt_MethodRef); + continue; + } + // ClientRpcParams + if (paramType.FullName == CodeGenHelpers.ClientRpcParams_FullName && !isServerRpc && paramIndex == paramCount - 1) + { + continue; + } - var postForInstr = processor.Create(OpCodes.Nop); + Instruction jumpInstruction = null; - processor.Emit(OpCodes.Ldloc, arrLenLocalIndex); - processor.Emit(OpCodes.Ldc_I4_M1); - processor.Emit(OpCodes.Cgt); - processor.Emit(OpCodes.Brfalse, postForInstr); + if (!paramType.IsValueType) + { + if (!GetWriteMethodForParameter(typeSystem.Boolean, out var boolMethodRef)) + { + m_Diagnostics.AddError(methodDefinition, $"Couldn't find boolean serializer! Something's wrong!"); + return; + } - processor.Emit(OpCodes.Ldloc, arrLenLocalIndex); - processor.Emit(OpCodes.Newarr, paramType.GetElementType()); - processor.Emit(OpCodes.Stloc, localIndex); + methodDefinition.Body.Variables.Add(new VariableDefinition(typeSystem.Boolean)); + int isSetLocalIndex = methodDefinition.Body.Variables.Count - 1; - nhandler.Body.Variables.Add(new VariableDefinition(typeSystem.Int32)); - int counterLocalIndex = nhandler.Body.Variables.Count - 1; + // bool isSet = (param != null); + instructions.Add(processor.Create(OpCodes.Ldarg, paramIndex + 1)); + instructions.Add(processor.Create(OpCodes.Ldnull)); + instructions.Add(processor.Create(OpCodes.Cgt_Un)); + instructions.Add(processor.Create(OpCodes.Stloc, isSetLocalIndex)); - var forBodyInstr = processor.Create(OpCodes.Nop); - var forCheckInstr = processor.Create(OpCodes.Nop); + // writer.WriteValueSafe(isSet); + instructions.Add(processor.Create(OpCodes.Ldloca, serializerLocIdx)); + instructions.Add(processor.Create(OpCodes.Ldloca, isSetLocalIndex)); + instructions.Add(processor.Create(OpCodes.Call, boolMethodRef)); - processor.Emit(OpCodes.Ldc_I4_0); - processor.Emit(OpCodes.Stloc, counterLocalIndex); - processor.Emit(OpCodes.Br, forCheckInstr); - processor.Append(forBodyInstr); + // if(isSet) { + jumpInstruction = processor.Create(OpCodes.Nop); + instructions.Add(processor.Create(OpCodes.Ldloc, isSetLocalIndex)); + instructions.Add(processor.Create(OpCodes.Brfalse, jumpInstruction)); + } - if (paramElemEnumIntType == typeSystem.Int32) + var foundMethodRef = GetWriteMethodForParameter(paramType, out var methodRef); + if (foundMethodRef) + { + // writer.WriteNetworkSerializable(param) for INetworkSerializable, OR + // writer.WriteNetworkSerializable(param, -1, 0) for INetworkSerializable arrays, OR + // writer.WriteValueSafe(param) for value types, OR + // writer.WriteValueSafe(param, -1, 0) for arrays of value types, OR + // writer.WriteValueSafe(param, false) for strings + instructions.Add(processor.Create(OpCodes.Ldloca, serializerLocIdx)); + var method = methodRef.Resolve(); + var checkParameter = method.Parameters[0]; + var isExtensionMethod = false; + if (checkParameter.ParameterType.Resolve() == + m_FastBufferWriter_TypeRef.MakeByReferenceType().Resolve()) { - nhandler.Body.Variables.Add(new VariableDefinition(typeSystem.Int32)); - int enumValLocalIndex = nhandler.Body.Variables.Count - 1; - - processor.Emit(OpCodes.Ldarg_1); - processor.Emit(OpCodes.Ldloca, enumValLocalIndex); - processor.Emit(OpCodes.Callvirt, m_NetworkSerializer_SerializeInt_MethodRef); - - processor.Emit(OpCodes.Ldloc, localIndex); - processor.Emit(OpCodes.Ldloc, counterLocalIndex); - processor.Emit(OpCodes.Ldloc, enumValLocalIndex); - processor.Emit(OpCodes.Stelem_I4); + isExtensionMethod = true; + checkParameter = method.Parameters[1]; } - else if (paramElemEnumIntType == typeSystem.UInt32) + if (checkParameter.IsIn) { - nhandler.Body.Variables.Add(new VariableDefinition(typeSystem.UInt32)); - int enumValLocalIndex = nhandler.Body.Variables.Count - 1; - - processor.Emit(OpCodes.Ldarg_1); - processor.Emit(OpCodes.Ldloca, enumValLocalIndex); - processor.Emit(OpCodes.Callvirt, m_NetworkSerializer_SerializeUint_MethodRef); - - processor.Emit(OpCodes.Ldloc, localIndex); - processor.Emit(OpCodes.Ldloc, counterLocalIndex); - processor.Emit(OpCodes.Ldloc, enumValLocalIndex); - processor.Emit(OpCodes.Stelem_I4); + instructions.Add(processor.Create(OpCodes.Ldarga, paramIndex + 1)); } - else if (paramElemEnumIntType == typeSystem.Byte) + else { - nhandler.Body.Variables.Add(new VariableDefinition(typeSystem.Byte)); - int enumValLocalIndex = nhandler.Body.Variables.Count - 1; - - processor.Emit(OpCodes.Ldarg_1); - processor.Emit(OpCodes.Ldloca, enumValLocalIndex); - processor.Emit(OpCodes.Callvirt, m_NetworkSerializer_SerializeByte_MethodRef); - - processor.Emit(OpCodes.Ldloc, localIndex); - processor.Emit(OpCodes.Ldloc, counterLocalIndex); - processor.Emit(OpCodes.Ldloc, enumValLocalIndex); - processor.Emit(OpCodes.Stelem_I1); + instructions.Add(processor.Create(OpCodes.Ldarg, paramIndex + 1)); } - else if (paramElemEnumIntType == typeSystem.SByte) + // Special handling for WriteValue() on arrays and strings since they have additional arguments. + if (paramType.IsArray + && ((!isExtensionMethod && methodRef.Parameters.Count == 3) + || (isExtensionMethod && methodRef.Parameters.Count == 4))) { - nhandler.Body.Variables.Add(new VariableDefinition(typeSystem.SByte)); - int enumValLocalIndex = nhandler.Body.Variables.Count - 1; - - processor.Emit(OpCodes.Ldarg_1); - processor.Emit(OpCodes.Ldloca, enumValLocalIndex); - processor.Emit(OpCodes.Callvirt, m_NetworkSerializer_SerializeSbyte_MethodRef); - - processor.Emit(OpCodes.Ldloc, localIndex); - processor.Emit(OpCodes.Ldloc, counterLocalIndex); - processor.Emit(OpCodes.Ldloc, enumValLocalIndex); - processor.Emit(OpCodes.Stelem_I1); + instructions.Add(processor.Create(OpCodes.Ldc_I4_M1)); + instructions.Add(processor.Create(OpCodes.Ldc_I4_0)); } - else if (paramElemEnumIntType == typeSystem.Int16) + else if (paramType == typeSystem.String + && ((!isExtensionMethod && methodRef.Parameters.Count == 2) + || (isExtensionMethod && methodRef.Parameters.Count == 3))) { - nhandler.Body.Variables.Add(new VariableDefinition(typeSystem.Int16)); - int enumValLocalIndex = nhandler.Body.Variables.Count - 1; + instructions.Add(processor.Create(OpCodes.Ldc_I4_0)); + } + instructions.Add(processor.Create(OpCodes.Call, methodRef)); + } + else + { + m_Diagnostics.AddError(methodDefinition, $"Don't know how to serialize {paramType.Name} - implement INetworkSerializable or add an extension method to FastBufferWriter to define serialization."); + continue; + } - processor.Emit(OpCodes.Ldarg_1); - processor.Emit(OpCodes.Ldloca, enumValLocalIndex); - processor.Emit(OpCodes.Callvirt, m_NetworkSerializer_SerializeShort_MethodRef); + if (jumpInstruction != null) + { + // } + instructions.Add(jumpInstruction); + } + } - processor.Emit(OpCodes.Ldloc, localIndex); - processor.Emit(OpCodes.Ldloc, counterLocalIndex); - processor.Emit(OpCodes.Ldloc, enumValLocalIndex); - processor.Emit(OpCodes.Stelem_I2); - } - else if (paramElemEnumIntType == typeSystem.UInt16) - { - nhandler.Body.Variables.Add(new VariableDefinition(typeSystem.UInt16)); - int enumValLocalIndex = nhandler.Body.Variables.Count - 1; + instructions.Add(endInstr); - processor.Emit(OpCodes.Ldarg_1); - processor.Emit(OpCodes.Ldloca, enumValLocalIndex); - processor.Emit(OpCodes.Callvirt, m_NetworkSerializer_SerializeUshort_MethodRef); + // SendServerRpc(ref serializer, rpcMethodId, serverRpcParams, rpcDelivery) -> ServerRpc + // SendClientRpc(ref serializer, rpcMethodId, clientRpcParams, rpcDelivery) -> ClientRpc + if (isServerRpc) + { + // ServerRpc + // SendServerRpc(ref serializer, rpcMethodId, serverRpcParams, rpcDelivery); + instructions.Add(processor.Create(OpCodes.Ldarg_0)); - processor.Emit(OpCodes.Ldloc, localIndex); - processor.Emit(OpCodes.Ldloc, counterLocalIndex); - processor.Emit(OpCodes.Ldloc, enumValLocalIndex); - processor.Emit(OpCodes.Stelem_I2); - } - else if (paramElemEnumIntType == typeSystem.Int64) - { - nhandler.Body.Variables.Add(new VariableDefinition(typeSystem.Int64)); - int enumValLocalIndex = nhandler.Body.Variables.Count - 1; + // serializer + instructions.Add(processor.Create(OpCodes.Ldloca, serializerLocIdx)); - processor.Emit(OpCodes.Ldarg_1); - processor.Emit(OpCodes.Ldloca, enumValLocalIndex); - processor.Emit(OpCodes.Callvirt, m_NetworkSerializer_SerializeLong_MethodRef); + // rpcMethodId + instructions.Add(processor.Create(OpCodes.Ldc_I4, unchecked((int)rpcMethodId))); - processor.Emit(OpCodes.Ldloc, localIndex); - processor.Emit(OpCodes.Ldloc, counterLocalIndex); - processor.Emit(OpCodes.Ldloc, enumValLocalIndex); - processor.Emit(OpCodes.Stelem_I8); - } - else if (paramElemEnumIntType == typeSystem.UInt64) - { - nhandler.Body.Variables.Add(new VariableDefinition(typeSystem.UInt64)); - int enumValLocalIndex = nhandler.Body.Variables.Count - 1; + if (hasRpcParams) + { + // rpcParams + instructions.Add(processor.Create(OpCodes.Ldarg, paramCount)); + } + else + { + // default + instructions.Add(processor.Create(OpCodes.Ldloc, rpcParamsIdx)); + } - processor.Emit(OpCodes.Ldarg_1); - processor.Emit(OpCodes.Ldloca, enumValLocalIndex); - processor.Emit(OpCodes.Callvirt, m_NetworkSerializer_SerializeUlong_MethodRef); + // rpcDelivery + instructions.Add(processor.Create(OpCodes.Ldc_I4, (int)rpcDelivery)); - processor.Emit(OpCodes.Ldloc, localIndex); - processor.Emit(OpCodes.Ldloc, counterLocalIndex); - processor.Emit(OpCodes.Ldloc, enumValLocalIndex); - processor.Emit(OpCodes.Stelem_I8); - } + // EndSendServerRpc + instructions.Add(processor.Create(OpCodes.Call, m_NetworkBehaviour_SendServerRpc_MethodRef)); + } + else + { + // ClientRpc + // SendClientRpc(ref serializer, rpcMethodId, clientRpcParams, rpcDelivery); + instructions.Add(processor.Create(OpCodes.Ldarg_0)); - processor.Emit(OpCodes.Ldloc, counterLocalIndex); - processor.Emit(OpCodes.Ldc_I4_1); - processor.Emit(OpCodes.Add); - processor.Emit(OpCodes.Stloc, counterLocalIndex); - processor.Append(forCheckInstr); - processor.Emit(OpCodes.Ldloc, counterLocalIndex); - processor.Emit(OpCodes.Ldloc, arrLenLocalIndex); - processor.Emit(OpCodes.Clt); - processor.Emit(OpCodes.Brtrue, forBodyInstr); - - processor.Append(postForInstr); - continue; + // serializer + instructions.Add(processor.Create(OpCodes.Ldloca, serializerLocIdx)); + + // rpcMethodId + instructions.Add(processor.Create(OpCodes.Ldc_I4, unchecked((int)rpcMethodId))); + + if (hasRpcParams) + { + // rpcParams + instructions.Add(processor.Create(OpCodes.Ldarg, paramCount)); } - } + else + { + // default + instructions.Add(processor.Create(OpCodes.Ldloc, rpcParamsIdx)); + } + + // rpcDelivery + instructions.Add(processor.Create(OpCodes.Ldc_I4, (int)rpcDelivery)); - // INetworkSerializable + // EndSendClientRpc + instructions.Add(processor.Create(OpCodes.Call, m_NetworkBehaviour_SendClientRpc_MethodRef)); + } - if (paramType.HasInterface(CodeGenHelpers.INetworkSerializable_FullName)) { - var paramTypeDef = paramType.Resolve(); - var paramTypeNetworkSerialize_MethodDef = paramTypeDef.Methods.FirstOrDefault(m => m.Name == CodeGenHelpers.INetworkSerializable_NetworkSerialize_Name); - var paramTypeNetworkSerialize_MethodRef = methodDefinition.Module.ImportReference(paramTypeNetworkSerialize_MethodDef); - if (paramTypeNetworkSerialize_MethodRef != null) + // TODO: Figure out why try/catch here cause the try block not to execute at all. + // End try block + //instructions.Add(processor.Create(OpCodes.Leave, lastInstr)); + + // writer.Dispose(); + var handlerFirst = processor.Create(OpCodes.Ldloca, serializerLocIdx); + instructions.Add(handlerFirst); + instructions.Add(processor.Create(OpCodes.Call, m_FastBufferWriter_Dispose)); + + // End finally block + //instructions.Add(processor.Create(OpCodes.Endfinally)); + + // try { ... serialization code ... } finally { writer.Dispose(); } + /*var handler = new ExceptionHandler(ExceptionHandlerType.Finally) { - if (paramType.IsValueType) - { - // struct (pass by value) - processor.Emit(OpCodes.Ldloca, localIndex); - processor.Emit(OpCodes.Ldarg_1); - processor.Emit(OpCodes.Call, paramTypeNetworkSerialize_MethodRef); - } - else - { - // class (pass by reference) - var paramTypeDefCtor = paramTypeDef.GetConstructors().FirstOrDefault(m => m.Parameters.Count == 0); - if (paramTypeDefCtor != null) - { - nhandler.Body.Variables.Add(new VariableDefinition(typeSystem.Boolean)); - int isSetLocalIndex = nhandler.Body.Variables.Count - 1; + TryStart = firstInstruction, + TryEnd = handlerFirst, + HandlerStart = handlerFirst, + HandlerEnd = lastInstr + }; + processor.Body.ExceptionHandlers.Add(handler);*/ + } - processor.Emit(OpCodes.Ldarg_1); - processor.Emit(OpCodes.Ldloca, isSetLocalIndex); - processor.Emit(OpCodes.Callvirt, m_NetworkSerializer_SerializeBool_MethodRef); + instructions.Add(lastInstr); + } - var notSetInstr = processor.Create(OpCodes.Nop); + { + var returnInstr = processor.Create(OpCodes.Ret); + var lastInstr = processor.Create(OpCodes.Nop); - processor.Emit(OpCodes.Ldloc, isSetLocalIndex); - processor.Emit(OpCodes.Brfalse, notSetInstr); + // if (__rpc_exec_stage == __RpcExecStage.Server) -> ServerRpc + // if (__rpc_exec_stage == __RpcExecStage.Client) -> ClientRpc + instructions.Add(processor.Create(OpCodes.Ldarg_0)); + instructions.Add(processor.Create(OpCodes.Ldfld, m_NetworkBehaviour_rpc_exec_stage_FieldRef)); + instructions.Add(processor.Create(OpCodes.Ldc_I4, (int)(isServerRpc ? NetworkBehaviour.__RpcExecStage.Server : NetworkBehaviour.__RpcExecStage.Client))); + instructions.Add(processor.Create(OpCodes.Ceq)); + instructions.Add(processor.Create(OpCodes.Brfalse, returnInstr)); - // new INetworkSerializable() - processor.Emit(OpCodes.Newobj, paramTypeDefCtor); - processor.Emit(OpCodes.Stloc, localIndex); + // if (networkManager.IsServer || networkManager.IsHost) -> ServerRpc + // if (networkManager.IsClient || networkManager.IsHost) -> ClientRpc + instructions.Add(processor.Create(OpCodes.Ldloc, netManLocIdx)); + instructions.Add(processor.Create(OpCodes.Callvirt, isServerRpc ? m_NetworkManager_getIsServer_MethodRef : m_NetworkManager_getIsClient_MethodRef)); + instructions.Add(processor.Create(OpCodes.Brtrue, lastInstr)); + instructions.Add(processor.Create(OpCodes.Ldloc, netManLocIdx)); + instructions.Add(processor.Create(OpCodes.Callvirt, m_NetworkManager_getIsHost_MethodRef)); + instructions.Add(processor.Create(OpCodes.Brtrue, lastInstr)); - // INetworkSerializable.NetworkSerialize(serializer) - processor.Emit(OpCodes.Ldloc, localIndex); - processor.Emit(OpCodes.Ldarg_1); - processor.Emit(OpCodes.Callvirt, paramTypeNetworkSerialize_MethodRef); + instructions.Add(returnInstr); + instructions.Add(lastInstr); + } - processor.Append(notSetInstr); - } - } + instructions.Reverse(); + instructions.ForEach(instruction => processor.Body.Instructions.Insert(0, instruction)); + } - continue; - } - } + private MethodDefinition GenerateStaticHandler(MethodDefinition methodDefinition, CustomAttribute rpcAttribute) + { + var typeSystem = methodDefinition.Module.TypeSystem; + var nhandler = new MethodDefinition( + $"{methodDefinition.Name}__nhandler", + MethodAttributes.Private | MethodAttributes.Static | MethodAttributes.HideBySig, + methodDefinition.Module.TypeSystem.Void); + nhandler.Parameters.Add(new ParameterDefinition("target", ParameterAttributes.None, m_NetworkBehaviour_TypeRef)); + nhandler.Parameters.Add(new ParameterDefinition("reader", ParameterAttributes.None, m_FastBufferReader_TypeRef.MakeByReferenceType())); + nhandler.Parameters.Add(new ParameterDefinition("rpcParams", ParameterAttributes.None, m_RpcParams_TypeRef)); - // INetworkSerializable[] - if (paramType.IsArray && paramType.GetElementType().HasInterface(CodeGenHelpers.INetworkSerializable_FullName)) + var processor = nhandler.Body.GetILProcessor(); + var isServerRpc = rpcAttribute.AttributeType.FullName == CodeGenHelpers.ServerRpcAttribute_FullName; + var requireOwnership = true; // default value MUST be = `ServerRpcAttribute.RequireOwnership` + foreach (var attrField in rpcAttribute.Fields) + { + switch (attrField.Name) { - var paramElemType = paramType.GetElementType(); - var paramElemTypeDef = paramElemType.Resolve(); - var paramElemNetworkSerialize_MethodDef = paramElemTypeDef.Methods.FirstOrDefault(m => m.Name == CodeGenHelpers.INetworkSerializable_NetworkSerialize_Name); - var paramElemNetworkSerialize_MethodRef = methodDefinition.Module.ImportReference(paramElemNetworkSerialize_MethodDef); - if (paramElemNetworkSerialize_MethodRef != null) - { - nhandler.Body.Variables.Add(new VariableDefinition(typeSystem.Int32)); - int arrLenLocalIndex = nhandler.Body.Variables.Count - 1; - - processor.Emit(OpCodes.Ldarg_1); - processor.Emit(OpCodes.Ldloca, arrLenLocalIndex); - processor.Emit(OpCodes.Callvirt, m_NetworkSerializer_SerializeInt_MethodRef); + case k_ServerRpcAttribute_RequireOwnership: + requireOwnership = attrField.Argument.Type == typeSystem.Boolean && (bool)attrField.Argument.Value; + break; + } + } - var postForInstr = processor.Create(OpCodes.Nop); + nhandler.Body.InitLocals = true; + // NetworkManager networkManager; + nhandler.Body.Variables.Add(new VariableDefinition(m_NetworkManager_TypeRef)); + int netManLocIdx = nhandler.Body.Variables.Count - 1; - processor.Emit(OpCodes.Ldloc, arrLenLocalIndex); - processor.Emit(OpCodes.Ldc_I4_M1); - processor.Emit(OpCodes.Cgt); - processor.Emit(OpCodes.Brfalse, postForInstr); + { + var returnInstr = processor.Create(OpCodes.Ret); + var lastInstr = processor.Create(OpCodes.Nop); - processor.Emit(OpCodes.Ldloc, arrLenLocalIndex); - processor.Emit(OpCodes.Newarr, paramElemType); - processor.Emit(OpCodes.Stloc, localIndex); + // networkManager = this.NetworkManager; + processor.Emit(OpCodes.Ldarg_0); + processor.Emit(OpCodes.Call, m_NetworkBehaviour_getNetworkManager_MethodRef); + processor.Emit(OpCodes.Stloc, netManLocIdx); - nhandler.Body.Variables.Add(new VariableDefinition(typeSystem.Int32)); - int counterLocalIndex = nhandler.Body.Variables.Count - 1; + // if (networkManager == null || !networkManager.IsListening) return; + processor.Emit(OpCodes.Ldloc, netManLocIdx); + processor.Emit(OpCodes.Brfalse, returnInstr); + processor.Emit(OpCodes.Ldloc, netManLocIdx); + processor.Emit(OpCodes.Callvirt, m_NetworkManager_getIsListening_MethodRef); + processor.Emit(OpCodes.Brtrue, lastInstr); - var forBodyInstr = processor.Create(OpCodes.Nop); - var forCheckInstr = processor.Create(OpCodes.Nop); + processor.Append(returnInstr); + processor.Append(lastInstr); + } - processor.Emit(OpCodes.Ldc_I4_0); - processor.Emit(OpCodes.Stloc, counterLocalIndex); - processor.Emit(OpCodes.Br, forCheckInstr); - processor.Append(forBodyInstr); + if (isServerRpc && requireOwnership) + { + var roReturnInstr = processor.Create(OpCodes.Ret); + var roLastInstr = processor.Create(OpCodes.Nop); - if (paramElemType.IsValueType) - { - // struct (pass by value) - processor.Emit(OpCodes.Ldloc, localIndex); - processor.Emit(OpCodes.Ldloc, counterLocalIndex); - processor.Emit(OpCodes.Ldelema, paramElemType); - processor.Emit(OpCodes.Ldarg_1); - processor.Emit(OpCodes.Call, paramElemNetworkSerialize_MethodRef); - } - else - { - // class (pass by reference) - var paramElemTypeDefCtor = paramElemTypeDef.GetConstructors().FirstOrDefault(m => m.Parameters.Count == 0); - if (paramElemTypeDefCtor != null) - { - nhandler.Body.Variables.Add(new VariableDefinition(typeSystem.Boolean)); - int isSetLocalIndex = nhandler.Body.Variables.Count - 1; + // if (rpcParams.Server.Receive.SenderClientId != target.OwnerClientId) { ... } return; + processor.Emit(OpCodes.Ldarg_2); + processor.Emit(OpCodes.Ldfld, m_RpcParams_Server_FieldRef); + processor.Emit(OpCodes.Ldfld, m_ServerRpcParams_Receive_FieldRef); + processor.Emit(OpCodes.Ldfld, m_ServerRpcParams_Receive_SenderClientId_FieldRef); + processor.Emit(OpCodes.Ldarg_0); + processor.Emit(OpCodes.Call, m_NetworkBehaviour_getOwnerClientId_MethodRef); + processor.Emit(OpCodes.Ceq); + processor.Emit(OpCodes.Ldc_I4, 0); + processor.Emit(OpCodes.Ceq); + processor.Emit(OpCodes.Brfalse, roLastInstr); - processor.Emit(OpCodes.Ldarg_1); - processor.Emit(OpCodes.Ldloca, isSetLocalIndex); - processor.Emit(OpCodes.Callvirt, m_NetworkSerializer_SerializeBool_MethodRef); + var logNextInstr = processor.Create(OpCodes.Nop); - var notSetInstr = processor.Create(OpCodes.Nop); + // if (LogLevel.Normal > networkManager.LogLevel) + processor.Emit(OpCodes.Ldloc, netManLocIdx); + processor.Emit(OpCodes.Ldfld, m_NetworkManager_LogLevel_FieldRef); + processor.Emit(OpCodes.Ldc_I4, (int)LogLevel.Normal); + processor.Emit(OpCodes.Cgt); + processor.Emit(OpCodes.Ldc_I4, 0); + processor.Emit(OpCodes.Ceq); + processor.Emit(OpCodes.Brfalse, logNextInstr); - processor.Emit(OpCodes.Ldloc, isSetLocalIndex); - processor.Emit(OpCodes.Brfalse, notSetInstr); + // Debug.LogError(...); + processor.Emit(OpCodes.Ldstr, "Only the owner can invoke a ServerRpc that requires ownership!"); + processor.Emit(OpCodes.Call, m_Debug_LogError_MethodRef); - processor.Emit(OpCodes.Ldloc, localIndex); - processor.Emit(OpCodes.Ldloc, counterLocalIndex); - processor.Emit(OpCodes.Newobj, paramElemTypeDefCtor); - processor.Emit(OpCodes.Stelem_Ref); + processor.Append(logNextInstr); - processor.Emit(OpCodes.Ldloc, localIndex); - processor.Emit(OpCodes.Ldloc, counterLocalIndex); - processor.Emit(OpCodes.Ldelem_Ref); - processor.Emit(OpCodes.Ldarg_1); - processor.Emit(OpCodes.Call, paramElemNetworkSerialize_MethodRef); + processor.Append(roReturnInstr); + processor.Append(roLastInstr); + } - processor.Append(notSetInstr); - } - } + // read method parameters from stream + int paramCount = methodDefinition.Parameters.Count; + int[] paramLocalMap = new int[paramCount]; + for (int paramIndex = 0; paramIndex < paramCount; ++paramIndex) + { + var paramDef = methodDefinition.Parameters[paramIndex]; + var paramType = paramDef.ParameterType; - processor.Emit(OpCodes.Ldloc, counterLocalIndex); - processor.Emit(OpCodes.Ldc_I4_1); - processor.Emit(OpCodes.Add); - processor.Emit(OpCodes.Stloc, counterLocalIndex); - processor.Append(forCheckInstr); - processor.Emit(OpCodes.Ldloc, counterLocalIndex); - processor.Emit(OpCodes.Ldloc, arrLenLocalIndex); - processor.Emit(OpCodes.Clt); - processor.Emit(OpCodes.Brtrue, forBodyInstr); - - processor.Append(postForInstr); - continue; - } - } + // local variable + nhandler.Body.Variables.Add(new VariableDefinition(paramType)); + int localIndex = nhandler.Body.Variables.Count - 1; + paramLocalMap[paramIndex] = localIndex; // ServerRpcParams, ClientRpcParams { @@ -2640,6 +1277,56 @@ private MethodDefinition GenerateStaticHandler(MethodDefinition methodDefinition continue; } } + + Instruction jumpInstruction = null; + + if (!paramType.IsValueType) + { + if (!GetReadMethodForParameter(typeSystem.Boolean, out var boolMethodRef)) + { + m_Diagnostics.AddError(methodDefinition, $"Couldn't find boolean deserializer! Something's wrong!"); + } + + // reader.ReadValueSafe(out bool isSet) + nhandler.Body.Variables.Add(new VariableDefinition(typeSystem.Boolean)); + int isSetLocalIndex = nhandler.Body.Variables.Count - 1; + processor.Emit(OpCodes.Ldarg_1); + processor.Emit(OpCodes.Ldloca, isSetLocalIndex); + processor.Emit(OpCodes.Call, boolMethodRef); + + // paramType param = null; + processor.Emit(OpCodes.Ldnull); + processor.Emit(OpCodes.Stloc, localIndex); + + // if(isSet) { + jumpInstruction = processor.Create(OpCodes.Nop); + processor.Emit(OpCodes.Ldloc, isSetLocalIndex); + processor.Emit(OpCodes.Brfalse, jumpInstruction); + } + + var foundMethodRef = GetReadMethodForParameter(paramType, out var methodRef); + if (foundMethodRef) + { + // reader.ReadValueSafe(out localVar); + processor.Emit(OpCodes.Ldarg_1); + processor.Emit(OpCodes.Ldloca, localIndex); + if (paramType == typeSystem.String) + { + processor.Emit(OpCodes.Ldc_I4_0); + } + processor.Emit(OpCodes.Call, methodRef); + } + else + { + m_Diagnostics.AddError(methodDefinition, $"Don't know how to deserialize {paramType.Name} - implement INetworkSerializable or add an extension method to FastBufferReader to define serialization."); + continue; + } + + if (jumpInstruction != null) + { + // } + processor.Append(jumpInstruction); + } } // NetworkBehaviour.__rpc_exec_stage = __RpcExecStage.Server; -> ServerRpc diff --git a/com.unity.netcode.gameobjects/Editor/CodeGen/RuntimeAccessModifiersILPP.cs b/com.unity.netcode.gameobjects/Editor/CodeGen/RuntimeAccessModifiersILPP.cs index 8281fa8c18..844acbb9ac 100644 --- a/com.unity.netcode.gameobjects/Editor/CodeGen/RuntimeAccessModifiersILPP.cs +++ b/com.unity.netcode.gameobjects/Editor/CodeGen/RuntimeAccessModifiersILPP.cs @@ -26,7 +26,7 @@ public override ILPostProcessResult Process(ICompiledAssembly compiledAssembly) m_Diagnostics.Clear(); // read - var assemblyDefinition = CodeGenHelpers.AssemblyDefinitionFor(compiledAssembly); + var assemblyDefinition = CodeGenHelpers.AssemblyDefinitionFor(compiledAssembly, out var unused); if (assemblyDefinition == null) { m_Diagnostics.AddError($"Cannot read Netcode Runtime assembly definition: {compiledAssembly.Name}"); @@ -88,6 +88,11 @@ private void ProcessNetworkManager(TypeDefinition typeDefinition, string[] assem fieldDefinition.IsPublic = true; } + if (fieldDefinition.Name == nameof(NetworkManager.RpcReceive)) + { + fieldDefinition.IsPublic = true; + } + if (fieldDefinition.Name == nameof(NetworkManager.__rpc_name_table)) { fieldDefinition.IsPublic = true; @@ -112,19 +117,6 @@ private void ProcessNetworkBehaviour(TypeDefinition typeDefinition) fieldDefinition.IsFamily = true; } } - - foreach (var methodDefinition in typeDefinition.Methods) - { - switch (methodDefinition.Name) - { - case nameof(NetworkBehaviour.__beginSendServerRpc): - case nameof(NetworkBehaviour.__endSendServerRpc): - case nameof(NetworkBehaviour.__beginSendClientRpc): - case nameof(NetworkBehaviour.__endSendClientRpc): - methodDefinition.IsFamily = true; - break; - } - } } } } diff --git a/com.unity.netcode.gameobjects/Runtime/Configuration/HashSize.cs b/com.unity.netcode.gameobjects/Runtime/Configuration/HashSize.cs index 7d76c9c48f..c5519f1654 100644 --- a/com.unity.netcode.gameobjects/Runtime/Configuration/HashSize.cs +++ b/com.unity.netcode.gameobjects/Runtime/Configuration/HashSize.cs @@ -5,7 +5,7 @@ namespace Unity.Netcode /// Note that the HashSize does not say anything about the actual final output due to the var int encoding /// It just says how many bytes the maximum will be /// - public enum HashSize + public enum HashSize : byte { /// /// Four byte hash diff --git a/com.unity.netcode.gameobjects/Runtime/Configuration/NetworkConfig.cs b/com.unity.netcode.gameobjects/Runtime/Configuration/NetworkConfig.cs index 8c05216bb0..9abaa1dd01 100644 --- a/com.unity.netcode.gameobjects/Runtime/Configuration/NetworkConfig.cs +++ b/com.unity.netcode.gameobjects/Runtime/Configuration/NetworkConfig.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using UnityEngine; using System.Linq; +using Unity.Collections; namespace Unity.Netcode { @@ -159,24 +160,26 @@ public class NetworkConfig public string ToBase64() { NetworkConfig config = this; - using var buffer = PooledNetworkBuffer.Get(); - using var writer = PooledNetworkWriter.Get(buffer); - writer.WriteUInt16Packed(config.ProtocolVersion); - writer.WriteInt32Packed(config.TickRate); - writer.WriteInt32Packed(config.ClientConnectionBufferTimeout); - writer.WriteBool(config.ConnectionApproval); - writer.WriteInt32Packed(config.LoadSceneTimeOut); - writer.WriteBool(config.EnableTimeResync); - writer.WriteBool(config.EnsureNetworkVariableLengthSafety); - writer.WriteBits((byte)config.RpcHashSize, 2); - writer.WriteBool(ForceSamePrefabs); - writer.WriteBool(EnableSceneManagement); - writer.WriteBool(RecycleNetworkIds); - writer.WriteSinglePacked(NetworkIdRecycleDelay); - writer.WriteBool(EnableNetworkLogs); - buffer.PadBuffer(); - - return Convert.ToBase64String(buffer.ToArray()); + var writer = new FastBufferWriter(1300, Allocator.Temp); + using (writer) + { + writer.WriteValueSafe(config.ProtocolVersion); + writer.WriteValueSafe(config.TickRate); + writer.WriteValueSafe(config.ClientConnectionBufferTimeout); + writer.WriteValueSafe(config.ConnectionApproval); + writer.WriteValueSafe(config.LoadSceneTimeOut); + writer.WriteValueSafe(config.EnableTimeResync); + writer.WriteValueSafe(config.EnsureNetworkVariableLengthSafety); + writer.WriteValueSafe(config.RpcHashSize); + writer.WriteValueSafe(ForceSamePrefabs); + writer.WriteValueSafe(EnableSceneManagement); + writer.WriteValueSafe(RecycleNetworkIds); + writer.WriteValueSafe(NetworkIdRecycleDelay); + writer.WriteValueSafe(EnableNetworkLogs); + + // Allocates + return Convert.ToBase64String(writer.ToArray()); + } } /// @@ -187,23 +190,23 @@ public void FromBase64(string base64) { NetworkConfig config = this; byte[] binary = Convert.FromBase64String(base64); - using var buffer = new NetworkBuffer(binary); - using var reader = PooledNetworkReader.Get(buffer); - - config.ProtocolVersion = reader.ReadUInt16Packed(); - ushort sceneCount = reader.ReadUInt16Packed(); - config.TickRate = reader.ReadInt32Packed(); - config.ClientConnectionBufferTimeout = reader.ReadInt32Packed(); - config.ConnectionApproval = reader.ReadBool(); - config.LoadSceneTimeOut = reader.ReadInt32Packed(); - config.EnableTimeResync = reader.ReadBool(); - config.EnsureNetworkVariableLengthSafety = reader.ReadBool(); - config.RpcHashSize = (HashSize)reader.ReadBits(2); - config.ForceSamePrefabs = reader.ReadBool(); - config.EnableSceneManagement = reader.ReadBool(); - config.RecycleNetworkIds = reader.ReadBool(); - config.NetworkIdRecycleDelay = reader.ReadSinglePacked(); - config.EnableNetworkLogs = reader.ReadBool(); + using var reader = new FastBufferReader(binary, Allocator.Temp); + using (reader) + { + reader.ReadValueSafe(out config.ProtocolVersion); + reader.ReadValueSafe(out config.TickRate); + reader.ReadValueSafe(out config.ClientConnectionBufferTimeout); + reader.ReadValueSafe(out config.ConnectionApproval); + reader.ReadValueSafe(out config.LoadSceneTimeOut); + reader.ReadValueSafe(out config.EnableTimeResync); + reader.ReadValueSafe(out config.EnsureNetworkVariableLengthSafety); + reader.ReadValueSafe(out config.RpcHashSize); + reader.ReadValueSafe(out config.ForceSamePrefabs); + reader.ReadValueSafe(out config.EnableSceneManagement); + reader.ReadValueSafe(out config.RecycleNetworkIds); + reader.ReadValueSafe(out config.NetworkIdRecycleDelay); + reader.ReadValueSafe(out config.EnableNetworkLogs); + } } @@ -221,35 +224,35 @@ public ulong GetConfig(bool cache = true) return m_ConfigHash.Value; } - using var buffer = PooledNetworkBuffer.Get(); - using var writer = PooledNetworkWriter.Get(buffer); + var writer = new FastBufferWriter(1300, Allocator.Temp); + using (writer) + { + writer.WriteValueSafe(ProtocolVersion); + writer.WriteValueSafe(NetworkConstants.PROTOCOL_VERSION); - writer.WriteUInt16Packed(ProtocolVersion); - writer.WriteString(NetworkConstants.PROTOCOL_VERSION); + if (ForceSamePrefabs) + { + var sortedDictionary = NetworkPrefabOverrideLinks.OrderBy(x => x.Key); + foreach (var sortedEntry in sortedDictionary) - if (ForceSamePrefabs) - { - var sortedDictionary = NetworkPrefabOverrideLinks.OrderBy(x => x.Key); - foreach (var sortedEntry in sortedDictionary) + { + writer.WriteValueSafe(sortedEntry.Key); + } + } + writer.WriteValueSafe(ConnectionApproval); + writer.WriteValueSafe(ForceSamePrefabs); + writer.WriteValueSafe(EnableSceneManagement); + writer.WriteValueSafe(EnsureNetworkVariableLengthSafety); + writer.WriteValueSafe(RpcHashSize); + if (cache) { - writer.WriteUInt32Packed(sortedEntry.Key); + m_ConfigHash = XXHash.Hash64(writer.ToArray()); + return m_ConfigHash.Value; } - } - writer.WriteBool(ConnectionApproval); - writer.WriteBool(ForceSamePrefabs); - writer.WriteBool(EnableSceneManagement); - writer.WriteBool(EnsureNetworkVariableLengthSafety); - writer.WriteBits((byte)RpcHashSize, 2); - buffer.PadBuffer(); - - if (cache) - { - m_ConfigHash = XXHash.Hash64(buffer.ToArray()); - return m_ConfigHash.Value; - } - return XXHash.Hash64(buffer.ToArray()); + return XXHash.Hash64(writer.ToArray()); + } } /// diff --git a/com.unity.netcode.gameobjects/Runtime/Core/NetworkBehaviour.cs b/com.unity.netcode.gameobjects/Runtime/Core/NetworkBehaviour.cs index aa1e25338b..fa77cd72cb 100644 --- a/com.unity.netcode.gameobjects/Runtime/Core/NetworkBehaviour.cs +++ b/com.unity.netcode.gameobjects/Runtime/Core/NetworkBehaviour.cs @@ -3,7 +3,9 @@ using UnityEngine; using System.Reflection; using System.Linq; -using System.IO; +using Unity.Collections; +using Unity.Collections.LowLevel.Unsafe; +using Unity.Netcode.Messages; namespace Unity.Netcode { @@ -14,7 +16,7 @@ public abstract class NetworkBehaviour : MonoBehaviour { #pragma warning disable IDE1006 // disable naming rule violation check // RuntimeAccessModifiersILPP will make this `protected` - internal enum __RpcExecStage + public enum __RpcExecStage #pragma warning restore IDE1006 // restore naming rule violation check { None = 0, @@ -22,19 +24,6 @@ internal enum __RpcExecStage Client = 2 } - private static void SetUpdateStage(ref T param) where T : IHasUpdateStage - { - if (param.UpdateStage == NetworkUpdateStage.Unset) - { - param.UpdateStage = NetworkUpdateLoop.UpdateStage; - - if (param.UpdateStage == NetworkUpdateStage.Initialization) - { - param.UpdateStage = NetworkUpdateStage.EarlyUpdate; - } - } - } - #pragma warning disable IDE1006 // disable naming rule violation check // NetworkBehaviourILPP will override this in derived classes to return the name of the concrete type internal virtual string __getTypeName() => nameof(NetworkBehaviour); @@ -44,66 +33,40 @@ private static void SetUpdateStage(ref T param) where T : IHasUpdateStage #pragma warning disable IDE1006 // disable naming rule violation check [NonSerialized] // RuntimeAccessModifiersILPP will make this `protected` - internal __RpcExecStage __rpc_exec_stage = __RpcExecStage.None; + public __RpcExecStage __rpc_exec_stage = __RpcExecStage.None; #pragma warning restore 414 // restore assigned but its value is never used #pragma warning restore IDE1006 // restore naming rule violation check -#pragma warning disable IDE1006 // disable naming rule violation check - // RuntimeAccessModifiersILPP will make this `protected` - internal NetworkSerializer __beginSendServerRpc(uint rpcMethodId, ServerRpcParams serverRpcParams, RpcDelivery rpcDelivery) -#pragma warning restore IDE1006 // restore naming rule violation check - { - PooledNetworkWriter writer; - - SetUpdateStage(ref serverRpcParams.Send); - - if (serverRpcParams.Send.UpdateStage == NetworkUpdateStage.Initialization) - { - throw new NotSupportedException( - $"{nameof(NetworkUpdateStage.Initialization)} cannot be used as a target for processing RPCs."); - } - - var messageQueueContainer = NetworkManager.MessageQueueContainer; - var networkDelivery = rpcDelivery == RpcDelivery.Reliable ? NetworkDelivery.ReliableSequenced : NetworkDelivery.UnreliableSequenced; - - if (IsHost) - { - writer = messageQueueContainer.BeginAddQueueItemToFrame(MessageQueueContainer.MessageType.ServerRpc, Time.realtimeSinceStartup, networkDelivery, - NetworkManager.ServerClientId, null, MessageQueueHistoryFrame.QueueFrameType.Inbound, serverRpcParams.Send.UpdateStage); - } - else - { - writer = messageQueueContainer.BeginAddQueueItemToFrame(MessageQueueContainer.MessageType.ServerRpc, Time.realtimeSinceStartup, networkDelivery, - NetworkManager.ServerClientId, null, MessageQueueHistoryFrame.QueueFrameType.Outbound, NetworkUpdateStage.PostLateUpdate); - - writer.WriteByte((byte)MessageQueueContainer.MessageType.ServerRpc); - writer.WriteByte((byte)serverRpcParams.Send.UpdateStage); // NetworkUpdateStage - } - writer.WriteUInt64Packed(NetworkObjectId); // NetworkObjectId - writer.WriteUInt16Packed(NetworkBehaviourId); // NetworkBehaviourId - writer.WriteUInt32Packed(rpcMethodId); // NetworkRpcMethodId - - - return writer.Serializer; - } - -#pragma warning disable IDE1006 // disable naming rule violation check - // RuntimeAccessModifiersILPP will make this `protected` - internal void __endSendServerRpc(NetworkSerializer serializer, uint rpcMethodId, ServerRpcParams serverRpcParams, RpcDelivery rpcDelivery) -#pragma warning restore IDE1006 // restore naming rule violation check + public void SendServerRpc(ref FastBufferWriter writer, uint rpcMethodId, ServerRpcParams sendParams, RpcDelivery delivery) { - if (serializer == null) - { - return; + NetworkDelivery networkDelivery = NetworkDelivery.Reliable; + switch (delivery) + { + case RpcDelivery.Reliable: + networkDelivery = NetworkDelivery.ReliableFragmentedSequenced; + break; + case RpcDelivery.Unreliable: + if (writer.Length > 1300) + { + throw new OverflowException("RPC parameters are too large for unreliable delivery."); + } + networkDelivery = NetworkDelivery.Unreliable; + break; } - SetUpdateStage(ref serverRpcParams.Send); - - var rpcMessageSize = IsHost - ? NetworkManager.MessageQueueContainer.EndAddQueueItemToFrame(serializer.Writer, MessageQueueHistoryFrame.QueueFrameType.Inbound, serverRpcParams.Send.UpdateStage) - : NetworkManager.MessageQueueContainer.EndAddQueueItemToFrame(serializer.Writer, MessageQueueHistoryFrame.QueueFrameType.Outbound, NetworkUpdateStage.PostLateUpdate); - + var message = new RpcMessage + { + Data = new RpcMessage.Metadata + { + Type = RpcMessage.RpcType.Server, + NetworkObjectId = NetworkObjectId, + NetworkBehaviourId = NetworkBehaviourId, + NetworkMethodId = rpcMethodId + }, + RPCData = writer + }; + var rpcMessageSize = NetworkManager.SendMessage(message, networkDelivery, NetworkManager.ServerClientId, true); #if DEVELOPMENT_BUILD || UNITY_EDITOR if (NetworkManager.__rpc_name_table.TryGetValue(rpcMethodId, out var rpcMethodName)) { @@ -117,108 +80,53 @@ internal void __endSendServerRpc(NetworkSerializer serializer, uint rpcMethodId, #endif } -#pragma warning disable IDE1006 // disable naming rule violation check - // RuntimeAccessModifiersILPP will make this `protected` - internal NetworkSerializer __beginSendClientRpc(uint rpcMethodId, ClientRpcParams clientRpcParams, RpcDelivery rpcDelivery) -#pragma warning restore IDE1006 // restore naming rule violation check + public unsafe void SendClientRpc(ref FastBufferWriter writer, uint rpcMethodId, ClientRpcParams sendParams, RpcDelivery delivery) { - PooledNetworkWriter writer; - - SetUpdateStage(ref clientRpcParams.Send); - - if (clientRpcParams.Send.UpdateStage == NetworkUpdateStage.Initialization) - { - throw new NotSupportedException( - $"{nameof(NetworkUpdateStage.Initialization)} cannot be used as a target for processing RPCs."); - } - - // This will start a new queue item entry and will then return the writer to the current frame's stream - var networkDelivery = rpcDelivery == RpcDelivery.Reliable ? NetworkDelivery.ReliableSequenced : NetworkDelivery.UnreliableSequenced; - - ulong[] clientIds = clientRpcParams.Send.TargetClientIds ?? NetworkManager.ConnectedClientsIds; - if (clientRpcParams.Send.TargetClientIds != null && clientRpcParams.Send.TargetClientIds.Length == 0) - { - clientIds = NetworkManager.ConnectedClientsIds; + NetworkDelivery networkDelivery = NetworkDelivery.Reliable; + switch (delivery) + { + case RpcDelivery.Reliable: + networkDelivery = NetworkDelivery.ReliableFragmentedSequenced; + break; + case RpcDelivery.Unreliable: + if (writer.Length > 1300) + { + throw new OverflowException("RPC parameters are too large for unreliable delivery."); + } + networkDelivery = NetworkDelivery.Unreliable; + break; } - //NOTES ON BELOW CHANGES: - //The following checks for IsHost and whether the host client id is part of the clients to recieve the RPC - //Is part of a patch-fix to handle looping back RPCs into the next frame's inbound queue. - //!!! This code is temporary and will change (soon) when NetworkSerializer can be configured for mutliple NetworkWriters!!! - var containsServerClientId = clientIds.Contains(NetworkManager.ServerClientId); - bool addHeader = true; - var messageQueueContainer = NetworkManager.MessageQueueContainer; - if (IsHost && containsServerClientId) + var message = new RpcMessage { - //Always write to the next frame's inbound queue - writer = messageQueueContainer.BeginAddQueueItemToFrame(MessageQueueContainer.MessageType.ClientRpc, Time.realtimeSinceStartup, networkDelivery, - NetworkManager.ServerClientId, null, MessageQueueHistoryFrame.QueueFrameType.Inbound, clientRpcParams.Send.UpdateStage); - - //Handle sending to the other clients, if so the above notes explain why this code is here (a temporary patch-fix) - if (clientIds.Length > 1) - { - //Set the loopback frame - messageQueueContainer.SetLoopBackFrameItem(clientRpcParams.Send.UpdateStage); - - //Switch to the outbound queue - writer = messageQueueContainer.BeginAddQueueItemToFrame(MessageQueueContainer.MessageType.ClientRpc, Time.realtimeSinceStartup, networkDelivery, NetworkObjectId, - clientIds, MessageQueueHistoryFrame.QueueFrameType.Outbound, NetworkUpdateStage.PostLateUpdate); - } - else + Data = new RpcMessage.Metadata { - addHeader = false; - } - } - else - { - writer = messageQueueContainer.BeginAddQueueItemToFrame(MessageQueueContainer.MessageType.ClientRpc, Time.realtimeSinceStartup, networkDelivery, NetworkObjectId, - clientIds, MessageQueueHistoryFrame.QueueFrameType.Outbound, NetworkUpdateStage.PostLateUpdate); - } + Type = RpcMessage.RpcType.Client, + NetworkObjectId = NetworkObjectId, + NetworkBehaviourId = NetworkBehaviourId, + NetworkMethodId = rpcMethodId + }, + RPCData = writer + }; + int messageSize; - if (addHeader) + if (sendParams.Send.TargetClientIds != null) { - writer.WriteByte((byte)MessageQueueContainer.MessageType.ClientRpc); - writer.WriteByte((byte)clientRpcParams.Send.UpdateStage); // NetworkUpdateStage + messageSize = NetworkManager.SendMessage(message, networkDelivery, sendParams.Send.TargetClientIds, true); } - writer.WriteUInt64Packed(NetworkObjectId); // NetworkObjectId - writer.WriteUInt16Packed(NetworkBehaviourId); // NetworkBehaviourId - writer.WriteUInt32Packed(rpcMethodId); // NetworkRpcMethodId - - - return writer.Serializer; - } - -#pragma warning disable IDE1006 // disable naming rule violation check - // RuntimeAccessModifiersILPP will make this `protected` - internal void __endSendClientRpc(NetworkSerializer serializer, uint rpcMethodId, ClientRpcParams clientRpcParams, RpcDelivery rpcDelivery) -#pragma warning restore IDE1006 // restore naming rule violation check - { - if (serializer == null) + else if (sendParams.Send.TargetClientIdsNativeArray != null) { - return; + // NativeArray doesn't implement required IReadOnlyList interface, but that's ok, pointer + length + // will be more efficient anyway. + messageSize = NetworkManager.SendMessage(message, networkDelivery, + (ulong*)sendParams.Send.TargetClientIdsNativeArray.Value.GetUnsafePtr(), + sendParams.Send.TargetClientIdsNativeArray.Value.Length); } - - SetUpdateStage(ref clientRpcParams.Send); - - if (IsHost) + else { - ulong[] clientIds = clientRpcParams.Send.TargetClientIds ?? NetworkManager.ConnectedClientsIds; - if (clientRpcParams.Send.TargetClientIds != null && clientRpcParams.Send.TargetClientIds.Length == 0) - { - clientIds = NetworkManager.ConnectedClientsIds; - } - - var containsServerClientId = clientIds.Contains(NetworkManager.ServerClientId); - if (containsServerClientId && clientIds.Length == 1) - { - NetworkManager.MessageQueueContainer.EndAddQueueItemToFrame(serializer.Writer, MessageQueueHistoryFrame.QueueFrameType.Inbound, clientRpcParams.Send.UpdateStage); - - return; - } + messageSize = NetworkManager.SendMessage(message, networkDelivery, NetworkManager.ConnectedClientsIds, true); } - var messageSize = NetworkManager.MessageQueueContainer.EndAddQueueItemToFrame(serializer.Writer, MessageQueueHistoryFrame.QueueFrameType.Outbound, NetworkUpdateStage.PostLateUpdate); - #if DEVELOPMENT_BUILD || UNITY_EDITOR if (NetworkManager.__rpc_name_table.TryGetValue(rpcMethodId, out var rpcMethodName)) { @@ -463,16 +371,16 @@ internal void InitializeVariables() internal void PreNetworkVariableWrite() { // reset our "which variables got written" data - m_NetworkVariableIndexesToReset.Clear(); - m_NetworkVariableIndexesToResetSet.Clear(); + NetworkVariableIndexesToReset.Clear(); + NetworkVariableIndexesToResetSet.Clear(); } internal void PostNetworkVariableWrite() { // mark any variables we wrote as no longer dirty - for (int i = 0; i < m_NetworkVariableIndexesToReset.Count; i++) + for (int i = 0; i < NetworkVariableIndexesToReset.Count; i++) { - NetworkVariableFields[m_NetworkVariableIndexesToReset[i]].ResetDirty(); + NetworkVariableFields[NetworkVariableIndexesToReset[i]].ResetDirty(); } } @@ -487,8 +395,8 @@ internal void VariableUpdate(ulong clientId) NetworkVariableUpdate(clientId, NetworkBehaviourId); } - private readonly List m_NetworkVariableIndexesToReset = new List(); - private readonly HashSet m_NetworkVariableIndexesToResetSet = new HashSet(); + internal readonly List NetworkVariableIndexesToReset = new List(); + internal readonly HashSet NetworkVariableIndexesToResetSet = new HashSet(); private void NetworkVariableUpdate(ulong clientId, int behaviourIndex) { @@ -509,92 +417,43 @@ private void NetworkVariableUpdate(ulong clientId, int behaviourIndex) { for (int j = 0; j < m_DeliveryMappedNetworkVariableIndices.Count; j++) { - using var buffer = PooledNetworkBuffer.Get(); - using var writer = PooledNetworkWriter.Get(buffer); - // TODO: could skip this if no variables dirty, though obsolete w/ Snapshot - writer.WriteUInt64Packed(NetworkObjectId); - writer.WriteUInt16Packed(NetworkObject.GetNetworkBehaviourOrderIndex(this)); - - var bufferSizeCapture = new BufferSizeCapture(buffer); - - var writtenAny = false; + var shouldSend = false; for (int k = 0; k < NetworkVariableFields.Count; k++) { - if (!m_DeliveryMappedNetworkVariableIndices[j].Contains(k)) + if (NetworkVariableFields[k].ShouldWrite(clientId, IsServer)) { - // This var does not belong to the currently iterating delivery group. - if (NetworkManager.NetworkConfig.EnsureNetworkVariableLengthSafety) - { - writer.WriteUInt16Packed(0); - } - else - { - writer.WriteBool(false); - } - - continue; + shouldSend = true; } + } - // if I'm dirty AND a client, write (server always has all permissions) - // if I'm dirty AND the server AND the client can read me, send. - bool shouldWrite = NetworkVariableFields[k].ShouldWrite(clientId, IsServer); - - if (NetworkManager.NetworkConfig.EnsureNetworkVariableLengthSafety) - { - if (!shouldWrite) - { - writer.WriteUInt16Packed(0); - } - } - else + if (shouldSend) + { + var message = new NetworkVariableDeltaMessage { - writer.WriteBool(shouldWrite); - } - - if (shouldWrite) + NetworkObjectId = NetworkObjectId, + NetworkBehaviourIndex = NetworkObject.GetNetworkBehaviourOrderIndex(this), + NetworkBehaviour = this, + ClientId = clientId, + DeliveryMappedNetworkVariableIndex = m_DeliveryMappedNetworkVariableIndices[j] + }; + // TODO: Serialization is where the IsDirty flag gets changed. + // Messages don't get sent from the server to itself, so if we're host and sending to ourselves, + // we still have to actually serialize the message even though we're not sending it, otherwise + // the dirty flag doesn't change properly. These two pieces should be decoupled at some point + // so we don't have to do this serialization work if we're not going to use the result. + if (IsServer && clientId == NetworkManager.ServerClientId) { - writtenAny = true; - - if (NetworkManager.NetworkConfig.EnsureNetworkVariableLengthSafety) - { - using var varBuffer = PooledNetworkBuffer.Get(); - NetworkVariableFields[k].WriteDelta(varBuffer); - varBuffer.PadBuffer(); - - writer.WriteUInt16Packed((ushort)varBuffer.Length); - buffer.CopyFrom(varBuffer); - } - else - { - NetworkVariableFields[k].WriteDelta(buffer); - buffer.PadBuffer(); - } - - if (!m_NetworkVariableIndexesToResetSet.Contains(k)) + var tmpWriter = new FastBufferWriter(1300, Allocator.Temp); +#pragma warning disable CS0728 // Warns that tmpWriter may be reassigned within Serialize, but Serialize does not reassign it. + using (tmpWriter) { - m_NetworkVariableIndexesToResetSet.Add(k); - m_NetworkVariableIndexesToReset.Add(k); + message.Serialize(ref tmpWriter); } - - NetworkManager.NetworkMetrics.TrackNetworkVariableDeltaSent( - clientId, - NetworkObjectId, - name, - NetworkVariableFields[k].Name, - __getTypeName(), - bufferSizeCapture.Flush()); +#pragma warning restore CS0728 // Warns that tmpWriter may be reassigned within Serialize, but Serialize does not reassign it. } - } - - if (writtenAny) - { - var context = NetworkManager.MessageQueueContainer.EnterInternalCommandContext( - MessageQueueContainer.MessageType.NetworkVariableDelta, m_DeliveryTypesForNetworkVariableGroups[j], - new[] { clientId }, NetworkUpdateLoop.UpdateStage); - if (context != null) + else { - using var nonNullContext = (InternalCommandContext)context; - nonNullContext.NetworkWriter.WriteBytes(buffer.GetBuffer(), buffer.Position); + NetworkManager.SendMessage(message, m_DeliveryTypesForNetworkVariableGroups[j], clientId); } } } @@ -615,203 +474,86 @@ private bool CouldHaveDirtyNetworkVariables() return false; } - internal void HandleNetworkVariableDeltas(Stream stream, ulong clientId) - { - using var reader = PooledNetworkReader.Get(stream); - for (int i = 0; i < NetworkVariableFields.Count; i++) - { - ushort varSize = 0; - - if (NetworkManager.NetworkConfig.EnsureNetworkVariableLengthSafety) - { - varSize = reader.ReadUInt16Packed(); - - if (varSize == 0) - { - continue; - } - } - else - { - if (!reader.ReadBool()) - { - continue; - } - } - - if (NetworkManager.IsServer) - { - // we are choosing not to fire an exception here, because otherwise a malicious client could use this to crash the server - if (NetworkManager.NetworkConfig.EnsureNetworkVariableLengthSafety) - { - if (NetworkLog.CurrentLogLevel <= LogLevel.Normal) - { - NetworkLog.LogWarning($"Client wrote to {typeof(NetworkVariable<>).Name} without permission. => {nameof(NetworkObjectId)}: {NetworkObjectId} - {nameof(NetworkObject.GetNetworkBehaviourOrderIndex)}(): {NetworkObject.GetNetworkBehaviourOrderIndex(this)} - VariableIndex: {i}"); - NetworkLog.LogError($"[{NetworkVariableFields[i].GetType().Name}]"); - } - - stream.Position += varSize; - continue; - } - - //This client wrote somewhere they are not allowed. This is critical - //We can't just skip this field. Because we don't actually know how to dummy read - //That is, we don't know how many bytes to skip. Because the interface doesn't have a - //Read that gives us the value. Only a Read that applies the value straight away - //A dummy read COULD be added to the interface for this situation, but it's just being too nice. - //This is after all a developer fault. A critical error should be fine. - // - TwoTen - if (NetworkLog.CurrentLogLevel <= LogLevel.Error) - { - NetworkLog.LogError($"Client wrote to {typeof(NetworkVariable<>).Name} without permission. No more variables can be read. This is critical. => {nameof(NetworkObjectId)}: {NetworkObjectId} - {nameof(NetworkObject.GetNetworkBehaviourOrderIndex)}(): {NetworkObject.GetNetworkBehaviourOrderIndex(this)} - VariableIndex: {i}"); - NetworkLog.LogError($"[{NetworkVariableFields[i].GetType().Name}]"); - } - - return; - } - long readStartPos = stream.Position; - - NetworkVariableFields[i].ReadDelta(stream, NetworkManager.IsServer); - NetworkManager.NetworkMetrics.TrackNetworkVariableDeltaReceived( - clientId, - NetworkObjectId, - name, - NetworkVariableFields[i].Name, - __getTypeName(), - stream.Length); - - (stream as NetworkBuffer).SkipPadBits(); - - if (NetworkManager.NetworkConfig.EnsureNetworkVariableLengthSafety) - { - if (stream.Position > (readStartPos + varSize)) - { - if (NetworkLog.CurrentLogLevel <= LogLevel.Normal) - { - NetworkLog.LogWarning( - $"Var delta read too far. {stream.Position - (readStartPos + varSize)} bytes. => {nameof(NetworkObjectId)}: {NetworkObjectId} - {nameof(NetworkObject.GetNetworkBehaviourOrderIndex)}(): {NetworkObject.GetNetworkBehaviourOrderIndex(this)} - VariableIndex: {i}"); - } - - stream.Position = readStartPos + varSize; - } - else if (stream.Position < (readStartPos + varSize)) - { - if (NetworkLog.CurrentLogLevel <= LogLevel.Normal) - { - NetworkLog.LogWarning( - $"Var delta read too little. {(readStartPos + varSize) - stream.Position} bytes. => {nameof(NetworkObjectId)}: {NetworkObjectId} - {nameof(NetworkObject.GetNetworkBehaviourOrderIndex)}(): {NetworkObject.GetNetworkBehaviourOrderIndex(this)} - VariableIndex: {i}"); - } - - stream.Position = readStartPos + varSize; - } - } - } - } - - internal void WriteNetworkVariableData(Stream stream, ulong clientId) + internal void WriteNetworkVariableData(ref FastBufferWriter writer, ulong clientId) { if (NetworkVariableFields.Count == 0) { return; } - using var writer = PooledNetworkWriter.Get(stream); for (int j = 0; j < NetworkVariableFields.Count; j++) { bool canClientRead = NetworkVariableFields[j].CanClientRead(clientId); - if (NetworkManager.NetworkConfig.EnsureNetworkVariableLengthSafety) + if (canClientRead) { - if (!canClientRead) - { - writer.WriteUInt16Packed(0); - } + var writePos = writer.Position; + writer.WriteValueSafe((ushort)0); + var startPos = writer.Position; + NetworkVariableFields[j].WriteField(ref writer); + var size = writer.Position - startPos; + writer.Seek(writePos); + writer.WriteValueSafe((ushort)size); + writer.Seek(startPos + size); } else { - writer.WriteBool(canClientRead); - } - - if (canClientRead) - { - if (NetworkManager.NetworkConfig.EnsureNetworkVariableLengthSafety) - { - using var varBuffer = PooledNetworkBuffer.Get(); - NetworkVariableFields[j].WriteField(varBuffer); - varBuffer.PadBuffer(); - - writer.WriteUInt16Packed((ushort)varBuffer.Length); - varBuffer.CopyTo(stream); - } - else - { - NetworkVariableFields[j].WriteField(stream); - writer.WritePadBits(); - } + writer.WriteValueSafe((ushort)0); } + writer.WriteValueSafe((ushort)0x12AB); } } - internal void SetNetworkVariableData(Stream stream) + internal void SetNetworkVariableData(ref FastBufferReader reader) { if (NetworkVariableFields.Count == 0) { return; } - using var reader = PooledNetworkReader.Get(stream); for (int j = 0; j < NetworkVariableFields.Count; j++) { - ushort varSize = 0; - - if (NetworkManager.NetworkConfig.EnsureNetworkVariableLengthSafety) + reader.ReadValueSafe(out ushort varSize); + ushort magic; + if (varSize == 0) { - varSize = reader.ReadUInt16Packed(); - - if (varSize == 0) + reader.ReadValueSafe(out magic); + if (magic != (ushort)0x12AB) { - continue; + NetworkLog.LogWarning($"Var data ended not on the magic value."); } + continue; } - else - { - if (!reader.ReadBool()) - { - continue; - } - } - - long readStartPos = stream.Position; - NetworkVariableFields[j].ReadField(stream); - reader.SkipPadBits(); + var readStartPos = reader.Position; + NetworkVariableFields[j].ReadField(ref reader); if (NetworkManager.NetworkConfig.EnsureNetworkVariableLengthSafety) { - if (stream is NetworkBuffer networkBuffer) - { - networkBuffer.SkipPadBits(); - } - - if (stream.Position > (readStartPos + varSize)) + if (reader.Position > (readStartPos + varSize)) { if (NetworkLog.CurrentLogLevel <= LogLevel.Normal) { - NetworkLog.LogWarning($"Var data read too far. {stream.Position - (readStartPos + varSize)} bytes."); + NetworkLog.LogWarning($"Var data read too far. {reader.Position - (readStartPos + varSize)} bytes."); } - stream.Position = readStartPos + varSize; + reader.Seek(readStartPos + varSize); } - else if (stream.Position < (readStartPos + varSize)) + else if (reader.Position < (readStartPos + varSize)) { if (NetworkLog.CurrentLogLevel <= LogLevel.Normal) { - NetworkLog.LogWarning($"Var data read too little. {(readStartPos + varSize) - stream.Position} bytes."); + NetworkLog.LogWarning($"Var data read too little. {(readStartPos + varSize) - reader.Position} bytes."); } - stream.Position = readStartPos + varSize; + reader.Seek(readStartPos + varSize); } } + reader.ReadValueSafe(out magic); + if (magic != (ushort)0x12AB) + { + NetworkLog.LogWarning($"Var data ended not on the magic value."); + } } } diff --git a/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs b/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs index f1b184444c..a6aac7cff2 100644 --- a/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs +++ b/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs @@ -2,6 +2,7 @@ using System.Collections; using System.Collections.Generic; using System.Linq; +using Unity.Netcode.Messages; using UnityEngine; #if UNITY_EDITOR using UnityEditor; @@ -24,7 +25,10 @@ public class NetworkManager : MonoBehaviour, INetworkUpdateSystem #pragma warning disable IDE1006 // disable naming rule violation check // RuntimeAccessModifiersILPP will make this `public` - internal static readonly Dictionary> __rpc_func_table = new Dictionary>(); + internal delegate void RpcReceive(NetworkBehaviour behaviour, ref FastBufferReader reader, __RpcParams parameters); + + // RuntimeAccessModifiersILPP will make this `public` + internal static readonly Dictionary __rpc_func_table = new Dictionary(); #if DEVELOPMENT_BUILD || UNITY_EDITOR // RuntimeAccessModifiersILPP will make this `public` @@ -39,16 +43,15 @@ public class NetworkManager : MonoBehaviour, INetworkUpdateSystem private static ProfilerMarker s_TransportConnect = new ProfilerMarker($"{nameof(NetworkManager)}.TransportConnect"); private static ProfilerMarker s_HandleIncomingData = new ProfilerMarker($"{nameof(NetworkManager)}.{nameof(HandleIncomingData)}"); private static ProfilerMarker s_TransportDisconnect = new ProfilerMarker($"{nameof(NetworkManager)}.TransportDisconnect"); - private static ProfilerMarker s_InvokeRpc = new ProfilerMarker($"{nameof(NetworkManager)}.{nameof(InvokeRpc)}"); #endif private const double k_TimeSyncFrequency = 1.0d; // sync every second, TODO will be removed once timesync is done via snapshots - internal MessageQueueContainer MessageQueueContainer { get; private set; } - internal SnapshotSystem SnapshotSystem { get; private set; } internal NetworkBehaviourUpdater BehaviourUpdater { get; private set; } + private MessagingSystem m_MessagingSystem; + private NetworkPrefabHandler m_PrefabHandler; public NetworkPrefabHandler PrefabHandler @@ -64,6 +67,93 @@ public NetworkPrefabHandler PrefabHandler } } + private class NetworkManagerHooks : INetworkHooks + { + private NetworkManager m_NetworkManager; + + internal NetworkManagerHooks(NetworkManager manager) + { + m_NetworkManager = manager; + } + + public void OnBeforeSendMessage(ulong clientId, Type messageType, NetworkDelivery delivery) + { + } + + public void OnAfterSendMessage(ulong clientId, Type messageType, NetworkDelivery delivery, int messageSizeBytes) + { + } + + public void OnBeforeReceiveMessage(ulong senderId, Type messageType, int messageSizeBytes) + { + } + + public void OnAfterReceiveMessage(ulong senderId, Type messageType, int messageSizeBytes) + { + } + + public void OnBeforeSendBatch(ulong clientId, int messageCount, int batchSizeInBytes, NetworkDelivery delivery) + { + } + + public void OnAfterSendBatch(ulong clientId, int messageCount, int batchSizeInBytes, NetworkDelivery delivery) + { + } + + public void OnBeforeReceiveBatch(ulong senderId, int messageCount, int batchSizeInBytes) + { + } + + public void OnAfterReceiveBatch(ulong senderId, int messageCount, int batchSizeInBytes) + { + } + + public bool OnVerifyCanSend(ulong destinationId, Type messageType, NetworkDelivery delivery) + { + return true; + } + + public bool OnVerifyCanReceive(ulong senderId, Type messageType) + { + if (m_NetworkManager.PendingClients.TryGetValue(senderId, out PendingClient client) && + (client.ConnectionState == PendingClient.State.PendingApproval || + (client.ConnectionState == PendingClient.State.PendingConnection && + messageType != typeof(ConnectionRequestMessage)))) + { + if (NetworkLog.CurrentLogLevel <= LogLevel.Normal) + { + NetworkLog.LogWarning($"Message received from {nameof(senderId)}={senderId.ToString()} before it has been accepted"); + } + + return false; + } + + return true; + } + } + + private class NetworkManagerMessageSender : IMessageSender + { + private NetworkManager m_NetworkManager; + + public NetworkManagerMessageSender(NetworkManager manager) + { + m_NetworkManager = manager; + } + + public void Send(ulong clientId, NetworkDelivery delivery, ref FastBufferWriter batchData) + { + + var length = batchData.Length; + //TODO: Transport needs to have a way to send it data without copying and allocating here. + var bytes = batchData.ToArray(); + var sendBuffer = new ArraySegment(bytes, 0, length); + + m_NetworkManager.NetworkConfig.NetworkTransport.Send(clientId, sendBuffer, delivery); + + } + } + /// /// Returns the to use as the override as could be defined within the NetworkPrefab list /// Note: This should be used to create pools (with components) @@ -132,9 +222,6 @@ public GameObject GetNetworkPrefabOverride(GameObject gameObject) public NetworkSceneManager SceneManager { get; private set; } - // Has to have setter for tests - internal IInternalMessageHandler MessageHandler { get; set; } - /// /// Gets the networkId of the server /// @@ -148,7 +235,11 @@ public GameObject GetNetworkPrefabOverride(GameObject gameObject) public ulong LocalClientId { get => IsServer ? NetworkConfig.NetworkTransport.ServerClientId : m_LocalClientId; - internal set => m_LocalClientId = value; + internal set + { + m_LocalClientId = value; + m_MessagingSystem.SetLocalClientId(value); + } } private ulong m_LocalClientId; @@ -355,13 +446,19 @@ private void Initialize(bool server) NetworkLog.LogInfo(nameof(Initialize)); } - // Register INetworkUpdateSystem for receiving data from the wire - // Must always be registered before any other systems or messages can end up being re-ordered by frame timing - // Cannot allow any new data to arrive from the wire after MessageQueueContainer's Initialization update - // has run this.RegisterNetworkUpdate(NetworkUpdateStage.EarlyUpdate); + this.RegisterNetworkUpdate(NetworkUpdateStage.PostLateUpdate); + + m_MessagingSystem = new MessagingSystem(new NetworkManagerMessageSender(this), this, ulong.MaxValue); + + m_MessagingSystem.Hook(new NetworkManagerHooks(this)); +#if DEVELOPMENT_BUILD || UNITY_EDITOR + m_MessagingSystem.Hook(new ProfilingHooks()); +#endif + m_MessagingSystem.Hook(new MetricHooks(this)); + + LocalClientId = ulong.MaxValue; - LocalClientId = 0; PendingClients.Clear(); ConnectedClients.Clear(); ConnectedClientsList.Clear(); @@ -376,8 +473,6 @@ private void Initialize(bool server) BehaviourUpdater = new NetworkBehaviourUpdater(); - // Only create this if it's not already set (like in test cases) - MessageHandler ??= CreateMessageHandler(); if (NetworkMetrics == null) { @@ -426,20 +521,6 @@ private void Initialize(bool server) NetworkTickSystem = new NetworkTickSystem(NetworkConfig.TickRate, 0, 0); NetworkTickSystem.Tick += OnNetworkManagerTick; - // This should never happen, but in the event that it does there should be (at a minimum) a unity error logged. - if (MessageQueueContainer != null) - { - Debug.LogError( - "Init was invoked, but messageQueueContainer was already initialized! (destroying previous instance)"); - MessageQueueContainer.Dispose(); - MessageQueueContainer = null; - } - - // The MessageQueueContainer must be initialized within the Init method ONLY - // It should ONLY be shutdown and destroyed in the Shutdown method (other than just above) - MessageQueueContainer = new MessageQueueContainer(this); - - // Register INetworkUpdateSystem (always register this after messageQueueContainer has been instantiated) this.RegisterNetworkUpdate(NetworkUpdateStage.PreUpdate); // This is used to remove entries not needed or invalid @@ -712,6 +793,7 @@ public SocketTasks StartClient() } Initialize(false); + m_MessagingSystem.ClientConnected(ServerClientId); var socketTasks = NetworkConfig.NetworkTransport.StartClient(); @@ -757,6 +839,8 @@ public SocketTasks StartHost() Initialize(true); var socketTasks = NetworkConfig.NetworkTransport.StartServer(); + m_MessagingSystem.ClientConnected(ServerClientId); + LocalClientId = ServerClientId; IsServer = true; IsClient = true; @@ -864,11 +948,9 @@ public void Shutdown() if (IsServer) { // make sure all messages are flushed before transport disconnect clients - if (MessageQueueContainer != null) + if (m_MessagingSystem != null) { - MessageQueueContainer.ProcessAndFlushMessageQueue( - queueType: MessageQueueContainer.MessageQueueProcessingTypes.Send, - NetworkUpdateStage.PostLateUpdate); // flushing messages in case transport's disconnect + m_MessagingSystem.ProcessSendQueues(); } var disconnectedIds = new HashSet(); @@ -915,16 +997,8 @@ public void Shutdown() IsServer = false; IsClient = false; - // Unregister INetworkUpdateSystem before shutting down the MessageQueueContainer this.UnregisterAllNetworkUpdates(); - //If an instance of the MessageQueueContainer is still around, then shut it down and remove the reference - if (MessageQueueContainer != null) - { - MessageQueueContainer.Dispose(); - MessageQueueContainer = null; - } - if (SnapshotSystem != null) { SnapshotSystem.Dispose(); @@ -937,6 +1011,12 @@ public void Shutdown() NetworkTickSystem = null; } + if (m_MessagingSystem != null) + { + m_MessagingSystem.Dispose(); + m_MessagingSystem = null; + } + NetworkConfig.NetworkTransport.OnTransportEvent -= HandleRawTransportPoll; if (SpawnManager != null) @@ -954,18 +1034,11 @@ public void Shutdown() SceneManager = null; } - if (MessageHandler != null) - { - MessageHandler = null; - } - if (CustomMessagingManager != null) { CustomMessagingManager = null; } - m_MessageBatcher.Shutdown(); - if (BehaviourUpdater != null) { BehaviourUpdater = null; @@ -993,6 +1066,9 @@ public void NetworkUpdate(NetworkUpdateStage updateStage) case NetworkUpdateStage.PreUpdate: OnNetworkPreUpdate(); break; + case NetworkUpdateStage.PostLateUpdate: + OnNetworkPostLateUpdate(); + break; } } @@ -1014,6 +1090,8 @@ private void OnNetworkEarlyUpdate() // Only do another iteration if: there are no more messages AND (there is no limit to max events or we have processed less than the maximum) } while (IsListening && networkEvent != NetworkEvent.Nothing); + m_MessagingSystem.ProcessIncomingMessageQueue(); + #if DEVELOPMENT_BUILD || UNITY_EDITOR s_TransportPoll.End(); #endif @@ -1032,6 +1110,11 @@ private void OnNetworkPreUpdate() } } + private void OnNetworkPostLateUpdate() + { + m_MessagingSystem.ProcessSendQueues(); + } + /// /// This function runs once whenever the local tick is incremented and is responsible for the following (in order): /// - collect commands/inputs and send them to the server (TBD) @@ -1054,18 +1137,15 @@ private void OnNetworkManagerTick() private void SendConnectionRequest() { - var clientIds = new[] { ServerClientId }; - var context = MessageQueueContainer.EnterInternalCommandContext(MessageQueueContainer.MessageType.ConnectionRequest, NetworkDelivery.ReliableSequenced, clientIds, NetworkUpdateStage.EarlyUpdate); - if (context != null) - { - using var nonNullContext = (InternalCommandContext)context; - nonNullContext.NetworkWriter.WriteUInt64Packed(NetworkConfig.GetConfig()); - - if (NetworkConfig.ConnectionApproval) - { - nonNullContext.NetworkWriter.WriteByteArray(NetworkConfig.ConnectionData); - } - } + var message = new ConnectionRequestMessage + { + ConfigHash = NetworkConfig.GetConfig(), + ShouldSendConnectionData = NetworkConfig.ConnectionApproval, + ConnectionData = + new FixedUnmanagedArray(NetworkConfig + .ConnectionData) + }; + SendMessage(message, NetworkDelivery.ReliableSequenced, ServerClientId); } private IEnumerator ApprovalTimeout(ulong clientId) @@ -1092,14 +1172,13 @@ private IEnumerator ApprovalTimeout(ulong clientId) private void HandleRawTransportPoll(NetworkEvent networkEvent, ulong clientId, ArraySegment payload, float receiveTime) { - NetworkMetrics.TrackTransportBytesReceived(payload.Count); - switch (networkEvent) { case NetworkEvent.Connect: #if DEVELOPMENT_BUILD || UNITY_EDITOR s_TransportConnect.Begin(); #endif + m_MessagingSystem.ClientConnected(clientId); if (IsServer) { if (NetworkLog.CurrentLogLevel <= LogLevel.Developer) @@ -1168,116 +1247,85 @@ private void HandleRawTransportPoll(NetworkEvent networkEvent, ulong clientId, A } } - private readonly NetworkBuffer m_InputBufferWrapper = new NetworkBuffer(new byte[0]); - private readonly MessageBatcher m_MessageBatcher = new MessageBatcher(); - - internal void HandleIncomingData(ulong clientId, ArraySegment payload, float receiveTime) + public unsafe int SendMessage(in TMessageType message, NetworkDelivery delivery, in TClientIdListType clientIds, bool serverCanSendToServerId = false) + where TMessageType : INetworkMessage + where TClientIdListType : IReadOnlyList { -#if DEVELOPMENT_BUILD || UNITY_EDITOR - s_HandleIncomingData.Begin(); -#endif - - if (NetworkLog.CurrentLogLevel <= LogLevel.Developer) + // Prevent server sending to itself + if (IsServer && !serverCanSendToServerId) { - NetworkLog.LogInfo("Unwrapping Data Header"); - } - - m_InputBufferWrapper.SetTarget(payload.Array); - m_InputBufferWrapper.SetLength(payload.Count + payload.Offset); - m_InputBufferWrapper.Position = payload.Offset; + ulong* nonServerIds = stackalloc ulong[clientIds.Count]; + int newIdx = 0; + for (int idx = 0; idx < clientIds.Count; ++idx) + { + if (clientIds[idx] == ServerClientId) + { + continue; + } - using var messageStream = m_InputBufferWrapper; - // Client tried to send a network message that was not the connection request before he was accepted. + nonServerIds[newIdx++] = clientIds[idx]; + } - if (MessageQueueContainer.IsUsingBatching()) - { - m_MessageBatcher.ReceiveItems(messageStream, ReceiveCallback, clientId, receiveTime); - } - else - { - var messageType = (MessageQueueContainer.MessageType)messageStream.ReadByte(); - MessageHandler.MessageReceiveQueueItem(clientId, messageStream, receiveTime, messageType); - NetworkMetrics.TrackNetworkMessageReceived(clientId, MessageQueueContainer.GetMessageTypeName(messageType), payload.Count); + if (newIdx == 0) + { + return 0; + } + return m_MessagingSystem.SendMessage(message, delivery, nonServerIds, newIdx); } -#if DEVELOPMENT_BUILD || UNITY_EDITOR - s_HandleIncomingData.End(); -#endif + return m_MessagingSystem.SendMessage(message, delivery, clientIds); } - private void ReceiveCallback(NetworkBuffer messageBuffer, MessageQueueContainer.MessageType messageType, ulong clientId, float receiveTime) + public unsafe int SendMessage(in T message, NetworkDelivery delivery, + ulong* clientIds, int numClientIds, bool serverCanSendToServerId = false) + where T : INetworkMessage { - MessageHandler.MessageReceiveQueueItem(clientId, messageBuffer, receiveTime, messageType); - NetworkMetrics.TrackNetworkMessageReceived(clientId, MessageQueueContainer.GetMessageTypeName(messageType), messageBuffer.Length); - } - - /// - /// Called when an inbound queued RPC is invoked - /// -#pragma warning disable 618 - internal void InvokeRpc(MessageFrameItem item, NetworkUpdateStage networkUpdateStage) - { -#if DEVELOPMENT_BUILD || UNITY_EDITOR - s_InvokeRpc.Begin(); -#endif - using var reader = PooledNetworkReader.Get(item.NetworkBuffer); - var networkObjectId = reader.ReadUInt64Packed(); - var networkBehaviourId = reader.ReadUInt16Packed(); - var networkMethodId = reader.ReadUInt32Packed(); - - if (__rpc_func_table.ContainsKey(networkMethodId)) + // Prevent server sending to itself + if (IsServer && !serverCanSendToServerId) { - if (!SpawnManager.SpawnedObjects.ContainsKey(networkObjectId)) + ulong* nonServerIds = stackalloc ulong[numClientIds]; + int newIdx = 0; + for (int idx = 0; idx < numClientIds; ++idx) { - return; - } - - var networkObject = SpawnManager.SpawnedObjects[networkObjectId]; + if (clientIds[idx] == ServerClientId) + { + continue; + } - var networkBehaviour = networkObject.GetNetworkBehaviourAtOrderIndex(networkBehaviourId); - if (networkBehaviour == null) - { - return; + nonServerIds[newIdx++] = clientIds[idx]; } - var rpcParams = new __RpcParams(); - switch (item.MessageType) + if (newIdx == 0) { - case MessageQueueContainer.MessageType.ServerRpc: - rpcParams.Server = new ServerRpcParams - { - Receive = new ServerRpcReceiveParams - { - UpdateStage = networkUpdateStage, - SenderClientId = item.NetworkId - } - }; - break; - case MessageQueueContainer.MessageType.ClientRpc: - rpcParams.Client = new ClientRpcParams - { - Receive = new ClientRpcReceiveParams - { - UpdateStage = networkUpdateStage - } - }; - break; + return 0; } + return m_MessagingSystem.SendMessage(message, delivery, nonServerIds, newIdx); + } + + return m_MessagingSystem.SendMessage(message, delivery, clientIds, numClientIds); + } - __rpc_func_table[networkMethodId](networkBehaviour, new NetworkSerializer(item.NetworkReader), rpcParams); + public int SendMessage(in T message, NetworkDelivery delivery, ulong clientId, bool serverCanSendToServerId = false) + where T : INetworkMessage + { + // Prevent server sending to itself + if (IsServer && clientId == ServerClientId && !serverCanSendToServerId) + { + return 0; + } + return m_MessagingSystem.SendMessage(message, delivery, clientId); + } + internal void HandleIncomingData(ulong clientId, ArraySegment payload, float receiveTime) + { #if DEVELOPMENT_BUILD || UNITY_EDITOR - if (__rpc_name_table.TryGetValue(networkMethodId, out var rpcMethodName)) - { - NetworkMetrics.TrackRpcReceived( - item.NetworkId, - networkObjectId, - rpcMethodName, - networkBehaviour.__getTypeName(), - item.StreamSize); - } - s_InvokeRpc.End(); + s_HandleIncomingData.Begin(); +#endif + + m_MessagingSystem.HandleIncomingData(clientId, payload, receiveTime); + +#if DEVELOPMENT_BUILD || UNITY_EDITOR + s_HandleIncomingData.End(); #endif - } } /// @@ -1360,6 +1408,7 @@ private void OnClientDisconnectFromServer(ulong clientId) ConnectedClients.Remove(clientId); } + m_MessagingSystem.ClientDisconnected(clientId); } private void SyncTime() @@ -1372,13 +1421,11 @@ private void SyncTime() NetworkLog.LogInfo("Syncing Time To Clients"); } - ulong[] clientIds = ConnectedClientsIds; - var context = MessageQueueContainer.EnterInternalCommandContext(MessageQueueContainer.MessageType.TimeSync, NetworkDelivery.Unreliable, clientIds, NetworkUpdateStage.EarlyUpdate); - if (context != null) + var message = new TimeSyncMessage { - using var nonNullContext = (InternalCommandContext)context; - nonNullContext.NetworkWriter.WriteInt32Packed(NetworkTickSystem.ServerTime.Tick); - } + Tick = NetworkTickSystem.ServerTime.Tick + }; + SendMessage(message, NetworkDelivery.Unreliable, ConnectedClientsIds); #if DEVELOPMENT_BUILD || UNITY_EDITOR s_SyncTime.End(); #endif @@ -1407,7 +1454,7 @@ internal void HandleApproval(ulong ownerClientId, bool createPlayerObject, uint? if (createPlayerObject) { var networkObject = SpawnManager.CreateLocalNetworkObject(false, playerPrefabHash ?? NetworkConfig.PlayerPrefab.GetComponent().GlobalObjectIdHash, ownerClientId, null, position, rotation); - SpawnManager.SpawnNetworkObjectLocally(networkObject, SpawnManager.GetNetworkObjectId(), false, true, ownerClientId, null, false, false); + SpawnManager.SpawnNetworkObjectLocally(networkObject, SpawnManager.GetNetworkObjectId(), false, true, ownerClientId, false); ConnectedClients[ownerClientId].PlayerObject = networkObject; } @@ -1415,28 +1462,30 @@ internal void HandleApproval(ulong ownerClientId, bool createPlayerObject, uint? // Server doesn't send itself the connection approved message if (ownerClientId != ServerClientId) { - var context = MessageQueueContainer.EnterInternalCommandContext(MessageQueueContainer.MessageType.ConnectionApproved, NetworkDelivery.ReliableSequenced, new[] { ownerClientId }, NetworkUpdateStage.EarlyUpdate); - if (context != null) + var message = new ConnectionApprovedMessage { - using var nonNullContext = (InternalCommandContext)context; - nonNullContext.NetworkWriter.WriteUInt64Packed(ownerClientId); - nonNullContext.NetworkWriter.WriteInt32Packed(LocalTime.Tick); - - // If scene management is disabled, then just serialize all client relative (observed) NetworkObjects - if (!NetworkConfig.EnableSceneManagement) + OwnerClientId = ownerClientId, + NetworkTick = LocalTime.Tick + }; + if (!NetworkConfig.EnableSceneManagement) + { + if (SpawnManager.SpawnedObjectsList.Count != 0) { - SpawnManager.SerializeObservedNetworkObjects(ownerClientId, nonNullContext.NetworkWriter); + message.SceneObjectCount = SpawnManager.SpawnedObjectsList.Count; + message.SpawnedObjectsList = SpawnManager.SpawnedObjectsList; } } + SendMessage(message, NetworkDelivery.ReliableFragmentedSequenced, ownerClientId); + // If scene management is enabled, then let NetworkSceneManager handle the initial scene and NetworkObject synchronization - if (NetworkConfig.EnableSceneManagement) + if (!NetworkConfig.EnableSceneManagement) { - SceneManager.SynchronizeNetworkObjects(ownerClientId); + InvokeOnClientConnectedCallback(ownerClientId); } else { - InvokeOnClientConnectedCallback(ownerClientId); + SceneManager.SynchronizeNetworkObjects(ownerClientId); } } else // Server just adds itself as an observer to all spawned NetworkObjects @@ -1477,54 +1526,17 @@ internal void ApprovedPlayerSpawn(ulong clientId, uint playerPrefabHash) continue; //The new client. } - var context = MessageQueueContainer.EnterInternalCommandContext(MessageQueueContainer.MessageType.CreateObject, NetworkDelivery.ReliableSequenced, new[] { clientPair.Key }, NetworkUpdateLoop.UpdateStage); - if (context != null) + var message = new CreateObjectMessage { - using var nonNullContext = (InternalCommandContext)context; - nonNullContext.NetworkWriter.WriteBool(true); - nonNullContext.NetworkWriter.WriteUInt64Packed(ConnectedClients[clientId].PlayerObject.NetworkObjectId); - nonNullContext.NetworkWriter.WriteUInt64Packed(clientId); - - //Does not have a parent - nonNullContext.NetworkWriter.WriteBool(false); - - // This is not a scene object - nonNullContext.NetworkWriter.WriteBool(false); - - nonNullContext.NetworkWriter.WriteUInt32Packed(playerPrefabHash); - - if (ConnectedClients[clientId].PlayerObject.IncludeTransformWhenSpawning == null || ConnectedClients[clientId].PlayerObject.IncludeTransformWhenSpawning(clientId)) - { - nonNullContext.NetworkWriter.WriteBool(true); - nonNullContext.NetworkWriter.WriteSinglePacked(ConnectedClients[clientId].PlayerObject.transform.position.x); - nonNullContext.NetworkWriter.WriteSinglePacked(ConnectedClients[clientId].PlayerObject.transform.position.y); - nonNullContext.NetworkWriter.WriteSinglePacked(ConnectedClients[clientId].PlayerObject.transform.position.z); - - nonNullContext.NetworkWriter.WriteSinglePacked(ConnectedClients[clientId].PlayerObject.transform.rotation.eulerAngles.x); - nonNullContext.NetworkWriter.WriteSinglePacked(ConnectedClients[clientId].PlayerObject.transform.rotation.eulerAngles.y); - nonNullContext.NetworkWriter.WriteSinglePacked(ConnectedClients[clientId].PlayerObject.transform.rotation.eulerAngles.z); - } - else - { - nonNullContext.NetworkWriter.WriteBool(false); - } - - nonNullContext.NetworkWriter.WriteBool(false); //No payload data - - ConnectedClients[clientId].PlayerObject.WriteNetworkVariableData(nonNullContext.NetworkWriter.GetStream(), clientPair.Key); - } + ObjectInfo = ConnectedClients[clientId].PlayerObject.GetMessageSceneObject(clientPair.Key) + }; + message.ObjectInfo.Metadata.Hash = playerPrefabHash; + message.ObjectInfo.Metadata.IsSceneObject = false; + message.ObjectInfo.Metadata.HasParent = false; + message.ObjectInfo.Metadata.IsPlayerObject = true; + message.ObjectInfo.Metadata.OwnerClientId = clientId; + SendMessage(message, NetworkDelivery.ReliableFragmentedSequenced, clientPair.Key); } } - - private IInternalMessageHandler CreateMessageHandler() - { - IInternalMessageHandler messageHandler = new InternalMessageHandler(this); - -#if DEVELOPMENT_BUILD || UNITY_EDITOR - messageHandler = new InternalMessageHandlerProfilingDecorator(messageHandler); -#endif - - return messageHandler; - } } } diff --git a/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs b/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs index 019ab39301..5cfdc2d5d2 100644 --- a/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs +++ b/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs @@ -1,8 +1,7 @@ using System; using System.Collections.Generic; -using System.IO; -using System.Linq; using System.Runtime.CompilerServices; +using Unity.Netcode.Messages; using UnityEngine; namespace Unity.Netcode @@ -12,7 +11,7 @@ namespace Unity.Netcode /// [AddComponentMenu("Netcode/" + nameof(NetworkObject), -99)] [DisallowMultipleComponent] - public sealed class NetworkObject : MonoBehaviour + public sealed class NetworkObject : MonoBehaviour, INetworkSerializable { [HideInInspector] [SerializeField] @@ -56,10 +55,12 @@ internal void GenerateGlobalObjectIdHash() /// internal NetworkManager NetworkManagerOwner; + private ulong m_NetworkObjectId; + /// /// Gets the unique Id of this object that is synced across the network /// - public ulong NetworkObjectId { get; internal set; } + public ulong NetworkObjectId { get => m_NetworkObjectId; internal set => m_NetworkObjectId = value; } /// /// Gets the ClientId of the owner of this NetworkObject @@ -311,19 +312,13 @@ public void NetworkHide(ulong clientId) } else { - // Send destroy call - var context = NetworkManager.MessageQueueContainer.EnterInternalCommandContext(MessageQueueContainer.MessageType.DestroyObject, NetworkDelivery.ReliableSequenced, new[] { clientId }, NetworkUpdateStage.PostLateUpdate); - if (context != null) + var message = new DestroyObjectMessage { - using var nonNullContext = (InternalCommandContext)context; - var bufferSizeCapture = new CommandContextSizeCapture(nonNullContext); - bufferSizeCapture.StartMeasureSegment(); - - nonNullContext.NetworkWriter.WriteUInt64Packed(NetworkObjectId); - - var size = bufferSizeCapture.StopMeasureSegment(); - NetworkManager.NetworkMetrics.TrackObjectDestroySent(clientId, NetworkObjectId, name, size); - } + NetworkObjectId = NetworkObjectId + }; + var size = NetworkManager.SendMessage(message, NetworkDelivery.ReliableSequenced, clientId); + // Send destroy call + NetworkManager.NetworkMetrics.TrackObjectDestroySent(clientId, NetworkObjectId, name, size); } } @@ -467,7 +462,7 @@ private void SpawnInternal(bool destroyWithScene, ulong? ownerClientId, bool pla throw new NotServerException($"Only server can spawn {nameof(NetworkObject)}s"); } - NetworkManager.SpawnManager.SpawnNetworkObjectLocally(this, NetworkManager.SpawnManager.GetNetworkObjectId(), false, playerObject, ownerClientId, null, false, destroyWithScene); + NetworkManager.SpawnManager.SpawnNetworkObjectLocally(this, NetworkManager.SpawnManager.GetNetworkObjectId(), false, playerObject, ownerClientId, destroyWithScene); if (NetworkManager.NetworkConfig.UseSnapshotSpawn) { @@ -572,36 +567,6 @@ internal void SetCachedParent(Transform parentTransform) m_CachedParent = parentTransform; } - internal static void WriteNetworkParenting(NetworkWriter writer, bool isReparented, ulong? latestParent) - { - writer.WriteBool(isReparented); - if (isReparented) - { - var isLatestParentSet = latestParent != null && latestParent.HasValue; - writer.WriteBool(isLatestParentSet); - if (isLatestParentSet) - { - writer.WriteUInt64Packed(latestParent.Value); - } - } - } - - internal static (bool IsReparented, ulong? LatestParent) ReadNetworkParenting(NetworkReader reader) - { - ulong? latestParent = null; - bool isReparented = reader.ReadBool(); - if (isReparented) - { - var isLatestParentSet = reader.ReadBool(); - if (isLatestParentSet) - { - latestParent = reader.ReadUInt64Packed(); - } - } - - return (isReparented, latestParent); - } - internal (bool IsReparented, ulong? LatestParent) GetNetworkParenting() => (m_IsReparented, m_LatestParent); internal void SetNetworkParenting(bool isReparented, ulong? latestParent) @@ -717,12 +682,28 @@ private void OnTransformParentChanged() m_IsReparented = true; ApplyNetworkParenting(); - var context = NetworkManager.MessageQueueContainer.EnterInternalCommandContext(MessageQueueContainer.MessageType.ParentSync, NetworkDelivery.ReliableSequenced, NetworkManager.ConnectedClientsIds.Where(id => Observers.Contains(id)).ToArray(), NetworkUpdateLoop.UpdateStage); - if (context != null) + var message = new ParentSyncMessage { - using var nonNullContext = (InternalCommandContext)context; - nonNullContext.NetworkWriter.WriteUInt64Packed(NetworkObjectId); - WriteNetworkParenting(nonNullContext.NetworkWriter, m_IsReparented, m_LatestParent); + NetworkObjectId = NetworkObjectId, + IsReparented = m_IsReparented, + IsLatestParentSet = m_LatestParent != null && m_LatestParent.HasValue, + LatestParent = m_LatestParent + }; + + unsafe + { + var maxCount = NetworkManager.ConnectedClientsIds.Length; + ulong* clientIds = stackalloc ulong[maxCount]; + int idx = 0; + foreach (var clientId in NetworkManager.ConnectedClientsIds) + { + if (Observers.Contains(clientId)) + { + clientIds[idx++] = clientId; + } + } + + NetworkManager.SendMessage(message, NetworkDelivery.ReliableSequenced, clientIds, idx); } } @@ -842,23 +823,23 @@ internal List ChildNetworkBehaviours } } - internal void WriteNetworkVariableData(Stream stream, ulong clientId) + internal void WriteNetworkVariableData(ref FastBufferWriter writer, ulong clientId) { for (int i = 0; i < ChildNetworkBehaviours.Count; i++) { var behavior = ChildNetworkBehaviours[i]; behavior.InitializeVariables(); - behavior.WriteNetworkVariableData(stream, clientId); + behavior.WriteNetworkVariableData(ref writer, clientId); } } - internal void SetNetworkVariableData(Stream stream) + internal void SetNetworkVariableData(ref FastBufferReader reader) { for (int i = 0; i < ChildNetworkBehaviours.Count; i++) { var behaviour = ChildNetworkBehaviours[i]; behaviour.InitializeVariables(); - behaviour.SetNetworkVariableData(stream); + behaviour.SetNetworkVariableData(ref reader); } } @@ -904,176 +885,232 @@ internal NetworkBehaviour GetNetworkBehaviourAtOrderIndex(ushort index) return ChildNetworkBehaviours[index]; } - /// - /// Used to serialize a NetworkObjects during scene synchronization that occurs - /// upon a client being approved or a scene transition. - /// - /// writer into the outbound stream - /// clientid we are targeting - internal void SerializeSceneObject(NetworkWriter writer, ulong targetClientId) + internal struct SceneObject { - writer.WriteBool(IsPlayerObject); - writer.WriteUInt64Packed(NetworkObjectId); - writer.WriteUInt64Packed(OwnerClientId); - - NetworkObject parentNetworkObject = null; - - if (!AlwaysReplicateAsRoot && transform.parent != null) + public struct SceneObjectMetadata { - parentNetworkObject = transform.parent.GetComponent(); - } + public ulong NetworkObjectId; + public ulong OwnerClientId; + public uint Hash; - if (parentNetworkObject == null) - { - // We don't have a parent - writer.WriteBool(false); + public bool IsPlayerObject; + public bool HasParent; + public bool IsSceneObject; + public bool HasTransform; + public bool IsReparented; } - else + + public SceneObjectMetadata Metadata; + + #region If(Metadata.HasParent) + public ulong ParentObjectId; + #endregion + + #region If(Metadata.HasTransform) + public struct TransformData { - // We do have a parent - writer.WriteBool(true); - // Write the parent's NetworkObjectId to be used for linking back to the child - writer.WriteUInt64Packed(parentNetworkObject.NetworkObjectId); + public Vector3 Position; + public Quaternion Rotation; } - // Write if we are a scene object or not - writer.WriteBool(IsSceneObject ?? true); + public TransformData Transform; + #endregion - // Write the hash for this NetworkObject - writer.WriteUInt32Packed(HostCheckForGlobalObjectIdHashOverride()); + #region If(Metadata.IsReparented) + public bool IsLatestParentSet; - if (IncludeTransformWhenSpawning == null || IncludeTransformWhenSpawning(OwnerClientId)) - { - // Set the position and rotation data marker to true (i.e. flag to know, when reading from the stream, that position and rotation data follows). - writer.WriteBool(true); + #region If(IsLatestParentSet) + public ulong? LatestParent; + #endregion + #endregion - // Write position - writer.WriteSinglePacked(transform.position.x); - writer.WriteSinglePacked(transform.position.y); - writer.WriteSinglePacked(transform.position.z); + public NetworkObject OwnerObject; + public ulong TargetClientId; - // Write rotation - writer.WriteSinglePacked(transform.rotation.eulerAngles.x); - writer.WriteSinglePacked(transform.rotation.eulerAngles.y); - writer.WriteSinglePacked(transform.rotation.eulerAngles.z); - } - else + public unsafe void Serialize(ref FastBufferWriter writer) { - // Set the position and rotation data marker to false (i.e. flag to know, when reading from the stream, that position and rotation data *was not included*) - writer.WriteBool(false); - } + if (!writer.TryBeginWrite( + sizeof(SceneObjectMetadata) + + (Metadata.HasParent ? FastBufferWriter.GetWriteSize(ParentObjectId) : 0) + + (Metadata.HasTransform ? FastBufferWriter.GetWriteSize(Transform) : 0) + + (Metadata.IsReparented + ? FastBufferWriter.GetWriteSize(IsLatestParentSet) + + (IsLatestParentSet ? FastBufferWriter.GetWriteSize() : 0) + : 0))) + { + throw new OverflowException("Could not serialize SceneObject: Out of buffer space."); + } - { - var (isReparented, latestParent) = GetNetworkParenting(); - WriteNetworkParenting(writer, isReparented, latestParent); - } + writer.WriteValue(Metadata); + + if (Metadata.HasParent) + { + writer.WriteValue(ParentObjectId); + } - // NetworkVariable section + if (Metadata.HasTransform) + { + writer.WriteValue(Transform); + } - // todo: remove this WriteBool and the matching read - writer.WriteBool(true); + if (Metadata.IsReparented) + { + writer.WriteValue(IsLatestParentSet); + if (IsLatestParentSet) + { + writer.WriteValue((ulong)LatestParent); + } + } - var buffer = writer.GetStream() as NetworkBuffer; + OwnerObject.WriteNetworkVariableData(ref writer, TargetClientId); + } - // Write placeholder size, NOT as a packed value, initially as zero (i.e. we do not know how much NetworkVariable data will be written yet) - writer.WriteUInt32(0); + public unsafe void Deserialize(ref FastBufferReader reader) + { + if (!reader.TryBeginRead(sizeof(SceneObjectMetadata))) + { + throw new OverflowException("Could not deserialize SceneObject: Out of buffer space."); + } + reader.ReadValue(out Metadata); + if (!reader.TryBeginRead( + (Metadata.HasParent ? FastBufferWriter.GetWriteSize(ParentObjectId) : 0) + + (Metadata.HasTransform ? FastBufferWriter.GetWriteSize(Transform) : 0) + + (Metadata.IsReparented + ? FastBufferWriter.GetWriteSize(IsLatestParentSet) + + (IsLatestParentSet ? FastBufferWriter.GetWriteSize() : 0) + : 0))) + { + throw new OverflowException("Could not deserialize SceneObject: Out of buffer space."); + } - // Mark our current position before we potentially write any NetworkVariable data - var positionBeforeNetworkVariableData = buffer.Position; + if (Metadata.HasParent) + { + reader.ReadValue(out ParentObjectId); + } - // Write network variable data - WriteNetworkVariableData(buffer, targetClientId); + if (Metadata.HasTransform) + { + reader.ReadValue(out Transform); + } + + if (Metadata.IsReparented) + { + reader.ReadValue(out IsLatestParentSet); + if (IsLatestParentSet) + { + reader.ReadValue(out ulong latestParent); + LatestParent = latestParent; + } + } + } + } - // If our current buffer position is greater than our positionBeforeNetworkVariableData then we wrote NetworkVariable data - // Part 1: This will include the total NetworkVariable data size, if there was NetworkVariable data written, to the stream - // in order to be able to skip past this entry on the deserialization side in the event this NetworkObject fails to be - // constructed (See Part 2 below in the DeserializeSceneObject method) - if (buffer.Position > positionBeforeNetworkVariableData) + internal SceneObject GetMessageSceneObject(ulong targetClientId) + { + var obj = new SceneObject { - // Store our current stream buffer position - var endOfNetworkVariableData = buffer.Position; + Metadata = new SceneObject.SceneObjectMetadata + { + IsPlayerObject = IsPlayerObject, + NetworkObjectId = NetworkObjectId, + OwnerClientId = OwnerClientId, + IsSceneObject = IsSceneObject ?? true, + Hash = HostCheckForGlobalObjectIdHashOverride() + }, + OwnerObject = this, + TargetClientId = targetClientId + }; - // Calculate the total NetworkVariable data size written - var networkVariableDataSize = endOfNetworkVariableData - positionBeforeNetworkVariableData; + NetworkObject parentNetworkObject = null; - // Move the stream position back to just before we wrote our size (we include the unpacked UInt32 data size placeholder) - buffer.Position = positionBeforeNetworkVariableData - sizeof(uint); + if (!AlwaysReplicateAsRoot && transform.parent != null) + { + parentNetworkObject = transform.parent.GetComponent(); + } - // Now write the actual data size written into our unpacked UInt32 placeholder position - writer.WriteUInt32((uint)(networkVariableDataSize)); + if (parentNetworkObject) + { + obj.Metadata.HasParent = true; + obj.ParentObjectId = parentNetworkObject.NetworkObjectId; + } + if (IncludeTransformWhenSpawning == null || IncludeTransformWhenSpawning(OwnerClientId)) + { + obj.Metadata.HasTransform = true; + obj.Transform = new SceneObject.TransformData + { + Position = transform.position, + Rotation = transform.rotation + }; + } - // Finally, revert the buffer position back to the end of the network variable data written - buffer.Position = endOfNetworkVariableData; + var (isReparented, latestParent) = GetNetworkParenting(); + obj.Metadata.IsReparented = isReparented; + if (isReparented) + { + var isLatestParentSet = latestParent != null && latestParent.HasValue; + obj.IsLatestParentSet = isLatestParentSet; + if (isLatestParentSet) + { + obj.LatestParent = latestParent.Value; + } } + + return obj; } /// /// Used to deserialize a serialized scene object which occurs /// when the client is approved or during a scene transition /// - /// inbound stream - /// reader for the stream + /// Deserialized scene object data + /// reader for the NetworkVariable data /// NetworkManager instance /// optional to use NetworkObject deserialized - internal static NetworkObject DeserializeSceneObject(NetworkBuffer objectStream, NetworkReader reader, NetworkManager networkManager) + internal static NetworkObject AddSceneObject(in SceneObject sceneObject, ref FastBufferReader variableData, NetworkManager networkManager) { - var isPlayerObject = reader.ReadBool(); - var networkId = reader.ReadUInt64Packed(); - var ownerClientId = reader.ReadUInt64Packed(); - var hasParent = reader.ReadBool(); + Vector3? position = null; + Quaternion? rotation = null; ulong? parentNetworkId = null; - if (hasParent) + if (sceneObject.Metadata.HasTransform) { - parentNetworkId = reader.ReadUInt32Packed(); + position = sceneObject.Transform.Position; + rotation = sceneObject.Transform.Rotation; } - bool isSceneObject = reader.ReadBool(); - - uint globalObjectIdHash = reader.ReadUInt32Packed(); - Vector3? position = null; - Quaternion? rotation = null; - - // Check to see if we have position and rotation values that follows - if (reader.ReadBool()) + if (sceneObject.Metadata.HasParent) { - position = new Vector3(reader.ReadSinglePacked(), reader.ReadSinglePacked(), reader.ReadSinglePacked()); - rotation = Quaternion.Euler(reader.ReadSinglePacked(), reader.ReadSinglePacked(), reader.ReadSinglePacked()); + parentNetworkId = sceneObject.ParentObjectId; } - var (isReparented, latestParent) = ReadNetworkParenting(reader); - //Attempt to create a local NetworkObject - var networkObject = networkManager.SpawnManager.CreateLocalNetworkObject(isSceneObject, globalObjectIdHash, ownerClientId, parentNetworkId, position, rotation, isReparented); - - networkObject?.SetNetworkParenting(isReparented, latestParent); + var networkObject = networkManager.SpawnManager.CreateLocalNetworkObject( + sceneObject.Metadata.IsSceneObject, sceneObject.Metadata.Hash, + sceneObject.Metadata.OwnerClientId, parentNetworkId, position, rotation, sceneObject.Metadata.IsReparented); - // Determine if this NetworkObject has NetworkVariable data to read - var networkVariableDataIsIncluded = reader.ReadBool(); + networkObject?.SetNetworkParenting(sceneObject.Metadata.IsReparented, sceneObject.LatestParent); - if (networkVariableDataIsIncluded) + if (networkObject == null) { - // (See Part 1 above in the NetworkObject.SerializeSceneObject method to better understand this) - // Part 2: This makes sure that if one NetworkObject fails to construct (for whatever reason) then we can "skip past" - // that specific NetworkObject but continue processing any remaining serialized NetworkObjects as opposed to just - // throwing an exception and skipping the remaining (if any) NetworkObjects. This will prevent one misconfigured - // issue (or more) from breaking the entire loading process. - var networkVariableDataSize = reader.ReadUInt32(); - if (networkObject == null) - { - // Log the error that the NetworkObject failed to construct - Debug.LogError($"Failed to spawn {nameof(NetworkObject)} for Hash {globalObjectIdHash}."); + // Log the error that the NetworkObject failed to construct + Debug.LogError($"Failed to spawn {nameof(NetworkObject)} for Hash {sceneObject.Metadata.Hash}."); - // If we failed to load this NetworkObject, then skip past the network variable data - objectStream.Position += networkVariableDataSize; + // If we failed to load this NetworkObject, then skip past the network variable data + variableData.ReadValueSafe(out ushort varSize); + variableData.Seek(variableData.Position + varSize); - // We have nothing left to do here. - return null; + variableData.ReadValueSafe(out ushort magic); + if (magic != (ushort)0x12AB) + { + NetworkLog.LogWarning($"Var data ended not on the magic value."); } + + // We have nothing left to do here. + return null; } // Spawn the NetworkObject - networkManager.SpawnManager.SpawnNetworkObjectLocally(networkObject, networkId, isSceneObject, isPlayerObject, ownerClientId, objectStream, true, false); + networkManager.SpawnManager.SpawnNetworkObjectLocally(networkObject, sceneObject, ref variableData, false); return networkObject; } @@ -1102,5 +1139,10 @@ internal uint HostCheckForGlobalObjectIdHashOverride() return GlobalObjectIdHash; } + + public void NetworkSerialize(BufferSerializer serializer) where T : IBufferSerializerImplementation + { + serializer.SerializeValue(ref m_NetworkObjectId); + } } } diff --git a/com.unity.netcode.gameobjects/Runtime/Core/SnapshotSystem.cs b/com.unity.netcode.gameobjects/Runtime/Core/SnapshotSystem.cs index e4affa5472..38c712fe4a 100644 --- a/com.unity.netcode.gameobjects/Runtime/Core/SnapshotSystem.cs +++ b/com.unity.netcode.gameobjects/Runtime/Core/SnapshotSystem.cs @@ -1,6 +1,8 @@ using System; using System.Collections.Generic; -using System.IO; +using Unity.Collections; +using Unity.Collections.LowLevel.Unsafe; +using Unity.Netcode.Messages; using UnityEngine; namespace Unity.Netcode @@ -89,7 +91,6 @@ internal class Snapshot internal SnapshotDespawnCommand[] Despawns; internal int NumDespawns = 0; - private MemoryStream m_BufferStream; internal NetworkManager NetworkManager; // indexed by ObjectId @@ -100,11 +101,8 @@ internal class Snapshot /// Constructor /// Allocated a MemoryStream to be reused for this Snapshot /// - /// The NetworkManaher this Snapshot uses. Needed upon receive to set Variables - /// Whether this Snapshot uses the tick as an index internal Snapshot() { - m_BufferStream = new MemoryStream(RecvBuffer, 0, k_BufferSize); // we ask for twice as many slots because there could end up being one free spot between each pair of slot used Allocator = new IndexAllocator(k_BufferSize, k_MaxVariables * 2); Spawns = new SnapshotSpawnCommand[m_MaxSpawns]; @@ -232,25 +230,7 @@ internal void AddDespawn(SnapshotDespawnCommand command) } } - /// - /// Write an Entry to send - /// Must match ReadEntry - /// - /// The writer to write the entry to - internal void WriteEntry(NetworkWriter writer, in Entry entry) - { - //todo: major refactor. - // use blittable types and copy variable in memory locally - // only serialize when put on the wire for network transfer - writer.WriteUInt64Packed(entry.Key.NetworkObjectId); - writer.WriteUInt16(entry.Key.BehaviourIndex); - writer.WriteUInt16(entry.Key.VariableIndex); - writer.WriteInt32Packed(entry.Key.TickWritten); - writer.WriteUInt16(entry.Position); - writer.WriteUInt16(entry.Length); - } - - internal ClientData.SentSpawn WriteSpawn(in ClientData clientData, NetworkWriter writer, in SnapshotSpawnCommand spawn) + internal ClientData.SentSpawn GetSpawnData(in ClientData clientData, in SnapshotSpawnCommand spawn, out SnapshotDataMessage.SpawnData data) { // remember which spawn we sent this connection with which sequence number // that way, upon ack, we can track what is being ack'ed @@ -259,23 +239,25 @@ internal ClientData.SentSpawn WriteSpawn(in ClientData clientData, NetworkWriter sentSpawn.Tick = spawn.TickWritten; sentSpawn.SequenceNumber = clientData.SequenceNumber; - writer.WriteUInt64Packed(spawn.NetworkObjectId); - writer.WriteUInt64Packed(spawn.GlobalObjectIdHash); - writer.WriteBool(spawn.IsSceneObject); - - writer.WriteBool(spawn.IsPlayerObject); - writer.WriteUInt64Packed(spawn.OwnerClientId); - writer.WriteUInt64Packed(spawn.ParentNetworkId); - writer.WriteVector3(spawn.ObjectPosition); - writer.WriteRotation(spawn.ObjectRotation); - writer.WriteVector3(spawn.ObjectScale); - - writer.WriteInt32Packed(spawn.TickWritten); - + data = new SnapshotDataMessage.SpawnData + { + NetworkObjectId = spawn.NetworkObjectId, + Hash = spawn.GlobalObjectIdHash, + IsSceneObject = spawn.IsSceneObject, + + IsPlayerObject = spawn.IsPlayerObject, + OwnerClientId = spawn.OwnerClientId, + ParentNetworkId = spawn.ParentNetworkId, + Position = spawn.ObjectPosition, + Rotation = spawn.ObjectRotation, + Scale = spawn.ObjectScale, + + TickWritten = spawn.TickWritten + }; return sentSpawn; } - internal ClientData.SentSpawn WriteDespawn(in ClientData clientData, NetworkWriter writer, in SnapshotDespawnCommand despawn) + internal ClientData.SentSpawn GetDespawnData(in ClientData clientData, in SnapshotDespawnCommand despawn, out SnapshotDataMessage.DespawnData data) { // remember which spawn we sent this connection with which sequence number // that way, upon ack, we can track what is being ack'ed @@ -284,8 +266,11 @@ internal ClientData.SentSpawn WriteDespawn(in ClientData clientData, NetworkWrit sentSpawn.Tick = despawn.TickWritten; sentSpawn.SequenceNumber = clientData.SequenceNumber; - writer.WriteUInt64Packed(despawn.NetworkObjectId); - writer.WriteInt32Packed(despawn.TickWritten); + data = new SnapshotDataMessage.DespawnData + { + NetworkObjectId = despawn.NetworkObjectId, + TickWritten = despawn.TickWritten + }; return sentSpawn; } @@ -293,45 +278,45 @@ internal ClientData.SentSpawn WriteDespawn(in ClientData clientData, NetworkWrit /// Read a received Entry /// Must match WriteEntry /// - /// The readed to read the entry from - internal Entry ReadEntry(NetworkReader reader) + /// Deserialized snapshot entry data + internal Entry ReadEntry(SnapshotDataMessage.EntryData data) { Entry entry; - entry.Key.NetworkObjectId = reader.ReadUInt64Packed(); - entry.Key.BehaviourIndex = reader.ReadUInt16(); - entry.Key.VariableIndex = reader.ReadUInt16(); - entry.Key.TickWritten = reader.ReadInt32Packed(); - entry.Position = reader.ReadUInt16(); - entry.Length = reader.ReadUInt16(); + entry.Key.NetworkObjectId = data.NetworkObjectId; + entry.Key.BehaviourIndex = data.BehaviourIndex; + entry.Key.VariableIndex = data.VariableIndex; + entry.Key.TickWritten = data.TickWritten; + entry.Position = data.Position; + entry.Length = data.Length; return entry; } - internal SnapshotSpawnCommand ReadSpawn(NetworkReader reader) + internal SnapshotSpawnCommand ReadSpawn(SnapshotDataMessage.SpawnData data) { var command = new SnapshotSpawnCommand(); - command.NetworkObjectId = reader.ReadUInt64Packed(); - command.GlobalObjectIdHash = (uint)reader.ReadUInt64Packed(); - command.IsSceneObject = reader.ReadBool(); - command.IsPlayerObject = reader.ReadBool(); - command.OwnerClientId = reader.ReadUInt64Packed(); - command.ParentNetworkId = reader.ReadUInt64Packed(); - command.ObjectPosition = reader.ReadVector3(); - command.ObjectRotation = reader.ReadRotation(); - command.ObjectScale = reader.ReadVector3(); + command.NetworkObjectId = data.NetworkObjectId; + command.GlobalObjectIdHash = data.Hash; + command.IsSceneObject = data.IsSceneObject; + command.IsPlayerObject = data.IsPlayerObject; + command.OwnerClientId = data.OwnerClientId; + command.ParentNetworkId = data.ParentNetworkId; + command.ObjectPosition = data.Position; + command.ObjectRotation = data.Rotation; + command.ObjectScale = data.Scale; - command.TickWritten = reader.ReadInt32Packed(); + command.TickWritten = data.TickWritten; return command; } - internal SnapshotDespawnCommand ReadDespawn(NetworkReader reader) + internal SnapshotDespawnCommand ReadDespawn(SnapshotDataMessage.DespawnData data) { var command = new SnapshotDespawnCommand(); - command.NetworkObjectId = reader.ReadUInt64Packed(); - command.TickWritten = reader.ReadInt32Packed(); + command.NetworkObjectId = data.NetworkObjectId; + command.TickWritten = data.TickWritten; return command; } @@ -367,29 +352,26 @@ internal void AllocateEntry(ref Entry entry, int index, int size) /// Must match WriteBuffer /// The stream is actually a memory stream and we seek to each variable position as we deserialize them /// - /// The NetworkReader to read our buffer of variables from - /// The stream to read our buffer of variables from - internal void ReadBuffer(NetworkReader reader, Stream snapshotStream) + /// The message to pull the buffer from + internal void ReadBuffer(in SnapshotDataMessage message) { - int snapshotSize = reader.ReadUInt16(); - snapshotStream.Read(RecvBuffer, 0, snapshotSize); + RecvBuffer = message.ReceiveMainBuffer.ToArray(); // Note: Allocates } /// /// Read the snapshot index from a buffer /// Stores the entry. Allocates memory if needed. The actual buffer will be read later /// - /// The reader to read the index from - internal void ReadIndex(NetworkReader reader) + /// The message to read the index from + internal void ReadIndex(in SnapshotDataMessage message) { Entry entry; - short entries = reader.ReadInt16(); - for (var i = 0; i < entries; i++) + for (var i = 0; i < message.Entries.Length; i++) { bool added = false; - entry = ReadEntry(reader); + entry = ReadEntry(message.Entries[i]); int pos = Find(entry.Key);// should return if there's anything more recent if (pos == Entry.NotFound) @@ -415,27 +397,37 @@ internal void ReadIndex(NetworkReader reader) var networkVariable = FindNetworkVar(Entries[pos].Key); if (networkVariable != null) { - m_BufferStream.Seek(Entries[pos].Position, SeekOrigin.Begin); - // todo: consider refactoring out in its own function to accomodate - // other ways to (de)serialize - // Not using keepDirtyDelta anymore which is great. todo: remove and check for the overall effect on > 2 player - networkVariable.ReadDelta(m_BufferStream, false); + unsafe + { + // This avoids copies - using Allocator.None creates a direct memory view into the buffer. + fixed (byte* buffer = RecvBuffer) + { + var reader = new FastBufferReader(buffer, Collections.Allocator.None, RecvBuffer.Length); +#pragma warning disable CS0728 // Warns that reader may be reassigned within ReadDelta, but ReadDelta does not reassign it. + using (reader) + { + reader.Seek(Entries[pos].Position); + // todo: consider refactoring out in its own function to accomodate + // other ways to (de)serialize + // Not using keepDirtyDelta anymore which is great. todo: remove and check for the overall effect on > 2 player + networkVariable.ReadDelta(ref reader, false); + } +#pragma warning restore CS0728 // Warns that reader may be reassigned within ReadDelta, but ReadDelta does not reassign it. + } + } } } } } - internal void ReadSpawns(NetworkReader reader) + internal void ReadSpawns(in SnapshotDataMessage message) { SnapshotSpawnCommand spawnCommand; SnapshotDespawnCommand despawnCommand; - short spawnCount = reader.ReadInt16(); - short despawnCount = reader.ReadInt16(); - - for (var i = 0; i < spawnCount; i++) + for (var i = 0; i < message.Spawns.Length; i++) { - spawnCommand = ReadSpawn(reader); + spawnCommand = ReadSpawn(message.Spawns[i]); if (TickAppliedSpawn.ContainsKey(spawnCommand.NetworkObjectId) && spawnCommand.TickWritten <= TickAppliedSpawn[spawnCommand.NetworkObjectId]) @@ -450,17 +442,17 @@ internal void ReadSpawns(NetworkReader reader) if (spawnCommand.ParentNetworkId == spawnCommand.NetworkObjectId) { var networkObject = NetworkManager.SpawnManager.CreateLocalNetworkObject(false, spawnCommand.GlobalObjectIdHash, spawnCommand.OwnerClientId, null, spawnCommand.ObjectPosition, spawnCommand.ObjectRotation); - NetworkManager.SpawnManager.SpawnNetworkObjectLocally(networkObject, spawnCommand.NetworkObjectId, true, spawnCommand.IsPlayerObject, spawnCommand.OwnerClientId, null, false, false); + NetworkManager.SpawnManager.SpawnNetworkObjectLocally(networkObject, spawnCommand.NetworkObjectId, true, spawnCommand.IsPlayerObject, spawnCommand.OwnerClientId, false); } else { var networkObject = NetworkManager.SpawnManager.CreateLocalNetworkObject(false, spawnCommand.GlobalObjectIdHash, spawnCommand.OwnerClientId, spawnCommand.ParentNetworkId, spawnCommand.ObjectPosition, spawnCommand.ObjectRotation); - NetworkManager.SpawnManager.SpawnNetworkObjectLocally(networkObject, spawnCommand.NetworkObjectId, true, spawnCommand.IsPlayerObject, spawnCommand.OwnerClientId, null, false, false); + NetworkManager.SpawnManager.SpawnNetworkObjectLocally(networkObject, spawnCommand.NetworkObjectId, true, spawnCommand.IsPlayerObject, spawnCommand.OwnerClientId, false); } } - for (var i = 0; i < despawnCount; i++) + for (var i = 0; i < message.Despawns.Length; i++) { - despawnCommand = ReadDespawn(reader); + despawnCommand = ReadDespawn(message.Despawns[i]); if (TickAppliedDespawn.ContainsKey(despawnCommand.NetworkObjectId) && despawnCommand.TickWritten <= TickAppliedDespawn[despawnCommand.NetworkObjectId]) @@ -479,10 +471,10 @@ internal void ReadSpawns(NetworkReader reader) } } - internal void ReadAcks(ulong clientId, ClientData clientData, NetworkReader reader, ConnectionRtt connection) + internal void ReadAcks(ulong clientId, ClientData clientData, in SnapshotDataMessage message, ConnectionRtt connection) { - ushort ackSequence = reader.ReadUInt16(); - ushort seqMask = reader.ReadUInt16(); + ushort ackSequence = message.Ack.LastReceivedSequence; + ushort seqMask = message.Ack.ReceivedSequenceMask; // process the latest acknowledgment ProcessSingleAck(ackSequence, clientId, clientData, connection); @@ -727,34 +719,38 @@ private void SendSnapshot(ulong clientId) m_ConnectionRtts[clientId].NotifySend(m_ClientData[clientId].SequenceNumber, Time.unscaledTime); - var context = m_NetworkManager.MessageQueueContainer.EnterInternalCommandContext(MessageQueueContainer.MessageType.SnapshotData, NetworkDelivery.Unreliable, new[] { clientId }, NetworkUpdateLoop.UpdateStage); - if (context != null) + var sequence = m_ClientData[clientId].SequenceNumber; + var message = new SnapshotDataMessage { - using var nonNullContext = (InternalCommandContext)context; - var sequence = m_ClientData[clientId].SequenceNumber; - - // write the tick and sequence header - nonNullContext.NetworkWriter.WriteInt32Packed(m_CurrentTick); - nonNullContext.NetworkWriter.WriteUInt16(sequence); - - var buffer = (NetworkBuffer)nonNullContext.NetworkWriter.GetStream(); - - using var writer = PooledNetworkWriter.Get(buffer); - // write the snapshot: buffer, index, spawns, despawns - writer.WriteUInt16(SentinelBefore); - WriteBuffer(buffer); - WriteIndex(buffer); - WriteAcks(buffer, clientId); - WriteSpawns(buffer, clientId); - writer.WriteUInt16(SentinelAfter); - - m_ClientData[clientId].LastReceivedSequence = 0; - - // todo: this is incorrect (well, sub-optimal) - // we should still continue ack'ing past messages, in case this one is dropped - m_ClientData[clientId].ReceivedSequenceMask = 0; - m_ClientData[clientId].SequenceNumber++; - } + CurrentTick = m_CurrentTick, + Sequence = sequence, + Range = (ushort)m_Snapshot.Allocator.Range, + + // todo --M1-- + // this sends the whole buffer + // we'll need to build a per-client list + SendMainBuffer = m_Snapshot.MainBuffer, + + Ack = new SnapshotDataMessage.AckData + { + LastReceivedSequence = m_ClientData[clientId].LastReceivedSequence, + ReceivedSequenceMask = m_ClientData[clientId].ReceivedSequenceMask + } + }; + + + // write the snapshot: buffer, index, spawns, despawns + WriteIndex(ref message); + WriteSpawns(ref message, clientId); + + m_NetworkManager.SendMessage(message, NetworkDelivery.Unreliable, clientId); + + m_ClientData[clientId].LastReceivedSequence = 0; + + // todo: this is incorrect (well, sub-optimal) + // we should still continue ack'ing past messages, in case this one is dropped + m_ClientData[clientId].ReceivedSequenceMask = 0; + m_ClientData[clientId].SequenceNumber++; } // Checks if a given SpawnCommand should be written to a Snapshot Message @@ -789,7 +785,7 @@ private bool ShouldWriteDespawn(in SnapshotDespawnCommand despawnCommand) return (1 << diff) > (despawnCommand.TimesWritten - 1); } - private void WriteSpawns(NetworkBuffer buffer, ulong clientId) + private void WriteSpawns(ref SnapshotDataMessage message, ulong clientId) { var spawnWritten = 0; var despawnWritten = 0; @@ -816,35 +812,31 @@ private void WriteSpawns(NetworkBuffer buffer, ulong clientId) clientData.NextDespawnIndex = 0; } - using var writer = PooledNetworkWriter.Get(buffer); - var positionSpawns = writer.GetStream().Position; - writer.WriteInt16((short)m_Snapshot.NumSpawns); - var positionDespawns = writer.GetStream().Position; - writer.WriteInt16((short)m_Snapshot.NumDespawns); + message.Spawns = new NativeArray(m_Snapshot.NumSpawns, Allocator.TempJob); + message.Despawns = new NativeArray(m_Snapshot.NumDespawns, Allocator.TempJob); + var spawnUsage = 0; for (var j = 0; j < m_Snapshot.NumSpawns && !overSize; j++) { var index = clientData.NextSpawnIndex; - var savedPosition = writer.GetStream().Position; // todo: re-enable ShouldWriteSpawn, once we have a mechanism to not let despawn pass in front of spawns if (m_Snapshot.Spawns[index].TargetClientIds.Contains(clientId) /*&& ShouldWriteSpawn(m_Snapshot.Spawns[index])*/) { - var sentSpawn = m_Snapshot.WriteSpawn(clientData, writer, in m_Snapshot.Spawns[index]); + spawnUsage += FastBufferWriter.GetWriteSize(); // limit spawn sizes, compare current pos to very first position we wrote to - if (writer.GetStream().Position - positionSpawns > m_NetworkManager.NetworkConfig.SnapshotMaxSpawnUsage) + if (spawnUsage > m_NetworkManager.NetworkConfig.SnapshotMaxSpawnUsage) { overSize = true; - // revert back the position to undo the write - writer.GetStream().Position = savedPosition; - } - else - { - m_Snapshot.Spawns[index].TimesWritten++; - clientData.SentSpawns.Add(sentSpawn); - spawnWritten++; + break; } + var sentSpawn = m_Snapshot.GetSpawnData(clientData, in m_Snapshot.Spawns[index], out var spawn); + message.Spawns[j] = spawn; + + m_Snapshot.Spawns[index].TimesWritten++; + clientData.SentSpawns.Add(sentSpawn); + spawnWritten++; } clientData.NextSpawnIndex = (clientData.NextSpawnIndex + 1) % m_Snapshot.NumSpawns; } @@ -860,81 +852,51 @@ private void WriteSpawns(NetworkBuffer buffer, ulong clientId) for (var j = 0; j < m_Snapshot.NumDespawns && !overSize; j++) { var index = clientData.NextDespawnIndex; - var savedPosition = writer.GetStream().Position; // todo: re-enable ShouldWriteSpawn, once we have a mechanism to not let despawn pass in front of spawns if (m_Snapshot.Despawns[index].TargetClientIds.Contains(clientId) /*&& ShouldWriteDespawn(m_Snapshot.Despawns[index])*/) { - var sentDespawn = m_Snapshot.WriteDespawn(clientData, writer, in m_Snapshot.Despawns[index]); + spawnUsage += FastBufferWriter.GetWriteSize(); // limit spawn sizes, compare current pos to very first position we wrote to - if (writer.GetStream().Position - positionSpawns > m_NetworkManager.NetworkConfig.SnapshotMaxSpawnUsage) + if (spawnUsage > m_NetworkManager.NetworkConfig.SnapshotMaxSpawnUsage) { overSize = true; - // revert back the position to undo the write - writer.GetStream().Position = savedPosition; - } - else - { - m_Snapshot.Despawns[index].TimesWritten++; - clientData.SentSpawns.Add(sentDespawn); - despawnWritten++; + break; } + var sentDespawn = m_Snapshot.GetDespawnData(clientData, in m_Snapshot.Despawns[index], out var despawn); + message.Despawns[j] = despawn; + m_Snapshot.Despawns[index].TimesWritten++; + clientData.SentSpawns.Add(sentDespawn); + despawnWritten++; } clientData.NextDespawnIndex = (clientData.NextDespawnIndex + 1) % m_Snapshot.NumDespawns; } - - long positionAfter = 0; - - positionAfter = writer.GetStream().Position; - writer.GetStream().Position = positionSpawns; - writer.WriteInt16((short)spawnWritten); - writer.GetStream().Position = positionAfter; - - positionAfter = writer.GetStream().Position; - writer.GetStream().Position = positionDespawns; - writer.WriteInt16((short)despawnWritten); - writer.GetStream().Position = positionAfter; - } - - private void WriteAcks(NetworkBuffer buffer, ulong clientId) - { - using var writer = PooledNetworkWriter.Get(buffer); - // todo: revisit whether 16-bit is enough for LastReceivedSequence - writer.WriteUInt16(m_ClientData[clientId].LastReceivedSequence); - writer.WriteUInt16(m_ClientData[clientId].ReceivedSequenceMask); } /// /// Write the snapshot index to a buffer /// - /// The buffer to write the index to - private void WriteIndex(NetworkBuffer buffer) + /// The message to write the index to + private void WriteIndex(ref SnapshotDataMessage message) { - using var writer = PooledNetworkWriter.Get(buffer); - writer.WriteInt16((short)m_Snapshot.LastEntry); + message.Entries = new NativeArray(m_Snapshot.LastEntry, Allocator.TempJob); for (var i = 0; i < m_Snapshot.LastEntry; i++) { - m_Snapshot.WriteEntry(writer, in m_Snapshot.Entries[i]); + var entryMeta = m_Snapshot.Entries[i]; + var entry = entryMeta.Key; + message.Entries[i] = new SnapshotDataMessage.EntryData + { + NetworkObjectId = entry.NetworkObjectId, + BehaviourIndex = entry.BehaviourIndex, + VariableIndex = entry.VariableIndex, + TickWritten = entry.TickWritten, + Position = entryMeta.Position, + Length = entryMeta.Length + }; } } - /// - /// Write the buffer of a snapshot - /// Must match ReadBuffer - /// - /// The NetworkBuffer to write our buffer of variables to - private void WriteBuffer(NetworkBuffer buffer) - { - using var writer = PooledNetworkWriter.Get(buffer); - writer.WriteUInt16((ushort)m_Snapshot.Allocator.Range); - - // todo --M1-- - // this sends the whole buffer - // we'll need to build a per-client list - buffer.Write(m_Snapshot.MainBuffer, 0, m_Snapshot.Allocator.Range); - } - internal void Spawn(SnapshotSpawnCommand command) { command.TickWritten = m_CurrentTick; @@ -976,19 +938,26 @@ internal void Store(ulong networkObjectId, int behaviourIndex, int variableIndex WriteVariableToSnapshot(m_Snapshot, networkVariable, pos); } - private void WriteVariableToSnapshot(Snapshot snapshot, NetworkVariableBase networkVariable, int index) + private unsafe void WriteVariableToSnapshot(Snapshot snapshot, NetworkVariableBase networkVariable, int index) { // write var into buffer, possibly adjusting entry's position and Length - using var varBuffer = PooledNetworkBuffer.Get(); - networkVariable.WriteDelta(varBuffer); - if (varBuffer.Length > snapshot.Entries[index].Length) + var varBuffer = new FastBufferWriter(1300, Allocator.Temp); +#pragma warning disable CS0728 // Warns that varBuffer may be reassigned within ReadDelta, but ReadDelta does not reassign it. + using (varBuffer) { - // allocate this Entry's buffer - snapshot.AllocateEntry(ref snapshot.Entries[index], index, (int)varBuffer.Length); - } + networkVariable.WriteDelta(ref varBuffer); + if (varBuffer.Length > snapshot.Entries[index].Length) + { + // allocate this Entry's buffer + snapshot.AllocateEntry(ref snapshot.Entries[index], index, (int)varBuffer.Length); + } - // Copy the serialized NetworkVariable into our buffer - Buffer.BlockCopy(varBuffer.GetBuffer(), 0, snapshot.MainBuffer, snapshot.Entries[index].Position, (int)varBuffer.Length); + fixed (byte* buffer = snapshot.MainBuffer) + { + UnsafeUtility.MemCpy(buffer + snapshot.Entries[index].Position, varBuffer.GetUnsafePtr(), varBuffer.Length); + } + } +#pragma warning restore CS0728 // Warns that varBuffer may be reassigned within ReadDelta, but ReadDelta does not reassign it. } @@ -996,33 +965,23 @@ private void WriteVariableToSnapshot(Snapshot snapshot, NetworkVariableBase netw /// Entry point when a Snapshot is received /// This is where we read and store the received snapshot /// - /// - /// The stream to read from - internal void ReadSnapshot(ulong clientId, Stream snapshotStream) + /// + /// The message to read from + internal void HandleSnapshot(ulong clientId, in SnapshotDataMessage message) { - // todo: temporary hack around bug - if (!m_NetworkManager.IsServer) - { - clientId = m_NetworkManager.ServerClientId; - } - - using var reader = PooledNetworkReader.Get(snapshotStream); // make sure we have a ClientData entry for each client if (!m_ClientData.ContainsKey(clientId)) { m_ClientData.Add(clientId, new ClientData()); } - var snapshotTick = reader.ReadInt32Packed(); - var sequence = reader.ReadUInt16(); - - if (sequence >= m_ClientData[clientId].LastReceivedSequence) + if (message.Sequence >= m_ClientData[clientId].LastReceivedSequence) { if (m_ClientData[clientId].ReceivedSequenceMask != 0) { // since each bit in ReceivedSequenceMask is relative to the last received sequence // we need to shift all the bits by the difference in sequence - var shift = sequence - m_ClientData[clientId].LastReceivedSequence; + var shift = message.Sequence - m_ClientData[clientId].LastReceivedSequence; if (shift < sizeof(ushort) * 8) { m_ClientData[clientId].ReceivedSequenceMask <<= shift; @@ -1037,14 +996,14 @@ internal void ReadSnapshot(ulong clientId, Stream snapshotStream) { // because the bit we're adding for the previous ReceivedSequenceMask // was implicit, it needs to be shift by one less - var shift = sequence - 1 - m_ClientData[clientId].LastReceivedSequence; + var shift = message.Sequence - 1 - m_ClientData[clientId].LastReceivedSequence; if (shift < sizeof(ushort) * 8) { m_ClientData[clientId].ReceivedSequenceMask |= (ushort)(1 << shift); } } - m_ClientData[clientId].LastReceivedSequence = sequence; + m_ClientData[clientId].LastReceivedSequence = message.Sequence; } else { @@ -1055,22 +1014,10 @@ internal void ReadSnapshot(ulong clientId, Stream snapshotStream) // without this, we incur extra retransmit, not a catastrophic failure } - var sentinel = reader.ReadUInt16(); - if (sentinel != SentinelBefore) - { - Debug.Log("Critical : snapshot integrity (before)"); - } - - m_Snapshot.ReadBuffer(reader, snapshotStream); - m_Snapshot.ReadIndex(reader); - m_Snapshot.ReadAcks(clientId, m_ClientData[clientId], reader, GetConnectionRtt(clientId)); - m_Snapshot.ReadSpawns(reader); - - sentinel = reader.ReadUInt16(); - if (sentinel != SentinelAfter) - { - Debug.Log("Critical : snapshot integrity (after)"); - } + m_Snapshot.ReadBuffer(message); + m_Snapshot.ReadIndex(message); + m_Snapshot.ReadAcks(clientId, m_ClientData[clientId], message, GetConnectionRtt(clientId)); + m_Snapshot.ReadSpawns(message); } // todo --M1-- diff --git a/com.unity.netcode.gameobjects/Runtime/Logging/NetworkLog.cs b/com.unity.netcode.gameobjects/Runtime/Logging/NetworkLog.cs index 09d829c474..a6149a494a 100644 --- a/com.unity.netcode.gameobjects/Runtime/Logging/NetworkLog.cs +++ b/com.unity.netcode.gameobjects/Runtime/Logging/NetworkLog.cs @@ -1,3 +1,4 @@ +using Unity.Netcode.Messages; using UnityEngine; namespace Unity.Netcode @@ -56,18 +57,16 @@ private static void LogServer(string message, LogType logType) if (NetworkManager.Singleton != null && !NetworkManager.Singleton.IsServer && NetworkManager.Singleton.NetworkConfig.EnableNetworkLogs) { - var context = NetworkManager.Singleton.MessageQueueContainer.EnterInternalCommandContext(MessageQueueContainer.MessageType.ServerLog, NetworkDelivery.ReliableSequenced, new[] { NetworkManager.Singleton.ServerClientId }, NetworkUpdateLoop.UpdateStage); - if (context != null) + + var networkMessage = new ServerLogMessage { - using var nonNullContext = (InternalCommandContext)context; - var bufferSizeCapture = new CommandContextSizeCapture(nonNullContext); - bufferSizeCapture.StartMeasureSegment(); - nonNullContext.NetworkWriter.WriteByte((byte)logType); - nonNullContext.NetworkWriter.WriteStringPacked(message); - var size = bufferSizeCapture.StopMeasureSegment(); + LogType = logType, + Message = message + }; + var size = NetworkManager.Singleton.SendMessage(networkMessage, NetworkDelivery.ReliableFragmentedSequenced, + NetworkManager.Singleton.ServerClientId); - NetworkManager.Singleton.NetworkMetrics.TrackServerLogSent(NetworkManager.Singleton.ServerClientId, (uint)logType, size); - } + NetworkManager.Singleton.NetworkMetrics.TrackServerLogSent(NetworkManager.Singleton.ServerClientId, (uint)logType, size); } } @@ -75,7 +74,7 @@ private static void LogServer(string message, LogType logType) internal static void LogWarningServerLocal(string message, ulong sender) => Debug.LogWarning($"[Netcode-Server Sender={sender}] {message}"); internal static void LogErrorServerLocal(string message, ulong sender) => Debug.LogError($"[Netcode-Server Sender={sender}] {message}"); - internal enum LogType + internal enum LogType : byte { Info, Warning, diff --git a/com.unity.netcode.gameobjects/Runtime/Messaging/BatchHeader.cs b/com.unity.netcode.gameobjects/Runtime/Messaging/BatchHeader.cs new file mode 100644 index 0000000000..68afbe2651 --- /dev/null +++ b/com.unity.netcode.gameobjects/Runtime/Messaging/BatchHeader.cs @@ -0,0 +1,7 @@ +namespace Unity.Netcode +{ + public struct BatchHeader + { + public ushort BatchSize; + } +} diff --git a/com.unity.netcode.gameobjects/Runtime/Messaging/InternalMessageHandler.cs.meta b/com.unity.netcode.gameobjects/Runtime/Messaging/BatchHeader.cs.meta similarity index 83% rename from com.unity.netcode.gameobjects/Runtime/Messaging/InternalMessageHandler.cs.meta rename to com.unity.netcode.gameobjects/Runtime/Messaging/BatchHeader.cs.meta index 69b0cf5836..b96a0b8213 100644 --- a/com.unity.netcode.gameobjects/Runtime/Messaging/InternalMessageHandler.cs.meta +++ b/com.unity.netcode.gameobjects/Runtime/Messaging/BatchHeader.cs.meta @@ -1,5 +1,5 @@ fileFormatVersion: 2 -guid: 1b0dde7481361454ab22f5fca90c4c24 +guid: 941fcfe2222f8734ab5bfb9bc4787717 MonoImporter: externalObjects: {} serializedVersion: 2 diff --git a/com.unity.netcode.gameobjects/Runtime/Messaging/Bind.cs b/com.unity.netcode.gameobjects/Runtime/Messaging/Bind.cs new file mode 100644 index 0000000000..f5bb5e1364 --- /dev/null +++ b/com.unity.netcode.gameobjects/Runtime/Messaging/Bind.cs @@ -0,0 +1,14 @@ +using System; + +namespace Unity.Netcode +{ + public class Bind : Attribute + { + public Type BoundType; + + public Bind(Type boundType) + { + BoundType = boundType; + } + } +} diff --git a/com.unity.netcode.gameobjects/Runtime/Messaging/MessageBatcher.cs.meta b/com.unity.netcode.gameobjects/Runtime/Messaging/Bind.cs.meta similarity index 83% rename from com.unity.netcode.gameobjects/Runtime/Messaging/MessageBatcher.cs.meta rename to com.unity.netcode.gameobjects/Runtime/Messaging/Bind.cs.meta index 7ce284cfef..76e8e8e1a8 100644 --- a/com.unity.netcode.gameobjects/Runtime/Messaging/MessageBatcher.cs.meta +++ b/com.unity.netcode.gameobjects/Runtime/Messaging/Bind.cs.meta @@ -1,5 +1,5 @@ fileFormatVersion: 2 -guid: d17c5017b93fd444ba31f8824025957c +guid: 9697b6526afcb8a429e65b78d4453e90 MonoImporter: externalObjects: {} serializedVersion: 2 diff --git a/com.unity.netcode.gameobjects/Runtime/Messaging/CustomMessageManager.cs b/com.unity.netcode.gameobjects/Runtime/Messaging/CustomMessageManager.cs index 2a7b0e1314..6aa0a20d86 100644 --- a/com.unity.netcode.gameobjects/Runtime/Messaging/CustomMessageManager.cs +++ b/com.unity.netcode.gameobjects/Runtime/Messaging/CustomMessageManager.cs @@ -1,6 +1,6 @@ using System; using System.Collections.Generic; -using System.IO; +using Unity.Netcode.Messages; namespace Unity.Netcode { @@ -21,18 +21,27 @@ internal CustomMessagingManager(NetworkManager networkManager) /// Delegate used for incoming unnamed messages /// /// The clientId that sent the message - /// The stream containing the message data - public delegate void UnnamedMessageDelegate(ulong clientId, Stream stream); + /// The stream containing the message data + public delegate void UnnamedMessageDelegate(ulong clientId, ref FastBufferReader reader); /// /// Event invoked when unnamed messages arrive /// public event UnnamedMessageDelegate OnUnnamedMessage; - internal void InvokeUnnamedMessage(ulong clientId, Stream stream) + internal void InvokeUnnamedMessage(ulong clientId, ref FastBufferReader reader) { - OnUnnamedMessage?.Invoke(clientId, stream); - m_NetworkManager.NetworkMetrics.TrackUnnamedMessageReceived(clientId, stream.SafeGetLengthOrDefault()); + if (OnUnnamedMessage != null) + { + var pos = reader.Position; + var delegates = OnUnnamedMessage.GetInvocationList(); + foreach (var handler in delegates) + { + reader.Seek(pos); + ((UnnamedMessageDelegate)handler).Invoke(clientId, ref reader); + } + } + m_NetworkManager.NetworkMetrics.TrackUnnamedMessageReceived(clientId, reader.Length); } /// @@ -41,22 +50,20 @@ internal void InvokeUnnamedMessage(ulong clientId, Stream stream) /// The clients to send to, sends to everyone if null /// The message stream containing the data /// The delivery type (QoS) to send data with - public void SendUnnamedMessage(List clientIds, NetworkBuffer messageBuffer, NetworkDelivery networkDelivery = NetworkDelivery.ReliableSequenced) + public void SendUnnamedMessage(List clientIds, ref FastBufferWriter messageBuffer, NetworkDelivery networkDelivery = NetworkDelivery.ReliableSequenced) { if (!m_NetworkManager.IsServer) { throw new InvalidOperationException("Can not send unnamed messages to multiple users as a client"); } - var context = m_NetworkManager.MessageQueueContainer.EnterInternalCommandContext(MessageQueueContainer.MessageType.UnnamedMessage, networkDelivery, clientIds.ToArray(), NetworkUpdateLoop.UpdateStage); - if (context != null) + var message = new UnnamedMessage { - using var nonNullContext = (InternalCommandContext)context; - messageBuffer.Position = 0; - messageBuffer.CopyTo(nonNullContext.NetworkWriter.GetStream()); - } + Data = messageBuffer + }; + var size = m_NetworkManager.SendMessage(message, networkDelivery, clientIds); - m_NetworkManager.NetworkMetrics.TrackUnnamedMessageSent(clientIds, messageBuffer.Length); + m_NetworkManager.NetworkMetrics.TrackUnnamedMessageSent(clientIds, size); } /// @@ -65,22 +72,20 @@ public void SendUnnamedMessage(List clientIds, NetworkBuffer messageBuffe /// The client to send the message to /// The message stream containing the data /// The delivery type (QoS) to send data with - public void SendUnnamedMessage(ulong clientId, NetworkBuffer messageBuffer, NetworkDelivery networkDelivery = NetworkDelivery.ReliableSequenced) + public void SendUnnamedMessage(ulong clientId, ref FastBufferWriter messageBuffer, NetworkDelivery networkDelivery = NetworkDelivery.ReliableSequenced) { - var context = m_NetworkManager.MessageQueueContainer.EnterInternalCommandContext(MessageQueueContainer.MessageType.UnnamedMessage, networkDelivery, new[] { clientId }, NetworkUpdateLoop.UpdateStage); - if (context != null) + var message = new UnnamedMessage { - using var nonNullContext = (InternalCommandContext)context; - m_NetworkManager.NetworkMetrics.TrackUnnamedMessageSent(clientId, messageBuffer.Position); - messageBuffer.Position = 0; - messageBuffer.CopyTo(nonNullContext.NetworkWriter.GetStream()); - } + Data = messageBuffer + }; + var size = m_NetworkManager.SendMessage(message, networkDelivery, clientId); + m_NetworkManager.NetworkMetrics.TrackUnnamedMessageSent(clientId, size); } /// /// Delegate used to handle named messages /// - public delegate void HandleNamedMessageDelegate(ulong senderClientId, Stream messagePayload); + public delegate void HandleNamedMessageDelegate(ulong senderClientId, ref FastBufferReader messagePayload); private Dictionary m_NamedMessageHandlers32 = new Dictionary(); private Dictionary m_NamedMessageHandlers64 = new Dictionary(); @@ -88,22 +93,22 @@ public void SendUnnamedMessage(ulong clientId, NetworkBuffer messageBuffer, Netw private Dictionary m_MessageHandlerNameLookup32 = new Dictionary(); private Dictionary m_MessageHandlerNameLookup64 = new Dictionary(); - internal void InvokeNamedMessage(ulong hash, ulong sender, Stream stream) + internal void InvokeNamedMessage(ulong hash, ulong sender, ref FastBufferReader reader) { - var bytesCount = stream.SafeGetLengthOrDefault(); + var bytesCount = reader.Length; if (m_NetworkManager == null) { // We dont know what size to use. Try every (more collision prone) if (m_NamedMessageHandlers32.TryGetValue(hash, out HandleNamedMessageDelegate messageHandler32)) { - messageHandler32(sender, stream); + messageHandler32(sender, ref reader); m_NetworkManager.NetworkMetrics.TrackNamedMessageReceived(sender, m_MessageHandlerNameLookup32[hash], bytesCount); } if (m_NamedMessageHandlers64.TryGetValue(hash, out HandleNamedMessageDelegate messageHandler64)) { - messageHandler64(sender, stream); + messageHandler64(sender, ref reader); m_NetworkManager.NetworkMetrics.TrackNamedMessageReceived(sender, m_MessageHandlerNameLookup64[hash], bytesCount); } } @@ -115,14 +120,14 @@ internal void InvokeNamedMessage(ulong hash, ulong sender, Stream stream) case HashSize.VarIntFourBytes: if (m_NamedMessageHandlers32.TryGetValue(hash, out HandleNamedMessageDelegate messageHandler32)) { - messageHandler32(sender, stream); + messageHandler32(sender, ref reader); m_NetworkManager.NetworkMetrics.TrackNamedMessageReceived(sender, m_MessageHandlerNameLookup32[hash], bytesCount); } break; case HashSize.VarIntEightBytes: if (m_NamedMessageHandlers64.TryGetValue(hash, out HandleNamedMessageDelegate messageHandler64)) { - messageHandler64(sender, stream); + messageHandler64(sender, ref reader); m_NetworkManager.NetworkMetrics.TrackNamedMessageReceived(sender, m_MessageHandlerNameLookup64[hash], bytesCount); } break; @@ -170,7 +175,7 @@ public void UnregisterNamedMessageHandler(string name) /// The client to send the message to /// The message stream containing the data /// The delivery type (QoS) to send data with - public void SendNamedMessage(string messageName, ulong clientId, Stream messageStream, NetworkDelivery networkDelivery = NetworkDelivery.ReliableSequenced) + public void SendNamedMessage(string messageName, ulong clientId, ref FastBufferWriter messageStream, NetworkDelivery networkDelivery = NetworkDelivery.ReliableSequenced) { ulong hash = 0; switch (m_NetworkManager.NetworkConfig.RpcHashSize) @@ -183,22 +188,13 @@ public void SendNamedMessage(string messageName, ulong clientId, Stream messageS break; } - var context = m_NetworkManager.MessageQueueContainer.EnterInternalCommandContext(MessageQueueContainer.MessageType.NamedMessage, networkDelivery, new[] { clientId }, NetworkUpdateLoop.UpdateStage); - if (context != null) + var message = new NamedMessage { - using var nonNullContext = (InternalCommandContext)context; - var bufferSizeCapture = new CommandContextSizeCapture(nonNullContext); - bufferSizeCapture.StartMeasureSegment(); - - nonNullContext.NetworkWriter.WriteUInt64Packed(hash); - - messageStream.Position = 0; - messageStream.CopyTo(nonNullContext.NetworkWriter.GetStream()); - - var size = bufferSizeCapture.StopMeasureSegment(); - - m_NetworkManager.NetworkMetrics.TrackNamedMessageSent(clientId, messageName, size); - } + Hash = hash, + Data = messageStream + }; + var size = m_NetworkManager.SendMessage(message, networkDelivery, clientId); + m_NetworkManager.NetworkMetrics.TrackNamedMessageSent(clientId, messageName, size); } /// @@ -208,7 +204,7 @@ public void SendNamedMessage(string messageName, ulong clientId, Stream messageS /// The clients to send to, sends to everyone if null /// The message stream containing the data /// The delivery type (QoS) to send data with - public void SendNamedMessage(string messageName, List clientIds, Stream messageStream, NetworkDelivery networkDelivery = NetworkDelivery.ReliableSequenced) + public void SendNamedMessage(string messageName, List clientIds, ref FastBufferWriter messageStream, NetworkDelivery networkDelivery = NetworkDelivery.ReliableSequenced) { if (!m_NetworkManager.IsServer) { @@ -225,24 +221,13 @@ public void SendNamedMessage(string messageName, List clientIds, Stream m hash = XXHash.Hash64(messageName); break; } - - var context = m_NetworkManager.MessageQueueContainer.EnterInternalCommandContext( - MessageQueueContainer.MessageType.NamedMessage, networkDelivery, - clientIds.ToArray(), NetworkUpdateLoop.UpdateStage); - if (context != null) + var message = new NamedMessage { - using var nonNullContext = (InternalCommandContext)context; - var bufferSizeCapture = new CommandContextSizeCapture(nonNullContext); - bufferSizeCapture.StartMeasureSegment(); - - nonNullContext.NetworkWriter.WriteUInt64Packed(hash); - - messageStream.Position = 0; - messageStream.CopyTo(nonNullContext.NetworkWriter.GetStream()); - - var size = bufferSizeCapture.StopMeasureSegment(); - m_NetworkManager.NetworkMetrics.TrackNamedMessageSent(clientIds, messageName, size); - } + Hash = hash, + Data = messageStream + }; + var size = m_NetworkManager.SendMessage(message, networkDelivery, clientIds); + m_NetworkManager.NetworkMetrics.TrackNamedMessageSent(clientIds, messageName, size); } } } diff --git a/com.unity.netcode.gameobjects/Runtime/Messaging/IInternalMessageHandler.cs b/com.unity.netcode.gameobjects/Runtime/Messaging/IInternalMessageHandler.cs deleted file mode 100644 index b90bf907f0..0000000000 --- a/com.unity.netcode.gameobjects/Runtime/Messaging/IInternalMessageHandler.cs +++ /dev/null @@ -1,22 +0,0 @@ -using System.IO; - -namespace Unity.Netcode -{ - internal interface IInternalMessageHandler - { - NetworkManager NetworkManager { get; } - void HandleConnectionRequest(ulong clientId, Stream stream); - void HandleConnectionApproved(ulong clientId, Stream stream, float receiveTime); - void HandleAddObject(ulong clientId, Stream stream); - void HandleDestroyObject(ulong clientId, Stream stream); - void HandleSceneEvent(ulong clientId, Stream stream); - void HandleChangeOwner(ulong clientId, Stream stream); - void HandleTimeSync(ulong clientId, Stream stream); - void HandleNetworkVariableDelta(ulong clientId, Stream stream); - void MessageReceiveQueueItem(ulong clientId, Stream stream, float receiveTime, MessageQueueContainer.MessageType messageType); - void HandleUnnamedMessage(ulong clientId, Stream stream); - void HandleNamedMessage(ulong clientId, Stream stream); - void HandleNetworkLog(ulong clientId, Stream stream); - void HandleSnapshot(ulong clientId, Stream messageStream); - } -} diff --git a/com.unity.netcode.gameobjects/Runtime/Messaging/IInternalMessageHandler.cs.meta b/com.unity.netcode.gameobjects/Runtime/Messaging/IInternalMessageHandler.cs.meta deleted file mode 100644 index 2efca3c244..0000000000 --- a/com.unity.netcode.gameobjects/Runtime/Messaging/IInternalMessageHandler.cs.meta +++ /dev/null @@ -1,3 +0,0 @@ -fileFormatVersion: 2 -guid: 55d4eb01eae74f4bb603dc1d11897d48 -timeCreated: 1617912807 \ No newline at end of file diff --git a/com.unity.netcode.gameobjects/Runtime/Messaging/IMessageSender.cs b/com.unity.netcode.gameobjects/Runtime/Messaging/IMessageSender.cs new file mode 100644 index 0000000000..c265b29f1d --- /dev/null +++ b/com.unity.netcode.gameobjects/Runtime/Messaging/IMessageSender.cs @@ -0,0 +1,7 @@ +namespace Unity.Netcode +{ + public interface IMessageSender + { + void Send(ulong clientId, NetworkDelivery delivery, ref FastBufferWriter batchData); + } +} diff --git a/com.unity.netcode.gameobjects/Runtime/Messaging/MessageQueue/MessageFrameItem.cs.meta b/com.unity.netcode.gameobjects/Runtime/Messaging/IMessageSender.cs.meta similarity index 83% rename from com.unity.netcode.gameobjects/Runtime/Messaging/MessageQueue/MessageFrameItem.cs.meta rename to com.unity.netcode.gameobjects/Runtime/Messaging/IMessageSender.cs.meta index c23ed27901..56d9cc9435 100644 --- a/com.unity.netcode.gameobjects/Runtime/Messaging/MessageQueue/MessageFrameItem.cs.meta +++ b/com.unity.netcode.gameobjects/Runtime/Messaging/IMessageSender.cs.meta @@ -1,5 +1,5 @@ fileFormatVersion: 2 -guid: dd8d54fea7e319940b47c30a4aadff15 +guid: 15b54cd88eba22648ade4240523b8c65 MonoImporter: externalObjects: {} serializedVersion: 2 diff --git a/com.unity.netcode.gameobjects/Runtime/Messaging/INetworkHooks.cs b/com.unity.netcode.gameobjects/Runtime/Messaging/INetworkHooks.cs new file mode 100644 index 0000000000..1464bec065 --- /dev/null +++ b/com.unity.netcode.gameobjects/Runtime/Messaging/INetworkHooks.cs @@ -0,0 +1,20 @@ +using System; + +namespace Unity.Netcode +{ + public interface INetworkHooks + { + void OnBeforeSendMessage(ulong clientId, Type messageType, NetworkDelivery delivery); + void OnAfterSendMessage(ulong clientId, Type messageType, NetworkDelivery delivery, int messageSizeBytes); + void OnBeforeReceiveMessage(ulong senderId, Type messageType, int messageSizeBytes); + void OnAfterReceiveMessage(ulong senderId, Type messageType, int messageSizeBytes); + void OnBeforeSendBatch(ulong clientId, int messageCount, int batchSizeInBytes, NetworkDelivery delivery); + void OnAfterSendBatch(ulong clientId, int messageCount, int batchSizeInBytes, NetworkDelivery delivery); + void OnBeforeReceiveBatch(ulong senderId, int messageCount, int batchSizeInBytes); + void OnAfterReceiveBatch(ulong senderId, int messageCount, int batchSizeInBytes); + + + bool OnVerifyCanSend(ulong destinationId, Type messageType, NetworkDelivery delivery); + bool OnVerifyCanReceive(ulong senderId, Type messageType); + } +} diff --git a/com.unity.netcode.gameobjects/Runtime/Messaging/InternalCommandContext.cs.meta b/com.unity.netcode.gameobjects/Runtime/Messaging/INetworkHooks.cs.meta similarity index 83% rename from com.unity.netcode.gameobjects/Runtime/Messaging/InternalCommandContext.cs.meta rename to com.unity.netcode.gameobjects/Runtime/Messaging/INetworkHooks.cs.meta index 0f738540a2..cc2de4ab71 100644 --- a/com.unity.netcode.gameobjects/Runtime/Messaging/InternalCommandContext.cs.meta +++ b/com.unity.netcode.gameobjects/Runtime/Messaging/INetworkHooks.cs.meta @@ -1,5 +1,5 @@ fileFormatVersion: 2 -guid: d855a86964512f147b1766c70f027504 +guid: b199c5a160beabb47bd6b0e4f06cfcd2 MonoImporter: externalObjects: {} serializedVersion: 2 diff --git a/com.unity.netcode.gameobjects/Runtime/Messaging/INetworkMessage.cs b/com.unity.netcode.gameobjects/Runtime/Messaging/INetworkMessage.cs new file mode 100644 index 0000000000..d6f2f11fcd --- /dev/null +++ b/com.unity.netcode.gameobjects/Runtime/Messaging/INetworkMessage.cs @@ -0,0 +1,7 @@ +namespace Unity.Netcode +{ + public interface INetworkMessage + { + void Serialize(ref FastBufferWriter writer); + } +} diff --git a/com.unity.netcode.gameobjects/Runtime/Messaging/INetworkMessage.cs.meta b/com.unity.netcode.gameobjects/Runtime/Messaging/INetworkMessage.cs.meta new file mode 100644 index 0000000000..d6c9a32145 --- /dev/null +++ b/com.unity.netcode.gameobjects/Runtime/Messaging/INetworkMessage.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 16079de8d8821a24c91db930bc892b5d +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/com.unity.netcode.gameobjects/Runtime/Messaging/InternalCommandContext.cs b/com.unity.netcode.gameobjects/Runtime/Messaging/InternalCommandContext.cs deleted file mode 100644 index 79a38489ee..0000000000 --- a/com.unity.netcode.gameobjects/Runtime/Messaging/InternalCommandContext.cs +++ /dev/null @@ -1,45 +0,0 @@ -using System; -using System.Linq; - -namespace Unity.Netcode -{ - /// - /// A context used for building an internal command. - /// - internal struct InternalCommandContext : IDisposable - { - public PooledNetworkWriter NetworkWriter; - - private ulong[] m_ClientIds; - private NetworkUpdateStage m_UpdateStage; - private MessageQueueContainer m_Owner; - - public InternalCommandContext(PooledNetworkWriter writer, ulong[] clientIds, NetworkUpdateStage updateStage, MessageQueueContainer owner) - { - NetworkWriter = writer; - m_ClientIds = clientIds; - m_UpdateStage = updateStage; - m_Owner = owner; - } - - public void Dispose() - { - Cleanup(); - } - - public void Cleanup() - { - if (m_Owner.NetworkManager.IsHost) - { - var containsServerClientId = m_ClientIds.Contains(m_Owner.NetworkManager.ServerClientId); - if (containsServerClientId && m_ClientIds.Length == 1) - { - m_Owner.EndAddQueueItemToFrame(NetworkWriter, MessageQueueHistoryFrame.QueueFrameType.Inbound, m_UpdateStage); - return; - } - } - - m_Owner.EndAddQueueItemToFrame(NetworkWriter, MessageQueueHistoryFrame.QueueFrameType.Outbound, NetworkUpdateStage.PostLateUpdate); - } - } -} diff --git a/com.unity.netcode.gameobjects/Runtime/Messaging/InternalMessageHandler.cs b/com.unity.netcode.gameobjects/Runtime/Messaging/InternalMessageHandler.cs deleted file mode 100644 index 1d61b31180..0000000000 --- a/com.unity.netcode.gameobjects/Runtime/Messaging/InternalMessageHandler.cs +++ /dev/null @@ -1,304 +0,0 @@ -using System.IO; -using UnityEngine; - - -namespace Unity.Netcode -{ - internal class InternalMessageHandler : IInternalMessageHandler - { - public NetworkManager NetworkManager => m_NetworkManager; - private NetworkManager m_NetworkManager; - - public InternalMessageHandler(NetworkManager networkManager) - { - m_NetworkManager = networkManager; - } - - public void HandleConnectionRequest(ulong clientId, Stream stream) - { - if (NetworkManager.PendingClients.TryGetValue(clientId, out PendingClient client)) - { - // Set to pending approval to prevent future connection requests from being approved - client.ConnectionState = PendingClient.State.PendingApproval; - } - - using var reader = PooledNetworkReader.Get(stream); - ulong configHash = reader.ReadUInt64Packed(); - if (!NetworkManager.NetworkConfig.CompareConfig(configHash)) - { - if (NetworkLog.CurrentLogLevel <= LogLevel.Normal) - { - NetworkLog.LogWarning($"{nameof(NetworkConfig)} mismatch. The configuration between the server and client does not match"); - } - - // Treat this similar to a client that is not approved (remove from pending and disconnect at transport layer) - NetworkManager.PendingClients.Remove(clientId); - NetworkManager.NetworkConfig.NetworkTransport.DisconnectRemoteClient(clientId); - return; - } - - if (NetworkManager.NetworkConfig.ConnectionApproval) - { - byte[] connectionBuffer = reader.ReadByteArray(); - NetworkManager.InvokeConnectionApproval(connectionBuffer, clientId, - (createPlayerObject, playerPrefabHash, approved, position, rotation) => - NetworkManager.HandleApproval(clientId, createPlayerObject, playerPrefabHash, approved, position, rotation)); - } - else - { - NetworkManager.HandleApproval(clientId, NetworkManager.NetworkConfig.PlayerPrefab != null, null, true, null, null); - } - } - - /// - /// Client Side: handles the connection approved message - /// - /// transport derived client identifier (currently not used) - /// incoming stream - /// time this message was received (currently not used) - public void HandleConnectionApproved(ulong clientId, Stream stream, float receiveTime) - { - using var reader = PooledNetworkReader.Get(stream); - NetworkManager.LocalClientId = reader.ReadUInt64Packed(); - - int tick = reader.ReadInt32Packed(); - var time = new NetworkTime(NetworkManager.NetworkTickSystem.TickRate, tick); - NetworkManager.NetworkTimeSystem.Reset(time.Time, 0.15f); // Start with a constant RTT of 150 until we receive values from the transport. - - NetworkManager.ConnectedClients.Add(NetworkManager.LocalClientId, new NetworkClient { ClientId = NetworkManager.LocalClientId }); - - // Only if scene management is disabled do we handle NetworkObject synchronization at this point - if (!NetworkManager.NetworkConfig.EnableSceneManagement) - { - NetworkManager.SpawnManager.DestroySceneObjects(); - - // is not packed! - var objectCount = reader.ReadUInt16(); - for (ushort i = 0; i < objectCount; i++) - { - NetworkObject.DeserializeSceneObject(reader.GetStream() as NetworkBuffer, reader, m_NetworkManager); - } - - // Mark the client being connected - m_NetworkManager.IsConnectedClient = true; - // When scene management is disabled we notify after everything is synchronized - m_NetworkManager.InvokeOnClientConnectedCallback(clientId); - } - } - - public void HandleAddObject(ulong clientId, Stream stream) - { - using var reader = PooledNetworkReader.Get(stream); - var isPlayerObject = reader.ReadBool(); - var networkId = reader.ReadUInt64Packed(); - var ownerClientId = reader.ReadUInt64Packed(); - var hasParent = reader.ReadBool(); - ulong? parentNetworkId = null; - - if (hasParent) - { - parentNetworkId = reader.ReadUInt64Packed(); - } - - var softSync = reader.ReadBool(); - var prefabHash = reader.ReadUInt32Packed(); - - Vector3? pos = null; - Quaternion? rot = null; - if (reader.ReadBool()) - { - pos = new Vector3(reader.ReadSinglePacked(), reader.ReadSinglePacked(), reader.ReadSinglePacked()); - rot = Quaternion.Euler(reader.ReadSinglePacked(), reader.ReadSinglePacked(), reader.ReadSinglePacked()); - } - - var (isReparented, latestParent) = NetworkObject.ReadNetworkParenting(reader); - - var networkObject = NetworkManager.SpawnManager.CreateLocalNetworkObject(softSync, prefabHash, ownerClientId, parentNetworkId, pos, rot, isReparented); - networkObject.SetNetworkParenting(isReparented, latestParent); - NetworkManager.SpawnManager.SpawnNetworkObjectLocally(networkObject, networkId, softSync, isPlayerObject, ownerClientId, stream, true, false); - m_NetworkManager.NetworkMetrics.TrackObjectSpawnReceived(clientId, networkObject.NetworkObjectId, networkObject.name, stream.Length); - } - - public void HandleDestroyObject(ulong clientId, Stream stream) - { - using var reader = PooledNetworkReader.Get(stream); - ulong networkObjectId = reader.ReadUInt64Packed(); - - if (!NetworkManager.SpawnManager.SpawnedObjects.TryGetValue(networkObjectId, out var networkObject)) - { - // This is the same check and log message that happens inside OnDespawnObject, but we have to do it here - // while we still have access to the network ID, otherwise the log message will be less useful. - if (NetworkLog.CurrentLogLevel <= LogLevel.Normal) - { - NetworkLog.LogWarning($"Trying to destroy {nameof(NetworkObject)} #{networkObjectId} but it does not exist in {nameof(NetworkSpawnManager.SpawnedObjects)} anymore!"); - } - - return; - } - - m_NetworkManager.NetworkMetrics.TrackObjectDestroyReceived(clientId, networkObjectId, networkObject.name, stream.Length); - NetworkManager.SpawnManager.OnDespawnObject(networkObject, true); - } - - /// - /// Called for all Scene Management related events - /// - /// - /// - public void HandleSceneEvent(ulong clientId, Stream stream) - { - NetworkManager.SceneManager.HandleSceneEvent(clientId, stream); - } - - public void HandleChangeOwner(ulong clientId, Stream stream) - { - using var reader = PooledNetworkReader.Get(stream); - ulong networkObjectId = reader.ReadUInt64Packed(); - ulong ownerClientId = reader.ReadUInt64Packed(); - - if (!NetworkManager.SpawnManager.SpawnedObjects.TryGetValue(networkObjectId, out var networkObject)) - { - if (NetworkLog.CurrentLogLevel <= LogLevel.Normal) - { - NetworkLog.LogWarning($"Trying to handle owner change but {nameof(NetworkObject)} #{networkObjectId} does not exist in {nameof(NetworkSpawnManager.SpawnedObjects)} anymore!"); - } - - return; - } - - if (networkObject.OwnerClientId == NetworkManager.LocalClientId) - { - //We are current owner. - networkObject.InvokeBehaviourOnLostOwnership(); - } - - networkObject.OwnerClientId = ownerClientId; - - if (ownerClientId == NetworkManager.LocalClientId) - { - //We are new owner. - networkObject.InvokeBehaviourOnGainedOwnership(); - } - - NetworkManager.NetworkMetrics.TrackOwnershipChangeReceived(clientId, networkObject.NetworkObjectId, networkObject.name, stream.Length); - } - - public void HandleTimeSync(ulong clientId, Stream stream) - { - using var reader = PooledNetworkReader.Get(stream); - int tick = reader.ReadInt32Packed(); - var time = new NetworkTime(NetworkManager.NetworkTickSystem.TickRate, tick); - NetworkManager.NetworkTimeSystem.Sync(time.Time, NetworkManager.NetworkConfig.NetworkTransport.GetCurrentRtt(clientId) / 1000d); - } - - public void HandleNetworkVariableDelta(ulong clientId, Stream stream) - { - using var reader = PooledNetworkReader.Get(stream); - ulong networkObjectId = reader.ReadUInt64Packed(); - ushort networkBehaviourIndex = reader.ReadUInt16Packed(); - - if (NetworkManager.SpawnManager.SpawnedObjects.TryGetValue(networkObjectId, out NetworkObject networkObject)) - { - NetworkBehaviour behaviour = networkObject.GetNetworkBehaviourAtOrderIndex(networkBehaviourIndex); - - if (behaviour == null) - { - if (NetworkLog.CurrentLogLevel <= LogLevel.Normal) - { - NetworkLog.LogWarning($"Network variable delta message received for a non-existent behaviour. {nameof(networkObjectId)}: {networkObjectId}, {nameof(networkBehaviourIndex)}: {networkBehaviourIndex}"); - } - } - else - { - behaviour.HandleNetworkVariableDeltas(stream, clientId); - } - } - else if (NetworkManager.IsServer) - { - if (NetworkLog.CurrentLogLevel <= LogLevel.Normal) - { - NetworkLog.LogWarning($"Network variable delta message received for a non-existent object with {nameof(networkObjectId)}: {networkObjectId}. This delta was lost."); - } - } - } - - /// - /// Converts the stream to a PerformanceQueueItem and adds it to the receive queue - /// - public void MessageReceiveQueueItem(ulong clientId, Stream stream, float receiveTime, MessageQueueContainer.MessageType messageType) - { - if (NetworkManager.IsServer && clientId == NetworkManager.ServerClientId) - { - return; - } - - if (messageType == MessageQueueContainer.MessageType.None) - { - if (NetworkLog.CurrentLogLevel <= LogLevel.Error) - { - NetworkLog.LogError($"Message header contained an invalid type: {((int)messageType).ToString()}"); - } - - return; - } - - if (NetworkLog.CurrentLogLevel <= LogLevel.Developer) - { - NetworkLog.LogInfo($"Data Header: {nameof(messageType)}={((int)messageType).ToString()}"); - } - - if (NetworkManager.PendingClients.TryGetValue(clientId, out PendingClient client) && (client.ConnectionState == PendingClient.State.PendingApproval || client.ConnectionState == PendingClient.State.PendingConnection && messageType != MessageQueueContainer.MessageType.ConnectionRequest)) - { - if (NetworkLog.CurrentLogLevel <= LogLevel.Normal) - { - NetworkLog.LogWarning($"Message received from {nameof(clientId)}={clientId.ToString()} before it has been accepted"); - } - - return; - } - - var messageQueueContainer = NetworkManager.MessageQueueContainer; - messageQueueContainer.AddQueueItemToInboundFrame(messageType, receiveTime, clientId, (NetworkBuffer)stream); - } - - public void HandleUnnamedMessage(ulong clientId, Stream stream) - { - NetworkManager.CustomMessagingManager.InvokeUnnamedMessage(clientId, stream); - } - - public void HandleNamedMessage(ulong clientId, Stream stream) - { - using var reader = PooledNetworkReader.Get(stream); - ulong hash = reader.ReadUInt64Packed(); - - NetworkManager.CustomMessagingManager.InvokeNamedMessage(hash, clientId, stream); - } - - public void HandleNetworkLog(ulong clientId, Stream stream) - { - using var reader = PooledNetworkReader.Get(stream); - var length = stream.Length; - var logType = (NetworkLog.LogType)reader.ReadByte(); - m_NetworkManager.NetworkMetrics.TrackServerLogReceived(clientId, (uint)logType, length); - string message = reader.ReadStringPacked(); - - switch (logType) - { - case NetworkLog.LogType.Info: - NetworkLog.LogInfoServerLocal(message, clientId); - break; - case NetworkLog.LogType.Warning: - NetworkLog.LogWarningServerLocal(message, clientId); - break; - case NetworkLog.LogType.Error: - NetworkLog.LogErrorServerLocal(message, clientId); - break; - } - } - - public void HandleSnapshot(ulong clientId, Stream messageStream) - { - m_NetworkManager.SnapshotSystem.ReadSnapshot(clientId, messageStream); - } - } -} diff --git a/com.unity.netcode.gameobjects/Runtime/Messaging/MessageBatcher.cs b/com.unity.netcode.gameobjects/Runtime/Messaging/MessageBatcher.cs deleted file mode 100644 index ccdb4161b4..0000000000 --- a/com.unity.netcode.gameobjects/Runtime/Messaging/MessageBatcher.cs +++ /dev/null @@ -1,204 +0,0 @@ -using System; -using System.IO; -using System.Collections.Generic; - -namespace Unity.Netcode -{ - internal class MessageBatcher - { - public class SendStream - { - public NetworkDelivery Delivery; - public PooledNetworkBuffer Buffer; - public PooledNetworkWriter Writer; - public bool IsEmpty = true; - - public SendStream() - { - Buffer = PooledNetworkBuffer.Get(); - Writer = PooledNetworkWriter.Get(Buffer); - } - } - - // Stores the stream of batched RPC to send to each client, by ClientId - private readonly Dictionary m_SendDict = new Dictionary(); - - public void Shutdown() - { - foreach (var kvp in m_SendDict) - { - kvp.Value.Writer.Dispose(); - kvp.Value.Buffer.Dispose(); - } - - m_SendDict.Clear(); - } - - - // Used to mark longer lengths. Works because we can't have zero-sized messages - private const byte k_LongLenMarker = 0; - - internal static void PushLength(int length, ref PooledNetworkWriter writer) - { - // If length is single byte we write it - if (length < 256) - { - writer.WriteByte((byte)length); // write the amounts of bytes that are coming up - } - else - { - // otherwise we write a two-byte length - writer.WriteByte(k_LongLenMarker); // mark larger size - writer.WriteByte((byte)(length % 256)); // write the length modulo 256 - writer.WriteByte((byte)(length / 256)); // write the length divided by 256 - } - } - - private int PopLength(in NetworkBuffer messageBuffer) - { - int read = messageBuffer.ReadByte(); - // if we read a non-zero value, we have a single byte length - // or a -1 error we can return - if (read != k_LongLenMarker) - { - return read; - } - - // otherwise, a two-byte length follows. We'll read in len1, len2 - int len1 = messageBuffer.ReadByte(); - if (len1 < 0) - { - // pass errors back to caller - return len1; - } - - int len2 = messageBuffer.ReadByte(); - if (len2 < 0) - { - // pass errors back to caller - return len2; - } - - return len1 + len2 * 256; - } - - /// - /// Add a FrameQueueItem to be sent - /// - public void QueueItem( - IReadOnlyCollection targetList, - in MessageFrameItem item, - int automaticSendThresholdBytes, - SendCallbackType sendCallback) - { - foreach (ulong clientId in targetList) - { - if (!m_SendDict.ContainsKey(clientId)) - { - // todo: consider what happens if many clients join and leave the game consecutively - // we probably need a cleanup mechanism at some point - m_SendDict[clientId] = new SendStream(); - } - - SendStream sendStream = m_SendDict[clientId]; - - if (sendStream.IsEmpty) - { - sendStream.IsEmpty = false; - sendStream.Delivery = item.Delivery; - } - // If the item is a different channel we have to flush and change channels. - // This isn't great if channels are interleaved, but having a different stream - // per channel would create ordering issues. - else if (sendStream.Delivery != item.Delivery) - { - sendCallback(clientId, sendStream); - // clear the batch that was sent from the SendDict - sendStream.Buffer.SetLength(0); - sendStream.Buffer.Position = 0; - - sendStream.Delivery = item.Delivery; - } - - // write the amounts of bytes that are coming up - PushLength(item.MessageData.Count, ref sendStream.Writer); - - // write the message to send - sendStream.Writer.WriteBytes(item.MessageData.Array, item.MessageData.Count, item.MessageData.Offset); - - if (sendStream.Buffer.Length >= automaticSendThresholdBytes) - { - sendCallback(clientId, sendStream); - // clear the batch that was sent from the SendDict - sendStream.Buffer.SetLength(0); - sendStream.Buffer.Position = 0; - sendStream.IsEmpty = true; - } - } - } - - public delegate void SendCallbackType(ulong clientId, SendStream messageStream); - public delegate void ReceiveCallbackType(NetworkBuffer messageStream, MessageQueueContainer.MessageType messageType, ulong clientId, float receiveTime); - - /// - /// Send any batch of messages that are of length above threshold - /// - /// the threshold in bytes - /// the function to call for sending the batch - public void SendItems(int thresholdBytes, SendCallbackType sendCallback) - { - foreach (KeyValuePair entry in m_SendDict) - { - if (!entry.Value.IsEmpty) - { - // read the queued message - int length = (int)m_SendDict[entry.Key].Buffer.Length; - - if (length >= thresholdBytes) - { - sendCallback(entry.Key, entry.Value); - // clear the batch that was sent from the SendDict - entry.Value.Buffer.SetLength(0); - entry.Value.Buffer.Position = 0; - entry.Value.IsEmpty = true; - } - } - } - } - - /// - /// Process the messageStream and call the callback with individual messages - /// - /// the messageStream containing the batched messages - /// the callback to call has type int f(message, type, clientId, time) - /// the clientId to pass back to callback - /// the packet receive time to pass back to callback - public void ReceiveItems(in NetworkBuffer messageBuffer, ReceiveCallbackType receiveCallback, ulong clientId, float receiveTime) - { - using var copy = PooledNetworkBuffer.Get(); - do - { - // read the length of the next message - int messageSize = PopLength(messageBuffer); - if (messageSize < 0) - { - // abort if there's an error reading lengths - return; - } - - // copy what comes after current stream position - long position = messageBuffer.Position; - copy.SetLength(messageSize); - copy.Position = 0; - Buffer.BlockCopy(messageBuffer.GetBuffer(), (int)position, copy.GetBuffer(), 0, messageSize); - - var messageType = (MessageQueueContainer.MessageType)copy.ReadByte(); - receiveCallback(copy, messageType, clientId, receiveTime); - - // seek over the message - // MessageReceiveQueueItem peeks at content, it doesn't advance - messageBuffer.Seek(messageSize, SeekOrigin.Current); - } while (messageBuffer.Position < messageBuffer.Length); - } - } -} diff --git a/com.unity.netcode.gameobjects/Runtime/Messaging/MessageHeader.cs b/com.unity.netcode.gameobjects/Runtime/Messaging/MessageHeader.cs new file mode 100644 index 0000000000..bb5ae79ec5 --- /dev/null +++ b/com.unity.netcode.gameobjects/Runtime/Messaging/MessageHeader.cs @@ -0,0 +1,8 @@ +namespace Unity.Netcode +{ + public struct MessageHeader + { + public byte MessageType; + public short MessageSize; + } +} diff --git a/com.unity.netcode.gameobjects/Runtime/Messaging/MessageHeader.cs.meta b/com.unity.netcode.gameobjects/Runtime/Messaging/MessageHeader.cs.meta new file mode 100644 index 0000000000..1931b10738 --- /dev/null +++ b/com.unity.netcode.gameobjects/Runtime/Messaging/MessageHeader.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 74fa727ddec342c48ab49156a32b977b +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/com.unity.netcode.gameobjects/Runtime/Messaging/MessageQueue/MessageFrameItem.cs b/com.unity.netcode.gameobjects/Runtime/Messaging/MessageQueue/MessageFrameItem.cs deleted file mode 100644 index d5acb5e836..0000000000 --- a/com.unity.netcode.gameobjects/Runtime/Messaging/MessageQueue/MessageFrameItem.cs +++ /dev/null @@ -1,33 +0,0 @@ -using System; -using System.IO; - -namespace Unity.Netcode -{ - /// - /// MessageFrameItem - /// Container structure for messages written to the Queue Frame - /// Used for both Inbound and Outbound messages - /// NOTE: This structure will change in the near future and is in a state of flux. - /// This will include removing specific properties or changing property types - /// - internal struct MessageFrameItem - { - public NetworkUpdateStage UpdateStage; - public MessageQueueContainer.MessageType MessageType; - /// - /// Sender's network Identifier, or recipient identifier for server RPCs - /// - public ulong NetworkId; - public NetworkDelivery Delivery; - /// - /// Everything other than server RPCs - /// - public ulong[] ClientNetworkIds; - public long StreamSize; - public float Timestamp; - public PooledNetworkWriter NetworkWriter; - public PooledNetworkReader NetworkReader; - public Stream NetworkBuffer; - public ArraySegment MessageData; - } -} diff --git a/com.unity.netcode.gameobjects/Runtime/Messaging/MessageQueue/MessageQueueContainer.cs b/com.unity.netcode.gameobjects/Runtime/Messaging/MessageQueue/MessageQueueContainer.cs deleted file mode 100644 index ff29b4f324..0000000000 --- a/com.unity.netcode.gameobjects/Runtime/Messaging/MessageQueue/MessageQueueContainer.cs +++ /dev/null @@ -1,826 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using UnityEngine; - -namespace Unity.Netcode -{ - /// - /// MessageQueueContainer - /// Handles the management of a Message Queue - /// - internal class MessageQueueContainer : INetworkUpdateSystem, IDisposable - { - private const int k_MinQueueHistory = 2; //We need a minimum of 2 queue history buffers in order to properly handle looping back messages when a host - -#if UNITY_EDITOR || DEVELOPMENT_BUILD - private static int s_MessageQueueContainerInstances; -#endif - - public enum MessageType - { - ConnectionRequest, - ConnectionApproved, - ClientRpc, - ServerRpc, - CreateObject, - DestroyObject, - ChangeOwner, - TimeSync, - UnnamedMessage, - NamedMessage, - ServerLog, - SnapshotData, - NetworkVariableDelta, - SceneEvent, - ParentSync, - - None //Indicates end of frame - } - - public enum MessageQueueProcessingTypes - { - Send, - Receive, - } - - private static readonly IReadOnlyDictionary k_MessageTypeNames; - - static MessageQueueContainer() - { - var messageTypeNames = new Dictionary(); - foreach (var messageType in Enum.GetValues(typeof(MessageType))) - { - messageTypeNames.Add((int)messageType, messageType.ToString()); - } - - k_MessageTypeNames = messageTypeNames; - } - - public static string GetMessageTypeName(MessageType messageType) - { - if (!k_MessageTypeNames.TryGetValue((int)messageType, out var messageTypeName)) - { - messageTypeName = string.Empty; - } - - return messageTypeName; - } - - // Inbound and Outbound QueueHistoryFrames - private readonly Dictionary>> m_QueueHistory = - new Dictionary>>(); - - private MessageQueueProcessor m_MessageQueueProcessor; - - private uint m_MaxFrameHistory; - private int m_InboundStreamBufferIndex; - private int m_OutBoundStreamBufferIndex; - private bool m_IsTestingEnabled; - private bool m_ProcessUpdateStagesExternally; - private bool m_IsNotUsingBatching; - - // TODO hack: Fixed update can run multiple times in a frame and the queue history frame doesn't get cleared - // until the end of the frame. This results in messages executing at FixedUpdate being invoked multiple times. - // This is used to prevent it being called more than once per frame. - // This will be fixed by the upcoming serialization refactor. - private bool m_HasRunFixedUpdate; - - internal readonly NetworkManager NetworkManager; - - /// - /// Returns if batching is enabled - /// - /// true or false - public bool IsUsingBatching() - { - return !m_IsNotUsingBatching; - } - - /// - /// Enables or disables batching - /// - /// true or false - public void EnableBatchedMessages(bool isbatchingEnabled) - { - m_IsNotUsingBatching = !isbatchingEnabled; - } - - /// - /// Creates a context for an internal command. - /// The context contains a NetworkWriter property used to fill out the command body. - /// If used as IDisposable, the command will be sent at the end of the using() block. - /// If not used as IDisposable, the command can be sent by calling context.Finalize() - /// - /// The type of message being sent - /// The channel the message is being sent on - /// The destinations for this message - /// The stage at which the message will be processed on the receiving side - /// - internal InternalCommandContext? EnterInternalCommandContext(MessageType messageType, NetworkDelivery networkDelivery, ulong[] clientIds, NetworkUpdateStage updateStage) - { - if (updateStage == NetworkUpdateStage.Initialization) - { - NetworkLog.LogWarning($"Trying to send a message of type {messageType} to be executed during Initialization stage. Changing to EarlyUpdate."); - updateStage = NetworkUpdateStage.EarlyUpdate; - } - - if (NetworkManager.IsServer) - { - clientIds = clientIds.Where(id => id != NetworkManager.ServerClientId).ToArray(); - } - - if (clientIds.Length == 0) - { - return null; - } - - var writer = BeginAddQueueItemToFrame(messageType, Time.realtimeSinceStartup, networkDelivery, NetworkManager.LocalClientId, clientIds, MessageQueueHistoryFrame.QueueFrameType.Outbound, NetworkUpdateStage.PostLateUpdate); - - writer.WriteByte((byte)messageType); - writer.WriteByte((byte)updateStage); // NetworkUpdateStage - - return new InternalCommandContext(writer, clientIds, updateStage, this); - } - - - /// - /// Will process the message queue and then move to the next available frame - /// - /// Inbound or Outbound - /// Network Update Stage assigned MessageQueueHistoryFrame to be processed and flushed - public void ProcessAndFlushMessageQueue(MessageQueueProcessingTypes queueType, NetworkUpdateStage currentUpdateStage) - { - bool isListening = !ReferenceEquals(NetworkManager, null) && NetworkManager.IsListening; - switch (queueType) - { - case MessageQueueProcessingTypes.Receive: - { - m_MessageQueueProcessor.ProcessReceiveQueue(currentUpdateStage, m_IsTestingEnabled); - break; - } - case MessageQueueProcessingTypes.Send: - { - m_MessageQueueProcessor.ProcessSendQueue(isListening); - break; - } - } - } - - /// - /// Gets the current frame for the Inbound or Outbound queue - /// - /// Inbound or Outbound - /// Network Update Stage the MessageQueueHistoryFrame is assigned to - /// QueueHistoryFrame - public MessageQueueHistoryFrame GetCurrentFrame(MessageQueueHistoryFrame.QueueFrameType qType, NetworkUpdateStage currentUpdateStage) - { - if (m_QueueHistory.ContainsKey(qType)) - { - int streamBufferIndex = GetStreamBufferIndex(qType); - - if (m_QueueHistory[qType].ContainsKey(streamBufferIndex)) - { - if (m_QueueHistory[qType][streamBufferIndex].ContainsKey(currentUpdateStage)) - { - return m_QueueHistory[qType][streamBufferIndex][currentUpdateStage]; - } - } - } - - return null; - } - - /// - /// Returns the queue type's current stream buffer index - /// - /// Inbound or Outbound - /// - private int GetStreamBufferIndex(MessageQueueHistoryFrame.QueueFrameType queueType) - { - return queueType == MessageQueueHistoryFrame.QueueFrameType.Inbound ? m_InboundStreamBufferIndex : m_OutBoundStreamBufferIndex; - } - - /// - /// Progresses the current frame to the next QueueHistoryFrame for the QueueHistoryFrame.QueueFrameType. - /// All other frames other than the current frame is considered the live rollback history - /// - /// Inbound or Outbound - public void AdvanceFrameHistory(MessageQueueHistoryFrame.QueueFrameType queueType) - { - int streamBufferIndex = GetStreamBufferIndex(queueType); - - if (!m_QueueHistory.ContainsKey(queueType)) - { - Debug.LogError($"You must initialize the {nameof(MessageQueueContainer)} before using Unity.Netcode!"); - return; - } - - if (!m_QueueHistory[queueType].ContainsKey(streamBufferIndex)) - { - Debug.LogError($"{nameof(MessageQueueContainer)} {queueType} queue stream buffer index out of range! [{streamBufferIndex}]"); - return; - } - - - foreach (KeyValuePair queueHistoryByUpdates in m_QueueHistory[queueType][streamBufferIndex]) - { - var messageQueueHistoryItem = queueHistoryByUpdates.Value; - - //This only gets reset when we advanced to next frame (do not reset this in the ResetQueueHistoryFrame) - messageQueueHistoryItem.HasLoopbackData = false; - - ResetQueueHistoryFrame(messageQueueHistoryItem); - IncrementAndSetQueueHistoryFrame(messageQueueHistoryItem); - } - - //Roll to the next stream buffer - streamBufferIndex++; - - //If we have hit our maximum history, roll back over to the first one - if (streamBufferIndex >= m_MaxFrameHistory) - { - streamBufferIndex = 0; - } - - if (queueType == MessageQueueHistoryFrame.QueueFrameType.Inbound) - { - m_InboundStreamBufferIndex = streamBufferIndex; - } - else - { - m_OutBoundStreamBufferIndex = streamBufferIndex; - } - } - - /// - /// IncrementAndSetQueueHistoryFrame - /// Increments and sets frame count for this queue frame - /// - /// QueueHistoryFrame to be reset - private void IncrementAndSetQueueHistoryFrame(MessageQueueHistoryFrame messageQueueFrame) - { - if (messageQueueFrame.GetQueueFrameType() == MessageQueueHistoryFrame.QueueFrameType.Inbound) - { - } - else - { - } - } - - /// - /// ResetQueueHistoryFrame - /// Resets the queue history frame passed to this method - /// - /// QueueHistoryFrame to be reset - private static void ResetQueueHistoryFrame(MessageQueueHistoryFrame messageQueueFrame) - { - //If we are dirt and have loopback data then don't clear this frame - if (messageQueueFrame.IsDirty && !messageQueueFrame.HasLoopbackData) - { - messageQueueFrame.TotalSize = 0; - messageQueueFrame.QueueItemOffsets.Clear(); - messageQueueFrame.QueueBuffer.Position = 0; - messageQueueFrame.MarkCurrentStreamPosition(); - messageQueueFrame.IsDirty = false; - } - } - - /// - /// AddQueueItemToInboundFrame - /// Adds a message queue item to the outbound frame - /// - /// type of message - /// when it was received - /// who sent the message - /// the message being received - internal void AddQueueItemToInboundFrame(MessageType messageType, float receiveTimestamp, ulong senderNetworkId, NetworkBuffer messageBuffer) - { - var updateStage = (NetworkUpdateStage)messageBuffer.ReadByte(); - - var messageFrameItem = GetQueueHistoryFrame(MessageQueueHistoryFrame.QueueFrameType.Inbound, updateStage); - messageFrameItem.IsDirty = true; - - long startPosition = messageFrameItem.QueueBuffer.Position; - - //Write the packed version of the queueItem to our current queue history buffer - messageFrameItem.QueueWriter.WriteUInt16((ushort)messageType); - messageFrameItem.QueueWriter.WriteSingle(receiveTimestamp); - messageFrameItem.QueueWriter.WriteUInt64(senderNetworkId); - - //Inbound we copy the entire packet and store the position offset - long streamSize = messageBuffer.Length - messageBuffer.Position; - messageFrameItem.QueueWriter.WriteInt64(streamSize); - // This 0 is an offset into the following stream. Since we're copying from the offset rather than copying the whole buffer, it can stay at 0. - // In other words, we're not using the offset anymore, but it's being left for now in case it becomes necessary again later. - messageFrameItem.QueueWriter.WriteInt64(0); - messageFrameItem.QueueWriter.WriteBytes(messageBuffer.GetBuffer(), streamSize, (int)messageBuffer.Position); - - //Add the packed size to the offsets for parsing over various entries - messageFrameItem.QueueItemOffsets.Add((uint)messageFrameItem.QueueBuffer.Position); - - //Calculate the packed size based on stream progression - messageFrameItem.TotalSize += (uint)(messageFrameItem.QueueBuffer.Position - startPosition); - } - - /// - /// SetLoopBackFrameItem - /// ***Temporary fix for host mode loopback RPC writer work-around - /// Sets the next frame inbond buffer as the loopback queue history frame in the current frame's outbound buffer - /// - /// - public void SetLoopBackFrameItem(NetworkUpdateStage updateStage) - { - //Get the next frame's inbound queue history frame - var loopbackHistoryframe = GetQueueHistoryFrame(MessageQueueHistoryFrame.QueueFrameType.Inbound, updateStage, true); - - //Get the current frame's outbound queue history frame - var messageQueueHistoryItem = GetQueueHistoryFrame(MessageQueueHistoryFrame.QueueFrameType.Outbound, NetworkUpdateStage.PostLateUpdate); - - if (messageQueueHistoryItem != null) - { - messageQueueHistoryItem.LoopbackHistoryFrame = loopbackHistoryframe; - } - else - { - Debug.LogError($"Could not find the outbound {nameof(MessageQueueHistoryFrame)}!"); - } - } - - /// - /// GetLoopBackWriter - /// Gets the loop back writer for the history frame (if one exists) - /// ***Temporary fix for host mode loopback RPC writer work-around - /// - /// type of queue frame - /// state it should be invoked on - /// - public MessageQueueHistoryFrame GetLoopBackHistoryFrame(MessageQueueHistoryFrame.QueueFrameType queueFrameType, NetworkUpdateStage updateStage) - { - return GetQueueHistoryFrame(queueFrameType, updateStage); - } - - /// - /// BeginAddQueueItemToOutboundFrame - /// Adds a queue item to the outbound queue frame - /// - /// type of the queue item - /// when queue item was submitted - /// channel this queue item is being sent - /// source network id of the sender - /// target network id(s) - /// type of queue frame - /// what update stage the RPC should be invoked on - /// PooledNetworkWriter - public PooledNetworkWriter BeginAddQueueItemToFrame(MessageType messageType, float sendTimestamp, NetworkDelivery networkDelivery, ulong senderNetworkId, ulong[] targetNetworkIds, - MessageQueueHistoryFrame.QueueFrameType queueFrameType, NetworkUpdateStage updateStage) - { - bool getNextFrame = NetworkManager.IsHost && queueFrameType == MessageQueueHistoryFrame.QueueFrameType.Inbound; - - var messageQueueHistoryFrame = GetQueueHistoryFrame(queueFrameType, updateStage, getNextFrame); - messageQueueHistoryFrame.IsDirty = true; - - //Write the packed version of the queueItem to our current queue history buffer - messageQueueHistoryFrame.QueueWriter.WriteUInt16((ushort)messageType); - messageQueueHistoryFrame.QueueWriter.WriteSingle(sendTimestamp); - messageQueueHistoryFrame.QueueWriter.WriteUInt64(senderNetworkId); - if (queueFrameType == MessageQueueHistoryFrame.QueueFrameType.Outbound) - { - messageQueueHistoryFrame.QueueWriter.WriteByte((byte)networkDelivery); - } - - if (queueFrameType != MessageQueueHistoryFrame.QueueFrameType.Inbound) - { - if (targetNetworkIds != null && targetNetworkIds.Length != 0) - { - //In the event the host is one of the networkIds, for outbound we want to ignore it (at this spot only!!) - //Get a count of clients we are going to send to (and write into the buffer) - var numberOfClients = 0; - for (int i = 0; i < targetNetworkIds.Length; i++) - { - if (NetworkManager.IsHost && targetNetworkIds[i] == NetworkManager.ServerClientId) - { - continue; - } - - numberOfClients++; - } - - //Write our total number of clients - messageQueueHistoryFrame.QueueWriter.WriteInt32(numberOfClients); - - //Now write the cliend ids - for (int i = 0; i < targetNetworkIds.Length; i++) - { - if (NetworkManager.IsHost && targetNetworkIds[i] == NetworkManager.ServerClientId) - { - continue; - } - - messageQueueHistoryFrame.QueueWriter.WriteUInt64(targetNetworkIds[i]); - } - } - else - { - messageQueueHistoryFrame.QueueWriter.WriteInt32(0); - } - } - - //Mark where we started in the stream to later determine the actual RPC message size (position before writing RPC message vs position after write has completed) - messageQueueHistoryFrame.MarkCurrentStreamPosition(); - - //Write a filler dummy size of 0 to hold this position in order to write to it once the RPC is done writing. - messageQueueHistoryFrame.QueueWriter.WriteInt64(0); - - if (NetworkManager.IsHost && queueFrameType == MessageQueueHistoryFrame.QueueFrameType.Inbound) - { - if (!IsUsingBatching()) - { - messageQueueHistoryFrame.QueueWriter.WriteInt64(1); - } - else - { - messageQueueHistoryFrame.QueueWriter.WriteInt64(0); - } - - messageQueueHistoryFrame.HasLoopbackData = true; //The only case for this is when it is the Host - } - - //Return the writer to the invoking method. - return messageQueueHistoryFrame.QueueWriter; - } - - /// - /// Signifies the end of this outbound RPC. - /// We store final MSG size and track the total current frame queue size - /// - /// NetworkWriter that was used - /// type of the queue frame that was used - /// stage the RPC is going to be invoked - public long EndAddQueueItemToFrame(NetworkWriter writer, MessageQueueHistoryFrame.QueueFrameType queueFrameType, NetworkUpdateStage updateStage) - { - bool getNextFrame = NetworkManager.IsHost && queueFrameType == MessageQueueHistoryFrame.QueueFrameType.Inbound; - - var messageQueueHistoryItem = GetQueueHistoryFrame(queueFrameType, updateStage, getNextFrame); - var loopBackHistoryFrame = messageQueueHistoryItem.LoopbackHistoryFrame; - - var pbWriter = (PooledNetworkWriter)writer; - if (pbWriter != messageQueueHistoryItem.QueueWriter) - { - Debug.LogError($"{nameof(MessageQueueContainer)} {queueFrameType} passed writer is not the same as the current {nameof(PooledNetworkWriter)} for the {queueFrameType}!"); - } - - //The total size of the frame is the last known position of the stream - messageQueueHistoryItem.TotalSize = (uint)messageQueueHistoryItem.QueueBuffer.Position; - - long currentPosition = messageQueueHistoryItem.QueueBuffer.Position; - ulong bitPosition = messageQueueHistoryItem.QueueBuffer.BitPosition; - - ////////////////////////////////////////////////////////////// - //>>>> REPOSITIONING STREAM TO RPC MESSAGE SIZE LOCATION <<<< - ////////////////////////////////////////////////////////////// - messageQueueHistoryItem.QueueBuffer.Position = messageQueueHistoryItem.GetCurrentMarkedPosition(); - - long messageOffset = 8; - if (getNextFrame && IsUsingBatching()) - { - messageOffset += 8; - } - - //subtracting 8 byte to account for the value of the size of the RPC (why the 8 above in - long messageSize = messageQueueHistoryItem.TotalSize - (messageQueueHistoryItem.GetCurrentMarkedPosition() + messageOffset); - - if (messageSize > 0) - { - //Write the actual size of the RPC message - messageQueueHistoryItem.QueueWriter.WriteInt64(messageSize); - } - else - { - Debug.LogWarning("MSGSize of < zero detected!! Setting message size to zero!"); - messageQueueHistoryItem.QueueWriter.WriteInt64(0); - } - - if (loopBackHistoryFrame != null) - { - if (messageSize > 0) - { - //Point to where the size of the message is stored - loopBackHistoryFrame.QueueBuffer.Position = loopBackHistoryFrame.GetCurrentMarkedPosition(); - - //Write the actual size of the RPC message - loopBackHistoryFrame.QueueWriter.WriteInt64(messageSize); - - if (!IsUsingBatching()) - { - //Write the offset for the header info copied - loopBackHistoryFrame.QueueWriter.WriteInt64(1); - } - else - { - //Write the offset for the header info copied - loopBackHistoryFrame.QueueWriter.WriteInt64(0); - } - - //Write message data - loopBackHistoryFrame.QueueWriter.WriteBytes( - messageQueueHistoryItem.QueueBuffer.GetBuffer(), messageSize - 2, - // Skip the 2 byte network header - // The network header is read on the receiving side to be able to call - // AddQueueItemToInboundFrame, which needs the message type and update stage - // (which are the two values in the network header) in order to create - // the inbound queue item. Here, we're skipping that - the loopback frame item - // is added to the inbound frame directly rather than passed along the wire. - // Since this skips the process that reads the network header, we skip writing it. - (int)messageQueueHistoryItem.QueueBuffer.Position + 2); - - //Set the total size for this stream - loopBackHistoryFrame.TotalSize = (uint)loopBackHistoryFrame.QueueBuffer.Position; - - //Add the total size to the offsets for parsing over various entries - loopBackHistoryFrame.QueueItemOffsets.Add((uint)loopBackHistoryFrame.QueueBuffer.Position); - } - else - { - Debug.LogWarning($"{nameof(messageSize)} < zero detected! Setting message size to zero!"); - //Write the actual size of the RPC message - loopBackHistoryFrame.QueueWriter.WriteInt64(0); - } - - messageQueueHistoryItem.LoopbackHistoryFrame = null; - } - - ////////////////////////////////////////////////////////////// - //<<<< REPOSITIONING STREAM BACK TO THE CURRENT TAIL >>>> - ////////////////////////////////////////////////////////////// - messageQueueHistoryItem.QueueBuffer.Position = currentPosition; - messageQueueHistoryItem.QueueBuffer.BitPosition = bitPosition; - - //Add the packed size to the offsets for parsing over various entries - messageQueueHistoryItem.QueueItemOffsets.Add((uint)messageQueueHistoryItem.QueueBuffer.Position); - - return messageSize; - } - - /// - /// Gets the current queue history frame (inbound or outbound) - /// - /// inbound or outbound - /// network update stage the queue history frame is assigned to - /// whether to get the next frame or not (true/false) - /// QueueHistoryFrame or null - public MessageQueueHistoryFrame GetQueueHistoryFrame(MessageQueueHistoryFrame.QueueFrameType frameType, NetworkUpdateStage updateStage, bool getNextFrame = false) - { - int streamBufferIndex = GetStreamBufferIndex(frameType); - - //We want to write into the future/next frame - if (getNextFrame) - { - streamBufferIndex++; - - //If we have hit our maximum history, roll back over to the first one - if (streamBufferIndex >= m_MaxFrameHistory) - { - streamBufferIndex = 0; - } - } - - if (!m_QueueHistory.ContainsKey(frameType)) - { - Debug.LogError($"{nameof(MessageQueueHistoryFrame)} {nameof(MessageQueueHistoryFrame.QueueFrameType)} {frameType} does not exist!"); - return null; - } - - if (!m_QueueHistory[frameType].ContainsKey(streamBufferIndex)) - { - Debug.LogError($"{nameof(MessageQueueContainer)} {frameType} queue stream buffer index out of range! [{streamBufferIndex}]"); - return null; - } - - if (!m_QueueHistory[frameType][streamBufferIndex].ContainsKey(updateStage)) - { - Debug.LogError($"{nameof(MessageQueueContainer)} {updateStage} update type does not exist!"); - return null; - } - - return m_QueueHistory[frameType][streamBufferIndex][updateStage]; - } - - /// - /// The NetworkUpdate method used by the NetworkUpdateLoop - /// - /// the stage to process RPC Queues - public void NetworkUpdate(NetworkUpdateStage updateStage) - { - if (updateStage == NetworkUpdateStage.FixedUpdate) - { - if (m_HasRunFixedUpdate) - { - return; - } - - m_HasRunFixedUpdate = true; - } - ProcessAndFlushMessageQueue(MessageQueueProcessingTypes.Receive, updateStage); - - if (updateStage == NetworkUpdateStage.PostLateUpdate) - { - ProcessAndFlushMessageQueue(MessageQueueProcessingTypes.Send, updateStage); - m_MessageQueueProcessor.AdvanceFrameHistoryIfNeeded(); - m_HasRunFixedUpdate = false; - } - } - - /// - /// This will allocate [maxFrameHistory] + [1 currentFrame] number of PooledNetworkBuffers and keep them open until the session ends - /// Note: For zero frame history set maxFrameHistory to zero - /// - /// - private void Initialize(uint maxFrameHistory) - { - //This makes sure that we don't exceed a ridiculous value by capping the number of queue history frames to ushort.MaxValue - //If this value is exceeded, then it will be kept at the ceiling of ushort.Maxvalue. - //Note: If running at a 60pps rate (16ms update frequency) this would yield 17.47 minutes worth of queue frame history. - if (maxFrameHistory > ushort.MaxValue) - { - if (NetworkLog.CurrentLogLevel == LogLevel.Developer) - { - NetworkLog.LogWarning($"The {nameof(MessageQueueHistoryFrame)} size cannot exceed {ushort.MaxValue} {nameof(MessageQueueHistoryFrame)}s! Capping at {ushort.MaxValue} {nameof(MessageQueueHistoryFrame)}s."); - } - maxFrameHistory = ushort.MaxValue; - } - - ClearParameters(); - - m_MessageQueueProcessor = new MessageQueueProcessor(this, NetworkManager); - m_MaxFrameHistory = maxFrameHistory + k_MinQueueHistory; - - if (!m_QueueHistory.ContainsKey(MessageQueueHistoryFrame.QueueFrameType.Inbound)) - { - m_QueueHistory.Add(MessageQueueHistoryFrame.QueueFrameType.Inbound, new Dictionary>()); - } - - if (!m_QueueHistory.ContainsKey(MessageQueueHistoryFrame.QueueFrameType.Outbound)) - { - m_QueueHistory.Add(MessageQueueHistoryFrame.QueueFrameType.Outbound, new Dictionary>()); - } - - for (int i = 0; i < m_MaxFrameHistory; i++) - { - if (!m_QueueHistory[MessageQueueHistoryFrame.QueueFrameType.Outbound].ContainsKey(i)) - { - m_QueueHistory[MessageQueueHistoryFrame.QueueFrameType.Outbound].Add(i, new Dictionary()); - var queueHistoryFrame = new MessageQueueHistoryFrame(MessageQueueHistoryFrame.QueueFrameType.Outbound, NetworkUpdateStage.PostLateUpdate); - queueHistoryFrame.QueueBuffer = PooledNetworkBuffer.Get(); - queueHistoryFrame.QueueBuffer.Position = 0; - queueHistoryFrame.QueueWriter = PooledNetworkWriter.Get(queueHistoryFrame.QueueBuffer); - queueHistoryFrame.QueueReader = PooledNetworkReader.Get(queueHistoryFrame.QueueBuffer); - queueHistoryFrame.QueueItemOffsets = new List(); - - //For now all outbound, we will always have a single update in which they are processed (LATEUPDATE) - m_QueueHistory[MessageQueueHistoryFrame.QueueFrameType.Outbound][i].Add(NetworkUpdateStage.PostLateUpdate, queueHistoryFrame); - } - - if (!m_QueueHistory[MessageQueueHistoryFrame.QueueFrameType.Inbound].ContainsKey(i)) - { - m_QueueHistory[MessageQueueHistoryFrame.QueueFrameType.Inbound].Add(i, new Dictionary()); - - //For inbound, we create a queue history frame per update stage - foreach (NetworkUpdateStage netUpdateStage in Enum.GetValues(typeof(NetworkUpdateStage))) - { - var messageQueueHistoryFrame = new MessageQueueHistoryFrame(MessageQueueHistoryFrame.QueueFrameType.Inbound, netUpdateStage); - messageQueueHistoryFrame.QueueBuffer = PooledNetworkBuffer.Get(); - messageQueueHistoryFrame.QueueBuffer.Position = 0; - messageQueueHistoryFrame.QueueWriter = PooledNetworkWriter.Get(messageQueueHistoryFrame.QueueBuffer); - messageQueueHistoryFrame.QueueReader = PooledNetworkReader.Get(messageQueueHistoryFrame.QueueBuffer); - messageQueueHistoryFrame.QueueItemOffsets = new List(); - m_QueueHistory[MessageQueueHistoryFrame.QueueFrameType.Inbound][i].Add(netUpdateStage, messageQueueHistoryFrame); - } - } - } - - //As long as this instance is using the pre-defined update stages - if (!m_ProcessUpdateStagesExternally) - { - //Register with the network update loop system - this.RegisterAllNetworkUpdates(); - } - } - - /// - /// Clears the stream indices and frames process properties - /// - private void ClearParameters() - { - m_InboundStreamBufferIndex = 0; - m_OutBoundStreamBufferIndex = 0; - } - - /// - /// Flushes the internal messages - /// Removes itself from the network update loop - /// Disposes readers, writers, clears the queue history, and resets any parameters - /// - private void Shutdown() - { -#if UNITY_EDITOR || DEVELOPMENT_BUILD - - if (NetworkLog.CurrentLogLevel == LogLevel.Developer) - { - NetworkLog.LogInfo($"[Instance : {s_MessageQueueContainerInstances}] {nameof(MessageQueueContainer)} shutting down."); - } -#endif - //As long as this instance is using the pre-defined update stages - if (!m_ProcessUpdateStagesExternally) - { - //Remove ourself from the network loop update system - this.UnregisterAllNetworkUpdates(); - } - - //Dispose of any readers and writers - foreach (var queueHistorySection in m_QueueHistory) - { - foreach (var queueHistoryItemByStage in queueHistorySection.Value) - { - foreach (var queueHistoryItem in queueHistoryItemByStage.Value) - { - queueHistoryItem.Value.QueueWriter?.Dispose(); - queueHistoryItem.Value.QueueReader?.Dispose(); - queueHistoryItem.Value.QueueBuffer?.Dispose(); - } - } - } - m_MessageQueueProcessor.Shutdown(); - - //Clear history and parameters - m_QueueHistory.Clear(); - - ClearParameters(); - } - - /// - /// Cleans up our instance count and warns if there instantiation issues - /// - public void Dispose() - { - Shutdown(); - -#if UNITY_EDITOR || DEVELOPMENT_BUILD - if (s_MessageQueueContainerInstances > 0) - { - if (NetworkLog.CurrentLogLevel == LogLevel.Developer) - { - NetworkLog.LogInfo($"[Instance : {s_MessageQueueContainerInstances}] {nameof(MessageQueueContainer)} disposed."); - } - - s_MessageQueueContainerInstances--; - } - else //This should never happen...if so something else has gone very wrong. - { - if (NetworkLog.CurrentLogLevel >= LogLevel.Normal) - { - NetworkLog.LogError($"[*** Warning ***] {nameof(MessageQueueContainer)} is being disposed twice?"); - } - - throw new Exception("[*** Warning ***] System state is not stable! Check all references to the Dispose method!"); - } -#endif - } - - /// - /// MessageQueueContainer - Constructor - /// Note about processExternally: this values determines if it will register with the Network Update Loop - /// or not. If not, then most likely unit tests are being preformed on this class. The default value is false. - /// - /// - /// determines if it handles processing externally - public MessageQueueContainer(NetworkManager networkManager, uint maxFrameHistory = 0, bool processExternally = false) - { - NetworkManager = networkManager; - -#if UNITY_EDITOR || DEVELOPMENT_BUILD - //Keep track of how many instances we have instantiated - s_MessageQueueContainerInstances++; - - if (NetworkLog.CurrentLogLevel == LogLevel.Developer) - { - NetworkLog.LogInfo($"[Instance : {s_MessageQueueContainerInstances}] {nameof(MessageQueueContainer)} Initialized"); - } -#endif - - m_ProcessUpdateStagesExternally = processExternally; - Initialize(maxFrameHistory); - } - -#if UNITY_INCLUDE_TESTS - /// - /// Enables testing of the MessageQueueContainer - /// - /// - public void SetTestingState(bool enabled) - { - m_IsTestingEnabled = enabled; - } -#endif - } -} diff --git a/com.unity.netcode.gameobjects/Runtime/Messaging/MessageQueue/MessageQueueContainer.cs.meta b/com.unity.netcode.gameobjects/Runtime/Messaging/MessageQueue/MessageQueueContainer.cs.meta deleted file mode 100644 index 940ca413f4..0000000000 --- a/com.unity.netcode.gameobjects/Runtime/Messaging/MessageQueue/MessageQueueContainer.cs.meta +++ /dev/null @@ -1,11 +0,0 @@ -fileFormatVersion: 2 -guid: a7afb55ac18f78a48b5a9af1766d6bfb -MonoImporter: - externalObjects: {} - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: - assetBundleName: - assetBundleVariant: diff --git a/com.unity.netcode.gameobjects/Runtime/Messaging/MessageQueue/MessageQueueHistoryFrame.cs b/com.unity.netcode.gameobjects/Runtime/Messaging/MessageQueue/MessageQueueHistoryFrame.cs deleted file mode 100644 index 20aa522487..0000000000 --- a/com.unity.netcode.gameobjects/Runtime/Messaging/MessageQueue/MessageQueueHistoryFrame.cs +++ /dev/null @@ -1,244 +0,0 @@ -using System.Collections.Generic; -using UnityEngine; - -namespace Unity.Netcode -{ - /// - /// Used by the MessageQueueContainer to hold queued messages - /// - internal class MessageQueueHistoryFrame - { - public enum QueueFrameType - { - Inbound, - Outbound, - } - - public bool IsDirty; //Used to determine if this queue history frame has been reset (cleaned) yet - public bool HasLoopbackData; //Used to determine if a dirt frame is dirty because messages are being looped back betwen HostClient and HostServer - public uint TotalSize; - public List QueueItemOffsets; - - public PooledNetworkBuffer QueueBuffer; - public PooledNetworkWriter QueueWriter; - public MessageQueueHistoryFrame LoopbackHistoryFrame; //Temporary fix for Host mode loopback work around. - - - public PooledNetworkReader QueueReader; - - private int m_QueueItemOffsetIndex; - private MessageFrameItem m_CurrentItem; - private readonly QueueFrameType m_QueueFrameType; - private int m_MaximumClients; - private long m_CurrentStreamSizeMark; - private NetworkUpdateStage m_StreamUpdateStage; //Update stage specific to messages (typically inbound has most potential for variation) - private int m_MaxStreamBounds; - private const int k_MinStreamBounds = 0; - - /// - /// Returns whether this is an inbound or outbound frame - /// - /// - public QueueFrameType GetQueueFrameType() - { - return m_QueueFrameType; - } - - /// - /// Marks the current size of the stream (used primarily for sanity checks) - /// - public void MarkCurrentStreamPosition() - { - if (QueueBuffer != null) - { - m_CurrentStreamSizeMark = QueueBuffer.Position; - } - else - { - m_CurrentStreamSizeMark = 0; - } - } - - /// - /// Returns the current position that was marked (to track size of msg) - /// - /// m_CurrentStreamSizeMark - public long GetCurrentMarkedPosition() - { - return m_CurrentStreamSizeMark; - } - - /// - /// Internal method to get the current Queue Item from the stream at its current position - /// - /// FrameQueueItem - internal MessageFrameItem GetCurrentQueueItem() - { - //Write the packed version of the queueItem to our current queue history buffer - m_CurrentItem.MessageType = (MessageQueueContainer.MessageType)QueueReader.ReadUInt16(); - m_CurrentItem.Timestamp = QueueReader.ReadSingle(); - m_CurrentItem.NetworkId = QueueReader.ReadUInt64(); - if (m_QueueFrameType == QueueFrameType.Outbound) - { - m_CurrentItem.Delivery = (NetworkDelivery)QueueReader.ReadByteDirect(); - } - - //Clear out any current value for the client ids - m_CurrentItem.ClientNetworkIds = new ulong[0]; - - //If outbound, determine if any client ids needs to be added - if (m_QueueFrameType == QueueFrameType.Outbound) - { - //Outbound we care about both channel and clients - int numClients = QueueReader.ReadInt32(); - if (numClients > 0 && numClients < m_MaximumClients) - { - ulong[] clientIdArray = new ulong[numClients]; - for (int i = 0; i < numClients; i++) - { - clientIdArray[i] = QueueReader.ReadUInt64(); - } - - m_CurrentItem.ClientNetworkIds = clientIdArray; - } - } - - m_CurrentItem.UpdateStage = m_StreamUpdateStage; - - //Get the stream size - m_CurrentItem.StreamSize = QueueReader.ReadInt64(); - - //Sanity checking for boundaries - if (m_CurrentItem.StreamSize < m_MaxStreamBounds && m_CurrentItem.StreamSize >= k_MinStreamBounds) - { - //Inbound and Outbound message streams are handled differently - if (m_QueueFrameType == QueueFrameType.Inbound) - { - //Get our offset - long position = QueueReader.ReadInt64(); - - //Always make sure we are positioned at the start of the stream before we write - m_CurrentItem.NetworkBuffer.Position = 0; - - //Write the entire message to the m_CurrentQueueItem stream (1 stream is re-used for all incoming messages) - m_CurrentItem.NetworkWriter.ReadAndWrite(QueueReader, m_CurrentItem.StreamSize); - - //Reset the position back to the offset so std API can process the message properly - //(i.e. minus the already processed header) - m_CurrentItem.NetworkBuffer.Position = position; - } - else - { - //Create a byte array segment for outbound sending - m_CurrentItem.MessageData = QueueReader.CreateArraySegment((int)m_CurrentItem.StreamSize, (int)QueueBuffer.Position); - } - } - else - { - Debug.LogWarning($"{nameof(m_CurrentItem)}.{nameof(MessageFrameItem.StreamSize)} exceeds allowed size ({m_MaxStreamBounds} vs {m_CurrentItem.StreamSize})! Exiting from the current MessageQueue enumeration loop!"); - m_CurrentItem.MessageType = MessageQueueContainer.MessageType.None; - } - - return m_CurrentItem; - } - - /// - /// Handles getting the next queue item from this frame - /// If none are remaining, then it returns a queue item type of NONE - /// - /// FrameQueueItem - internal MessageFrameItem GetNextQueueItem() - { - QueueBuffer.Position = QueueItemOffsets[m_QueueItemOffsetIndex]; - m_QueueItemOffsetIndex++; - if (m_QueueItemOffsetIndex >= QueueItemOffsets.Count) - { - m_CurrentItem.MessageType = MessageQueueContainer.MessageType.None; - return m_CurrentItem; - } - - return GetCurrentQueueItem(); - } - - /// - /// Should be called the first time a queue item is pulled from a queue history frame. - /// This will reset the frame's stream indices and add a new stream and stream writer to the m_CurrentQueueItem instance. - /// - /// FrameQueueItem - internal MessageFrameItem GetFirstQueueItem() - { - if (QueueBuffer.Position > 0) - { - m_QueueItemOffsetIndex = 0; - QueueBuffer.Position = 0; - - if (m_QueueFrameType == QueueFrameType.Inbound) - { - if (m_CurrentItem.NetworkBuffer == null) - { - m_CurrentItem.NetworkBuffer = PooledNetworkBuffer.Get(); - } - - if (m_CurrentItem.NetworkWriter == null) - { - m_CurrentItem.NetworkWriter = PooledNetworkWriter.Get(m_CurrentItem.NetworkBuffer); - } - - if (m_CurrentItem.NetworkReader == null) - { - m_CurrentItem.NetworkReader = PooledNetworkReader.Get(m_CurrentItem.NetworkBuffer); - } - } - - return GetCurrentQueueItem(); - } - - m_CurrentItem.MessageType = MessageQueueContainer.MessageType.None; - return m_CurrentItem; - } - - /// - /// Should be called once all processing of the current frame is complete. - /// This only closes the m_CurrentQueueItem's stream which is used as a "middle-man" (currently) - /// for delivering the message to the method requesting a queue item from a frame. - /// - public void CloseQueue() - { - if (m_CurrentItem.NetworkWriter != null) - { - m_CurrentItem.NetworkWriter.Dispose(); - m_CurrentItem.NetworkWriter = null; - } - - if (m_CurrentItem.NetworkReader != null) - { - m_CurrentItem.NetworkReader.Dispose(); - m_CurrentItem.NetworkReader = null; - } - - if (m_CurrentItem.NetworkBuffer != null) - { - m_CurrentItem.NetworkBuffer.Dispose(); - m_CurrentItem.NetworkBuffer = null; - } - } - - - /// - /// QueueHistoryFrame Constructor - /// - /// Inbound or Outbound - /// Network Update Stage this MessageQueueHistoryFrame is assigned to - /// maximum number of clients - /// maximum size of the message stream a message can have (defaults to 1MB) - public MessageQueueHistoryFrame(QueueFrameType queueType, NetworkUpdateStage updateStage, int maxClients = 512, int maxStreamBounds = 1 << 20) - { - //The added 512 is the Queue History Frame header information, leaving room to grow - m_MaxStreamBounds = maxStreamBounds + 512; - m_MaximumClients = maxClients; - m_QueueFrameType = queueType; - m_CurrentItem = new MessageFrameItem(); - m_StreamUpdateStage = updateStage; - } - } -} diff --git a/com.unity.netcode.gameobjects/Runtime/Messaging/MessageQueue/MessageQueueHistoryFrame.cs.meta b/com.unity.netcode.gameobjects/Runtime/Messaging/MessageQueue/MessageQueueHistoryFrame.cs.meta deleted file mode 100644 index f358c46a3f..0000000000 --- a/com.unity.netcode.gameobjects/Runtime/Messaging/MessageQueue/MessageQueueHistoryFrame.cs.meta +++ /dev/null @@ -1,11 +0,0 @@ -fileFormatVersion: 2 -guid: 4a459a55be3842e45a39f5e6129dbd21 -MonoImporter: - externalObjects: {} - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: - assetBundleName: - assetBundleVariant: diff --git a/com.unity.netcode.gameobjects/Runtime/Messaging/MessageQueue/MessageQueueProcessor.cs b/com.unity.netcode.gameobjects/Runtime/Messaging/MessageQueue/MessageQueueProcessor.cs deleted file mode 100644 index f14a4a1443..0000000000 --- a/com.unity.netcode.gameobjects/Runtime/Messaging/MessageQueue/MessageQueueProcessor.cs +++ /dev/null @@ -1,353 +0,0 @@ -using System; -using System.Collections.Generic; -using Unity.Profiling; -using UnityEngine; - -namespace Unity.Netcode -{ - /// - /// MessageQueueProcessing - /// Handles processing of MessageQueues - /// Inbound to invocation - /// Outbound to send - /// - internal class MessageQueueProcessor - { -#if DEVELOPMENT_BUILD || UNITY_EDITOR - private static ProfilerMarker s_ProcessReceiveQueue = new ProfilerMarker($"{nameof(MessageQueueProcessor)}.{nameof(ProcessReceiveQueue)}"); - private static ProfilerMarker s_ProcessSendQueue = new ProfilerMarker($"{nameof(MessageQueueProcessor)}.{nameof(ProcessSendQueue)}"); -#endif - - // Batcher object used to manage the message batching on the send side - private readonly MessageBatcher m_MessageBatcher = new MessageBatcher(); - private const int k_BatchThreshold = 512; - // Selected mostly arbitrarily... Better solution to come soon. - private const int k_FragmentationThreshold = 1024; - - private bool m_AdvanceInboundFrameHistory = false; - - //The MessageQueueContainer that is associated with this MessageQueueProcessor - private MessageQueueContainer m_MessageQueueContainer; - - private readonly NetworkManager m_NetworkManager; - private readonly List m_TargetIdBuffer = new List(); - - public void Shutdown() - { - m_MessageBatcher.Shutdown(); - } - - public void ProcessMessage(in MessageFrameItem item) - { - try - { - switch (item.MessageType) - { - case MessageQueueContainer.MessageType.ConnectionRequest: - if (m_NetworkManager.IsServer) - { - m_NetworkManager.MessageHandler.HandleConnectionRequest(item.NetworkId, item.NetworkBuffer); - } - - break; - case MessageQueueContainer.MessageType.ConnectionApproved: - if (m_NetworkManager.IsClient) - { - m_NetworkManager.MessageHandler.HandleConnectionApproved(item.NetworkId, item.NetworkBuffer, item.Timestamp); - } - - break; - case MessageQueueContainer.MessageType.ClientRpc: - case MessageQueueContainer.MessageType.ServerRpc: - // Can rely on currentStage == the original updateStage in the buffer - // After all, that's the whole point of it being in the buffer. - m_NetworkManager.InvokeRpc(item, item.UpdateStage); - break; - case MessageQueueContainer.MessageType.CreateObject: - if (m_NetworkManager.IsClient) - { - m_NetworkManager.MessageHandler.HandleAddObject(item.NetworkId, item.NetworkBuffer); - } - - break; - case MessageQueueContainer.MessageType.DestroyObject: - if (m_NetworkManager.IsClient) - { - m_NetworkManager.MessageHandler.HandleDestroyObject(item.NetworkId, item.NetworkBuffer); - } - - break; - case MessageQueueContainer.MessageType.ChangeOwner: - if (m_NetworkManager.IsClient) - { - m_NetworkManager.MessageHandler.HandleChangeOwner(item.NetworkId, item.NetworkBuffer); - } - - break; - case MessageQueueContainer.MessageType.TimeSync: - if (m_NetworkManager.IsClient) - { - m_NetworkManager.MessageHandler.HandleTimeSync(item.NetworkId, item.NetworkBuffer); - } - - break; - - case MessageQueueContainer.MessageType.UnnamedMessage: - m_NetworkManager.MessageHandler.HandleUnnamedMessage(item.NetworkId, item.NetworkBuffer); - break; - case MessageQueueContainer.MessageType.NamedMessage: - m_NetworkManager.MessageHandler.HandleNamedMessage(item.NetworkId, item.NetworkBuffer); - break; - case MessageQueueContainer.MessageType.ServerLog: - if (m_NetworkManager.IsServer && m_NetworkManager.NetworkConfig.EnableNetworkLogs) - { - m_NetworkManager.MessageHandler.HandleNetworkLog(item.NetworkId, item.NetworkBuffer); - } - - break; - case MessageQueueContainer.MessageType.SnapshotData: - m_NetworkManager.MessageHandler.HandleSnapshot(item.NetworkId, item.NetworkBuffer); - break; - case MessageQueueContainer.MessageType.NetworkVariableDelta: - m_NetworkManager.MessageHandler.HandleNetworkVariableDelta(item.NetworkId, item.NetworkBuffer); - break; - case MessageQueueContainer.MessageType.SceneEvent: - m_NetworkManager.MessageHandler.HandleSceneEvent(item.NetworkId, item.NetworkBuffer); - break; - case MessageQueueContainer.MessageType.ParentSync: - if (m_NetworkManager.IsClient) - { - var networkObjectId = item.NetworkReader.ReadUInt64Packed(); - var (isReparented, latestParent) = NetworkObject.ReadNetworkParenting(item.NetworkReader); - if (m_NetworkManager.SpawnManager.SpawnedObjects.ContainsKey(networkObjectId)) - { - var networkObject = m_NetworkManager.SpawnManager.SpawnedObjects[networkObjectId]; - networkObject.SetNetworkParenting(isReparented, latestParent); - networkObject.ApplyNetworkParenting(); - } - else if (NetworkLog.CurrentLogLevel <= LogLevel.Developer) - { - NetworkLog.LogWarning($"Read {item.MessageType} for {nameof(NetworkObject)} #{networkObjectId} but could not find it in the {nameof(m_NetworkManager.SpawnManager.SpawnedObjects)}"); - } - } - - break; - - default: - NetworkLog.LogWarning($"Received unknown message {((int)item.MessageType).ToString()}"); - break; - } - } - catch (Exception ex) - { - Debug.LogException(ex); - - if (NetworkLog.CurrentLogLevel <= LogLevel.Normal) - { - NetworkLog.LogWarning($"A {item.MessageType} threw an exception while executing! Please check Unity logs for more information."); - } - } - } - - /// - /// ProcessReceiveQueue - /// Public facing interface method to start processing all messages in the current inbound frame - /// - public void ProcessReceiveQueue(NetworkUpdateStage currentStage, bool isTesting) - { - if (!ReferenceEquals(m_MessageQueueContainer, null)) - { -#if DEVELOPMENT_BUILD || UNITY_EDITOR - s_ProcessReceiveQueue.Begin(); -#endif - var currentFrame = m_MessageQueueContainer.GetQueueHistoryFrame(MessageQueueHistoryFrame.QueueFrameType.Inbound, currentStage); - var nextFrame = m_MessageQueueContainer.GetQueueHistoryFrame(MessageQueueHistoryFrame.QueueFrameType.Inbound, currentStage, true); - if (nextFrame.IsDirty && nextFrame.HasLoopbackData) - { - m_AdvanceInboundFrameHistory = true; - } - - if (currentFrame != null && currentFrame.IsDirty) - { - var currentQueueItem = currentFrame.GetFirstQueueItem(); - while (currentQueueItem.MessageType != MessageQueueContainer.MessageType.None) - { - m_AdvanceInboundFrameHistory = true; - - if (!isTesting) - { - currentQueueItem.UpdateStage = currentStage; - ProcessMessage(currentQueueItem); - } - - currentQueueItem = currentFrame.GetNextQueueItem(); - } - - //We call this to dispose of the shared stream writer and stream - currentFrame.CloseQueue(); - } - -#if DEVELOPMENT_BUILD || UNITY_EDITOR - s_ProcessReceiveQueue.End(); -#endif - } - } - - public void AdvanceFrameHistoryIfNeeded() - { - if (m_AdvanceInboundFrameHistory) - { - m_MessageQueueContainer.AdvanceFrameHistory(MessageQueueHistoryFrame.QueueFrameType.Inbound); - m_AdvanceInboundFrameHistory = false; - } - } - - /// - /// ProcessSendQueue - /// Called to send both performance RPC and internal messages and then flush the outbound queue - /// - internal void ProcessSendQueue(bool isListening) - { -#if DEVELOPMENT_BUILD || UNITY_EDITOR - s_ProcessSendQueue.Begin(); -#endif - - MessageQueueSendAndFlush(isListening); - -#if DEVELOPMENT_BUILD || UNITY_EDITOR - s_ProcessSendQueue.End(); -#endif - } - - /// - /// FillTargetList - /// Fills a list with the ClientId's an item is targeted to - /// - /// the MessageQueueItem we want targets for - /// the list to fill - private static void FillTargetList(in MessageFrameItem item, List targetList) - { - switch (item.MessageType) - { - case MessageQueueContainer.MessageType.ServerRpc: - targetList.Add(item.NetworkId); - break; - default: - // todo: consider the implications of default usage of queueItem.clientIds - case MessageQueueContainer.MessageType.ClientRpc: - // copy the list - targetList.AddRange(item.ClientNetworkIds); - break; - } - } - - /// - /// Sends all message queue items in the current outbound frame - /// - /// if flase it will just process through the queue items but attempt to send - private void MessageQueueSendAndFlush(bool isListening) - { - var advanceFrameHistory = false; - if (!ReferenceEquals(m_MessageQueueContainer, null)) - { - var currentFrame = m_MessageQueueContainer.GetCurrentFrame(MessageQueueHistoryFrame.QueueFrameType.Outbound, NetworkUpdateStage.PostLateUpdate); - if (currentFrame != null) - { - var currentQueueItem = currentFrame.GetFirstQueueItem(); - while (currentQueueItem.MessageType != MessageQueueContainer.MessageType.None) - { - advanceFrameHistory = true; - if (isListening) - { - m_TargetIdBuffer.Clear(); - FillTargetList(currentQueueItem, m_TargetIdBuffer); - - if (m_MessageQueueContainer.IsUsingBatching()) - { - m_MessageBatcher.QueueItem(m_TargetIdBuffer, currentQueueItem, k_BatchThreshold, SendCallback); - } - else - { - SendFrameQueueItem(m_TargetIdBuffer, currentQueueItem); - } - - foreach (var target in m_TargetIdBuffer) - { - m_NetworkManager.NetworkMetrics.TrackNetworkMessageSent(target, MessageQueueContainer.GetMessageTypeName(currentQueueItem.MessageType), currentQueueItem.MessageData.Count); - } - } - - currentQueueItem = currentFrame.GetNextQueueItem(); - } - - //If the size is < m_BatchThreshold then just send the messages - if (isListening && advanceFrameHistory && m_MessageQueueContainer.IsUsingBatching()) - { - m_MessageBatcher.SendItems(0, SendCallback); - } - } - - //If we processed any messages, then advance to the next frame - if (advanceFrameHistory) - { - m_MessageQueueContainer.AdvanceFrameHistory(MessageQueueHistoryFrame.QueueFrameType.Outbound); - } - } - } - - /// - /// This is the callback from the batcher when it need to send a batch - /// - /// clientId to send to - /// the stream to send - private void SendCallback(ulong clientId, MessageBatcher.SendStream sendStream) - { - var length = (int)sendStream.Buffer.Length; - var bytes = sendStream.Buffer.GetBuffer(); - var sendBuffer = new ArraySegment(bytes, 0, length); - - var networkDelivery = sendStream.Delivery; - // If the length is greater than the fragmented threshold, switch to a fragmented channel. - // This is kind of a hack to get around issues with certain usages patterns on fragmentation with UNet. - // We send everything unfragmented to avoid those issues, and only switch to the fragmented channel - // if we have no other choice. - if (length > k_FragmentationThreshold) - { - networkDelivery = NetworkDelivery.ReliableFragmentedSequenced; - } - - m_MessageQueueContainer.NetworkManager.NetworkMetrics.TrackTransportBytesSent(length); - m_MessageQueueContainer.NetworkManager.NetworkConfig.NetworkTransport.Send(clientId, sendBuffer, networkDelivery); - } - - /// - /// SendFrameQueueItem - /// Sends the Message Queue Item to the specified destination - /// - /// Information on what to send - private void SendFrameQueueItem(IReadOnlyCollection targetIds, in MessageFrameItem item) - { - var networkDelivery = item.Delivery; - // If the length is greater than the fragmented threshold, switch to a fragmented channel. - // This is kind of a hack to get around issues with certain usages patterns on fragmentation with UNet. - // We send everything unfragmented to avoid those issues, and only switch to the fragmented channel - // if we have no other choice. - if (item.MessageData.Count > k_FragmentationThreshold) - { - networkDelivery = NetworkDelivery.ReliableFragmentedSequenced; - } - - foreach (var clientId in targetIds) - { - m_MessageQueueContainer.NetworkManager.NetworkMetrics.TrackTransportBytesSent(item.MessageData.Count); - m_MessageQueueContainer.NetworkManager.NetworkConfig.NetworkTransport.Send(clientId, item.MessageData, networkDelivery); - } - } - - internal MessageQueueProcessor(MessageQueueContainer messageQueueContainer, NetworkManager networkManager) - { - m_MessageQueueContainer = messageQueueContainer; - m_NetworkManager = networkManager; - } - } -} diff --git a/com.unity.netcode.gameobjects/Runtime/Messaging/MessageQueue/MessageQueueProcessor.cs.meta b/com.unity.netcode.gameobjects/Runtime/Messaging/MessageQueue/MessageQueueProcessor.cs.meta deleted file mode 100644 index 31916fc881..0000000000 --- a/com.unity.netcode.gameobjects/Runtime/Messaging/MessageQueue/MessageQueueProcessor.cs.meta +++ /dev/null @@ -1,11 +0,0 @@ -fileFormatVersion: 2 -guid: d81f244f7054e46469f5a3e8a7327024 -MonoImporter: - externalObjects: {} - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: - assetBundleName: - assetBundleVariant: diff --git a/com.unity.netcode.gameobjects/Tests/Editor/Profiling.meta b/com.unity.netcode.gameobjects/Runtime/Messaging/Messages.meta similarity index 77% rename from com.unity.netcode.gameobjects/Tests/Editor/Profiling.meta rename to com.unity.netcode.gameobjects/Runtime/Messaging/Messages.meta index 933cfaf938..6b3c35953c 100644 --- a/com.unity.netcode.gameobjects/Tests/Editor/Profiling.meta +++ b/com.unity.netcode.gameobjects/Runtime/Messaging/Messages.meta @@ -1,5 +1,5 @@ fileFormatVersion: 2 -guid: 30b2fb9742b202343a827caae5ff5bb2 +guid: fd834639d7f09614fa4f3296921871d8 folderAsset: yes DefaultImporter: externalObjects: {} diff --git a/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/ChangeOwnershipMessage.cs b/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/ChangeOwnershipMessage.cs new file mode 100644 index 0000000000..553aa29c54 --- /dev/null +++ b/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/ChangeOwnershipMessage.cs @@ -0,0 +1,53 @@ +namespace Unity.Netcode.Messages +{ + internal struct ChangeOwnershipMessage : INetworkMessage + { + public ulong NetworkObjectId; + public ulong OwnerClientId; + + public void Serialize(ref FastBufferWriter writer) + { + writer.WriteValueSafe(this); + } + + public static void Receive(ref FastBufferReader reader, NetworkContext context) + { + var networkManager = (NetworkManager)context.SystemOwner; + if (!networkManager.IsClient) + { + return; + } + reader.ReadValueSafe(out ChangeOwnershipMessage message); + message.Handle(context.SenderId, networkManager, reader.Length); + } + + public void Handle(ulong senderId, NetworkManager networkManager, int messageSize) + { + if (!networkManager.SpawnManager.SpawnedObjects.TryGetValue(NetworkObjectId, out var networkObject)) + { + if (NetworkLog.CurrentLogLevel <= LogLevel.Normal) + { + NetworkLog.LogWarning($"Trying to handle owner change but {nameof(NetworkObject)} #{NetworkObjectId} does not exist in {nameof(NetworkSpawnManager.SpawnedObjects)} anymore!"); + } + + return; + } + + if (networkObject.OwnerClientId == networkManager.LocalClientId) + { + //We are current owner. + networkObject.InvokeBehaviourOnLostOwnership(); + } + + networkObject.OwnerClientId = OwnerClientId; + + if (OwnerClientId == networkManager.LocalClientId) + { + //We are new owner. + networkObject.InvokeBehaviourOnGainedOwnership(); + } + + networkManager.NetworkMetrics.TrackOwnershipChangeReceived(senderId, networkObject.NetworkObjectId, networkObject.name, messageSize); + } + } +} diff --git a/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/ChangeOwnershipMessage.cs.meta b/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/ChangeOwnershipMessage.cs.meta new file mode 100644 index 0000000000..a99599609e --- /dev/null +++ b/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/ChangeOwnershipMessage.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 841becdc46b20d5408a81bc30ac950f9 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/ConnectionApprovedMessage.cs b/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/ConnectionApprovedMessage.cs new file mode 100644 index 0000000000..2a10ec35f6 --- /dev/null +++ b/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/ConnectionApprovedMessage.cs @@ -0,0 +1,92 @@ +using System; +using System.Collections.Generic; + +namespace Unity.Netcode.Messages +{ + internal struct ConnectionApprovedMessage : INetworkMessage + { + public ulong OwnerClientId; + public int NetworkTick; + public int SceneObjectCount; + + // Not serialized, held as references to serialize NetworkVariable data + public HashSet SpawnedObjectsList; + + public void Serialize(ref FastBufferWriter writer) + { + if (!writer.TryBeginWrite(sizeof(ulong) + sizeof(int) + sizeof(int))) + { + throw new OverflowException( + $"Not enough space in the write buffer to serialize {nameof(ConnectionApprovedMessage)}"); + } + writer.WriteValue(OwnerClientId); + writer.WriteValue(NetworkTick); + writer.WriteValue(SceneObjectCount); + + if (SceneObjectCount != 0) + { + // Serialize NetworkVariable data + foreach (var sobj in SpawnedObjectsList) + { + if (sobj.CheckObjectVisibility == null || sobj.CheckObjectVisibility(OwnerClientId)) + { + sobj.Observers.Add(OwnerClientId); + var sceneObject = sobj.GetMessageSceneObject(OwnerClientId); + sceneObject.Serialize(ref writer); + } + } + } + } + + public static void Receive(ref FastBufferReader reader, NetworkContext context) + { + var networkManager = (NetworkManager)context.SystemOwner; + if (!networkManager.IsClient) + { + return; + } + + if (!reader.TryBeginRead(sizeof(ulong) + sizeof(int) + sizeof(int))) + { + throw new OverflowException( + $"Not enough space in the buffer to read {nameof(ConnectionApprovedMessage)}"); + } + + var message = new ConnectionApprovedMessage(); + reader.ReadValue(out message.OwnerClientId); + reader.ReadValue(out message.NetworkTick); + reader.ReadValue(out message.SceneObjectCount); + message.Handle(ref reader, context.SenderId, networkManager); + } + + public void Handle(ref FastBufferReader reader, ulong clientId, NetworkManager networkManager) + { + networkManager.LocalClientId = OwnerClientId; + + var time = new NetworkTime(networkManager.NetworkTickSystem.TickRate, NetworkTick); + networkManager.NetworkTimeSystem.Reset(time.Time, 0.15f); // Start with a constant RTT of 150 until we receive values from the transport. + + networkManager.ConnectedClients.Add(networkManager.LocalClientId, new NetworkClient { ClientId = networkManager.LocalClientId }); + + // Only if scene management is disabled do we handle NetworkObject synchronization at this point + if (!networkManager.NetworkConfig.EnableSceneManagement) + { + networkManager.SpawnManager.DestroySceneObjects(); + + // Deserializing NetworkVariable data is deferred from Receive() to Handle to avoid needing + // to create a list to hold the data. This is a breach of convention for performance reasons. + for (ushort i = 0; i < SceneObjectCount; i++) + { + var sceneObject = new NetworkObject.SceneObject(); + sceneObject.Deserialize(ref reader); + NetworkObject.AddSceneObject(sceneObject, ref reader, networkManager); + } + + // Mark the client being connected + networkManager.IsConnectedClient = true; + // When scene management is disabled we notify after everything is synchronized + networkManager.InvokeOnClientConnectedCallback(clientId); + } + } + } +} diff --git a/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/ConnectionApprovedMessage.cs.meta b/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/ConnectionApprovedMessage.cs.meta new file mode 100644 index 0000000000..31cdaad7f8 --- /dev/null +++ b/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/ConnectionApprovedMessage.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: ebbc74ce01b073340aa445f3bd59ff62 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/ConnectionRequestMessage.cs b/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/ConnectionRequestMessage.cs new file mode 100644 index 0000000000..1e3a2f67a3 --- /dev/null +++ b/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/ConnectionRequestMessage.cs @@ -0,0 +1,147 @@ +using System; +using System.Runtime.InteropServices; + +namespace Unity.Netcode.Messages +{ + internal struct ConnectionRequestMessage : INetworkMessage + { + public ulong ConfigHash; + + [StructLayout(LayoutKind.Explicit, Size = 512)] + public struct ConnectionDataStorage : IFixedArrayStorage + { + + } + + public FixedUnmanagedArray ConnectionData; + + public bool ShouldSendConnectionData; + + public void Serialize(ref FastBufferWriter writer) + { + if (ShouldSendConnectionData) + { + if (!writer.TryBeginWrite(FastBufferWriter.GetWriteSize(ConfigHash) + + FastBufferWriter.GetWriteSize(ConnectionData))) + { + throw new OverflowException( + $"Not enough space in the write buffer to serialize {nameof(ConnectionRequestMessage)}"); + } + writer.WriteValue(ConfigHash); + writer.WriteValue(ConnectionData.Count); + writer.WriteValue(ConnectionData, ConnectionData.Count); + } + else + { + if (!writer.TryBeginWrite(FastBufferWriter.GetWriteSize(ConfigHash))) + { + throw new OverflowException( + $"Not enough space in the write buffer to serialize {nameof(ConnectionRequestMessage)}"); + } + writer.WriteValue(ConfigHash); + } + } + + public static void Receive(ref FastBufferReader reader, NetworkContext context) + { + var networkManager = (NetworkManager)context.SystemOwner; + if (!networkManager.IsServer) + { + return; + } + + var message = new ConnectionRequestMessage(); + if (networkManager.NetworkConfig.ConnectionApproval) + { + if (!reader.TryBeginRead(FastBufferWriter.GetWriteSize(message.ConfigHash) + + FastBufferWriter.GetWriteSize())) + { + if (NetworkLog.CurrentLogLevel <= LogLevel.Normal) + { + NetworkLog.LogWarning($"Incomplete connection request message given config - possible {nameof(NetworkConfig)} mismatch."); + } + + networkManager.DisconnectClient(context.SenderId); + return; + } + reader.ReadValue(out message.ConfigHash); + + if (!networkManager.NetworkConfig.CompareConfig(message.ConfigHash)) + { + if (NetworkLog.CurrentLogLevel <= LogLevel.Normal) + { + NetworkLog.LogWarning($"{nameof(NetworkConfig)} mismatch. The configuration between the server and client does not match"); + } + + networkManager.DisconnectClient(context.SenderId); + return; + } + + reader.ReadValue(out int length); + if (!reader.TryBeginRead(FastBufferWriter.GetWriteSize() * length)) + { + if (NetworkLog.CurrentLogLevel <= LogLevel.Normal) + { + NetworkLog.LogWarning($"Incomplete connection request message."); + } + + networkManager.DisconnectClient(context.SenderId); + return; + } + reader.ReadValue(out message.ConnectionData, length); + } + else + { + if (!reader.TryBeginRead(FastBufferWriter.GetWriteSize(message.ConfigHash))) + { + if (NetworkLog.CurrentLogLevel <= LogLevel.Normal) + { + NetworkLog.LogWarning($"Incomplete connection request message."); + } + + networkManager.DisconnectClient(context.SenderId); + return; + } + reader.ReadValue(out message.ConfigHash); + + if (!networkManager.NetworkConfig.CompareConfig(message.ConfigHash)) + { + if (NetworkLog.CurrentLogLevel <= LogLevel.Normal) + { + NetworkLog.LogWarning($"{nameof(NetworkConfig)} mismatch. The configuration between the server and client does not match"); + } + + networkManager.DisconnectClient(context.SenderId); + return; + } + } + message.Handle(networkManager, context.SenderId); + } + + public void Handle(NetworkManager networkManager, ulong senderId) + { + if (networkManager.PendingClients.TryGetValue(senderId, out PendingClient client)) + { + // Set to pending approval to prevent future connection requests from being approved + client.ConnectionState = PendingClient.State.PendingApproval; + } + + if (networkManager.NetworkConfig.ConnectionApproval) + { + // Note: Delegate creation allocates. + // Note: ToArray() also allocates. :( + networkManager.InvokeConnectionApproval(ConnectionData.ToArray(), senderId, + (createPlayerObject, playerPrefabHash, approved, position, rotation) => + { + var localCreatePlayerObject = createPlayerObject; + networkManager.HandleApproval(senderId, localCreatePlayerObject, playerPrefabHash, approved, + position, rotation); + }); + } + else + { + networkManager.HandleApproval(senderId, networkManager.NetworkConfig.PlayerPrefab != null, null, true, null, null); + } + } + } +} diff --git a/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/ConnectionRequestMessage.cs.meta b/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/ConnectionRequestMessage.cs.meta new file mode 100644 index 0000000000..6b6ae756d3 --- /dev/null +++ b/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/ConnectionRequestMessage.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: fd160468676e06049ad81dcfb22c3dc2 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/CreateObjectMessage.cs b/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/CreateObjectMessage.cs new file mode 100644 index 0000000000..83a7cae252 --- /dev/null +++ b/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/CreateObjectMessage.cs @@ -0,0 +1,30 @@ +namespace Unity.Netcode.Messages +{ + internal struct CreateObjectMessage : INetworkMessage + { + public NetworkObject.SceneObject ObjectInfo; + + public void Serialize(ref FastBufferWriter writer) + { + ObjectInfo.Serialize(ref writer); + } + + public static void Receive(ref FastBufferReader reader, NetworkContext context) + { + var networkManager = (NetworkManager)context.SystemOwner; + if (!networkManager.IsClient) + { + return; + } + var message = new CreateObjectMessage(); + message.ObjectInfo.Deserialize(ref reader); + message.Handle(context.SenderId, ref reader, networkManager); + } + + public void Handle(ulong senderId, ref FastBufferReader reader, NetworkManager networkManager) + { + var networkObject = NetworkObject.AddSceneObject(ObjectInfo, ref reader, networkManager); + networkManager.NetworkMetrics.TrackObjectSpawnReceived(senderId, networkObject.NetworkObjectId, networkObject.name, reader.Length); + } + } +} diff --git a/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/CreateObjectMessage.cs.meta b/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/CreateObjectMessage.cs.meta new file mode 100644 index 0000000000..1fa4fb6a31 --- /dev/null +++ b/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/CreateObjectMessage.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: ff075de988adf5a4294620aa2d85fcd0 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/DestroyObjectMessage.cs b/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/DestroyObjectMessage.cs new file mode 100644 index 0000000000..a6ecbb3175 --- /dev/null +++ b/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/DestroyObjectMessage.cs @@ -0,0 +1,41 @@ +namespace Unity.Netcode.Messages +{ + internal struct DestroyObjectMessage : INetworkMessage + { + public ulong NetworkObjectId; + + public void Serialize(ref FastBufferWriter writer) + { + writer.WriteValueSafe(this); + } + + public static void Receive(ref FastBufferReader reader, NetworkContext context) + { + var networkManager = (NetworkManager)context.SystemOwner; + if (!networkManager.IsClient) + { + return; + } + reader.ReadValueSafe(out DestroyObjectMessage message); + message.Handle(context.SenderId, networkManager, reader.Length); + } + + public void Handle(ulong senderId, NetworkManager networkManager, int messageSize) + { + if (!networkManager.SpawnManager.SpawnedObjects.TryGetValue(NetworkObjectId, out var networkObject)) + { + // This is the same check and log message that happens inside OnDespawnObject, but we have to do it here + // while we still have access to the network ID, otherwise the log message will be less useful. + if (NetworkLog.CurrentLogLevel <= LogLevel.Normal) + { + NetworkLog.LogWarning($"Trying to destroy {nameof(NetworkObject)} #{NetworkObjectId} but it does not exist in {nameof(NetworkSpawnManager.SpawnedObjects)} anymore!"); + } + + return; + } + + networkManager.NetworkMetrics.TrackObjectDestroyReceived(senderId, NetworkObjectId, networkObject.name, messageSize); + networkManager.SpawnManager.OnDespawnObject(networkObject, true); + } + } +} diff --git a/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/DestroyObjectMessage.cs.meta b/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/DestroyObjectMessage.cs.meta new file mode 100644 index 0000000000..35e4b0468d --- /dev/null +++ b/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/DestroyObjectMessage.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 18473ed11c97e7241aecb0c72d4bffb2 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/NamedMessage.cs b/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/NamedMessage.cs new file mode 100644 index 0000000000..d50ac193f3 --- /dev/null +++ b/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/NamedMessage.cs @@ -0,0 +1,22 @@ +namespace Unity.Netcode.Messages +{ + internal struct NamedMessage : INetworkMessage + { + public ulong Hash; + public FastBufferWriter Data; + + public unsafe void Serialize(ref FastBufferWriter writer) + { + writer.WriteValueSafe(Hash); + writer.WriteBytesSafe(Data.GetUnsafePtr(), Data.Length); + } + + public static void Receive(ref FastBufferReader reader, NetworkContext context) + { + var message = new NamedMessage(); + reader.ReadValueSafe(out message.Hash); + + ((NetworkManager)context.SystemOwner).CustomMessagingManager.InvokeNamedMessage(message.Hash, context.SenderId, ref reader); + } + } +} diff --git a/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/NamedMessage.cs.meta b/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/NamedMessage.cs.meta new file mode 100644 index 0000000000..f3977c4080 --- /dev/null +++ b/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/NamedMessage.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 2b6bcd41dcce65743ba387fc4e2c6fa8 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/NetworkVariableDeltaMessage.cs b/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/NetworkVariableDeltaMessage.cs new file mode 100644 index 0000000000..f548b218fa --- /dev/null +++ b/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/NetworkVariableDeltaMessage.cs @@ -0,0 +1,227 @@ +using System; +using System.Collections.Generic; +using Unity.Collections; + +namespace Unity.Netcode.Messages +{ + /// + /// This particular struct is a little weird because it doesn't actually contain the data + /// it's serializing. Instead, it contains references to the data it needs to do the + /// serialization. This is due to the generally amorphous nature of network variable + /// deltas, since they're all driven by custom virtual method overloads. + /// + internal struct NetworkVariableDeltaMessage : INetworkMessage + { + public ulong NetworkObjectId; + public ushort NetworkBehaviourIndex; + + public HashSet DeliveryMappedNetworkVariableIndex; + public ulong ClientId; + public NetworkBehaviour NetworkBehaviour; + + public void Serialize(ref FastBufferWriter writer) + { + if (!writer.TryBeginWrite(FastBufferWriter.GetWriteSize(NetworkObjectId) + + FastBufferWriter.GetWriteSize(NetworkBehaviourIndex))) + { + throw new OverflowException( + $"Not enough space in the buffer to write {nameof(NetworkVariableDeltaMessage)}"); + } + writer.WriteValue(NetworkObjectId); + writer.WriteValue(NetworkBehaviourIndex); + for (int k = 0; k < NetworkBehaviour.NetworkVariableFields.Count; k++) + { + if (!DeliveryMappedNetworkVariableIndex.Contains(k)) + { + // This var does not belong to the currently iterating delivery group. + if (NetworkBehaviour.NetworkManager.NetworkConfig.EnsureNetworkVariableLengthSafety) + { + writer.WriteValueSafe((short)0); + } + else + { + writer.WriteValueSafe(false); + } + + continue; + } + + // if I'm dirty AND a client, write (server always has all permissions) + // if I'm dirty AND the server AND the client can read me, send. + bool shouldWrite = NetworkBehaviour.NetworkVariableFields[k].ShouldWrite(ClientId, NetworkBehaviour.NetworkManager.IsServer); + + if (NetworkBehaviour.NetworkManager.NetworkConfig.EnsureNetworkVariableLengthSafety) + { + if (!shouldWrite) + { + writer.WriteValueSafe((ushort)0); + } + } + else + { + writer.WriteValueSafe(shouldWrite); + } + + if (shouldWrite) + { + if (NetworkBehaviour.NetworkManager.NetworkConfig.EnsureNetworkVariableLengthSafety) + { + var tmpWriter = new FastBufferWriter(1300, Allocator.Temp, short.MaxValue); + NetworkBehaviour.NetworkVariableFields[k].WriteDelta(ref tmpWriter); + + writer.WriteValueSafe((ushort)tmpWriter.Length); + tmpWriter.CopyTo(ref writer); + } + else + { + NetworkBehaviour.NetworkVariableFields[k].WriteDelta(ref writer); + } + + if (!NetworkBehaviour.NetworkVariableIndexesToResetSet.Contains(k)) + { + NetworkBehaviour.NetworkVariableIndexesToResetSet.Add(k); + NetworkBehaviour.NetworkVariableIndexesToReset.Add(k); + } + + NetworkBehaviour.NetworkManager.NetworkMetrics.TrackNetworkVariableDeltaSent( + ClientId, + NetworkBehaviour.NetworkObjectId, + NetworkBehaviour.name, + NetworkBehaviour.NetworkVariableFields[k].Name, + NetworkBehaviour.__getTypeName(), + writer.Length); + } + } + } + + public static void Receive(ref FastBufferReader reader, NetworkContext context) + { + var networkManager = (NetworkManager)context.SystemOwner; + + var message = new NetworkVariableDeltaMessage(); + if (!reader.TryBeginRead(FastBufferWriter.GetWriteSize(message.NetworkObjectId) + + FastBufferWriter.GetWriteSize(message.NetworkBehaviourIndex))) + { + throw new OverflowException( + $"Not enough data in the buffer to read {nameof(NetworkVariableDeltaMessage)}"); + } + reader.ReadValue(out message.NetworkObjectId); + reader.ReadValue(out message.NetworkBehaviourIndex); + message.Handle(context.SenderId, ref reader, networkManager); + } + + public void Handle(ulong senderId, ref FastBufferReader reader, NetworkManager networkManager) + { + if (networkManager.SpawnManager.SpawnedObjects.TryGetValue(NetworkObjectId, out NetworkObject networkObject)) + { + NetworkBehaviour behaviour = networkObject.GetNetworkBehaviourAtOrderIndex(NetworkBehaviourIndex); + + if (behaviour == null) + { + if (NetworkLog.CurrentLogLevel <= LogLevel.Normal) + { + NetworkLog.LogWarning($"Network variable delta message received for a non-existent behaviour. {nameof(NetworkObjectId)}: {NetworkObjectId}, {nameof(NetworkBehaviourIndex)}: {NetworkBehaviourIndex}"); + } + } + else + { + for (int i = 0; i < behaviour.NetworkVariableFields.Count; i++) + { + ushort varSize = 0; + + if (networkManager.NetworkConfig.EnsureNetworkVariableLengthSafety) + { + reader.ReadValueSafe(out varSize); + + if (varSize == 0) + { + continue; + } + } + else + { + reader.ReadValueSafe(out bool deltaExists); + if (!deltaExists) + { + continue; + } + } + + if (networkManager.IsServer) + { + // we are choosing not to fire an exception here, because otherwise a malicious client could use this to crash the server + if (networkManager.NetworkConfig.EnsureNetworkVariableLengthSafety) + { + if (NetworkLog.CurrentLogLevel <= LogLevel.Normal) + { + NetworkLog.LogWarning($"Client wrote to {typeof(NetworkVariable<>).Name} without permission. => {nameof(NetworkObjectId)}: {NetworkObjectId} - {nameof(NetworkObject.GetNetworkBehaviourOrderIndex)}(): {networkObject.GetNetworkBehaviourOrderIndex(behaviour)} - VariableIndex: {i}"); + NetworkLog.LogError($"[{behaviour.NetworkVariableFields[i].GetType().Name}]"); + } + + reader.Seek(reader.Position + varSize); + continue; + } + + //This client wrote somewhere they are not allowed. This is critical + //We can't just skip this field. Because we don't actually know how to dummy read + //That is, we don't know how many bytes to skip. Because the interface doesn't have a + //Read that gives us the value. Only a Read that applies the value straight away + //A dummy read COULD be added to the interface for this situation, but it's just being too nice. + //This is after all a developer fault. A critical error should be fine. + // - TwoTen + if (NetworkLog.CurrentLogLevel <= LogLevel.Error) + { + NetworkLog.LogError($"Client wrote to {typeof(NetworkVariable<>).Name} without permission. No more variables can be read. This is critical. => {nameof(NetworkObjectId)}: {NetworkObjectId} - {nameof(NetworkObject.GetNetworkBehaviourOrderIndex)}(): {networkObject.GetNetworkBehaviourOrderIndex(behaviour)} - VariableIndex: {i}"); + NetworkLog.LogError($"[{behaviour.NetworkVariableFields[i].GetType().Name}]"); + } + + return; + } + int readStartPos = reader.Position; + + behaviour.NetworkVariableFields[i].ReadDelta(ref reader, networkManager.IsServer); + networkManager.NetworkMetrics.TrackNetworkVariableDeltaReceived( + senderId, + behaviour.NetworkObjectId, + behaviour.name, + behaviour.NetworkVariableFields[i].Name, + behaviour.__getTypeName(), + reader.Length); + + + if (networkManager.NetworkConfig.EnsureNetworkVariableLengthSafety) + { + if (reader.Position > (readStartPos + varSize)) + { + if (NetworkLog.CurrentLogLevel <= LogLevel.Normal) + { + NetworkLog.LogWarning( + $"Var delta read too far. {reader.Position - (readStartPos + varSize)} bytes. => {nameof(NetworkObjectId)}: {NetworkObjectId} - {nameof(NetworkObject.GetNetworkBehaviourOrderIndex)}(): {networkObject.GetNetworkBehaviourOrderIndex(behaviour)} - VariableIndex: {i}"); + } + + reader.Seek(readStartPos + varSize); + } + else if (reader.Position < (readStartPos + varSize)) + { + if (NetworkLog.CurrentLogLevel <= LogLevel.Normal) + { + NetworkLog.LogWarning( + $"Var delta read too little. {(readStartPos + varSize) - reader.Position} bytes. => {nameof(NetworkObjectId)}: {NetworkObjectId} - {nameof(NetworkObject.GetNetworkBehaviourOrderIndex)}(): {networkObject.GetNetworkBehaviourOrderIndex(behaviour)} - VariableIndex: {i}"); + } + + reader.Seek(readStartPos + varSize); + } + } + } + } + } + else if (networkManager.IsServer) + { + if (NetworkLog.CurrentLogLevel <= LogLevel.Normal) + { + NetworkLog.LogWarning($"Network variable delta message received for a non-existent object with {nameof(NetworkObjectId)}: {NetworkObjectId}. This delta was lost."); + } + } + } + } +} diff --git a/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/NetworkVariableDeltaMessage.cs.meta b/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/NetworkVariableDeltaMessage.cs.meta new file mode 100644 index 0000000000..3593f1a23c --- /dev/null +++ b/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/NetworkVariableDeltaMessage.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: dfa1a454cc9fdb647ba89479fa6b8299 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/ParentSyncMessage.cs b/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/ParentSyncMessage.cs new file mode 100644 index 0000000000..05827cc273 --- /dev/null +++ b/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/ParentSyncMessage.cs @@ -0,0 +1,69 @@ +namespace Unity.Netcode.Messages +{ + public struct ParentSyncMessage : INetworkMessage + { + public ulong NetworkObjectId; + + public bool IsReparented; + + #region If(Metadata.IsReparented) + public bool IsLatestParentSet; + + #region If(IsLatestParentSet) + public ulong? LatestParent; + #endregion + #endregion + + public void Serialize(ref FastBufferWriter writer) + { + writer.WriteValueSafe(NetworkObjectId); + writer.WriteValueSafe(IsReparented); + if (IsReparented) + { + writer.WriteValueSafe(IsLatestParentSet); + if (IsLatestParentSet) + { + writer.WriteValueSafe((ulong)LatestParent); + } + } + } + + public static void Receive(ref FastBufferReader reader, NetworkContext context) + { + var networkManager = (NetworkManager)context.SystemOwner; + if (!networkManager.IsClient) + { + return; + } + + var message = new ParentSyncMessage(); + reader.ReadValueSafe(out message.NetworkObjectId); + reader.ReadValueSafe(out message.IsReparented); + if (message.IsReparented) + { + reader.ReadValueSafe(out message.IsLatestParentSet); + if (message.IsLatestParentSet) + { + reader.ReadValueSafe(out ulong latestParent); + message.LatestParent = latestParent; + } + } + + message.Handle(networkManager); + } + + public void Handle(NetworkManager networkManager) + { + if (networkManager.SpawnManager.SpawnedObjects.ContainsKey(NetworkObjectId)) + { + var networkObject = networkManager.SpawnManager.SpawnedObjects[NetworkObjectId]; + networkObject.SetNetworkParenting(IsReparented, LatestParent); + networkObject.ApplyNetworkParenting(); + } + else if (NetworkLog.CurrentLogLevel <= LogLevel.Developer) + { + NetworkLog.LogWarning($"Read {nameof(ParentSyncMessage)} for {nameof(NetworkObject)} #{NetworkObjectId} but could not find it in the {nameof(networkManager.SpawnManager.SpawnedObjects)}"); + } + } + } +} diff --git a/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/ParentSyncMessage.cs.meta b/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/ParentSyncMessage.cs.meta new file mode 100644 index 0000000000..2b019784f2 --- /dev/null +++ b/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/ParentSyncMessage.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 237bfa46868f1ff48863f3a6df2f5506 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/RpcMessage.cs b/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/RpcMessage.cs new file mode 100644 index 0000000000..4246a6d66d --- /dev/null +++ b/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/RpcMessage.cs @@ -0,0 +1,101 @@ +using System; + +namespace Unity.Netcode.Messages +{ + internal struct RpcMessage : INetworkMessage + { + public enum RpcType : byte + { + Server, + Client + } + + public struct Metadata + { + public RpcType Type; + public ulong NetworkObjectId; + public ushort NetworkBehaviourId; + public uint NetworkMethodId; + } + + public Metadata Data; + public FastBufferWriter RPCData; + + + public unsafe void Serialize(ref FastBufferWriter writer) + { + if (!writer.TryBeginWrite(FastBufferWriter.GetWriteSize(Data) + RPCData.Length)) + { + throw new OverflowException("Not enough space in the buffer to store RPC data."); + } + writer.WriteValue(Data); + writer.WriteBytes(RPCData.GetUnsafePtr(), RPCData.Length); + } + + public static void Receive(ref FastBufferReader reader, NetworkContext context) + { + var message = new RpcMessage(); + if (!reader.TryBeginRead(FastBufferWriter.GetWriteSize(message.Data))) + { + throw new OverflowException("Not enough space in the buffer to read RPC data."); + } + reader.ReadValue(out message.Data); + message.Handle(ref reader, (NetworkManager)context.SystemOwner, context.SenderId); + } + + public void Handle(ref FastBufferReader reader, NetworkManager networkManager, ulong senderId) + { + if (NetworkManager.__rpc_func_table.ContainsKey(Data.NetworkMethodId)) + { + if (!networkManager.SpawnManager.SpawnedObjects.ContainsKey(Data.NetworkObjectId)) + { + return; + } + + var networkObject = networkManager.SpawnManager.SpawnedObjects[Data.NetworkObjectId]; + + var networkBehaviour = networkObject.GetNetworkBehaviourAtOrderIndex(Data.NetworkBehaviourId); + if (networkBehaviour == null) + { + return; + } + + var rpcParams = new __RpcParams(); + switch (Data.Type) + { + case RpcType.Server: + rpcParams.Server = new ServerRpcParams + { + Receive = new ServerRpcReceiveParams + { + SenderClientId = senderId + } + }; + break; + case RpcType.Client: + rpcParams.Client = new ClientRpcParams + { + Receive = new ClientRpcReceiveParams + { + } + }; + break; + } + + NetworkManager.__rpc_func_table[Data.NetworkMethodId](networkBehaviour, ref reader, rpcParams); + +#if DEVELOPMENT_BUILD || UNITY_EDITOR + if (NetworkManager.__rpc_name_table.TryGetValue(Data.NetworkMethodId, out var rpcMethodName)) + { + networkManager.NetworkMetrics.TrackRpcReceived( + senderId, + Data.NetworkObjectId, + rpcMethodName, + networkBehaviour.__getTypeName(), + reader.Length); + } +#endif + } + } + } +} diff --git a/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/RpcMessage.cs.meta b/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/RpcMessage.cs.meta new file mode 100644 index 0000000000..e0a661e791 --- /dev/null +++ b/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/RpcMessage.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: f8fc9f8cca6a18b428460b62bce2d8f7 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/SceneEventMessage.cs b/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/SceneEventMessage.cs new file mode 100644 index 0000000000..2b2632f624 --- /dev/null +++ b/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/SceneEventMessage.cs @@ -0,0 +1,19 @@ +namespace Unity.Netcode.Messages +{ + // Todo: Would be lovely to get this one nicely formatted with all the data it sends in the struct + // like most of the other messages when we have some more time and can come back and refactor this. + internal struct SceneEventMessage : INetworkMessage + { + public SceneEventData EventData; + + public void Serialize(ref FastBufferWriter writer) + { + EventData.Serialize(ref writer); + } + + public static void Receive(ref FastBufferReader reader, NetworkContext context) + { + ((NetworkManager)context.SystemOwner).SceneManager.HandleSceneEvent(context.SenderId, ref reader); + } + } +} diff --git a/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/SceneEventMessage.cs.meta b/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/SceneEventMessage.cs.meta new file mode 100644 index 0000000000..9e4e619fcd --- /dev/null +++ b/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/SceneEventMessage.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 152f6ccef0320cf4abcf19099c1997e3 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/ServerLogMessage.cs b/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/ServerLogMessage.cs new file mode 100644 index 0000000000..66a21cc497 --- /dev/null +++ b/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/ServerLogMessage.cs @@ -0,0 +1,51 @@ +namespace Unity.Netcode.Messages +{ + internal struct ServerLogMessage : INetworkMessage + { + public NetworkLog.LogType LogType; + // It'd be lovely to be able to replace this with FixedUnmanagedArray... + // But it's not really practical. On the sending side, the user is likely to want + // to work with strings and would need to convert, and on the receiving side, + // we'd have to convert it to a string to be able to pass it to the log system. + // So an allocation is unavoidable here on both sides. + public string Message; + + + public void Serialize(ref FastBufferWriter writer) + { + writer.WriteValueSafe(LogType); + BytePacker.WriteValuePacked(ref writer, Message); + } + + public static void Receive(ref FastBufferReader reader, NetworkContext context) + { + var networkManager = (NetworkManager)context.SystemOwner; + if (networkManager.IsServer && networkManager.NetworkConfig.EnableNetworkLogs) + { + var message = new ServerLogMessage(); + reader.ReadValueSafe(out message.LogType); + ByteUnpacker.ReadValuePacked(ref reader, out message.Message); + message.Handle(context.SenderId, networkManager, reader.Length); + } + } + + public void Handle(ulong senderId, NetworkManager networkManager, int messageSize) + { + + networkManager.NetworkMetrics.TrackServerLogReceived(senderId, (uint)LogType, messageSize); + + switch (LogType) + { + case NetworkLog.LogType.Info: + NetworkLog.LogInfoServerLocal(Message, senderId); + break; + case NetworkLog.LogType.Warning: + NetworkLog.LogWarningServerLocal(Message, senderId); + break; + case NetworkLog.LogType.Error: + NetworkLog.LogErrorServerLocal(Message, senderId); + break; + } + } + } +} diff --git a/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/ServerLogMessage.cs.meta b/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/ServerLogMessage.cs.meta new file mode 100644 index 0000000000..ebc461d1f1 --- /dev/null +++ b/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/ServerLogMessage.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 9d1c8aae1f7b7194eb3ab1cab260f34f +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/SnapshotDataMessage.cs b/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/SnapshotDataMessage.cs new file mode 100644 index 0000000000..797e5e75db --- /dev/null +++ b/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/SnapshotDataMessage.cs @@ -0,0 +1,127 @@ +using Unity.Collections; +using Unity.Collections.LowLevel.Unsafe; +using UnityEngine; + +namespace Unity.Netcode.Messages +{ + internal struct SnapshotDataMessage : INetworkMessage + { + public int CurrentTick; + public ushort Sequence; + + public ushort Range; + + public byte[] SendMainBuffer; + public NativeArray ReceiveMainBuffer; + + public struct AckData + { + public ushort LastReceivedSequence; + public ushort ReceivedSequenceMask; + } + + public AckData Ack; + + public struct EntryData + { + public ulong NetworkObjectId; + public ushort BehaviourIndex; + public ushort VariableIndex; + public int TickWritten; + public ushort Position; + public ushort Length; + } + + public NativeArray Entries; + + public struct SpawnData + { + public ulong NetworkObjectId; + public uint Hash; + public bool IsSceneObject; + + public bool IsPlayerObject; + public ulong OwnerClientId; + public ulong ParentNetworkId; + public Vector3 Position; + public Quaternion Rotation; + public Vector3 Scale; + + public int TickWritten; + } + + public NativeArray Spawns; + + public struct DespawnData + { + public ulong NetworkObjectId; + public int TickWritten; + } + + public NativeArray Despawns; + + public unsafe void Serialize(ref FastBufferWriter writer) + { + writer.WriteValue(CurrentTick); + writer.WriteValue(Sequence); + + writer.WriteValue(Range); + writer.WriteBytes(SendMainBuffer, Range); + writer.WriteValue(Ack); + + writer.WriteValue((ushort)Entries.Length); + writer.WriteBytes((byte*)Entries.GetUnsafePtr(), Entries.Length * sizeof(EntryData)); + + writer.WriteValue((ushort)Spawns.Length); + writer.WriteBytes((byte*)Spawns.GetUnsafePtr(), Spawns.Length * sizeof(SpawnData)); + + writer.WriteValue((ushort)Despawns.Length); + writer.WriteBytes((byte*)Despawns.GetUnsafePtr(), Despawns.Length * sizeof(DespawnData)); + } + + public static unsafe void Receive(ref FastBufferReader reader, NetworkContext context) + { + var networkManager = (NetworkManager)context.SystemOwner; + var message = new SnapshotDataMessage(); + reader.ReadValue(out message.CurrentTick); + reader.ReadValue(out message.Sequence); + + reader.ReadValue(out message.Range); + message.ReceiveMainBuffer = new NativeArray(message.Range, Allocator.Temp); + reader.ReadBytes((byte*)message.ReceiveMainBuffer.GetUnsafePtr(), message.Range); + reader.ReadValue(out message.Ack); + + reader.ReadValue(out ushort length); + message.Entries = new NativeArray(length, Allocator.Temp); + reader.ReadBytes((byte*)message.Entries.GetUnsafePtr(), message.Entries.Length * sizeof(EntryData)); + + reader.ReadValue(out length); + message.Spawns = new NativeArray(length, Allocator.Temp); + reader.ReadBytes((byte*)message.Spawns.GetUnsafePtr(), message.Spawns.Length * sizeof(SpawnData)); + + reader.ReadValue(out length); + message.Despawns = new NativeArray(length, Allocator.Temp); + reader.ReadBytes((byte*)message.Despawns.GetUnsafePtr(), message.Despawns.Length * sizeof(DespawnData)); + + using (message.ReceiveMainBuffer) + using (message.Entries) + using (message.Spawns) + using (message.Despawns) + { + message.Handle(context.SenderId, networkManager); + } + } + + public void Handle(ulong senderId, NetworkManager networkManager) + { + // todo: temporary hack around bug + if (!networkManager.IsServer) + { + senderId = networkManager.ServerClientId; + } + + var snapshotSystem = networkManager.SnapshotSystem; + snapshotSystem.HandleSnapshot(senderId, this); + } + } +} diff --git a/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/SnapshotDataMessage.cs.meta b/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/SnapshotDataMessage.cs.meta new file mode 100644 index 0000000000..a549a7c571 --- /dev/null +++ b/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/SnapshotDataMessage.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 5cf75026c2ab86646aac16b39d7259ad +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/TimeSyncMessage.cs b/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/TimeSyncMessage.cs new file mode 100644 index 0000000000..380858391b --- /dev/null +++ b/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/TimeSyncMessage.cs @@ -0,0 +1,29 @@ +namespace Unity.Netcode.Messages +{ + internal struct TimeSyncMessage : INetworkMessage + { + public int Tick; + + public void Serialize(ref FastBufferWriter writer) + { + writer.WriteValueSafe(this); + } + + public static void Receive(ref FastBufferReader reader, NetworkContext context) + { + var networkManager = (NetworkManager)context.SystemOwner; + if (!networkManager.IsClient) + { + return; + } + reader.ReadValueSafe(out TimeSyncMessage message); + message.Handle(context.SenderId, networkManager); + } + + public void Handle(ulong senderId, NetworkManager networkManager) + { + var time = new NetworkTime(networkManager.NetworkTickSystem.TickRate, Tick); + networkManager.NetworkTimeSystem.Sync(time.Time, networkManager.NetworkConfig.NetworkTransport.GetCurrentRtt(senderId) / 1000d); + } + } +} diff --git a/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/TimeSyncMessage.cs.meta b/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/TimeSyncMessage.cs.meta new file mode 100644 index 0000000000..8c64b06ba3 --- /dev/null +++ b/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/TimeSyncMessage.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 94afa081c8f5e0a4fb05e0643a968c46 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/UnnamedMessage.cs b/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/UnnamedMessage.cs new file mode 100644 index 0000000000..7fb49e5676 --- /dev/null +++ b/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/UnnamedMessage.cs @@ -0,0 +1,17 @@ +namespace Unity.Netcode.Messages +{ + internal struct UnnamedMessage : INetworkMessage + { + public FastBufferWriter Data; + + public unsafe void Serialize(ref FastBufferWriter writer) + { + writer.WriteBytesSafe(Data.GetUnsafePtr(), Data.Length); + } + + public static void Receive(ref FastBufferReader reader, NetworkContext context) + { + ((NetworkManager)context.SystemOwner).CustomMessagingManager.InvokeUnnamedMessage(context.SenderId, ref reader); + } + } +} diff --git a/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/UnnamedMessage.cs.meta b/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/UnnamedMessage.cs.meta new file mode 100644 index 0000000000..838f2b445e --- /dev/null +++ b/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/UnnamedMessage.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: b7cece0a7c7653648a7bc8fa920843be +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/com.unity.netcode.gameobjects/Runtime/Messaging/MessagingSystem.cs b/com.unity.netcode.gameobjects/Runtime/Messaging/MessagingSystem.cs new file mode 100644 index 0000000000..ca52657925 --- /dev/null +++ b/com.unity.netcode.gameobjects/Runtime/Messaging/MessagingSystem.cs @@ -0,0 +1,521 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Runtime.CompilerServices; +using Unity.Collections; +using UnityEngine; + +namespace Unity.Netcode +{ + + public class InvalidMessageStructureException : SystemException + { + public InvalidMessageStructureException() { } + public InvalidMessageStructureException(string issue) : base(issue) { } + } + + public class MessagingSystem : IDisposable + { + #region Internal Types + private struct ReceiveQueueItem + { + public FastBufferReader Reader; + public MessageHeader Header; + public ulong SenderId; + public float Timestamp; + } + + private struct SendQueueItem + { + public BatchHeader BatchHeader; + public FastBufferWriter Writer; + public readonly NetworkDelivery NetworkDelivery; + + public SendQueueItem(NetworkDelivery delivery, int writerSize, Allocator writerAllocator, int maxWriterSize = -1) + { + Writer = new FastBufferWriter(writerSize, writerAllocator, maxWriterSize); + NetworkDelivery = delivery; + BatchHeader = default; + } + } + + internal delegate void MessageHandler(ref FastBufferReader reader, NetworkContext context); + #endregion + + #region Private Members + private DynamicUnmanagedArray m_IncomingMessageQueue = new DynamicUnmanagedArray(16); + + private MessageHandler[] m_MessageHandlers = new MessageHandler[255]; + private Type[] m_ReverseTypeMap = new Type[255]; + + private Dictionary m_MessageTypes = new Dictionary(); + private NativeHashMap>> m_SendQueues = new NativeHashMap>>(64, Allocator.Persistent); + + private List m_Hooks = new List(); + + private byte m_HighMessageType; + private object m_Owner; + private IMessageSender m_MessageSender; + private ulong m_LocalClientId; + private bool m_Disposed; + #endregion + + internal Type[] MessageTypes => m_ReverseTypeMap; + internal MessageHandler[] MessageHandlers => m_MessageHandlers; + internal int MessageHandlerCount => m_HighMessageType; + + internal byte GetMessageType(Type t) + { + return m_MessageTypes[t]; + } + + public MessagingSystem(IMessageSender messageSender, object owner, ulong localClientId = long.MaxValue) + { + try + { + m_LocalClientId = localClientId; + m_MessageSender = messageSender; + m_Owner = owner; + + var interfaceType = typeof(INetworkMessage); + var implementationTypes = new List(); + foreach (var assembly in AppDomain.CurrentDomain.GetAssemblies()) + { + foreach (var type in assembly.GetTypes()) + { + if (type.IsInterface || type.IsAbstract) + { + continue; + } + + if (interfaceType.IsAssignableFrom(type)) + { + var attributes = type.GetCustomAttributes(typeof(Bind), false); + // If [Bind(ownerType)] isn't provided, it defaults to being bound to NetworkManager + // This is technically a breach of domain by having MessagingSystem know about the existence + // of NetworkManager... but ultimately, Bind is provided to support testing, not to support + // general use of MessagingSystem outside of Netcode for GameObjects, so having MessagingSystem + // know about NetworkManager isn't so bad. Especially since it's just a default value. + // This is just a convenience to keep us and our users from having to use + // [Bind(typeof(NetworkManager))] on every message - only tests that don't want to use + // the full NetworkManager need to worry about it. + var allowedToBind = attributes.Length == 0 && m_Owner is NetworkManager; + for (var i = 0; i < attributes.Length; ++i) + { + var bindAttribute = (Bind)attributes[i]; + if ( + (bindAttribute.BoundType != null && + bindAttribute.BoundType.IsInstanceOfType(m_Owner)) || + (m_Owner == null && bindAttribute.BoundType == null)) + { + allowedToBind = true; + break; + } + } + + if (!allowedToBind) + { + continue; + } + + implementationTypes.Add(type); + } + } + } + + implementationTypes.Sort((a, b) => string.CompareOrdinal(a.FullName, b.FullName)); + foreach (var type in implementationTypes) + { + RegisterMessageType(type); + } + } + catch (Exception) + { + Dispose(); + throw; + } + } + + public void Dispose() + { + if (m_Disposed) + { + return; + } + + var keys = m_SendQueues.GetKeyArray(Allocator.Temp); + using (keys) + { + foreach (var key in keys) + { + ClientDisconnected(key); + } + } + m_SendQueues.Dispose(); + m_IncomingMessageQueue.Dispose(); + m_Disposed = true; + } + + ~MessagingSystem() + { + Dispose(); + } + + public void SetLocalClientId(ulong localClientId) + { + m_LocalClientId = localClientId; + } + + public void Hook(INetworkHooks hooks) + { + m_Hooks.Add(hooks); + } + + private void RegisterMessageType(Type messageType) + { + if (!typeof(INetworkMessage).IsAssignableFrom(messageType)) + { + throw new ArgumentException("RegisterMessageType types must be INetworkMessage types."); + } + + var method = messageType.GetMethod("Receive"); + if (method == null) + { + throw new InvalidMessageStructureException( + $"{messageType.FullName}: All INetworkMessage types must implement public static void Receive(ref FastBufferReader reader, NetworkContext context)"); + } + + var asDelegate = Delegate.CreateDelegate(typeof(MessageHandler), method, false); + if (asDelegate == null) + { + throw new InvalidMessageStructureException( + $"{messageType.FullName}: All INetworkMessage types must implement public static void Receive(ref FastBufferReader reader, NetworkContext context)"); + } + + m_MessageHandlers[m_HighMessageType] = (MessageHandler)asDelegate; + m_ReverseTypeMap[m_HighMessageType] = messageType; + m_MessageTypes[messageType] = m_HighMessageType++; + } + + internal void HandleIncomingData(ulong clientId, ArraySegment data, float receiveTime) + { + unsafe + { + fixed (byte* nativeData = data.Array) + { + var batchReader = + new FastBufferReader(nativeData, Allocator.None, data.Count, data.Offset); + if (!batchReader.TryBeginRead(sizeof(BatchHeader))) + { + NetworkLog.LogWarning("Received a packet too small to contain a BatchHeader. Ignoring it."); + return; + } + + batchReader.ReadValue(out BatchHeader batchHeader); + + for (var hookIdx = 0; hookIdx < m_Hooks.Count; ++hookIdx) + { + m_Hooks[hookIdx].OnBeforeReceiveBatch(clientId, batchHeader.BatchSize, batchReader.Length); + } + + for (var messageIdx = 0; messageIdx < batchHeader.BatchSize; ++messageIdx) + { + if (!batchReader.TryBeginRead(sizeof(MessageHeader))) + { + NetworkLog.LogWarning("Received a batch that didn't have enough data for all of its batches, ending early!"); + return; + } + batchReader.ReadValue(out MessageHeader messageHeader); + if (!batchReader.TryBeginRead(messageHeader.MessageSize)) + { + NetworkLog.LogWarning("Received a message that claimed a size larger than the packet, ending early!"); + return; + } + m_IncomingMessageQueue.Add(new ReceiveQueueItem + { + Header = messageHeader, + SenderId = clientId, + Timestamp = receiveTime, + // Copy the data for this message into a new FastBufferReader that owns that memory. + // We can't guarantee the memory in the ArraySegment stays valid because we don't own it, + // so we must move it to memory we do own. + Reader = new FastBufferReader(batchReader.GetUnsafePtrAtCurrentPosition(), Allocator.TempJob, messageHeader.MessageSize) + }); + batchReader.Seek(batchReader.Position + messageHeader.MessageSize); + } + for (var hookIdx = 0; hookIdx < m_Hooks.Count; ++hookIdx) + { + m_Hooks[hookIdx].OnAfterReceiveBatch(clientId, batchHeader.BatchSize, batchReader.Length); + } + } + } + } + + private bool CanReceive(ulong clientId, Type messageType) + { + for (var hookIdx = 0; hookIdx < m_Hooks.Count; ++hookIdx) + { + if (!m_Hooks[hookIdx].OnVerifyCanReceive(clientId, messageType)) + { + return false; + } + } + + return true; + } + + public void HandleMessage(in MessageHeader header, ref FastBufferReader reader, ulong senderId, float timestamp) + { + var context = new NetworkContext + { + SystemOwner = m_Owner, + SenderId = senderId, + Timestamp = timestamp, + Header = header + }; + var type = m_ReverseTypeMap[header.MessageType]; + if (!CanReceive(senderId, type)) + { + reader.Dispose(); + return; + } + + for (var hookIdx = 0; hookIdx < m_Hooks.Count; ++hookIdx) + { + m_Hooks[hookIdx].OnBeforeReceiveMessage(senderId, type, reader.Length); + } + var handler = m_MessageHandlers[header.MessageType]; +#pragma warning disable CS0728 // Warns that reader may be reassigned within the handler, but the handler does not reassign it. + using (reader) + { + // No user-land message handler exceptions should escape the receive loop. + // If an exception is throw, the message is ignored. + // Example use case: A bad message is received that can't be deserialized and throws + // an OverflowException because it specifies a length greater than the number of bytes in it + // for some dynamic-length value. + try + { + handler.Invoke(ref reader, context); + } + catch (Exception e) + { + Debug.LogError(e); + } + } +#pragma warning restore CS0728 // Warns that reader may be reassigned within the handler, but the handler does not reassign it. + for (var hookIdx = 0; hookIdx < m_Hooks.Count; ++hookIdx) + { + m_Hooks[hookIdx].OnAfterReceiveMessage(senderId, type, reader.Length); + } + } + + internal void ProcessIncomingMessageQueue() + { + for (var i = 0; i < m_IncomingMessageQueue.Count; ++i) + { + // Avoid copies... + ref var item = ref m_IncomingMessageQueue.GetValueRef(i); + HandleMessage(item.Header, ref item.Reader, item.SenderId, item.Timestamp); + } + + m_IncomingMessageQueue.Clear(); + } + + internal void ClientConnected(ulong clientId) + { + m_SendQueues[clientId] = DynamicUnmanagedArray.CreateRef(); + } + + internal void ClientDisconnected(ulong clientId) + { + var queue = m_SendQueues[clientId]; + for (var i = 0; i < queue.Value.Count; ++i) + { + queue.Value.GetValueRef(i).Writer.Dispose(); + } + + DynamicUnmanagedArray.ReleaseRef(queue); + m_SendQueues.Remove(clientId); + } + + private bool CanSend(ulong clientId, Type messageType, NetworkDelivery delivery) + { + for (var hookIdx = 0; hookIdx < m_Hooks.Count; ++hookIdx) + { + if (!m_Hooks[hookIdx].OnVerifyCanSend(clientId, messageType, delivery)) + { + return false; + } + } + + return true; + } + + internal unsafe int SendMessage(in TMessageType message, NetworkDelivery delivery, in TClientIdListType clientIds) + where TMessageType : INetworkMessage + where TClientIdListType : IReadOnlyList + { + var maxSize = delivery == NetworkDelivery.ReliableFragmentedSequenced ? 64000 : 1300; + var tmpSerializer = new FastBufferWriter(1300, Allocator.Temp, maxSize); +#pragma warning disable CS0728 // Warns that tmpSerializer may be reassigned within Serialize, but Serialize does not reassign it. + using (tmpSerializer) + { + message.Serialize(ref tmpSerializer); + + for (var i = 0; i < clientIds.Count; ++i) + { + var clientId = clientIds[i]; + + if (!CanSend(clientId, typeof(TMessageType), delivery)) + { + continue; + } + + for (var hookIdx = 0; hookIdx < m_Hooks.Count; ++hookIdx) + { + m_Hooks[hookIdx].OnBeforeSendMessage(clientId, typeof(TMessageType), delivery); + } + + ref var sendQueueItem = ref m_SendQueues[clientId].Value; + if (sendQueueItem.Count == 0) + { + sendQueueItem.Add(new SendQueueItem(delivery, 1300, Allocator.TempJob, + maxSize)); + sendQueueItem.GetValueRef(0).Writer.Seek(sizeof(BatchHeader)); + } + else + { + ref var lastQueueItem = ref sendQueueItem.GetValueRef(sendQueueItem.Count - 1); + if (lastQueueItem.NetworkDelivery != delivery || + lastQueueItem.Writer.MaxCapacity - lastQueueItem.Writer.Position < tmpSerializer.Length) + { + sendQueueItem.Add(new SendQueueItem(delivery, 1300, Allocator.TempJob, + maxSize)); + sendQueueItem.GetValueRef(sendQueueItem.Count - 1).Writer.Seek(sizeof(BatchHeader)); + } + } + + ref var writeQueueItem = ref sendQueueItem.GetValueRef(sendQueueItem.Count - 1); + writeQueueItem.Writer.TryBeginWrite(sizeof(MessageHeader) + tmpSerializer.Length); + var header = new MessageHeader + { + MessageSize = (short)tmpSerializer.Length, + MessageType = m_MessageTypes[typeof(TMessageType)], + }; + + + if (clientId == m_LocalClientId) + { + m_IncomingMessageQueue.Add(new ReceiveQueueItem + { + Header = header, + Reader = new FastBufferReader(ref tmpSerializer, Allocator.TempJob), + SenderId = clientId, + Timestamp = Time.realtimeSinceStartup + }); + continue; + } + + writeQueueItem.Writer.WriteValue(header); + writeQueueItem.Writer.WriteBytes(tmpSerializer.GetUnsafePtr(), tmpSerializer.Length); + writeQueueItem.BatchHeader.BatchSize++; + for (var hookIdx = 0; hookIdx < m_Hooks.Count; ++hookIdx) + { + m_Hooks[hookIdx].OnAfterSendMessage(clientId, typeof(TMessageType), delivery, tmpSerializer.Length + sizeof(MessageHeader)); + } + } +#pragma warning restore CS0728 // Warns that tmpSerializer may be reassigned within Serialize, but Serialize does not reassign it. + + return tmpSerializer.Length; + } + } + + private struct PointerListWrapper : IReadOnlyList + where T : unmanaged + { + private unsafe T* m_Value; + private int m_Length; + + internal unsafe PointerListWrapper(T* ptr, int length) + { + m_Value = ptr; + m_Length = length; + } + + public int Count + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => m_Length; + } + + public unsafe T this[int index] + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => m_Value[index]; + } + + public IEnumerator GetEnumerator() + { + throw new NotImplementedException(); + } + + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } + } + + internal unsafe int SendMessage(in T message, NetworkDelivery delivery, + ulong* clientIds, int numClientIds) + where T : INetworkMessage + { + return SendMessage(message, delivery, new PointerListWrapper(clientIds, numClientIds)); + } + + internal unsafe int SendMessage(in T message, NetworkDelivery delivery, ulong clientId) + where T : INetworkMessage + { + ulong* clientIds = stackalloc ulong[] { clientId }; + return SendMessage(message, delivery, new PointerListWrapper(clientIds, 1)); + } + + internal void ProcessSendQueues() + { + foreach (var kvp in m_SendQueues) + { + var clientId = kvp.Key; + ref var sendQueueItem = ref kvp.Value.Value; + for (var i = 0; i < sendQueueItem.Count; ++i) + { + ref var queueItem = ref sendQueueItem.GetValueRef(i); + if (queueItem.BatchHeader.BatchSize == 0) + { + queueItem.Writer.Dispose(); + continue; + } + + for (var hookIdx = 0; hookIdx < m_Hooks.Count; ++hookIdx) + { + m_Hooks[hookIdx].OnBeforeSendBatch(clientId, queueItem.BatchHeader.BatchSize, queueItem.Writer.Length, queueItem.NetworkDelivery); + } + + queueItem.Writer.Seek(0); +#if UNITY_EDITOR || DEVELOPMENT_BUILD + // Skipping the Verify and sneaking the write mark in because we know it's fine. + queueItem.Writer.AllowedWriteMark = 2; +#endif + queueItem.Writer.WriteValue(queueItem.BatchHeader); + + m_MessageSender.Send(clientId, queueItem.NetworkDelivery, ref queueItem.Writer); + queueItem.Writer.Dispose(); + + for (var hookIdx = 0; hookIdx < m_Hooks.Count; ++hookIdx) + { + m_Hooks[hookIdx].OnAfterSendBatch(clientId, queueItem.BatchHeader.BatchSize, queueItem.Writer.Length, queueItem.NetworkDelivery); + } + } + sendQueueItem.Clear(); + } + } + } +} diff --git a/com.unity.netcode.gameobjects/Runtime/Messaging/MessagingSystem.cs.meta b/com.unity.netcode.gameobjects/Runtime/Messaging/MessagingSystem.cs.meta new file mode 100644 index 0000000000..2aa51a8da4 --- /dev/null +++ b/com.unity.netcode.gameobjects/Runtime/Messaging/MessagingSystem.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 7a6de3c592caa3a41bdfe9b1e818bcf4 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/com.unity.netcode.gameobjects/Runtime/Messaging/NetworkContext.cs b/com.unity.netcode.gameobjects/Runtime/Messaging/NetworkContext.cs new file mode 100644 index 0000000000..f973177f2a --- /dev/null +++ b/com.unity.netcode.gameobjects/Runtime/Messaging/NetworkContext.cs @@ -0,0 +1,10 @@ +namespace Unity.Netcode +{ + public ref struct NetworkContext + { + public object SystemOwner; + public ulong SenderId; + public float Timestamp; + public MessageHeader Header; + } +} diff --git a/com.unity.netcode.gameobjects/Runtime/Messaging/NetworkContext.cs.meta b/com.unity.netcode.gameobjects/Runtime/Messaging/NetworkContext.cs.meta new file mode 100644 index 0000000000..523510d13f --- /dev/null +++ b/com.unity.netcode.gameobjects/Runtime/Messaging/NetworkContext.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: f9a4d028b61e2b140927b5ebee04d384 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/com.unity.netcode.gameobjects/Runtime/Messaging/RpcParams.cs b/com.unity.netcode.gameobjects/Runtime/Messaging/RpcParams.cs index ae81a523bb..33e69bb852 100644 --- a/com.unity.netcode.gameobjects/Runtime/Messaging/RpcParams.cs +++ b/com.unity.netcode.gameobjects/Runtime/Messaging/RpcParams.cs @@ -1,34 +1,13 @@ +using Unity.Collections; + namespace Unity.Netcode { - public interface IHasUpdateStage + public struct ServerRpcSendParams { - NetworkUpdateStage UpdateStage - { - get; - set; - } - } - - public struct ServerRpcSendParams : IHasUpdateStage - { - private NetworkUpdateStage m_UpdateStage; - - public NetworkUpdateStage UpdateStage - { - get => m_UpdateStage; - set => m_UpdateStage = value; - } } - public struct ServerRpcReceiveParams : IHasUpdateStage + public struct ServerRpcReceiveParams { - private NetworkUpdateStage m_UpdateStage; - - public NetworkUpdateStage UpdateStage - { - get => m_UpdateStage; - set => m_UpdateStage = value; - } public ulong SenderClientId; } @@ -38,27 +17,22 @@ public struct ServerRpcParams public ServerRpcReceiveParams Receive; } - public struct ClientRpcSendParams : IHasUpdateStage + public struct ClientRpcSendParams { - private NetworkUpdateStage m_UpdateStage; - - public NetworkUpdateStage UpdateStage - { - get => m_UpdateStage; - set => m_UpdateStage = value; - } + /// + /// ulong array version of target id list - use either this OR TargetClientIdsNativeArray + /// public ulong[] TargetClientIds; + + /// + /// NativeArray version of target id list - use either this OR TargetClientIds + /// This option avoids any GC allocations but is a bit trickier to use. + /// + public NativeArray? TargetClientIdsNativeArray; } - public struct ClientRpcReceiveParams : IHasUpdateStage + public struct ClientRpcReceiveParams { - private NetworkUpdateStage m_UpdateStage; - - public NetworkUpdateStage UpdateStage - { - get => m_UpdateStage; - set => m_UpdateStage = value; - } } public struct ClientRpcParams diff --git a/com.unity.netcode.gameobjects/Runtime/Metrics/BufferSizeCapture.cs b/com.unity.netcode.gameobjects/Runtime/Metrics/BufferSizeCapture.cs deleted file mode 100644 index f0a0d69f32..0000000000 --- a/com.unity.netcode.gameobjects/Runtime/Metrics/BufferSizeCapture.cs +++ /dev/null @@ -1,65 +0,0 @@ -namespace Unity.Netcode -{ - internal struct BufferSizeCapture - { - private readonly long m_InitialLength; - private readonly NetworkBuffer m_Buffer; - - private long m_PreviousLength; - - public BufferSizeCapture(NetworkBuffer buffer) - { - m_Buffer = buffer; - m_InitialLength = buffer.Length; - m_PreviousLength = m_InitialLength; - } - - public long Flush() - { - var currentLength = m_Buffer.Length; - var segmentLength = currentLength - m_PreviousLength + m_InitialLength; - - m_PreviousLength = currentLength; - - return segmentLength; - } - } - - internal struct CommandContextSizeCapture - { - private readonly InternalCommandContext m_Context; - private long m_OverheadBegin; - private long m_OverheadEnd; - private long m_SegmentBegin; - - public CommandContextSizeCapture(InternalCommandContext context) - { - m_Context = context; - m_OverheadBegin = 0L; - m_OverheadEnd = 0L; - m_SegmentBegin = 0L; - } - - public void StartMeasureOverhead() - { - m_OverheadBegin = m_Context.NetworkWriter.GetStream().Position; - } - - public void StopMeasureOverhead() - { - m_OverheadEnd = m_Context.NetworkWriter.GetStream().Position; - } - - public void StartMeasureSegment() - { - m_SegmentBegin = m_Context.NetworkWriter.GetStream().Position; - } - - public long StopMeasureSegment() - { - var segmentEnd = m_Context.NetworkWriter.GetStream().Position; - - return m_OverheadEnd - m_OverheadBegin + segmentEnd - m_SegmentBegin; - } - } -} diff --git a/com.unity.netcode.gameobjects/Runtime/Metrics/BufferSizeCapture.cs.meta b/com.unity.netcode.gameobjects/Runtime/Metrics/BufferSizeCapture.cs.meta deleted file mode 100644 index d4697c6c67..0000000000 --- a/com.unity.netcode.gameobjects/Runtime/Metrics/BufferSizeCapture.cs.meta +++ /dev/null @@ -1,11 +0,0 @@ -fileFormatVersion: 2 -guid: 669bf3c074851d748bc8bf5ce4b78768 -MonoImporter: - externalObjects: {} - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: - assetBundleName: - assetBundleVariant: diff --git a/com.unity.netcode.gameobjects/Runtime/Metrics/MetricHooks.cs b/com.unity.netcode.gameobjects/Runtime/Metrics/MetricHooks.cs new file mode 100644 index 0000000000..741aac5697 --- /dev/null +++ b/com.unity.netcode.gameobjects/Runtime/Metrics/MetricHooks.cs @@ -0,0 +1,61 @@ +using System; + +namespace Unity.Netcode +{ + internal class MetricHooks : INetworkHooks + { + private readonly NetworkManager m_NetworkManager; + + public MetricHooks(NetworkManager networkManager) + { + m_NetworkManager = networkManager; + } + + + public void OnBeforeSendMessage(ulong clientId, Type messageType, NetworkDelivery delivery) + { + } + + public void OnAfterSendMessage(ulong clientId, Type messageType, NetworkDelivery delivery, int messageSizeBytes) + { + m_NetworkManager.NetworkMetrics.TrackNetworkMessageSent(clientId, messageType.Name, messageSizeBytes); + } + + public void OnBeforeReceiveMessage(ulong senderId, Type messageType, int messageSizeBytes) + { + m_NetworkManager.NetworkMetrics.TrackNetworkMessageReceived(senderId, messageType.Name, messageSizeBytes); + } + + public void OnAfterReceiveMessage(ulong senderId, Type messageType, int messageSizeBytes) + { + } + + public void OnBeforeSendBatch(ulong clientId, int messageCount, int batchSizeInBytes, NetworkDelivery delivery) + { + } + + public void OnAfterSendBatch(ulong clientId, int messageCount, int batchSizeInBytes, NetworkDelivery delivery) + { + m_NetworkManager.NetworkMetrics.TrackTransportBytesSent(batchSizeInBytes); + } + + public void OnBeforeReceiveBatch(ulong senderId, int messageCount, int batchSizeInBytes) + { + m_NetworkManager.NetworkMetrics.TrackTransportBytesReceived(batchSizeInBytes); + } + + public void OnAfterReceiveBatch(ulong senderId, int messageCount, int batchSizeInBytes) + { + } + + public bool OnVerifyCanSend(ulong destinationId, Type messageType, NetworkDelivery delivery) + { + return true; + } + + public bool OnVerifyCanReceive(ulong senderId, Type messageType) + { + return true; + } + } +} diff --git a/com.unity.netcode.gameobjects/Runtime/Metrics/MetricHooks.cs.meta b/com.unity.netcode.gameobjects/Runtime/Metrics/MetricHooks.cs.meta new file mode 100644 index 0000000000..2ad0052672 --- /dev/null +++ b/com.unity.netcode.gameobjects/Runtime/Metrics/MetricHooks.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 07e749f617714f248823af11048bfe46 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/com.unity.netcode.gameobjects/Runtime/NetworkVariable/Collections/NetworkList.cs b/com.unity.netcode.gameobjects/Runtime/NetworkVariable/Collections/NetworkList.cs index 7099b3c53e..ce35aca17f 100644 --- a/com.unity.netcode.gameobjects/Runtime/NetworkVariable/Collections/NetworkList.cs +++ b/com.unity.netcode.gameobjects/Runtime/NetworkVariable/Collections/NetworkList.cs @@ -1,6 +1,5 @@ using System; using System.Collections.Generic; -using System.IO; using Unity.Collections; namespace Unity.Netcode @@ -52,6 +51,7 @@ public NetworkList(IEnumerable values) foreach (var value in values) { m_List.Add(value); + } } @@ -70,40 +70,39 @@ public override bool IsDirty() } /// - public override void WriteDelta(Stream stream) + public override void WriteDelta(ref FastBufferWriter writer) { - using var writer = PooledNetworkWriter.Get(stream); - writer.WriteUInt16Packed((ushort)m_DirtyEvents.Length); + writer.WriteValueSafe((ushort)m_DirtyEvents.Length); for (int i = 0; i < m_DirtyEvents.Length; i++) { - writer.WriteBits((byte)m_DirtyEvents[i].Type, 3); + writer.WriteValueSafe(m_DirtyEvents[i].Type); switch (m_DirtyEvents[i].Type) { case NetworkListEvent.EventType.Add: { - writer.WriteObjectPacked(m_DirtyEvents[i].Value); //BOX + writer.WriteValueSafe(m_DirtyEvents[i].Value); } break; case NetworkListEvent.EventType.Insert: { - writer.WriteInt32Packed(m_DirtyEvents[i].Index); - writer.WriteObjectPacked(m_DirtyEvents[i].Value); //BOX + writer.WriteValueSafe(m_DirtyEvents[i].Index); + writer.WriteValueSafe(m_DirtyEvents[i].Value); } break; case NetworkListEvent.EventType.Remove: { - writer.WriteObjectPacked(m_DirtyEvents[i].Value); //BOX + writer.WriteValueSafe(m_DirtyEvents[i].Value); } break; case NetworkListEvent.EventType.RemoveAt: { - writer.WriteInt32Packed(m_DirtyEvents[i].Index); + writer.WriteValueSafe(m_DirtyEvents[i].Index); } break; case NetworkListEvent.EventType.Value: { - writer.WriteInt32Packed(m_DirtyEvents[i].Index); - writer.WriteObjectPacked(m_DirtyEvents[i].Value); //BOX + writer.WriteValueSafe(m_DirtyEvents[i].Index); + writer.WriteValueSafe(m_DirtyEvents[i].Value); } break; case NetworkListEvent.EventType.Clear: @@ -116,41 +115,40 @@ public override void WriteDelta(Stream stream) } /// - public override void WriteField(Stream stream) + public override void WriteField(ref FastBufferWriter writer) { - using var writer = PooledNetworkWriter.Get(stream); - writer.WriteUInt16Packed((ushort)m_List.Length); + writer.WriteValueSafe((ushort)m_List.Length); for (int i = 0; i < m_List.Length; i++) { - writer.WriteObjectPacked(m_List[i]); //BOX + writer.WriteValueSafe(m_List[i]); } } /// - public override void ReadField(Stream stream) + public override void ReadField(ref FastBufferReader reader) { - using var reader = PooledNetworkReader.Get(stream); m_List.Clear(); - ushort count = reader.ReadUInt16Packed(); + reader.ReadValueSafe(out ushort count); for (int i = 0; i < count; i++) { - m_List.Add((T)reader.ReadObjectPacked(typeof(T))); //BOX + reader.ReadValueSafe(out T value); + m_List.Add(value); } } /// - public override void ReadDelta(Stream stream, bool keepDirtyDelta) + public override void ReadDelta(ref FastBufferReader reader, bool keepDirtyDelta) { - using var reader = PooledNetworkReader.Get(stream); - ushort deltaCount = reader.ReadUInt16Packed(); + reader.ReadValueSafe(out ushort deltaCount); for (int i = 0; i < deltaCount; i++) { - var eventType = (NetworkListEvent.EventType)reader.ReadBits(3); + reader.ReadValueSafe(out NetworkListEvent.EventType eventType); switch (eventType) { case NetworkListEvent.EventType.Add: { - m_List.Add((T)reader.ReadObjectPacked(typeof(T))); //BOX + reader.ReadValueSafe(out T value); + m_List.Add(value); if (OnListChanged != null) { @@ -175,9 +173,10 @@ public override void ReadDelta(Stream stream, bool keepDirtyDelta) break; case NetworkListEvent.EventType.Insert: { - int index = reader.ReadInt32Packed(); + reader.ReadValueSafe(out int index); + reader.ReadValueSafe(out T value); m_List.InsertRangeWithBeginEnd(index, index + 1); - m_List[index] = (T)reader.ReadObjectPacked(typeof(T)); //BOX + m_List[index] = value; if (OnListChanged != null) { @@ -202,8 +201,8 @@ public override void ReadDelta(Stream stream, bool keepDirtyDelta) break; case NetworkListEvent.EventType.Remove: { - var value = (T)reader.ReadObjectPacked(typeof(T)); //BOX - int index = NativeArrayExtensions.IndexOf(m_List, value); + reader.ReadValueSafe(out T value); + int index = m_List.IndexOf(value); if (index == -1) { break; @@ -234,7 +233,7 @@ public override void ReadDelta(Stream stream, bool keepDirtyDelta) break; case NetworkListEvent.EventType.RemoveAt: { - int index = reader.ReadInt32Packed(); + reader.ReadValueSafe(out int index); T value = m_List[index]; m_List.RemoveAt(index); @@ -261,8 +260,8 @@ public override void ReadDelta(Stream stream, bool keepDirtyDelta) break; case NetworkListEvent.EventType.Value: { - int index = reader.ReadInt32Packed(); - var value = (T)reader.ReadObjectPacked(typeof(T)); //BOX + reader.ReadValueSafe(out int index); + reader.ReadValueSafe(out T value); if (index < m_List.Length) { m_List[index] = value; @@ -465,7 +464,7 @@ public struct NetworkListEvent /// /// Enum representing the different operations available for triggering an event. /// - public enum EventType + public enum EventType : byte { /// /// Add diff --git a/com.unity.netcode.gameobjects/Runtime/NetworkVariable/INetworkVariable.cs b/com.unity.netcode.gameobjects/Runtime/NetworkVariable/INetworkVariable.cs new file mode 100644 index 0000000000..18ab561108 --- /dev/null +++ b/com.unity.netcode.gameobjects/Runtime/NetworkVariable/INetworkVariable.cs @@ -0,0 +1,72 @@ +using System.IO; + +namespace Unity.Netcode +{ + /// + /// Interface for network value containers + /// + public interface INetworkVariable + { + /// + /// Gets or sets the name of the network variable's instance + /// (MemberInfo) where it was declared. + /// + string Name { get; } + + /// + /// Resets the dirty state and marks the variable as synced / clean + /// + void ResetDirty(); + + /// + /// Gets Whether or not the container is dirty + /// + /// Whether or not the container is dirty + bool IsDirty(); + + /// + /// Gets Whether or not a specific client can write to the varaible + /// + /// The clientId of the remote client + /// Whether or not the client can write to the variable + bool CanClientWrite(ulong clientId); + + /// + /// Gets Whether or not a specific client can read to the varaible + /// + /// The clientId of the remote client + /// Whether or not the client can read to the variable + bool CanClientRead(ulong clientId); + + /// + /// Writes the dirty changes, that is, the changes since the variable was last dirty, to the writer + /// + /// The stream to write the dirty changes to + void WriteDelta(ref FastBufferWriter writer); + + /// + /// Writes the complete state of the variable to the writer + /// + /// The stream to write the state to + void WriteField(ref FastBufferWriter writer); + + /// + /// Reads delta from the reader and applies them to the internal value + /// + /// The stream to read the delta from + /// Whether or not the delta should be kept as dirty or consumed + void ReadDelta(ref FastBufferReader reader, bool keepDirtyDelta); + + /// + /// Reads the complete state from the reader and applies it + /// + /// The stream to read the state from + void ReadField(ref FastBufferReader reader); + + /// + /// Sets NetworkBehaviour the container belongs to. + /// + /// The behaviour the container behaves to + void SetNetworkBehaviour(NetworkBehaviour behaviour); + } +} diff --git a/com.unity.netcode.gameobjects/Runtime/NetworkVariable/INetworkVariable.cs.meta b/com.unity.netcode.gameobjects/Runtime/NetworkVariable/INetworkVariable.cs.meta new file mode 100644 index 0000000000..8ff8391fb8 --- /dev/null +++ b/com.unity.netcode.gameobjects/Runtime/NetworkVariable/INetworkVariable.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 87f1fd4778c2dab4b8bc02c738cade25 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/com.unity.netcode.gameobjects/Runtime/NetworkVariable/NetworkVariable.cs b/com.unity.netcode.gameobjects/Runtime/NetworkVariable/NetworkVariable.cs index adde44288c..6defd0d8d9 100644 --- a/com.unity.netcode.gameobjects/Runtime/NetworkVariable/NetworkVariable.cs +++ b/com.unity.netcode.gameobjects/Runtime/NetworkVariable/NetworkVariable.cs @@ -1,6 +1,5 @@ using System.Collections.Generic; using UnityEngine; -using System.IO; using System; namespace Unity.Netcode @@ -98,22 +97,22 @@ private protected void Set(T value) /// /// Writes the variable to the writer /// - /// The stream to write the value to - public override void WriteDelta(Stream stream) + /// The stream to write the value to + public override void WriteDelta(ref FastBufferWriter writer) { - WriteField(stream); + WriteField(ref writer); } + /// /// Reads value from the reader and applies it /// - /// The stream to read the value from + /// The stream to read the value from /// Whether or not the container should keep the dirty delta, or mark the delta as consumed - public override void ReadDelta(Stream stream, bool keepDirtyDelta) + public override void ReadDelta(ref FastBufferReader reader, bool keepDirtyDelta) { - using var reader = PooledNetworkReader.Get(stream); T previousValue = m_InternalValue; - m_InternalValue = (T)reader.ReadObjectPacked(typeof(T)); + reader.ReadValueSafe(out m_InternalValue); if (keepDirtyDelta) { @@ -124,16 +123,15 @@ public override void ReadDelta(Stream stream, bool keepDirtyDelta) } /// - public override void ReadField(Stream stream) + public override void ReadField(ref FastBufferReader reader) { - ReadDelta(stream, false); + ReadDelta(ref reader, false); } /// - public override void WriteField(Stream stream) + public override void WriteField(ref FastBufferWriter writer) { - using var writer = PooledNetworkWriter.Get(stream); - writer.WriteObjectPacked(m_InternalValue); //BOX + writer.WriteValueSafe(m_InternalValue); } } } diff --git a/com.unity.netcode.gameobjects/Runtime/NetworkVariable/NetworkVariableBase.cs b/com.unity.netcode.gameobjects/Runtime/NetworkVariable/NetworkVariableBase.cs index 84bc6b5bef..4fe0bb7f67 100644 --- a/com.unity.netcode.gameobjects/Runtime/NetworkVariable/NetworkVariableBase.cs +++ b/com.unity.netcode.gameobjects/Runtime/NetworkVariable/NetworkVariableBase.cs @@ -88,27 +88,28 @@ public bool CanClientRead(ulong clientId) /// /// Writes the dirty changes, that is, the changes since the variable was last dirty, to the writer /// - /// The stream to write the dirty changes to - public abstract void WriteDelta(Stream stream); + /// The stream to write the dirty changes to + public abstract void WriteDelta(ref FastBufferWriter writer); /// /// Writes the complete state of the variable to the writer /// - /// The stream to write the state to - public abstract void WriteField(Stream stream); + /// The stream to write the state to + public abstract void WriteField(ref FastBufferWriter writer); /// /// Reads the complete state from the reader and applies it /// - /// The stream to read the state from - public abstract void ReadField(Stream stream); + /// The stream to read the state from + public abstract void ReadField(ref FastBufferReader reader); /// /// Reads delta from the reader and applies them to the internal value /// - /// The stream to read the delta from + /// The stream to read the delta from /// Whether or not the delta should be kept as dirty or consumed - public abstract void ReadDelta(Stream stream, bool keepDirtyDelta); + + public abstract void ReadDelta(ref FastBufferReader reader, bool keepDirtyDelta); public virtual void Dispose() { diff --git a/com.unity.netcode.gameobjects/Runtime/Profiling/InternalMessageHandlerProfilingDecorator.cs b/com.unity.netcode.gameobjects/Runtime/Profiling/InternalMessageHandlerProfilingDecorator.cs deleted file mode 100644 index 3a76cf735a..0000000000 --- a/com.unity.netcode.gameobjects/Runtime/Profiling/InternalMessageHandlerProfilingDecorator.cs +++ /dev/null @@ -1,172 +0,0 @@ -using System.IO; -using Unity.Profiling; - -namespace Unity.Netcode -{ - internal class InternalMessageHandlerProfilingDecorator : IInternalMessageHandler - { - private readonly ProfilerMarker m_HandleConnectionRequest = new ProfilerMarker($"{nameof(InternalMessageHandler)}.{nameof(HandleConnectionRequest)}"); - private readonly ProfilerMarker m_HandleConnectionApproved = new ProfilerMarker($"{nameof(InternalMessageHandler)}.{nameof(HandleConnectionApproved)}"); - private readonly ProfilerMarker m_HandleAddObject = new ProfilerMarker($"{nameof(InternalMessageHandler)}.{nameof(HandleAddObject)}"); - private readonly ProfilerMarker m_HandleDestroyObject = new ProfilerMarker($"{nameof(InternalMessageHandler)}.{nameof(HandleDestroyObject)}"); - private readonly ProfilerMarker m_HandleSceneEvent = new ProfilerMarker($"{nameof(InternalMessageHandler)}.{nameof(HandleSceneEvent)}"); - private readonly ProfilerMarker m_HandleChangeOwner = new ProfilerMarker($"{nameof(InternalMessageHandler)}.{nameof(HandleChangeOwner)}"); - private readonly ProfilerMarker m_HandleTimeSync = new ProfilerMarker($"{nameof(InternalMessageHandler)}.{nameof(HandleTimeSync)}"); - private readonly ProfilerMarker m_HandleNetworkVariableDelta = new ProfilerMarker($"{nameof(InternalMessageHandler)}.{nameof(HandleNetworkVariableDelta)}"); - private readonly ProfilerMarker m_HandleUnnamedMessage = new ProfilerMarker($"{nameof(InternalMessageHandler)}.{nameof(HandleUnnamedMessage)}"); - private readonly ProfilerMarker m_HandleNamedMessage = new ProfilerMarker($"{nameof(InternalMessageHandler)}.{nameof(HandleNamedMessage)}"); - private readonly ProfilerMarker m_HandleNetworkLog = new ProfilerMarker($"{nameof(InternalMessageHandler)}.{nameof(HandleNetworkLog)}"); - private readonly ProfilerMarker m_MessageReceiveQueueItemServerRpc = new ProfilerMarker($"{nameof(InternalMessageHandler)}.{nameof(MessageReceiveQueueItem)}.{nameof(MessageQueueContainer.MessageType.ServerRpc)}"); - private readonly ProfilerMarker m_MessageReceiveQueueItemClientRpc = new ProfilerMarker($"{nameof(InternalMessageHandler)}.{nameof(MessageReceiveQueueItem)}.{nameof(MessageQueueContainer.MessageType.ClientRpc)}"); - private readonly ProfilerMarker m_MessageReceiveQueueItemInternalMessage = new ProfilerMarker($"{nameof(InternalMessageHandler)}.{nameof(MessageReceiveQueueItem)}.InternalMessage"); - - private readonly IInternalMessageHandler m_MessageHandler; - - internal InternalMessageHandlerProfilingDecorator(IInternalMessageHandler messageHandler) - { - m_MessageHandler = messageHandler; - } - - public NetworkManager NetworkManager => m_MessageHandler.NetworkManager; - - public void HandleConnectionRequest(ulong clientId, Stream stream) - { - m_HandleConnectionRequest.Begin(); - - m_MessageHandler.HandleConnectionRequest(clientId, stream); - - m_HandleConnectionRequest.End(); - } - - public void HandleConnectionApproved(ulong clientId, Stream stream, float receiveTime) - { - m_HandleConnectionApproved.Begin(); - - m_MessageHandler.HandleConnectionApproved(clientId, stream, receiveTime); - - m_HandleConnectionApproved.End(); - } - - public void HandleAddObject(ulong clientId, Stream stream) - { - m_HandleAddObject.Begin(); - - m_MessageHandler.HandleAddObject(clientId, stream); - - m_HandleAddObject.End(); - } - - public void HandleDestroyObject(ulong clientId, Stream stream) - { - m_HandleDestroyObject.Begin(); - - m_MessageHandler.HandleDestroyObject(clientId, stream); - - m_HandleDestroyObject.End(); - } - - - public void HandleChangeOwner(ulong clientId, Stream stream) - { - m_HandleChangeOwner.Begin(); - - m_MessageHandler.HandleChangeOwner(clientId, stream); - - m_HandleChangeOwner.End(); - } - - public void HandleTimeSync(ulong clientId, Stream stream) - { - m_HandleTimeSync.Begin(); - - m_MessageHandler.HandleTimeSync(clientId, stream); - - m_HandleTimeSync.End(); - } - - public void HandleNetworkVariableDelta(ulong clientId, Stream stream) - { - m_HandleNetworkVariableDelta.Begin(); - - m_MessageHandler.HandleNetworkVariableDelta(clientId, stream); - - m_HandleNetworkVariableDelta.End(); - } - - public void HandleSnapshot(ulong clientId, Stream stream) - { - m_HandleNetworkVariableDelta.Begin(); - - m_MessageHandler.HandleSnapshot(clientId, stream); - - m_HandleNetworkVariableDelta.End(); - } - - public void MessageReceiveQueueItem(ulong clientId, Stream stream, float receiveTime, MessageQueueContainer.MessageType messageType) - { - switch (messageType) - { - case MessageQueueContainer.MessageType.ServerRpc: - m_MessageReceiveQueueItemServerRpc.Begin(); - break; - case MessageQueueContainer.MessageType.ClientRpc: - m_MessageReceiveQueueItemClientRpc.Begin(); - break; - default: - m_MessageReceiveQueueItemInternalMessage.Begin(); - break; - } - - m_MessageHandler.MessageReceiveQueueItem(clientId, stream, receiveTime, messageType); - - switch (messageType) - { - case MessageQueueContainer.MessageType.ServerRpc: - m_MessageReceiveQueueItemServerRpc.End(); - break; - case MessageQueueContainer.MessageType.ClientRpc: - m_MessageReceiveQueueItemClientRpc.End(); - break; - default: - m_MessageReceiveQueueItemInternalMessage.End(); - break; - } - } - - public void HandleUnnamedMessage(ulong clientId, Stream stream) - { - m_HandleUnnamedMessage.Begin(); - - m_MessageHandler.HandleUnnamedMessage(clientId, stream); - - m_HandleUnnamedMessage.End(); - } - - public void HandleNamedMessage(ulong clientId, Stream stream) - { - m_HandleNamedMessage.Begin(); - - m_MessageHandler.HandleNamedMessage(clientId, stream); - - m_HandleNamedMessage.End(); - } - - public void HandleNetworkLog(ulong clientId, Stream stream) - { - m_HandleNetworkLog.Begin(); - - m_MessageHandler.HandleNetworkLog(clientId, stream); - - m_HandleNetworkLog.End(); - } - - public void HandleSceneEvent(ulong clientId, Stream stream) - { - m_HandleSceneEvent.Begin(); - - m_MessageHandler.HandleSceneEvent(clientId, stream); - - m_HandleSceneEvent.End(); - } - } -} diff --git a/com.unity.netcode.gameobjects/Runtime/Profiling/InternalMessageHandlerProfilingDecorator.cs.meta b/com.unity.netcode.gameobjects/Runtime/Profiling/InternalMessageHandlerProfilingDecorator.cs.meta deleted file mode 100644 index 3eff6cb487..0000000000 --- a/com.unity.netcode.gameobjects/Runtime/Profiling/InternalMessageHandlerProfilingDecorator.cs.meta +++ /dev/null @@ -1,11 +0,0 @@ -fileFormatVersion: 2 -guid: c8f43d517df0d304cbdba69447759009 -MonoImporter: - externalObjects: {} - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: - assetBundleName: - assetBundleVariant: diff --git a/com.unity.netcode.gameobjects/Runtime/Profiling/ProfilingHooks.cs b/com.unity.netcode.gameobjects/Runtime/Profiling/ProfilingHooks.cs new file mode 100644 index 0000000000..a328be4926 --- /dev/null +++ b/com.unity.netcode.gameobjects/Runtime/Profiling/ProfilingHooks.cs @@ -0,0 +1,90 @@ +using System; +using System.Collections.Generic; +using Unity.Profiling; + +namespace Unity.Netcode +{ + internal class ProfilingHooks : INetworkHooks + { + private Dictionary m_HandlerProfilerMarkers = new Dictionary(); + private Dictionary m_SenderProfilerMarkers = new Dictionary(); + private readonly ProfilerMarker m_SendBatch = new ProfilerMarker($"{nameof(MessagingSystem)}.SendBatch"); + private readonly ProfilerMarker m_ReceiveBatch = new ProfilerMarker($"{nameof(MessagingSystem)}.ReceiveBatchBatch"); + + private ProfilerMarker GetHandlerProfilerMarker(Type type) + { + var result = m_HandlerProfilerMarkers.TryGetValue(type, out var marker); + if (result) + { + return marker; + } + + marker = new ProfilerMarker($"{nameof(MessagingSystem)}.DeserializeAndHandle.{type.Name}"); + m_HandlerProfilerMarkers[type] = marker; + return marker; + } + + private ProfilerMarker GetSenderProfilerMarker(Type type) + { + var result = m_SenderProfilerMarkers.TryGetValue(type, out var marker); + if (result) + { + return marker; + } + + marker = new ProfilerMarker($"{nameof(MessagingSystem)}.SerializeAndEnqueue.{type.Name}"); + m_SenderProfilerMarkers[type] = marker; + return marker; + } + + public void OnBeforeSendMessage(ulong clientId, Type messageType, NetworkDelivery delivery) + { + GetSenderProfilerMarker(messageType).Begin(); + } + + public void OnAfterSendMessage(ulong clientId, Type messageType, NetworkDelivery delivery, int messageSizeBytes) + { + GetSenderProfilerMarker(messageType).End(); + } + + public void OnBeforeReceiveMessage(ulong senderId, Type messageType, int messageSizeBytes) + { + GetHandlerProfilerMarker(messageType).Begin(); + } + + public void OnAfterReceiveMessage(ulong senderId, Type messageType, int messageSizeBytes) + { + GetHandlerProfilerMarker(messageType).End(); + } + + public void OnBeforeSendBatch(ulong clientId, int messageCount, int batchSizeInBytes, NetworkDelivery delivery) + { + m_SendBatch.Begin(); + } + + public void OnAfterSendBatch(ulong clientId, int messageCount, int batchSizeInBytes, NetworkDelivery delivery) + { + m_SendBatch.End(); + } + + public void OnBeforeReceiveBatch(ulong senderId, int messageCount, int batchSizeInBytes) + { + m_ReceiveBatch.Begin(); + } + + public void OnAfterReceiveBatch(ulong senderId, int messageCount, int batchSizeInBytes) + { + m_ReceiveBatch.End(); + } + + public bool OnVerifyCanSend(ulong destinationId, Type messageType, NetworkDelivery delivery) + { + return true; + } + + public bool OnVerifyCanReceive(ulong senderId, Type messageType) + { + return true; + } + } +} diff --git a/com.unity.netcode.gameobjects/Runtime/Profiling/ProfilingHooks.cs.meta b/com.unity.netcode.gameobjects/Runtime/Profiling/ProfilingHooks.cs.meta new file mode 100644 index 0000000000..b30d52e826 --- /dev/null +++ b/com.unity.netcode.gameobjects/Runtime/Profiling/ProfilingHooks.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: da46509a8ea6d1c4390e0102f6422785 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/com.unity.netcode.gameobjects/Runtime/SceneManagement/NetworkSceneManager.cs b/com.unity.netcode.gameobjects/Runtime/SceneManagement/NetworkSceneManager.cs index 9325ad3482..b941bdad31 100644 --- a/com.unity.netcode.gameobjects/Runtime/SceneManagement/NetworkSceneManager.cs +++ b/com.unity.netcode.gameobjects/Runtime/SceneManagement/NetworkSceneManager.cs @@ -1,7 +1,7 @@ using System.Collections.Generic; using System; -using System.IO; using System.Linq; +using Unity.Netcode.Messages; using UnityEngine; using UnityEngine.SceneManagement; @@ -83,13 +83,11 @@ public class SceneEvent /// /// Main class for managing network scenes when is enabled. - /// Uses the message to communicate between the server and client(s) + /// Uses the message to communicate between the server and client(s) /// public class NetworkSceneManager : IDisposable { - private const MessageQueueContainer.MessageType k_MessageType = MessageQueueContainer.MessageType.SceneEvent; - private const NetworkDelivery k_DeliveryType = NetworkDelivery.ReliableSequenced; - private const NetworkUpdateStage k_NetworkUpdateStage = NetworkUpdateStage.EarlyUpdate; + private const NetworkDelivery k_DeliveryType = NetworkDelivery.ReliableFragmentedSequenced; // Used to be able to turn re-synchronization off for future snapshot development purposes. internal static bool DisableReSynchronization; @@ -119,6 +117,7 @@ public class NetworkSceneManager : IDisposable /// public event SceneEventDelegate OnSceneEvent; + /// /// Delegate declaration for the handler that provides /// an additional level of scene loading security and/or validation to assure the scene being loaded /// is valid scene to be loaded in the LoadSceneMode specified. @@ -330,7 +329,6 @@ internal NetworkSceneManager(NetworkManager networkManager) /// loading mode is "a valid scene to be loaded in the LoadSceneMode specified". /// /// index into ScenesInBuild - /// Name of the scene /// LoadSceneMode the scene is going to be loaded /// true (Valid) or false (Invalid) internal bool ValidateSceneBeforeLoading(uint sceneIndex, LoadSceneMode loadSceneMode) @@ -473,26 +471,14 @@ private void SendSceneEventData(ulong[] targetClientIds) // Silently return as there is nothing to be done return; } - - var context = m_NetworkManager.MessageQueueContainer.EnterInternalCommandContext( - k_MessageType, k_DeliveryType, targetClientIds, k_NetworkUpdateStage); - - if (context != null) + var message = new SceneEventMessage { - using var nonNullContext = (InternalCommandContext)context; - var bufferSizeCapture = new CommandContextSizeCapture(nonNullContext); - bufferSizeCapture.StartMeasureSegment(); - - SceneEventData.OnWrite(nonNullContext.NetworkWriter); + EventData = SceneEventData + }; + var size = m_NetworkManager.SendMessage(message, k_DeliveryType, targetClientIds); - var size = bufferSizeCapture.StopMeasureSegment(); - m_NetworkManager.NetworkMetrics.TrackSceneEventSent( - targetClientIds, (uint)SceneEventData.SceneEventType, ScenesInBuild[(int)SceneEventData.SceneIndex], size); - return; - } - - // This should never happen, but if it does something very bad has happened and we should throw an exception - throw new Exception($"{nameof(InternalCommandContext)} is null! {nameof(NetworkSceneManager)} failed to send event notification {SceneEventData.SceneEventType} to target clientIds {targetClientIds}!"); + m_NetworkManager.NetworkMetrics.TrackSceneEventSent( + targetClientIds, (uint)SceneEventData.SceneEventType, ScenesInBuild[(int)SceneEventData.SceneIndex], size); } /// @@ -634,30 +620,24 @@ private SceneEventProgress ValidateSceneEvent(string sceneName, bool isUnloading /// private bool OnSceneEventProgressCompleted(SceneEventProgress sceneEventProgress) { - // Send a message to all clients that all clients are done loading or unloading - var context = m_NetworkManager.MessageQueueContainer.EnterInternalCommandContext( - k_MessageType, k_DeliveryType, m_NetworkManager.ConnectedClientsIds, k_NetworkUpdateStage); - if (context != null) - { - using var nonNullContext = (InternalCommandContext)context; - ClientSynchEventData.SceneEventGuid = sceneEventProgress.Guid; - ClientSynchEventData.SceneIndex = sceneEventProgress.SceneBuildIndex; - ClientSynchEventData.SceneEventType = sceneEventProgress.SceneEventType; - ClientSynchEventData.ClientsCompleted = sceneEventProgress.DoneClients; - ClientSynchEventData.ClientsTimedOut = m_NetworkManager.ConnectedClients.Keys.Except(sceneEventProgress.DoneClients).ToList(); - var bufferSizeCapture = new CommandContextSizeCapture(nonNullContext); - bufferSizeCapture.StartMeasureSegment(); + ClientSynchEventData.SceneEventGuid = sceneEventProgress.Guid; + ClientSynchEventData.SceneIndex = sceneEventProgress.SceneBuildIndex; + ClientSynchEventData.SceneEventType = sceneEventProgress.SceneEventType; + ClientSynchEventData.ClientsCompleted = sceneEventProgress.DoneClients; + ClientSynchEventData.ClientsTimedOut = m_NetworkManager.ConnectedClients.Keys.Except(sceneEventProgress.DoneClients).ToList(); - ClientSynchEventData.OnWrite(nonNullContext.NetworkWriter); + var message = new SceneEventMessage + { + EventData = ClientSynchEventData + }; + var size = m_NetworkManager.SendMessage(message, k_DeliveryType, m_NetworkManager.ConnectedClientsIds); - var size = bufferSizeCapture.StopMeasureSegment(); - m_NetworkManager.NetworkMetrics.TrackSceneEventSent( - m_NetworkManager.ConnectedClientsIds, - (uint)sceneEventProgress.SceneEventType, - ScenesInBuild[(int)sceneEventProgress.SceneBuildIndex], - size); - } + m_NetworkManager.NetworkMetrics.TrackSceneEventSent( + m_NetworkManager.ConnectedClientsIds, + (uint)sceneEventProgress.SceneEventType, + ScenesInBuild[(int)sceneEventProgress.SceneBuildIndex], + size); // Send a local notification to the server that all clients are done loading or unloading OnSceneEvent?.Invoke(new SceneEvent() @@ -942,7 +922,7 @@ public SceneEventProgressStatus LoadScene(string sceneName, LoadSceneMode loadSc /// Handles both forms of scene loading /// /// Stream data associated with the event - private void OnClientSceneLoadingEvent(Stream objectStream) + private void OnClientSceneLoadingEvent() { if (!IsSceneIndexValid(SceneEventData.SceneIndex)) { @@ -1088,7 +1068,7 @@ private void OnServerLoadedScene(Scene scene) { if (!keyValuePairBySceneHandle.Value.IsPlayerObject) { - m_NetworkManager.SpawnManager.SpawnNetworkObjectLocally(keyValuePairBySceneHandle.Value, m_NetworkManager.SpawnManager.GetNetworkObjectId(), true, false, null, null, false, true); + m_NetworkManager.SpawnManager.SpawnNetworkObjectLocally(keyValuePairBySceneHandle.Value, m_NetworkManager.SpawnManager.GetNetworkObjectId(), true, false, null, true); } } } @@ -1102,26 +1082,14 @@ private void OnServerLoadedScene(Scene scene) var clientId = m_NetworkManager.ConnectedClientsList[j].ClientId; if (clientId != m_NetworkManager.ServerClientId) { - var context = m_NetworkManager.MessageQueueContainer.EnterInternalCommandContext( - k_MessageType, k_DeliveryType, new ulong[] { clientId }, k_NetworkUpdateStage); - if (context != null) + SceneEventData.TargetClientId = clientId; + var message = new SceneEventMessage { - // Set the target client id that will be used during in scene NetworkObject serialization - SceneEventData.TargetClientId = clientId; - using var nonNullContext = (InternalCommandContext)context; - - var bufferSizeCapture = new CommandContextSizeCapture(nonNullContext); - bufferSizeCapture.StartMeasureSegment(); - - SceneEventData.OnWrite(nonNullContext.NetworkWriter); + EventData = SceneEventData + }; + var size = m_NetworkManager.SendMessage(message, k_DeliveryType, clientId); - var size = bufferSizeCapture.StopMeasureSegment(); - m_NetworkManager.NetworkMetrics.TrackSceneEventSent(clientId, (uint)SceneEventData.SceneEventType, scene.name, size); - } - else - { - throw new Exception($"{nameof(NetworkSceneManager)} failed to send event notification {SceneEventData.SceneEventType} to target clientId {clientId}!"); - } + m_NetworkManager.NetworkMetrics.TrackSceneEventSent(clientId, (uint)SceneEventData.SceneEventType, scene.name, size); } } @@ -1217,22 +1185,14 @@ internal void SynchronizeNetworkObjects(ulong clientId) ClientSynchEventData.AddSpawnedNetworkObjects(); - var context = m_NetworkManager.MessageQueueContainer.EnterInternalCommandContext( - k_MessageType, k_DeliveryType, new ulong[] { clientId }, k_NetworkUpdateStage); - if (context != null) + var message = new SceneEventMessage { + EventData = ClientSynchEventData + }; + var size = m_NetworkManager.SendMessage(message, k_DeliveryType, clientId); - using var nonNullContext = (InternalCommandContext)context; - - var bufferSizeCapture = new CommandContextSizeCapture(nonNullContext); - bufferSizeCapture.StartMeasureSegment(); - - ClientSynchEventData.OnWrite(nonNullContext.NetworkWriter); - - var size = bufferSizeCapture.StopMeasureSegment(); - m_NetworkManager.NetworkMetrics.TrackSceneEventSent( - clientId, (uint)ClientSynchEventData.SceneEventType, "", size); - } + m_NetworkManager.NetworkMetrics.TrackSceneEventSent( + clientId, (uint)ClientSynchEventData.SceneEventType, "", size); // Notify the local server that the client has been sent the SceneEventData.SceneEventTypes.S2C_Event_Sync event OnSceneEvent?.Invoke(new SceneEvent() @@ -1367,20 +1327,14 @@ private void ClientLoadedSynchronization(uint sceneIndex, int sceneHandle) ClientSynchEventData.SceneEventType = SceneEventData.SceneEventTypes.C2S_LoadComplete; ClientSynchEventData.SceneIndex = sceneIndex; - var context = m_NetworkManager.MessageQueueContainer.EnterInternalCommandContext( - k_MessageType, k_DeliveryType, new ulong[] { m_NetworkManager.ServerClientId }, k_NetworkUpdateStage); - if (context != null) - { - using var nonNullContext = (InternalCommandContext)context; - var bufferSizeCapture = new CommandContextSizeCapture(nonNullContext); - bufferSizeCapture.StartMeasureSegment(); - - ClientSynchEventData.OnWrite(nonNullContext.NetworkWriter); + var message = new SceneEventMessage + { + EventData = ClientSynchEventData + }; + var size = m_NetworkManager.SendMessage(message, k_DeliveryType, m_NetworkManager.ServerClientId); - var size = bufferSizeCapture.StopMeasureSegment(); - m_NetworkManager.NetworkMetrics.TrackSceneEventSent(m_NetworkManager.ServerClientId, (uint)ClientSynchEventData.SceneEventType, sceneName, size); - } + m_NetworkManager.NetworkMetrics.TrackSceneEventSent(m_NetworkManager.ServerClientId, (uint)ClientSynchEventData.SceneEventType, sceneName, size); // Send notification to local client that the scene has finished loading OnSceneEvent?.Invoke(new SceneEvent() @@ -1393,21 +1347,20 @@ private void ClientLoadedSynchronization(uint sceneIndex, int sceneHandle) }); // Check to see if we still have scenes to load and synchronize with - HandleClientSceneEvent(null); + HandleClientSceneEvent(); } /// /// Client Side: /// Handles incoming Scene_Event messages for clients /// - /// data associated with the event - private void HandleClientSceneEvent(Stream stream) + private void HandleClientSceneEvent() { switch (SceneEventData.SceneEventType) { case SceneEventData.SceneEventTypes.S2C_Load: { - OnClientSceneLoadingEvent(stream); + OnClientSceneLoadingEvent(); break; } case SceneEventData.SceneEventTypes.S2C_Unload: @@ -1485,8 +1438,7 @@ private void HandleClientSceneEvent(Stream stream) /// Handles incoming Scene_Event messages for host or server /// /// client who sent the event - /// data associated with the event - private void HandleServerSceneEvent(ulong clientId, Stream stream) + private void HandleServerSceneEvent(ulong clientId) { switch (SceneEventData.SceneEventType) { @@ -1566,33 +1518,24 @@ private void HandleServerSceneEvent(ulong clientId, Stream stream) /// Both Client and Server: Incoming scene event entry point /// /// client who sent the scene event - /// data associated with the scene event - internal void HandleSceneEvent(ulong clientId, Stream stream) + /// data associated with the scene event + internal void HandleSceneEvent(ulong clientId, ref FastBufferReader reader) { if (m_NetworkManager != null) { - if (stream != null) - { - var reader = NetworkReaderPool.GetReader(stream); - SceneEventData.OnRead(reader); - NetworkReaderPool.PutBackInPool(reader); + SceneEventData.Deserialize(ref reader); - m_NetworkManager.NetworkMetrics.TrackSceneEventReceived( - clientId, (uint)SceneEventData.SceneEventType, ScenesInBuild[(int)SceneEventData.SceneIndex], stream.Length); - if (SceneEventData.IsSceneEventClientSide()) - { - HandleClientSceneEvent(stream); - } - else - { - HandleServerSceneEvent(clientId, stream); - } + m_NetworkManager.NetworkMetrics.TrackSceneEventReceived( + clientId, (uint)SceneEventData.SceneEventType, ScenesInBuild[(int)SceneEventData.SceneIndex], reader.Length); + + if (SceneEventData.IsSceneEventClientSide()) + { + HandleClientSceneEvent(); } else { - Debug.LogError($"Scene Event {nameof(OnClientSceneLoadingEvent)} was invoked with a null stream!"); - return; + HandleServerSceneEvent(clientId); } } else diff --git a/com.unity.netcode.gameobjects/Runtime/SceneManagement/SceneEventData.cs b/com.unity.netcode.gameobjects/Runtime/SceneManagement/SceneEventData.cs index 4ac904dd2a..2d9dc9b1ac 100644 --- a/com.unity.netcode.gameobjects/Runtime/SceneManagement/SceneEventData.cs +++ b/com.unity.netcode.gameobjects/Runtime/SceneManagement/SceneEventData.cs @@ -1,14 +1,15 @@ using System.Collections.Generic; using System; using System.Linq; -using UnityEngine; +using Unity.Collections; +using Unity.Netcode.Messages; using UnityEngine.SceneManagement; namespace Unity.Netcode { /// - /// Used by for messages + /// Used by for messages /// Note: This is only when is enabled /// public class SceneEventData : IDisposable @@ -19,7 +20,7 @@ public class SceneEventData : IDisposable /// A Server To Client Event (S2C) /// A Client to Server Event (C2S) /// - public enum SceneEventTypes + public enum SceneEventTypes : byte { /// /// Load a scene @@ -119,7 +120,8 @@ public enum SceneEventTypes /// private List m_NetworkObjectsToBeRemoved = new List(); - internal PooledNetworkBuffer InternalBuffer; + private bool m_HasInternalBuffer; + internal FastBufferReader InternalBuffer; private NetworkManager m_NetworkManager; @@ -304,51 +306,51 @@ private int SortNetworkObjects(NetworkObject first, NetworkObject second) /// Client and Server Side: /// Serializes data based on the SceneEvent type () /// - /// to write the scene event data - internal void OnWrite(NetworkWriter writer) + /// to write the scene event data + internal void Serialize(ref FastBufferWriter writer) { // Write the scene event type - writer.WriteByte((byte)SceneEventType); + writer.WriteValueSafe(SceneEventType); // Write the scene loading mode - writer.WriteByte((byte)LoadSceneMode); + writer.WriteValueSafe(LoadSceneMode); // Write the scene event progress Guid if (SceneEventType != SceneEventTypes.S2C_Sync) { - writer.WriteByteArray(SceneEventGuid.ToByteArray()); + writer.WriteValueSafe(SceneEventGuid); } // Write the scene index and handle - writer.WriteUInt32Packed(SceneIndex); - writer.WriteInt32Packed(SceneHandle); + writer.WriteValueSafe(SceneIndex); + writer.WriteValueSafe(SceneHandle); switch (SceneEventType) { case SceneEventTypes.S2C_Sync: { - WriteSceneSynchronizationData(writer); + WriteSceneSynchronizationData(ref writer); break; } case SceneEventTypes.S2C_Load: { - SerializeScenePlacedObjects(writer); + SerializeScenePlacedObjects(ref writer); break; } case SceneEventTypes.C2S_SyncComplete: { - WriteClientSynchronizationResults(writer); + WriteClientSynchronizationResults(ref writer); break; } case SceneEventTypes.S2C_ReSync: { - WriteClientReSynchronizationData(writer); + WriteClientReSynchronizationData(ref writer); break; } case SceneEventTypes.S2C_LoadComplete: case SceneEventTypes.S2C_UnLoadComplete: { - WriteSceneEventProgressDone(writer); + WriteSceneEventProgressDone(ref writer); break; } } @@ -359,40 +361,41 @@ internal void OnWrite(NetworkWriter writer) /// Called at the end of an S2C_Load event once the scene is loaded and scene placed NetworkObjects /// have been locally spawned /// - internal void WriteSceneSynchronizationData(NetworkWriter writer) + internal void WriteSceneSynchronizationData(ref FastBufferWriter writer) { // Write the scenes we want to load, in the order we want to load them - writer.WriteUIntArrayPacked(ScenesToSynchronize.ToArray()); - writer.WriteUIntArrayPacked(SceneHandlesToSynchronize.ToArray()); + writer.WriteValueSafe(ScenesToSynchronize.ToArray()); + writer.WriteValueSafe(SceneHandlesToSynchronize.ToArray()); + // Store our current position in the stream to come back and say how much data we have written - var positionStart = writer.GetStream().Position; + var positionStart = writer.Position; // Size Place Holder -- Start // !!NOTE!!: Since this is a placeholder to be set after we know how much we have written, // for stream offset purposes this MUST not be a packed value! - writer.WriteUInt32(0); - var totalBytes = 0; + writer.WriteValueSafe((int)0); + int totalBytes = 0; // Write the number of NetworkObjects we are serializing - writer.WriteInt32Packed(m_NetworkObjectsSync.Count()); - - foreach (var networkObject in m_NetworkObjectsSync) - { - var noStart = writer.GetStream().Position; - writer.WriteInt32Packed(networkObject.gameObject.scene.handle); - networkObject.SerializeSceneObject(writer, TargetClientId); - var noStop = writer.GetStream().Position; + writer.WriteValueSafe(m_NetworkObjectsSync.Count()); + for (var i = 0; i < m_NetworkObjectsSync.Count(); ++i) + { + var noStart = writer.Position; + var sceneObject = m_NetworkObjectsSync[i].GetMessageSceneObject(TargetClientId); + writer.WriteValueSafe(m_NetworkObjectsSync[i].gameObject.scene.handle); + sceneObject.Serialize(ref writer); + var noStop = writer.Position; totalBytes += (int)(noStop - noStart); } // Size Place Holder -- End - var positionEnd = writer.GetStream().Position; + var positionEnd = writer.Position; var bytesWritten = (uint)(positionEnd - (positionStart + sizeof(uint))); - writer.GetStream().Position = positionStart; + writer.Seek(positionStart); // Write the total size written to the stream by NetworkObjects being serialized - writer.WriteUInt32(bytesWritten); - writer.GetStream().Position = positionEnd; + writer.WriteValueSafe(bytesWritten); + writer.Seek(positionEnd); } /// @@ -401,14 +404,13 @@ internal void WriteSceneSynchronizationData(NetworkWriter writer) /// have been locally spawned /// Maximum number of objects that could theoretically be synchronized is 65536 /// - internal void SerializeScenePlacedObjects(NetworkWriter writer) + internal void SerializeScenePlacedObjects(ref FastBufferWriter writer) { var numberOfObjects = (ushort)0; - var stream = writer.GetStream(); - var headPosition = stream.Position; + var headPosition = writer.Position; // Write our count place holder (must not be packed!) - writer.WriteUInt16(0); + writer.WriteValueSafe((ushort)0); foreach (var keyValuePairByGlobalObjectIdHash in m_NetworkManager.SceneManager.ScenePlacedObjects) { @@ -417,21 +419,22 @@ internal void SerializeScenePlacedObjects(NetworkWriter writer) if (keyValuePairBySceneHandle.Value.Observers.Contains(TargetClientId)) { // Write our server relative scene handle for the NetworkObject being serialized - writer.WriteInt32Packed(keyValuePairBySceneHandle.Key); + writer.WriteValueSafe(keyValuePairBySceneHandle.Key); // Serialize the NetworkObject - keyValuePairBySceneHandle.Value.SerializeSceneObject(writer, TargetClientId); + var sceneObject = keyValuePairBySceneHandle.Value.GetMessageSceneObject(TargetClientId); + sceneObject.Serialize(ref writer); numberOfObjects++; } } } - var tailPosition = stream.Position; + var tailPosition = writer.Position; // Reposition to our count position to the head before we wrote our object count - stream.Position = headPosition; + writer.Seek(headPosition); // Write number of NetworkObjects serialized (must not be packed!) - writer.WriteUInt16(numberOfObjects); + writer.WriteValueSafe(numberOfObjects); // Set our position back to the tail - stream.Position = tailPosition; + writer.Seek(tailPosition); } /// @@ -439,69 +442,51 @@ internal void SerializeScenePlacedObjects(NetworkWriter writer) /// Deserialize data based on the SceneEvent type. /// /// - internal void OnRead(NetworkReader reader) + internal void Deserialize(ref FastBufferReader reader) { - var sceneEventTypeValue = reader.ReadByte(); - - if (Enum.IsDefined(typeof(SceneEventTypes), sceneEventTypeValue)) - { - SceneEventType = (SceneEventTypes)sceneEventTypeValue; - } - else - { - Debug.LogError($"Serialization Read Error: {nameof(SceneEventType)} vale {sceneEventTypeValue} is not within the range of the defined {nameof(SceneEventTypes)} enumerator!"); - } - - var loadSceneModeValue = reader.ReadByte(); - - if (Enum.IsDefined(typeof(LoadSceneMode), loadSceneModeValue)) - { - LoadSceneMode = (LoadSceneMode)loadSceneModeValue; - } - else - { - Debug.LogError($"Serialization Read Error: {nameof(LoadSceneMode)} vale {loadSceneModeValue} is not within the range of the defined {nameof(LoadSceneMode)} enumerator!"); - } + reader.ReadValueSafe(out SceneEventType); + reader.ReadValueSafe(out LoadSceneMode); if (SceneEventType != SceneEventTypes.S2C_Sync) { - SceneEventGuid = new Guid(reader.ReadByteArray()); + reader.ReadValueSafe(out SceneEventGuid); } - SceneIndex = reader.ReadUInt32Packed(); - SceneHandle = reader.ReadInt32Packed(); + reader.ReadValueSafe(out SceneIndex); + reader.ReadValueSafe(out SceneHandle); switch (SceneEventType) { case SceneEventTypes.S2C_Sync: { - CopySceneSyncrhonizationData(reader); + CopySceneSyncrhonizationData(ref reader); break; } case SceneEventTypes.C2S_SyncComplete: { - CheckClientSynchronizationResults(reader); + CheckClientSynchronizationResults(ref reader); break; } case SceneEventTypes.S2C_Load: { - SetInternalBuffer(); - // We store off the trailing in-scene placed serialized NetworkObject data to - // be processed once we are done loading. - InternalBuffer.Position = 0; - InternalBuffer.CopyUnreadFrom(reader.GetStream()); - InternalBuffer.Position = 0; + unsafe + { + // We store off the trailing in-scene placed serialized NetworkObject data to + // be processed once we are done loading. + m_HasInternalBuffer = true; + InternalBuffer = new FastBufferReader(reader.GetUnsafePtrAtCurrentPosition(), Allocator.TempJob, reader.Length - reader.Position); + } break; } case SceneEventTypes.S2C_ReSync: { - ReadClientReSynchronizationData(reader); + ReadClientReSynchronizationData(ref reader); break; } case SceneEventTypes.S2C_LoadComplete: case SceneEventTypes.S2C_UnLoadComplete: { - ReadSceneEventProgressDone(reader); + ReadSceneEventProgressDone(ref reader); break; } } @@ -513,21 +498,26 @@ internal void OnRead(NetworkReader reader) /// into the internal buffer to be used throughout the synchronization process. /// /// - internal void CopySceneSyncrhonizationData(NetworkReader reader) + internal void CopySceneSyncrhonizationData(ref FastBufferReader reader) { - SetInternalBuffer(); m_NetworkObjectsSync.Clear(); - ScenesToSynchronize = new Queue(reader.ReadUIntArrayPacked()); - SceneHandlesToSynchronize = new Queue(reader.ReadUIntArrayPacked()); - InternalBuffer.Position = 0; + reader.ReadValueSafe(out uint[] scenesToSynchronize); + reader.ReadValueSafe(out uint[] sceneHandlesToSynchronize); + ScenesToSynchronize = new Queue(scenesToSynchronize); + SceneHandlesToSynchronize = new Queue(sceneHandlesToSynchronize); // is not packed! - var sizeToCopy = reader.ReadUInt32(); - - using var writer = PooledNetworkWriter.Get(InternalBuffer); - writer.ReadAndWrite(reader, (long)sizeToCopy); + reader.ReadValueSafe(out int sizeToCopy); + unsafe + { + if (!reader.TryBeginRead(sizeToCopy)) + { + throw new OverflowException("Not enough space in the buffer to read recorded synchronization data size."); + } - InternalBuffer.Position = 0; + m_HasInternalBuffer = true; + InternalBuffer = new FastBufferReader(reader.GetUnsafePtrAtCurrentPosition(), Allocator.TempJob, sizeToCopy); + } } /// @@ -537,19 +527,28 @@ internal void CopySceneSyncrhonizationData(NetworkReader reader) /// internal void DeserializeScenePlacedObjects() { - using var reader = PooledNetworkReader.Get(InternalBuffer); - // is not packed! - var newObjectsCount = reader.ReadUInt16(); - - for (ushort i = 0; i < newObjectsCount; i++) + try { - // Set our relative scene to the NetworkObject - m_NetworkManager.SceneManager.SetTheSceneBeingSynchronized(reader.ReadInt32Packed()); + // is not packed! + InternalBuffer.ReadValueSafe(out ushort newObjectsCount); - // Deserialize the NetworkObject - NetworkObject.DeserializeSceneObject(InternalBuffer as NetworkBuffer, reader, m_NetworkManager); + for (ushort i = 0; i < newObjectsCount; i++) + { + InternalBuffer.ReadValueSafe(out int sceneHandle); + // Set our relative scene to the NetworkObject + m_NetworkManager.SceneManager.SetTheSceneBeingSynchronized(sceneHandle); + + // Deserialize the NetworkObject + var sceneObject = new NetworkObject.SceneObject(); + sceneObject.Deserialize(ref InternalBuffer); + NetworkObject.AddSceneObject(sceneObject, ref InternalBuffer, m_NetworkManager); + } + } + finally + { + InternalBuffer.Dispose(); + m_HasInternalBuffer = false; } - ReleaseInternalBuffer(); } /// @@ -559,9 +558,9 @@ internal void DeserializeScenePlacedObjects() /// client handles any returned values by the server. /// /// - internal void ReadClientReSynchronizationData(NetworkReader reader) + internal void ReadClientReSynchronizationData(ref FastBufferReader reader) { - var networkObjectsToRemove = reader.ReadULongArrayPacked(); + reader.ReadValueSafe(out uint[] networkObjectsToRemove); if (networkObjectsToRemove.Length > 0) { @@ -613,10 +612,10 @@ internal void ReadClientReSynchronizationData(NetworkReader reader) /// the server will compile a list and send back an Event_ReSync message to the client. /// /// - internal void WriteClientReSynchronizationData(NetworkWriter writer) + internal void WriteClientReSynchronizationData(ref FastBufferWriter writer) { //Write how many objects need to be removed - writer.WriteULongArrayPacked(m_NetworkObjectsToBeRemoved.ToArray()); + writer.WriteValueSafe(m_NetworkObjectsToBeRemoved.ToArray()); } /// @@ -637,13 +636,13 @@ internal bool ClientNeedsReSynchronization() /// have since been despawned. /// /// - internal void CheckClientSynchronizationResults(NetworkReader reader) + internal void CheckClientSynchronizationResults(ref FastBufferReader reader) { m_NetworkObjectsToBeRemoved.Clear(); - var networkObjectIdCount = reader.ReadUInt32Packed(); + reader.ReadValueSafe(out uint networkObjectIdCount); for (int i = 0; i < networkObjectIdCount; i++) { - var networkObjectId = (ulong)reader.ReadUInt32Packed(); + reader.ReadValueSafe(out uint networkObjectId); if (!m_NetworkManager.SpawnManager.SpawnedObjects.ContainsKey(networkObjectId)) { m_NetworkObjectsToBeRemoved.Add(networkObjectId); @@ -659,13 +658,13 @@ internal void CheckClientSynchronizationResults(NetworkReader reader) /// of NetworkObjects that might have been despawned while the client was processing the Event_Sync. /// /// - internal void WriteClientSynchronizationResults(NetworkWriter writer) + internal void WriteClientSynchronizationResults(ref FastBufferWriter writer) { //Write how many objects were spawned - writer.WriteUInt32Packed((uint)m_NetworkObjectsSync.Count); + writer.WriteValueSafe((uint)m_NetworkObjectsSync.Count); foreach (var networkObject in m_NetworkObjectsSync) { - writer.WriteUInt32Packed((uint)networkObject.NetworkObjectId); + writer.WriteValueSafe((uint)networkObject.NetworkObjectId); } } @@ -675,46 +674,55 @@ internal void WriteClientSynchronizationResults(NetworkWriter writer) /// it is finished loading. The client will also build a list of NetworkObjects that it spawned during /// this process which will be used as part of the Event_Sync_Complete response. /// - /// /// internal void SynchronizeSceneNetworkObjects(NetworkManager networkManager) { - using var reader = PooledNetworkReader.Get(InternalBuffer); - // Process all NetworkObjects for this scene - var newObjectsCount = reader.ReadInt32Packed(); - - for (int i = 0; i < newObjectsCount; i++) + try { - /// We want to make sure for each NetworkObject we have the appropriate scene selected as the scene that is - /// currently being synchronized. This assures in-scene placed NetworkObjects will use the right NetworkObject - /// from the list of populated - m_NetworkManager.SceneManager.SetTheSceneBeingSynchronized(reader.ReadInt32Packed()); + // Process all NetworkObjects for this scene + InternalBuffer.ReadValueSafe(out int newObjectsCount); - var spawnedNetworkObject = NetworkObject.DeserializeSceneObject(InternalBuffer, reader, networkManager); - if (!m_NetworkObjectsSync.Contains(spawnedNetworkObject)) + for (int i = 0; i < newObjectsCount; i++) { - m_NetworkObjectsSync.Add(spawnedNetworkObject); + // We want to make sure for each NetworkObject we have the appropriate scene selected as the scene that is + // currently being synchronized. This assures in-scene placed NetworkObjects will use the right NetworkObject + // from the list of populated + InternalBuffer.ReadValueSafe(out int handle); + m_NetworkManager.SceneManager.SetTheSceneBeingSynchronized(handle); + + var sceneObject = new NetworkObject.SceneObject(); + sceneObject.Deserialize(ref InternalBuffer); + + var spawnedNetworkObject = NetworkObject.AddSceneObject(sceneObject, ref InternalBuffer, networkManager); + if (!m_NetworkObjectsSync.Contains(spawnedNetworkObject)) + { + m_NetworkObjectsSync.Add(spawnedNetworkObject); + } } } - ReleaseInternalBuffer(); + finally + { + InternalBuffer.Dispose(); + m_HasInternalBuffer = false; + } } /// /// Writes the all clients loaded or unloaded completed and timed out lists /// /// - internal void WriteSceneEventProgressDone(NetworkWriter writer) + internal void WriteSceneEventProgressDone(ref FastBufferWriter writer) { - writer.WriteUInt16Packed((ushort)ClientsCompleted.Count); + writer.WriteValueSafe((ushort)ClientsCompleted.Count); foreach (var clientId in ClientsCompleted) { - writer.WriteUInt64Packed(clientId); + writer.WriteValueSafe(clientId); } - writer.WriteUInt16Packed((ushort)ClientsTimedOut.Count); + writer.WriteValueSafe((ushort)ClientsTimedOut.Count); foreach (var clientId in ClientsTimedOut) { - writer.WriteUInt64Packed(clientId); + writer.WriteValueSafe(clientId); } } @@ -722,43 +730,22 @@ internal void WriteSceneEventProgressDone(NetworkWriter writer) /// Reads the all clients loaded or unloaded completed and timed out lists /// /// - internal void ReadSceneEventProgressDone(NetworkReader reader) + internal void ReadSceneEventProgressDone(ref FastBufferReader reader) { - var completedCount = reader.ReadUInt16Packed(); + reader.ReadValueSafe(out ushort completedCount); ClientsCompleted = new List(); for (int i = 0; i < completedCount; i++) { - ClientsCompleted.Add(reader.ReadUInt64Packed()); + reader.ReadValueSafe(out ulong clientId); + ClientsCompleted.Add(clientId); } - var timedOutCount = reader.ReadUInt16Packed(); + reader.ReadValueSafe(out ushort timedOutCount); ClientsTimedOut = new List(); for (int i = 0; i < timedOutCount; i++) { - ClientsTimedOut.Add(reader.ReadUInt64Packed()); - } - } - - /// - /// Gets a PooledNetworkBuffer if needed - /// - private void SetInternalBuffer() - { - if (InternalBuffer == null) - { - InternalBuffer = NetworkBufferPool.GetBuffer(); - } - } - - /// - /// Releases the PooledNetworkBuffer when no longer needed - /// - private void ReleaseInternalBuffer() - { - if (InternalBuffer != null) - { - NetworkBufferPool.PutBackInPool(InternalBuffer); - InternalBuffer = null; + reader.ReadValueSafe(out ulong clientId); + ClientsTimedOut.Add(clientId); } } @@ -767,7 +754,11 @@ private void ReleaseInternalBuffer() /// public void Dispose() { - ReleaseInternalBuffer(); + if (m_HasInternalBuffer) + { + InternalBuffer.Dispose(); + m_HasInternalBuffer = false; + } } /// diff --git a/com.unity.netcode.gameobjects/Runtime/SceneManagement/SceneEventProgress.cs b/com.unity.netcode.gameobjects/Runtime/SceneManagement/SceneEventProgress.cs index f9f2010217..338f0c9c91 100644 --- a/com.unity.netcode.gameobjects/Runtime/SceneManagement/SceneEventProgress.cs +++ b/com.unity.netcode.gameobjects/Runtime/SceneManagement/SceneEventProgress.cs @@ -34,7 +34,7 @@ public enum SceneEventProgressStatus SceneEventInProgress, /// /// Returned if the scene name used with - /// or is invalid + /// or is invalid /// InvalidSceneName, /// diff --git a/com.unity.netcode.gameobjects/Runtime/Serialization/AutoNetworkSerializable.cs b/com.unity.netcode.gameobjects/Runtime/Serialization/AutoNetworkSerializable.cs deleted file mode 100644 index 0e496ba57a..0000000000 --- a/com.unity.netcode.gameobjects/Runtime/Serialization/AutoNetworkSerializable.cs +++ /dev/null @@ -1,38 +0,0 @@ -namespace Unity.Netcode -{ - /// - /// AutoBitWritable implements INetworkSerializable and automatically serializes fields using reflection - /// - public abstract class AutoNetworkSerializable : INetworkSerializable - { - private void Write(NetworkWriter writer) - { - var fields = SerializationManager.GetFieldsForType(GetType()); - for (int i = 0; i < fields.Length; i++) - { - writer.WriteObjectPacked(fields[i].GetValue(this)); - } - } - - private void Read(NetworkReader reader) - { - var fields = SerializationManager.GetFieldsForType(GetType()); - for (int i = 0; i < fields.Length; i++) - { - fields[i].SetValue(this, reader.ReadObjectPacked(fields[i].FieldType)); - } - } - - public void NetworkSerialize(NetworkSerializer serializer) - { - if (serializer.IsReading) - { - Read(serializer.Reader); - } - else - { - Write(serializer.Writer); - } - } - } -} diff --git a/com.unity.netcode.gameobjects/Runtime/Serialization/AutoNetworkSerializable.cs.meta b/com.unity.netcode.gameobjects/Runtime/Serialization/AutoNetworkSerializable.cs.meta deleted file mode 100644 index 9680ca664a..0000000000 --- a/com.unity.netcode.gameobjects/Runtime/Serialization/AutoNetworkSerializable.cs.meta +++ /dev/null @@ -1,11 +0,0 @@ -fileFormatVersion: 2 -guid: ffcc1ff68a0732d41b80725a9d665e1c -MonoImporter: - externalObjects: {} - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: - assetBundleName: - assetBundleVariant: diff --git a/com.unity.netcode.gameobjects/Runtime/Serialization/BitCounter.cs b/com.unity.netcode.gameobjects/Runtime/Serialization/BitCounter.cs new file mode 100644 index 0000000000..4feb4477da --- /dev/null +++ b/com.unity.netcode.gameobjects/Runtime/Serialization/BitCounter.cs @@ -0,0 +1,126 @@ +using System.Runtime.CompilerServices; + +namespace Unity.Netcode +{ + public static class BitCounter + { + // Since we don't have access to BitOperations.LeadingZeroCount() (which would have been the fastest) + // we use the De Bruijn sequence to do this calculation + // See https://en.wikipedia.org/wiki/De_Bruijn_sequence and https://www.chessprogramming.org/De_Bruijn_Sequence + private const ulong k_DeBruijnMagic64 = 0x37E84A99DAE458F; + private const uint k_DeBruijnMagic32 = 0x06EB14F9; + + // We're counting bytes, not bits, so these have all had the operation x/8 + 1 applied + private static readonly int[] k_DeBruijnTableBytes64 = + { + 0/8+1, 1/8+1, 17/8+1, 2/8+1, 18/8+1, 50/8+1, 3/8+1, 57/8+1, + 47/8+1, 19/8+1, 22/8+1, 51/8+1, 29/8+1, 4/8+1, 33/8+1, 58/8+1, + 15/8+1, 48/8+1, 20/8+1, 27/8+1, 25/8+1, 23/8+1, 52/8+1, 41/8+1, + 54/8+1, 30/8+1, 38/8+1, 5/8+1, 43/8+1, 34/8+1, 59/8+1, 8/8+1, + 63/8+1, 16/8+1, 49/8+1, 56/8+1, 46/8+1, 21/8+1, 28/8+1, 32/8+1, + 14/8+1, 26/8+1, 24/8+1, 40/8+1, 53/8+1, 37/8+1, 42/8+1, 7/8+1, + 62/8+1, 55/8+1, 45/8+1, 31/8+1, 13/8+1, 39/8+1, 36/8+1, 6/8+1, + 61/8+1, 44/8+1, 12/8+1, 35/8+1, 60/8+1, 11/8+1, 10/8+1, 9/8+1, + }; + + private static readonly int[] k_DeBruijnTableBytes32 = + { + 0/8+1, 1/8+1, 16/8+1, 2/8+1, 29/8+1, 17/8+1, 3/8+1, 22/8+1, + 30/8+1, 20/8+1, 18/8+1, 11/8+1, 13/8+1, 4/8+1, 7/8+1, 23/8+1, + 31/8+1, 15/8+1, 28/8+1, 21/8+1, 19/8+1, 10/8+1, 12/8+1, 6/8+1, + 14/8+1, 27/8+1, 9/8+1, 5/8+1, 26/8+1, 8/8+1, 25/8+1, 24/8+1, + }; + + // And here we're counting the number of set bits, not the position of the highest set, + // so these still have +1 applied - unfortunately 0 and 1 both return the same value. + private static readonly int[] k_DeBruijnTableBits64 = + { + 0+1, 1+1, 17+1, 2+1, 18+1, 50+1, 3+1, 57+1, + 47+1, 19+1, 22+1, 51+1, 29+1, 4+1, 33+1, 58+1, + 15+1, 48+1, 20+1, 27+1, 25+1, 23+1, 52+1, 41+1, + 54+1, 30+1, 38+1, 5+1, 43+1, 34+1, 59+1, 8+1, + 63+1, 16+1, 49+1, 56+1, 46+1, 21+1, 28+1, 32+1, + 14+1, 26+1, 24+1, 40+1, 53+1, 37+1, 42+1, 7+1, + 62+1, 55+1, 45+1, 31+1, 13+1, 39+1, 36+1, 6+1, + 61+1, 44+1, 12+1, 35+1, 60+1, 11+1, 10+1, 9+1, + }; + + private static readonly int[] k_DeBruijnTableBits32 = + { + 0+1, 1+1, 16+1, 2+1, 29+1, 17+1, 3+1, 22+1, + 30+1, 20+1, 18+1, 11+1, 13+1, 4+1, 7+1, 23+1, + 31+1, 15+1, 28+1, 21+1, 19+1, 10+1, 12+1, 6+1, + 14+1, 27+1, 9+1, 5+1, 26+1, 8+1, 25+1, 24+1, + }; + + /// + /// Get the minimum number of bytes required to represent the given value + /// + /// The value + /// The number of bytes required + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static int GetUsedByteCount(uint value) + { + value |= value >> 1; + value |= value >> 2; + value |= value >> 4; + value |= value >> 8; + value |= value >> 16; + value = value & ~(value >> 1); + return k_DeBruijnTableBytes32[value * k_DeBruijnMagic32 >> 27]; + } + + /// + /// Get the minimum number of bytes required to represent the given value + /// + /// The value + /// The number of bytes required + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static int GetUsedByteCount(ulong value) + { + value |= value >> 1; + value |= value >> 2; + value |= value >> 4; + value |= value >> 8; + value |= value >> 16; + value |= value >> 32; + value = value & ~(value >> 1); + return k_DeBruijnTableBytes64[value * k_DeBruijnMagic64 >> 58]; + } + + /// + /// Get the minimum number of bits required to represent the given value + /// + /// The value + /// The number of bits required + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static int GetUsedBitCount(uint value) + { + value |= value >> 1; + value |= value >> 2; + value |= value >> 4; + value |= value >> 8; + value |= value >> 16; + value = value & ~(value >> 1); + return k_DeBruijnTableBits32[value * k_DeBruijnMagic32 >> 27]; + } + + /// + /// Get the minimum number of bits required to represent the given value + /// + /// The value + /// The number of bits required + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static int GetUsedBitCount(ulong value) + { + value |= value >> 1; + value |= value >> 2; + value |= value >> 4; + value |= value >> 8; + value |= value >> 16; + value |= value >> 32; + value = value & ~(value >> 1); + return k_DeBruijnTableBits64[value * k_DeBruijnMagic64 >> 58]; + } + } +} diff --git a/com.unity.netcode.gameobjects/Runtime/Serialization/BitCounter.cs.meta b/com.unity.netcode.gameobjects/Runtime/Serialization/BitCounter.cs.meta new file mode 100644 index 0000000000..f9d96d15b6 --- /dev/null +++ b/com.unity.netcode.gameobjects/Runtime/Serialization/BitCounter.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 6983de23935090341bf45d5564401b9d +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/com.unity.netcode.gameobjects/Runtime/Serialization/BitReader.cs b/com.unity.netcode.gameobjects/Runtime/Serialization/BitReader.cs new file mode 100644 index 0000000000..411152046d --- /dev/null +++ b/com.unity.netcode.gameobjects/Runtime/Serialization/BitReader.cs @@ -0,0 +1,217 @@ +using System; +using System.Runtime.CompilerServices; + +namespace Unity.Netcode +{ + /// + /// Helper class for doing bitwise reads for a FastBufferReader. + /// Ensures all bitwise reads end on proper byte alignment so FastBufferReader doesn't have to be concerned + /// with misaligned reads. + /// + public ref struct BitReader + { + private Ref m_Reader; + private readonly unsafe byte* m_BufferPointer; + private readonly int m_Position; + private int m_BitPosition; +#if DEVELOPMENT_BUILD || UNITY_EDITOR + private int m_AllowedBitwiseReadMark; +#endif + + private const int k_BitsPerByte = 8; + + /// + /// Whether or not the current BitPosition is evenly divisible by 8. I.e. whether or not the BitPosition is at a byte boundary. + /// + public bool BitAligned + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => (m_BitPosition & 7) == 0; + } + + internal unsafe BitReader(ref FastBufferReader reader) + { + m_Reader = new Ref(ref reader); + + m_BufferPointer = m_Reader.Value.BufferPointer + m_Reader.Value.Position; + m_Position = m_Reader.Value.Position; + m_BitPosition = 0; +#if DEVELOPMENT_BUILD || UNITY_EDITOR + m_AllowedBitwiseReadMark = (m_Reader.Value.AllowedReadMark - m_Position) * k_BitsPerByte; +#endif + } + + /// + /// Pads the read bit count to byte alignment and commits the read back to the reader + /// + public void Dispose() + { + var bytesWritten = m_BitPosition >> 3; + if (!BitAligned) + { + // Accounting for the partial read + ++bytesWritten; + } + + m_Reader.Value.CommitBitwiseReads(bytesWritten); + } + + /// + /// Verifies the requested bit count can be read from the buffer. + /// This exists as a separate method to allow multiple bit reads to be bounds checked with a single call. + /// If it returns false, you may not read, and in editor and development builds, attempting to do so will + /// throw an exception. In release builds, attempting to do so will read junk memory. + /// + /// Number of bits you want to read, in total + /// True if you can read, false if that would exceed buffer bounds + public bool TryBeginReadBits(uint bitCount) + { + var newBitPosition = m_BitPosition + bitCount; + var totalBytesWrittenInBitwiseContext = newBitPosition >> 3; + if ((newBitPosition & 7) != 0) + { + // Accounting for the partial read + ++totalBytesWrittenInBitwiseContext; + } + + if (m_Reader.Value.PositionInternal + totalBytesWrittenInBitwiseContext > m_Reader.Value.LengthInternal) + { + return false; + } +#if DEVELOPMENT_BUILD || UNITY_EDITOR + m_AllowedBitwiseReadMark = (int)newBitPosition; +#endif + return true; + } + + /// + /// Read a certain amount of bits from the stream. + /// + /// Value to store bits into. + /// Amount of bits to read + public unsafe void ReadBits(out ulong value, uint bitCount) + { +#if DEVELOPMENT_BUILD || UNITY_EDITOR + if (bitCount > 64) + { + throw new ArgumentOutOfRangeException(nameof(bitCount), "Cannot read more than 64 bits from a 64-bit value!"); + } + + if (bitCount < 0) + { + throw new ArgumentOutOfRangeException(nameof(bitCount), "Cannot read fewer than 0 bits!"); + } + + int checkPos = (int)(m_BitPosition + bitCount); + if (checkPos > m_AllowedBitwiseReadMark) + { + throw new OverflowException("Attempted to read without first calling TryBeginReadBits()"); + } +#endif + ulong val = 0; + + int wholeBytes = (int)bitCount / k_BitsPerByte; + byte* asBytes = (byte*)&val; + if (BitAligned) + { + if (wholeBytes != 0) + { + ReadPartialValue(out val, wholeBytes); + } + } + else + { + for (var i = 0; i < wholeBytes; ++i) + { + ReadMisaligned(out asBytes[i]); + } + } + + val |= (ulong)ReadByteBits((int)bitCount & 7) << ((int)bitCount & ~7); + value = val; + } + + /// + /// Read bits from stream. + /// + /// Value to store bits into. + /// Amount of bits to read. + public void ReadBits(out byte value, uint bitCount) + { +#if DEVELOPMENT_BUILD || UNITY_EDITOR + int checkPos = (int)(m_BitPosition + bitCount); + if (checkPos > m_AllowedBitwiseReadMark) + { + throw new OverflowException("Attempted to read without first calling TryBeginReadBits()"); + } +#endif + value = ReadByteBits((int)bitCount); + } + + /// + /// Read a single bit from the buffer + /// + /// Out value of the bit. True represents 1, False represents 0 + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public unsafe void ReadBit(out bool bit) + { +#if DEVELOPMENT_BUILD || UNITY_EDITOR + int checkPos = (m_BitPosition + 1); + if (checkPos > m_AllowedBitwiseReadMark) + { + throw new OverflowException("Attempted to read without first calling TryBeginReadBits()"); + } +#endif + + int offset = m_BitPosition & 7; + int pos = m_BitPosition >> 3; + bit = (m_BufferPointer[pos] & (1 << offset)) != 0; + ++m_BitPosition; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private unsafe void ReadPartialValue(out T value, int bytesToRead, int offsetBytes = 0) where T : unmanaged + { + var val = new T(); + byte* ptr = ((byte*)&val) + offsetBytes; + byte* bufferPointer = m_BufferPointer + m_Position; + BytewiseUtility.FastCopyBytes(ptr, bufferPointer, bytesToRead); + + m_BitPosition += bytesToRead * k_BitsPerByte; + value = val; + } + + private byte ReadByteBits(int bitCount) + { + if (bitCount > 8) + { + throw new ArgumentOutOfRangeException(nameof(bitCount), "Cannot read more than 8 bits into an 8-bit value!"); + } + + if (bitCount < 0) + { + throw new ArgumentOutOfRangeException(nameof(bitCount), "Cannot read fewer than 0 bits!"); + } + + int result = 0; + var convert = new ByteBool(); + for (int i = 0; i < bitCount; ++i) + { + ReadBit(out bool bit); + result |= convert.Collapse(bit) << i; + } + + return (byte)result; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private unsafe void ReadMisaligned(out byte value) + { + int off = m_BitPosition & 7; + int pos = m_BitPosition >> 3; + int shift1 = 8 - off; + + value = (byte)((m_BufferPointer[pos] >> shift1) | (m_BufferPointer[(m_BitPosition += 8) >> 3] << shift1)); + } + } +} diff --git a/com.unity.netcode.gameobjects/Runtime/Serialization/BitReader.cs.meta b/com.unity.netcode.gameobjects/Runtime/Serialization/BitReader.cs.meta new file mode 100644 index 0000000000..9b0d159683 --- /dev/null +++ b/com.unity.netcode.gameobjects/Runtime/Serialization/BitReader.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 72e2d94a96ca96a4fb2921df9adc2fdf +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/com.unity.netcode.gameobjects/Runtime/Serialization/BitWriter.cs b/com.unity.netcode.gameobjects/Runtime/Serialization/BitWriter.cs new file mode 100644 index 0000000000..688210a9f5 --- /dev/null +++ b/com.unity.netcode.gameobjects/Runtime/Serialization/BitWriter.cs @@ -0,0 +1,210 @@ +using System; +using System.Runtime.CompilerServices; + +namespace Unity.Netcode +{ + /// + /// Helper class for doing bitwise writes for a FastBufferWriter. + /// Ensures all bitwise writes end on proper byte alignment so FastBufferWriter doesn't have to be concerned + /// with misaligned writes. + /// + public ref struct BitWriter + { + private Ref m_Writer; + private unsafe byte* m_BufferPointer; + private readonly int m_Position; + private int m_BitPosition; +#if DEVELOPMENT_BUILD || UNITY_EDITOR + private int m_AllowedBitwiseWriteMark; +#endif + private const int k_BitsPerByte = 8; + + /// + /// Whether or not the current BitPosition is evenly divisible by 8. I.e. whether or not the BitPosition is at a byte boundary. + /// + public bool BitAligned + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => (m_BitPosition & 7) == 0; + } + + internal unsafe BitWriter(ref FastBufferWriter writer) + { + m_Writer = new Ref(ref writer); + m_BufferPointer = writer.BufferPointer + writer.PositionInternal; + m_Position = writer.PositionInternal; + m_BitPosition = 0; +#if DEVELOPMENT_BUILD || UNITY_EDITOR + m_AllowedBitwiseWriteMark = (m_Writer.Value.AllowedWriteMark - m_Writer.Value.Position) * k_BitsPerByte; +#endif + } + + /// + /// Pads the written bit count to byte alignment and commits the write back to the writer + /// + public void Dispose() + { + var bytesWritten = m_BitPosition >> 3; + if (!BitAligned) + { + // Accounting for the partial write + ++bytesWritten; + } + + m_Writer.Value.CommitBitwiseWrites(bytesWritten); + } + + /// + /// Allows faster serialization by batching bounds checking. + /// When you know you will be writing multiple fields back-to-back and you know the total size, + /// you can call TryBeginWriteBits() once on the total size, and then follow it with calls to + /// WriteBit() or WriteBits(). + /// + /// Bitwise write operations will throw OverflowException in editor and development builds if you + /// go past the point you've marked using TryBeginWriteBits(). In release builds, OverflowException will not be thrown + /// for performance reasons, since the point of using TryBeginWrite is to avoid bounds checking in the following + /// operations in release builds. Instead, attempting to write past the marked position in release builds + /// will write to random memory and cause undefined behavior, likely including instability and crashes. + /// + /// Number of bits you want to write, in total + /// True if you can write, false if that would exceed buffer bounds + public unsafe bool TryBeginWriteBits(int bitCount) + { + var newBitPosition = m_BitPosition + bitCount; + var totalBytesWrittenInBitwiseContext = newBitPosition >> 3; + if ((newBitPosition & 7) != 0) + { + // Accounting for the partial write + ++totalBytesWrittenInBitwiseContext; + } + + if (m_Position + totalBytesWrittenInBitwiseContext > m_Writer.Value.CapacityInternal) + { + if (m_Position + totalBytesWrittenInBitwiseContext > m_Writer.Value.MaxCapacityInternal) + { + return false; + } + if (m_Writer.Value.CapacityInternal < m_Writer.Value.MaxCapacityInternal) + { + m_Writer.Value.Grow(totalBytesWrittenInBitwiseContext); + m_BufferPointer = m_Writer.Value.BufferPointer + m_Writer.Value.PositionInternal; + } + else + { + return false; + } + } +#if DEVELOPMENT_BUILD || UNITY_EDITOR + m_AllowedBitwiseWriteMark = newBitPosition; +#endif + return true; + } + + /// + /// Write s certain amount of bits to the stream. + /// + /// Value to get bits from. + /// Amount of bits to write + public unsafe void WriteBits(ulong value, uint bitCount) + { +#if DEVELOPMENT_BUILD || UNITY_EDITOR + if (bitCount > 64) + { + throw new ArgumentOutOfRangeException(nameof(bitCount), "Cannot write more than 64 bits from a 64-bit value!"); + } + + int checkPos = (int)(m_BitPosition + bitCount); + if (checkPos > m_AllowedBitwiseWriteMark) + { + throw new OverflowException("Attempted to write without first calling FastBufferWriter.TryBeginWriteBits()"); + } +#endif + + int wholeBytes = (int)bitCount / k_BitsPerByte; + byte* asBytes = (byte*)&value; + if (BitAligned) + { + if (wholeBytes != 0) + { + WritePartialValue(value, wholeBytes); + } + } + else + { + for (var i = 0; i < wholeBytes; ++i) + { + WriteMisaligned(asBytes[i]); + } + } + + for (var count = wholeBytes * k_BitsPerByte; count < bitCount; ++count) + { + WriteBit((value & (1UL << count)) != 0); + } + } + + /// + /// Write bits to stream. + /// + /// Value to get bits from. + /// Amount of bits to write. + public void WriteBits(byte value, uint bitCount) + { +#if DEVELOPMENT_BUILD || UNITY_EDITOR + int checkPos = (int)(m_BitPosition + bitCount); + if (checkPos > m_AllowedBitwiseWriteMark) + { + throw new OverflowException("Attempted to write without first calling FastBufferWriter.TryBeginWriteBits()"); + } +#endif + + for (int i = 0; i < bitCount; ++i) + { + WriteBit(((value >> i) & 1) != 0); + } + } + + /// + /// Write a single bit to the buffer + /// + /// Value of the bit. True represents 1, False represents 0 + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public unsafe void WriteBit(bool bit) + { +#if DEVELOPMENT_BUILD || UNITY_EDITOR + int checkPos = (m_BitPosition + 1); + if (checkPos > m_AllowedBitwiseWriteMark) + { + throw new OverflowException("Attempted to write without first calling FastBufferWriter.TryBeginWriteBits()"); + } +#endif + + int offset = m_BitPosition & 7; + int pos = m_BitPosition >> 3; + ++m_BitPosition; + m_BufferPointer[pos] = (byte)(bit ? (m_BufferPointer[pos] & ~(1 << offset)) | (1 << offset) : (m_BufferPointer[pos] & ~(1 << offset))); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private unsafe void WritePartialValue(T value, int bytesToWrite, int offsetBytes = 0) where T : unmanaged + { + byte* ptr = ((byte*)&value) + offsetBytes; + byte* bufferPointer = m_BufferPointer + m_Position; + BytewiseUtility.FastCopyBytes(bufferPointer, ptr, bytesToWrite); + + m_BitPosition += bytesToWrite * k_BitsPerByte; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private unsafe void WriteMisaligned(byte value) + { + int off = m_BitPosition & 7; + int pos = m_BitPosition >> 3; + int shift1 = 8 - off; + m_BufferPointer[pos + 1] = (byte)((m_BufferPointer[pos + 1] & (0xFF << off)) | (value >> shift1)); + m_BufferPointer[pos] = (byte)((m_BufferPointer[pos] & (0xFF >> shift1)) | (value << off)); + + m_BitPosition += 8; + } + } +} diff --git a/com.unity.netcode.gameobjects/Runtime/Serialization/BitWriter.cs.meta b/com.unity.netcode.gameobjects/Runtime/Serialization/BitWriter.cs.meta new file mode 100644 index 0000000000..604982f3c0 --- /dev/null +++ b/com.unity.netcode.gameobjects/Runtime/Serialization/BitWriter.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 8d6360e096142c149a11a2e86560c350 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/com.unity.netcode.gameobjects/Runtime/Serialization/BufferSerializer.cs b/com.unity.netcode.gameobjects/Runtime/Serialization/BufferSerializer.cs new file mode 100644 index 0000000000..a9c112ed8f --- /dev/null +++ b/com.unity.netcode.gameobjects/Runtime/Serialization/BufferSerializer.cs @@ -0,0 +1,293 @@ +using System; +using UnityEngine; + +namespace Unity.Netcode +{ + /// + /// Two-way serializer wrapping FastBufferReader or FastBufferWriter. + /// + /// Implemented as a ref struct for two reasons: + /// 1. The BufferSerializer cannot outlive the FBR/FBW it wraps or using it will cause a crash + /// 2. The BufferSerializer must always be passed by reference and can't be copied + /// + /// Ref structs help enforce both of those rules: they can't out live the stack context in which they were + /// created, and they're always passed by reference no matter what. + /// + /// BufferSerializer doesn't wrapp FastBufferReader or FastBufferWriter directly because it can't. + /// ref structs can't implement interfaces, and in order to be able to have two different implementations with + /// the same interface (which allows us to avoid an "if(IsReader)" on every call), the thing directly wrapping + /// the struct has to implement an interface. So IBufferSerializerImplementation exists as the interface, + /// which is implemented by a normal struct, while the ref struct wraps the normal one to enforce the two above + /// requirements. (Allowing direct access to the IBufferSerializerImplementation struct would allow dangerous + /// things to happen because the struct's lifetime could outlive the Reader/Writer's.) + /// + /// The implementation struct + public ref struct BufferSerializer where TImplementation : IBufferSerializerImplementation + { + private TImplementation m_Implementation; + + /// + /// Check if the contained implementation is a reader + /// + public bool IsReader => m_Implementation.IsReader; + + /// + /// Check if the contained implementation is a writer + /// + public bool IsWriter => m_Implementation.IsWriter; + + public BufferSerializer(TImplementation implementation) + { + m_Implementation = implementation; + } + + /// + /// Retrieves the FastBufferReader instance. Only valid if IsReader = true, throws + /// InvalidOperationException otherwise. + /// + /// Reader instance + public ref FastBufferReader GetFastBufferReader() + { + return ref m_Implementation.GetFastBufferReader(); + } + + /// + /// Retrieves the FastBufferWriter instance. Only valid if IsWriter = true, throws + /// InvalidOperationException otherwise. + /// + /// Writer instance + public ref FastBufferWriter GetFastBufferWriter() + { + return ref m_Implementation.GetFastBufferWriter(); + } + + /// + /// Serialize an object value. + /// Note: Will ALWAYS cause allocations when reading. + /// This function is also much slower than the others as it has to figure out how to serialize + /// the object using runtime reflection. + /// It's recommended not to use this unless you have no choice. + /// + /// Throws OverflowException if the end of the buffer has been reached. + /// Write buffers will grow up to the maximum allowable message size before throwing OverflowException. + /// + /// Value to serialize + /// Type to deserialize to when reading + /// + /// If true, will force an isNull byte to be written. + /// Some types will write this byte regardless. + /// + public void SerializeValue(ref object value, Type type, bool isNullable = false) + { + m_Implementation.SerializeValue(ref value, type, isNullable); + } + + /// + /// Serialize an INetworkSerializable + /// + /// Throws OverflowException if the end of the buffer has been reached. + /// Write buffers will grow up to the maximum allowable message size before throwing OverflowException. + /// + /// Value to serialize + public void SerializeNetworkSerializable(ref T value) where T : INetworkSerializable, new() + { + m_Implementation.SerializeNetworkSerializable(ref value); + } + + /// + /// Serialize a GameObject + /// + /// Throws OverflowException if the end of the buffer has been reached. + /// Write buffers will grow up to the maximum allowable message size before throwing OverflowException. + /// + /// Value to serialize + public void SerializeValue(ref GameObject value) + { + m_Implementation.SerializeValue(ref value); + } + + /// + /// Serialize a NetworkObject + /// + /// Throws OverflowException if the end of the buffer has been reached. + /// Write buffers will grow up to the maximum allowable message size before throwing OverflowException. + /// + /// Value to serialize + public void SerializeValue(ref NetworkObject value) + { + m_Implementation.SerializeValue(ref value); + } + + /// + /// Serialize a NetworkBehaviour + /// + /// Throws OverflowException if the end of the buffer has been reached. + /// Write buffers will grow up to the maximum allowable message size before throwing OverflowException. + /// + /// Value to serialize + public void SerializeValue(ref NetworkBehaviour value) + { + m_Implementation.SerializeValue(ref value); + } + + /// + /// Serialize a string. + /// + /// Note: Will ALWAYS allocate a new string when reading. + /// + /// Throws OverflowException if the end of the buffer has been reached. + /// Write buffers will grow up to the maximum allowable message size before throwing OverflowException. + /// + /// Value to serialize + /// + /// If true, will truncate each char to one byte. + /// This is slower than two-byte chars, but uses less bandwidth. + /// + public void SerializeValue(ref string s, bool oneByteChars = false) + { + m_Implementation.SerializeValue(ref s, oneByteChars); + } + + /// + /// Serialize an array value. + /// + /// Note: Will ALWAYS allocate a new array when reading. + /// If you have a statically-sized array that you know is large enough, it's recommended to + /// serialize the size yourself and iterate serializing array members. + /// + /// (This is because C# doesn't allow setting an array's length value, so deserializing + /// into an existing array of larger size would result in an array that doesn't have as many values + /// as its Length indicates it should.) + /// + /// Throws OverflowException if the end of the buffer has been reached. + /// Write buffers will grow up to the maximum allowable message size before throwing OverflowException. + /// + /// Value to serialize + public void SerializeValue(ref T[] array) where T : unmanaged + { + m_Implementation.SerializeValue(ref array); + } + + /// + /// Serialize a single byte + /// + /// Throws OverflowException if the end of the buffer has been reached. + /// Write buffers will grow up to the maximum allowable message size before throwing OverflowException. + /// + /// Value to serialize + public void SerializeValue(ref byte value) + { + m_Implementation.SerializeValue(ref value); + } + + /// + /// Serialize an unmanaged type. Supports basic value types as well as structs. + /// The provided type will be copied to/from the buffer as it exists in memory. + /// + /// Throws OverflowException if the end of the buffer has been reached. + /// Write buffers will grow up to the maximum allowable message size before throwing OverflowException. + /// + /// Value to serialize + public void SerializeValue(ref T value) where T : unmanaged + { + m_Implementation.SerializeValue(ref value); + } + + /// + /// Allows faster serialization by batching bounds checking. + /// When you know you will be writing multiple fields back-to-back and you know the total size, + /// you can call PreCheck() once on the total size, and then follow it with calls to + /// SerializeValuePreChecked() for faster serialization. Write buffers will grow during PreCheck() + /// if needed. + /// + /// PreChecked serialization operations will throw OverflowException in editor and development builds if you + /// go past the point you've marked using PreCheck(). In release builds, OverflowException will not be thrown + /// for performance reasons, since the point of using PreCheck is to avoid bounds checking in the following + /// operations in release builds. + /// + /// Number of bytes you plan to read or write + /// True if the read/write can proceed, false otherwise. + public bool PreCheck(int amount) + { + return m_Implementation.PreCheck(amount); + } + + /// + /// Serialize a GameObject + /// + /// Value to serialize + public void SerializeValuePreChecked(ref GameObject value) + { + m_Implementation.SerializeValuePreChecked(ref value); + } + + /// + /// Serialize a NetworkObject + /// + /// Value to serialize + public void SerializeValuePreChecked(ref NetworkObject value) + { + m_Implementation.SerializeValuePreChecked(ref value); + } + + /// + /// Serialize a NetworkBehaviour + /// + /// Value to serialize + public void SerializeValuePreChecked(ref NetworkBehaviour value) + { + m_Implementation.SerializeValuePreChecked(ref value); + } + + /// + /// Serialize a string. + /// + /// Note: Will ALWAYS allocate a new string when reading. + /// + /// Value to serialize + /// + /// If true, will truncate each char to one byte. + /// This is slower than two-byte chars, but uses less bandwidth. + /// + public void SerializeValuePreChecked(ref string s, bool oneByteChars = false) + { + m_Implementation.SerializeValuePreChecked(ref s, oneByteChars); + } + + /// + /// Serialize an array value. + /// + /// Note: Will ALWAYS allocate a new array when reading. + /// If you have a statically-sized array that you know is large enough, it's recommended to + /// serialize the size yourself and iterate serializing array members. + /// + /// (This is because C# doesn't allow setting an array's length value, so deserializing + /// into an existing array of larger size would result in an array that doesn't have as many values + /// as its Length indicates it should.) + /// + /// Value to serialize + public void SerializeValuePreChecked(ref T[] array) where T : unmanaged + { + m_Implementation.SerializeValuePreChecked(ref array); + } + + /// + /// Serialize a single byte + /// + /// Value to serialize + public void SerializeValuePreChecked(ref byte value) + { + m_Implementation.SerializeValuePreChecked(ref value); + } + + /// + /// Serialize an unmanaged type. Supports basic value types as well as structs. + /// The provided type will be copied to/from the buffer as it exists in memory. + /// + /// Value to serialize + public void SerializeValuePreChecked(ref T value) where T : unmanaged + { + m_Implementation.SerializeValuePreChecked(ref value); + } + } +} diff --git a/com.unity.netcode.gameobjects/Runtime/Serialization/BufferSerializer.cs.meta b/com.unity.netcode.gameobjects/Runtime/Serialization/BufferSerializer.cs.meta new file mode 100644 index 0000000000..fe70dbe5be --- /dev/null +++ b/com.unity.netcode.gameobjects/Runtime/Serialization/BufferSerializer.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: fca519b9bc3b32e4f9bd7cd138d690af +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/com.unity.netcode.gameobjects/Runtime/Serialization/BufferSerializerReader.cs b/com.unity.netcode.gameobjects/Runtime/Serialization/BufferSerializerReader.cs new file mode 100644 index 0000000000..ec64ed89ac --- /dev/null +++ b/com.unity.netcode.gameobjects/Runtime/Serialization/BufferSerializerReader.cs @@ -0,0 +1,113 @@ +using System; +using UnityEngine; + +namespace Unity.Netcode +{ + internal struct BufferSerializerReader : IBufferSerializerImplementation + { + private Ref m_Reader; + + public BufferSerializerReader(ref FastBufferReader reader) + { + m_Reader = new Ref(ref reader); + } + + public bool IsReader => true; + public bool IsWriter => false; + + public ref FastBufferReader GetFastBufferReader() + { + return ref m_Reader.Value; + } + + public ref FastBufferWriter GetFastBufferWriter() + { + throw new InvalidOperationException("Cannot retrieve a FastBufferWriter from a serializer where IsWriter = false"); + } + + public void SerializeValue(ref object value, Type type, bool isNullable = false) + { + m_Reader.Value.ReadObject(out value, type, isNullable); + } + + public void SerializeValue(ref GameObject value) + { + m_Reader.Value.ReadValueSafe(out value); + } + + public void SerializeValue(ref NetworkObject value) + { + m_Reader.Value.ReadValueSafe(out value); + } + + public void SerializeValue(ref NetworkBehaviour value) + { + m_Reader.Value.ReadValueSafe(out value); + } + + public void SerializeValue(ref string s, bool oneByteChars = false) + { + m_Reader.Value.ReadValueSafe(out s, oneByteChars); + } + + public void SerializeValue(ref T[] array) where T : unmanaged + { + m_Reader.Value.ReadValueSafe(out array); + } + + public void SerializeValue(ref byte value) + { + m_Reader.Value.ReadByteSafe(out value); + } + + public void SerializeValue(ref T value) where T : unmanaged + { + m_Reader.Value.ReadValueSafe(out value); + } + + public void SerializeNetworkSerializable(ref T value) where T : INetworkSerializable, new() + { + m_Reader.Value.ReadNetworkSerializable(out value); + } + + public bool PreCheck(int amount) + { + return m_Reader.Value.TryBeginRead(amount); + } + + public void SerializeValuePreChecked(ref GameObject value) + { + m_Reader.Value.ReadValue(out value); + } + + public void SerializeValuePreChecked(ref NetworkObject value) + { + m_Reader.Value.ReadValue(out value); + } + + public void SerializeValuePreChecked(ref NetworkBehaviour value) + { + m_Reader.Value.ReadValue(out value); + } + + public void SerializeValuePreChecked(ref string s, bool oneByteChars = false) + { + m_Reader.Value.ReadValue(out s, oneByteChars); + } + + public void SerializeValuePreChecked(ref T[] array) where T : unmanaged + { + m_Reader.Value.ReadValue(out array); + } + + public void SerializeValuePreChecked(ref byte value) + { + m_Reader.Value.ReadValue(out value); + } + + public void SerializeValuePreChecked(ref T value) where T : unmanaged + { + m_Reader.Value.ReadValue(out value); + } + } +} diff --git a/com.unity.netcode.gameobjects/Runtime/Serialization/BufferSerializerReader.cs.meta b/com.unity.netcode.gameobjects/Runtime/Serialization/BufferSerializerReader.cs.meta new file mode 100644 index 0000000000..ed94cf148a --- /dev/null +++ b/com.unity.netcode.gameobjects/Runtime/Serialization/BufferSerializerReader.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 70dd17b6c14f7cd43ba5380d01cf91ef +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/com.unity.netcode.gameobjects/Runtime/Serialization/BufferSerializerWriter.cs b/com.unity.netcode.gameobjects/Runtime/Serialization/BufferSerializerWriter.cs new file mode 100644 index 0000000000..e9ddd01dc0 --- /dev/null +++ b/com.unity.netcode.gameobjects/Runtime/Serialization/BufferSerializerWriter.cs @@ -0,0 +1,113 @@ +using System; +using UnityEngine; + +namespace Unity.Netcode +{ + internal struct BufferSerializerWriter : IBufferSerializerImplementation + { + private Ref m_Writer; + + public BufferSerializerWriter(ref FastBufferWriter writer) + { + m_Writer = new Ref(ref writer); + } + + public bool IsReader => false; + public bool IsWriter => true; + + public ref FastBufferReader GetFastBufferReader() + { + throw new InvalidOperationException("Cannot retrieve a FastBufferReader from a serializer where IsReader = false"); + } + + public ref FastBufferWriter GetFastBufferWriter() + { + return ref m_Writer.Value; + } + + public void SerializeValue(ref object value, Type type, bool isNullable = false) + { + m_Writer.Value.WriteObject(value, isNullable); + } + + public void SerializeValue(ref GameObject value) + { + m_Writer.Value.WriteValueSafe(value); + } + + public void SerializeValue(ref NetworkObject value) + { + m_Writer.Value.WriteValueSafe(value); + } + + public void SerializeValue(ref NetworkBehaviour value) + { + m_Writer.Value.WriteValueSafe(value); + } + + public void SerializeValue(ref string s, bool oneByteChars = false) + { + m_Writer.Value.WriteValueSafe(s, oneByteChars); + } + + public void SerializeValue(ref T[] array) where T : unmanaged + { + m_Writer.Value.WriteValueSafe(array); + } + + public void SerializeValue(ref byte value) + { + m_Writer.Value.WriteByteSafe(value); + } + + public void SerializeValue(ref T value) where T : unmanaged + { + m_Writer.Value.WriteValueSafe(value); + } + + public void SerializeNetworkSerializable(ref T value) where T : INetworkSerializable, new() + { + m_Writer.Value.WriteNetworkSerializable(value); + } + + public bool PreCheck(int amount) + { + return m_Writer.Value.TryBeginWrite(amount); + } + + public void SerializeValuePreChecked(ref GameObject value) + { + m_Writer.Value.WriteValue(value); + } + + public void SerializeValuePreChecked(ref NetworkObject value) + { + m_Writer.Value.WriteValue(value); + } + + public void SerializeValuePreChecked(ref NetworkBehaviour value) + { + m_Writer.Value.WriteValue(value); + } + + public void SerializeValuePreChecked(ref string s, bool oneByteChars = false) + { + m_Writer.Value.WriteValue(s, oneByteChars); + } + + public void SerializeValuePreChecked(ref T[] array) where T : unmanaged + { + m_Writer.Value.WriteValue(array); + } + + public void SerializeValuePreChecked(ref byte value) + { + m_Writer.Value.WriteByte(value); + } + + public void SerializeValuePreChecked(ref T value) where T : unmanaged + { + m_Writer.Value.WriteValue(value); + } + } +} diff --git a/com.unity.netcode.gameobjects/Runtime/Serialization/BufferSerializerWriter.cs.meta b/com.unity.netcode.gameobjects/Runtime/Serialization/BufferSerializerWriter.cs.meta new file mode 100644 index 0000000000..183a7bafad --- /dev/null +++ b/com.unity.netcode.gameobjects/Runtime/Serialization/BufferSerializerWriter.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: c05ed8e3061e62147a012cc01a64b5a5 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/com.unity.netcode.gameobjects/Runtime/Serialization/BytePacker.cs b/com.unity.netcode.gameobjects/Runtime/Serialization/BytePacker.cs new file mode 100644 index 0000000000..494b9a80da --- /dev/null +++ b/com.unity.netcode.gameobjects/Runtime/Serialization/BytePacker.cs @@ -0,0 +1,617 @@ +using System; +using System.Runtime.CompilerServices; +using UnityEngine; + +namespace Unity.Netcode +{ + /// + /// Utility class for packing values in serialization. + /// + public static class BytePacker + { + #region Managed TypePacking + + /// + /// Writes a boxed object in a packed format + /// Named differently from other WriteValuePacked methods to avoid accidental boxing. + /// Don't use this method unless you have no other choice. + /// + /// Writer to write to + /// The object to write + /// + /// If true, an extra byte will be written to indicate whether or not the value is null. + /// Some types will always write this. + /// + public static void WriteObjectPacked(ref FastBufferWriter writer, object value, bool isNullable = false) + { +#if UNITY_NETCODE_DEBUG_NO_PACKING + writer.WriteObject(value, isNullable); + return; +#endif + if (isNullable || value.GetType().IsNullable()) + { + bool isNull = value == null || (value is UnityEngine.Object o && o == null); + + WriteValuePacked(ref writer, isNull); + + if (isNull) + { + return; + } + } + + var type = value.GetType(); + var hasSerializer = SerializationTypeTable.SerializersPacked.TryGetValue(type, out var serializer); + if (hasSerializer) + { + serializer(ref writer, value); + return; + } + + if (value is Array array) + { + WriteValuePacked(ref writer, array.Length); + + for (int i = 0; i < array.Length; i++) + { + WriteObjectPacked(ref writer, array.GetValue(i)); + } + } + + if (value.GetType().IsEnum) + { + switch (Convert.GetTypeCode(value)) + { + case TypeCode.Boolean: + WriteValuePacked(ref writer, (byte)value); + break; + case TypeCode.Char: + WriteValuePacked(ref writer, (char)value); + break; + case TypeCode.SByte: + WriteValuePacked(ref writer, (sbyte)value); + break; + case TypeCode.Byte: + WriteValuePacked(ref writer, (byte)value); + break; + case TypeCode.Int16: + WriteValuePacked(ref writer, (short)value); + break; + case TypeCode.UInt16: + WriteValuePacked(ref writer, (ushort)value); + break; + case TypeCode.Int32: + WriteValuePacked(ref writer, (int)value); + break; + case TypeCode.UInt32: + WriteValuePacked(ref writer, (uint)value); + break; + case TypeCode.Int64: + WriteValuePacked(ref writer, (long)value); + break; + case TypeCode.UInt64: + WriteValuePacked(ref writer, (ulong)value); + break; + } + return; + } + if (value is GameObject) + { + ((GameObject)value).TryGetComponent(out var networkObject); + if (networkObject == null) + { + throw new ArgumentException($"{nameof(BytePacker)} cannot write {nameof(GameObject)} types that does not has a {nameof(NetworkObject)} component attached. {nameof(GameObject)}: {((GameObject)value).name}"); + } + + if (!networkObject.IsSpawned) + { + throw new ArgumentException($"{nameof(BytePacker)} cannot write {nameof(NetworkObject)} types that are not spawned. {nameof(GameObject)}: {((GameObject)value).name}"); + } + + WriteValuePacked(ref writer, networkObject.NetworkObjectId); + return; + } + if (value is NetworkObject) + { + if (!((NetworkObject)value).IsSpawned) + { + throw new ArgumentException($"{nameof(BytePacker)} cannot write {nameof(NetworkObject)} types that are not spawned. {nameof(GameObject)}: {((NetworkObject)value).gameObject.name}"); + } + + WriteValuePacked(ref writer, ((NetworkObject)value).NetworkObjectId); + return; + } + if (value is NetworkBehaviour) + { + if (!((NetworkBehaviour)value).HasNetworkObject || !((NetworkBehaviour)value).NetworkObject.IsSpawned) + { + throw new ArgumentException($"{nameof(BytePacker)} cannot write {nameof(NetworkBehaviour)} types that are not spawned. {nameof(GameObject)}: {((NetworkBehaviour)value).gameObject.name}"); + } + + WriteValuePacked(ref writer, ((NetworkBehaviour)value).NetworkObjectId); + WriteValuePacked(ref writer, ((NetworkBehaviour)value).NetworkBehaviourId); + return; + } + if (value is INetworkSerializable) + { + //TODO ((INetworkSerializable)value).NetworkSerialize(new NetworkSerializer(this)); + return; + } + + throw new ArgumentException($"{nameof(BytePacker)} cannot write type {value.GetType().Name} - it does not implement {nameof(INetworkSerializable)}"); + } + #endregion + + #region Unmanaged Type Packing + +#if UNITY_NETCODE_DEBUG_NO_PACKING + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void WriteValuePacked(ref FastBufferWriter writer, T value) where T: unmanaged => writer.WriteValueSafe(value); +#else + /// + /// Write a packed enum value. + /// + /// The writer to write to + /// The value to write + /// An enum type + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static unsafe void WriteValuePacked(ref FastBufferWriter writer, TEnum value) where TEnum : unmanaged, Enum + { + TEnum enumValue = value; + switch (sizeof(TEnum)) + { + case sizeof(int): + WriteValuePacked(ref writer, *(int*)&enumValue); + break; + case sizeof(byte): + WriteValuePacked(ref writer, *(byte*)&enumValue); + break; + case sizeof(short): + WriteValuePacked(ref writer, *(short*)&enumValue); + break; + case sizeof(long): + WriteValuePacked(ref writer, *(long*)&enumValue); + break; + } + } + + /// + /// Write single-precision floating point value to the buffer as a varint + /// + /// The writer to write to + /// Value to write + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void WriteValuePacked(ref FastBufferWriter writer, float value) + { + WriteUInt32Packed(ref writer, ToUint(value)); + } + + /// + /// Write double-precision floating point value to the buffer as a varint + /// + /// The writer to write to + /// Value to write + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void WriteValuePacked(ref FastBufferWriter writer, double value) + { + WriteUInt64Packed(ref writer, ToUlong(value)); + } + + /// + /// Write a byte to the buffer. + /// + /// The writer to write to + /// Value to write + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void WriteValuePacked(ref FastBufferWriter writer, byte value) => writer.WriteByteSafe(value); + + /// + /// Write a signed byte to the buffer. + /// + /// The writer to write to + /// Value to write + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void WriteValuePacked(ref FastBufferWriter writer, sbyte value) => writer.WriteByteSafe((byte)value); + + /// + /// Write a bool to the buffer. + /// + /// The writer to write to + /// Value to write + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void WriteValuePacked(ref FastBufferWriter writer, bool value) => writer.WriteValueSafe(value); + + + /// + /// Write a signed short (Int16) as a ZigZag encoded varint to the buffer. + /// WARNING: If the value you're writing is > 2287, this will use MORE space + /// (3 bytes instead of 2), and if your value is > 240 you'll get no savings at all. + /// Only use this if you're certain your value will be small. + /// + /// The writer to write to + /// Value to write + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void WriteValuePacked(ref FastBufferWriter writer, short value) => WriteUInt32Packed(ref writer, (ushort)Arithmetic.ZigZagEncode(value)); + + /// + /// Write an unsigned short (UInt16) as a varint to the buffer. + /// WARNING: If the value you're writing is > 2287, this will use MORE space + /// (3 bytes instead of 2), and if your value is > 240 you'll get no savings at all. + /// Only use this if you're certain your value will be small. + /// + /// The writer to write to + /// Value to write + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void WriteValuePacked(ref FastBufferWriter writer, ushort value) => WriteUInt32Packed(ref writer, value); + + /// + /// Write a two-byte character as a varint to the buffer. + /// WARNING: If the value you're writing is > 2287, this will use MORE space + /// (3 bytes instead of 2), and if your value is > 240 you'll get no savings at all. + /// Only use this if you're certain your value will be small. + /// + /// The writer to write to + /// Value to write + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void WriteValuePacked(ref FastBufferWriter writer, char c) => WriteUInt32Packed(ref writer, c); + + /// + /// Write a signed int (Int32) as a ZigZag encoded varint to the buffer. + /// + /// The writer to write to + /// Value to write + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void WriteValuePacked(ref FastBufferWriter writer, int value) => WriteUInt32Packed(ref writer, (uint)Arithmetic.ZigZagEncode(value)); + + /// + /// Write an unsigned int (UInt32) to the buffer. + /// + /// The writer to write to + /// Value to write + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void WriteValuePacked(ref FastBufferWriter writer, uint value) => WriteUInt32Packed(ref writer, value); + + /// + /// Write an unsigned long (UInt64) to the buffer. + /// + /// The writer to write to + /// Value to write + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void WriteValuePacked(ref FastBufferWriter writer, ulong value) => WriteUInt64Packed(ref writer, value); + + /// + /// Write a signed long (Int64) as a ZigZag encoded varint to the buffer. + /// + /// The writer to write to + /// Value to write + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void WriteValuePacked(ref FastBufferWriter writer, long value) => WriteUInt64Packed(ref writer, Arithmetic.ZigZagEncode(value)); + + /// + /// Convenience method that writes two packed Vector3 from the ray to the buffer + /// + /// The writer to write to + /// Ray to write + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void WriteValuePacked(ref FastBufferWriter writer, Ray ray) + { + WriteValuePacked(ref writer, ray.origin); + WriteValuePacked(ref writer, ray.direction); + } + + /// + /// Convenience method that writes two packed Vector2 from the ray to the buffer + /// + /// The writer to write to + /// Ray2D to write + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void WriteValuePacked(ref FastBufferWriter writer, Ray2D ray2d) + { + WriteValuePacked(ref writer, ray2d.origin); + WriteValuePacked(ref writer, ray2d.direction); + } + + /// + /// Convenience method that writes four varint floats from the color to the buffer + /// + /// The writer to write to + /// Color to write + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void WriteValuePacked(ref FastBufferWriter writer, Color color) + { + WriteValuePacked(ref writer, color.r); + WriteValuePacked(ref writer, color.g); + WriteValuePacked(ref writer, color.b); + WriteValuePacked(ref writer, color.a); + } + + /// + /// Convenience method that writes four varint floats from the color to the buffer + /// + /// The writer to write to + /// Color to write + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void WriteValuePacked(ref FastBufferWriter writer, Color32 color) + { + WriteValuePacked(ref writer, color.r); + WriteValuePacked(ref writer, color.g); + WriteValuePacked(ref writer, color.b); + WriteValuePacked(ref writer, color.a); + } + + /// + /// Convenience method that writes two varint floats from the vector to the buffer + /// + /// The writer to write to + /// Vector to write + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void WriteValuePacked(ref FastBufferWriter writer, Vector2 vector2) + { + WriteValuePacked(ref writer, vector2.x); + WriteValuePacked(ref writer, vector2.y); + } + + /// + /// Convenience method that writes three varint floats from the vector to the buffer + /// + /// The writer to write to + /// Vector to write + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void WriteValuePacked(ref FastBufferWriter writer, Vector3 vector3) + { + WriteValuePacked(ref writer, vector3.x); + WriteValuePacked(ref writer, vector3.y); + WriteValuePacked(ref writer, vector3.z); + } + + /// + /// Convenience method that writes four varint floats from the vector to the buffer + /// + /// The writer to write to + /// Vector to write + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void WriteValuePacked(ref FastBufferWriter writer, Vector4 vector4) + { + WriteValuePacked(ref writer, vector4.x); + WriteValuePacked(ref writer, vector4.y); + WriteValuePacked(ref writer, vector4.z); + WriteValuePacked(ref writer, vector4.w); + } + + /// + /// Writes the rotation to the buffer. + /// + /// The writer to write to + /// Rotation to write + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void WriteValuePacked(ref FastBufferWriter writer, Quaternion rotation) + { + WriteValuePacked(ref writer, rotation.x); + WriteValuePacked(ref writer, rotation.y); + WriteValuePacked(ref writer, rotation.z); + WriteValuePacked(ref writer, rotation.w); + } + + /// + /// Writes a string in a packed format + /// + /// The writer to write to + /// The value to pack + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void WriteValuePacked(ref FastBufferWriter writer, string s) + { + WriteValuePacked(ref writer, (uint)s.Length); + int target = s.Length; + for (int i = 0; i < target; ++i) + { + WriteValuePacked(ref writer, s[i]); + } + } +#endif + #endregion + + #region Bit Packing + +#if UNITY_NETCODE_DEBUG_NO_PACKING + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void WriteValueBitPacked(ref FastBufferWriter writer, T value) where T: unmanaged => writer.WriteValueSafe(value); +#else + /// + /// Writes a 14-bit signed short to the buffer in a bit-encoded packed format. + /// The first bit indicates whether the value is 1 byte or 2. + /// The sign bit takes up another bit. + /// That leaves 14 bits for the value. + /// A value greater than 2^14-1 or less than -2^14 will throw an exception in editor and development builds. + /// In release builds builds the exception is not thrown and the value is truncated by losing its two + /// most significant bits after zig-zag encoding. + /// + /// The writer to write to + /// The value to pack + public static void WriteValueBitPacked(ref FastBufferWriter writer, short value) => WriteValueBitPacked(ref writer, (ushort)Arithmetic.ZigZagEncode(value)); + + /// + /// Writes a 15-bit unsigned short to the buffer in a bit-encoded packed format. + /// The first bit indicates whether the value is 1 byte or 2. + /// That leaves 15 bits for the value. + /// A value greater than 2^15-1 will throw an exception in editor and development builds. + /// In release builds builds the exception is not thrown and the value is truncated by losing its + /// most significant bit. + /// + /// The writer to write to + /// The value to pack + public static void WriteValueBitPacked(ref FastBufferWriter writer, ushort value) + { +#if DEVELOPMENT_BUILD || UNITY_EDITOR + if (value >= 0b1000_0000_0000_0000) + { + throw new ArgumentException("BitPacked ushorts must be <= 15 bits"); + } +#endif + + if (value <= 0b0111_1111) + { + if (!writer.TryBeginWriteInternal(1)) + { + throw new OverflowException("Writing past the end of the buffer"); + } + writer.WriteByte((byte)(value << 1)); + return; + } + + if (!writer.TryBeginWriteInternal(2)) + { + throw new OverflowException("Writing past the end of the buffer"); + } + writer.WriteValue((ushort)((value << 1) | 0b1)); + } + + /// + /// Writes a 29-bit signed int to the buffer in a bit-encoded packed format. + /// The first two bits indicate whether the value is 1, 2, 3, or 4 bytes. + /// The sign bit takes up another bit. + /// That leaves 29 bits for the value. + /// A value greater than 2^29-1 or less than -2^29 will throw an exception in editor and development builds. + /// In release builds builds the exception is not thrown and the value is truncated by losing its three + /// most significant bits after zig-zag encoding. + /// + /// The writer to write to + /// The value to pack + public static void WriteValueBitPacked(ref FastBufferWriter writer, int value) => WriteValueBitPacked(ref writer, (uint)Arithmetic.ZigZagEncode(value)); + + /// + /// Writes a 30-bit unsigned int to the buffer in a bit-encoded packed format. + /// The first two bits indicate whether the value is 1, 2, 3, or 4 bytes. + /// That leaves 30 bits for the value. + /// A value greater than 2^30-1 will throw an exception in editor and development builds. + /// In release builds builds the exception is not thrown and the value is truncated by losing its two + /// most significant bits. + /// + /// The writer to write to + /// The value to pack + public static void WriteValueBitPacked(ref FastBufferWriter writer, uint value) + { +#if DEVELOPMENT_BUILD || UNITY_EDITOR + if (value >= 0b0100_0000_0000_0000_0000_0000_0000_0000) + { + throw new ArgumentException("BitPacked uints must be <= 30 bits"); + } +#endif + value <<= 2; + var numBytes = BitCounter.GetUsedByteCount(value); + if (!writer.TryBeginWriteInternal(numBytes)) + { + throw new OverflowException("Writing past the end of the buffer"); + } + writer.WritePartialValue(value | (uint)(numBytes - 1), numBytes); + } + + /// + /// Writes a 60-bit signed long to the buffer in a bit-encoded packed format. + /// The first three bits indicate whether the value is 1, 2, 3, 4, 5, 6, 7, or 8 bytes. + /// The sign bit takes up another bit. + /// That leaves 60 bits for the value. + /// A value greater than 2^60-1 or less than -2^60 will throw an exception in editor and development builds. + /// In release builds builds the exception is not thrown and the value is truncated by losing its four + /// most significant bits after zig-zag encoding. + /// + /// The writer to write to + /// The value to pack + public static void WriteValueBitPacked(ref FastBufferWriter writer, long value) => WriteValueBitPacked(ref writer, Arithmetic.ZigZagEncode(value)); + + /// + /// Writes a 61-bit unsigned long to the buffer in a bit-encoded packed format. + /// The first three bits indicate whether the value is 1, 2, 3, 4, 5, 6, 7, or 8 bytes. + /// That leaves 31 bits for the value. + /// A value greater than 2^61-1 will throw an exception in editor and development builds. + /// In release builds builds the exception is not thrown and the value is truncated by losing its three + /// most significant bits. + /// + /// The writer to write to + /// The value to pack + public static void WriteValueBitPacked(ref FastBufferWriter writer, ulong value) + { +#if DEVELOPMENT_BUILD || UNITY_EDITOR + if (value >= 0b0010_0000_0000_0000_0000_0000_0000_0000_0000_0000_0000_0000_0000_0000_0000_0000) + { + throw new ArgumentException("BitPacked ulongs must be <= 61 bits"); + } +#endif + value <<= 3; + var numBytes = BitCounter.GetUsedByteCount(value); + if (!writer.TryBeginWriteInternal(numBytes)) + { + throw new OverflowException("Writing past the end of the buffer"); + } + writer.WritePartialValue(value | (uint)(numBytes - 1), numBytes); + } +#endif + #endregion + + #region Private Methods + private static void WriteUInt64Packed(ref FastBufferWriter writer, ulong value) + { + if (value <= 240) + { + writer.WriteByteSafe((byte)value); + return; + } + if (value <= 2287) + { + writer.WriteByteSafe((byte)(((value - 240) >> 8) + 241)); + writer.WriteByteSafe((byte)(value - 240)); + return; + } + var writeBytes = BitCounter.GetUsedByteCount(value); + + if (!writer.TryBeginWriteInternal(writeBytes + 1)) + { + throw new OverflowException("Writing past the end of the buffer"); + } + writer.WriteByte((byte)(247 + writeBytes)); + writer.WritePartialValue(value, writeBytes); + } + + // Looks like the same code as WriteUInt64Packed? + // It's actually different because it will call the more efficient 32-bit version + // of BytewiseUtility.GetUsedByteCount(). + private static void WriteUInt32Packed(ref FastBufferWriter writer, uint value) + { + if (value <= 240) + { + writer.WriteByteSafe((byte)value); + return; + } + if (value <= 2287) + { + writer.WriteByteSafe((byte)(((value - 240) >> 8) + 241)); + writer.WriteByteSafe((byte)(value - 240)); + return; + } + var writeBytes = BitCounter.GetUsedByteCount(value); + + if (!writer.TryBeginWriteInternal(writeBytes + 1)) + { + throw new OverflowException("Writing past the end of the buffer"); + } + writer.WriteByte((byte)(247 + writeBytes)); + writer.WritePartialValue(value, writeBytes); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static unsafe uint ToUint(T value) where T : unmanaged + { + uint* asUint = (uint*)&value; + return *asUint; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static unsafe ulong ToUlong(T value) where T : unmanaged + { + ulong* asUlong = (ulong*)&value; + return *asUlong; + } + #endregion + } +} diff --git a/com.unity.netcode.gameobjects/Runtime/Serialization/BytePacker.cs.meta b/com.unity.netcode.gameobjects/Runtime/Serialization/BytePacker.cs.meta new file mode 100644 index 0000000000..dfb5e75613 --- /dev/null +++ b/com.unity.netcode.gameobjects/Runtime/Serialization/BytePacker.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: a3ec13587ae68cb49b82af8612d47698 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/com.unity.netcode.gameobjects/Runtime/Serialization/ByteUnpacker.cs b/com.unity.netcode.gameobjects/Runtime/Serialization/ByteUnpacker.cs new file mode 100644 index 0000000000..857829e126 --- /dev/null +++ b/com.unity.netcode.gameobjects/Runtime/Serialization/ByteUnpacker.cs @@ -0,0 +1,697 @@ +using System; +using System.Runtime.CompilerServices; +using UnityEngine; + +namespace Unity.Netcode +{ + public static class ByteUnpacker + { + #region Managed TypePacking + + /// + /// Reads a boxed object in a packed format + /// Named differently from other ReadValuePacked methods to avoid accidental boxing + /// Don't use this method unless you have no other choice. + /// + /// The reader to read from + /// The object to read + /// The type of the object to read (i.e., typeof(int)) + /// + /// If true, reads a byte indicating whether or not the object is null. + /// Should match the way the object was written. + /// + public static void ReadObjectPacked(ref FastBufferReader reader, out object value, Type type, bool isNullable = false) + { +#if UNITY_NETCODE_DEBUG_NO_PACKING + reader.ReadObject(out value, type, isNullable); + return; +#endif + if (isNullable || type.IsNullable()) + { + reader.ReadValueSafe(out bool isNull); + + if (isNull) + { + value = null; + return; + } + } + + var hasDeserializer = SerializationTypeTable.DeserializersPacked.TryGetValue(type, out var deserializer); + if (hasDeserializer) + { + deserializer(ref reader, out value); + return; + } + + if (type.IsArray && type.HasElementType) + { + ReadValuePacked(ref reader, out int length); + + var arr = Array.CreateInstance(type.GetElementType(), length); + + for (int i = 0; i < length; i++) + { + ReadObjectPacked(ref reader, out object item, type.GetElementType()); + arr.SetValue(item, i); + } + + value = arr; + return; + } + + if (type.IsEnum) + { + switch (Type.GetTypeCode(type)) + { + case TypeCode.Boolean: + ReadValuePacked(ref reader, out byte boolVal); + value = Enum.ToObject(type, boolVal != 0); + return; + case TypeCode.Char: + ReadValuePacked(ref reader, out char charVal); + value = Enum.ToObject(type, charVal); + return; + case TypeCode.SByte: + ReadValuePacked(ref reader, out byte sbyteVal); + value = Enum.ToObject(type, sbyteVal); + return; + case TypeCode.Byte: + ReadValuePacked(ref reader, out byte byteVal); + value = Enum.ToObject(type, byteVal); + return; + case TypeCode.Int16: + ReadValuePacked(ref reader, out short shortVal); + value = Enum.ToObject(type, shortVal); + return; + case TypeCode.UInt16: + ReadValuePacked(ref reader, out ushort ushortVal); + value = Enum.ToObject(type, ushortVal); + return; + case TypeCode.Int32: + ReadValuePacked(ref reader, out int intVal); + value = Enum.ToObject(type, intVal); + return; + case TypeCode.UInt32: + ReadValuePacked(ref reader, out uint uintVal); + value = Enum.ToObject(type, uintVal); + return; + case TypeCode.Int64: + ReadValuePacked(ref reader, out long longVal); + value = Enum.ToObject(type, longVal); + return; + case TypeCode.UInt64: + ReadValuePacked(ref reader, out ulong ulongVal); + value = Enum.ToObject(type, ulongVal); + return; + } + } + + if (type == typeof(GameObject)) + { + reader.ReadValueSafe(out GameObject go); + value = go; + return; + } + + if (type == typeof(NetworkObject)) + { + reader.ReadValueSafe(out NetworkObject no); + value = no; + return; + } + + if (typeof(NetworkBehaviour).IsAssignableFrom(type)) + { + reader.ReadValueSafe(out NetworkBehaviour nb); + value = nb; + return; + } + /*if (value is INetworkSerializable) + { + //TODO ((INetworkSerializable)value).NetworkSerialize(new NetworkSerializer(this)); + return; + }*/ + + throw new ArgumentException($"{nameof(FastBufferReader)} cannot read type {type.Name} - it does not implement {nameof(INetworkSerializable)}"); + } + #endregion + + #region Unmanaged Type Packing + +#if UNITY_NETCODE_DEBUG_NO_PACKING + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void ReadValuePacked(ref FastBufferReader reader, out T value) where T: unmanaged => reader.ReadValueSafe(out value); +#else + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static unsafe void ReadValuePacked(ref FastBufferReader reader, out TEnum value) where TEnum : unmanaged, Enum + { + switch (sizeof(TEnum)) + { + case sizeof(int): + ReadValuePacked(ref reader, out int asInt); + value = *(TEnum*)&asInt; + break; + case sizeof(byte): + ReadValuePacked(ref reader, out byte asByte); + value = *(TEnum*)&asByte; + break; + case sizeof(short): + ReadValuePacked(ref reader, out short asShort); + value = *(TEnum*)&asShort; + break; + case sizeof(long): + ReadValuePacked(ref reader, out long asLong); + value = *(TEnum*)&asLong; + break; + default: + throw new InvalidOperationException("Enum is a size that cannot exist?!"); + } + } + + /// + /// Read single-precision floating point value from the stream as a varint + /// + /// The reader to read from + /// Value to read + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void ReadValuePacked(ref FastBufferReader reader, out float value) + { + ReadUInt32Packed(ref reader, out uint asUInt); + value = ToSingle(asUInt); + } + + /// + /// Read double-precision floating point value from the stream as a varint + /// + /// The reader to read from + /// Value to read + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void ReadValuePacked(ref FastBufferReader reader, out double value) + { + ReadUInt64Packed(ref reader, out ulong asULong); + value = ToDouble(asULong); + } + + /// + /// Read a byte from the stream. + /// + /// The reader to read from + /// Value to read + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void ReadValuePacked(ref FastBufferReader reader, out byte value) => reader.ReadByteSafe(out value); + + /// + /// Read a signed byte from the stream. + /// + /// The reader to read from + /// Value to read + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void ReadValuePacked(ref FastBufferReader reader, out sbyte value) + { + reader.ReadByteSafe(out byte byteVal); + value = (sbyte)byteVal; + } + + /// + /// Read a boolean from the stream. + /// + /// The reader to read from + /// Value to read + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void ReadValuePacked(ref FastBufferReader reader, out bool value) => reader.ReadValueSafe(out value); + + + /// + /// Read an usigned short (Int16) as a varint from the stream. + /// + /// The reader to read from + /// Value to read + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void ReadValuePacked(ref FastBufferReader reader, out short value) + { + ReadUInt32Packed(ref reader, out uint readValue); + value = (short)Arithmetic.ZigZagDecode(readValue); + } + + /// + /// Read an unsigned short (UInt16) as a varint from the stream. + /// + /// The reader to read from + /// Value to read + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void ReadValuePacked(ref FastBufferReader reader, out ushort value) + { + ReadUInt32Packed(ref reader, out uint readValue); + value = (ushort)readValue; + } + + /// + /// Read a two-byte character as a varint from the stream. + /// + /// The reader to read from + /// Value to read + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void ReadValuePacked(ref FastBufferReader reader, out char c) + { + ReadUInt32Packed(ref reader, out uint readValue); + c = (char)readValue; + } + + /// + /// Read a signed int (Int32) as a ZigZag encoded varint from the stream. + /// + /// The reader to read from + /// Value to read + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void ReadValuePacked(ref FastBufferReader reader, out int value) + { + ReadUInt32Packed(ref reader, out uint readValue); + value = (int)Arithmetic.ZigZagDecode(readValue); + } + + /// + /// Read an unsigned int (UInt32) from the stream. + /// + /// The reader to read from + /// Value to read + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void ReadValuePacked(ref FastBufferReader reader, out uint value) => ReadUInt32Packed(ref reader, out value); + + /// + /// Read an unsigned long (UInt64) from the stream. + /// + /// The reader to read from + /// Value to read + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void ReadValuePacked(ref FastBufferReader reader, out ulong value) => ReadUInt64Packed(ref reader, out value); + + /// + /// Read a signed long (Int64) as a ZigZag encoded varint from the stream. + /// + /// The reader to read from + /// Value to read + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void ReadValuePacked(ref FastBufferReader reader, out long value) + { + ReadUInt64Packed(ref reader, out ulong readValue); + value = Arithmetic.ZigZagDecode(readValue); + } + + /// + /// Convenience method that reads two packed Vector3 from the ray from the stream + /// + /// The reader to read from + /// Ray to read + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void ReadValuePacked(ref FastBufferReader reader, out Ray ray) + { + ReadValuePacked(ref reader, out Vector3 origin); + ReadValuePacked(ref reader, out Vector3 direction); + ray = new Ray(origin, direction); + } + + /// + /// Convenience method that reads two packed Vector2 from the ray from the stream + /// + /// The reader to read from + /// Ray2D to read + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void ReadValuePacked(ref FastBufferReader reader, out Ray2D ray2d) + { + ReadValuePacked(ref reader, out Vector2 origin); + ReadValuePacked(ref reader, out Vector2 direction); + ray2d = new Ray2D(origin, direction); + } + + /// + /// Convenience method that reads four varint floats from the color from the stream + /// + /// The reader to read from + /// Color to read + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void ReadValuePacked(ref FastBufferReader reader, out Color color) + { + color = new Color(); + ReadValuePacked(ref reader, out color.r); + ReadValuePacked(ref reader, out color.g); + ReadValuePacked(ref reader, out color.b); + ReadValuePacked(ref reader, out color.a); + } + + /// + /// Convenience method that reads four varint floats from the color from the stream + /// + /// The reader to read from + /// Color to read + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void ReadValuePacked(ref FastBufferReader reader, out Color32 color) + { + color = new Color32(); + ReadValuePacked(ref reader, out color.r); + ReadValuePacked(ref reader, out color.g); + ReadValuePacked(ref reader, out color.b); + ReadValuePacked(ref reader, out color.a); + } + + /// + /// Convenience method that reads two varint floats from the vector from the stream + /// + /// The reader to read from + /// Vector to read + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void ReadValuePacked(ref FastBufferReader reader, out Vector2 vector2) + { + vector2 = new Vector2(); + ReadValuePacked(ref reader, out vector2.x); + ReadValuePacked(ref reader, out vector2.y); + } + + /// + /// Convenience method that reads three varint floats from the vector from the stream + /// + /// The reader to read from + /// Vector to read + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void ReadValuePacked(ref FastBufferReader reader, out Vector3 vector3) + { + vector3 = new Vector3(); + ReadValuePacked(ref reader, out vector3.x); + ReadValuePacked(ref reader, out vector3.y); + ReadValuePacked(ref reader, out vector3.z); + } + + /// + /// Convenience method that reads four varint floats from the vector from the stream + /// + /// The reader to read from + /// Vector to read + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void ReadValuePacked(ref FastBufferReader reader, out Vector4 vector4) + { + vector4 = new Vector4(); + ReadValuePacked(ref reader, out vector4.x); + ReadValuePacked(ref reader, out vector4.y); + ReadValuePacked(ref reader, out vector4.z); + ReadValuePacked(ref reader, out vector4.w); + } + + /// + /// Reads the rotation from the stream. + /// + /// The reader to read from + /// Rotation to read + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void ReadValuePacked(ref FastBufferReader reader, out Quaternion rotation) + { + rotation = new Quaternion(); + ReadValuePacked(ref reader, out rotation.x); + ReadValuePacked(ref reader, out rotation.y); + ReadValuePacked(ref reader, out rotation.z); + ReadValuePacked(ref reader, out rotation.w); + } + + /// + /// Reads a string in a packed format + /// + /// The reader to read from + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static unsafe void ReadValuePacked(ref FastBufferReader reader, out string s) + { + ReadValuePacked(ref reader, out uint length); + s = "".PadRight((int)length); + int target = s.Length; + fixed (char* c = s) + { + for (int i = 0; i < target; ++i) + { + ReadValuePacked(ref reader, out c[i]); + } + } + } +#endif + #endregion + + #region Bit Packing + +#if UNITY_NETCODE_DEBUG_NO_PACKING + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void ReadValueBitPacked(ref FastBufferReader reader, T value) where T: unmanaged => reader.ReadValueSafe(out value); +#else + /// + /// Read a bit-packed 14-bit signed short from the stream. + /// See BytePacker.cs for a description of the format. + /// + /// The reader to read from + /// The value to read + public static void ReadValueBitPacked(ref FastBufferReader reader, out short value) + { + ReadValueBitPacked(ref reader, out ushort readValue); + value = (short)Arithmetic.ZigZagDecode(readValue); + } + + /// + /// Read a bit-packed 15-bit unsigned short from the stream. + /// See BytePacker.cs for a description of the format. + /// + /// The reader to read from + /// The value to read + public static unsafe void ReadValueBitPacked(ref FastBufferReader reader, out ushort value) + { + ushort returnValue = 0; + byte* ptr = ((byte*)&returnValue); + byte* data = reader.GetUnsafePtrAtCurrentPosition(); + int numBytes = (data[0] & 0b1) + 1; + if (!reader.TryBeginReadInternal(numBytes)) + { + throw new OverflowException("Reading past the end of the buffer"); + } + reader.MarkBytesRead(numBytes); + switch (numBytes) + { + case 1: + *ptr = *data; + break; + case 2: + *ptr = *data; + *(ptr + 1) = *(data + 1); + break; + default: + throw new InvalidOperationException("Could not read bit-packed value: impossible byte count"); + } + + value = (ushort)(returnValue >> 1); + } + + /// + /// Read a bit-packed 29-bit signed int from the stream. + /// See BytePacker.cs for a description of the format. + /// + /// The reader to read from + /// The value to read + public static void ReadValueBitPacked(ref FastBufferReader reader, out int value) + { + ReadValueBitPacked(ref reader, out uint readValue); + value = (int)Arithmetic.ZigZagDecode(readValue); + } + + /// + /// Read a bit-packed 30-bit unsigned int from the stream. + /// See BytePacker.cs for a description of the format. + /// + /// The reader to read from + /// The value to read + public static unsafe void ReadValueBitPacked(ref FastBufferReader reader, out uint value) + { + uint returnValue = 0; + byte* ptr = ((byte*)&returnValue); + byte* data = reader.GetUnsafePtrAtCurrentPosition(); + int numBytes = (data[0] & 0b11) + 1; + if (!reader.TryBeginReadInternal(numBytes)) + { + throw new OverflowException("Reading past the end of the buffer"); + } + reader.MarkBytesRead(numBytes); + switch (numBytes) + { + case 1: + *ptr = *data; + break; + case 2: + *ptr = *data; + *(ptr + 1) = *(data + 1); + break; + case 3: + *ptr = *data; + *(ptr + 1) = *(data + 1); + *(ptr + 2) = *(data + 2); + break; + case 4: + *ptr = *data; + *(ptr + 1) = *(data + 1); + *(ptr + 2) = *(data + 2); + *(ptr + 3) = *(data + 3); + break; + } + + value = returnValue >> 2; + } + + /// + /// Read a bit-packed 60-bit signed long from the stream. + /// See BytePacker.cs for a description of the format. + /// + /// The reader to read from + /// The value to read + public static void ReadValueBitPacked(ref FastBufferReader reader, out long value) + { + ReadValueBitPacked(ref reader, out ulong readValue); + value = Arithmetic.ZigZagDecode(readValue); + } + + /// + /// Read a bit-packed 61-bit signed long from the stream. + /// See BytePacker.cs for a description of the format. + /// + /// The reader to read from + /// The value to read + public static unsafe void ReadValueBitPacked(ref FastBufferReader reader, out ulong value) + { + ulong returnValue = 0; + byte* ptr = ((byte*)&returnValue); + byte* data = reader.GetUnsafePtrAtCurrentPosition(); + int numBytes = (data[0] & 0b111) + 1; + if (!reader.TryBeginReadInternal(numBytes)) + { + throw new OverflowException("Reading past the end of the buffer"); + } + reader.MarkBytesRead(numBytes); + switch (numBytes) + { + case 1: + *ptr = *data; + break; + case 2: + *ptr = *data; + *(ptr + 1) = *(data + 1); + break; + case 3: + *ptr = *data; + *(ptr + 1) = *(data + 1); + *(ptr + 2) = *(data + 2); + break; + case 4: + *ptr = *data; + *(ptr + 1) = *(data + 1); + *(ptr + 2) = *(data + 2); + *(ptr + 3) = *(data + 3); + break; + case 5: + *ptr = *data; + *(ptr + 1) = *(data + 1); + *(ptr + 2) = *(data + 2); + *(ptr + 3) = *(data + 3); + *(ptr + 4) = *(data + 4); + break; + case 6: + *ptr = *data; + *(ptr + 1) = *(data + 1); + *(ptr + 2) = *(data + 2); + *(ptr + 3) = *(data + 3); + *(ptr + 4) = *(data + 4); + *(ptr + 5) = *(data + 5); + break; + case 7: + *ptr = *data; + *(ptr + 1) = *(data + 1); + *(ptr + 2) = *(data + 2); + *(ptr + 3) = *(data + 3); + *(ptr + 4) = *(data + 4); + *(ptr + 5) = *(data + 5); + *(ptr + 6) = *(data + 6); + break; + case 8: + *ptr = *data; + *(ptr + 1) = *(data + 1); + *(ptr + 2) = *(data + 2); + *(ptr + 3) = *(data + 3); + *(ptr + 4) = *(data + 4); + *(ptr + 5) = *(data + 5); + *(ptr + 6) = *(data + 6); + *(ptr + 7) = *(data + 7); + break; + } + + value = returnValue >> 3; + } +#endif + #endregion + + #region Private Methods + private static void ReadUInt64Packed(ref FastBufferReader reader, out ulong value) + { + reader.ReadByteSafe(out byte firstByte); + if (firstByte <= 240) + { + value = firstByte; + return; + } + + if (firstByte <= 248) + { + reader.ReadByteSafe(out byte secondByte); + value = 240UL + ((firstByte - 241UL) << 8) + secondByte; + return; + } + + var numBytes = firstByte - 247; + if (!reader.TryBeginReadInternal(numBytes)) + { + throw new OverflowException("Reading past the end of the buffer"); + } + reader.ReadPartialValue(out value, numBytes); + } + + private static void ReadUInt32Packed(ref FastBufferReader reader, out uint value) + { + reader.ReadByteSafe(out byte firstByte); + if (firstByte <= 240) + { + value = firstByte; + return; + } + + if (firstByte <= 248) + { + reader.ReadByteSafe(out byte secondByte); + value = 240U + ((firstByte - 241U) << 8) + secondByte; + return; + } + + var numBytes = firstByte - 247; + if (!reader.TryBeginReadInternal(numBytes)) + { + throw new OverflowException("Reading past the end of the buffer"); + } + reader.ReadPartialValue(out value, numBytes); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static unsafe float ToSingle(T value) where T : unmanaged + { + float* asFloat = (float*)&value; + return *asFloat; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static unsafe double ToDouble(T value) where T : unmanaged + { + double* asDouble = (double*)&value; + return *asDouble; + } + #endregion + } +} diff --git a/com.unity.netcode.gameobjects/Runtime/Serialization/ByteUnpacker.cs.meta b/com.unity.netcode.gameobjects/Runtime/Serialization/ByteUnpacker.cs.meta new file mode 100644 index 0000000000..f3784bcdb4 --- /dev/null +++ b/com.unity.netcode.gameobjects/Runtime/Serialization/ByteUnpacker.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 73484532f9cd8a7418b6a7ac770df851 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/com.unity.netcode.gameobjects/Runtime/Serialization/BytewiseUtility.cs b/com.unity.netcode.gameobjects/Runtime/Serialization/BytewiseUtility.cs new file mode 100644 index 0000000000..e0940c3e6a --- /dev/null +++ b/com.unity.netcode.gameobjects/Runtime/Serialization/BytewiseUtility.cs @@ -0,0 +1,80 @@ +using Unity.Collections.LowLevel.Unsafe; + +namespace Unity.Netcode +{ + public static class BytewiseUtility + { + /// + /// Helper function optimized for quickly copying small numbers of bytes. + /// Faster than UnsafeUtil.Memcpy and other alternatives for amount <= 8 + /// Slower for amount > 8 + /// + /// Pointer to the source value + /// Pointer to the destination value + /// Number of bytes to copy + public static unsafe void FastCopyBytes(byte* dest, byte* source, int amount) + { + // Switch statement to write small values with assignments + // is considerably faster than calling UnsafeUtility.MemCpy + // in all builds - editor, mono, and ILCPP + switch (amount) + { + case 1: + *dest = *source; + break; + case 2: + *dest = *source; + *(dest + 1) = *(source + 1); + break; + case 3: + *dest = *source; + *(dest + 1) = *(source + 1); + *(dest + 2) = *(source + 2); + break; + case 4: + *dest = *source; + *(dest + 1) = *(source + 1); + *(dest + 2) = *(source + 2); + *(dest + 3) = *(source + 3); + break; + case 5: + *dest = *source; + *(dest + 1) = *(source + 1); + *(dest + 2) = *(source + 2); + *(dest + 3) = *(source + 3); + *(dest + 4) = *(source + 4); + break; + case 6: + *dest = *source; + *(dest + 1) = *(source + 1); + *(dest + 2) = *(source + 2); + *(dest + 3) = *(source + 3); + *(dest + 4) = *(source + 4); + *(dest + 5) = *(source + 5); + break; + case 7: + *dest = *source; + *(dest + 1) = *(source + 1); + *(dest + 2) = *(source + 2); + *(dest + 3) = *(source + 3); + *(dest + 4) = *(source + 4); + *(dest + 5) = *(source + 5); + *(dest + 6) = *(source + 6); + break; + case 8: + *dest = *source; + *(dest + 1) = *(source + 1); + *(dest + 2) = *(source + 2); + *(dest + 3) = *(source + 3); + *(dest + 4) = *(source + 4); + *(dest + 5) = *(source + 5); + *(dest + 6) = *(source + 6); + *(dest + 7) = *(source + 7); + break; + default: + UnsafeUtility.MemCpy(dest, source, amount); + break; + } + } + } +} diff --git a/com.unity.netcode.gameobjects/Runtime/Serialization/BytewiseUtility.cs.meta b/com.unity.netcode.gameobjects/Runtime/Serialization/BytewiseUtility.cs.meta new file mode 100644 index 0000000000..12bebf3d01 --- /dev/null +++ b/com.unity.netcode.gameobjects/Runtime/Serialization/BytewiseUtility.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 5df078ced492f8c45966997ceda09c8f +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/com.unity.netcode.gameobjects/Runtime/Serialization/FastBufferReader.cs b/com.unity.netcode.gameobjects/Runtime/Serialization/FastBufferReader.cs new file mode 100644 index 0000000000..c0d77a6ac5 --- /dev/null +++ b/com.unity.netcode.gameobjects/Runtime/Serialization/FastBufferReader.cs @@ -0,0 +1,850 @@ +using System; +using System.Runtime.CompilerServices; +using Unity.Collections; +using Unity.Collections.LowLevel.Unsafe; + +namespace Unity.Netcode +{ + public struct FastBufferReader : IDisposable + { + internal readonly unsafe byte* BufferPointer; + internal int PositionInternal; + internal readonly int LengthInternal; + private readonly Allocator m_Allocator; +#if DEVELOPMENT_BUILD || UNITY_EDITOR + internal int AllowedReadMark; + private bool m_InBitwiseContext; +#endif + + /// + /// Get the current read position + /// + public int Position + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => PositionInternal; + } + + /// + /// Get the total length of the buffer + /// + public int Length + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => LengthInternal; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal void CommitBitwiseReads(int amount) + { + PositionInternal += amount; +#if DEVELOPMENT_BUILD || UNITY_EDITOR + m_InBitwiseContext = false; +#endif + } + + public unsafe FastBufferReader(NativeArray buffer, Allocator allocator, int length = -1, int offset = 0) + { + LengthInternal = Math.Max(1, length == -1 ? buffer.Length : length); + if (allocator == Allocator.None) + { + BufferPointer = (byte*)buffer.GetUnsafePtr() + offset; + } + else + { + void* bufferPtr = UnsafeUtility.Malloc(LengthInternal, UnsafeUtility.AlignOf(), allocator); + UnsafeUtility.MemCpy(bufferPtr, (byte*)buffer.GetUnsafePtr() + offset, LengthInternal); + BufferPointer = (byte*)bufferPtr; + } + PositionInternal = offset; + m_Allocator = allocator; +#if DEVELOPMENT_BUILD || UNITY_EDITOR + AllowedReadMark = 0; + m_InBitwiseContext = false; +#endif + } + + /// + /// Create a FastBufferReader from an ArraySegment. + /// A new buffer will be created using the given allocator and the value will be copied in. + /// FastBufferReader will then own the data. + /// + /// The buffer to copy from + /// The allocator to use + /// The number of bytes to copy (all if this is -1) + /// The offset of the buffer to start copying from + public unsafe FastBufferReader(ArraySegment buffer, Allocator allocator, int length = -1, int offset = 0) + { + LengthInternal = Math.Max(1, length == -1 ? (buffer.Count - offset) : length); + if (allocator == Allocator.None) + { + throw new NotSupportedException("Allocator.None cannot be used with managed source buffers."); + } + else + { + void* bufferPtr = UnsafeUtility.Malloc(LengthInternal, UnsafeUtility.AlignOf(), allocator); + fixed (byte* data = buffer.Array) + { + UnsafeUtility.MemCpy(bufferPtr, data + offset, LengthInternal); + } + + BufferPointer = (byte*)bufferPtr; + } + + PositionInternal = 0; + m_Allocator = allocator; +#if DEVELOPMENT_BUILD || UNITY_EDITOR + AllowedReadMark = 0; + m_InBitwiseContext = false; +#endif + } + + /// + /// Create a FastBufferReader from an existing byte array. + /// A new buffer will be created using the given allocator and the value will be copied in. + /// FastBufferReader will then own the data. + /// + /// The buffer to copy from + /// The allocator to use + /// The number of bytes to copy (all if this is -1) + /// The offset of the buffer to start copying from + public unsafe FastBufferReader(byte[] buffer, Allocator allocator, int length = -1, int offset = 0) + { + LengthInternal = Math.Max(1, length == -1 ? (buffer.Length - offset) : length); + if (allocator == Allocator.None) + { + throw new NotSupportedException("Allocator.None cannot be used with managed source buffers."); + } + else + { + void* bufferPtr = UnsafeUtility.Malloc(LengthInternal, UnsafeUtility.AlignOf(), allocator); + fixed (byte* data = buffer) + { + UnsafeUtility.MemCpy(bufferPtr, data + offset, LengthInternal); + } + + BufferPointer = (byte*)bufferPtr; + } + + PositionInternal = 0; + m_Allocator = allocator; +#if DEVELOPMENT_BUILD || UNITY_EDITOR + AllowedReadMark = 0; + m_InBitwiseContext = false; +#endif + } + + /// + /// Create a FastBufferReader from an existing byte buffer. + /// A new buffer will be created using the given allocator and the value will be copied in. + /// FastBufferReader will then own the data. + /// + /// The buffer to copy from + /// The allocator to use + /// The number of bytes to copy + /// The offset of the buffer to start copying from + public unsafe FastBufferReader(byte* buffer, Allocator allocator, int length, int offset = 0) + { + LengthInternal = Math.Max(1, length); + if (allocator == Allocator.None) + { + BufferPointer = buffer + offset; + } + else + { + void* bufferPtr = UnsafeUtility.Malloc(LengthInternal, UnsafeUtility.AlignOf(), allocator); + UnsafeUtility.MemCpy(bufferPtr, buffer + offset, LengthInternal); + BufferPointer = (byte*)bufferPtr; + } + + PositionInternal = 0; + m_Allocator = allocator; +#if DEVELOPMENT_BUILD || UNITY_EDITOR + AllowedReadMark = 0; + m_InBitwiseContext = false; +#endif + } + + /// + /// Create a FastBufferReader from a FastBufferWriter. + /// A new buffer will be created using the given allocator and the value will be copied in. + /// FastBufferReader will then own the data. + /// + /// The writer to copy from + /// The allocator to use + /// The number of bytes to copy (all if this is -1) + /// The offset of the buffer to start copying from + public unsafe FastBufferReader(ref FastBufferWriter writer, Allocator allocator, int length = -1, int offset = 0) + { + LengthInternal = Math.Max(1, length == -1 ? writer.Length : length); + void* bufferPtr = UnsafeUtility.Malloc(LengthInternal, UnsafeUtility.AlignOf(), allocator); + UnsafeUtility.MemCpy(bufferPtr, writer.GetUnsafePtr() + offset, LengthInternal); + BufferPointer = (byte*)bufferPtr; + PositionInternal = 0; + m_Allocator = allocator; +#if DEVELOPMENT_BUILD || UNITY_EDITOR + AllowedReadMark = 0; + m_InBitwiseContext = false; +#endif + } + + /// + /// Frees the allocated buffer + /// + public unsafe void Dispose() + { + UnsafeUtility.Free(BufferPointer, m_Allocator); + } + + /// + /// Move the read position in the stream + /// + /// Absolute value to move the position to, truncated to Length + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Seek(int where) + { + PositionInternal = Math.Min(Length, where); + } + + /// + /// Mark that some bytes are going to be read via GetUnsafePtr(). + /// + /// Amount that will be read + /// + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal void MarkBytesRead(int amount) + { +#if DEVELOPMENT_BUILD || UNITY_EDITOR + if (m_InBitwiseContext) + { + throw new InvalidOperationException( + "Cannot use BufferReader in bytewise mode while in a bitwise context."); + } + if (PositionInternal + amount > AllowedReadMark) + { + throw new OverflowException("Attempted to read without first calling TryBeginRead()"); + } +#endif + PositionInternal += amount; + } + + /// + /// Retrieve a BitReader to be able to perform bitwise operations on the buffer. + /// No bytewise operations can be performed on the buffer until bitReader.Dispose() has been called. + /// At the end of the operation, FastBufferReader will remain byte-aligned. + /// + /// A BitReader + public BitReader EnterBitwiseContext() + { +#if DEVELOPMENT_BUILD || UNITY_EDITOR + m_InBitwiseContext = true; +#endif + return new BitReader(ref this); + } + + /// + /// Allows faster serialization by batching bounds checking. + /// When you know you will be reading multiple fields back-to-back and you know the total size, + /// you can call TryBeginRead() once on the total size, and then follow it with calls to + /// ReadValue() instead of ReadValueSafe() for faster serialization. + /// + /// Unsafe read operations will throw OverflowException in editor and development builds if you + /// go past the point you've marked using TryBeginRead(). In release builds, OverflowException will not be thrown + /// for performance reasons, since the point of using TryBeginRead is to avoid bounds checking in the following + /// operations in release builds. + /// + /// Amount of bytes to read + /// True if the read is allowed, false otherwise + /// If called while in a bitwise context + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool TryBeginRead(int bytes) + { +#if DEVELOPMENT_BUILD || UNITY_EDITOR + if (m_InBitwiseContext) + { + throw new InvalidOperationException( + "Cannot use BufferReader in bytewise mode while in a bitwise context."); + } +#endif + if (PositionInternal + bytes > LengthInternal) + { + return false; + } +#if DEVELOPMENT_BUILD || UNITY_EDITOR + AllowedReadMark = PositionInternal + bytes; +#endif + return true; + } + + /// + /// Allows faster serialization by batching bounds checking. + /// When you know you will be reading multiple fields back-to-back and you know the total size, + /// you can call TryBeginRead() once on the total size, and then follow it with calls to + /// ReadValue() instead of ReadValueSafe() for faster serialization. + /// + /// Unsafe read operations will throw OverflowException in editor and development builds if you + /// go past the point you've marked using TryBeginRead(). In release builds, OverflowException will not be thrown + /// for performance reasons, since the point of using TryBeginRead is to avoid bounds checking in the following + /// operations in release builds. + /// + /// The value you want to read + /// True if the read is allowed, false otherwise + /// If called while in a bitwise context + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public unsafe bool TryBeginReadValue(in T value) where T : unmanaged + { +#if DEVELOPMENT_BUILD || UNITY_EDITOR + if (m_InBitwiseContext) + { + throw new InvalidOperationException( + "Cannot use BufferReader in bytewise mode while in a bitwise context."); + } +#endif + int len = sizeof(T); + if (PositionInternal + len > LengthInternal) + { + return false; + } +#if DEVELOPMENT_BUILD || UNITY_EDITOR + AllowedReadMark = PositionInternal + len; +#endif + return true; + } + + /// + /// Internal version of TryBeginRead. + /// Differs from TryBeginRead only in that it won't ever move the AllowedReadMark backward. + /// + /// + /// + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal bool TryBeginReadInternal(int bytes) + { +#if DEVELOPMENT_BUILD || UNITY_EDITOR + if (m_InBitwiseContext) + { + throw new InvalidOperationException( + "Cannot use BufferReader in bytewise mode while in a bitwise context."); + } +#endif + if (PositionInternal + bytes > LengthInternal) + { + return false; + } +#if DEVELOPMENT_BUILD || UNITY_EDITOR + if (PositionInternal + bytes > AllowedReadMark) + { + AllowedReadMark = PositionInternal + bytes; + } +#endif + return true; + } + + /// + /// Returns an array representation of the underlying byte buffer. + /// !!Allocates a new array!! + /// + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public unsafe byte[] ToArray() + { + byte[] ret = new byte[Length]; + fixed (byte* b = ret) + { + UnsafeUtility.MemCpy(b, BufferPointer, Length); + } + return ret; + } + + /// + /// Gets a direct pointer to the underlying buffer + /// + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public unsafe byte* GetUnsafePtr() + { + return BufferPointer; + } + + /// + /// Gets a direct pointer to the underlying buffer at the current read position + /// + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public unsafe byte* GetUnsafePtrAtCurrentPosition() + { + return BufferPointer + PositionInternal; + } + + /// + /// Read an INetworkSerializable + /// + /// INetworkSerializable instance + /// + /// + public void ReadNetworkSerializable(out T value) where T : INetworkSerializable, new() + { + value = new T(); + var bufferSerializer = new BufferSerializer(new BufferSerializerReader(ref this)); + value.NetworkSerialize(bufferSerializer); + } + + /// + /// Read an array of INetworkSerializables + /// + /// INetworkSerializable instance + /// + /// + public void ReadNetworkSerializable(out T[] value) where T : INetworkSerializable, new() + { + ReadValueSafe(out int size); + value = new T[size]; + for (var i = 0; i < size; ++i) + { + ReadNetworkSerializable(out value[i]); + } + } + + /// + /// Reads a string + /// NOTE: ALLOCATES + /// + /// Stores the read string + /// Whether or not to use one byte per character. This will only allow ASCII + public unsafe void ReadValue(out string s, bool oneByteChars = false) + { + ReadValue(out uint length); + s = "".PadRight((int)length); + int target = s.Length; + fixed (char* native = s) + { + if (oneByteChars) + { + for (int i = 0; i < target; ++i) + { + ReadByte(out byte b); + native[i] = (char)b; + } + } + else + { + ReadBytes((byte*)native, target * sizeof(char)); + } + } + } + + /// + /// Reads a string. + /// NOTE: ALLOCATES + /// + /// "Safe" version - automatically performs bounds checking. Less efficient than bounds checking + /// for multiple reads at once by calling TryBeginRead. + /// + /// Stores the read string + /// Whether or not to use one byte per character. This will only allow ASCII + public unsafe void ReadValueSafe(out string s, bool oneByteChars = false) + { +#if DEVELOPMENT_BUILD || UNITY_EDITOR + if (m_InBitwiseContext) + { + throw new InvalidOperationException( + "Cannot use BufferReader in bytewise mode while in a bitwise context."); + } +#endif + + if (!TryBeginReadInternal(sizeof(uint))) + { + throw new OverflowException("Reading past the end of the buffer"); + } + + ReadValue(out uint length); + + if (!TryBeginReadInternal((int)length * (oneByteChars ? 1 : sizeof(char)))) + { + throw new OverflowException("Reading past the end of the buffer"); + } + s = "".PadRight((int)length); + int target = s.Length; + fixed (char* native = s) + { + if (oneByteChars) + { + for (int i = 0; i < target; ++i) + { + ReadByte(out byte b); + native[i] = (char)b; + } + } + else + { + ReadBytes((byte*)native, target * sizeof(char)); + } + } + } + + /// + /// Writes an unmanaged array + /// NOTE: ALLOCATES + /// + /// Stores the read array + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public unsafe void ReadValue(out T[] array) where T : unmanaged + { + ReadValue(out int sizeInTs); + int sizeInBytes = sizeInTs * sizeof(T); + array = new T[sizeInTs]; + fixed (T* native = array) + { + byte* bytes = (byte*)(native); + ReadBytes(bytes, sizeInBytes); + } + } + + /// + /// Reads an unmanaged array + /// NOTE: ALLOCATES + /// + /// "Safe" version - automatically performs bounds checking. Less efficient than bounds checking + /// for multiple reads at once by calling TryBeginRead. + /// + /// Stores the read array + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public unsafe void ReadValueSafe(out T[] array) where T : unmanaged + { +#if DEVELOPMENT_BUILD || UNITY_EDITOR + if (m_InBitwiseContext) + { + throw new InvalidOperationException( + "Cannot use BufferReader in bytewise mode while in a bitwise context."); + } +#endif + + if (!TryBeginReadInternal(sizeof(int))) + { + throw new OverflowException("Reading past the end of the buffer"); + } + ReadValue(out int sizeInTs); + int sizeInBytes = sizeInTs * sizeof(T); + if (!TryBeginReadInternal(sizeInBytes)) + { + throw new OverflowException("Reading past the end of the buffer"); + } + array = new T[sizeInTs]; + fixed (T* native = array) + { + byte* bytes = (byte*)(native); + ReadBytes(bytes, sizeInBytes); + } + } + + /// + /// Read a partial value. The value is zero-initialized and then the specified number of bytes is read into it. + /// + /// Value to read + /// Number of bytes + /// Offset into the value to write the bytes + /// + /// + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public unsafe void ReadPartialValue(out T value, int bytesToRead, int offsetBytes = 0) where T : unmanaged + { +#if DEVELOPMENT_BUILD || UNITY_EDITOR + if (m_InBitwiseContext) + { + throw new InvalidOperationException( + "Cannot use BufferReader in bytewise mode while in a bitwise context."); + } + if (PositionInternal + bytesToRead > AllowedReadMark) + { + throw new OverflowException("Attempted to read without first calling TryBeginRead()"); + } +#endif + + var val = new T(); + byte* ptr = ((byte*)&val) + offsetBytes; + byte* bufferPointer = BufferPointer + PositionInternal; + UnsafeUtility.MemCpy(ptr, bufferPointer, bytesToRead); + + PositionInternal += bytesToRead; + value = val; + } + + /// + /// Read a byte to the stream. + /// + /// Stores the read value + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public unsafe void ReadByte(out byte value) + { +#if DEVELOPMENT_BUILD || UNITY_EDITOR + if (m_InBitwiseContext) + { + throw new InvalidOperationException( + "Cannot use BufferReader in bytewise mode while in a bitwise context."); + } + if (PositionInternal + 1 > AllowedReadMark) + { + throw new OverflowException("Attempted to read without first calling TryBeginRead()"); + } +#endif + value = BufferPointer[PositionInternal++]; + } + + /// + /// Read a byte to the stream. + /// + /// "Safe" version - automatically performs bounds checking. Less efficient than bounds checking + /// for multiple reads at once by calling TryBeginRead. + /// + /// Stores the read value + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public unsafe void ReadByteSafe(out byte value) + { +#if DEVELOPMENT_BUILD || UNITY_EDITOR + if (m_InBitwiseContext) + { + throw new InvalidOperationException( + "Cannot use BufferReader in bytewise mode while in a bitwise context."); + } +#endif + + if (!TryBeginReadInternal(1)) + { + throw new OverflowException("Reading past the end of the buffer"); + } + value = BufferPointer[PositionInternal++]; + } + + /// + /// Read multiple bytes to the stream + /// + /// Pointer to the destination buffer + /// Number of bytes to read - MUST BE <= BUFFER SIZE + /// Offset of the byte buffer to store into + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public unsafe void ReadBytes(byte* value, int size, int offset = 0) + { +#if DEVELOPMENT_BUILD || UNITY_EDITOR + if (m_InBitwiseContext) + { + throw new InvalidOperationException( + "Cannot use BufferReader in bytewise mode while in a bitwise context."); + } + if (PositionInternal + size > AllowedReadMark) + { + throw new OverflowException("Attempted to read without first calling TryBeginRead()"); + } +#endif + UnsafeUtility.MemCpy(value + offset, (BufferPointer + PositionInternal), size); + PositionInternal += size; + } + + /// + /// Read multiple bytes to the stream + /// + /// "Safe" version - automatically performs bounds checking. Less efficient than bounds checking + /// for multiple reads at once by calling TryBeginRead. + /// + /// Pointer to the destination buffer + /// Number of bytes to read - MUST BE <= BUFFER SIZE + /// Offset of the byte buffer to store into + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public unsafe void ReadBytesSafe(byte* value, int size, int offset = 0) + { +#if DEVELOPMENT_BUILD || UNITY_EDITOR + if (m_InBitwiseContext) + { + throw new InvalidOperationException( + "Cannot use BufferReader in bytewise mode while in a bitwise context."); + } +#endif + + if (!TryBeginReadInternal(size)) + { + throw new OverflowException("Writing past the end of the buffer"); + } + UnsafeUtility.MemCpy(value + offset, (BufferPointer + PositionInternal), size); + PositionInternal += size; + } + + /// + /// Read multiple bytes from the stream + /// + /// Pointer to the destination buffer + /// Number of bytes to read - MUST BE <= BUFFER SIZE + /// Offset of the byte buffer to store into + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public unsafe void ReadBytes(ref byte[] value, int size, int offset = 0) + { + fixed (byte* ptr = value) + { + ReadBytes(ptr, size, offset); + } + } + + /// + /// Read multiple bytes from the stream + /// + /// "Safe" version - automatically performs bounds checking. Less efficient than bounds checking + /// for multiple reads at once by calling TryBeginRead. + /// + /// Pointer to the destination buffer + /// Number of bytes to read - MUST BE <= BUFFER SIZE + /// Offset of the byte buffer to store into + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public unsafe void ReadBytesSafe(ref byte[] value, int size, int offset = 0) + { + fixed (byte* ptr = value) + { + ReadBytesSafe(ptr, size, offset); + } + } + + /// + /// Read a value of type FixedUnmanagedArray from the buffer. + /// + /// The value to copy + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public unsafe void ReadValue(out FixedUnmanagedArray value, int count) + where TPropertyType : unmanaged + where TStorageType : unmanaged, IFixedArrayStorage + { + int len = sizeof(TPropertyType) * count; +#if DEVELOPMENT_BUILD || UNITY_EDITOR + if (m_InBitwiseContext) + { + throw new InvalidOperationException( + "Cannot use BufferWriter in bytewise mode while in a bitwise context."); + } + if (PositionInternal + len > AllowedReadMark) + { + throw new OverflowException("Attempted to write without first calling TryBeginWrite()"); + } +#endif + + value = new FixedUnmanagedArray(); + BytewiseUtility.FastCopyBytes((byte*)value.GetArrayPtr(), BufferPointer + PositionInternal, len); + value.Count = len; + PositionInternal += len; + } + + /// + /// Read a value of type FixedUnmanagedArray from the buffer. + /// + /// "Safe" version - automatically performs bounds checking. Less efficient than bounds checking + /// for multiple reads at once by calling TryBeginRead. + /// + /// The value to copy + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public unsafe void ReadValueSafe(out FixedUnmanagedArray value, int count) + where TPropertyType : unmanaged + where TStorageType : unmanaged, IFixedArrayStorage + { + int len = sizeof(TPropertyType) * count; +#if DEVELOPMENT_BUILD || UNITY_EDITOR + if (m_InBitwiseContext) + { + throw new InvalidOperationException( + "Cannot use BufferWriter in bytewise mode while in a bitwise context."); + } + if (PositionInternal + len > AllowedReadMark) + { + throw new OverflowException("Attempted to write without first calling TryBeginWrite()"); + } +#endif + + value = new FixedUnmanagedArray(); + BytewiseUtility.FastCopyBytes((byte*)value.GetArrayPtr(), BufferPointer + PositionInternal, len); + value.Count = len; + PositionInternal += len; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + [Obsolete("FixedUnmanagedArray must be written/read using a count.")] + public void ReadValue(out FixedUnmanagedArray value) + where TPropertyType : unmanaged + where TStorageType : unmanaged, IFixedArrayStorage + { + throw new NotSupportedException("FixedUnmanagedArray must be written/read using a count."); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + [Obsolete("FixedUnmanagedArray must be written/read using a count.")] + public void ReadValueSafe(out FixedUnmanagedArray value) + where TPropertyType : unmanaged + where TStorageType : unmanaged, IFixedArrayStorage + { + throw new NotSupportedException("FixedUnmanagedArray must be written/read using a count."); + } + + /// + /// Read a value of any unmanaged type to the buffer. + /// It will be copied from the buffer exactly as it existed in memory on the writing end. + /// + /// The read value + /// Any unmanaged type + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public unsafe void ReadValue(out T value) where T : unmanaged + { + int len = sizeof(T); + +#if DEVELOPMENT_BUILD || UNITY_EDITOR + if (m_InBitwiseContext) + { + throw new InvalidOperationException( + "Cannot use BufferReader in bytewise mode while in a bitwise context."); + } + if (PositionInternal + len > AllowedReadMark) + { + throw new OverflowException("Attempted to read without first calling TryBeginRead()"); + } +#endif + + fixed (T* ptr = &value) + { + BytewiseUtility.FastCopyBytes((byte*)ptr, BufferPointer + PositionInternal, len); + } + PositionInternal += len; + } + + /// + /// Read a value of any unmanaged type to the buffer. + /// It will be copied from the buffer exactly as it existed in memory on the writing end. + /// + /// "Safe" version - automatically performs bounds checking. Less efficient than bounds checking + /// for multiple reads at once by calling TryBeginRead. + /// + /// The read value + /// Any unmanaged type + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public unsafe void ReadValueSafe(out T value) where T : unmanaged + { + int len = sizeof(T); + +#if DEVELOPMENT_BUILD || UNITY_EDITOR + if (m_InBitwiseContext) + { + throw new InvalidOperationException( + "Cannot use BufferReader in bytewise mode while in a bitwise context."); + } +#endif + + if (!TryBeginReadInternal(len)) + { + throw new OverflowException("Writing past the end of the buffer"); + } + + + fixed (T* ptr = &value) + { + BytewiseUtility.FastCopyBytes((byte*)ptr, BufferPointer + PositionInternal, len); + } + PositionInternal += len; + } + } +} diff --git a/com.unity.netcode.gameobjects/Runtime/Serialization/FastBufferReader.cs.meta b/com.unity.netcode.gameobjects/Runtime/Serialization/FastBufferReader.cs.meta new file mode 100644 index 0000000000..3667f738aa --- /dev/null +++ b/com.unity.netcode.gameobjects/Runtime/Serialization/FastBufferReader.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 5479786a4b1f57648a0fe56bd37a823b +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/com.unity.netcode.gameobjects/Runtime/Serialization/FastBufferReaderExtensions.cs b/com.unity.netcode.gameobjects/Runtime/Serialization/FastBufferReaderExtensions.cs new file mode 100644 index 0000000000..0e6a668c5e --- /dev/null +++ b/com.unity.netcode.gameobjects/Runtime/Serialization/FastBufferReaderExtensions.cs @@ -0,0 +1,327 @@ + +using System; +using UnityEngine; + +namespace Unity.Netcode +{ + public static class FastBufferReaderExtensions + { + + /// + /// Reads a boxed object in a standard format + /// Named differently from other ReadValue methods to avoid accidental boxing + /// + /// The reader to use to read the value + /// The object to read + /// The type to be read + /// + /// If true, reads a byte indicating whether or not the object is null. + /// Should match the way the object was written. + /// + public static void ReadObject(this ref FastBufferReader reader, out object value, Type type, bool isNullable = false) + { + if (isNullable || type.IsNullable()) + { + reader.ReadValueSafe(out bool isNull); + + if (isNull) + { + value = null; + return; + } + } + + var hasDeserializer = SerializationTypeTable.Deserializers.TryGetValue(type, out var deserializer); + if (hasDeserializer) + { + deserializer(ref reader, out value); + return; + } + + if (type.IsArray && type.HasElementType) + { + reader.ReadValueSafe(out int length); + + var arr = Array.CreateInstance(type.GetElementType(), length); + + for (int i = 0; i < length; i++) + { + reader.ReadObject(out object item, type.GetElementType()); + arr.SetValue(item, i); + } + + value = arr; + return; + } + + if (type.IsEnum) + { + switch (Type.GetTypeCode(type)) + { + case TypeCode.Boolean: + reader.ReadValueSafe(out byte boolVal); + value = Enum.ToObject(type, boolVal != 0); + return; + case TypeCode.Char: + reader.ReadValueSafe(out char charVal); + value = Enum.ToObject(type, charVal); + return; + case TypeCode.SByte: + reader.ReadValueSafe(out sbyte sbyteVal); + value = Enum.ToObject(type, sbyteVal); + return; + case TypeCode.Byte: + reader.ReadValueSafe(out byte byteVal); + value = Enum.ToObject(type, byteVal); + return; + case TypeCode.Int16: + reader.ReadValueSafe(out short shortVal); + value = Enum.ToObject(type, shortVal); + return; + case TypeCode.UInt16: + reader.ReadValueSafe(out ushort ushortVal); + value = Enum.ToObject(type, ushortVal); + return; + case TypeCode.Int32: + reader.ReadValueSafe(out int intVal); + value = Enum.ToObject(type, intVal); + return; + case TypeCode.UInt32: + reader.ReadValueSafe(out uint uintVal); + value = Enum.ToObject(type, uintVal); + return; + case TypeCode.Int64: + reader.ReadValueSafe(out long longVal); + value = Enum.ToObject(type, longVal); + return; + case TypeCode.UInt64: + reader.ReadValueSafe(out ulong ulongVal); + value = Enum.ToObject(type, ulongVal); + return; + } + } + + if (type == typeof(GameObject)) + { + reader.ReadValueSafe(out GameObject go); + value = go; + return; + } + + if (type == typeof(NetworkObject)) + { + reader.ReadValueSafe(out NetworkObject no); + value = no; + return; + } + + if (typeof(NetworkBehaviour).IsAssignableFrom(type)) + { + reader.ReadValueSafe(out NetworkBehaviour nb); + value = nb; + return; + } + /*if (value is INetworkSerializable) + { + //TODO ((INetworkSerializable)value).NetworkSerialize(new NetworkSerializer(this)); + return; + }*/ + + throw new ArgumentException($"{nameof(FastBufferReader)} cannot read type {type.Name} - it does not implement {nameof(INetworkSerializable)}"); + } + + /// + /// Read a GameObject + /// + /// The reader to use to read the value + /// value to read + public static void ReadValue(this ref FastBufferReader reader, out GameObject value) + { + reader.ReadValue(out ulong networkObjectId); + + if (NetworkManager.Singleton.SpawnManager.SpawnedObjects.TryGetValue(networkObjectId, out NetworkObject networkObject)) + { + value = networkObject.gameObject; + return; + } + + if (NetworkLog.CurrentLogLevel <= LogLevel.Normal) + { + NetworkLog.LogWarning($"{nameof(FastBufferReader)} cannot find the {nameof(GameObject)} sent in the {nameof(NetworkSpawnManager.SpawnedObjects)} list, it may have been destroyed. {nameof(networkObjectId)}: {networkObjectId}"); + } + + value = null; + } + + /// + /// Read a GameObject + /// + /// "Safe" version - automatically performs bounds checking. Less efficient than bounds checking + /// for multiple reads at once by calling TryBeginRead. + /// + /// The reader to use to read the value + /// value to read + public static void ReadValueSafe(this ref FastBufferReader reader, out GameObject value) + { + reader.ReadValueSafe(out ulong networkObjectId); + + if (NetworkManager.Singleton.SpawnManager.SpawnedObjects.TryGetValue(networkObjectId, out NetworkObject networkObject)) + { + value = networkObject.gameObject; + return; + } + + if (NetworkLog.CurrentLogLevel <= LogLevel.Normal) + { + NetworkLog.LogWarning($"{nameof(FastBufferReader)} cannot find the {nameof(GameObject)} sent in the {nameof(NetworkSpawnManager.SpawnedObjects)} list, it may have been destroyed. {nameof(networkObjectId)}: {networkObjectId}"); + } + + value = null; + } + + /// + /// Read an array of GameObjects + /// + /// The reader to use to read the value + /// value to read + public static void ReadValueSafe(this ref FastBufferReader reader, out GameObject[] value) + { + reader.ReadValueSafe(out int size); + value = new GameObject[size]; + for (var i = 0; i < size; ++i) + { + reader.ReadValueSafe(out value[i]); + } + } + + /// + /// Read a NetworkObject + /// + /// The reader to use to read the value + /// value to read + public static void ReadValue(this ref FastBufferReader reader, out NetworkObject value) + { + reader.ReadValue(out ulong networkObjectId); + + if (NetworkManager.Singleton.SpawnManager.SpawnedObjects.TryGetValue(networkObjectId, out NetworkObject networkObject)) + { + value = networkObject; + return; + } + + if (NetworkLog.CurrentLogLevel <= LogLevel.Normal) + { + NetworkLog.LogWarning($"{nameof(FastBufferReader)} cannot find the {nameof(GameObject)} sent in the {nameof(NetworkSpawnManager.SpawnedObjects)} list, it may have been destroyed. {nameof(networkObjectId)}: {networkObjectId}"); + } + + value = null; + } + + /// + /// Read a NetworkObject + /// + /// "Safe" version - automatically performs bounds checking. Less efficient than bounds checking + /// for multiple reads at once by calling TryBeginRead. + /// + /// The reader to use to read the value + /// value to read + public static void ReadValueSafe(this ref FastBufferReader reader, out NetworkObject value) + { + reader.ReadValueSafe(out ulong networkObjectId); + + if (NetworkManager.Singleton.SpawnManager.SpawnedObjects.TryGetValue(networkObjectId, out NetworkObject networkObject)) + { + value = networkObject; + return; + } + + if (NetworkLog.CurrentLogLevel <= LogLevel.Normal) + { + NetworkLog.LogWarning($"{nameof(FastBufferReader)} cannot find the {nameof(GameObject)} sent in the {nameof(NetworkSpawnManager.SpawnedObjects)} list, it may have been destroyed. {nameof(networkObjectId)}: {networkObjectId}"); + } + + value = null; + } + + /// + /// Read an array of NetworkObjects + /// + /// The reader to use to read the value + /// value to read + public static void ReadValueSafe(this ref FastBufferReader reader, out NetworkObject[] value) + { + reader.ReadValueSafe(out int size); + value = new NetworkObject[size]; + for (var i = 0; i < size; ++i) + { + reader.ReadValueSafe(out value[i]); + } + } + + /// + /// Read a NetworkBehaviour + /// + /// The reader to use to read the value + /// value to read + public static void ReadValue(this ref FastBufferReader reader, out NetworkBehaviour value) + { + reader.ReadValue(out ulong networkObjectId); + reader.ReadValue(out ushort networkBehaviourId); + + if (NetworkManager.Singleton.SpawnManager.SpawnedObjects.TryGetValue(networkObjectId, out NetworkObject networkObject)) + { + value = networkObject.GetNetworkBehaviourAtOrderIndex(networkBehaviourId); + return; + } + + if (NetworkLog.CurrentLogLevel <= LogLevel.Normal) + { + NetworkLog.LogWarning($"{nameof(FastBufferReader)} cannot find the {nameof(NetworkBehaviour)} sent in the {nameof(NetworkSpawnManager.SpawnedObjects)} list, it may have been destroyed. {nameof(networkObjectId)}: {networkObjectId}"); + } + + value = null; + } + + /// + /// Read a NetworkBehaviour + /// + /// "Safe" version - automatically performs bounds checking. Less efficient than bounds checking + /// for multiple reads at once by calling TryBeginRead. + /// + /// The reader to use to read the value + /// value to read + public static void ReadValueSafe(this ref FastBufferReader reader, out NetworkBehaviour value) + { + reader.ReadValueSafe(out ulong networkObjectId); + reader.ReadValueSafe(out ushort networkBehaviourId); + + if (NetworkManager.Singleton.SpawnManager.SpawnedObjects.TryGetValue(networkObjectId, out NetworkObject networkObject)) + { + value = networkObject.GetNetworkBehaviourAtOrderIndex(networkBehaviourId); + return; + } + + if (NetworkLog.CurrentLogLevel <= LogLevel.Normal) + { + NetworkLog.LogWarning($"{nameof(FastBufferReader)} cannot find the {nameof(NetworkBehaviour)} sent in the {nameof(NetworkSpawnManager.SpawnedObjects)} list, it may have been destroyed. {nameof(networkObjectId)}: {networkObjectId}"); + } + + value = null; + } + + /// + /// Read an array of NetworkBehaviours + /// + /// The reader to use to read the value + /// value to read + public static void ReadValueSafe(this ref FastBufferReader reader, out NetworkBehaviour[] value) + { + reader.ReadValueSafe(out int size); + value = new NetworkBehaviour[size]; + for (var i = 0; i < size; ++i) + { + reader.ReadValueSafe(out value[i]); + } + } + } +} diff --git a/com.unity.netcode.gameobjects/Runtime/Serialization/FastBufferReaderExtensions.cs.meta b/com.unity.netcode.gameobjects/Runtime/Serialization/FastBufferReaderExtensions.cs.meta new file mode 100644 index 0000000000..d00798303c --- /dev/null +++ b/com.unity.netcode.gameobjects/Runtime/Serialization/FastBufferReaderExtensions.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 4dc2c9158967ec847895c0d9653283fa +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/com.unity.netcode.gameobjects/Runtime/Serialization/FastBufferWriter.cs b/com.unity.netcode.gameobjects/Runtime/Serialization/FastBufferWriter.cs new file mode 100644 index 0000000000..5c9db03925 --- /dev/null +++ b/com.unity.netcode.gameobjects/Runtime/Serialization/FastBufferWriter.cs @@ -0,0 +1,899 @@ +using System; +using System.Runtime.CompilerServices; +using Unity.Collections; +using Unity.Collections.LowLevel.Unsafe; + +namespace Unity.Netcode +{ + public struct FastBufferWriter : IDisposable + { + internal unsafe byte* BufferPointer; + internal int PositionInternal; + private int m_Length; + internal int CapacityInternal; + internal readonly int MaxCapacityInternal; + private readonly Allocator m_Allocator; +#if DEVELOPMENT_BUILD || UNITY_EDITOR + internal int AllowedWriteMark; + private bool m_InBitwiseContext; +#endif + + /// + /// The current write position + /// + public int Position + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => PositionInternal; + } + + /// + /// The current total buffer size + /// + public int Capacity + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => CapacityInternal; + } + + /// + /// The maximum possible total buffer size + /// + public int MaxCapacity + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => MaxCapacityInternal; + } + + /// + /// The total amount of bytes that have been written to the stream + /// + public int Length + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => PositionInternal > m_Length ? PositionInternal : m_Length; + } + + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal void CommitBitwiseWrites(int amount) + { + PositionInternal += amount; +#if DEVELOPMENT_BUILD || UNITY_EDITOR + m_InBitwiseContext = false; +#endif + } + + /// + /// Create a FastBufferWriter. + /// + /// Size of the buffer to create + /// Allocator to use in creating it + /// Maximum size the buffer can grow to. If less than size, buffer cannot grow. + public unsafe FastBufferWriter(int size, Allocator allocator, int maxSize = -1) + { + void* buffer = UnsafeUtility.Malloc(size, UnsafeUtility.AlignOf(), allocator); +#if DEVELOPMENT_BUILD || UNITY_EDITOR + UnsafeUtility.MemSet(buffer, 0, size); +#endif + BufferPointer = (byte*)buffer; + PositionInternal = 0; + m_Length = 0; + CapacityInternal = size; + m_Allocator = allocator; + MaxCapacityInternal = maxSize < size ? size : maxSize; +#if DEVELOPMENT_BUILD || UNITY_EDITOR + AllowedWriteMark = 0; + m_InBitwiseContext = false; +#endif + } + + /// + /// Frees the allocated buffer + /// + public unsafe void Dispose() + { + UnsafeUtility.Free(BufferPointer, m_Allocator); + } + + /// + /// Move the write position in the stream. + /// Note that moving forward past the current length will extend the buffer's Length value even if you don't write. + /// + /// Absolute value to move the position to, truncated to Capacity + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Seek(int where) + { + // This avoids us having to synchronize length all the time. + // Writing things is a much more common operation than seeking + // or querying length. The length here is a high watermark of + // what's been written. So before we seek, if the current position + // is greater than the length, we update that watermark. + // When querying length later, we'll return whichever of the two + // values is greater, thus if we write past length, length increases + // because position increases, and if we seek backward, length remembers + // the position it was in. + // Seeking forward will not update the length. + where = Math.Min(where, CapacityInternal); + if (PositionInternal > m_Length && where < PositionInternal) + { + m_Length = PositionInternal; + } + + PositionInternal = where; + } + + /// + /// Truncate the stream by setting Length to the specified value. + /// If Position is greater than the specified value, it will be moved as well. + /// + /// The value to truncate to. If -1, the current position will be used. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Truncate(int where = -1) + { + if (where == -1) + { + where = Position; + } + + if (PositionInternal > where) + { + PositionInternal = where; + } + + if (m_Length > where) + { + m_Length = where; + } + } + + /// + /// Retrieve a BitWriter to be able to perform bitwise operations on the buffer. + /// No bytewise operations can be performed on the buffer until bitWriter.Dispose() has been called. + /// At the end of the operation, FastBufferWriter will remain byte-aligned. + /// + /// A BitWriter + public BitWriter EnterBitwiseContext() + { +#if DEVELOPMENT_BUILD || UNITY_EDITOR + m_InBitwiseContext = true; +#endif + return new BitWriter(ref this); + } + + internal unsafe void Grow(int additionalSizeRequired) + { + var desiredSize = CapacityInternal * 2; + while (desiredSize < Position + additionalSizeRequired) + { + desiredSize *= 2; + } + + var newSize = Math.Min(desiredSize, MaxCapacityInternal); + void* buffer = UnsafeUtility.Malloc(newSize, UnsafeUtility.AlignOf(), m_Allocator); +#if DEVELOPMENT_BUILD || UNITY_EDITOR + UnsafeUtility.MemSet(buffer, 0, newSize); +#endif + UnsafeUtility.MemCpy(buffer, BufferPointer, Length); + UnsafeUtility.Free(BufferPointer, m_Allocator); + BufferPointer = (byte*)buffer; + CapacityInternal = newSize; + } + + /// + /// Allows faster serialization by batching bounds checking. + /// When you know you will be writing multiple fields back-to-back and you know the total size, + /// you can call TryBeginWrite() once on the total size, and then follow it with calls to + /// WriteValue() instead of WriteValueSafe() for faster serialization. + /// + /// Unsafe write operations will throw OverflowException in editor and development builds if you + /// go past the point you've marked using TryBeginWrite(). In release builds, OverflowException will not be thrown + /// for performance reasons, since the point of using TryBeginWrite is to avoid bounds checking in the following + /// operations in release builds. + /// + /// Amount of bytes to write + /// True if the write is allowed, false otherwise + /// If called while in a bitwise context + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool TryBeginWrite(int bytes) + { +#if DEVELOPMENT_BUILD || UNITY_EDITOR + if (m_InBitwiseContext) + { + throw new InvalidOperationException( + "Cannot use BufferWriter in bytewise mode while in a bitwise context."); + } +#endif + if (PositionInternal + bytes > CapacityInternal) + { + if (PositionInternal + bytes > MaxCapacityInternal) + { + return false; + } + + if (CapacityInternal < MaxCapacityInternal) + { + Grow(bytes); + } + else + { + return false; + } + } +#if DEVELOPMENT_BUILD || UNITY_EDITOR + AllowedWriteMark = PositionInternal + bytes; +#endif + return true; + } + + /// + /// Allows faster serialization by batching bounds checking. + /// When you know you will be writing multiple fields back-to-back and you know the total size, + /// you can call TryBeginWrite() once on the total size, and then follow it with calls to + /// WriteValue() instead of WriteValueSafe() for faster serialization. + /// + /// Unsafe write operations will throw OverflowException in editor and development builds if you + /// go past the point you've marked using TryBeginWrite(). In release builds, OverflowException will not be thrown + /// for performance reasons, since the point of using TryBeginWrite is to avoid bounds checking in the following + /// operations in release builds. Instead, attempting to write past the marked position in release builds + /// will write to random memory and cause undefined behavior, likely including instability and crashes. + /// + /// The value you want to write + /// True if the write is allowed, false otherwise + /// If called while in a bitwise context + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public unsafe bool TryBeginWriteValue(in T value) where T : unmanaged + { +#if DEVELOPMENT_BUILD || UNITY_EDITOR + if (m_InBitwiseContext) + { + throw new InvalidOperationException( + "Cannot use BufferWriter in bytewise mode while in a bitwise context."); + } +#endif + int len = sizeof(T); + if (PositionInternal + len > CapacityInternal) + { + if (PositionInternal + len > MaxCapacityInternal) + { + return false; + } + + if (CapacityInternal < MaxCapacityInternal) + { + Grow(len); + } + else + { + return false; + } + } +#if DEVELOPMENT_BUILD || UNITY_EDITOR + AllowedWriteMark = PositionInternal + len; +#endif + return true; + } + + /// + /// Internal version of TryBeginWrite. + /// Differs from TryBeginWrite only in that it won't ever move the AllowedWriteMark backward. + /// + /// + /// + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool TryBeginWriteInternal(int bytes) + { +#if DEVELOPMENT_BUILD || UNITY_EDITOR + if (m_InBitwiseContext) + { + throw new InvalidOperationException( + "Cannot use BufferWriter in bytewise mode while in a bitwise context."); + } +#endif + if (PositionInternal + bytes > CapacityInternal) + { + if (PositionInternal + bytes > MaxCapacityInternal) + { + return false; + } + + if (CapacityInternal < MaxCapacityInternal) + { + Grow(bytes); + } + else + { + return false; + } + } +#if DEVELOPMENT_BUILD || UNITY_EDITOR + if (PositionInternal + bytes > AllowedWriteMark) + { + AllowedWriteMark = PositionInternal + bytes; + } +#endif + return true; + } + + /// + /// Returns an array representation of the underlying byte buffer. + /// !!Allocates a new array!! + /// + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public unsafe byte[] ToArray() + { + byte[] ret = new byte[Length]; + fixed (byte* b = ret) + { + UnsafeUtility.MemCpy(b, BufferPointer, Length); + } + + return ret; + } + + /// + /// Gets a direct pointer to the underlying buffer + /// + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public unsafe byte* GetUnsafePtr() + { + return BufferPointer; + } + + /// + /// Gets a direct pointer to the underlying buffer at the current read position + /// + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public unsafe byte* GetUnsafePtrAtCurrentPosition() + { + return BufferPointer + PositionInternal; + } + + /// + /// Get the required size to write a string + /// + /// The string to write + /// Whether or not to use one byte per character. This will only allow ASCII + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static int GetWriteSize(string s, bool oneByteChars = false) + { + return sizeof(int) + s.Length * (oneByteChars ? sizeof(byte) : sizeof(char)); + } + + /// + /// Write an INetworkSerializable + /// + /// The value to write + /// + public void WriteNetworkSerializable(in T value) where T : INetworkSerializable + { + var bufferSerializer = new BufferSerializer(new BufferSerializerWriter(ref this)); + value.NetworkSerialize(bufferSerializer); + } + + /// + /// Write an array of INetworkSerializables + /// + /// The value to write + /// + /// + /// + public void WriteNetworkSerializable(INetworkSerializable[] array, int count = -1, int offset = 0) where T : INetworkSerializable + { + int sizeInTs = count != -1 ? count : array.Length - offset; + WriteValueSafe(sizeInTs); + foreach (var item in array) + { + WriteNetworkSerializable(item); + } + } + + /// + /// Writes a string + /// + /// The string to write + /// Whether or not to use one byte per character. This will only allow ASCII + public unsafe void WriteValue(string s, bool oneByteChars = false) + { + WriteValue((uint)s.Length); + int target = s.Length; + if (oneByteChars) + { + for (int i = 0; i < target; ++i) + { + WriteByte((byte)s[i]); + } + } + else + { + fixed (char* native = s) + { + WriteBytes((byte*)native, target * sizeof(char)); + } + } + } + + /// + /// Writes a string + /// + /// "Safe" version - automatically performs bounds checking. Less efficient than bounds checking + /// for multiple writes at once by calling TryBeginWrite. + /// + /// The string to write + /// Whether or not to use one byte per character. This will only allow ASCII + public unsafe void WriteValueSafe(string s, bool oneByteChars = false) + { +#if DEVELOPMENT_BUILD || UNITY_EDITOR + if (m_InBitwiseContext) + { + throw new InvalidOperationException( + "Cannot use BufferWriter in bytewise mode while in a bitwise context."); + } +#endif + + int sizeInBytes = GetWriteSize(s, oneByteChars); + + if (!TryBeginWriteInternal(sizeInBytes)) + { + throw new OverflowException("Writing past the end of the buffer"); + } + + WriteValue((uint)s.Length); + int target = s.Length; + if (oneByteChars) + { + for (int i = 0; i < target; ++i) + { + WriteByte((byte)s[i]); + } + } + else + { + fixed (char* native = s) + { + WriteBytes((byte*)native, target * sizeof(char)); + } + } + } + + /// + /// Get the required size to write an unmanaged array + /// + /// The array to write + /// The amount of elements to write + /// Where in the array to start + /// + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static unsafe int GetWriteSize(T[] 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; + } + + /// + /// Writes an unmanaged array + /// + /// The array to write + /// The amount of elements to write + /// Where in the array to start + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public unsafe void WriteValue(T[] array, int count = -1, int offset = 0) where T : unmanaged + { + int sizeInTs = count != -1 ? count : array.Length - offset; + int sizeInBytes = sizeInTs * sizeof(T); + WriteValue(sizeInTs); + fixed (T* native = array) + { + byte* bytes = (byte*)(native + offset); + WriteBytes(bytes, sizeInBytes); + } + } + + /// + /// Writes an unmanaged array + /// + /// "Safe" version - automatically performs bounds checking. Less efficient than bounds checking + /// for multiple writes at once by calling TryBeginWrite. + /// + /// The array to write + /// The amount of elements to write + /// Where in the array to start + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public unsafe void WriteValueSafe(T[] array, int count = -1, int offset = 0) where T : unmanaged + { +#if DEVELOPMENT_BUILD || UNITY_EDITOR + if (m_InBitwiseContext) + { + throw new InvalidOperationException( + "Cannot use BufferWriter in bytewise mode while in a bitwise context."); + } +#endif + + int sizeInTs = count != -1 ? count : array.Length - offset; + int sizeInBytes = sizeInTs * sizeof(T); + + if (!TryBeginWriteInternal(sizeInBytes + sizeof(int))) + { + throw new OverflowException("Writing past the end of the buffer"); + } + WriteValue(sizeInTs); + fixed (T* native = array) + { + byte* bytes = (byte*)(native + offset); + WriteBytes(bytes, sizeInBytes); + } + } + + /// + /// Write a partial value. The specified number of bytes is written from the value and the rest is ignored. + /// + /// Value to write + /// Number of bytes + /// Offset into the value to begin reading the bytes + /// + /// + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public unsafe void WritePartialValue(T value, int bytesToWrite, int offsetBytes = 0) where T : unmanaged + { +#if DEVELOPMENT_BUILD || UNITY_EDITOR + if (m_InBitwiseContext) + { + throw new InvalidOperationException( + "Cannot use BufferWriter in bytewise mode while in a bitwise context."); + } + if (PositionInternal + bytesToWrite > AllowedWriteMark) + { + throw new OverflowException("Attempted to write without first calling TryBeginWrite()"); + } +#endif + + byte* ptr = ((byte*)&value) + offsetBytes; + byte* bufferPointer = BufferPointer + PositionInternal; + UnsafeUtility.MemCpy(bufferPointer, ptr, bytesToWrite); + + PositionInternal += bytesToWrite; + } + + /// + /// Write a byte to the stream. + /// + /// Value to write + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public unsafe void WriteByte(byte value) + { +#if DEVELOPMENT_BUILD || UNITY_EDITOR + if (m_InBitwiseContext) + { + throw new InvalidOperationException( + "Cannot use BufferWriter in bytewise mode while in a bitwise context."); + } + if (PositionInternal + 1 > AllowedWriteMark) + { + throw new OverflowException("Attempted to write without first calling TryBeginWrite()"); + } +#endif + BufferPointer[PositionInternal++] = value; + } + + /// + /// Write a byte to the stream. + /// + /// "Safe" version - automatically performs bounds checking. Less efficient than bounds checking + /// for multiple writes at once by calling TryBeginWrite. + /// + /// Value to write + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public unsafe void WriteByteSafe(byte value) + { +#if DEVELOPMENT_BUILD || UNITY_EDITOR + if (m_InBitwiseContext) + { + throw new InvalidOperationException( + "Cannot use BufferWriter in bytewise mode while in a bitwise context."); + } +#endif + + if (!TryBeginWriteInternal(1)) + { + throw new OverflowException("Writing past the end of the buffer"); + } + BufferPointer[PositionInternal++] = value; + } + + /// + /// 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(byte* value, int size, int offset = 0) + { +#if DEVELOPMENT_BUILD || UNITY_EDITOR + if (m_InBitwiseContext) + { + throw new InvalidOperationException( + "Cannot use BufferWriter in bytewise mode while in a bitwise context."); + } + if (PositionInternal + size > AllowedWriteMark) + { + throw new OverflowException("Attempted to write without first calling TryBeginWrite()"); + } +#endif + UnsafeUtility.MemCpy((BufferPointer + PositionInternal), value + offset, size); + PositionInternal += size; + } + + /// + /// Write multiple bytes to the stream + /// + /// "Safe" version - automatically performs bounds checking. Less efficient than bounds checking + /// for multiple writes at once by calling TryBeginWrite. + /// + /// Value to write + /// Number of bytes to write + /// Offset into the buffer to begin writing + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public unsafe void WriteBytesSafe(byte* value, int size, int offset = 0) + { +#if DEVELOPMENT_BUILD || UNITY_EDITOR + if (m_InBitwiseContext) + { + throw new InvalidOperationException( + "Cannot use BufferWriter in bytewise mode while in a bitwise context."); + } +#endif + + if (!TryBeginWriteInternal(size)) + { + throw new OverflowException("Writing past the end of the buffer"); + } + UnsafeUtility.MemCpy((BufferPointer + PositionInternal), value + offset, size); + PositionInternal += size; + } + + /// + /// 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(byte[] value, int size = -1, int offset = 0) + { + fixed (byte* ptr = value) + { + WriteBytes(ptr, size == -1 ? value.Length : size, offset); + } + } + + /// + /// Write multiple bytes to the stream + /// + /// "Safe" version - automatically performs bounds checking. Less efficient than bounds checking + /// for multiple writes at once by calling TryBeginWrite. + /// + /// Value to write + /// Number of bytes to write + /// Offset into the buffer to begin writing + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public unsafe void WriteBytesSafe(byte[] value, int size = -1, int offset = 0) + { + fixed (byte* ptr = value) + { + 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. + /// They will be copied to the other writer starting at the other writer's current position. + /// + /// Writer to copy to + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public unsafe void CopyTo(ref FastBufferWriter other) + { + other.WriteBytes(BufferPointer, PositionInternal); + } + + /// + /// Copy the contents of another writer into this writer. + /// The contents will be copied from the beginning of the other writer to its current position. + /// They will be copied to this writer starting at this writer's current position. + /// + /// Writer to copy to + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public unsafe void CopyFrom(ref FastBufferWriter other) + { + WriteBytes(other.BufferPointer, other.PositionInternal); + } + + + /// + /// Get the size required to write a FixedUnmanagedArray + /// + /// + /// + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static unsafe int GetWriteSize(in FixedUnmanagedArray value, int count) + where TPropertyType : unmanaged + where TStorageType : unmanaged, IFixedArrayStorage + { + return count * sizeof(TPropertyType); + } + + /// + /// Write a value of type FixedUnmanagedArray to the buffer. + /// + /// The value to copy + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public unsafe void WriteValue(in FixedUnmanagedArray value, int count) + where TPropertyType : unmanaged + where TStorageType : unmanaged, IFixedArrayStorage + { + int len = sizeof(TPropertyType) * count; + +#if DEVELOPMENT_BUILD || UNITY_EDITOR + if (m_InBitwiseContext) + { + throw new InvalidOperationException( + "Cannot use BufferWriter in bytewise mode while in a bitwise context."); + } + if (PositionInternal + len > AllowedWriteMark) + { + throw new OverflowException("Attempted to write without first calling TryBeginWrite()"); + } +#endif + + BytewiseUtility.FastCopyBytes(BufferPointer + PositionInternal, (byte*)value.GetArrayPtr(), len); + PositionInternal += len; + } + + /// + /// Write a value of type FixedUnmanagedArray to the buffer. + /// + /// "Safe" version - automatically performs bounds checking. Less efficient than bounds checking + /// for multiple writes at once by calling TryBeginWrite. + /// + /// The value to copy + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public unsafe void WriteValueSafe(in FixedUnmanagedArray value, int count) + where TPropertyType : unmanaged + where TStorageType : unmanaged, IFixedArrayStorage + { + int len = sizeof(TPropertyType) * count; + +#if DEVELOPMENT_BUILD || UNITY_EDITOR + if (m_InBitwiseContext) + { + throw new InvalidOperationException( + "Cannot use BufferWriter in bytewise mode while in a bitwise context."); + } +#endif + + if (!TryBeginWriteInternal(len)) + { + throw new OverflowException("Writing past the end of the buffer"); + } + + BytewiseUtility.FastCopyBytes(BufferPointer + PositionInternal, (byte*)value.GetArrayPtr(), len); + PositionInternal += len; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + [Obsolete("FixedUnmanagedArray must be written/read using a count.")] + public void WriteValue(in FixedUnmanagedArray value) + where TPropertyType : unmanaged + where TStorageType : unmanaged, IFixedArrayStorage + { + throw new NotSupportedException("FixedUnmanagedArray must be written/read using a count."); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + [Obsolete("FixedUnmanagedArray must be written/read using a count.")] + public void WriteValueSafe(in FixedUnmanagedArray value) + where TPropertyType : unmanaged + where TStorageType : unmanaged, IFixedArrayStorage + { + throw new NotSupportedException("FixedUnmanagedArray must be written/read using a count."); + } + + /// + /// Get the size required to write an unmanaged value + /// + /// + /// + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static unsafe int GetWriteSize(in T value) where T : unmanaged + { + return sizeof(T); + } + + /// + /// Get the size required to write an unmanaged value of type T + /// + /// + /// + public static unsafe int GetWriteSize() where T : unmanaged + { + return sizeof(T); + } + + /// + /// Write a value of any unmanaged type (including unmanaged structs) to the buffer. + /// It will be copied into the buffer exactly as it exists in memory. + /// + /// The value to copy + /// Any unmanaged type + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public unsafe void WriteValue(in T value) where T : unmanaged + { + int len = sizeof(T); + +#if DEVELOPMENT_BUILD || UNITY_EDITOR + if (m_InBitwiseContext) + { + throw new InvalidOperationException( + "Cannot use BufferWriter in bytewise mode while in a bitwise context."); + } + if (PositionInternal + len > AllowedWriteMark) + { + throw new OverflowException("Attempted to write without first calling TryBeginWrite()"); + } +#endif + + fixed (T* ptr = &value) + { + BytewiseUtility.FastCopyBytes(BufferPointer + PositionInternal, (byte*)ptr, len); + } + PositionInternal += len; + } + + /// + /// Write a value of any unmanaged type (including unmanaged structs) to the buffer. + /// It will be copied into the buffer exactly as it exists in memory. + /// + /// "Safe" version - automatically performs bounds checking. Less efficient than bounds checking + /// for multiple writes at once by calling TryBeginWrite. + /// + /// The value to copy + /// Any unmanaged type + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public unsafe void WriteValueSafe(in T value) where T : unmanaged + { + int len = sizeof(T); + +#if DEVELOPMENT_BUILD || UNITY_EDITOR + if (m_InBitwiseContext) + { + throw new InvalidOperationException( + "Cannot use BufferWriter in bytewise mode while in a bitwise context."); + } +#endif + + if (!TryBeginWriteInternal(len)) + { + throw new OverflowException("Writing past the end of the buffer"); + } + + fixed (T* ptr = &value) + { + BytewiseUtility.FastCopyBytes(BufferPointer + PositionInternal, (byte*)ptr, len); + } + PositionInternal += len; + } + } +} diff --git a/com.unity.netcode.gameobjects/Runtime/Serialization/FastBufferWriter.cs.meta b/com.unity.netcode.gameobjects/Runtime/Serialization/FastBufferWriter.cs.meta new file mode 100644 index 0000000000..0c31b46524 --- /dev/null +++ b/com.unity.netcode.gameobjects/Runtime/Serialization/FastBufferWriter.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 819a511316a46104db673c8a0eab9e72 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/com.unity.netcode.gameobjects/Runtime/Serialization/FastBufferWriterExtensions.cs b/com.unity.netcode.gameobjects/Runtime/Serialization/FastBufferWriterExtensions.cs new file mode 100644 index 0000000000..2d56adb036 --- /dev/null +++ b/com.unity.netcode.gameobjects/Runtime/Serialization/FastBufferWriterExtensions.cs @@ -0,0 +1,399 @@ + +using System; +using UnityEngine; + +namespace Unity.Netcode +{ + public static class FastBufferWriterExtensions + { + + /// + /// Writes a boxed object in a standard format + /// Named differently from other WriteValue methods to avoid accidental boxing + /// + /// The writer to use to write the value + /// The object to write + /// + /// If true, an extra byte will be written to indicate whether or not the value is null. + /// Some types will always write this. + /// + public static void WriteObject(this ref FastBufferWriter writer, object value, bool isNullable = false) + { + if (isNullable || value.GetType().IsNullable()) + { + bool isNull = value == null || (value is UnityEngine.Object o && o == null); + + writer.WriteValueSafe(isNull); + + if (isNull) + { + return; + } + } + + var type = value.GetType(); + var hasSerializer = SerializationTypeTable.Serializers.TryGetValue(type, out var serializer); + if (hasSerializer) + { + serializer(ref writer, value); + return; + } + + if (value is Array array) + { + writer.WriteValueSafe(array.Length); + + for (int i = 0; i < array.Length; i++) + { + writer.WriteObject(array.GetValue(i)); + } + + return; + } + + if (value.GetType().IsEnum) + { + switch (Convert.GetTypeCode(value)) + { + case TypeCode.Boolean: + writer.WriteValueSafe((byte)value); + break; + case TypeCode.Char: + writer.WriteValueSafe((char)value); + break; + case TypeCode.SByte: + writer.WriteValueSafe((sbyte)value); + break; + case TypeCode.Byte: + writer.WriteValueSafe((byte)value); + break; + case TypeCode.Int16: + writer.WriteValueSafe((short)value); + break; + case TypeCode.UInt16: + writer.WriteValueSafe((ushort)value); + break; + case TypeCode.Int32: + writer.WriteValueSafe((int)value); + break; + case TypeCode.UInt32: + writer.WriteValueSafe((uint)value); + break; + case TypeCode.Int64: + writer.WriteValueSafe((long)value); + break; + case TypeCode.UInt64: + writer.WriteValueSafe((ulong)value); + break; + } + return; + } + if (value is GameObject) + { + writer.WriteValueSafe((GameObject)value); + return; + } + if (value is NetworkObject) + { + writer.WriteValueSafe((NetworkObject)value); + return; + } + if (value is NetworkBehaviour) + { + writer.WriteValueSafe((NetworkBehaviour)value); + return; + } + if (value is INetworkSerializable) + { + //TODO ((INetworkSerializable)value).NetworkSerialize(new NetworkSerializer(this)); + return; + } + + throw new ArgumentException($"{nameof(FastBufferWriter)} cannot write type {value.GetType().Name} - it does not implement {nameof(INetworkSerializable)}"); + } + + /// + /// Get the required amount of space to write a GameObject + /// + /// + /// + public static int GetWriteSize(GameObject value) + { + return sizeof(ulong); + } + + /// + /// Get the required amount of space to write a GameObject + /// + /// + public static int GetGameObjectWriteSize() + { + return sizeof(ulong); + } + + /// + /// Write a GameObject + /// + /// The writer to use to write the value + /// The value to write + public static void WriteValue(this ref FastBufferWriter writer, in GameObject value) + { + value.TryGetComponent(out var networkObject); + if (networkObject == null) + { + throw new ArgumentException($"{nameof(FastBufferWriter)} cannot write {nameof(GameObject)} types that does not has a {nameof(NetworkObject)} component attached. {nameof(GameObject)}: {(value).name}"); + } + + if (!networkObject.IsSpawned) + { + throw new ArgumentException($"{nameof(FastBufferWriter)} cannot write {nameof(NetworkObject)} types that are not spawned. {nameof(GameObject)}: {(value).name}"); + } + + writer.WriteValue(networkObject.NetworkObjectId); + } + + /// + /// Write an array of GameObjects + /// + /// The writer to use to write the value + /// The value to write + /// + /// + public static void WriteValue(this ref FastBufferWriter writer, GameObject[] value) + { + writer.WriteValue((int)value.Length); + foreach (var item in value) + { + writer.WriteValue(item); + } + } + + /// + /// Write a GameObject + /// + /// "Safe" version - automatically performs bounds checking. Less efficient than bounds checking + /// for multiple writes at once by calling TryBeginWrite. + /// + /// The writer to use to write the value + /// The value to write + public static void WriteValueSafe(this ref FastBufferWriter writer, in GameObject value) + { + value.TryGetComponent(out var networkObject); + if (networkObject == null) + { + throw new ArgumentException($"{nameof(FastBufferWriter)} cannot write {nameof(GameObject)} types that does not has a {nameof(NetworkObject)} component attached. {nameof(GameObject)}: {(value).name}"); + } + + if (!networkObject.IsSpawned) + { + throw new ArgumentException($"{nameof(FastBufferWriter)} cannot write {nameof(NetworkObject)} types that are not spawned. {nameof(GameObject)}: {(value).name}"); + } + + writer.WriteValueSafe(networkObject.NetworkObjectId); + } + + /// + /// Write an array of GameObjects + /// + /// "Safe" version - automatically performs bounds checking. Less efficient than bounds checking + /// for multiple writes at once by calling TryBeginWrite. + /// + /// The writer to use to write the value + /// The value to write + /// + /// + public static void WriteValueSafe(this ref FastBufferWriter writer, GameObject[] value) + { + writer.WriteValueSafe((int)value.Length); + foreach (var item in value) + { + writer.WriteValueSafe(item); + } + } + + /// + /// Get the required size to write a NetworkObject + /// + /// + /// + public static int GetWriteSize(NetworkObject value) + { + return sizeof(ulong); + } + + /// + /// Get the required size to write a NetworkObject + /// + /// + public static int GetNetworkObjectWriteSize() + { + return sizeof(ulong); + } + + + /// + /// Write a NetworkObject + /// + /// The writer to use to write the value + /// The value to write + public static void WriteValue(this ref FastBufferWriter writer, in NetworkObject value) + { + if (!value.IsSpawned) + { + throw new ArgumentException($"{nameof(FastBufferWriter)} cannot write {nameof(NetworkObject)} types that are not spawned. {nameof(GameObject)}: {value.name}"); + } + + writer.WriteValue(value.NetworkObjectId); + } + + /// + /// Write an array of NetworkObjects + /// + /// The writer to use to write the value + /// The value to write + /// + /// + public static void WriteValue(this ref FastBufferWriter writer, NetworkObject[] value) + { + writer.WriteValue((int)value.Length); + foreach (var item in value) + { + writer.WriteValue(item); + } + } + + /// + /// Write a NetworkObject + /// + /// "Safe" version - automatically performs bounds checking. Less efficient than bounds checking + /// for multiple writes at once by calling TryBeginWrite. + /// + /// The writer to use to write the value + /// The value to write + public static void WriteValueSafe(this ref FastBufferWriter writer, in NetworkObject value) + { + if (!value.IsSpawned) + { + throw new ArgumentException($"{nameof(FastBufferWriter)} cannot write {nameof(NetworkObject)} types that are not spawned. {nameof(GameObject)}: {value.name}"); + } + writer.WriteValueSafe(value.NetworkObjectId); + } + + /// + /// Write an array of NetworkObjects + /// + /// "Safe" version - automatically performs bounds checking. Less efficient than bounds checking + /// for multiple writes at once by calling TryBeginWrite. + /// + /// The writer to use to write the value + /// The value to write + /// + /// + public static void WriteValueSafe(this ref FastBufferWriter writer, NetworkObject[] value) + { + writer.WriteValueSafe((int)value.Length); + foreach (var item in value) + { + writer.WriteValueSafe(item); + } + } + + /// + /// Get the required size to write a NetworkBehaviour + /// + /// + /// + public static int GetWriteSize(NetworkBehaviour value) + { + return sizeof(ulong) + sizeof(ushort); + } + + + /// + /// Get the required size to write a NetworkBehaviour + /// + /// + public static int GetNetworkBehaviourWriteSize() + { + return sizeof(ulong) + sizeof(ushort); + } + + + /// + /// Write a NetworkBehaviour + /// + /// The writer to use to write the value + /// The value to write + public static void WriteValue(this ref FastBufferWriter writer, in NetworkBehaviour value) + { + if (!value.HasNetworkObject || !value.NetworkObject.IsSpawned) + { + throw new ArgumentException($"{nameof(FastBufferWriter)} cannot write {nameof(NetworkBehaviour)} types that are not spawned. {nameof(GameObject)}: {(value).gameObject.name}"); + } + + writer.WriteValue(value.NetworkObjectId); + writer.WriteValue(value.NetworkBehaviourId); + } + + /// + /// Write an array of NetworkBehaviours + /// + /// The writer to use to write the value + /// The value to write + /// + /// + public static void WriteValue(this ref FastBufferWriter writer, NetworkBehaviour[] value) + { + writer.WriteValue((int)value.Length); + foreach (var item in value) + { + writer.WriteValue(item); + } + } + + /// + /// Write a NetworkBehaviour + /// + /// "Safe" version - automatically performs bounds checking. Less efficient than bounds checking + /// for multiple writes at once by calling TryBeginWrite. + /// + /// The writer to use to write the value + /// The value to write + /// + /// + public static void WriteValueSafe(this ref FastBufferWriter writer, in NetworkBehaviour value) + { + if (!value.HasNetworkObject || !value.NetworkObject.IsSpawned) + { + throw new ArgumentException($"{nameof(FastBufferWriter)} cannot write {nameof(NetworkBehaviour)} types that are not spawned. {nameof(GameObject)}: {(value).gameObject.name}"); + } + + if (!writer.TryBeginWriteInternal(sizeof(ulong) + sizeof(ushort))) + { + throw new OverflowException("Writing past the end of the buffer"); + } + writer.WriteValue(value.NetworkObjectId); + writer.WriteValue(value.NetworkBehaviourId); + } + + /// + /// Write an array of NetworkBehaviours + /// + /// "Safe" version - automatically performs bounds checking. Less efficient than bounds checking + /// for multiple writes at once by calling TryBeginWrite. + /// + /// The writer to use to write the value + /// The value to write + /// + /// + public static void WriteValueSafe(this ref FastBufferWriter writer, NetworkBehaviour[] value) + { + writer.WriteValueSafe((int)value.Length); + foreach (var item in value) + { + writer.WriteValueSafe(item); + } + } + + } +} diff --git a/com.unity.netcode.gameobjects/Runtime/Serialization/FastBufferWriterExtensions.cs.meta b/com.unity.netcode.gameobjects/Runtime/Serialization/FastBufferWriterExtensions.cs.meta new file mode 100644 index 0000000000..b96c1f3b21 --- /dev/null +++ b/com.unity.netcode.gameobjects/Runtime/Serialization/FastBufferWriterExtensions.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 1a9dd964797964b4a99e03706516a8a9 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/com.unity.netcode.gameobjects/Runtime/Serialization/IBufferSerializerImplementation.cs b/com.unity.netcode.gameobjects/Runtime/Serialization/IBufferSerializerImplementation.cs new file mode 100644 index 0000000000..079600300c --- /dev/null +++ b/com.unity.netcode.gameobjects/Runtime/Serialization/IBufferSerializerImplementation.cs @@ -0,0 +1,37 @@ +using System; +using UnityEngine; + +namespace Unity.Netcode +{ + public interface IBufferSerializerImplementation + { + bool IsReader { get; } + bool IsWriter { get; } + + ref FastBufferReader GetFastBufferReader(); + ref FastBufferWriter GetFastBufferWriter(); + + void SerializeValue(ref object value, Type type, bool isNullable = false); + void SerializeValue(ref GameObject value); + void SerializeValue(ref NetworkObject value); + void SerializeValue(ref NetworkBehaviour value); + void SerializeValue(ref string s, bool oneByteChars = false); + void SerializeValue(ref T[] array) where T : unmanaged; + void SerializeValue(ref byte value); + void SerializeValue(ref T value) where T : unmanaged; + + // Has to have a different name to avoid conflicting with "where T: unmananged" + // Using SerializeValue(INetworkSerializable) will result in boxing on struct INetworkSerializables + // So this is provided as an alternative to avoid boxing allocations. + void SerializeNetworkSerializable(ref T value) where T : INetworkSerializable, new(); + + bool PreCheck(int amount); + void SerializeValuePreChecked(ref GameObject value); + void SerializeValuePreChecked(ref NetworkObject value); + void SerializeValuePreChecked(ref NetworkBehaviour value); + void SerializeValuePreChecked(ref string s, bool oneByteChars = false); + void SerializeValuePreChecked(ref T[] array) where T : unmanaged; + void SerializeValuePreChecked(ref byte value); + void SerializeValuePreChecked(ref T value) where T : unmanaged; + } +} diff --git a/com.unity.netcode.gameobjects/Runtime/Serialization/IBufferSerializerImplementation.cs.meta b/com.unity.netcode.gameobjects/Runtime/Serialization/IBufferSerializerImplementation.cs.meta new file mode 100644 index 0000000000..f5c741d324 --- /dev/null +++ b/com.unity.netcode.gameobjects/Runtime/Serialization/IBufferSerializerImplementation.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 9220f80eab4051e4d9b9504ef840eba4 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/com.unity.netcode.gameobjects/Runtime/Serialization/INetworkSerializable.cs b/com.unity.netcode.gameobjects/Runtime/Serialization/INetworkSerializable.cs index 48ca46d27c..54b979b729 100644 --- a/com.unity.netcode.gameobjects/Runtime/Serialization/INetworkSerializable.cs +++ b/com.unity.netcode.gameobjects/Runtime/Serialization/INetworkSerializable.cs @@ -2,6 +2,6 @@ namespace Unity.Netcode { public interface INetworkSerializable { - void NetworkSerialize(NetworkSerializer serializer); + void NetworkSerialize(BufferSerializer serializer) where T : IBufferSerializerImplementation; } } diff --git a/com.unity.netcode.gameobjects/Runtime/Serialization/NetworkBuffer.cs b/com.unity.netcode.gameobjects/Runtime/Serialization/NetworkBuffer.cs deleted file mode 100644 index 0d391dd5f9..0000000000 --- a/com.unity.netcode.gameobjects/Runtime/Serialization/NetworkBuffer.cs +++ /dev/null @@ -1,601 +0,0 @@ -using System; -using System.IO; -using static Unity.Netcode.Arithmetic; - -namespace Unity.Netcode -{ - /// - /// A buffer that can be used at the bit level - /// - public class NetworkBuffer : Stream - { - private const int k_InitialCapacity = 16; - private const float k_InitialGrowthFactor = 2.0f; - - private byte[] m_Target; - - /// - /// A buffer that supports writing data smaller than a single byte. This buffer also has a built-in compression algorithm that can (optionally) be used to write compressed data. - /// - /// Initial capacity of buffer in bytes. - /// Factor by which buffer should grow when necessary. - public NetworkBuffer(int capacity, float growthFactor) - { - m_Target = new byte[capacity]; - GrowthFactor = growthFactor; - Resizable = true; - } - - /// - /// A buffer that supports writing data smaller than a single byte. This buffer also has a built-in compression algorithm that can (optionally) be used to write compressed data. - /// - /// Factor by which buffer should grow when necessary. - public NetworkBuffer(float growthFactor) : this(k_InitialCapacity, growthFactor) { } - - /// - /// A buffer that supports writing data smaller than a single byte. This buffer also has a built-in compression algorithm that can (optionally) be used to write compressed data. - /// - /// - public NetworkBuffer(int capacity) : this(capacity, k_InitialGrowthFactor) { } - - /// - /// A buffer that supports writing data smaller than a single byte. This buffer also has a built-in compression algorithm that can (optionally) be used to write compressed data. - /// - public NetworkBuffer() : this(k_InitialCapacity, k_InitialGrowthFactor) { } - - /// - /// A buffer that supports writing data smaller than a single byte. This buffer also has a built-in compression algorithm that can (optionally) be used to write compressed data. - /// NOTE: when using a pre-allocated buffer, the buffer will not grow! - /// - /// Pre-allocated buffer to write to - public NetworkBuffer(byte[] target) - { - m_Target = target; - Resizable = false; - BitLength = (ulong)(target.Length << 3); - } - - internal void SetTarget(byte[] target) - { - m_Target = target; - BitLength = (ulong)(target.Length << 3); - Position = 0; - } - - /// - /// Whether or not the buffer will grow the buffer to accomodate more data. - /// - public bool Resizable { get; } - - private float m_GrowthFactor; - - /// - /// Factor by which buffer should grow when necessary. - /// - public float GrowthFactor { set { m_GrowthFactor = value <= 1 ? 1.5f : value; } get { return m_GrowthFactor; } } - - /// - /// Whether or not buffeer supports reading. (Always true) - /// - public override bool CanRead => true; - - /// - /// Whether or not or there is any data to be read from the buffer. - /// - public bool HasDataToRead => Position < Length; - - /// - /// Whether or not seeking is supported by this buffer. (Always true) - /// - public override bool CanSeek => true; - - /// - /// Whether or not this buffer can accept new data. NOTE: this will return true even if only fewer than 8 bits can be written! - /// - public override bool CanWrite => !BitAligned || Position < m_Target.LongLength || Resizable; - - /// - /// Current buffer size. The buffer will not be resized (if possible) until Position is equal to Capacity and an attempt to write data is made. - /// - public long Capacity - { - get => m_Target.LongLength; // Optimized CeilingExact - set - { - if (value < Length) - { - throw new ArgumentOutOfRangeException("New capcity too small!"); - } - - SetCapacity(value); - } - } - - /// - /// The current length of data considered to be "written" to the buffer. - /// - public override long Length { get => Div8Ceil(BitLength); } - - /// - /// The index that will be written to when any call to write data is made to this buffer. - /// - public override long Position { get => (long)(BitPosition >> 3); set => BitPosition = (ulong)value << 3; } - - /// - /// Bit offset into the buffer that new data will be written to. - /// - public ulong BitPosition { get; set; } - - /// - /// Length of data (in bits) that is considered to be written to the buffer. - /// - public ulong BitLength { get; private set; } - - /// - /// Whether or not the current BitPosition is evenly divisible by 8. I.e. whether or not the BitPosition is at a byte boundary. - /// - public bool BitAligned { get => (BitPosition & 7) == 0; } - - /// - /// Flush buffer. This does nothing since data is written directly to a byte buffer. - /// - public override void Flush() { } // NOP - - /// - /// Grow buffer if possible. According to Max(bufferLength, 1) * growthFactor^Ceil(newContent/Max(bufferLength, 1)) - /// - /// How many new values need to be accomodated (at least). - //private void Grow(long newContent) => SetCapacity(Math.Max(target.LongLength, 1) * (long)Math.Pow(GrowthFactor, CeilingExact(newContent, Math.Max(target.LongLength, 1)))); - /* - private void Grow(long newContent) - { - float grow = newContent / 64; - if (((long)grow) != grow) grow += 1; - SetCapacity((Capacity + 64) * (long)grow); - } - */ - private void Grow(long newContent) - { - long value = newContent + Capacity; - long newCapacity = value; - - if (newCapacity < 256) - { - newCapacity = 256; - } - // We are ok with this overflowing since the next statement will deal - // with the cases where _capacity*2 overflows. - if (newCapacity < Capacity * 2) - { - newCapacity = Capacity * 2; - } - // We want to expand the array up to Array.MaxArrayLengthOneDimensional - // And we want to give the user the value that they asked for - if ((uint)(Capacity * 2) > int.MaxValue) - { - newCapacity = value > int.MaxValue ? value : int.MaxValue; - } - - SetCapacity(newCapacity); - } - - /// - /// Read a misaligned byte. WARNING: If the current BitPosition isn't byte misaligned, - /// avoid using this method as it may cause an IndexOutOfBoundsException in such a case. - /// - /// A byte extracted from up to two separate buffer indices. - private byte ReadByteMisaligned() - { - int mod = (int)(BitPosition & 7); - return (byte)((m_Target[(int)Position] >> mod) | (m_Target[(int)(BitPosition += 8) >> 3] << (8 - mod))); - } - - /// - /// Read an aligned byte from the buffer. It's recommended to not use this when the BitPosition is byte-misaligned. - /// - /// The byte stored at the current Position index - private byte ReadByteAligned() => m_Target[Position++]; - - /// - /// Read a byte as a byte. This is just for internal use so as to minimize casts (cuz they ugly af). - /// - /// - internal byte ReadByteInternal() => BitAligned ? ReadByteAligned() : ReadByteMisaligned(); - - /// - /// Read a byte from the buffer. This takes into account possible byte misalignment. - /// - /// A byte from the buffer or, if a byte can't be read, -1. - public override int ReadByte() => HasDataToRead ? BitAligned ? ReadByteAligned() : ReadByteMisaligned() : -1; - - /// - /// Peeks a byte without advancing the position - /// - /// The peeked byte - public int PeekByte() => - HasDataToRead - ? BitAligned ? m_Target[Position] : - (byte)((m_Target[(int)Position] >> (int)(BitPosition & 7)) | (m_Target[(int)(BitPosition + 8) >> 3] << (8 - (int)(BitPosition & 7)))) - : -1; - - /// - /// Read a single bit from the buffer. - /// - /// A bit in bool format. (True represents 1, False represents 0) - public bool ReadBit() => (m_Target[Position] & (1 << (int)(BitPosition++ & 7))) != 0; - - /// - /// Read a subset of the buffer buffer and write the contents to the supplied buffer. - /// - /// Buffer to copy data to. - /// Offset into the buffer to write data to. - /// How many bytes to attempt to read. - /// Amount of bytes read. - public override int Read(byte[] buffer, int offset, int count) - { - int tLen = Math.Min(count, (int)(Length - Position)); - for (int i = 0; i < tLen; ++i) - { - buffer[offset + i] = ReadByteInternal(); - } - - return tLen; - } - - /// - /// Set position in buffer to read from/write to. - /// - /// Offset from position origin. - /// How to calculate offset. - /// The new position in the buffer that data will be written to. - public override long Seek(long offset, SeekOrigin origin) - { - return (long)(( - BitPosition = - ( - origin == SeekOrigin.Current ? offset > 0 ? Math.Min(BitPosition + ((ulong)offset << 3), (ulong)m_Target.Length << 3) : - (offset ^ SIGN_BIT_64) > Position ? 0UL : - BitPosition - (ulong)((offset ^ SIGN_BIT_64) << 3) : - origin == SeekOrigin.Begin ? (ulong)Math.Max(0, offset) << 3 : - (ulong)Math.Max(m_Target.Length - offset, 0) << 3 - )) >> 3) + (long)((BitPosition & 1UL) | ((BitPosition >> 1) & 1UL) | ((BitPosition >> 2) & 1UL)); - } - - /// - /// Set the capacity of the internal buffer. - /// - /// New capacity of the buffer - private void SetCapacity(long value) - { - if (!Resizable) - { - throw new NotSupportedException("Can't resize non resizable buffer"); // Don't do shit because fuck you (comment by @GabrielTofvesson -TwoTen) - } - - byte[] newTarg = new byte[value]; - long len = Math.Min(value, m_Target.LongLength); - Buffer.BlockCopy(m_Target, 0, newTarg, 0, (int)len); - if (value < m_Target.LongLength) - { - BitPosition = (ulong)value << 3; - } - - m_Target = newTarg; - } - - /// - /// Set length of data considered to be "written" to the buffer. - /// - /// New length of the written data. - public override void SetLength(long value) - { - if (value < 0) - { - throw new IndexOutOfRangeException("Cannot set a negative length!"); - } - - if (value > Capacity) - { - Grow(value - Capacity); - } - - BitLength = (ulong)value << 3; - BitPosition = Math.Min((ulong)value << 3, BitPosition); - } - - /// - /// Write data from the given buffer to the internal buffer. - /// - /// Buffer to write from. - /// Offset in given buffer to start reading from. - /// Amount of bytes to read copy from given buffer to buffer. - public override void Write(byte[] buffer, int offset, int count) - { - // Check bit alignment. If misaligned, each byte written has to be misaligned - if (BitAligned) - { - if (Position + count >= m_Target.Length) - { - Grow(count); - } - - Buffer.BlockCopy(buffer, offset, m_Target, (int)Position, count); - Position += count; - } - else - { - if (Position + count + 1 >= m_Target.Length) - { - Grow(count); - } - - for (int i = 0; i < count; ++i) - { - WriteMisaligned(buffer[offset + i]); - } - } - - if (BitPosition > BitLength) - { - BitLength = BitPosition; - } - } - - /// - /// Write byte value to the internal buffer. - /// - /// The byte value to write. - public override void WriteByte(byte value) - { - // Check bit alignment. If misaligned, each byte written has to be misaligned - if (BitAligned) - { - if (Position + 1 >= m_Target.Length) - { - Grow(1); - } - - m_Target[Position] = value; - Position += 1; - } - else - { - if (Position + 1 + 1 >= m_Target.Length) - { - Grow(1); - } - - WriteMisaligned(value); - } - - if (BitPosition > BitLength) - { - BitLength = BitPosition; - } - } - - /// - /// Write a misaligned byte. NOTE: Using this when the bit position isn't byte-misaligned may cause an IndexOutOfBoundsException! This does not update the current Length of the buffer. - /// - /// Value to write - private void WriteMisaligned(byte value) - { - int off = (int)(BitPosition & 7); - int shift1 = 8 - off; - m_Target[Position + 1] = (byte)((m_Target[Position + 1] & (0xFF << off)) | (value >> shift1)); - m_Target[Position] = (byte)((m_Target[Position] & (0xFF >> shift1)) | (value << off)); - - BitPosition += 8; - } - - - /// - /// Write a byte (in an int format) to the buffer. This does not update the current Length of the buffer. - /// - /// Value to write - private void WriteIntByte(int value) => WriteBytePrivate((byte)value); - - /// - /// Write a byte (in a ulong format) to the buffer. This does not update the current Length of the buffer. - /// - /// Value to write - private void WriteULongByte(ulong byteValue) => WriteBytePrivate((byte)byteValue); - - /// - /// Write a byte to the buffer. This does not update the current Length of the buffer. - /// - /// Value to write - private void WriteBytePrivate(byte value) - { - if (Div8Ceil(BitPosition) == m_Target.LongLength) - { - Grow(1); - } - - if (BitAligned) - { - m_Target[Position] = value; - BitPosition += 8; - } - else - { - WriteMisaligned(value); - } - - UpdateLength(); - } - - /// - /// Write data from the given buffer to the internal buffer. - /// - /// Buffer to write from. - public void Write(byte[] buffer) => Write(buffer, 0, buffer.Length); - - /// - /// Write a single bit to the buffer - /// - /// Value of the bit. True represents 1, False represents 0 - public void WriteBit(bool bit) - { - if (BitAligned && Position == m_Target.Length) - { - Grow(1); - } - - int offset = (int)(BitPosition & 7); - long pos = Position; - ++BitPosition; - m_Target[pos] = (byte)(bit ? (m_Target[pos] & ~(1 << offset)) | (1 << offset) : (m_Target[pos] & ~(1 << offset))); - UpdateLength(); - } - - /// - /// Copy data from another stream - /// - /// Stream to copy from - /// How many bytes to read. Set to value less than one to read until ReadByte returns -1 - public void CopyFrom(Stream s, int count = -1) - { - if (s is NetworkBuffer b) - { - Write(b.m_Target, 0, count < 0 ? (int)b.Length : count); - } - else - { - long currentPosition = s.Position; - s.Position = 0; - - int read; - bool readToEnd = count < 0; - while ((readToEnd || count-- > 0) && (read = s.ReadByte()) != -1) - { - WriteIntByte(read); - } - - UpdateLength(); - - s.Position = currentPosition; - } - } - - /// - /// Copies internal buffer to stream - /// - /// The stream to copy to - /// The maximum amount of bytes to copy. Set to value less than one to copy the full length -#if !NET35 - public new void CopyTo(Stream stream, int count = -1) -#else - public void CopyTo(Stream stream, int count = -1) -#endif - { - stream.Write(m_Target, 0, count < 0 ? (int)Length : count); - } - - /// - /// Copies urnead bytes from the source stream - /// - /// The source stream to copy from - /// The max amount of bytes to copy - public void CopyUnreadFrom(Stream s, int count = -1) - { - long currentPosition = s.Position; - - int read; - bool readToEnd = count < 0; - while ((readToEnd || count-- > 0) && (read = s.ReadByte()) != -1) - { - WriteIntByte(read); - } - - UpdateLength(); - - s.Position = currentPosition; - } - - // TODO: Implement CopyFrom() for NetworkBuffer with bitCount parameter - /// - /// Copys the bits from the provided NetworkBuffer - /// - /// The buffer to copy from - /// The amount of data evel - /// Whether or not to copy at the bit level rather than the byte level - public void CopyFrom(NetworkBuffer buffer, int dataCount, bool copyBits) - { - if (!copyBits) - { - CopyFrom(buffer, dataCount); - } - else - { - ulong count = dataCount < 0 ? buffer.BitLength : (ulong)dataCount; - if (buffer.BitLength < count) - { - throw new IndexOutOfRangeException("Attempted to read more data than is available"); - } - - Write(buffer.GetBuffer(), 0, (int)(count >> 3)); - for (int i = (int)(count & 7); i >= 0; --i) - { - WriteBit(buffer.ReadBit()); - } - } - } - - /// - /// Update length of data considered to be "written" to the buffer. - /// - private void UpdateLength() - { - if (BitPosition > BitLength) - { - BitLength = BitPosition; - } - } - - /// - /// Get the internal buffer being written to by this buffer. - /// - /// - public byte[] GetBuffer() => m_Target; - - /// - /// Creates a copy of the internal buffer. This only contains the used bytes - /// - /// A copy of used bytes in the internal buffer - public byte[] ToArray() - { - byte[] copy = new byte[Length]; - Buffer.BlockCopy(m_Target, 0, copy, 0, (int)Length); - return copy; - } - - /// - /// Writes zeros to fill the last byte - /// - public void PadBuffer() - { - while (!BitAligned) - { - WriteBit(false); - } - } - - /// - /// Reads zeros until the the buffer is byte aligned - /// - public void SkipPadBits() - { - while (!BitAligned) - { - ReadBit(); - } - } - - /// - /// Returns hex encoded version of the buffer - /// - /// Hex encoded version of the buffer - public override string ToString() => BitConverter.ToString(m_Target, 0, (int)Length); - } -} diff --git a/com.unity.netcode.gameobjects/Runtime/Serialization/NetworkBuffer.cs.meta b/com.unity.netcode.gameobjects/Runtime/Serialization/NetworkBuffer.cs.meta deleted file mode 100644 index 5976fd468c..0000000000 --- a/com.unity.netcode.gameobjects/Runtime/Serialization/NetworkBuffer.cs.meta +++ /dev/null @@ -1,11 +0,0 @@ -fileFormatVersion: 2 -guid: f6242c3ec07a9e5489d3cdd0cb083173 -MonoImporter: - externalObjects: {} - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: - assetBundleName: - assetBundleVariant: diff --git a/com.unity.netcode.gameobjects/Runtime/Serialization/NetworkReader.cs b/com.unity.netcode.gameobjects/Runtime/Serialization/NetworkReader.cs deleted file mode 100644 index 98f6af1976..0000000000 --- a/com.unity.netcode.gameobjects/Runtime/Serialization/NetworkReader.cs +++ /dev/null @@ -1,2296 +0,0 @@ -#define ARRAY_WRITE_PERMISSIVE // Allow attempt to write "packed" byte array (calls WriteByteArray()) -#define ARRAY_RESOLVE_IMPLICIT // Include WriteArray() method with automatic type resolution -#define ARRAY_WRITE_PREMAP // Create a prefixed array diff mapping -#define ARRAY_DIFF_ALLOW_RESIZE // Whether or not to permit writing diffs of differently sized arrays - -using System; -using System.IO; -using System.Text; -using UnityEngine; - -namespace Unity.Netcode -{ - /// - /// A BinaryReader that can do bit wise manipulation when backed by a NetworkBuffer - /// - public class NetworkReader - { - private Stream m_Source; - private NetworkBuffer m_NetworkSource; - - /// - /// Creates a new NetworkReader backed by a given stream - /// - /// The stream to read from - public NetworkReader(Stream stream) - { - m_Source = stream; - m_NetworkSource = stream as NetworkBuffer; - } - - /// - /// Changes the underlying stream the reader is reading from - /// - /// The stream to read from - public void SetStream(Stream stream) - { - m_Source = stream; - m_NetworkSource = stream as NetworkBuffer; - } - - /// - /// Retrieves the underlying stream the reader is reading from - /// - /// - public Stream GetStream() - { - return m_Source; - } - - /// - /// Reads a single byte - /// - /// The byte read as an integer - public int ReadByte() => m_Source.ReadByte(); - - /// - /// Reads a byte - /// - /// The byte read - public byte ReadByteDirect() => (byte)m_Source.ReadByte(); - - /// - /// Reads a single bit - /// - /// The bit read - public bool ReadBit() - { - if (m_NetworkSource == null) - { - throw new InvalidOperationException($"Cannot read bits on a non-{nameof(NetworkBuffer)} stream"); - } - - return m_NetworkSource.ReadBit(); - } - - /// - /// Reads a single bit - /// - /// The bit read - public bool ReadBool() - { - if (m_NetworkSource == null) - { - return m_Source.ReadByte() != 0; - } - - // return ReadBit(); // old (buggy) - return ReadByte() != 0; // new (hotfix) - } - - /// - /// Skips pad bits and aligns the position to the next byte - /// - public void SkipPadBits() - { - if (m_NetworkSource == null) - { - throw new InvalidOperationException($"Cannot read bits on a non-{nameof(NetworkBuffer)} stream"); - } - - while (!m_NetworkSource.BitAligned) - { - ReadBit(); - } - } - - /// - /// Reads a single boxed object of a given type in a packed format - /// - /// The type to read - /// Returns the boxed read object - public object ReadObjectPacked(Type type) - { - if (type.IsNullable()) - { - bool isNull = ReadBool(); - - if (isNull) - { - return null; - } - } - - if (SerializationManager.TryDeserialize(m_Source, type, out object obj)) - { - return obj; - } - - if (type.IsArray && type.HasElementType) - { - int size = ReadInt32Packed(); - - var array = Array.CreateInstance(type.GetElementType(), size); - - for (int i = 0; i < size; i++) - { - array.SetValue(ReadObjectPacked(type.GetElementType()), i); - } - - return array; - } - - if (type == typeof(byte)) - { - return ReadByteDirect(); - } - - if (type == typeof(sbyte)) - { - return ReadSByte(); - } - - if (type == typeof(ushort)) - { - return ReadUInt16Packed(); - } - - if (type == typeof(short)) - { - return ReadInt16Packed(); - } - - if (type == typeof(int)) - { - return ReadInt32Packed(); - } - - if (type == typeof(uint)) - { - return ReadUInt32Packed(); - } - - if (type == typeof(long)) - { - return ReadInt64Packed(); - } - - if (type == typeof(ulong)) - { - return ReadUInt64Packed(); - } - - if (type == typeof(float)) - { - return ReadSinglePacked(); - } - - if (type == typeof(double)) - { - return ReadDoublePacked(); - } - - if (type == typeof(string)) - { - return ReadStringPacked(); - } - - if (type == typeof(bool)) - { - return ReadBool(); - } - - if (type == typeof(Vector2)) - { - return ReadVector2Packed(); - } - - if (type == typeof(Vector3)) - { - return ReadVector3Packed(); - } - - if (type == typeof(Vector4)) - { - return ReadVector4Packed(); - } - - if (type == typeof(Color)) - { - return ReadColorPacked(); - } - - if (type == typeof(Color32)) - { - return ReadColor32(); - } - - if (type == typeof(Ray)) - { - return ReadRayPacked(); - } - - if (type == typeof(Quaternion)) - { - return ReadRotationPacked(); - } - - if (type == typeof(char)) - { - return ReadCharPacked(); - } - - if (type.IsEnum) - { - return ReadInt32Packed(); - } - - if (type == typeof(GameObject)) - { - ulong networkObjectId = ReadUInt64Packed(); - - if (NetworkManager.Singleton.SpawnManager.SpawnedObjects.TryGetValue(networkObjectId, out NetworkObject networkObject)) - { - return networkObject.gameObject; - } - - if (NetworkLog.CurrentLogLevel <= LogLevel.Normal) - { - NetworkLog.LogWarning($"{nameof(NetworkReader)} cannot find the {nameof(GameObject)} sent in the {nameof(NetworkSpawnManager.SpawnedObjects)} list, it may have been destroyed. {nameof(networkObjectId)}: {networkObjectId}"); - } - - return null; - } - - if (type == typeof(NetworkObject)) - { - ulong networkObjectId = ReadUInt64Packed(); - - if (NetworkManager.Singleton.SpawnManager.SpawnedObjects.TryGetValue(networkObjectId, out NetworkObject networkObject)) - { - return networkObject; - } - - if (NetworkLog.CurrentLogLevel <= LogLevel.Normal) - { - NetworkLog.LogWarning($"{nameof(NetworkReader)} cannot find the {nameof(NetworkObject)} sent in the {nameof(NetworkSpawnManager.SpawnedObjects)} list, it may have been destroyed. {nameof(networkObjectId)}: {networkObjectId}"); - } - - return null; - } - - if (typeof(NetworkBehaviour).IsAssignableFrom(type)) - { - ulong networkObjectId = ReadUInt64Packed(); - ushort behaviourId = ReadUInt16Packed(); - if (NetworkManager.Singleton.SpawnManager.SpawnedObjects.TryGetValue(networkObjectId, out NetworkObject networkObject)) - { - return networkObject.GetNetworkBehaviourAtOrderIndex(behaviourId); - } - - if (NetworkLog.CurrentLogLevel <= LogLevel.Normal) - { - NetworkLog.LogWarning($"{nameof(NetworkReader)} cannot find the {nameof(NetworkBehaviour)} sent in the {nameof(NetworkSpawnManager.SpawnedObjects)} list, it may have been destroyed. {nameof(networkObjectId)}: {networkObjectId}"); - } - - return null; - } - - if (typeof(INetworkSerializable).IsAssignableFrom(type)) - { - object instance = Activator.CreateInstance(type); - ((INetworkSerializable)instance).NetworkSerialize(new NetworkSerializer(this)); - return instance; - } - - Type nullableUnderlyingType = Nullable.GetUnderlyingType(type); - - if (nullableUnderlyingType != null && SerializationManager.IsTypeSupported(nullableUnderlyingType)) - { - return ReadObjectPacked(nullableUnderlyingType); - } - - throw new ArgumentException($"{nameof(NetworkReader)} cannot read type {type.Name}"); - } - - /// - /// Read a single-precision floating point value from the stream. - /// - /// The read value - public float ReadSingle() => new UIntFloat { UIntValue = ReadUInt32() }.FloatValue; - - /// - /// Read a double-precision floating point value from the stream. - /// - /// The read value - public double ReadDouble() => new UIntFloat { ULongValue = ReadUInt64() }.DoubleValue; - - /// - /// Read a single-precision floating point value from the stream from a varint - /// - /// The read value - public float ReadSinglePacked() => new UIntFloat { UIntValue = ReadUInt32Packed() }.FloatValue; - - /// - /// Read a double-precision floating point value from the stream as a varint - /// - /// The read value - public double ReadDoublePacked() => new UIntFloat { ULongValue = ReadUInt64Packed() }.DoubleValue; - - /// - /// Read a Vector2 from the stream. - /// - /// The Vector2 read from the stream. - public Vector2 ReadVector2() => new Vector2(ReadSingle(), ReadSingle()); - - /// - /// Read a Vector2 from the stream. - /// - /// The Vector2 read from the stream. - public Vector2 ReadVector2Packed() => new Vector2(ReadSinglePacked(), ReadSinglePacked()); - - /// - /// Read a Vector3 from the stream. - /// - /// The Vector3 read from the stream. - public Vector3 ReadVector3() => new Vector3(ReadSingle(), ReadSingle(), ReadSingle()); - - /// - /// Read a Vector3 from the stream. - /// - /// The Vector3 read from the stream. - public Vector3 ReadVector3Packed() => new Vector3(ReadSinglePacked(), ReadSinglePacked(), ReadSinglePacked()); - - /// - /// Read a Vector4 from the stream. - /// - /// The Vector4 read from the stream. - public Vector4 ReadVector4() => new Vector4(ReadSingle(), ReadSingle(), ReadSingle(), ReadSingle()); - - /// - /// Read a Vector4 from the stream. - /// - /// The Vector4 read from the stream. - public Vector4 ReadVector4Packed() => new Vector4(ReadSinglePacked(), ReadSinglePacked(), ReadSinglePacked(), ReadSinglePacked()); - - /// - /// Read a Color from the stream. - /// - /// The Color read from the stream. - public Color ReadColor() => new Color(ReadSingle(), ReadSingle(), ReadSingle(), ReadSingle()); - - /// - /// Read a Color from the stream. - /// - /// The Color read from the stream. - public Color ReadColorPacked() => new Color(ReadSinglePacked(), ReadSinglePacked(), ReadSinglePacked(), ReadSinglePacked()); - - /// - /// Read a Color32 from the stream. - /// - /// The Color32 read from the stream. - public Color32 ReadColor32() => new Color32((byte)ReadByte(), (byte)ReadByte(), (byte)ReadByte(), (byte)ReadByte()); - - /// - /// Read a Ray from the stream. - /// - /// The Ray read from the stream. - public Ray ReadRay() => new Ray(ReadVector3(), ReadVector3()); - - /// - /// Read a Ray from the stream. - /// - /// The Ray read from the stream. - public Ray ReadRayPacked() => new Ray(ReadVector3Packed(), ReadVector3Packed()); - - /// - /// Read a Ray2D from the stream. - /// - /// The Ray2D read from the stream. - public Ray2D ReadRay2D() => new Ray2D(ReadVector2(), ReadVector2()); - - /// - /// Read a Ray2D from the stream. - /// - /// The Ray2D read from the stream. - public Ray2D ReadRay2DPacked() => new Ray2D(ReadVector2Packed(), ReadVector2Packed()); - - /// - /// Read a single-precision floating point value from the stream. The value is between (inclusive) the minValue and maxValue. - /// - /// Minimum value that this value could be - /// Maximum possible value that this could be - /// How many bytes the compressed value occupies. Must be between 1 and 4 (inclusive) - /// The read value - public float ReadRangedSingle(float minValue, float maxValue, int bytes) - { - if (bytes < 1 || bytes > 4) - { - throw new ArgumentOutOfRangeException("Result must occupy between 1 and 4 bytes!"); - } - - uint read = 0; - for (int i = 0; i < bytes; ++i) - { - read |= (uint)ReadByte() << (i << 3); - } - - return ((float)read / ((0x100 * bytes) - 1) * (maxValue - minValue)) + minValue; - } - - /// - /// read a double-precision floating point value from the stream. The value is between (inclusive) the minValue and maxValue. - /// - /// Minimum value that this value could be - /// Maximum possible value that this could be - /// How many bytes the compressed value occupies. Must be between 1 and 8 (inclusive) - /// The read value - public double ReadRangedDouble(double minValue, double maxValue, int bytes) - { - if (bytes < 1 || bytes > 8) - { - throw new ArgumentOutOfRangeException("Result must occupy between 1 and 8 bytes!"); - } - - ulong read = 0; - for (int i = 0; i < bytes; ++i) - { - read |= (ulong)ReadByte() << (i << 3); - } - - return ((double)read / ((0x100 * bytes) - 1) * (maxValue - minValue)) + minValue; - } - - /// - /// Reads the rotation from the stream - /// - /// The rotation read from the stream - public Quaternion ReadRotationPacked() - { - float x = ReadSinglePacked(); - float y = ReadSinglePacked(); - float z = ReadSinglePacked(); - - // numerical precision issues can make the remainder very slightly negative. - // In this case, use 0 for w as, otherwise, w would be NaN. - float remainder = 1f - Mathf.Pow(x, 2) - Mathf.Pow(y, 2) - Mathf.Pow(z, 2); - float w = (remainder > 0f) ? Mathf.Sqrt(remainder) : 0.0f; - - return new Quaternion(x, y, z, w); - } - - /// - /// Reads the rotation from the stream - /// - /// The rotation read from the stream - public Quaternion ReadRotation() - { - float x = ReadSingle(); - float y = ReadSingle(); - float z = ReadSingle(); - float w = ReadSingle(); - - return new Quaternion(x, y, z, w); - } - - /// - /// Read a certain amount of bits from the stream. - /// - /// How many bits to read. Minimum 0, maximum 8. - /// The bits that were read - public ulong ReadBits(int bitCount) - { - if (m_NetworkSource == null) - { - throw new InvalidOperationException($"Cannot read bits on a non-{nameof(NetworkBuffer)} stream"); - } - - if (bitCount > 64) - { - throw new ArgumentOutOfRangeException(nameof(bitCount), "Cannot read more than 64 bits into a 64-bit value!"); - } - - if (bitCount < 0) - { - throw new ArgumentOutOfRangeException(nameof(bitCount), "Cannot read fewer than 0 bits!"); - } - - ulong read = 0; - for (int i = 0; i + 8 < bitCount; i += 8) - { - read |= (ulong)ReadByte() << i; - } - - read |= (ulong)ReadByteBits(bitCount & 7) << (bitCount & ~7); - return read; - } - - /// - /// Read a certain amount of bits from the stream. - /// - /// How many bits to read. Minimum 0, maximum 64. - /// The bits that were read - public byte ReadByteBits(int bitCount) - { - if (m_NetworkSource == null) - { - throw new InvalidOperationException($"Cannot read bits on a non-{nameof(NetworkBuffer)} stream"); - } - - if (bitCount > 8) - { - throw new ArgumentOutOfRangeException(nameof(bitCount), "Cannot read more than 8 bits into an 8-bit value!"); - } - - if (bitCount < 0) - { - throw new ArgumentOutOfRangeException(nameof(bitCount), "Cannot read fewer than 0 bits!"); - } - - int result = 0; - var convert = new ByteBool(); - for (int i = 0; i < bitCount; ++i) - { - result |= convert.Collapse(ReadBit()) << i; - } - - return (byte)result; - } - - /// - /// Read a nibble (4 bits) from the stream. - /// - /// Whether or not the nibble should be left-shifted by 4 bits - /// The nibble that was read - public byte ReadNibble(bool asUpper) - { - if (m_NetworkSource == null) - { - throw new InvalidOperationException($"Cannot read bits on a non-{nameof(NetworkBuffer)} stream"); - } - - var convert = new ByteBool(); - - byte result = (byte) - ( - convert.Collapse(ReadBit()) | - (convert.Collapse(ReadBit()) << 1) | - (convert.Collapse(ReadBit()) << 2) | - (convert.Collapse(ReadBit()) << 3) - ); - if (asUpper) - { - result <<= 4; - } - - return result; - } - - // Marginally faster than the one that accepts a bool - /// - /// Read a nibble (4 bits) from the stream. - /// - /// The nibble that was read - public byte ReadNibble() - { - if (m_NetworkSource == null) - { - throw new InvalidOperationException($"Cannot read bits on a non-{nameof(NetworkBuffer)} stream"); - } - - var convert = new ByteBool(); - return (byte) - ( - convert.Collapse(ReadBit()) | - (convert.Collapse(ReadBit()) << 1) | - (convert.Collapse(ReadBit()) << 2) | - (convert.Collapse(ReadBit()) << 3) - ); - } - - /// - /// Reads a signed byte - /// - /// Value read from stream. - public sbyte ReadSByte() => (sbyte)ReadByte(); - - /// - /// Read an unsigned short (UInt16) from the stream. - /// - /// Value read from stream. - public ushort ReadUInt16() => (ushort)(ReadByte() | (ReadByte() << 8)); - - /// - /// Read a signed short (Int16) from the stream. - /// - /// Value read from stream. - public short ReadInt16() => (short)ReadUInt16(); - - /// - /// Read a single character from the stream - /// - /// Value read from stream. - public char ReadChar() => (char)ReadUInt16(); - - /// - /// Read an unsigned int (UInt32) from the stream. - /// - /// Value read from stream. - public uint ReadUInt32() => (uint)(ReadByte() | (ReadByte() << 8) | (ReadByte() << 16) | (ReadByte() << 24)); - - /// - /// Read a signed int (Int32) from the stream. - /// - /// Value read from stream. - public int ReadInt32() => (int)ReadUInt32(); - - /// - /// Read an unsigned long (UInt64) from the stream. - /// - /// Value read from stream. - public ulong ReadUInt64() => - ( - ((uint)ReadByte()) | - ((ulong)ReadByte() << 8) | - ((ulong)ReadByte() << 16) | - ((ulong)ReadByte() << 24) | - ((ulong)ReadByte() << 32) | - ((ulong)ReadByte() << 40) | - ((ulong)ReadByte() << 48) | - ((ulong)ReadByte() << 56) - ); - - /// - /// Read a signed long (Int64) from the stream. - /// - /// Value read from stream. - public long ReadInt64() => (long)ReadUInt64(); - - /// - /// Read a ZigZag encoded varint signed short (Int16) from the stream. - /// - /// Decoded un-varinted value. - public short ReadInt16Packed() => (short)Arithmetic.ZigZagDecode(ReadUInt64Packed()); - - /// - /// Read a varint unsigned short (UInt16) from the stream. - /// - /// Un-varinted value. - public ushort ReadUInt16Packed() => (ushort)ReadUInt64Packed(); - - /// - /// Read a varint two-byte character from the stream. - /// - /// Un-varinted value. - public char ReadCharPacked() => (char)ReadUInt16Packed(); - - /// - /// Read a ZigZag encoded varint signed int (Int32) from the stream. - /// - /// Decoded un-varinted value. - public int ReadInt32Packed() => (int)Arithmetic.ZigZagDecode(ReadUInt64Packed()); - - /// - /// Read a varint unsigned int (UInt32) from the stream. - /// - /// Un-varinted value. - public uint ReadUInt32Packed() => (uint)ReadUInt64Packed(); - - /// - /// Read a ZigZag encoded varint signed long(Int64) from the stream. - /// - /// Decoded un-varinted value. - public long ReadInt64Packed() => Arithmetic.ZigZagDecode(ReadUInt64Packed()); - - /// - /// Read a varint unsigned long (UInt64) from the stream. - /// - /// Un-varinted value. - public ulong ReadUInt64Packed() - { - ulong header = ReadByteDirect(); - if (header <= 240) - { - return header; - } - - if (header <= 248) - { - return 240 + ((header - 241) << 8) + ReadByteDirect(); - } - - if (header == 249) - { - return 2288UL + (ulong)(ReadByte() << 8) + ReadByteDirect(); - } - - ulong res = ReadByteDirect() | ((ulong)ReadByteDirect() << 8) | ((ulong)ReadByte() << 16); - int cmp = 2; - int hdr = (int)(header - 247); - while (hdr > ++cmp) - { - res |= (ulong)ReadByte() << (cmp << 3); - } - - return res; - } - - // Read arrays - - /// - /// Read a string from the stream. - /// - /// The string that was read. - /// If set to true one byte chars are used and only ASCII is supported. - public StringBuilder ReadString(bool oneByteChars) => ReadString(null, oneByteChars); - - /// - /// Read a string from the stream. - /// - /// The string that was read. - /// The builder to read the values into or null to use a new builder. - /// If set to true one byte chars are used and only ASCII is supported. - public StringBuilder ReadString(StringBuilder builder = null, bool oneByteChars = false) - { - int expectedLength = (int)ReadUInt32Packed(); - if (builder == null) - { - builder = new StringBuilder(expectedLength); - } - else if (builder.Capacity + builder.Length < expectedLength) - { - builder.Capacity = expectedLength + builder.Length; - } - - for (int i = 0; i < expectedLength; ++i) - { - builder.Insert(i, oneByteChars ? (char)ReadByte() : ReadChar()); - } - - return builder; - } - - /// - /// Read string encoded as a varint from the stream. - /// - /// The string that was read. - /// The builder to read the string into or null to use a new builder - public string ReadStringPacked(StringBuilder builder = null) - { - int expectedLength = (int)ReadUInt32Packed(); - if (builder == null) - { - builder = new StringBuilder(expectedLength); - } - else if (builder.Capacity + builder.Length < expectedLength) - { - builder.Capacity = expectedLength + builder.Length; - } - - for (int i = 0; i < expectedLength; ++i) - { - builder.Insert(i, ReadCharPacked()); - } - - return builder.ToString(); - } - - /// - /// Read string diff from the stream. - /// - /// The string based on the diff and the old version. - /// The version to compare the diff to. - /// If set to true one byte chars are used and only ASCII is supported. - public StringBuilder ReadStringDiff(string compare, bool oneByteChars = false) => ReadStringDiff(null, compare, oneByteChars); - - /// - /// Read string diff from the stream. - /// - /// The string based on the diff and the old version - /// The builder to read the string into or null to use a new builder. - /// The version to compare the diff to. - /// If set to true one byte chars are used and only ASCII is supported. - public StringBuilder ReadStringDiff(StringBuilder builder, string compare, bool oneByteChars = false) - { - if (m_NetworkSource == null) - { - throw new InvalidOperationException($"Cannot read bits on a non-{nameof(NetworkBuffer)} stream"); - } - - int expectedLength = (int)ReadUInt32Packed(); - if (builder == null) - { - builder = new StringBuilder(expectedLength); - } - else if (builder.Capacity < expectedLength) - { - builder.Capacity = expectedLength; - } - - ulong dBlockStart = m_NetworkSource.BitPosition + (ulong)(compare == null ? 0 : Math.Min(expectedLength, compare.Length)); - ulong mapStart; - int compareLength = compare?.Length ?? 0; - for (int i = 0; i < expectedLength; ++i) - { - if (i >= compareLength || ReadBit()) - { -#if ARRAY_WRITE_PREMAP - // Move to data section - mapStart = m_NetworkSource.BitPosition; - m_NetworkSource.BitPosition = dBlockStart; -#endif - // Read datum - builder.Insert(i, oneByteChars ? (char)ReadByte() : ReadChar()); -#if ARRAY_WRITE_PREMAP - dBlockStart = m_NetworkSource.BitPosition; - // Return to mapping section - m_NetworkSource.BitPosition = mapStart; -#endif - } - else if (i < compareLength) - { - builder.Insert(i, compare[i]); - } - } - - m_NetworkSource.BitPosition = dBlockStart; - return builder; - } - - /// - /// Read string diff from the stream. - /// - /// The string based on the diff and the old version. - /// The builder containing the current version and that will also be used as the output buffer. - /// If set to true one byte chars will be used and only ASCII will be supported. - public StringBuilder ReadStringDiff(StringBuilder compareAndBuffer, bool oneByteChars = false) - { - if (m_NetworkSource == null) - { - throw new InvalidOperationException($"Cannot read bits on a non-{nameof(NetworkBuffer)} stream"); - } - - int expectedLength = (int)ReadUInt32Packed(); - if (compareAndBuffer == null) - { - throw new ArgumentNullException(nameof(compareAndBuffer), "Buffer cannot be null"); - } - - if (compareAndBuffer.Capacity < expectedLength) - { - compareAndBuffer.Capacity = expectedLength; - } - - ulong dBlockStart = m_NetworkSource.BitPosition + (ulong)Math.Min(expectedLength, compareAndBuffer.Length); - ulong mapStart; - for (int i = 0; i < expectedLength; ++i) - { - if (i >= compareAndBuffer.Length || ReadBit()) - { -#if ARRAY_WRITE_PREMAP - // Move to data section - mapStart = m_NetworkSource.BitPosition; - m_NetworkSource.BitPosition = dBlockStart; -#endif - // Read datum - compareAndBuffer.Remove(i, 1); - compareAndBuffer.Insert(i, oneByteChars ? (char)ReadByte() : ReadChar()); -#if ARRAY_WRITE_PREMAP - dBlockStart = m_NetworkSource.BitPosition; - // Return to mapping section - m_NetworkSource.BitPosition = mapStart; -#endif - } - } - - m_NetworkSource.BitPosition = dBlockStart; - return compareAndBuffer; - } - - /// - /// Read string diff encoded as varints from the stream. - /// - /// The string based on the diff and the old version. - /// The version to compare the diff to. - public StringBuilder ReadStringPackedDiff(string compare) => ReadStringPackedDiff(null, compare); - - /// - /// Read string diff encoded as varints from the stream. - /// - /// The string based on the diff and the old version - /// The builder to read the string into or null to use a new builder. - /// The version to compare the diff to. - public StringBuilder ReadStringPackedDiff(StringBuilder builder, string compare) - { - if (m_NetworkSource == null) - { - throw new InvalidOperationException($"Cannot read bits on a non-{nameof(NetworkBuffer)} stream"); - } - - int expectedLength = (int)ReadUInt32Packed(); - if (builder == null) - { - builder = new StringBuilder(expectedLength); - } - else if (builder.Capacity < expectedLength) - { - builder.Capacity = expectedLength; - } - - ulong dBlockStart = m_NetworkSource.BitPosition + (ulong)(compare == null ? 0 : Math.Min(expectedLength, compare.Length)); - ulong mapStart; - int compareLength = compare?.Length ?? 0; - for (int i = 0; i < expectedLength; ++i) - { - if (i >= compareLength || ReadBit()) - { -#if ARRAY_WRITE_PREMAP - // Move to data section - mapStart = m_NetworkSource.BitPosition; - m_NetworkSource.BitPosition = dBlockStart; -#endif - // Read datum - builder.Insert(i, ReadCharPacked()); -#if ARRAY_WRITE_PREMAP - dBlockStart = m_NetworkSource.BitPosition; - // Return to mapping section - m_NetworkSource.BitPosition = mapStart; -#endif - } - else if (i < compareLength) - { - builder.Insert(i, compare[i]); - } - } - - m_NetworkSource.BitPosition = dBlockStart; - return builder; - } - - /// - /// Read string diff encoded as varints from the stream. - /// - /// The string based on the diff and the old version. - /// The builder containing the current version and that will also be used as the output buffer. - public StringBuilder ReadStringPackedDiff(StringBuilder compareAndBuffer) - { - if (m_NetworkSource == null) - { - throw new InvalidOperationException($"Cannot read bits on a non-{nameof(NetworkBuffer)} stream"); - } - - int expectedLength = (int)ReadUInt32Packed(); - if (compareAndBuffer == null) - { - throw new ArgumentNullException(nameof(compareAndBuffer), "Buffer cannot be null"); - } - - if (compareAndBuffer.Capacity < expectedLength) - { - compareAndBuffer.Capacity = expectedLength; - } - - ulong dBlockStart = m_NetworkSource.BitPosition + (ulong)Math.Min(expectedLength, compareAndBuffer.Length); - ulong mapStart; - for (int i = 0; i < expectedLength; ++i) - { - if (i >= compareAndBuffer.Length || ReadBit()) - { -#if ARRAY_WRITE_PREMAP - // Move to data section - mapStart = m_NetworkSource.BitPosition; - m_NetworkSource.BitPosition = dBlockStart; -#endif - // Read datum - compareAndBuffer.Remove(i, 1); - compareAndBuffer.Insert(i, ReadCharPacked()); -#if ARRAY_WRITE_PREMAP - dBlockStart = m_NetworkSource.BitPosition; - // Return to mapping section - m_NetworkSource.BitPosition = mapStart; -#endif - } - } - - m_NetworkSource.BitPosition = dBlockStart; - return compareAndBuffer; - } - - /// - /// Read byte array into an optional buffer from the stream. - /// - /// The byte array that has been read. - /// The array to read into. If the array is not large enough or if it's null. A new array is created. - /// The length of the array if it's known. Otherwise -1 - public byte[] ReadByteArray(byte[] readTo = null, long knownLength = -1) - { - if (knownLength < 0) - { - knownLength = (long)ReadUInt64Packed(); - } - - if (readTo == null || readTo.LongLength != knownLength) - { - readTo = new byte[knownLength]; - } - - for (long i = 0; i < knownLength; ++i) - { - readTo[i] = ReadByteDirect(); - } - - return readTo; - } - - /// - /// CreateArraySegment - /// Creates an array segment from the size and offset values passed in. - /// If none are passed in, then it creates an array segment of the entire buffer. - /// - /// size to copy - /// offset within the stream buffer to start copying - /// ArraySegment<byte> - public ArraySegment CreateArraySegment(int sizeToCopy = -1, int offset = -1) - { - if (m_NetworkSource != null) - { - //If no offset was passed, used the current position - bool noOffset = offset == -1; - bool noSizeToCopy = sizeToCopy == -1; - - offset = noOffset ? (int)m_NetworkSource.Position : offset; - sizeToCopy = noSizeToCopy && noOffset ? (int)m_NetworkSource.Length : sizeToCopy; - if (sizeToCopy > 0) - { - //Check to make sure we won't be copying beyond our bounds - if ((m_NetworkSource.Length - offset) >= sizeToCopy) - { - return new ArraySegment(m_NetworkSource.GetBuffer(), offset, sizeToCopy); - } - - //If we didn't pass anything in or passed the length of the buffer - if (sizeToCopy == m_NetworkSource.Length) - { - offset = 0; - } - else - { - Debug.LogError($"{nameof(sizeToCopy)} ({sizeToCopy}) exceeds bounds with an {nameof(offset)} of ({offset})! "); - return new ArraySegment(); - } - - //Return the request array segment - return new ArraySegment(m_NetworkSource.GetBuffer(), offset, sizeToCopy); - } - - Debug.LogError($"{nameof(sizeToCopy)} ({sizeToCopy}) is zero or less! "); - } - else - { - Debug.LogError("Reader has no stream assigned to it! "); - } - - return new ArraySegment(); - } - - /// - /// Read byte array diff into an optional buffer from the stream. - /// - /// The byte array created from the diff and original. - /// The buffer containing the old version or null. - /// The length of the array if it's known. Otherwise -1 - public byte[] ReadByteArrayDiff(byte[] readTo = null, long knownLength = -1) - { - if (m_NetworkSource == null) - { - throw new InvalidOperationException($"Cannot read bits on a non-{nameof(NetworkBuffer)} stream"); - } - - if (knownLength < 0) - { - knownLength = (long)ReadUInt64Packed(); - } - - byte[] writeTo = readTo == null || readTo.LongLength != knownLength ? new byte[knownLength] : readTo; - ulong dBlockStart = m_NetworkSource.BitPosition + (ulong)(readTo == null ? 0 : Math.Min(knownLength, readTo.LongLength)); - ulong mapStart; - long readToLength = readTo?.LongLength ?? 0; - for (long i = 0; i < knownLength; ++i) - { - if (i >= readToLength || ReadBit()) - { -#if ARRAY_WRITE_PREMAP - // Move to data section - mapStart = m_NetworkSource.BitPosition; - m_NetworkSource.BitPosition = dBlockStart; -#endif - // Read datum - writeTo[i] = ReadByteDirect(); -#if ARRAY_WRITE_PREMAP - dBlockStart = m_NetworkSource.BitPosition; - // Return to mapping section - m_NetworkSource.BitPosition = mapStart; -#endif - } - else if (i < readTo.LongLength) - { - writeTo[i] = readTo[i]; - } - } - - m_NetworkSource.BitPosition = dBlockStart; - return writeTo; - } - - /// - /// Read short array from the stream. - /// - /// The array read from the stream. - /// The buffer to read into or null to create a new array - /// The known length or -1 if unknown - public short[] ReadShortArray(short[] readTo = null, long knownLength = -1) - { - if (knownLength < 0) - { - knownLength = (long)ReadUInt64Packed(); - } - - if (readTo == null || readTo.LongLength != knownLength) - { - readTo = new short[knownLength]; - } - - for (long i = 0; i < knownLength; ++i) - { - readTo[i] = ReadInt16(); - } - - return readTo; - } - - /// - /// Read short array in a packed format from the stream. - /// - /// The array read from the stream. - /// The buffer to read into or null to create a new array - /// The known length or -1 if unknown - public short[] ReadShortArrayPacked(short[] readTo = null, long knownLength = -1) - { - if (knownLength < 0) - { - knownLength = (long)ReadUInt64Packed(); - } - - if (readTo == null || readTo.LongLength != knownLength) - { - readTo = new short[knownLength]; - } - - for (long i = 0; i < knownLength; ++i) - { - readTo[i] = ReadInt16Packed(); - } - - return readTo; - } - - /// - /// Read short array diff from the stream. - /// - /// The array created from the diff and the current version. - /// The buffer containing the old version or null. - /// The known length or -1 if unknown - public short[] ReadShortArrayDiff(short[] readTo = null, long knownLength = -1) - { - if (m_NetworkSource == null) - { - throw new InvalidOperationException($"Cannot read bits on a non-{nameof(NetworkBuffer)} stream"); - } - - if (knownLength < 0) - { - knownLength = (long)ReadUInt64Packed(); - } - - short[] writeTo = readTo == null || readTo.LongLength != knownLength ? new short[knownLength] : readTo; - ulong dBlockStart = m_NetworkSource.BitPosition + (ulong)(readTo == null ? 0 : Math.Min(knownLength, readTo.LongLength)); - ulong mapStart; - long readToLength = readTo?.LongLength ?? 0; - for (long i = 0; i < knownLength; ++i) - { - if (i >= readToLength || ReadBit()) - { -#if ARRAY_WRITE_PREMAP - // Move to data section - mapStart = m_NetworkSource.BitPosition; - m_NetworkSource.BitPosition = dBlockStart; -#endif - // Read datum - writeTo[i] = ReadInt16(); -#if ARRAY_WRITE_PREMAP - dBlockStart = m_NetworkSource.BitPosition; - // Return to mapping section - m_NetworkSource.BitPosition = mapStart; -#endif - } - else if (i < readTo.LongLength) - { - writeTo[i] = readTo[i]; - } - } - - m_NetworkSource.BitPosition = dBlockStart; - return writeTo; - } - - /// - /// Read short array diff in a packed format from the stream. - /// - /// The array created from the diff and the current version. - /// The buffer containing the old version or null. - /// The known length or -1 if unknown - public short[] ReadShortArrayPackedDiff(short[] readTo = null, long knownLength = -1) - { - if (m_NetworkSource == null) - { - throw new InvalidOperationException($"Cannot read bits on a non-{nameof(NetworkBuffer)} stream"); - } - - if (knownLength < 0) - { - knownLength = (long)ReadUInt64Packed(); - } - - short[] writeTo = readTo == null || readTo.LongLength != knownLength ? new short[knownLength] : readTo; - ulong data = m_NetworkSource.BitPosition + (ulong)(readTo == null ? 0 : Math.Min(knownLength, readTo.LongLength)); - ulong rset; - long readToLength = readTo?.LongLength ?? 0; - for (long i = 0; i < knownLength; ++i) - { - if (i >= readToLength || ReadBit()) - { -#if ARRAY_WRITE_PREMAP - // Move to data section - rset = m_NetworkSource.BitPosition; - m_NetworkSource.BitPosition = data; -#endif - // Read datum - writeTo[i] = ReadInt16Packed(); -#if ARRAY_WRITE_PREMAP - // Return to mapping section - data = m_NetworkSource.BitPosition; - m_NetworkSource.BitPosition = rset; -#endif - } - else if (i < readTo.LongLength) - { - writeTo[i] = readTo[i]; - } - } - - m_NetworkSource.BitPosition = data; - return writeTo; - } - - /// - /// Read ushort array from the stream. - /// - /// The array read from the stream. - /// The buffer to read into or null to create a new array - /// The known length or -1 if unknown - public ushort[] ReadUShortArray(ushort[] readTo = null, long knownLength = -1) - { - if (knownLength < 0) - { - knownLength = (long)ReadUInt64Packed(); - } - - if (readTo == null || readTo.LongLength != knownLength) - { - readTo = new ushort[knownLength]; - } - - for (long i = 0; i < knownLength; ++i) - { - readTo[i] = ReadUInt16(); - } - - return readTo; - } - - /// - /// Read ushort array in a packed format from the stream. - /// - /// The array read from the stream. - /// The buffer to read into or null to create a new array - /// The known length or -1 if unknown - public ushort[] ReadUShortArrayPacked(ushort[] readTo = null, long knownLength = -1) - { - if (knownLength < 0) - { - knownLength = (long)ReadUInt64Packed(); - } - - if (readTo == null || readTo.LongLength != knownLength) - { - readTo = new ushort[knownLength]; - } - - for (long i = 0; i < knownLength; ++i) - { - readTo[i] = ReadUInt16Packed(); - } - - return readTo; - } - - /// - /// Read ushort array diff from the stream. - /// - /// The array created from the diff and the current version. - /// The buffer containing the old version or null. - /// The known length or -1 if unknown - public ushort[] ReadUShortArrayDiff(ushort[] readTo = null, long knownLength = -1) - { - if (m_NetworkSource == null) - { - throw new InvalidOperationException($"Cannot read bits on a non-{nameof(NetworkBuffer)} stream"); - } - - if (knownLength < 0) - { - knownLength = (long)ReadUInt64Packed(); - } - - ushort[] writeTo = readTo == null || readTo.LongLength != knownLength ? new ushort[knownLength] : readTo; - ulong dBlockStart = m_NetworkSource.BitPosition + (ulong)(readTo == null ? 0 : Math.Min(knownLength, readTo.LongLength)); - ulong mapStart; - long readToLength = readTo?.LongLength ?? 0; - for (long i = 0; i < knownLength; ++i) - { - if (i >= readToLength || ReadBit()) - { -#if ARRAY_WRITE_PREMAP - // Move to data section - mapStart = m_NetworkSource.BitPosition; - m_NetworkSource.BitPosition = dBlockStart; -#endif - // Read datum - writeTo[i] = ReadUInt16(); -#if ARRAY_WRITE_PREMAP - dBlockStart = m_NetworkSource.BitPosition; - // Return to mapping section - m_NetworkSource.BitPosition = mapStart; -#endif - } - else if (i < readTo.LongLength) - { - writeTo[i] = readTo[i]; - } - } - - m_NetworkSource.BitPosition = dBlockStart; - return writeTo; - } - - /// - /// Read ushort array diff in a packed format from the stream. - /// - /// The array created from the diff and the current version. - /// The buffer containing the old version or null. - /// The known length or -1 if unknown - public ushort[] ReadUShortArrayPackedDiff(ushort[] readTo = null, long knownLength = -1) - { - if (m_NetworkSource == null) - { - throw new InvalidOperationException($"Cannot read bits on a non-{nameof(NetworkBuffer)} stream"); - } - - if (knownLength < 0) - { - knownLength = (long)ReadUInt64Packed(); - } - - ushort[] writeTo = readTo == null || readTo.LongLength != knownLength ? new ushort[knownLength] : readTo; - ulong data = m_NetworkSource.BitPosition + (ulong)(readTo == null ? 0 : Math.Min(knownLength, readTo.LongLength)); - ulong rset; - long readToLength = readTo?.LongLength ?? 0; - for (long i = 0; i < knownLength; ++i) - { - if (i >= readToLength || ReadBit()) - { -#if ARRAY_WRITE_PREMAP - // Move to data section - rset = m_NetworkSource.BitPosition; - m_NetworkSource.BitPosition = data; -#endif - // Read datum - writeTo[i] = ReadUInt16Packed(); -#if ARRAY_WRITE_PREMAP - // Return to mapping section - data = m_NetworkSource.BitPosition; - m_NetworkSource.BitPosition = rset; -#endif - } - else if (i < readTo.LongLength) - { - writeTo[i] = readTo[i]; - } - } - - m_NetworkSource.BitPosition = data; - return writeTo; - } - - /// - /// Read int array from the stream. - /// - /// The array read from the stream. - /// The buffer to read into or null to create a new array - /// The known length or -1 if unknown - public int[] ReadIntArray(int[] readTo = null, long knownLength = -1) - { - if (knownLength < 0) - { - knownLength = (long)ReadUInt64Packed(); - } - - if (readTo == null || readTo.LongLength != knownLength) - { - readTo = new int[knownLength]; - } - - for (long i = 0; i < knownLength; ++i) - { - readTo[i] = ReadInt32(); - } - - return readTo; - } - - /// - /// Read int array in a packed format from the stream. - /// - /// The array read from the stream. - /// The buffer to read into or null to create a new array - /// The known length or -1 if unknown - public int[] ReadIntArrayPacked(int[] readTo = null, long knownLength = -1) - { - if (knownLength < 0) - { - knownLength = (long)ReadUInt64Packed(); - } - - if (readTo == null || readTo.LongLength != knownLength) - { - readTo = new int[knownLength]; - } - - for (long i = 0; i < knownLength; ++i) - { - readTo[i] = ReadInt32Packed(); - } - - return readTo; - } - - /// - /// Read int array diff from the stream. - /// - /// The array created from the diff and the current version. - /// The buffer containing the old version or null. - /// The known length or -1 if unknown - public int[] ReadIntArrayDiff(int[] readTo = null, long knownLength = -1) - { - if (m_NetworkSource == null) - { - throw new InvalidOperationException($"Cannot read bits on a non-{nameof(NetworkBuffer)} stream"); - } - - if (knownLength < 0) - { - knownLength = (long)ReadUInt64Packed(); - } - - int[] writeTo = readTo == null || readTo.LongLength != knownLength ? new int[knownLength] : readTo; - ulong dBlockStart = m_NetworkSource.BitPosition + (ulong)(readTo == null ? 0 : Math.Min(knownLength, readTo.LongLength)); - ulong mapStart; - long readToLength = readTo?.LongLength ?? 0; - for (long i = 0; i < knownLength; ++i) - { - if (i >= readToLength || ReadBit()) - { -#if ARRAY_WRITE_PREMAP - // Move to data section - mapStart = m_NetworkSource.BitPosition; - m_NetworkSource.BitPosition = dBlockStart; -#endif - // Read datum - writeTo[i] = ReadInt32(); -#if ARRAY_WRITE_PREMAP - dBlockStart = m_NetworkSource.BitPosition; - // Return to mapping section - m_NetworkSource.BitPosition = mapStart; -#endif - } - else if (i < readTo.LongLength) - { - writeTo[i] = readTo[i]; - } - } - - m_NetworkSource.BitPosition = dBlockStart; - return writeTo; - } - - /// - /// Read int array diff in a packed format from the stream. - /// - /// The array created from the diff and the current version. - /// The buffer containing the old version or null. - /// The known length or -1 if unknown - public int[] ReadIntArrayPackedDiff(int[] readTo = null, long knownLength = -1) - { - if (m_NetworkSource == null) - { - throw new InvalidOperationException($"Cannot read bits on a non-{nameof(NetworkBuffer)} stream"); - } - - if (knownLength < 0) - { - knownLength = (long)ReadUInt64Packed(); - } - - int[] writeTo = readTo == null || readTo.LongLength != knownLength ? new int[knownLength] : readTo; - ulong data = m_NetworkSource.BitPosition + (ulong)(readTo == null ? 0 : Math.Min(knownLength, readTo.LongLength)); - ulong rset; - long readToLength = readTo?.LongLength ?? 0; - for (long i = 0; i < knownLength; ++i) - { - if (i >= readToLength || ReadBit()) - { -#if ARRAY_WRITE_PREMAP - // Move to data section - rset = m_NetworkSource.BitPosition; - m_NetworkSource.BitPosition = data; -#endif - // Read datum - writeTo[i] = ReadInt32Packed(); -#if ARRAY_WRITE_PREMAP - // Return to mapping section - data = m_NetworkSource.BitPosition; - m_NetworkSource.BitPosition = rset; -#endif - } - else if (i < readTo.LongLength) - { - writeTo[i] = readTo[i]; - } - } - - m_NetworkSource.BitPosition = data; - return writeTo; - } - - /// - /// Read uint array from the stream. - /// - /// The array read from the stream. - /// The buffer to read into or null to create a new array - /// The known length or -1 if unknown - public uint[] ReadUIntArray(uint[] readTo = null, long knownLength = -1) - { - if (knownLength < 0) - { - knownLength = (long)ReadUInt64Packed(); - } - - if (readTo == null || readTo.LongLength != knownLength) - { - readTo = new uint[knownLength]; - } - - for (long i = 0; i < knownLength; ++i) - { - readTo[i] = ReadUInt32(); - } - - return readTo; - } - - /// - /// Read uint array in a packed format from the stream. - /// - /// The array read from the stream. - /// The buffer to read into or null to create a new array - /// The known length or -1 if unknown - public uint[] ReadUIntArrayPacked(uint[] readTo = null, long knownLength = -1) - { - if (knownLength < 0) - { - knownLength = (long)ReadUInt64Packed(); - } - - if (readTo == null || readTo.LongLength != knownLength) - { - readTo = new uint[knownLength]; - } - - for (long i = 0; i < knownLength; ++i) - { - readTo[i] = ReadUInt32Packed(); - } - - return readTo; - } - - /// - /// Read uint array diff from the stream. - /// - /// The array created from the diff and the current version. - /// The buffer containing the old version or null. - /// The known length or -1 if unknown - public uint[] ReadUIntArrayDiff(uint[] readTo = null, long knownLength = -1) - { - if (m_NetworkSource == null) - { - throw new InvalidOperationException($"Cannot read bits on a non-{nameof(NetworkBuffer)} stream"); - } - - if (knownLength < 0) - { - knownLength = (long)ReadUInt64Packed(); - } - - uint[] writeTo = readTo == null || readTo.LongLength != knownLength ? new uint[knownLength] : readTo; - ulong dBlockStart = m_NetworkSource.BitPosition + (ulong)(readTo == null ? 0 : Math.Min(knownLength, readTo.LongLength)); - ulong mapStart; - long readToLength = readTo?.LongLength ?? 0; - for (long i = 0; i < knownLength; ++i) - { - if (i >= readToLength || ReadBit()) - { -#if ARRAY_WRITE_PREMAP - // Move to data section - mapStart = m_NetworkSource.BitPosition; - m_NetworkSource.BitPosition = dBlockStart; -#endif - // Read datum - writeTo[i] = ReadUInt32(); -#if ARRAY_WRITE_PREMAP - dBlockStart = m_NetworkSource.BitPosition; - // Return to mapping section - m_NetworkSource.BitPosition = mapStart; -#endif - } - else if (i < readTo.LongLength) - { - writeTo[i] = readTo[i]; - } - } - - m_NetworkSource.BitPosition = dBlockStart; - return writeTo; - } - - /// - /// Read long array from the stream. - /// - /// The array read from the stream. - /// The buffer to read into or null to create a new array - /// The known length or -1 if unknown - public long[] ReadLongArray(long[] readTo = null, long knownLength = -1) - { - if (knownLength < 0) - { - knownLength = (long)ReadUInt64Packed(); - } - - if (readTo == null || readTo.LongLength != knownLength) - { - readTo = new long[knownLength]; - } - - for (long i = 0; i < knownLength; ++i) - { - readTo[i] = ReadInt64(); - } - - return readTo; - } - - /// - /// Read long array in a packed format from the stream. - /// - /// The array read from the stream. - /// The buffer to read into or null to create a new array - /// The known length or -1 if unknown - public long[] ReadLongArrayPacked(long[] readTo = null, long knownLength = -1) - { - if (knownLength < 0) - { - knownLength = (long)ReadUInt64Packed(); - } - - if (readTo == null || readTo.LongLength != knownLength) - { - readTo = new long[knownLength]; - } - - for (long i = 0; i < knownLength; ++i) - { - readTo[i] = ReadInt64Packed(); - } - - return readTo; - } - - /// - /// Read long array diff from the stream. - /// - /// The array created from the diff and the current version. - /// The buffer containing the old version or null. - /// The known length or -1 if unknown - public long[] ReadLongArrayDiff(long[] readTo = null, long knownLength = -1) - { - if (m_NetworkSource == null) - { - throw new InvalidOperationException($"Cannot read bits on a non-{nameof(NetworkBuffer)} stream"); - } - - if (knownLength < 0) - { - knownLength = (long)ReadUInt64Packed(); - } - - long[] writeTo = readTo == null || readTo.LongLength != knownLength ? new long[knownLength] : readTo; - ulong dBlockStart = m_NetworkSource.BitPosition + (ulong)(readTo == null ? 0 : Math.Min(knownLength, readTo.LongLength)); - ulong mapStart; - long readToLength = readTo?.LongLength ?? 0; - for (long i = 0; i < knownLength; ++i) - { - if (i >= readToLength || ReadBit()) - { -#if ARRAY_WRITE_PREMAP - // Move to data section - mapStart = m_NetworkSource.BitPosition; - m_NetworkSource.BitPosition = dBlockStart; -#endif - // Read datum - writeTo[i] = ReadInt64(); -#if ARRAY_WRITE_PREMAP - dBlockStart = m_NetworkSource.BitPosition; - // Return to mapping section - m_NetworkSource.BitPosition = mapStart; -#endif - } - else if (i < readTo.LongLength) - { - writeTo[i] = readTo[i]; - } - } - - m_NetworkSource.BitPosition = dBlockStart; - return writeTo; - } - - /// - /// Read long array diff in a packed format from the stream. - /// - /// The array created from the diff and the current version. - /// The buffer containing the old version or null. - /// The known length or -1 if unknown - public long[] ReadLongArrayPackedDiff(long[] readTo = null, long knownLength = -1) - { - if (m_NetworkSource == null) - { - throw new InvalidOperationException($"Cannot read bits on a non-{nameof(NetworkBuffer)} stream"); - } - - if (knownLength < 0) - { - knownLength = (long)ReadUInt64Packed(); - } - - long[] writeTo = readTo == null || readTo.LongLength != knownLength ? new long[knownLength] : readTo; - ulong data = m_NetworkSource.BitPosition + (ulong)(readTo == null ? 0 : Math.Min(knownLength, readTo.LongLength)); - ulong rset; - long readToLength = readTo?.LongLength ?? 0; - for (long i = 0; i < knownLength; ++i) - { - if (i >= readToLength || ReadBit()) - { -#if ARRAY_WRITE_PREMAP - // Move to data section - rset = m_NetworkSource.BitPosition; - m_NetworkSource.BitPosition = data; -#endif - // Read datum - writeTo[i] = ReadInt64Packed(); -#if ARRAY_WRITE_PREMAP - // Return to mapping section - data = m_NetworkSource.BitPosition; - m_NetworkSource.BitPosition = rset; -#endif - } - else if (i < readTo.LongLength) - { - writeTo[i] = readTo[i]; - } - } - - m_NetworkSource.BitPosition = data; - return writeTo; - } - - /// - /// Read ulong array from the stream. - /// - /// The array read from the stream. - /// The buffer to read into or null to create a new array - /// The known length or -1 if unknown - public ulong[] ReadULongArray(ulong[] readTo = null, long knownLength = -1) - { - if (knownLength < 0) - { - knownLength = (long)ReadUInt64Packed(); - } - - if (readTo == null || readTo.LongLength != knownLength) - { - readTo = new ulong[knownLength]; - } - - for (long i = 0; i < knownLength; ++i) - { - readTo[i] = ReadUInt64(); - } - - return readTo; - } - - /// - /// Read ulong array in a packed format from the stream. - /// - /// The array read from the stream. - /// The buffer to read into or null to create a new array - /// The known length or -1 if unknown - public ulong[] ReadULongArrayPacked(ulong[] readTo = null, long knownLength = -1) - { - if (knownLength < 0) - { - knownLength = (long)ReadUInt64Packed(); - } - - if (readTo == null || readTo.LongLength != knownLength) - { - readTo = new ulong[knownLength]; - } - - for (long i = 0; i < knownLength; ++i) - { - readTo[i] = ReadUInt64Packed(); - } - - return readTo; - } - - /// - /// Read ulong array diff from the stream. - /// - /// The array created from the diff and the current version. - /// The buffer containing the old version or null. - /// The known length or -1 if unknown - public ulong[] ReadULongArrayDiff(ulong[] readTo = null, long knownLength = -1) - { - if (m_NetworkSource == null) - { - throw new InvalidOperationException($"Cannot read bits on a non-{nameof(NetworkBuffer)} stream"); - } - - if (knownLength < 0) - { - knownLength = (long)ReadUInt64Packed(); - } - - ulong[] writeTo = readTo == null || readTo.LongLength != knownLength ? new ulong[knownLength] : readTo; - ulong dBlockStart = m_NetworkSource.BitPosition + (ulong)(readTo == null ? 0 : Math.Min(knownLength, readTo.LongLength)); - ulong mapStart; - long readToLength = readTo?.LongLength ?? 0; - for (long i = 0; i < knownLength; ++i) - { - if (i >= readToLength || ReadBit()) - { -#if ARRAY_WRITE_PREMAP - // Move to data section - mapStart = m_NetworkSource.BitPosition; - m_NetworkSource.BitPosition = dBlockStart; -#endif - // Read datum - writeTo[i] = ReadUInt64(); -#if ARRAY_WRITE_PREMAP - dBlockStart = m_NetworkSource.BitPosition; - // Return to mapping section - m_NetworkSource.BitPosition = mapStart; -#endif - } - else if (i < readTo.LongLength) - { - writeTo[i] = readTo[i]; - } - } - - m_NetworkSource.BitPosition = dBlockStart; - return writeTo; - } - - /// - /// Read ulong array diff in a packed format from the stream. - /// - /// The array created from the diff and the current version. - /// The buffer containing the old version or null. - /// The known length or -1 if unknown - public ulong[] ReadULongArrayPackedDiff(ulong[] readTo = null, long knownLength = -1) - { - if (m_NetworkSource == null) - { - throw new InvalidOperationException($"Cannot read bits on a non-{nameof(NetworkBuffer)} stream"); - } - - if (knownLength < 0) - { - knownLength = (long)ReadUInt64Packed(); - } - - ulong[] writeTo = readTo == null || readTo.LongLength != knownLength ? new ulong[knownLength] : readTo; - ulong data = m_NetworkSource.BitPosition + (ulong)(readTo == null ? 0 : Math.Min(knownLength, readTo.LongLength)); - ulong rset; - long readToLength = readTo?.LongLength ?? 0; - for (long i = 0; i < knownLength; ++i) - { - if (i >= readToLength || ReadBit()) - { -#if ARRAY_WRITE_PREMAP - // Move to data section - rset = m_NetworkSource.BitPosition; - m_NetworkSource.BitPosition = data; -#endif - // Read datum - writeTo[i] = ReadUInt64Packed(); -#if ARRAY_WRITE_PREMAP - // Return to mapping section - data = m_NetworkSource.BitPosition; - m_NetworkSource.BitPosition = rset; -#endif - } - else if (i < readTo.LongLength) - { - writeTo[i] = readTo[i]; - } - } - - m_NetworkSource.BitPosition = data; - return writeTo; - } - - /// - /// Read float array from the stream. - /// - /// The array read from the stream. - /// The buffer to read into or null to create a new array - /// The known length or -1 if unknown - public float[] ReadFloatArray(float[] readTo = null, long knownLength = -1) - { - if (knownLength < 0) - { - knownLength = (long)ReadUInt64Packed(); - } - - if (readTo == null || readTo.LongLength != knownLength) - { - readTo = new float[knownLength]; - } - - for (long i = 0; i < knownLength; ++i) - { - readTo[i] = ReadSingle(); - } - - return readTo; - } - - /// - /// Read float array in a packed format from the stream. - /// - /// The array read from the stream. - /// The buffer to read into or null to create a new array - /// The known length or -1 if unknown - public float[] ReadFloatArrayPacked(float[] readTo = null, long knownLength = -1) - { - if (knownLength < 0) - { - knownLength = (long)ReadUInt64Packed(); - } - - if (readTo == null || readTo.LongLength != knownLength) - { - readTo = new float[knownLength]; - } - - for (long i = 0; i < knownLength; ++i) - { - readTo[i] = ReadSinglePacked(); - } - - return readTo; - } - - /// - /// Read float array diff from the stream. - /// - /// The array created from the diff and the current version. - /// The buffer containing the old version or null. - /// The known length or -1 if unknown - public float[] ReadFloatArrayDiff(float[] readTo = null, long knownLength = -1) - { - if (m_NetworkSource == null) - { - throw new InvalidOperationException($"Cannot read bits on a non-{nameof(NetworkBuffer)} stream"); - } - - if (knownLength < 0) - { - knownLength = (long)ReadUInt64Packed(); - } - - float[] writeTo = readTo == null || readTo.LongLength != knownLength ? new float[knownLength] : readTo; - ulong dBlockStart = m_NetworkSource.BitPosition + (ulong)(readTo == null ? 0 : Math.Min(knownLength, readTo.LongLength)); - ulong mapStart; - long readToLength = readTo?.LongLength ?? 0; - for (long i = 0; i < knownLength; ++i) - { - if (i >= readToLength || ReadBit()) - { -#if ARRAY_WRITE_PREMAP - // Move to data section - mapStart = m_NetworkSource.BitPosition; - m_NetworkSource.BitPosition = dBlockStart; -#endif - // Read datum - writeTo[i] = ReadSingle(); -#if ARRAY_WRITE_PREMAP - dBlockStart = m_NetworkSource.BitPosition; - // Return to mapping section - m_NetworkSource.BitPosition = mapStart; -#endif - } - else if (i < readTo.LongLength) - { - writeTo[i] = readTo[i]; - } - } - - m_NetworkSource.BitPosition = dBlockStart; - return writeTo; - } - - /// - /// Read float array diff in a packed format from the stream. - /// - /// The array created from the diff and the current version. - /// The buffer containing the old version or null. - /// The known length or -1 if unknown - public float[] ReadFloatArrayPackedDiff(float[] readTo = null, long knownLength = -1) - { - if (m_NetworkSource == null) - { - throw new InvalidOperationException($"Cannot read bits on a non-{nameof(NetworkBuffer)} stream"); - } - - if (knownLength < 0) - { - knownLength = (long)ReadUInt64Packed(); - } - - float[] writeTo = readTo == null || readTo.LongLength != knownLength ? new float[knownLength] : readTo; - ulong data = m_NetworkSource.BitPosition + (ulong)(readTo == null ? 0 : Math.Min(knownLength, readTo.LongLength)); - ulong rset; - long readToLength = readTo?.LongLength ?? 0; - for (long i = 0; i < knownLength; ++i) - { - if (i >= readToLength || ReadBit()) - { -#if ARRAY_WRITE_PREMAP - // Move to data section - rset = m_NetworkSource.BitPosition; - m_NetworkSource.BitPosition = data; -#endif - // Read datum - readTo[i] = ReadSinglePacked(); -#if ARRAY_WRITE_PREMAP - // Return to mapping section - data = m_NetworkSource.BitPosition; - m_NetworkSource.BitPosition = rset; -#endif - } - else if (i < readTo.LongLength) - { - writeTo[i] = readTo[i]; - } - } - - m_NetworkSource.BitPosition = data; - return writeTo; - } - - /// - /// Read double array from the stream. - /// - /// The array read from the stream. - /// The buffer to read into or null to create a new array - /// The known length or -1 if unknown - public double[] ReadDoubleArray(double[] readTo = null, long knownLength = -1) - { - if (knownLength < 0) - { - knownLength = (long)ReadUInt64Packed(); - } - - if (readTo == null || readTo.LongLength != knownLength) - { - readTo = new double[knownLength]; - } - - for (long i = 0; i < knownLength; ++i) - { - readTo[i] = ReadDouble(); - } - - return readTo; - } - - /// - /// Read double array in a packed format from the stream. - /// - /// The array read from the stream. - /// The buffer to read into or null to create a new array - /// The known length or -1 if unknown - public double[] ReadDoubleArrayPacked(double[] readTo = null, long knownLength = -1) - { - if (knownLength < 0) - { - knownLength = (long)ReadUInt64Packed(); - } - - if (readTo == null || readTo.LongLength != knownLength) - { - readTo = new double[knownLength]; - } - - for (long i = 0; i < knownLength; ++i) - { - readTo[i] = ReadDoublePacked(); - } - - return readTo; - } - - /// - /// Read double array diff from the stream. - /// - /// The array created from the diff and the current version. - /// The buffer containing the old version or null. - /// The known length or -1 if unknown - public double[] ReadDoubleArrayDiff(double[] readTo = null, long knownLength = -1) - { - if (m_NetworkSource == null) - { - throw new InvalidOperationException($"Cannot read bits on a non-{nameof(NetworkBuffer)} stream"); - } - - if (knownLength < 0) - { - knownLength = (long)ReadUInt64Packed(); - } - - double[] writeTo = readTo == null || readTo.LongLength != knownLength ? new double[knownLength] : readTo; - ulong dBlockStart = m_NetworkSource.BitPosition + (ulong)(readTo == null ? 0 : Math.Min(knownLength, readTo.LongLength)); - ulong mapStart; - long readToLength = readTo?.LongLength ?? 0; - for (long i = 0; i < knownLength; ++i) - { - if (i >= readToLength || ReadBit()) - { -#if ARRAY_WRITE_PREMAP - // Move to data section - mapStart = m_NetworkSource.BitPosition; - m_NetworkSource.BitPosition = dBlockStart; -#endif - // Read datum - writeTo[i] = ReadDouble(); -#if ARRAY_WRITE_PREMAP - dBlockStart = m_NetworkSource.BitPosition; - // Return to mapping section - m_NetworkSource.BitPosition = mapStart; -#endif - } - else if (i < readTo.LongLength) - { - writeTo[i] = readTo[i]; - } - } - - m_NetworkSource.BitPosition = dBlockStart; - return writeTo; - } - - /// - /// Read double array diff in a packed format from the stream. - /// - /// The array created from the diff and the current version. - /// The buffer containing the old version or null. - /// The known length or -1 if unknown - public double[] ReadDoubleArrayPackedDiff(double[] readTo = null, long knownLength = -1) - { - if (m_NetworkSource == null) - { - throw new InvalidOperationException($"Cannot read bits on a non-{nameof(NetworkBuffer)} stream"); - } - - if (knownLength < 0) - { - knownLength = (long)ReadUInt64Packed(); - } - - double[] writeTo = readTo == null || readTo.LongLength != knownLength ? new double[knownLength] : readTo; - ulong data = m_NetworkSource.BitPosition + (ulong)(readTo == null ? 0 : Math.Min(knownLength, readTo.LongLength)); - ulong rset; - long readToLength = readTo?.LongLength ?? 0; - for (long i = 0; i < knownLength; ++i) - { - if (i >= readToLength || ReadBit()) - { -#if ARRAY_WRITE_PREMAP - // Move to data section - rset = m_NetworkSource.BitPosition; - m_NetworkSource.BitPosition = data; -#endif - // Read datum - writeTo[i] = ReadDoublePacked(); -#if ARRAY_WRITE_PREMAP - // Return to mapping section - data = m_NetworkSource.BitPosition; - m_NetworkSource.BitPosition = rset; -#endif - } - else if (i < readTo.LongLength) - { - writeTo[i] = readTo[i]; - } - } - - m_NetworkSource.BitPosition = data; - return writeTo; - } - } -} diff --git a/com.unity.netcode.gameobjects/Runtime/Serialization/NetworkReader.cs.meta b/com.unity.netcode.gameobjects/Runtime/Serialization/NetworkReader.cs.meta deleted file mode 100644 index 697dfef2f4..0000000000 --- a/com.unity.netcode.gameobjects/Runtime/Serialization/NetworkReader.cs.meta +++ /dev/null @@ -1,11 +0,0 @@ -fileFormatVersion: 2 -guid: 4b085e35addc96e41a064eba3674887b -MonoImporter: - externalObjects: {} - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: - assetBundleName: - assetBundleVariant: diff --git a/com.unity.netcode.gameobjects/Runtime/Serialization/NetworkSerializer.cs b/com.unity.netcode.gameobjects/Runtime/Serialization/NetworkSerializer.cs deleted file mode 100644 index 26637e9273..0000000000 --- a/com.unity.netcode.gameobjects/Runtime/Serialization/NetworkSerializer.cs +++ /dev/null @@ -1,910 +0,0 @@ -using System; -using UnityEngine; - -namespace Unity.Netcode -{ - public sealed class NetworkSerializer - { - private readonly NetworkReader m_Reader; - private readonly NetworkWriter m_Writer; - - public NetworkReader Reader => m_Reader; - public NetworkWriter Writer => m_Writer; - - public bool IsReading { get; } - - public NetworkSerializer(NetworkReader reader) - { - m_Reader = reader; - IsReading = true; - } - - public NetworkSerializer(NetworkWriter writer) - { - m_Writer = writer; - IsReading = false; - } - - public void Serialize(ref bool value) - { - if (IsReading) - { - value = m_Reader.ReadBool(); - } - else - { - m_Writer.WriteBool(value); - } - } - - public void Serialize(ref char value) - { - if (IsReading) - { - value = m_Reader.ReadCharPacked(); - } - else - { - m_Writer.WriteCharPacked(value); - } - } - - public void Serialize(ref sbyte value) - { - if (IsReading) - { - value = m_Reader.ReadSByte(); - } - else - { - m_Writer.WriteSByte(value); - } - } - - public void Serialize(ref byte value) - { - if (IsReading) - { - value = m_Reader.ReadByteDirect(); - } - else - { - m_Writer.WriteByte(value); - } - } - - public void Serialize(ref short value) - { - if (IsReading) - { - value = m_Reader.ReadInt16Packed(); - } - else - { - m_Writer.WriteInt16Packed(value); - } - } - - public void Serialize(ref ushort value) - { - if (IsReading) - { - value = m_Reader.ReadUInt16Packed(); - } - else - { - m_Writer.WriteUInt16Packed(value); - } - } - - public void Serialize(ref int value) - { - if (IsReading) - { - value = m_Reader.ReadInt32Packed(); - } - else - { - m_Writer.WriteInt32Packed(value); - } - } - - public void Serialize(ref uint value) - { - if (IsReading) - { - value = m_Reader.ReadUInt32Packed(); - } - else - { - m_Writer.WriteUInt32Packed(value); - } - } - - public void Serialize(ref long value) - { - if (IsReading) - { - value = m_Reader.ReadInt64Packed(); - } - else - { - m_Writer.WriteInt64Packed(value); - } - } - - public void Serialize(ref ulong value) - { - if (IsReading) - { - value = m_Reader.ReadUInt64Packed(); - } - else - { - m_Writer.WriteUInt64Packed(value); - } - } - - public void Serialize(ref float value) - { - if (IsReading) - { - value = m_Reader.ReadSinglePacked(); - } - else - { - m_Writer.WriteSinglePacked(value); - } - } - - public void Serialize(ref double value) - { - if (IsReading) - { - value = m_Reader.ReadDoublePacked(); - } - else - { - m_Writer.WriteDoublePacked(value); - } - } - - public void Serialize(ref string value) - { - if (IsReading) - { - var isSet = m_Reader.ReadBool(); - value = isSet ? m_Reader.ReadStringPacked() : null; - } - else - { - var isSet = value != null; - m_Writer.WriteBool(isSet); - if (isSet) - { - m_Writer.WriteStringPacked(value); - } - } - } - - public void Serialize(ref Color value) - { - if (IsReading) - { - value = m_Reader.ReadColorPacked(); - } - else - { - m_Writer.WriteColorPacked(value); - } - } - - public void Serialize(ref Color32 value) - { - if (IsReading) - { - value = m_Reader.ReadColor32(); - } - else - { - m_Writer.WriteColor32(value); - } - } - - public void Serialize(ref Vector2 value) - { - if (IsReading) - { - value = m_Reader.ReadVector2Packed(); - } - else - { - m_Writer.WriteVector2Packed(value); - } - } - - public void Serialize(ref Vector3 value) - { - if (IsReading) - { - value = m_Reader.ReadVector3Packed(); - } - else - { - m_Writer.WriteVector3Packed(value); - } - } - - public void Serialize(ref Vector4 value) - { - if (IsReading) - { - value = m_Reader.ReadVector4Packed(); - } - else - { - m_Writer.WriteVector4Packed(value); - } - } - - public void Serialize(ref Quaternion value) - { - if (IsReading) - { - value = m_Reader.ReadRotationPacked(); - } - else - { - m_Writer.WriteRotationPacked(value); - } - } - - public void Serialize(ref Ray value) - { - if (IsReading) - { - value = m_Reader.ReadRayPacked(); - } - else - { - m_Writer.WriteRayPacked(value); - } - } - - public void Serialize(ref Ray2D value) - { - if (IsReading) - { - value = m_Reader.ReadRay2DPacked(); - } - else - { - m_Writer.WriteRay2DPacked(value); - } - } - - public unsafe void Serialize(ref TEnum value) where TEnum : unmanaged, Enum - { - if (sizeof(TEnum) == sizeof(int)) - { - if (IsReading) - { - int intValue = m_Reader.ReadInt32Packed(); - value = *(TEnum*)&intValue; - } - else - { - TEnum enumValue = value; - m_Writer.WriteInt32Packed(*(int*)&enumValue); - } - } - else if (sizeof(TEnum) == sizeof(byte)) - { - if (IsReading) - { - byte intValue = m_Reader.ReadByteDirect(); - value = *(TEnum*)&intValue; - } - else - { - TEnum enumValue = value; - m_Writer.WriteByte(*(byte*)&enumValue); - } - } - else if (sizeof(TEnum) == sizeof(short)) - { - if (IsReading) - { - short intValue = m_Reader.ReadInt16Packed(); - value = *(TEnum*)&intValue; - } - else - { - TEnum enumValue = value; - m_Writer.WriteInt16Packed(*(short*)&enumValue); - } - } - else if (sizeof(TEnum) == sizeof(long)) - { - if (IsReading) - { - long intValue = m_Reader.ReadInt64Packed(); - value = *(TEnum*)&intValue; - } - else - { - TEnum enumValue = value; - m_Writer.WriteInt64Packed(*(long*)&enumValue); - } - } - else if (IsReading) - { - value = default; - } - } - - public void Serialize(ref bool[] array) - { - if (IsReading) - { - var length = m_Reader.ReadInt32Packed(); - array = length > -1 ? new bool[length] : null; - for (var i = 0; i < length; ++i) - { - array[i] = m_Reader.ReadBool(); - } - } - else - { - var length = array?.Length ?? -1; - m_Writer.WriteInt32Packed(length); - for (var i = 0; i < length; ++i) - { - m_Writer.WriteBool(array[i]); - } - } - } - - public void Serialize(ref char[] array) - { - if (IsReading) - { - var length = m_Reader.ReadInt32Packed(); - array = length > -1 ? new char[length] : null; - for (var i = 0; i < length; ++i) - { - array[i] = m_Reader.ReadCharPacked(); - } - } - else - { - var length = array?.Length ?? -1; - m_Writer.WriteInt32Packed(length); - for (var i = 0; i < length; ++i) - { - m_Writer.WriteCharPacked(array[i]); - } - } - } - - public void Serialize(ref sbyte[] array) - { - if (IsReading) - { - var length = m_Reader.ReadInt32Packed(); - array = length > -1 ? new sbyte[length] : null; - for (var i = 0; i < length; ++i) - { - array[i] = m_Reader.ReadSByte(); - } - } - else - { - var length = array?.Length ?? -1; - m_Writer.WriteInt32Packed(length); - for (var i = 0; i < length; ++i) - { - m_Writer.WriteSByte(array[i]); - } - } - } - - public void Serialize(ref byte[] array) - { - if (IsReading) - { - var length = m_Reader.ReadInt32Packed(); - array = length > -1 ? new byte[length] : null; - for (var i = 0; i < length; ++i) - { - array[i] = m_Reader.ReadByteDirect(); - } - } - else - { - var length = array?.Length ?? -1; - m_Writer.WriteInt32Packed(length); - for (var i = 0; i < length; ++i) - { - m_Writer.WriteByte(array[i]); - } - } - } - - public void Serialize(ref short[] array) - { - if (IsReading) - { - var length = m_Reader.ReadInt32Packed(); - array = length > -1 ? new short[length] : null; - for (var i = 0; i < length; ++i) - { - array[i] = m_Reader.ReadInt16Packed(); - } - } - else - { - var length = array?.Length ?? -1; - m_Writer.WriteInt32Packed(length); - for (var i = 0; i < length; ++i) - { - m_Writer.WriteInt16Packed(array[i]); - } - } - } - - public void Serialize(ref ushort[] array) - { - if (IsReading) - { - var length = m_Reader.ReadInt32Packed(); - array = length > -1 ? new ushort[length] : null; - for (var i = 0; i < length; ++i) - { - array[i] = m_Reader.ReadUInt16Packed(); - } - } - else - { - var length = array?.Length ?? -1; - m_Writer.WriteInt32Packed(length); - for (var i = 0; i < length; ++i) - { - m_Writer.WriteUInt16Packed(array[i]); - } - } - } - - public void Serialize(ref int[] array) - { - if (IsReading) - { - var length = m_Reader.ReadInt32Packed(); - array = length > -1 ? new int[length] : null; - for (var i = 0; i < length; ++i) - { - array[i] = m_Reader.ReadInt32Packed(); - } - } - else - { - var length = array?.Length ?? -1; - m_Writer.WriteInt32Packed(length); - for (var i = 0; i < length; ++i) - { - m_Writer.WriteInt32Packed(array[i]); - } - } - } - - public void Serialize(ref uint[] array) - { - if (IsReading) - { - var length = m_Reader.ReadInt32Packed(); - array = length > -1 ? new uint[length] : null; - for (var i = 0; i < length; ++i) - { - array[i] = m_Reader.ReadUInt32Packed(); - } - } - else - { - var length = array?.Length ?? -1; - m_Writer.WriteInt32Packed(length); - for (var i = 0; i < length; ++i) - { - m_Writer.WriteUInt32Packed(array[i]); - } - } - } - - public void Serialize(ref long[] array) - { - if (IsReading) - { - var length = m_Reader.ReadInt32Packed(); - array = length > -1 ? new long[length] : null; - for (var i = 0; i < length; ++i) - { - array[i] = m_Reader.ReadInt64Packed(); - } - } - else - { - var length = array?.Length ?? -1; - m_Writer.WriteInt32Packed(length); - for (var i = 0; i < length; ++i) - { - m_Writer.WriteInt64Packed(array[i]); - } - } - } - - public void Serialize(ref ulong[] array) - { - if (IsReading) - { - var length = m_Reader.ReadInt32Packed(); - array = length > -1 ? new ulong[length] : null; - for (var i = 0; i < length; ++i) - { - array[i] = m_Reader.ReadUInt64Packed(); - } - } - else - { - var length = array?.Length ?? -1; - m_Writer.WriteInt32Packed(length); - for (var i = 0; i < length; ++i) - { - m_Writer.WriteUInt64Packed(array[i]); - } - } - } - - public void Serialize(ref float[] array) - { - if (IsReading) - { - var length = m_Reader.ReadInt32Packed(); - array = length > -1 ? new float[length] : null; - for (var i = 0; i < length; ++i) - { - array[i] = m_Reader.ReadSinglePacked(); - } - } - else - { - var length = array?.Length ?? -1; - m_Writer.WriteInt32Packed(length); - for (var i = 0; i < length; ++i) - { - m_Writer.WriteSinglePacked(array[i]); - } - } - } - - public void Serialize(ref double[] array) - { - if (IsReading) - { - var length = m_Reader.ReadInt32Packed(); - array = length > -1 ? new double[length] : null; - for (var i = 0; i < length; ++i) - { - array[i] = m_Reader.ReadDoublePacked(); - } - } - else - { - var length = array?.Length ?? -1; - m_Writer.WriteInt32Packed(length); - for (var i = 0; i < length; ++i) - { - m_Writer.WriteDoublePacked(array[i]); - } - } - } - - public void Serialize(ref string[] array) - { - if (IsReading) - { - var length = m_Reader.ReadInt32Packed(); - array = length > -1 ? new string[length] : null; - for (var i = 0; i < length; ++i) - { - var isSet = m_Reader.ReadBool(); - array[i] = isSet ? m_Reader.ReadStringPacked() : null; - } - } - else - { - var length = array?.Length ?? -1; - m_Writer.WriteInt32Packed(length); - for (var i = 0; i < length; ++i) - { - var isSet = array[i] != null; - m_Writer.WriteBool(isSet); - if (isSet) - { - m_Writer.WriteStringPacked(array[i]); - } - } - } - } - - public void Serialize(ref Color[] array) - { - if (IsReading) - { - var length = m_Reader.ReadInt32Packed(); - array = length > -1 ? new Color[length] : null; - for (var i = 0; i < length; ++i) - { - array[i] = m_Reader.ReadColorPacked(); - } - } - else - { - var length = array?.Length ?? -1; - m_Writer.WriteInt32Packed(length); - for (var i = 0; i < length; ++i) - { - m_Writer.WriteColorPacked(array[i]); - } - } - } - - public void Serialize(ref Color32[] array) - { - if (IsReading) - { - var length = m_Reader.ReadInt32Packed(); - array = length > -1 ? new Color32[length] : null; - for (var i = 0; i < length; ++i) - { - array[i] = m_Reader.ReadColor32(); - } - } - else - { - var length = array?.Length ?? -1; - m_Writer.WriteInt32Packed(length); - for (var i = 0; i < length; ++i) - { - m_Writer.WriteColor32(array[i]); - } - } - } - - public void Serialize(ref Vector2[] array) - { - if (IsReading) - { - var length = m_Reader.ReadInt32Packed(); - array = length > -1 ? new Vector2[length] : null; - for (var i = 0; i < length; ++i) - { - array[i] = m_Reader.ReadVector2Packed(); - } - } - else - { - var length = array?.Length ?? -1; - m_Writer.WriteInt32Packed(length); - for (var i = 0; i < length; ++i) - { - m_Writer.WriteVector2Packed(array[i]); - } - } - } - - public void Serialize(ref Vector3[] array) - { - if (IsReading) - { - var length = m_Reader.ReadInt32Packed(); - array = length > -1 ? new Vector3[length] : null; - for (var i = 0; i < length; ++i) - { - array[i] = m_Reader.ReadVector3Packed(); - } - } - else - { - var length = array?.Length ?? -1; - m_Writer.WriteInt32Packed(length); - for (var i = 0; i < length; ++i) - { - m_Writer.WriteVector3Packed(array[i]); - } - } - } - - public void Serialize(ref Vector4[] array) - { - if (IsReading) - { - var length = m_Reader.ReadInt32Packed(); - array = length > -1 ? new Vector4[length] : null; - for (var i = 0; i < length; ++i) - { - array[i] = m_Reader.ReadVector4Packed(); - } - } - else - { - var length = array?.Length ?? -1; - m_Writer.WriteInt32Packed(length); - for (var i = 0; i < length; ++i) - { - m_Writer.WriteVector4Packed(array[i]); - } - } - } - - public void Serialize(ref Quaternion[] array) - { - if (IsReading) - { - var length = m_Reader.ReadInt32Packed(); - array = length > -1 ? new Quaternion[length] : null; - for (var i = 0; i < length; ++i) - { - array[i] = m_Reader.ReadRotationPacked(); - } - } - else - { - var length = array?.Length ?? -1; - m_Writer.WriteInt32Packed(length); - for (var i = 0; i < length; ++i) - { - m_Writer.WriteRotationPacked(array[i]); - } - } - } - - public void Serialize(ref Ray[] array) - { - if (IsReading) - { - var length = m_Reader.ReadInt32Packed(); - array = length > -1 ? new Ray[length] : null; - for (var i = 0; i < length; ++i) - { - array[i] = m_Reader.ReadRayPacked(); - } - } - else - { - var length = array?.Length ?? -1; - m_Writer.WriteInt32Packed(length); - for (var i = 0; i < length; ++i) - { - m_Writer.WriteRayPacked(array[i]); - } - } - } - - public void Serialize(ref Ray2D[] array) - { - if (IsReading) - { - var length = m_Reader.ReadInt32Packed(); - array = length > -1 ? new Ray2D[length] : null; - for (var i = 0; i < length; ++i) - { - array[i] = m_Reader.ReadRay2DPacked(); - } - } - else - { - var length = array?.Length ?? -1; - m_Writer.WriteInt32Packed(length); - for (var i = 0; i < length; ++i) - { - m_Writer.WriteRay2DPacked(array[i]); - } - } - } - - public unsafe void Serialize(ref TEnum[] array) where TEnum : unmanaged, Enum - { - int length; - if (IsReading) - { - length = m_Reader.ReadInt32Packed(); - array = length > -1 ? new TEnum[length] : null; - } - else - { - length = array?.Length ?? -1; - m_Writer.WriteInt32Packed(length); - } - - if (sizeof(TEnum) == sizeof(int)) - { - if (IsReading) - { - for (var i = 0; i < length; ++i) - { - int intValue = m_Reader.ReadInt32Packed(); - array[i] = *(TEnum*)&intValue; - } - } - else - { - for (var i = 0; i < length; ++i) - { - TEnum enumValue = array[i]; - m_Writer.WriteInt32Packed(*(int*)&enumValue); - } - } - } - else if (sizeof(TEnum) == sizeof(byte)) - { - if (IsReading) - { - for (var i = 0; i < length; ++i) - { - byte intValue = m_Reader.ReadByteDirect(); - array[i] = *(TEnum*)&intValue; - } - } - else - { - for (var i = 0; i < length; ++i) - { - TEnum enumValue = array[i]; - m_Writer.WriteByte(*(byte*)&enumValue); - } - } - } - else if (sizeof(TEnum) == sizeof(short)) - { - if (IsReading) - { - for (var i = 0; i < length; ++i) - { - short intValue = m_Reader.ReadInt16Packed(); - array[i] = *(TEnum*)&intValue; - } - } - else - { - for (var i = 0; i < length; ++i) - { - TEnum enumValue = array[i]; - m_Writer.WriteInt16Packed(*(short*)&enumValue); - } - } - } - else if (sizeof(TEnum) == sizeof(long)) - { - if (IsReading) - { - for (var i = 0; i < length; ++i) - { - long intValue = m_Reader.ReadInt64Packed(); - array[i] = *(TEnum*)&intValue; - } - } - else - { - for (var i = 0; i < length; ++i) - { - TEnum enumValue = array[i]; - m_Writer.WriteInt64Packed(*(long*)&enumValue); - } - } - } - else if (IsReading) - { - array = default; - } - } - } -} diff --git a/com.unity.netcode.gameobjects/Runtime/Serialization/NetworkSerializer.cs.meta b/com.unity.netcode.gameobjects/Runtime/Serialization/NetworkSerializer.cs.meta deleted file mode 100644 index 99e6b04031..0000000000 --- a/com.unity.netcode.gameobjects/Runtime/Serialization/NetworkSerializer.cs.meta +++ /dev/null @@ -1,11 +0,0 @@ -fileFormatVersion: 2 -guid: 371e8ff5255834ad7a262f2bf6034b21 -MonoImporter: - externalObjects: {} - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: - assetBundleName: - assetBundleVariant: diff --git a/com.unity.netcode.gameobjects/Runtime/Serialization/NetworkWriter.cs b/com.unity.netcode.gameobjects/Runtime/Serialization/NetworkWriter.cs deleted file mode 100644 index e9961fc6d4..0000000000 --- a/com.unity.netcode.gameobjects/Runtime/Serialization/NetworkWriter.cs +++ /dev/null @@ -1,1875 +0,0 @@ -#define ARRAY_WRITE_PERMISSIVE // Allow attempt to write "packed" byte array (calls WriteByteArray()) -#define ARRAY_RESOLVE_IMPLICIT // Include WriteArray() method with automatic type resolution -#define ARRAY_WRITE_PREMAP // Create a prefixed array diff mapping -#define ARRAY_DIFF_ALLOW_RESIZE // Whether or not to permit writing diffs of differently sized arrays - -using System; -using System.Diagnostics; -using System.IO; -using UnityEngine; - -namespace Unity.Netcode -{ - // Improved version of NetworkWriter - /// - /// A BinaryWriter that can do bit wise manipulation when backed by a NetworkBuffer - /// - public class NetworkWriter - { - private Stream m_Sink; - private NetworkBuffer m_NetworkSink; - - /// - /// Creates a new NetworkWriter backed by a given stream - /// - /// The stream to use for writing - public NetworkWriter(Stream stream) - { - m_Sink = stream; - m_NetworkSink = stream as NetworkBuffer; - } - - /// - /// Changes the underlying stream the writer is writing to - /// - /// The stream to write to - public void SetStream(Stream stream) - { - m_Sink = stream; - m_NetworkSink = stream as NetworkBuffer; - } - - internal Stream GetStream() - { - return m_Sink; - } - - /// - /// Writes a boxed object in a packed format - /// - /// The object to write - public void WriteObjectPacked(object value) - { - // Check unitys custom null checks - bool isNull = value == null || (value is UnityEngine.Object && ((UnityEngine.Object)value) == null); - - if (isNull || value.GetType().IsNullable()) - { - WriteBool(isNull); - - if (isNull) - { - return; - } - } - - if (SerializationManager.TrySerialize(m_Sink, value)) - { - return; - } - - if (value is Array array) - { - var elementType = value.GetType().GetElementType(); - - if (SerializationManager.IsTypeSupported(elementType)) - { - WriteInt32Packed(array.Length); - - for (int i = 0; i < array.Length; i++) - { - WriteObjectPacked(array.GetValue(i)); - } - - return; - } - } - else if (value is byte) - { - WriteByte((byte)value); - return; - } - else if (value is sbyte) - { - WriteSByte((sbyte)value); - return; - } - else if (value is ushort) - { - WriteUInt16Packed((ushort)value); - return; - } - else if (value is short) - { - WriteInt16Packed((short)value); - return; - } - else if (value is int) - { - WriteInt32Packed((int)value); - return; - } - else if (value is uint) - { - WriteUInt32Packed((uint)value); - return; - } - else if (value is long) - { - WriteInt64Packed((long)value); - return; - } - else if (value is ulong) - { - WriteUInt64Packed((ulong)value); - return; - } - else if (value is float) - { - WriteSinglePacked((float)value); - return; - } - else if (value is double) - { - WriteDoublePacked((double)value); - return; - } - else if (value is string) - { - WriteStringPacked((string)value); - return; - } - else if (value is bool) - { - WriteBool((bool)value); - return; - } - else if (value is Vector2) - { - WriteVector2Packed((Vector2)value); - return; - } - else if (value is Vector3) - { - WriteVector3Packed((Vector3)value); - return; - } - else if (value is Vector4) - { - WriteVector4Packed((Vector4)value); - return; - } - else if (value is Color) - { - WriteColorPacked((Color)value); - return; - } - else if (value is Color32) - { - WriteColor32((Color32)value); - return; - } - else if (value is Ray) - { - WriteRayPacked((Ray)value); - return; - } - else if (value is Quaternion) - { - WriteRotationPacked((Quaternion)value); - return; - } - else if (value is char) - { - WriteCharPacked((char)value); - return; - } - else if (value.GetType().IsEnum) - { - WriteInt32Packed((int)value); - return; - } - else if (value is GameObject) - { - var networkObject = ((GameObject)value).GetComponent(); - if (networkObject == null) - { - throw new ArgumentException($"{nameof(NetworkWriter)} cannot write {nameof(GameObject)} types that does not has a {nameof(NetworkObject)} component attached. {nameof(GameObject)}: {((GameObject)value).name}"); - } - - if (!networkObject.IsSpawned) - { - throw new ArgumentException($"{nameof(NetworkWriter)} cannot write {nameof(NetworkObject)} types that are not spawned. {nameof(GameObject)}: {((GameObject)value).name}"); - } - - WriteUInt64Packed(networkObject.NetworkObjectId); - return; - } - else if (value is NetworkObject) - { - if (!((NetworkObject)value).IsSpawned) - { - throw new ArgumentException($"{nameof(NetworkWriter)} cannot write {nameof(NetworkObject)} types that are not spawned. {nameof(GameObject)}: {((NetworkObject)value).gameObject.name}"); - } - - WriteUInt64Packed(((NetworkObject)value).NetworkObjectId); - return; - } - else if (value is NetworkBehaviour) - { - if (!((NetworkBehaviour)value).HasNetworkObject || !((NetworkBehaviour)value).NetworkObject.IsSpawned) - { - throw new ArgumentException($"{nameof(NetworkWriter)} cannot write {nameof(NetworkBehaviour)} types that are not spawned. {nameof(GameObject)}: {((NetworkBehaviour)value).gameObject.name}"); - } - - WriteUInt64Packed(((NetworkBehaviour)value).NetworkObjectId); - WriteUInt16Packed(((NetworkBehaviour)value).NetworkBehaviourId); - return; - } - else if (value is INetworkSerializable) - { - ((INetworkSerializable)value).NetworkSerialize(new NetworkSerializer(this)); - return; - } - - throw new ArgumentException($"{nameof(NetworkWriter)} cannot write type {value.GetType().Name} - it does not implement {nameof(INetworkSerializable)}"); - } - - /// - /// Write single-precision floating point value to the stream - /// - /// Value to write - public void WriteSingle(float value) - { - WriteUInt32(new UIntFloat - { - FloatValue = value - }.UIntValue); - } - - /// - /// Write double-precision floating point value to the stream - /// - /// Value to write - public void WriteDouble(double value) - { - WriteUInt64(new UIntFloat - { - DoubleValue = value - }.ULongValue); - } - - /// - /// Write single-precision floating point value to the stream as a varint - /// - /// Value to write - public void WriteSinglePacked(float value) - { - WriteUInt32Packed(new UIntFloat - { - FloatValue = value - }.UIntValue); - } - - /// - /// Write double-precision floating point value to the stream as a varint - /// - /// Value to write - public void WriteDoublePacked(double value) - { - WriteUInt64Packed(new UIntFloat - { - DoubleValue = value - }.ULongValue); - } - - /// - /// Convenience method that writes two non-packed Vector3 from the ray to the stream - /// - /// Ray to write - public void WriteRay(Ray ray) - { - WriteVector3(ray.origin); - WriteVector3(ray.direction); - } - - /// - /// Convenience method that writes two packed Vector3 from the ray to the stream - /// - /// Ray to write - public void WriteRayPacked(Ray ray) - { - WriteVector3Packed(ray.origin); - WriteVector3Packed(ray.direction); - } - - /// - /// Convenience method that writes two non-packed Vector2 from the ray to the stream - /// - /// Ray2D to write - public void WriteRay2D(Ray2D ray2d) - { - WriteVector2(ray2d.origin); - WriteVector2(ray2d.direction); - } - - /// - /// Convenience method that writes two packed Vector2 from the ray to the stream - /// - /// Ray2D to write - public void WriteRay2DPacked(Ray2D ray2d) - { - WriteVector2Packed(ray2d.origin); - WriteVector2Packed(ray2d.direction); - } - - /// - /// Convenience method that writes four non-varint floats from the color to the stream - /// - /// Color to write - public void WriteColor(Color color) - { - WriteSingle(color.r); - WriteSingle(color.g); - WriteSingle(color.b); - WriteSingle(color.a); - } - - /// - /// Convenience method that writes four varint floats from the color to the stream - /// - /// Color to write - public void WriteColorPacked(Color color) - { - WriteSinglePacked(color.r); - WriteSinglePacked(color.g); - WriteSinglePacked(color.b); - WriteSinglePacked(color.a); - } - - /// - /// Convenience method that writes four non-varint floats from the color to the stream - /// - /// Color32 to write - public void WriteColor32(Color32 color32) - { - WriteByte(color32.r); - WriteByte(color32.g); - WriteByte(color32.b); - WriteByte(color32.a); - } - - /// - /// Convenience method that writes two non-varint floats from the vector to the stream - /// - /// Vector to write - public void WriteVector2(Vector2 vector2) - { - WriteSingle(vector2.x); - WriteSingle(vector2.y); - } - - /// - /// Convenience method that writes two varint floats from the vector to the stream - /// - /// Vector to write - public void WriteVector2Packed(Vector2 vector2) - { - WriteSinglePacked(vector2.x); - WriteSinglePacked(vector2.y); - } - - /// - /// Convenience method that writes three non-varint floats from the vector to the stream - /// - /// Vector to write - public void WriteVector3(Vector3 vector3) - { - WriteSingle(vector3.x); - WriteSingle(vector3.y); - WriteSingle(vector3.z); - } - - /// - /// Convenience method that writes three varint floats from the vector to the stream - /// - /// Vector to write - public void WriteVector3Packed(Vector3 vector3) - { - WriteSinglePacked(vector3.x); - WriteSinglePacked(vector3.y); - WriteSinglePacked(vector3.z); - } - - /// - /// Convenience method that writes four non-varint floats from the vector to the stream - /// - /// Vector to write - public void WriteVector4(Vector4 vector4) - { - WriteSingle(vector4.x); - WriteSingle(vector4.y); - WriteSingle(vector4.z); - WriteSingle(vector4.w); - } - - /// - /// Convenience method that writes four varint floats from the vector to the stream - /// - /// Vector to write - public void WriteVector4Packed(Vector4 vector4) - { - WriteSinglePacked(vector4.x); - WriteSinglePacked(vector4.y); - WriteSinglePacked(vector4.z); - WriteSinglePacked(vector4.w); - } - - /// - /// Write a single-precision floating point value to the stream. The value is between (inclusive) the minValue and maxValue. - /// - /// Value to write - /// Minimum value that this value could be - /// Maximum possible value that this could be - /// How many bytes the compressed result should occupy. Must be between 1 and 4 (inclusive) - public void WriteRangedSingle(float value, float minValue, float maxValue, int bytes) - { - if (bytes < 1 || bytes > 4) - { - throw new ArgumentOutOfRangeException("Result must occupy between 1 and 4 bytes!"); - } - - if (value < minValue || value > maxValue) - { - throw new ArgumentOutOfRangeException("Given value does not match the given constraints!"); - } - - uint result = (uint)((value - minValue) / (maxValue - minValue) * ((0x100 * bytes) - 1)); - for (int i = 0; i < bytes; ++i) - { - m_Sink.WriteByte((byte)(result >> (i << 3))); - } - } - - /// - /// Write a double-precision floating point value to the stream. The value is between (inclusive) the minValue and maxValue. - /// - /// Value to write - /// Minimum value that this value could be - /// Maximum possible value that this could be - /// How many bytes the compressed result should occupy. Must be between 1 and 8 (inclusive) - public void WriteRangedDouble(double value, double minValue, double maxValue, int bytes) - { - if (bytes < 1 || bytes > 8) - { - throw new ArgumentOutOfRangeException("Result must occupy between 1 and 8 bytes!"); - } - - if (value < minValue || value > maxValue) - { - throw new ArgumentOutOfRangeException("Given value does not match the given constraints!"); - } - - ulong result = (ulong)((value - minValue) / (maxValue - minValue) * ((0x100 * bytes) - 1)); - for (int i = 0; i < bytes; ++i) - { - WriteByte((byte)(result >> (i << 3))); - } - } - - /// - /// Writes the rotation to the stream. - /// - /// Rotation to write - public void WriteRotationPacked(Quaternion rotation) - { - if (Mathf.Sign(rotation.w) < 0) - { - WriteSinglePacked(-rotation.x); - WriteSinglePacked(-rotation.y); - WriteSinglePacked(-rotation.z); - } - else - { - WriteSinglePacked(rotation.x); - WriteSinglePacked(rotation.y); - WriteSinglePacked(rotation.z); - } - } - - /// - /// Writes the rotation to the stream. - /// - /// Rotation to write - public void WriteRotation(Quaternion rotation) - { - WriteSingle(rotation.x); - WriteSingle(rotation.y); - WriteSingle(rotation.z); - WriteSingle(rotation.w); - } - - /// - /// Writes a single bit - /// - /// - public void WriteBit(bool bit) - { - if (m_NetworkSink == null) - { - throw new InvalidOperationException($"Cannot write bits on a non-{nameof(NetworkBuffer)} stream"); - } - - m_NetworkSink.WriteBit(bit); - } - - /// - /// Writes a bool as a single bit - /// - /// - public void WriteBool(bool value) - { - if (m_NetworkSink == null) - { - m_Sink.WriteByte(value ? (byte)1 : (byte)0); - } - else - { - // WriteBit(value); // old (buggy) - WriteByte(value ? (byte)1 : (byte)0); // new (hotfix) - } - } - - /// - /// Writes pad bits to make the underlying stream aligned - /// - public void WritePadBits() - { - while (!m_NetworkSink.BitAligned) - { - WriteBit(false); - } - } - - /// - /// Write the lower half (lower nibble) of a byte. - /// - /// Value containing nibble to write. - public void WriteNibble(byte value) => WriteBits(value, 4); - - /// - /// Write either the upper or lower nibble of a byte to the stream. - /// - /// Value holding the nibble - /// Whether or not the upper nibble should be written. True to write the four high bits, else writes the four low bits. - public void WriteNibble(byte value, bool upper) => WriteNibble((byte)(value >> (upper ? 4 : 0))); - - /// - /// Write s certain amount of bits to the stream. - /// - /// Value to get bits from. - /// Amount of bits to write - public void WriteBits(ulong value, int bitCount) - { - if (m_NetworkSink == null) - { - throw new InvalidOperationException($"Cannot write bits on a non-{nameof(NetworkBuffer)} stream"); - } - - if (bitCount > 64) - { - throw new ArgumentOutOfRangeException(nameof(bitCount), "Cannot read more than 64 bits from a 64-bit value!"); - } - - if (bitCount < 0) - { - throw new ArgumentOutOfRangeException(nameof(bitCount), "Cannot read fewer than 0 bits!"); - } - - int count = 0; - for (; count + 8 < bitCount; count += 8) - { - m_NetworkSink.WriteByte((byte)(value >> count)); - } - - for (; count < bitCount; ++count) - { - m_NetworkSink.WriteBit((value & (1UL << count)) != 0); - } - } - - - /// - /// Write bits to stream. - /// - /// Value to get bits from. - /// Amount of bits to write. - public void WriteBits(byte value, int bitCount) - { - if (m_NetworkSink == null) - { - throw new InvalidOperationException($"Cannot write bits on a non-{nameof(NetworkBuffer)} stream"); - } - - for (int i = 0; i < bitCount; ++i) - { - m_NetworkSink.WriteBit(((value >> i) & 1) != 0); - } - } - - /// - /// Write a signed byte to the stream. - /// - /// Value to write - public void WriteSByte(sbyte value) => WriteByte((byte)value); - - /// - /// Write a single character to the stream. - /// - /// Character to write - public void WriteChar(char c) => WriteUInt16(c); - - /// - /// Write an unsigned short (UInt16) to the stream. - /// - /// Value to write - public void WriteUInt16(ushort value) - { - m_Sink.WriteByte((byte)value); - m_Sink.WriteByte((byte)(value >> 8)); - } - - /// - /// Write a signed short (Int16) to the stream. - /// - /// Value to write - public void WriteInt16(short value) => WriteUInt16((ushort)value); - - /// - /// Write an unsigned int (UInt32) to the stream. - /// - /// Value to write - public void WriteUInt32(uint value) - { - m_Sink.WriteByte((byte)value); - m_Sink.WriteByte((byte)(value >> 8)); - m_Sink.WriteByte((byte)(value >> 16)); - m_Sink.WriteByte((byte)(value >> 24)); - } - - /// - /// Write a signed int (Int32) to the stream. - /// - /// Value to write - public void WriteInt32(int value) => WriteUInt32((uint)value); - - /// - /// Write an unsigned long (UInt64) to the stream. - /// - /// Value to write - public void WriteUInt64(ulong value) - { - m_Sink.WriteByte((byte)value); - m_Sink.WriteByte((byte)(value >> 8)); - m_Sink.WriteByte((byte)(value >> 16)); - m_Sink.WriteByte((byte)(value >> 24)); - m_Sink.WriteByte((byte)(value >> 32)); - m_Sink.WriteByte((byte)(value >> 40)); - m_Sink.WriteByte((byte)(value >> 48)); - m_Sink.WriteByte((byte)(value >> 56)); - } - - /// - /// Write a signed long (Int64) to the stream. - /// - /// Value to write - public void WriteInt64(long value) => WriteUInt64((ulong)value); - - /// - /// Write a signed short (Int16) as a ZigZag encoded varint to the stream. - /// - /// Value to write - public void WriteInt16Packed(short value) => WriteInt64Packed(value); - - /// - /// Write an unsigned short (UInt16) as a varint to the stream. - /// - /// Value to write - public void WriteUInt16Packed(ushort value) => WriteUInt64Packed(value); - - /// - /// Write a two-byte character as a varint to the stream. - /// - /// Value to write - public void WriteCharPacked(char c) => WriteUInt16Packed(c); - - /// - /// Write a signed int (Int32) as a ZigZag encoded varint to the stream. - /// - /// Value to write - public void WriteInt32Packed(int value) => WriteInt64Packed(value); - - /// - /// Write an unsigned int (UInt32) as a varint to the stream. - /// - /// Value to write - public void WriteUInt32Packed(uint value) => WriteUInt64Packed(value); - - /// - /// Write a signed long (Int64) as a ZigZag encoded varint to the stream. - /// - /// Value to write - public void WriteInt64Packed(long value) => WriteUInt64Packed(Arithmetic.ZigZagEncode(value)); - - /// - /// Write an unsigned long (UInt64) as a varint to the stream. - /// - /// Value to write - public void WriteUInt64Packed(ulong value) - { - if (value <= 240) - { - WriteULongByte(value); - } - else if (value <= 2287) - { - WriteULongByte(((value - 240) >> 8) + 241); - WriteULongByte(value - 240); - } - else if (value <= 67823) - { - WriteULongByte(249); - WriteULongByte((value - 2288) >> 8); - WriteULongByte(value - 2288); - } - else - { - ulong header = 255; - ulong match = 0x00FF_FFFF_FFFF_FFFFUL; - while (value <= match) - { - --header; - match >>= 8; - } - - WriteULongByte(header); - int max = (int)(header - 247); - for (int i = 0; i < max; ++i) - { - WriteULongByte(value >> (i << 3)); - } - } - } - - /// - /// Write a byte (in an int format) to the stream. - /// - /// Value to write - private void WriteIntByte(int value) => WriteByte((byte)value); - - /// - /// Write a byte (in a ulong format) to the stream. - /// - /// Value to write - private void WriteULongByte(ulong byteValue) => WriteByte((byte)byteValue); - - /// - /// Write a byte to the stream. - /// - /// Value to write - public void WriteByte(byte value) - { - m_Sink.WriteByte(value); - } - - // As it turns out, strings cannot be treated as char arrays, since strings use pointers to store data rather than C# arrays - /// - /// Writes a string - /// - /// The string to write - /// Whether or not to use one byte per character. This will only allow ASCII - public void WriteString(string s, bool oneByteChars = false) - { - WriteUInt32Packed((uint)s.Length); - int target = s.Length; - for (int i = 0; i < target; ++i) - { - if (oneByteChars) - { - WriteByte((byte)s[i]); - } - else - { - WriteChar(s[i]); - } - } - } - - /// - /// Writes a string in a packed format - /// - /// - public void WriteStringPacked(string s) - { - WriteUInt32Packed((uint)s.Length); - int target = s.Length; - for (int i = 0; i < target; ++i) - { - WriteCharPacked(s[i]); - } - } - - /// - /// Writes the diff between two strings - /// - /// The new array - /// The previous array to use for diff - /// Whether or not to use single byte chars. This will only allow ASCII characters - public void WriteStringDiff(string write, string compare, bool oneByteChars = false) - { -#if !ARRAY_DIFF_ALLOW_RESIZE - if (write.Length != compare.Length) throw new ArgumentException("Mismatched string lengths"); -#endif - WriteUInt32Packed((uint)write.Length); - - // Premapping - int target; -#if ARRAY_WRITE_PREMAP -#if ARRAY_DIFF_ALLOW_RESIZE - target = Math.Min(write.Length, compare.Length); -#else - target = a1.Length; -#endif - for (int i = 0; i < target; ++i) - { - WriteBit(write[i] != compare[i]); - } -#else - target = write.Length; -#endif - for (int i = 0; i < target; ++i) - { - bool b = write[i] != compare[i]; -#if !ARRAY_WRITE_PREMAP - WriteBit(!b); -#endif - if (b) - { - if (oneByteChars) - { - WriteByte((byte)write[i]); - } - else - { - WriteChar(write[i]); - } - } - } - } - - /// - /// Writes the diff between two strings in a packed format - /// - /// The new string - /// The previous string to use for diff - public void WriteStringPackedDiff(string write, string compare) - { -#if !ARRAY_DIFF_ALLOW_RESIZE - if (write.Length != compare.Length) throw new ArgumentException("Mismatched string lengths"); -#endif - WriteUInt32Packed((uint)write.Length); - - // Premapping - int target; -#if ARRAY_WRITE_PREMAP -#if ARRAY_DIFF_ALLOW_RESIZE - target = Math.Min(write.Length, compare.Length); -#else - target = a1.Length; -#endif - for (int i = 0; i < target; ++i) - { - WriteBit(write[i] != compare[i]); - } -#else - target = write.Length; -#endif - for (int i = 0; i < target; ++i) - { - bool b = write[i] != compare[i]; -#if !ARRAY_WRITE_PREMAP - WriteBit(!b); -#endif - if (b) - { - WriteCharPacked(write[i]); - } - } - } - - private void CheckLengths(Array a1, Array a2) { } - - [Conditional("ARRAY_WRITE_PREMAP")] - private void WritePremap(Array a1, Array a2) - { - long target; - target = Math.Min(a1.LongLength, a2.LongLength); - for (long i = 0; i < target; ++i) - { - WriteBit(!a1.GetValue(i).Equals(a2.GetValue(i))); - } - // TODO: Byte-align here - } - - private ulong WriteArraySize(Array a1, Array a2, long length) - { - ulong write = (ulong)(length >= 0 ? length : a1.LongLength); - if (length < 0) - { - if (length > a1.LongLength) - { - throw new IndexOutOfRangeException("Cannot write more data than is available"); - } - - WriteUInt64Packed(write); - } - - return write; - } - - /// - /// Writes a byte array - /// - /// The array to write - /// The amount of elements to write - public void WriteByteArray(byte[] b, long count = -1) - { - ulong target = WriteArraySize(b, null, count); - for (ulong i = 0; i < target; ++i) - { - m_Sink.WriteByte(b[i]); - } - } - - - /// - /// WriteBytes - /// Takes a byte array buffer and writes the bytes into the currently assigned stream at its current position - /// This reduces the iterations required to write (n) bytes by a factor of up to 8x less iterations. - /// for blocks of memory that exceed 8 bytes in size. It also doesn't require passing arrays over the stack. - /// Ex: - /// 256 bytes iterates 32 times vs 256 times ------------------------- 8x less iterations - /// 64 bytes iterates 8 times vs 64 times----------------------------- 8x less iterations - /// 22 bytes iterates 5 times ( 2-Int64 1-Int32 2-Byte) vs 22 times -- 4x less iterations - /// - /// - /// - public void WriteBytes(byte[] buffer, long targetSize, int offset = 0) - { - long largeInt64Blocks = targetSize >> 3; //Divide by 8 - //8 Byte blocks - for (long i = 0; i < largeInt64Blocks; i++) - { - WriteInt64(BitConverter.ToInt64(buffer, offset)); - offset += 8; - } - - long blockOffset = largeInt64Blocks * 8; - long remainder = targetSize - blockOffset; - - //4 byte block - if (remainder >= 4) - { - WriteInt32(BitConverter.ToInt32(buffer, offset)); - offset += 4; - blockOffset += 4; - } - - //Remainder of bytes < 4 - if (targetSize - blockOffset > 0) - { - for (long i = 0; i < (targetSize - blockOffset); i++) - { - WriteByte(buffer[offset + i]); - } - } - } - - - /// - /// ReadAndWrite - /// Uses a NetworkReader to read (targetSize) bytes and will write (targetSize) bytes to current stream. - /// This reduces the iterations required to write (n) bytes by a factor of up to 8x less iterations. - /// for blocks of memory that exceed 8 bytes in size. It also doesn't require passing arrays over the stack. - /// Ex: - /// 256 bytes iterates 32 times vs 256 times ------------------------- 8x less iterations - /// 64 bytes iterates 8 times vs 64 times----------------------------- 8x less iterations - /// 22 bytes iterates 5 times ( 2-Int64 1-Int32 2-Byte) vs 22 times -- 4x less iterations - /// - /// - /// - public void ReadAndWrite(NetworkReader sourceReader, long targetSize) - { - long largeInt64Blocks = targetSize >> 3; //Divide by 8 - - //8 Byte blocks - for (long i = 0; i < largeInt64Blocks; i++) - { - WriteInt64(sourceReader.ReadInt64()); - } - - long offset = largeInt64Blocks * 8; - long remainder = targetSize - offset; - - //4 byte block - if (remainder >= 4) - { - WriteInt32(sourceReader.ReadInt32()); - offset += 4; - } - - //Remainder of bytes < 4 - if (targetSize - offset > 0) - { - for (long i = 0; i < (targetSize - offset); i++) - { - WriteByte(sourceReader.ReadByteDirect()); - } - } - } - - /// - /// Writes the diff between two byte arrays - /// - /// The new array - /// The previous array to use for diff - /// The amount of elements to write - public void WriteByteArrayDiff(byte[] write, byte[] compare, long count = -1) - { - CheckLengths(write, compare); - long target = (long)WriteArraySize(write, compare, count); - WritePremap(write, compare); - for (long i = 0; i < target; ++i) - { - bool b = i >= compare.LongLength || write[i] != compare[i]; -#if !ARRAY_WRITE_PREMAP - WriteBit(b); -#endif - if (b) - { - WriteByte(write[i]); - } - } - } - - /// - /// Writes a short array - /// - /// The array to write - /// The amount of elements to write - public void WriteShortArray(short[] b, long count = -1) - { - ulong target = WriteArraySize(b, null, count); - for (ulong i = 0; i < target; ++i) - { - WriteInt16(b[i]); - } - } - - /// - /// Writes the diff between two short arrays - /// - /// The new array - /// The previous array to use for diff - /// The amount of elements to write - public void WriteShortArrayDiff(short[] write, short[] compare, long count = -1) - { - CheckLengths(write, compare); - long target = (long)WriteArraySize(write, compare, count); - WritePremap(write, compare); - for (long i = 0; i < target; ++i) - { - bool b = i >= compare.LongLength || write[i] != compare[i]; -#if !ARRAY_WRITE_PREMAP - WriteBit(!b); -#endif - if (b) - { - WriteInt16(write[i]); - } - } - } - - /// - /// Writes a ushort array - /// - /// The array to write - /// The amount of elements to write - public void WriteUShortArray(ushort[] b, long count = -1) - { - ulong target = WriteArraySize(b, null, count); - for (ulong i = 0; i < target; ++i) - { - WriteUInt16(b[i]); - } - } - - /// - /// Writes the diff between two ushort arrays - /// - /// The new array - /// The previous array to use for diff - /// The amount of elements to write - public void WriteUShortArrayDiff(ushort[] write, ushort[] compare, long count = -1) - { - CheckLengths(write, compare); - long target = (long)WriteArraySize(write, compare, count); - WritePremap(write, compare); - for (long i = 0; i < target; ++i) - { - bool b = i >= compare.LongLength || write[i] != compare[i]; -#if !ARRAY_WRITE_PREMAP - WriteBit(!b); -#endif - if (b) - { - WriteUInt16(write[i]); - } - } - } - - /// - /// Writes a char array - /// - /// The array to write - /// The amount of elements to write - public void WriteCharArray(char[] b, long count = -1) - { - ulong target = WriteArraySize(b, null, count); - for (ulong i = 0; i < target; ++i) - { - WriteChar(b[i]); - } - } - - /// - /// Writes the diff between two char arrays - /// - /// The new array - /// The previous array to use for diff - /// The amount of elements to write - public void WriteCharArrayDiff(char[] write, char[] compare, long count = -1) - { - CheckLengths(write, compare); - long target = (long)WriteArraySize(write, compare, count); - WritePremap(write, compare); - for (long i = 0; i < target; ++i) - { - bool b = i >= compare.LongLength || write[i] != compare[i]; -#if !ARRAY_WRITE_PREMAP - WriteBit(!b); -#endif - if (b) - { - WriteChar(write[i]); - } - } - } - - /// - /// Writes a int array - /// - /// The array to write - /// The amount of elements to write - public void WriteIntArray(int[] b, long count = -1) - { - ulong target = WriteArraySize(b, null, count); - for (ulong i = 0; i < target; ++i) - { - WriteInt32(b[i]); - } - } - - /// - /// Writes the diff between two int arrays - /// - /// The new array - /// The previous array to use for diff - /// The amount of elements to write - public void WriteIntArrayDiff(int[] write, int[] compare, long count = -1) - { - CheckLengths(write, compare); - long target = (long)WriteArraySize(write, compare, count); - WritePremap(write, compare); - for (long i = 0; i < target; ++i) - { - bool b = i >= compare.LongLength || write[i] != compare[i]; -#if !ARRAY_WRITE_PREMAP - WriteBit(!b); -#endif - if (b) - { - WriteInt32(write[i]); - } - } - } - - /// - /// Writes a uint array - /// - /// The array to write - /// The amount of elements to write - public void WriteUIntArray(uint[] b, long count = -1) - { - ulong target = WriteArraySize(b, null, count); - for (ulong i = 0; i < target; ++i) - { - WriteUInt32(b[i]); - } - } - - /// - /// Writes the diff between two uint arrays - /// - /// The new array - /// The previous array to use for diff - /// The amount of elements to write - public void WriteUIntArrayDiff(uint[] write, uint[] compare, long count = -1) - { - CheckLengths(write, compare); - long target = (long)WriteArraySize(write, compare, count); - WritePremap(write, compare); - for (long i = 0; i < target; ++i) - { - bool b = i >= compare.LongLength || write[i] != compare[i]; -#if !ARRAY_WRITE_PREMAP - WriteBit(!b); -#endif - if (b) - { - WriteUInt32(write[i]); - } - } - } - - /// - /// Writes a long array - /// - /// The array to write - /// The amount of elements to write - public void WriteLongArray(long[] b, long count = -1) - { - ulong target = WriteArraySize(b, null, count); - for (ulong i = 0; i < target; ++i) - { - WriteInt64(b[i]); - } - } - - /// - /// Writes the diff between two long arrays - /// - /// The new array - /// The previous array to use for diff - /// The amount of elements to write - public void WriteLongArrayDiff(long[] write, long[] compare, long count = -1) - { - CheckLengths(write, compare); - long target = (long)WriteArraySize(write, compare, count); - WritePremap(write, compare); - for (long i = 0; i < target; ++i) - { - bool b = i >= compare.LongLength || write[i] != compare[i]; -#if !ARRAY_WRITE_PREMAP - WriteBit(!b); -#endif - if (b) - { - WriteInt64(write[i]); - } - } - } - - /// - /// Writes a ulong array - /// - /// The array to write - /// The amount of elements to write - public void WriteULongArray(ulong[] b, long count = -1) - { - ulong target = WriteArraySize(b, null, count); - for (ulong i = 0; i < target; ++i) - { - WriteUInt64(b[i]); - } - } - - /// - /// Writes the diff between two ulong arrays - /// - /// The new array - /// The previous array to use for diff - /// The amount of elements to write - public void WriteULongArrayDiff(ulong[] write, ulong[] compare, long count = -1) - { - CheckLengths(write, compare); - long target = (long)WriteArraySize(write, compare, count); - WritePremap(write, compare); - for (long i = 0; i < target; ++i) - { - bool b = i >= compare.LongLength || write[i] != compare[i]; -#if !ARRAY_WRITE_PREMAP - WriteBit(!b); -#endif - if (b) - { - WriteUInt64(write[i]); - } - } - } - - /// - /// Writes a float array - /// - /// The array to write - /// The amount of elements to write - public void WriteFloatArray(float[] b, long count = -1) - { - ulong target = WriteArraySize(b, null, count); - for (ulong i = 0; i < target; ++i) - { - WriteSingle(b[i]); - } - } - - /// - /// Writes the diff between two float arrays - /// - /// The new array - /// The previous array to use for diff - /// The amount of elements to write - public void WriteFloatArrayDiff(float[] write, float[] compare, long count = -1) - { - CheckLengths(write, compare); - long target = (long)WriteArraySize(write, compare, count); - WritePremap(write, compare); - for (long i = 0; i < target; ++i) - { - bool b = i >= compare.LongLength || write[i] != compare[i]; -#if !ARRAY_WRITE_PREMAP - WriteBit(!b); -#endif - if (b) - { - WriteSingle(write[i]); - } - } - } - - /// - /// Writes a double array - /// - /// The array to write - /// The amount of elements to write - public void WriteDoubleArray(double[] b, long count = -1) - { - ulong target = WriteArraySize(b, null, count); - for (ulong i = 0; i < target; ++i) - { - WriteDouble(b[i]); - } - } - - /// - /// Writes the diff between two double arrays - /// - /// The new array - /// The previous array to use for diff - /// The amount of elements to write - public void WriteDoubleArrayDiff(double[] write, double[] compare, long count = -1) - { - CheckLengths(write, compare); - long target = (long)WriteArraySize(write, compare, count); - WritePremap(write, compare); - for (long i = 0; i < target; ++i) - { - bool b = i >= compare.LongLength || write[i] != compare[i]; -#if !ARRAY_WRITE_PREMAP - WriteBit(!b); -#endif - if (b) - { - WriteDouble(write[i]); - } - } - } - - - // Packed arrays -#if ARRAY_RESOLVE_IMPLICIT - /// - /// Writes an array in a packed format - /// - /// The array to write - /// The amount of elements to write - public void WriteArrayPacked(Array a, long count = -1) - { - var arrayType = a.GetType(); - - -#if ARRAY_WRITE_PERMISSIVE - if (arrayType == typeof(byte[])) - { - WriteByteArray(a as byte[], count); - } - else -#endif - if (arrayType == typeof(short[])) - { - WriteShortArrayPacked(a as short[], count); - } - else if (arrayType == typeof(ushort[])) - { - WriteUShortArrayPacked(a as ushort[], count); - } - else if (arrayType == typeof(char[])) - { - WriteCharArrayPacked(a as char[], count); - } - else if (arrayType == typeof(int[])) - { - WriteIntArrayPacked(a as int[], count); - } - else if (arrayType == typeof(uint[])) - { - WriteUIntArrayPacked(a as uint[], count); - } - else if (arrayType == typeof(long[])) - { - WriteLongArrayPacked(a as long[], count); - } - else if (arrayType == typeof(ulong[])) - { - WriteULongArrayPacked(a as ulong[], count); - } - else if (arrayType == typeof(float[])) - { - WriteFloatArrayPacked(a as float[], count); - } - else if (arrayType == typeof(double[])) - { - WriteDoubleArrayPacked(a as double[], count); - } - else - { - throw new InvalidDataException("Unknown array type! Please serialize manually!"); - } - } - - /// - /// Writes the diff between two arrays in a packed format - /// - /// The new array - /// The previous array to use for diff - /// The amount of elements to write - public void WriteArrayPackedDiff(Array write, Array compare, long count = -1) - { - var arrayType = write.GetType(); - if (arrayType != compare.GetType()) - { - throw new ArrayTypeMismatchException("Cannot write diff of two differing array types"); - } - -#if ARRAY_WRITE_PERMISSIVE - if (arrayType == typeof(byte[])) - { - WriteByteArrayDiff(write as byte[], compare as byte[], count); - } - else -#endif - if (arrayType == typeof(short[])) - { - WriteShortArrayPackedDiff(write as short[], compare as short[], count); - } - else if (arrayType == typeof(ushort[])) - { - WriteUShortArrayPackedDiff(write as ushort[], compare as ushort[], count); - } - else if (arrayType == typeof(char[])) - { - WriteCharArrayPackedDiff(write as char[], compare as char[], count); - } - else if (arrayType == typeof(int[])) - { - WriteIntArrayPackedDiff(write as int[], compare as int[], count); - } - else if (arrayType == typeof(uint[])) - { - WriteUIntArrayPackedDiff(write as uint[], compare as uint[], count); - } - else if (arrayType == typeof(long[])) - { - WriteLongArrayPackedDiff(write as long[], compare as long[], count); - } - else if (arrayType == typeof(ulong[])) - { - WriteULongArrayPackedDiff(write as ulong[], compare as ulong[], count); - } - else if (arrayType == typeof(float[])) - { - WriteFloatArrayPackedDiff(write as float[], compare as float[], count); - } - else if (arrayType == typeof(double[])) - { - WriteDoubleArrayPackedDiff(write as double[], compare as double[], count); - } - else - { - throw new InvalidDataException("Unknown array type! Please serialize manually!"); - } - } -#endif - - /// - /// Writes a short array in a packed format - /// - /// The array to write - /// The amount of elements to write - public void WriteShortArrayPacked(short[] b, long count = -1) - { - ulong target = WriteArraySize(b, null, count); - for (ulong i = 0; i < target; ++i) - { - WriteInt16Packed(b[i]); - } - } - - /// - /// Writes the diff between two short arrays in a packed format - /// - /// The new array - /// The previous array to use for diff - /// The amount of elements to write - public void WriteShortArrayPackedDiff(short[] write, short[] compare, long count = -1) - { - CheckLengths(write, compare); - long target = (long)WriteArraySize(write, compare, count); - WritePremap(write, compare); - for (long i = 0; i < target; ++i) - { - bool b = i >= compare.LongLength || write[i] != compare[i]; -#if !ARRAY_WRITE_PREMAP - WriteBit(!b); -#endif - if (b) - { - WriteInt16Packed(write[i]); - } - } - } - - /// - /// Writes a ushort array in a packed format - /// - /// The array to write - /// The amount of elements to write - public void WriteUShortArrayPacked(ushort[] b, long count = -1) - { - ulong target = WriteArraySize(b, null, count); - for (ulong i = 0; i < target; ++i) - { - WriteUInt16Packed(b[i]); - } - } - - /// - /// Writes the diff between two ushort arrays in a packed format - /// - /// The new array - /// The previous array to use for diff - /// The amount of elements to write - public void WriteUShortArrayPackedDiff(ushort[] write, ushort[] compare, long count = -1) - { - CheckLengths(write, compare); - long target = (long)WriteArraySize(write, compare, count); - WritePremap(write, compare); - for (long i = 0; i < target; ++i) - { - bool b = i >= compare.LongLength || write[i] != compare[i]; -#if !ARRAY_WRITE_PREMAP - WriteBit(!b); -#endif - if (b) - { - WriteUInt16Packed(write[i]); - } - } - } - - /// - /// Writes a char array in a packed format - /// - /// The array to write - /// The amount of elements to write - public void WriteCharArrayPacked(char[] b, long count = -1) - { - ulong target = WriteArraySize(b, null, count); - for (ulong i = 0; i < target; ++i) - { - WriteCharPacked(b[i]); - } - } - - /// - /// Writes the diff between two char arrays in a packed format - /// - /// The new array - /// The previous array to use for diff - /// The amount of elements to write - public void WriteCharArrayPackedDiff(char[] write, char[] compare, long count = -1) - { - CheckLengths(write, compare); - long target = (long)WriteArraySize(write, compare, count); - WritePremap(write, compare); - for (long i = 0; i < target; ++i) - { - bool b = i >= compare.LongLength || write[i] != compare[i]; -#if !ARRAY_WRITE_PREMAP - WriteBit(!b); -#endif - if (b) - { - WriteCharPacked(write[i]); - } - } - } - - /// - /// Writes a int array in a packed format - /// - /// The array to write - /// The amount of elements to write - public void WriteIntArrayPacked(int[] b, long count = -1) - { - ulong target = WriteArraySize(b, null, count); - for (ulong i = 0; i < target; ++i) - { - WriteInt32Packed(b[i]); - } - } - - /// - /// Writes the diff between two int arrays - /// - /// The new array - /// The previous array to use for diff - /// The amount of elements to write - public void WriteIntArrayPackedDiff(int[] write, int[] compare, long count = -1) - { - CheckLengths(write, compare); - long target = (long)WriteArraySize(write, compare, count); - WritePremap(write, compare); - for (long i = 0; i < target; ++i) - { - bool b = i >= compare.LongLength || write[i] != compare[i]; -#if !ARRAY_WRITE_PREMAP - WriteBit(!b); -#endif - if (b) - { - WriteInt32Packed(write[i]); - } - } - } - - /// - /// Writes a uint array in a packed format - /// - /// The array to write - /// The amount of elements to write - public void WriteUIntArrayPacked(uint[] b, long count = -1) - { - ulong target = WriteArraySize(b, null, count); - for (ulong i = 0; i < target; ++i) - { - WriteUInt32Packed(b[i]); - } - } - - /// - /// Writes the diff between two uing arrays in a packed format - /// - /// The new array - /// The previous array to use for diff - /// The amount of elements to write - public void WriteUIntArrayPackedDiff(uint[] write, uint[] compare, long count = -1) - { - CheckLengths(write, compare); - long target = (long)WriteArraySize(write, compare, count); - WritePremap(write, compare); - for (long i = 0; i < target; ++i) - { - bool b = i >= compare.LongLength || write[i] != compare[i]; -#if !ARRAY_WRITE_PREMAP - WriteBit(!b); -#endif - if (b) - { - WriteUInt32Packed(write[i]); - } - } - } - - /// - /// Writes a long array in a packed format - /// - /// The array to write - /// The amount of elements to write - public void WriteLongArrayPacked(long[] b, long count = -1) - { - ulong target = WriteArraySize(b, null, count); - for (ulong i = 0; i < target; ++i) - { - WriteInt64Packed(b[i]); - } - } - - /// - /// Writes the diff between two long arrays in a packed format - /// - /// The new array - /// The previous array to use for diff - /// The amount of elements to write - public void WriteLongArrayPackedDiff(long[] write, long[] compare, long count = -1) - { - CheckLengths(write, compare); - long target = (long)WriteArraySize(write, compare, count); - WritePremap(write, compare); - for (long i = 0; i < target; ++i) - { - bool b = i >= compare.LongLength || write[i] != compare[i]; -#if !ARRAY_WRITE_PREMAP - WriteBit(!b); -#endif - if (b) - { - WriteInt64Packed(write[i]); - } - } - } - - /// - /// Writes a ulong array in a packed format - /// - /// The array to write - /// The amount of elements to write - public void WriteULongArrayPacked(ulong[] b, long count = -1) - { - ulong target = WriteArraySize(b, null, count); - for (ulong i = 0; i < target; ++i) - { - WriteUInt64Packed(b[i]); - } - } - - /// - /// Writes the diff between two ulong arrays in a packed format - /// - /// The new array - /// The previous array to use for diff - /// The amount of elements to write - public void WriteULongArrayPackedDiff(ulong[] write, ulong[] compare, long count = -1) - { - CheckLengths(write, compare); - long target = (long)WriteArraySize(write, compare, count); - WritePremap(write, compare); - for (long i = 0; i < target; ++i) - { - bool b = i >= compare.LongLength || write[i] != compare[i]; -#if !ARRAY_WRITE_PREMAP - WriteBit(!b); -#endif - if (b) - { - WriteUInt64Packed(write[i]); - } - } - } - - /// - /// Writes a float array in a packed format - /// - /// The array to write - /// The amount of elements to write - public void WriteFloatArrayPacked(float[] b, long count = -1) - { - ulong target = WriteArraySize(b, null, count); - for (ulong i = 0; i < target; ++i) - { - WriteSinglePacked(b[i]); - } - } - - /// - /// Writes the diff between two float arrays in a packed format - /// - /// The new array - /// The previous array to use for diff - /// The amount of elements to write - public void WriteFloatArrayPackedDiff(float[] write, float[] compare, long count = -1) - { - CheckLengths(write, compare); - long target = (long)WriteArraySize(write, compare, count); - WritePremap(write, compare); - for (long i = 0; i < target; ++i) - { - bool b = i >= compare.LongLength || write[i] != compare[i]; -#if !ARRAY_WRITE_PREMAP - WriteBit(!b); -#endif - if (b) - { - WriteSinglePacked(write[i]); - } - } - } - - /// - /// Writes a double array in a packed format - /// - /// The array to write - /// The amount of elements to write - public void WriteDoubleArrayPacked(double[] b, long count = -1) - { - ulong target = WriteArraySize(b, null, count); - for (ulong i = 0; i < target; ++i) - { - WriteDoublePacked(b[i]); - } - } - - /// - /// Writes the diff between two double arrays in a packed format - /// - /// The new array - /// The previous array to use for diff - /// The amount of elements to write - public void WriteDoubleArrayPackedDiff(double[] write, double[] compare, long count = -1) - { - CheckLengths(write, compare); - long target = (long)WriteArraySize(write, compare, count); - WritePremap(write, compare); - for (long i = 0; i < target; ++i) - { - bool b = i >= compare.LongLength || write[i] != compare[i]; -#if !ARRAY_WRITE_PREMAP - WriteBit(!b); -#endif - if (b) - { - WriteDoublePacked(write[i]); - } - } - } - } -} diff --git a/com.unity.netcode.gameobjects/Runtime/Serialization/NetworkWriter.cs.meta b/com.unity.netcode.gameobjects/Runtime/Serialization/NetworkWriter.cs.meta deleted file mode 100644 index b5407fc007..0000000000 --- a/com.unity.netcode.gameobjects/Runtime/Serialization/NetworkWriter.cs.meta +++ /dev/null @@ -1,11 +0,0 @@ -fileFormatVersion: 2 -guid: d5f796d542353ae43a8462806a5b98aa -MonoImporter: - externalObjects: {} - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: - assetBundleName: - assetBundleVariant: diff --git a/com.unity.netcode.gameobjects/Runtime/Serialization/Pooled/NetworkBufferPool.cs b/com.unity.netcode.gameobjects/Runtime/Serialization/Pooled/NetworkBufferPool.cs deleted file mode 100644 index b1cc8186f2..0000000000 --- a/com.unity.netcode.gameobjects/Runtime/Serialization/Pooled/NetworkBufferPool.cs +++ /dev/null @@ -1,98 +0,0 @@ -using System; -using System.Collections.Generic; - -namespace Unity.Netcode -{ - /// - /// Static class containing PooledNetworkBuffers - /// - public static class NetworkBufferPool - { - private static uint s_CreatedBuffers = 0; - private static Queue s_OverflowBuffers = new Queue(); - private static Queue s_Buffers = new Queue(); - - private const uint k_MaxBitPoolBuffers = 1024; - private const uint k_MaxCreatedDelta = 512; - - - /// - /// Retrieves an expandable PooledNetworkBuffer from the pool - /// - /// An expandable PooledNetworkBuffer - public static PooledNetworkBuffer GetBuffer() - { - if (s_Buffers.Count == 0) - { - if (s_OverflowBuffers.Count > 0) - { - if (NetworkLog.CurrentLogLevel <= LogLevel.Developer) - { - NetworkLog.LogInfo($"Retrieving {nameof(PooledNetworkBuffer)} from overflow pool. Recent burst?"); - } - - object weakBuffer = null; - while (s_OverflowBuffers.Count > 0 && ((weakBuffer = s_OverflowBuffers.Dequeue().Target) == null)) - { - ; - } - - if (weakBuffer != null) - { - var strongBuffer = (PooledNetworkBuffer)weakBuffer; - - strongBuffer.SetLength(0); - strongBuffer.Position = 0; - - return strongBuffer; - } - } - - if (s_CreatedBuffers == k_MaxBitPoolBuffers) - { - if (NetworkLog.CurrentLogLevel <= LogLevel.Normal) - { - NetworkLog.LogWarning($"{k_MaxBitPoolBuffers} buffers have been created. Did you forget to dispose?"); - } - } - else if (s_CreatedBuffers < k_MaxBitPoolBuffers) - { - s_CreatedBuffers++; - } - - return new PooledNetworkBuffer(); - } - - PooledNetworkBuffer buffer = s_Buffers.Dequeue(); - buffer.SetLength(0); - buffer.Position = 0; - - return buffer; - } - - /// - /// Puts a PooledNetworkBuffer back into the pool - /// - /// The buffer to put in the pool - public static void PutBackInPool(PooledNetworkBuffer buffer) - { - if (s_Buffers.Count > k_MaxCreatedDelta) - { - // The user just created lots of buffers without returning them in between. - // Buffers are essentially byte array wrappers. This is valuable memory. - // Thus we put this buffer as a weak reference incase of another burst - // But still leave it to GC - if (NetworkLog.CurrentLogLevel <= LogLevel.Developer) - { - NetworkLog.LogInfo($"Putting {nameof(PooledNetworkBuffer)} into overflow pool. Did you forget to dispose?"); - } - - s_OverflowBuffers.Enqueue(new WeakReference(buffer)); - } - else - { - s_Buffers.Enqueue(buffer); - } - } - } -} diff --git a/com.unity.netcode.gameobjects/Runtime/Serialization/Pooled/NetworkBufferPool.cs.meta b/com.unity.netcode.gameobjects/Runtime/Serialization/Pooled/NetworkBufferPool.cs.meta deleted file mode 100644 index 3f6106a42c..0000000000 --- a/com.unity.netcode.gameobjects/Runtime/Serialization/Pooled/NetworkBufferPool.cs.meta +++ /dev/null @@ -1,11 +0,0 @@ -fileFormatVersion: 2 -guid: b12cc234a5c700041bb1f08f8229f676 -MonoImporter: - externalObjects: {} - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: - assetBundleName: - assetBundleVariant: diff --git a/com.unity.netcode.gameobjects/Runtime/Serialization/Pooled/NetworkReaderPool.cs b/com.unity.netcode.gameobjects/Runtime/Serialization/Pooled/NetworkReaderPool.cs deleted file mode 100644 index bccc477d74..0000000000 --- a/com.unity.netcode.gameobjects/Runtime/Serialization/Pooled/NetworkReaderPool.cs +++ /dev/null @@ -1,60 +0,0 @@ -using System.Collections.Generic; -using System.IO; - -namespace Unity.Netcode -{ - /// - /// Static class containing PooledNetworkReaders - /// - public static class NetworkReaderPool - { - private static byte s_CreatedReaders = 0; - private static Queue s_Readers = new Queue(); - - /// - /// Retrieves a PooledNetworkReader - /// - /// The stream the reader should read from - /// A PooledNetworkReader - public static PooledNetworkReader GetReader(Stream stream) - { - if (s_Readers.Count == 0) - { - if (s_CreatedReaders == 254) - { - if (NetworkLog.CurrentLogLevel <= LogLevel.Normal) - { - NetworkLog.LogWarning("255 readers have been created. Did you forget to dispose?"); - } - } - else if (s_CreatedReaders < 255) - { - s_CreatedReaders++; - } - - return new PooledNetworkReader(stream); - } - - PooledNetworkReader reader = s_Readers.Dequeue(); - reader.SetStream(stream); - - return reader; - } - - /// - /// Puts a PooledNetworkReader back into the pool - /// - /// The reader to put in the pool - public static void PutBackInPool(PooledNetworkReader reader) - { - if (s_Readers.Count < 64) - { - s_Readers.Enqueue(reader); - } - else if (NetworkLog.CurrentLogLevel <= LogLevel.Developer) - { - NetworkLog.LogInfo($"{nameof(NetworkReaderPool)} already has 64 queued. Throwing to GC. Did you forget to dispose?"); - } - } - } -} diff --git a/com.unity.netcode.gameobjects/Runtime/Serialization/Pooled/NetworkReaderPool.cs.meta b/com.unity.netcode.gameobjects/Runtime/Serialization/Pooled/NetworkReaderPool.cs.meta deleted file mode 100644 index 09eb815b80..0000000000 --- a/com.unity.netcode.gameobjects/Runtime/Serialization/Pooled/NetworkReaderPool.cs.meta +++ /dev/null @@ -1,11 +0,0 @@ -fileFormatVersion: 2 -guid: 14a958cf00e46084693623b5c90ba657 -MonoImporter: - externalObjects: {} - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: - assetBundleName: - assetBundleVariant: diff --git a/com.unity.netcode.gameobjects/Runtime/Serialization/Pooled/NetworkWriterPool.cs b/com.unity.netcode.gameobjects/Runtime/Serialization/Pooled/NetworkWriterPool.cs deleted file mode 100644 index ff753f3f03..0000000000 --- a/com.unity.netcode.gameobjects/Runtime/Serialization/Pooled/NetworkWriterPool.cs +++ /dev/null @@ -1,60 +0,0 @@ -using System.Collections.Generic; -using System.IO; - -namespace Unity.Netcode -{ - /// - /// Static class containing PooledNetworkWriters - /// - public static class NetworkWriterPool - { - private static byte s_CreatedWriters = 0; - private static Queue s_Writers = new Queue(); - - /// - /// Retrieves a PooledNetworkWriter - /// - /// The stream the writer should write to - /// A PooledNetworkWriter - public static PooledNetworkWriter GetWriter(Stream stream) - { - if (s_Writers.Count == 0) - { - if (s_CreatedWriters == 254) - { - if (NetworkLog.CurrentLogLevel <= LogLevel.Normal) - { - NetworkLog.LogWarning("255 writers have been created. Did you forget to dispose?"); - } - } - else if (s_CreatedWriters < 255) - { - s_CreatedWriters++; - } - - return new PooledNetworkWriter(stream); - } - - PooledNetworkWriter writer = s_Writers.Dequeue(); - writer.SetStream(stream); - - return writer; - } - - /// - /// Puts a PooledNetworkWriter back into the pool - /// - /// The writer to put in the pool - public static void PutBackInPool(PooledNetworkWriter writer) - { - if (s_Writers.Count < 64) - { - s_Writers.Enqueue(writer); - } - else if (NetworkLog.CurrentLogLevel <= LogLevel.Developer) - { - NetworkLog.LogInfo($"{nameof(NetworkWriterPool)} already has 64 queued. Throwing to GC. Did you forget to dispose?"); - } - } - } -} diff --git a/com.unity.netcode.gameobjects/Runtime/Serialization/Pooled/NetworkWriterPool.cs.meta b/com.unity.netcode.gameobjects/Runtime/Serialization/Pooled/NetworkWriterPool.cs.meta deleted file mode 100644 index 1e4d7493fa..0000000000 --- a/com.unity.netcode.gameobjects/Runtime/Serialization/Pooled/NetworkWriterPool.cs.meta +++ /dev/null @@ -1,11 +0,0 @@ -fileFormatVersion: 2 -guid: 2cec275be55b22c4ba0fe27f74fbe2a9 -MonoImporter: - externalObjects: {} - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: - assetBundleName: - assetBundleVariant: diff --git a/com.unity.netcode.gameobjects/Runtime/Serialization/Pooled/PooledNetworkBuffer.cs b/com.unity.netcode.gameobjects/Runtime/Serialization/Pooled/PooledNetworkBuffer.cs deleted file mode 100644 index 1c744e76ae..0000000000 --- a/com.unity.netcode.gameobjects/Runtime/Serialization/Pooled/PooledNetworkBuffer.cs +++ /dev/null @@ -1,44 +0,0 @@ -namespace Unity.Netcode -{ - /// - /// Disposable NetworkBuffer that returns back to the NetworkBufferPool when disposed - /// - public sealed class PooledNetworkBuffer : NetworkBuffer - { - private bool m_IsDisposed = false; - - internal PooledNetworkBuffer() { } - - /// - /// Gets a PooledNetworkBuffer from the static NetworkBufferPool - /// - /// PooledNetworkBuffer - public static PooledNetworkBuffer Get() - { - var buffer = NetworkBufferPool.GetBuffer(); - buffer.m_IsDisposed = false; - return buffer; - } - - /// - /// Returns the PooledNetworkBuffer into the static NetworkBufferPool - /// Called by Dispose in the parent class implementation. - /// We cannot override Dispose because it's not declared virtual - /// And if we override it via `public new void Dispose()` then it doesn't get called - /// on anything with a static type other than PooledNetworkBuffer, which then results in a leak: - /// - /// Stream buffer = PooledNetworkBuffer.Get(); - /// buffer.Dispose(); - /// - /// ^ Static type is Stream, this calls Stream::Dispose() instead of PooledNetworkBuffer::Dispose() - /// - public override void Close() - { - if (!m_IsDisposed) - { - m_IsDisposed = true; - NetworkBufferPool.PutBackInPool(this); - } - } - } -} diff --git a/com.unity.netcode.gameobjects/Runtime/Serialization/Pooled/PooledNetworkBuffer.cs.meta b/com.unity.netcode.gameobjects/Runtime/Serialization/Pooled/PooledNetworkBuffer.cs.meta deleted file mode 100644 index 6054cbad29..0000000000 --- a/com.unity.netcode.gameobjects/Runtime/Serialization/Pooled/PooledNetworkBuffer.cs.meta +++ /dev/null @@ -1,11 +0,0 @@ -fileFormatVersion: 2 -guid: 4b9ca4cb2f1e219419c81cc68ad3b9b1 -MonoImporter: - externalObjects: {} - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: - assetBundleName: - assetBundleVariant: diff --git a/com.unity.netcode.gameobjects/Runtime/Serialization/Pooled/PooledNetworkReader.cs b/com.unity.netcode.gameobjects/Runtime/Serialization/Pooled/PooledNetworkReader.cs deleted file mode 100644 index 796b6e5072..0000000000 --- a/com.unity.netcode.gameobjects/Runtime/Serialization/Pooled/PooledNetworkReader.cs +++ /dev/null @@ -1,46 +0,0 @@ -using System; -using System.IO; -using UnityEngine; - -namespace Unity.Netcode -{ - /// - /// Disposable NetworkReader that returns the Reader to the NetworkReaderPool when disposed - /// - public sealed class PooledNetworkReader : NetworkReader, IDisposable - { - private NetworkSerializer m_Serializer; - public NetworkSerializer Serializer => m_Serializer ?? (m_Serializer = new NetworkSerializer(this)); - - private bool m_IsDisposed = false; - - internal PooledNetworkReader(Stream stream) : base(stream) { } - - /// - /// Gets a PooledNetworkReader from the static NetworkReaderPool - /// - /// PooledNetworkReader - public static PooledNetworkReader Get(Stream stream) - { - var reader = NetworkReaderPool.GetReader(stream); - reader.m_IsDisposed = false; - return reader; - } - - /// - /// Returns the PooledNetworkReader into the static NetworkReaderPool - /// - public void Dispose() - { - if (!m_IsDisposed) - { - m_IsDisposed = true; - NetworkReaderPool.PutBackInPool(this); - } - else - { - Debug.LogWarning("Disposing reader that thinks it is already disposed!"); - } - } - } -} diff --git a/com.unity.netcode.gameobjects/Runtime/Serialization/Pooled/PooledNetworkReader.cs.meta b/com.unity.netcode.gameobjects/Runtime/Serialization/Pooled/PooledNetworkReader.cs.meta deleted file mode 100644 index aaa5a52dd7..0000000000 --- a/com.unity.netcode.gameobjects/Runtime/Serialization/Pooled/PooledNetworkReader.cs.meta +++ /dev/null @@ -1,11 +0,0 @@ -fileFormatVersion: 2 -guid: 31928243e74f75541b5e5c4ae87149db -MonoImporter: - externalObjects: {} - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: - assetBundleName: - assetBundleVariant: diff --git a/com.unity.netcode.gameobjects/Runtime/Serialization/Pooled/PooledNetworkWriter.cs b/com.unity.netcode.gameobjects/Runtime/Serialization/Pooled/PooledNetworkWriter.cs deleted file mode 100644 index c199f99a39..0000000000 --- a/com.unity.netcode.gameobjects/Runtime/Serialization/Pooled/PooledNetworkWriter.cs +++ /dev/null @@ -1,46 +0,0 @@ -using System; -using System.IO; -using UnityEngine; - -namespace Unity.Netcode -{ - /// - /// Disposable NetworkWriter that returns the Writer to the NetworkWriterPool when disposed - /// - public sealed class PooledNetworkWriter : NetworkWriter, IDisposable - { - private NetworkSerializer m_Serializer; - public NetworkSerializer Serializer => m_Serializer ?? (m_Serializer = new NetworkSerializer(this)); - - private bool m_IsDisposed = false; - - internal PooledNetworkWriter(Stream stream) : base(stream) { } - - /// - /// Gets a PooledNetworkWriter from the static NetworkWriterPool - /// - /// PooledNetworkWriter - public static PooledNetworkWriter Get(Stream stream) - { - var writer = NetworkWriterPool.GetWriter(stream); - writer.m_IsDisposed = false; - return writer; - } - - /// - /// Returns the PooledNetworkWriter into the static NetworkWriterPool - /// - public void Dispose() - { - if (!m_IsDisposed) - { - m_IsDisposed = true; - NetworkWriterPool.PutBackInPool(this); - } - else - { - Debug.LogError("Writer is being disposed but thinks it is already disposed"); - } - } - } -} diff --git a/com.unity.netcode.gameobjects/Runtime/Serialization/Pooled/PooledNetworkWriter.cs.meta b/com.unity.netcode.gameobjects/Runtime/Serialization/Pooled/PooledNetworkWriter.cs.meta deleted file mode 100644 index 3a29bf205b..0000000000 --- a/com.unity.netcode.gameobjects/Runtime/Serialization/Pooled/PooledNetworkWriter.cs.meta +++ /dev/null @@ -1,11 +0,0 @@ -fileFormatVersion: 2 -guid: fe89760ee0eccbe4dbf1a0e7cbb86a81 -MonoImporter: - externalObjects: {} - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: - assetBundleName: - assetBundleVariant: diff --git a/com.unity.netcode.gameobjects/Runtime/Serialization/SerializationManager.cs b/com.unity.netcode.gameobjects/Runtime/Serialization/SerializationManager.cs deleted file mode 100644 index 4d0614b04d..0000000000 --- a/com.unity.netcode.gameobjects/Runtime/Serialization/SerializationManager.cs +++ /dev/null @@ -1,146 +0,0 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Reflection; -using UnityEngine; - -namespace Unity.Netcode -{ - /// - /// Helper class to manage the netcode serialization - /// - public static class SerializationManager - { - private static Dictionary s_FieldCache = new Dictionary(); - private static Dictionary s_CachedExternalSerializers = new Dictionary(); - private static Dictionary s_CachedExternalDeserializers = new Dictionary(); - - /// - /// The delegate used when registering custom deserialization for a type. - /// - /// The stream to read the data required to construct the type. - /// The type to deserialize. - public delegate T CustomDeserializationDelegate(Stream stream); - - /// - /// The delegate used when registering custom serialization for a type. - /// - /// The stream to write data to that is required to reconstruct the type in the deserialization delegate. - /// The instance to serialize to the stream. - /// The type to serialize. - public delegate void CustomSerializationDelegate(Stream stream, T instance); - - // These two are what we use internally. They box the value. - private delegate void BoxedSerializationDelegate(Stream stream, object instance); - - private delegate object BoxedDeserializationDelegate(Stream stream); - - /// - /// Registers a custom serialization and deserialization pair for a object. - /// This is useful for writing objects that are behind the third party wall. Such as .NET types. - /// - /// The delegate to invoke to serialize the type. - /// The delegate to invoke to deserialize the type. - /// The type to register. - public static void RegisterSerializationHandlers(CustomSerializationDelegate onSerialize, CustomDeserializationDelegate onDeserialize) - { - s_CachedExternalSerializers[typeof(T)] = (stream, instance) => onSerialize(stream, (T)instance); - s_CachedExternalDeserializers[typeof(T)] = stream => onDeserialize(stream); - } - - /// - /// Removes a serialization handler that was registered previously for a specific type. - /// This will remove both the serialization and deserialization handler. - /// - /// The type for the serialization handlers to remove. - /// Whether or not either the serialization or deserialization handlers for the type was removed. - public static bool RemoveSerializationHandlers() - { - bool serializationRemoval = s_CachedExternalSerializers.Remove(typeof(T)); - bool deserializationRemoval = s_CachedExternalDeserializers.Remove(typeof(T)); - - return serializationRemoval || deserializationRemoval; - } - - internal static bool TrySerialize(Stream stream, object obj) - { - if (s_CachedExternalSerializers.TryGetValue(obj.GetType(), out BoxedSerializationDelegate serializer)) - { - serializer(stream, obj); - return true; - } - - return false; - } - - internal static bool TryDeserialize(Stream stream, Type type, out object obj) - { - if (s_CachedExternalDeserializers.TryGetValue(type, out BoxedDeserializationDelegate deserializationDelegate)) - { - obj = deserializationDelegate(stream); - return true; - } - - obj = null; - return false; - } - - internal static FieldInfo[] GetFieldsForType(Type type) - { - if (s_FieldCache.TryGetValue(type, out FieldInfo[] value)) - { - return value; - } - - FieldInfo[] fields = type - .GetFields(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance) - .Where(x => (x.IsPublic || x.GetCustomAttributes(typeof(SerializeField), true).Length > 0) && IsTypeSupported(x.FieldType)) - .OrderBy(x => x.Name, StringComparer.Ordinal).ToArray(); - - s_FieldCache.Add(type, fields); - - return fields; - } - - private static HashSet s_SupportedTypes = new HashSet() - { - typeof(byte), - typeof(byte), - typeof(sbyte), - typeof(ushort), - typeof(short), - typeof(int), - typeof(uint), - typeof(long), - typeof(ulong), - typeof(float), - typeof(double), - typeof(string), - typeof(bool), - typeof(Vector2), - typeof(Vector3), - typeof(Vector4), - typeof(Color), - typeof(Color32), - typeof(Ray), - typeof(Quaternion), - typeof(char), - typeof(GameObject), - typeof(NetworkObject), - typeof(NetworkBehaviour) - }; - - /// - /// Returns if a type is supported for serialization - /// - /// The type to check - /// Whether or not the type is supported - public static bool IsTypeSupported(Type type) - { - return type.IsEnum || s_SupportedTypes.Contains(type) || type.HasInterface(typeof(INetworkSerializable)) || - (s_CachedExternalSerializers.ContainsKey(type) && s_CachedExternalDeserializers.ContainsKey(type)) || - (type.IsArray && type.HasElementType && IsTypeSupported(type.GetElementType())); - } - } -} diff --git a/com.unity.netcode.gameobjects/Runtime/Serialization/SerializationManager.cs.meta b/com.unity.netcode.gameobjects/Runtime/Serialization/SerializationManager.cs.meta deleted file mode 100644 index dbea7c922b..0000000000 --- a/com.unity.netcode.gameobjects/Runtime/Serialization/SerializationManager.cs.meta +++ /dev/null @@ -1,11 +0,0 @@ -fileFormatVersion: 2 -guid: 6f42870c1eac2da49aee0bc9057687b8 -MonoImporter: - externalObjects: {} - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: - assetBundleName: - assetBundleVariant: diff --git a/com.unity.netcode.gameobjects/Runtime/Serialization/SerializationTypeTable.cs b/com.unity.netcode.gameobjects/Runtime/Serialization/SerializationTypeTable.cs new file mode 100644 index 0000000000..fb861c91d7 --- /dev/null +++ b/com.unity.netcode.gameobjects/Runtime/Serialization/SerializationTypeTable.cs @@ -0,0 +1,449 @@ +using System; +using System.Collections.Generic; +using UnityEngine; + +namespace Unity.Netcode +{ + /// + /// Registry for telling FastBufferWriter and FastBufferReader how to read types when passed to + /// WriteObject and ReadObject, as well as telling BytePacker and ByteUnpacker how to do it when passed to + /// WriteObjectPacked and ReadObjectPacked. + /// + /// These object-based serialization functions shouldn't be used if at all possible, but if they're required, + /// and you need to serialize a type that's not natively supported, you can register it with the dictionaries here: + /// + /// Serializers and Deserializers for FastBufferWriter and FasteBufferReader + /// SerializersPacked and DeserializersPacked for BytePacker and ByteUnpacker + /// + public static class SerializationTypeTable + { + public delegate void Serialize(ref FastBufferWriter writer, object value); + public delegate void Deserialize(ref FastBufferReader reader, out object value); + + public static Dictionary Serializers = new Dictionary + { + [typeof(byte)] = (ref FastBufferWriter writer, object value) => writer.WriteByteSafe((byte)value), + [typeof(sbyte)] = (ref FastBufferWriter writer, object value) => writer.WriteByteSafe((byte)(sbyte)value), + + [typeof(ushort)] = (ref FastBufferWriter writer, object value) => writer.WriteValueSafe((ushort)value), + [typeof(short)] = (ref FastBufferWriter writer, object value) => writer.WriteValueSafe((short)value), + [typeof(uint)] = (ref FastBufferWriter writer, object value) => writer.WriteValueSafe((uint)value), + [typeof(int)] = (ref FastBufferWriter writer, object value) => writer.WriteValueSafe((int)value), + [typeof(ulong)] = (ref FastBufferWriter writer, object value) => writer.WriteValueSafe((ulong)value), + [typeof(long)] = (ref FastBufferWriter writer, object value) => writer.WriteValueSafe((long)value), + + [typeof(float)] = (ref FastBufferWriter writer, object value) => writer.WriteValueSafe((float)value), + [typeof(double)] = (ref FastBufferWriter writer, object value) => writer.WriteValueSafe((double)value), + + [typeof(string)] = (ref FastBufferWriter writer, object value) => writer.WriteValueSafe((string)value), + + [typeof(Vector2)] = (ref FastBufferWriter writer, object value) => writer.WriteValueSafe((Vector2)value), + [typeof(Vector3)] = (ref FastBufferWriter writer, object value) => writer.WriteValueSafe((Vector3)value), + [typeof(Vector4)] = (ref FastBufferWriter writer, object value) => writer.WriteValueSafe((Vector4)value), + [typeof(Color)] = (ref FastBufferWriter writer, object value) => writer.WriteValueSafe((Color)value), + [typeof(Color32)] = (ref FastBufferWriter writer, object value) => writer.WriteValueSafe((Color32)value), + [typeof(Ray)] = (ref FastBufferWriter writer, object value) => writer.WriteValueSafe((Ray)value), + [typeof(Ray2D)] = (ref FastBufferWriter writer, object value) => writer.WriteValueSafe((Ray2D)value), + [typeof(Quaternion)] = (ref FastBufferWriter writer, object value) => writer.WriteValueSafe((Quaternion)value), + + [typeof(char)] = (ref FastBufferWriter writer, object value) => writer.WriteValueSafe((char)value), + + [typeof(bool)] = (ref FastBufferWriter writer, object value) => writer.WriteValueSafe((bool)value), + + + [typeof(byte[])] = (ref FastBufferWriter writer, object value) => writer.WriteValueSafe((byte[])value), + [typeof(sbyte[])] = (ref FastBufferWriter writer, object value) => writer.WriteValueSafe((sbyte[])value), + + [typeof(ushort[])] = (ref FastBufferWriter writer, object value) => writer.WriteValueSafe((ushort[])value), + [typeof(short[])] = (ref FastBufferWriter writer, object value) => writer.WriteValueSafe((short[])value), + [typeof(uint[])] = (ref FastBufferWriter writer, object value) => writer.WriteValueSafe((uint[])value), + [typeof(int[])] = (ref FastBufferWriter writer, object value) => writer.WriteValueSafe((int[])value), + [typeof(ulong[])] = (ref FastBufferWriter writer, object value) => writer.WriteValueSafe((ulong[])value), + [typeof(long[])] = (ref FastBufferWriter writer, object value) => writer.WriteValueSafe((long[])value), + + [typeof(float[])] = (ref FastBufferWriter writer, object value) => writer.WriteValueSafe((float[])value), + [typeof(double[])] = (ref FastBufferWriter writer, object value) => writer.WriteValueSafe((double[])value), + + [typeof(Vector2[])] = (ref FastBufferWriter writer, object value) => writer.WriteValueSafe((Vector2[])value), + [typeof(Vector3[])] = (ref FastBufferWriter writer, object value) => writer.WriteValueSafe((Vector3[])value), + [typeof(Vector4[])] = (ref FastBufferWriter writer, object value) => writer.WriteValueSafe((Vector4[])value), + [typeof(Color[])] = (ref FastBufferWriter writer, object value) => writer.WriteValueSafe((Color[])value), + [typeof(Color32[])] = (ref FastBufferWriter writer, object value) => writer.WriteValueSafe((Color32[])value), + [typeof(Ray[])] = (ref FastBufferWriter writer, object value) => writer.WriteValueSafe((Ray[])value), + [typeof(Ray2D[])] = (ref FastBufferWriter writer, object value) => writer.WriteValueSafe((Ray2D[])value), + [typeof(Quaternion[])] = (ref FastBufferWriter writer, object value) => writer.WriteValueSafe((Quaternion[])value), + + [typeof(char[])] = (ref FastBufferWriter writer, object value) => writer.WriteValueSafe((char[])value), + + [typeof(bool[])] = (ref FastBufferWriter writer, object value) => writer.WriteValueSafe((bool[])value), + }; + + public static Dictionary Deserializers = new Dictionary + { + [typeof(byte)] = (ref FastBufferReader reader, out object value) => + { + reader.ReadByteSafe(out byte tmp); + value = tmp; + }, + [typeof(sbyte)] = (ref FastBufferReader reader, out object value) => + { + reader.ReadByteSafe(out byte tmp); + value = (sbyte)tmp; + }, + + [typeof(ushort)] = (ref FastBufferReader reader, out object value) => + { + reader.ReadValueSafe(out ushort tmp); + value = tmp; + }, + [typeof(short)] = (ref FastBufferReader reader, out object value) => + { + reader.ReadValueSafe(out short tmp); + value = tmp; + }, + [typeof(uint)] = (ref FastBufferReader reader, out object value) => + { + reader.ReadValueSafe(out uint tmp); + value = tmp; + }, + [typeof(int)] = (ref FastBufferReader reader, out object value) => + { + reader.ReadValueSafe(out int tmp); + value = tmp; + }, + [typeof(ulong)] = (ref FastBufferReader reader, out object value) => + { + reader.ReadValueSafe(out ulong tmp); + value = tmp; + }, + [typeof(long)] = (ref FastBufferReader reader, out object value) => + { + reader.ReadValueSafe(out long tmp); + value = tmp; + }, + + [typeof(float)] = (ref FastBufferReader reader, out object value) => + { + reader.ReadValueSafe(out float tmp); + value = tmp; + }, + [typeof(double)] = (ref FastBufferReader reader, out object value) => + { + reader.ReadValueSafe(out double tmp); + value = tmp; + }, + + [typeof(string)] = (ref FastBufferReader reader, out object value) => + { + reader.ReadValueSafe(out string tmp); + value = tmp; + }, + + [typeof(Vector2)] = (ref FastBufferReader reader, out object value) => + { + reader.ReadValueSafe(out Vector2 tmp); + value = tmp; + }, + [typeof(Vector3)] = (ref FastBufferReader reader, out object value) => + { + reader.ReadValueSafe(out Vector3 tmp); + value = tmp; + }, + [typeof(Vector4)] = (ref FastBufferReader reader, out object value) => + { + reader.ReadValueSafe(out Vector4 tmp); + value = tmp; + }, + [typeof(Color)] = (ref FastBufferReader reader, out object value) => + { + reader.ReadValueSafe(out Color tmp); + value = tmp; + }, + [typeof(Color32)] = (ref FastBufferReader reader, out object value) => + { + reader.ReadValueSafe(out Color32 tmp); + value = tmp; + }, + [typeof(Ray)] = (ref FastBufferReader reader, out object value) => + { + reader.ReadValueSafe(out Ray tmp); + value = tmp; + }, + [typeof(Ray2D)] = (ref FastBufferReader reader, out object value) => + { + reader.ReadValueSafe(out Ray2D tmp); + value = tmp; + }, + [typeof(Quaternion)] = (ref FastBufferReader reader, out object value) => + { + reader.ReadValueSafe(out Quaternion tmp); + value = tmp; + }, + + [typeof(char)] = (ref FastBufferReader reader, out object value) => + { + reader.ReadValueSafe(out char tmp); + value = tmp; + }, + + [typeof(bool)] = (ref FastBufferReader reader, out object value) => + { + reader.ReadValueSafe(out bool tmp); + value = tmp; + }, + + + [typeof(byte[])] = (ref FastBufferReader reader, out object value) => + { + reader.ReadValueSafe(out byte[] tmp); + value = tmp; + }, + [typeof(sbyte[])] = (ref FastBufferReader reader, out object value) => + { + reader.ReadValueSafe(out sbyte[] tmp); + value = tmp; + }, + + [typeof(ushort[])] = (ref FastBufferReader reader, out object value) => + { + reader.ReadValueSafe(out ushort[] tmp); + value = tmp; + }, + [typeof(short[])] = (ref FastBufferReader reader, out object value) => + { + reader.ReadValueSafe(out short[] tmp); + value = tmp; + }, + [typeof(uint[])] = (ref FastBufferReader reader, out object value) => + { + reader.ReadValueSafe(out uint[] tmp); + value = tmp; + }, + [typeof(int[])] = (ref FastBufferReader reader, out object value) => + { + reader.ReadValueSafe(out int[] tmp); + value = tmp; + }, + [typeof(ulong[])] = (ref FastBufferReader reader, out object value) => + { + reader.ReadValueSafe(out ulong[] tmp); + value = tmp; + }, + [typeof(long[])] = (ref FastBufferReader reader, out object value) => + { + reader.ReadValueSafe(out long[] tmp); + value = tmp; + }, + + [typeof(float[])] = (ref FastBufferReader reader, out object value) => + { + reader.ReadValueSafe(out float[] tmp); + value = tmp; + }, + [typeof(double[])] = (ref FastBufferReader reader, out object value) => + { + reader.ReadValueSafe(out double[] tmp); + value = tmp; + }, + + [typeof(Vector2[])] = (ref FastBufferReader reader, out object value) => + { + reader.ReadValueSafe(out Vector2[] tmp); + value = tmp; + }, + [typeof(Vector3[])] = (ref FastBufferReader reader, out object value) => + { + reader.ReadValueSafe(out Vector3[] tmp); + value = tmp; + }, + [typeof(Vector4[])] = (ref FastBufferReader reader, out object value) => + { + reader.ReadValueSafe(out Vector4[] tmp); + value = tmp; + }, + [typeof(Color[])] = (ref FastBufferReader reader, out object value) => + { + reader.ReadValueSafe(out Color[] tmp); + value = tmp; + }, + [typeof(Color32[])] = (ref FastBufferReader reader, out object value) => + { + reader.ReadValueSafe(out Color32[] tmp); + value = tmp; + }, + [typeof(Ray[])] = (ref FastBufferReader reader, out object value) => + { + reader.ReadValueSafe(out Ray[] tmp); + value = tmp; + }, + [typeof(Ray2D[])] = (ref FastBufferReader reader, out object value) => + { + reader.ReadValueSafe(out Ray2D[] tmp); + value = tmp; + }, + [typeof(Quaternion[])] = (ref FastBufferReader reader, out object value) => + { + reader.ReadValueSafe(out Quaternion[] tmp); + value = tmp; + }, + + [typeof(char[])] = (ref FastBufferReader reader, out object value) => + { + reader.ReadValueSafe(out char[] tmp); + value = tmp; + }, + + [typeof(bool[])] = (ref FastBufferReader reader, out object value) => + { + reader.ReadValueSafe(out bool[] tmp); + value = tmp; + }, + }; + + public static Dictionary SerializersPacked = new Dictionary + { + [typeof(byte)] = (ref FastBufferWriter writer, object value) => BytePacker.WriteValuePacked(ref writer, (byte)value), + [typeof(sbyte)] = (ref FastBufferWriter writer, object value) => BytePacker.WriteValuePacked(ref writer, (byte)(sbyte)value), + + [typeof(ushort)] = (ref FastBufferWriter writer, object value) => BytePacker.WriteValuePacked(ref writer, (ushort)value), + [typeof(short)] = (ref FastBufferWriter writer, object value) => BytePacker.WriteValuePacked(ref writer, (short)value), + [typeof(uint)] = (ref FastBufferWriter writer, object value) => BytePacker.WriteValuePacked(ref writer, (uint)value), + [typeof(int)] = (ref FastBufferWriter writer, object value) => BytePacker.WriteValuePacked(ref writer, (int)value), + [typeof(ulong)] = (ref FastBufferWriter writer, object value) => BytePacker.WriteValuePacked(ref writer, (ulong)value), + [typeof(long)] = (ref FastBufferWriter writer, object value) => BytePacker.WriteValuePacked(ref writer, (long)value), + + [typeof(float)] = (ref FastBufferWriter writer, object value) => BytePacker.WriteValuePacked(ref writer, (float)value), + [typeof(double)] = (ref FastBufferWriter writer, object value) => BytePacker.WriteValuePacked(ref writer, (double)value), + + [typeof(string)] = (ref FastBufferWriter writer, object value) => BytePacker.WriteValuePacked(ref writer, (string)value), + + [typeof(Vector2)] = (ref FastBufferWriter writer, object value) => BytePacker.WriteValuePacked(ref writer, (Vector2)value), + [typeof(Vector3)] = (ref FastBufferWriter writer, object value) => BytePacker.WriteValuePacked(ref writer, (Vector3)value), + [typeof(Vector4)] = (ref FastBufferWriter writer, object value) => BytePacker.WriteValuePacked(ref writer, (Vector4)value), + [typeof(Color)] = (ref FastBufferWriter writer, object value) => BytePacker.WriteValuePacked(ref writer, (Color)value), + [typeof(Color32)] = (ref FastBufferWriter writer, object value) => BytePacker.WriteValuePacked(ref writer, (Color32)value), + [typeof(Ray)] = (ref FastBufferWriter writer, object value) => BytePacker.WriteValuePacked(ref writer, (Ray)value), + [typeof(Ray2D)] = (ref FastBufferWriter writer, object value) => BytePacker.WriteValuePacked(ref writer, (Ray2D)value), + [typeof(Quaternion)] = (ref FastBufferWriter writer, object value) => BytePacker.WriteValuePacked(ref writer, (Quaternion)value), + + [typeof(char)] = (ref FastBufferWriter writer, object value) => BytePacker.WriteValuePacked(ref writer, (char)value), + + [typeof(bool)] = (ref FastBufferWriter writer, object value) => BytePacker.WriteValuePacked(ref writer, (bool)value), + }; + + public static Dictionary DeserializersPacked = new Dictionary + { + [typeof(byte)] = (ref FastBufferReader reader, out object value) => + { + ByteUnpacker.ReadValuePacked(ref reader, out byte tmp); + value = tmp; + }, + [typeof(sbyte)] = (ref FastBufferReader reader, out object value) => + { + ByteUnpacker.ReadValuePacked(ref reader, out byte tmp); + value = (sbyte)tmp; + }, + + [typeof(ushort)] = (ref FastBufferReader reader, out object value) => + { + ByteUnpacker.ReadValuePacked(ref reader, out ushort tmp); + value = tmp; + }, + [typeof(short)] = (ref FastBufferReader reader, out object value) => + { + ByteUnpacker.ReadValuePacked(ref reader, out short tmp); + value = tmp; + }, + [typeof(uint)] = (ref FastBufferReader reader, out object value) => + { + ByteUnpacker.ReadValuePacked(ref reader, out uint tmp); + value = tmp; + }, + [typeof(int)] = (ref FastBufferReader reader, out object value) => + { + ByteUnpacker.ReadValuePacked(ref reader, out int tmp); + value = tmp; + }, + [typeof(ulong)] = (ref FastBufferReader reader, out object value) => + { + ByteUnpacker.ReadValuePacked(ref reader, out ulong tmp); + value = tmp; + }, + [typeof(long)] = (ref FastBufferReader reader, out object value) => + { + ByteUnpacker.ReadValuePacked(ref reader, out long tmp); + value = tmp; + }, + + [typeof(float)] = (ref FastBufferReader reader, out object value) => + { + ByteUnpacker.ReadValuePacked(ref reader, out float tmp); + value = tmp; + }, + [typeof(double)] = (ref FastBufferReader reader, out object value) => + { + ByteUnpacker.ReadValuePacked(ref reader, out double tmp); + value = tmp; + }, + + [typeof(string)] = (ref FastBufferReader reader, out object value) => + { + ByteUnpacker.ReadValuePacked(ref reader, out string tmp); + value = tmp; + }, + + [typeof(Vector2)] = (ref FastBufferReader reader, out object value) => + { + ByteUnpacker.ReadValuePacked(ref reader, out Vector2 tmp); + value = tmp; + }, + [typeof(Vector3)] = (ref FastBufferReader reader, out object value) => + { + ByteUnpacker.ReadValuePacked(ref reader, out Vector3 tmp); + value = tmp; + }, + [typeof(Vector4)] = (ref FastBufferReader reader, out object value) => + { + ByteUnpacker.ReadValuePacked(ref reader, out Vector4 tmp); + value = tmp; + }, + [typeof(Color)] = (ref FastBufferReader reader, out object value) => + { + ByteUnpacker.ReadValuePacked(ref reader, out Color tmp); + value = tmp; + }, + [typeof(Color32)] = (ref FastBufferReader reader, out object value) => + { + ByteUnpacker.ReadValuePacked(ref reader, out Color32 tmp); + value = tmp; + }, + [typeof(Ray)] = (ref FastBufferReader reader, out object value) => + { + ByteUnpacker.ReadValuePacked(ref reader, out Ray tmp); + value = tmp; + }, + [typeof(Ray2D)] = (ref FastBufferReader reader, out object value) => + { + ByteUnpacker.ReadValuePacked(ref reader, out Ray2D tmp); + value = tmp; + }, + [typeof(Quaternion)] = (ref FastBufferReader reader, out object value) => + { + ByteUnpacker.ReadValuePacked(ref reader, out Quaternion tmp); + value = tmp; + }, + + [typeof(char)] = (ref FastBufferReader reader, out object value) => + { + ByteUnpacker.ReadValuePacked(ref reader, out char tmp); + value = tmp; + }, + + [typeof(bool)] = (ref FastBufferReader reader, out object value) => + { + ByteUnpacker.ReadValuePacked(ref reader, out bool tmp); + value = tmp; + }, + }; + } +} diff --git a/com.unity.netcode.gameobjects/Runtime/Serialization/SerializationTypeTable.cs.meta b/com.unity.netcode.gameobjects/Runtime/Serialization/SerializationTypeTable.cs.meta new file mode 100644 index 0000000000..58c25d1646 --- /dev/null +++ b/com.unity.netcode.gameobjects/Runtime/Serialization/SerializationTypeTable.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 49ee6a0e2ea6e9441a74b173c31cf389 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkPrefabHandler.cs b/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkPrefabHandler.cs index b15902ec57..ec8ca8d372 100644 --- a/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkPrefabHandler.cs +++ b/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkPrefabHandler.cs @@ -176,7 +176,7 @@ public bool RemoveHandler(NetworkObject networkObject) /// /// Use the of the overridden network prefab asset to remove a registered class that implements the interface. /// - /// of the source NetworkPrefab that was being overridden + /// of the source NetworkPrefab that was being overridden /// true (success) or false (failure) public bool RemoveHandler(uint globalObjectIdHash) { diff --git a/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkSpawnManager.cs b/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkSpawnManager.cs index 4527ec89d8..9a2634a5e2 100644 --- a/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkSpawnManager.cs +++ b/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkSpawnManager.cs @@ -1,7 +1,7 @@ using System; using System.Collections.Generic; -using System.IO; using System.Linq; +using Unity.Netcode.Messages; using UnityEngine; namespace Unity.Netcode @@ -102,22 +102,16 @@ internal void RemoveOwnership(NetworkObject networkObject) networkObject.OwnerClientIdInternal = null; - var context = NetworkManager.MessageQueueContainer.EnterInternalCommandContext(MessageQueueContainer.MessageType.ChangeOwner, NetworkDelivery.ReliableSequenced, NetworkManager.ConnectedClientsIds, NetworkUpdateLoop.UpdateStage); - if (context != null) + var message = new ChangeOwnershipMessage { - using var nonNullContext = (InternalCommandContext)context; - var bufferSizeCapture = new CommandContextSizeCapture(nonNullContext); - bufferSizeCapture.StartMeasureSegment(); + NetworkObjectId = networkObject.NetworkObjectId, + OwnerClientId = networkObject.OwnerClientId + }; + var size = NetworkManager.SendMessage(message, NetworkDelivery.ReliableSequenced, NetworkManager.ConnectedClientsIds); - nonNullContext.NetworkWriter.WriteUInt64Packed(networkObject.NetworkObjectId); - nonNullContext.NetworkWriter.WriteUInt64Packed(networkObject.OwnerClientId); - - var size = bufferSizeCapture.StopMeasureSegment(); - - foreach (var client in NetworkManager.ConnectedClients) - { - NetworkManager.NetworkMetrics.TrackOwnershipChangeSent(client.Key, networkObject.NetworkObjectId, networkObject.name, size); - } + foreach (var client in NetworkManager.ConnectedClients) + { + NetworkManager.NetworkMetrics.TrackOwnershipChangeSent(client.Key, networkObject.NetworkObjectId, networkObject.name, size); } } @@ -148,23 +142,17 @@ internal void ChangeOwnership(NetworkObject networkObject, ulong clientId) networkObject.OwnerClientId = clientId; - ulong[] clientIds = NetworkManager.ConnectedClientsIds; - var messageQueueContainer = NetworkManager.MessageQueueContainer; - var context = messageQueueContainer.EnterInternalCommandContext(MessageQueueContainer.MessageType.ChangeOwner, NetworkDelivery.ReliableSequenced, clientIds, NetworkUpdateLoop.UpdateStage); - if (context != null) - { - using var nonNullContext = (InternalCommandContext)context; - var bufferSizeCapture = new CommandContextSizeCapture(nonNullContext); - bufferSizeCapture.StartMeasureSegment(); - nonNullContext.NetworkWriter.WriteUInt64Packed(networkObject.NetworkObjectId); - nonNullContext.NetworkWriter.WriteUInt64Packed(clientId); + var message = new ChangeOwnershipMessage + { + NetworkObjectId = networkObject.NetworkObjectId, + OwnerClientId = networkObject.OwnerClientId + }; + var size = NetworkManager.SendMessage(message, NetworkDelivery.ReliableSequenced, NetworkManager.ConnectedClientsIds); - var size = bufferSizeCapture.StopMeasureSegment(); - foreach (var client in NetworkManager.ConnectedClients) - { - NetworkManager.NetworkMetrics.TrackOwnershipChangeSent(client.Key, networkObject.NetworkObjectId, networkObject.name, size); - } + foreach (var client in NetworkManager.ConnectedClients) + { + NetworkManager.NetworkMetrics.TrackOwnershipChangeSent(client.Key, networkObject.NetworkObjectId, networkObject.name, size); } } @@ -284,7 +272,7 @@ internal NetworkObject CreateLocalNetworkObject(bool isSceneObject, uint globalO } // Ran on both server and client - internal void SpawnNetworkObjectLocally(NetworkObject networkObject, ulong networkId, bool sceneObject, bool playerObject, ulong? ownerClientId, Stream dataStream, bool readNetworkVariable, bool destroyWithScene) + internal void SpawnNetworkObjectLocally(NetworkObject networkObject, ulong networkId, bool sceneObject, bool playerObject, ulong? ownerClientId, bool destroyWithScene) { if (networkObject == null) { @@ -296,11 +284,30 @@ internal void SpawnNetworkObjectLocally(NetworkObject networkObject, ulong netwo throw new SpawnStateException("Object is already spawned"); } - if (readNetworkVariable) + SpawnNetworkObjectLocallyCommon(networkObject, networkId, sceneObject, playerObject, ownerClientId, destroyWithScene); + } + + // Ran on both server and client + internal void SpawnNetworkObjectLocally(NetworkObject networkObject, in NetworkObject.SceneObject sceneObject, + ref FastBufferReader variableData, bool destroyWithScene) + { + if (networkObject == null) + { + throw new ArgumentNullException(nameof(networkObject), "Cannot spawn null object"); + } + + if (networkObject.IsSpawned) { - networkObject.SetNetworkVariableData(dataStream); + throw new SpawnStateException("Object is already spawned"); } + networkObject.SetNetworkVariableData(ref variableData); + + SpawnNetworkObjectLocallyCommon(networkObject, sceneObject.Metadata.NetworkObjectId, sceneObject.Metadata.IsSceneObject, sceneObject.Metadata.IsPlayerObject, sceneObject.Metadata.OwnerClientId, destroyWithScene); + } + + private void SpawnNetworkObjectLocallyCommon(NetworkObject networkObject, ulong networkId, bool sceneObject, bool playerObject, ulong? ownerClientId, bool destroyWithScene) + { if (SpawnedObjects.ContainsKey(networkId)) { Debug.LogWarning($"Trying to spawn {nameof(NetworkObject.NetworkObjectId)} {networkId} that already exists!"); @@ -377,20 +384,12 @@ internal void SendSpawnCallForObject(ulong clientId, NetworkObject networkObject return; } - var messageQueueContainer = NetworkManager.MessageQueueContainer; - - var context = messageQueueContainer.EnterInternalCommandContext(MessageQueueContainer.MessageType.CreateObject, NetworkDelivery.ReliableSequenced, new ulong[] { clientId }, NetworkUpdateLoop.UpdateStage); - if (context != null) + var message = new CreateObjectMessage { - using var nonNullContext = (InternalCommandContext)context; - var bufferSizeCapture = new CommandContextSizeCapture(nonNullContext); - bufferSizeCapture.StartMeasureSegment(); - - WriteSpawnCallForObject(nonNullContext.NetworkWriter, clientId, networkObject); - - var size = bufferSizeCapture.StopMeasureSegment(); - NetworkManager.NetworkMetrics.TrackObjectSpawnSent(clientId, networkObject.NetworkObjectId, networkObject.name, size); - } + ObjectInfo = networkObject.GetMessageSceneObject(clientId) + }; + var size = NetworkManager.SendMessage(message, NetworkDelivery.ReliableFragmentedSequenced, clientId); + NetworkManager.NetworkMetrics.TrackObjectSpawnSent(clientId, networkObject.NetworkObjectId, networkObject.name, size); } } @@ -411,49 +410,6 @@ internal void SendSpawnCallForObject(ulong clientId, NetworkObject networkObject return parentNetworkObject.NetworkObjectId; } - internal void WriteSpawnCallForObject(PooledNetworkWriter writer, ulong clientId, NetworkObject networkObject) - { - writer.WriteBool(networkObject.IsPlayerObject); - writer.WriteUInt64Packed(networkObject.NetworkObjectId); - writer.WriteUInt64Packed(networkObject.OwnerClientId); - - var parent = GetSpawnParentId(networkObject); - if (parent == null) - { - writer.WriteBool(false); - } - else - { - writer.WriteBool(true); - writer.WriteUInt64Packed(parent.Value); - } - - writer.WriteBool(networkObject.IsSceneObject ?? true); - writer.WriteUInt32Packed(networkObject.HostCheckForGlobalObjectIdHashOverride()); - - if (networkObject.IncludeTransformWhenSpawning == null || networkObject.IncludeTransformWhenSpawning(clientId)) - { - writer.WriteBool(true); - writer.WriteSinglePacked(networkObject.transform.position.x); - writer.WriteSinglePacked(networkObject.transform.position.y); - writer.WriteSinglePacked(networkObject.transform.position.z); - - writer.WriteSinglePacked(networkObject.transform.rotation.eulerAngles.x); - writer.WriteSinglePacked(networkObject.transform.rotation.eulerAngles.y); - writer.WriteSinglePacked(networkObject.transform.rotation.eulerAngles.z); - } - else - { - writer.WriteBool(false); - } - - { - var (isReparented, latestParent) = networkObject.GetNetworkParenting(); - NetworkObject.WriteNetworkParenting(writer, isReparented, latestParent); - } - networkObject.WriteNetworkVariableData(writer.GetStream(), clientId); - } - internal void DespawnObject(NetworkObject networkObject, bool destroyObject = false) { if (!networkObject.IsSpawned) @@ -565,7 +521,7 @@ internal void ServerSpawnSceneObjectsOnStartSweep() { if (networkObjects[i].IsSceneObject == null) { - SpawnNetworkObjectLocally(networkObjects[i], GetNetworkObjectId(), true, false, null, null, false, true); + SpawnNetworkObjectLocally(networkObjects[i], GetNetworkObjectId(), true, false, null, true); } } } @@ -638,39 +594,29 @@ internal void OnDespawnObject(NetworkObject networkObject, bool destroyGameObjec } else { - var messageQueueContainer = NetworkManager.MessageQueueContainer; - if (messageQueueContainer != null) + if (networkObject != null) { - if (networkObject != null) + // As long as we have any remaining clients, then notify of the object being destroy. + if (NetworkManager.ConnectedClientsList.Count > 0) { - // As long as we have any remaining clients, then notify of the object being destroy. - if (NetworkManager.ConnectedClientsList.Count > 0) - { - m_TargetClientIds.Clear(); + m_TargetClientIds.Clear(); - // We keep only the client for which the object is visible - // as the other clients have them already despawned - foreach (var clientId in NetworkManager.ConnectedClientsIds) + // We keep only the client for which the object is visible + // as the other clients have them already despawned + foreach (var clientId in NetworkManager.ConnectedClientsIds) + { + if (networkObject.IsNetworkVisibleTo(clientId)) { - if (networkObject.IsNetworkVisibleTo(clientId)) - { - m_TargetClientIds.Add(clientId); - } - } - - var context = messageQueueContainer.EnterInternalCommandContext(MessageQueueContainer.MessageType.DestroyObject, NetworkDelivery.ReliableSequenced, m_TargetClientIds.ToArray(), NetworkUpdateStage.PostLateUpdate); - if (context != null) - { - using var nonNullContext = (InternalCommandContext)context; - var bufferSizeCapture = new CommandContextSizeCapture(nonNullContext); - bufferSizeCapture.StartMeasureSegment(); - - nonNullContext.NetworkWriter.WriteUInt64Packed(networkObject.NetworkObjectId); - - var size = bufferSizeCapture.StopMeasureSegment(); - NetworkManager.NetworkMetrics.TrackObjectDestroySent(m_TargetClientIds, networkObject.NetworkObjectId, networkObject.name, size); + m_TargetClientIds.Add(clientId); } } + + var message = new DestroyObjectMessage + { + NetworkObjectId = networkObject.NetworkObjectId + }; + var size = NetworkManager.SendMessage(message, NetworkDelivery.ReliableSequenced, m_TargetClientIds); + NetworkManager.NetworkMetrics.TrackObjectDestroySent(m_TargetClientIds, networkObject.NetworkObjectId, networkObject.name, size); } } } @@ -696,42 +642,6 @@ internal void OnDespawnObject(NetworkObject networkObject, bool destroyGameObjec } } - /// - /// This will write all client observable NetworkObjects to the 's stream while also - /// adding the client to each 's list only if - /// observable to the client. - /// Maximum number of objects that could theoretically be serialized is 65536 for now - /// - /// the client identifier used to determine if a spawned NetworkObject is observable - /// contains the writer used for serialization - internal void SerializeObservedNetworkObjects(ulong clientId, NetworkWriter writer) - { - var stream = writer.GetStream(); - var headPosition = stream.Position; - var numberOfObjects = (ushort)0; - - // Write our count place holder(must not be packed!) - writer.WriteUInt16(0); - - foreach (var sobj in SpawnedObjectsList) - { - if (sobj.CheckObjectVisibility == null || sobj.CheckObjectVisibility(clientId)) - { - sobj.Observers.Add(clientId); - sobj.SerializeSceneObject(writer, clientId); - numberOfObjects++; - } - } - - var tailPosition = stream.Position; - // Reposition to our count position to the head before we wrote our object count - stream.Position = headPosition; - // Write number of NetworkObjects serialized (must not be packed!) - writer.WriteUInt16(numberOfObjects); - // Set our position back to the tail - stream.Position = tailPosition; - } - /// /// Updates all spawned for the specified client /// Note: if the clientId is the server then it is observable to all spawned 's diff --git a/com.unity.netcode.gameobjects/Runtime/Utility.meta b/com.unity.netcode.gameobjects/Runtime/Utility.meta new file mode 100644 index 0000000000..a71098491b --- /dev/null +++ b/com.unity.netcode.gameobjects/Runtime/Utility.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 07815748c1ee48a69d6981ec990575ed +timeCreated: 1629412677 \ No newline at end of file diff --git a/com.unity.netcode.gameobjects/Runtime/Utility/DynamicUnmanagedArray.cs b/com.unity.netcode.gameobjects/Runtime/Utility/DynamicUnmanagedArray.cs new file mode 100644 index 0000000000..f451bd2fef --- /dev/null +++ b/com.unity.netcode.gameobjects/Runtime/Utility/DynamicUnmanagedArray.cs @@ -0,0 +1,104 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using Unity.Collections; +using Unity.Collections.LowLevel.Unsafe; + +namespace Unity.Netcode +{ + public struct DynamicUnmanagedArray : IReadOnlyList, IDisposable where T : unmanaged + { + private unsafe T* m_Data; + private Allocator m_Allocator; + private int m_Length; + private int m_Capacity; + + public int Count => m_Length; + + private int Capacity => m_Capacity; + + public bool IsReadOnly => false; + + public unsafe DynamicUnmanagedArray(int capacity, Allocator allocator = Allocator.Persistent) + { + m_Data = (T*)UnsafeUtility.Malloc(capacity * sizeof(T), UnsafeUtility.AlignOf(), allocator); + m_Allocator = allocator; + m_Length = 0; + m_Capacity = capacity; + } + + public unsafe void Dispose() + { + UnsafeUtility.Free(m_Data, m_Allocator); + } + + public unsafe T this[int index] + { + get => m_Data[index]; + set => m_Data[index] = value; + } + + public unsafe ref T GetValueRef(int index) + { + return ref m_Data[index]; + } + + private unsafe void Resize() + { + m_Capacity *= 2; + var data = (T*)UnsafeUtility.Malloc(m_Capacity * sizeof(T), UnsafeUtility.AlignOf(), m_Allocator); + UnsafeUtility.MemCpy(data, m_Data, m_Length * sizeof(T)); + UnsafeUtility.Free(m_Data, m_Allocator); + m_Data = data; + } + + public unsafe void Add(T item) + { + if (m_Length == m_Capacity) + { + Resize(); + } + m_Data[m_Length++] = item; + } + + public unsafe T Pop() + { + return m_Data[--m_Length]; + } + + public void Clear() + { + m_Length = 0; + } + + public IEnumerator GetEnumerator() + { + throw new NotImplementedException(); + } + + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } + + public static unsafe Ref> CreateRef(int capacity = 16) + { + var array = + (DynamicUnmanagedArray*)UnsafeUtility.Malloc( + sizeof(DynamicUnmanagedArray), + UnsafeUtility.AlignOf>(), Allocator.Persistent); + + array->m_Data = (T*)UnsafeUtility.Malloc(capacity * sizeof(T), UnsafeUtility.AlignOf(), Allocator.Persistent); + array->m_Allocator = Allocator.Persistent; + array->m_Length = 0; + array->m_Capacity = capacity; + return new Ref>(array); + } + + public static unsafe void ReleaseRef(Ref> array) + { + array.Value.Dispose(); + UnsafeUtility.Free(array.Ptr, Allocator.Persistent); + } + } +} diff --git a/com.unity.netcode.gameobjects/Runtime/Utility/DynamicUnmanagedArray.cs.meta b/com.unity.netcode.gameobjects/Runtime/Utility/DynamicUnmanagedArray.cs.meta new file mode 100644 index 0000000000..a069f33e73 --- /dev/null +++ b/com.unity.netcode.gameobjects/Runtime/Utility/DynamicUnmanagedArray.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 521cff8a9643c6c4ab6d9ec0d4eb9a87 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/com.unity.netcode.gameobjects/Runtime/Utility/FixedUnmanagedArray.cs b/com.unity.netcode.gameobjects/Runtime/Utility/FixedUnmanagedArray.cs new file mode 100644 index 0000000000..04cac54419 --- /dev/null +++ b/com.unity.netcode.gameobjects/Runtime/Utility/FixedUnmanagedArray.cs @@ -0,0 +1,181 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Runtime.CompilerServices; +using Unity.Collections.LowLevel.Unsafe; + +namespace Unity.Netcode +{ + /// + /// For each usage of FixedUnmanagedArray, a storage class needs to be created for it. + /// Rather than providing a huge list of predefined storage classes, each use should just + /// create their own at the correct size (in bytes). The size should be an even multiple + /// of the value size being stored. + /// + /// Example: + /// + /// [StructLayout(LayoutKind.Explicit, Size = 256 * sizeof(int))] + /// struct FixedStorageInt256 : IFixedArrayStorage + /// { + /// } + /// + public interface IFixedArrayStorage + { + + } + + public struct FixedUnmanagedArray : IReadOnlyList + where TPropertyType : unmanaged + where TStorageType : unmanaged, IFixedArrayStorage + { + private int m_Length; + private TStorageType m_Data; + + public int Count + { + get { return m_Length; } + set { m_Length = value; } + } + + public unsafe int Capacity => sizeof(TStorageType) / sizeof(TPropertyType); + + public bool IsReadOnly => false; + + public unsafe TPropertyType[] ToArray() + { + var ret = new TPropertyType[Count]; + fixed (TPropertyType* b = ret) + { + fixed (TStorageType* ptr = &m_Data) + { + UnsafeUtility.MemCpy(b, ptr, Count * sizeof(TPropertyType)); + } + } + return ret; + } + + public unsafe FixedUnmanagedArray(TPropertyType* seedData, int size) + { + if (size > sizeof(TStorageType)) + { + throw new OverflowException("Seed data was larger than provided storage class."); + } + + m_Data = new TStorageType(); + fixed (TStorageType* ptr = &m_Data) + { + UnsafeUtility.MemCpy(ptr, seedData, size); + } + + m_Length = size; + } + + public unsafe FixedUnmanagedArray(TPropertyType[] seedData) + { + if (seedData.Length > sizeof(TStorageType)) + { + throw new OverflowException("Seed data was larger than provided storage class."); + } + m_Data = new TStorageType(); + fixed (TStorageType* ptr = &m_Data) + fixed (TPropertyType* seedPtr = seedData) + { + UnsafeUtility.MemCpy(ptr, seedPtr, seedData.Length); + } + + m_Length = seedData.Length; + } + + public unsafe FixedUnmanagedArray(TPropertyType[] seedData, int size) + { + if (size > sizeof(TStorageType)) + { + throw new OverflowException("Seed data was larger than provided storage class."); + } + + if (size > seedData.Length) + { + throw new ArgumentException("Size cannot be greater than seed data's length."); + } + + m_Data = new TStorageType(); + fixed (TStorageType* ptr = &m_Data) + fixed (TPropertyType* seedPtr = seedData) + { + UnsafeUtility.MemCpy(ptr, seedPtr, size); + } + + m_Length = size; + } + + public unsafe TPropertyType* GetArrayPtr() + { + fixed (TStorageType* ptr = &m_Data) + { + return (TPropertyType*)ptr; + } + } + + public unsafe TPropertyType this[int index] + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get + { + fixed (TStorageType* ptr = &m_Data) + { + var reinterpretPtr = (TPropertyType*)ptr; + return reinterpretPtr[index]; + } + } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + set + { + fixed (TStorageType* ptr = &m_Data) + { + var reinterpretPtr = (TPropertyType*)ptr; + reinterpretPtr[index] = value; + } + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public unsafe ref TPropertyType GetValueRef(int index) + { + fixed (TStorageType* ptr = &m_Data) + { + var reinterpretPtr = (TPropertyType*)ptr; + return ref reinterpretPtr[index]; + } + } + + public void Add(TPropertyType value) + { + if (m_Length == Capacity) + { + throw new OverflowException("The FixedUnmanagedArray is full."); + } + + this[m_Length++] = value; + } + + public TPropertyType Pop() + { + return this[--m_Length]; + } + + public void Clear() + { + m_Length = 0; + } + + public IEnumerator GetEnumerator() + { + throw new NotImplementedException(); + } + + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } + } +} diff --git a/com.unity.netcode.gameobjects/Runtime/Utility/FixedUnmanagedArray.cs.meta b/com.unity.netcode.gameobjects/Runtime/Utility/FixedUnmanagedArray.cs.meta new file mode 100644 index 0000000000..5f3b5fd9b4 --- /dev/null +++ b/com.unity.netcode.gameobjects/Runtime/Utility/FixedUnmanagedArray.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 7bde9c91128507449a535c7df4a029c8 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/com.unity.netcode.gameobjects/Runtime/Utility/Ref.cs b/com.unity.netcode.gameobjects/Runtime/Utility/Ref.cs new file mode 100644 index 0000000000..5a627b28f9 --- /dev/null +++ b/com.unity.netcode.gameobjects/Runtime/Utility/Ref.cs @@ -0,0 +1,36 @@ +using System.Runtime.CompilerServices; + +namespace Unity.Netcode +{ + public struct Ref where T : unmanaged + { + private unsafe T* m_Value; + + public unsafe Ref(ref T value) + { + fixed (T* ptr = &value) + { + m_Value = ptr; + } + } + + public unsafe Ref(T* ptr) + { + m_Value = ptr; + } + + public unsafe bool IsSet => m_Value != null; + + public unsafe ref T Value + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => ref *m_Value; + } + + public unsafe T* Ptr + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => m_Value; + } + } +} diff --git a/com.unity.netcode.gameobjects/Runtime/Utility/Ref.cs.meta b/com.unity.netcode.gameobjects/Runtime/Utility/Ref.cs.meta new file mode 100644 index 0000000000..56afe0844f --- /dev/null +++ b/com.unity.netcode.gameobjects/Runtime/Utility/Ref.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 00adc41a9c8699349a50dba23dbd46a2 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/com.unity.netcode.gameobjects/Tests/Editor/DummyMessageHandler.cs b/com.unity.netcode.gameobjects/Tests/Editor/DummyMessageHandler.cs deleted file mode 100644 index 8f3c4fa3b4..0000000000 --- a/com.unity.netcode.gameobjects/Tests/Editor/DummyMessageHandler.cs +++ /dev/null @@ -1,62 +0,0 @@ -using System.IO; -using UnityEngine; - -namespace Unity.Netcode.EditorTests -{ - internal class DummyMessageHandler : IInternalMessageHandler - { - public NetworkManager NetworkManager { get; } - - public DummyMessageHandler(NetworkManager networkManager) - { - NetworkManager = networkManager; - } - - public void HandleConnectionRequest(ulong clientId, Stream stream) => VerifyCalled(nameof(HandleConnectionRequest)); - - public void HandleConnectionApproved(ulong clientId, Stream stream, float receiveTime) => VerifyCalled(nameof(HandleConnectionApproved)); - - public void HandleAddObject(ulong clientId, Stream stream) => VerifyCalled(nameof(HandleAddObject)); - - public void HandleDestroyObject(ulong clientId, Stream stream) => VerifyCalled(nameof(HandleDestroyObject)); - - public void HandleSceneEvent(ulong clientId, Stream stream) => VerifyCalled(nameof(HandleSceneEvent)); - - public void HandleChangeOwner(ulong clientId, Stream stream) => VerifyCalled(nameof(HandleChangeOwner)); - - public void HandleTimeSync(ulong clientId, Stream stream) => VerifyCalled(nameof(HandleTimeSync)); - - public void HandleNetworkVariableDelta(ulong clientId, Stream stream) => VerifyCalled(nameof(HandleNetworkVariableDelta)); - - public void MessageReceiveQueueItem(ulong clientId, Stream stream, float receiveTime, MessageQueueContainer.MessageType messageType) - { - VerifyCalled(nameof(MessageReceiveQueueItem)); - if (NetworkManager) - { - // To actually process the message we have to add it to the inbound frame queue for the current update stage - // and then process and flush the queue for the current update stage to actually get it to run through - // MessageQueueContainer.ProcessMessage, which is where the actual code handling the message lives. - // That's what will then call back into this for the others. - var messageQueueContainer = NetworkManager.MessageQueueContainer; - messageQueueContainer.AddQueueItemToInboundFrame(messageType, receiveTime, clientId, (NetworkBuffer)stream); - messageQueueContainer.ProcessAndFlushMessageQueue(MessageQueueContainer.MessageQueueProcessingTypes.Receive, NetworkUpdateLoop.UpdateStage); - messageQueueContainer.AdvanceFrameHistory(MessageQueueHistoryFrame.QueueFrameType.Inbound); - } - } - - public void HandleUnnamedMessage(ulong clientId, Stream stream) => VerifyCalled(nameof(HandleUnnamedMessage)); - - public void HandleNamedMessage(ulong clientId, Stream stream) => VerifyCalled(nameof(HandleNamedMessage)); - - public void HandleNetworkLog(ulong clientId, Stream stream) => VerifyCalled(nameof(HandleNetworkLog)); - - public void HandleSnapshot(ulong clientId, Stream stream) => VerifyCalled(nameof(HandleSnapshot)); - - public void HandleAllClientsSwitchSceneCompleted(ulong clientId, Stream stream) => VerifyCalled(nameof(HandleAllClientsSwitchSceneCompleted)); - - private void VerifyCalled(string method) - { - Debug.Log(method); - } - } -} diff --git a/com.unity.netcode.gameobjects/Tests/Editor/DummyMessageHandler.cs.meta b/com.unity.netcode.gameobjects/Tests/Editor/DummyMessageHandler.cs.meta deleted file mode 100644 index 384300ede4..0000000000 --- a/com.unity.netcode.gameobjects/Tests/Editor/DummyMessageHandler.cs.meta +++ /dev/null @@ -1,11 +0,0 @@ -fileFormatVersion: 2 -guid: 1a90aa73e34ca0b4f9a5f4e3e593c6f3 -MonoImporter: - externalObjects: {} - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: - assetBundleName: - assetBundleVariant: diff --git a/com.unity.netcode.gameobjects/Tests/Editor/MessageBatcherTests.cs b/com.unity.netcode.gameobjects/Tests/Editor/MessageBatcherTests.cs deleted file mode 100644 index 5c29f2ceba..0000000000 --- a/com.unity.netcode.gameobjects/Tests/Editor/MessageBatcherTests.cs +++ /dev/null @@ -1,112 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Text; -using NUnit.Framework; - -namespace Unity.Netcode.EditorTests -{ - public class MessageBatcherTests - { - [Test] - public void SendWithThreshold() - { - const int k_BatchThreshold = 256; - const int k_QueueItemCount = 128; - - var sendBatcher = new MessageBatcher(); - var sendStreamQueue = new Queue(); - for (int i = 0; i < k_QueueItemCount; ++i) - { - var randomData = Encoding.ASCII.GetBytes(Guid.NewGuid().ToString()); - var queueItem = new MessageFrameItem - { - NetworkId = 123, - ClientNetworkIds = new ulong[] { 123 }, - Delivery = NetworkDelivery.Reliable, - MessageType = i % 2 == 0 ? MessageQueueContainer.MessageType.ServerRpc : MessageQueueContainer.MessageType.ClientRpc, - MessageData = new ArraySegment(randomData, 0, randomData.Length) - }; - sendBatcher.QueueItem( - queueItem.ClientNetworkIds, - queueItem, - k_BatchThreshold, - (networkId, sendStream) => - { - var queueStream = new NetworkBuffer(); - sendStream.Buffer.CopyTo(queueStream); - sendStreamQueue.Enqueue(queueStream); - }); - } - - // batch the rest - sendBatcher.SendItems( /* thresholdBytes = */ 0, - (networkId, sendStream) => - { - var queueStream = new NetworkBuffer(); - sendStream.Buffer.CopyTo(queueStream); - sendStreamQueue.Enqueue(queueStream); - }); - - var recvBatcher = new MessageBatcher(); - var recvItemCounter = 0; - foreach (var recvStream in sendStreamQueue) - { - recvStream.Position = 0; - recvBatcher.ReceiveItems(recvStream, (stream, type, id, time) => ++recvItemCounter, default, default); - } - - Assert.AreEqual(k_QueueItemCount, recvItemCounter); - } - - [Test] - public void SendWithoutThreshold() - { - const int k_BatchThreshold = 0; - const int k_QueueItemCount = 128; - - var sendBatcher = new MessageBatcher(); - var sendStreamQueue = new Queue(); - for (int i = 0; i < k_QueueItemCount; ++i) - { - var randomData = Encoding.ASCII.GetBytes(Guid.NewGuid().ToString()); - var queueItem = new MessageFrameItem - { - NetworkId = 123, - ClientNetworkIds = new ulong[] { 123 }, - Delivery = NetworkDelivery.Reliable, - MessageType = i % 2 == 0 ? MessageQueueContainer.MessageType.ServerRpc : MessageQueueContainer.MessageType.ClientRpc, - MessageData = new ArraySegment(randomData, 0, randomData.Length) - }; - sendBatcher.QueueItem( - queueItem.ClientNetworkIds, - queueItem, - k_BatchThreshold, - (networkId, sendStream) => - { - var queueStream = new NetworkBuffer(); - sendStream.Buffer.CopyTo(queueStream); - sendStreamQueue.Enqueue(queueStream); - }); - } - - // batch the rest - sendBatcher.SendItems( /* thresholdBytes = */ 0, - (networkId, sendStream) => - { - var queueStream = new NetworkBuffer(); - sendStream.Buffer.CopyTo(queueStream); - sendStreamQueue.Enqueue(queueStream); - }); - - var recvBatcher = new MessageBatcher(); - var recvItemCounter = 0; - foreach (var recvStream in sendStreamQueue) - { - recvStream.Position = 0; - recvBatcher.ReceiveItems(recvStream, (stream, type, id, time) => ++recvItemCounter, default, default); - } - - Assert.AreEqual(k_QueueItemCount, recvItemCounter); - } - } -} diff --git a/com.unity.netcode.gameobjects/Tests/Editor/MessageBatcherTests.cs.meta b/com.unity.netcode.gameobjects/Tests/Editor/MessageBatcherTests.cs.meta deleted file mode 100644 index 067c563c08..0000000000 --- a/com.unity.netcode.gameobjects/Tests/Editor/MessageBatcherTests.cs.meta +++ /dev/null @@ -1,11 +0,0 @@ -fileFormatVersion: 2 -guid: 696f48f7bbcaf9f40b72311d4b6da74c -MonoImporter: - externalObjects: {} - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: - assetBundleName: - assetBundleVariant: diff --git a/com.unity.netcode.gameobjects/Tests/Editor/MessagePacker.cs b/com.unity.netcode.gameobjects/Tests/Editor/MessagePacker.cs deleted file mode 100644 index a9ff025854..0000000000 --- a/com.unity.netcode.gameobjects/Tests/Editor/MessagePacker.cs +++ /dev/null @@ -1,25 +0,0 @@ -namespace Unity.Netcode.EditorTests -{ - internal static class MessagePacker - { - internal static NetworkBuffer WrapMessage(MessageQueueContainer.MessageType messageType, NetworkBuffer messageBody, bool useBatching) - { - var outBuffer = PooledNetworkBuffer.Get(); - var outStream = PooledNetworkWriter.Get(outBuffer); - { - if (useBatching) - { - // write the amounts of bytes that are coming up - MessageBatcher.PushLength((int)messageBody.Length, ref outStream); - - // write the message to send - outStream.WriteBytes(messageBody.GetBuffer(), messageBody.Length); - } - outStream.WriteByte((byte)messageType); - outStream.WriteByte((byte)NetworkUpdateLoop.UpdateStage); - } - outStream.Dispose(); - return outBuffer; - } - } -} diff --git a/com.unity.netcode.gameobjects/Tests/Editor/MessagePacker.cs.meta b/com.unity.netcode.gameobjects/Tests/Editor/MessagePacker.cs.meta deleted file mode 100644 index bba14bbdec..0000000000 --- a/com.unity.netcode.gameobjects/Tests/Editor/MessagePacker.cs.meta +++ /dev/null @@ -1,11 +0,0 @@ -fileFormatVersion: 2 -guid: 4b721568f27649bdb006cd201500309f -MonoImporter: - externalObjects: {} - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: - assetBundleName: - assetBundleVariant: diff --git a/com.unity.netcode.gameobjects/Tests/Editor/Messaging.meta b/com.unity.netcode.gameobjects/Tests/Editor/Messaging.meta new file mode 100644 index 0000000000..c03aaa7bb4 --- /dev/null +++ b/com.unity.netcode.gameobjects/Tests/Editor/Messaging.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 55531000b0344935b665541f089df60e +timeCreated: 1630354914 \ No newline at end of file diff --git a/com.unity.netcode.gameobjects/Tests/Editor/Messaging/MessageReceivingTests.cs b/com.unity.netcode.gameobjects/Tests/Editor/Messaging/MessageReceivingTests.cs new file mode 100644 index 0000000000..9794ca415e --- /dev/null +++ b/com.unity.netcode.gameobjects/Tests/Editor/Messaging/MessageReceivingTests.cs @@ -0,0 +1,202 @@ +using System; +using System.Collections.Generic; +using NUnit.Framework; +using NUnit.Framework.Internal; +using Unity.Collections; +using Unity.Collections.LowLevel.Unsafe; + +namespace Unity.Netcode.EditorTests +{ + public class MessageReceivingTests + { + [Bind(typeof(MessageReceivingTests))] + private struct TestMessage : INetworkMessage + { + public int A; + public int B; + public int C; + public static bool Deserialized; + public static List DeserializedValues = new List(); + + public void Serialize(ref FastBufferWriter writer) + { + writer.WriteValueSafe(this); + } + + public static void Receive(ref FastBufferReader reader, NetworkContext context) + { + Deserialized = true; + reader.ReadValueSafe(out TestMessage value); + DeserializedValues.Add(value); + } + } + + private MessagingSystem m_MessagingSystem; + + [SetUp] + public void SetUp() + { + TestMessage.Deserialized = false; + TestMessage.DeserializedValues.Clear(); + + m_MessagingSystem = new MessagingSystem(new NopMessageSender(), this); + } + + [TearDown] + public void TearDown() + { + m_MessagingSystem.Dispose(); + } + + private TestMessage GetMessage() + { + var random = new Random(); + return new TestMessage + { + A = random.Next(), + B = random.Next(), + C = random.Next(), + }; + } + + [Test] + public void WhenHandlingAMessage_ReceiveMethodIsCalled() + { + var messageHeader = new MessageHeader + { + MessageSize = (short)UnsafeUtility.SizeOf(), + MessageType = m_MessagingSystem.GetMessageType(typeof(TestMessage)), + }; + var message = GetMessage(); + + var writer = new FastBufferWriter(1300, Allocator.Temp); + using (writer) + { + writer.TryBeginWrite(FastBufferWriter.GetWriteSize(message)); + writer.WriteValue(message); + + var reader = new FastBufferReader(ref writer, Allocator.Temp); + using (reader) + { + m_MessagingSystem.HandleMessage(messageHeader, ref reader, 0, 0); + Assert.IsTrue(TestMessage.Deserialized); + Assert.AreEqual(1, TestMessage.DeserializedValues.Count); + Assert.AreEqual(message, TestMessage.DeserializedValues[0]); + } + } + } + + [Test] + public void WhenHandlingIncomingData_ReceiveIsNotCalledBeforeProcessingIncomingMessageQueue() + { + var batchHeader = new BatchHeader + { + BatchSize = 1 + }; + var messageHeader = new MessageHeader + { + MessageSize = (short)UnsafeUtility.SizeOf(), + MessageType = m_MessagingSystem.GetMessageType(typeof(TestMessage)), + }; + var message = GetMessage(); + + var writer = new FastBufferWriter(1300, Allocator.Temp); + using (writer) + { + writer.TryBeginWrite(FastBufferWriter.GetWriteSize(batchHeader) + + FastBufferWriter.GetWriteSize(messageHeader) + + FastBufferWriter.GetWriteSize(message)); + writer.WriteValue(batchHeader); + writer.WriteValue(messageHeader); + writer.WriteValue(message); + + var reader = new FastBufferReader(ref writer, Allocator.Temp); + using (reader) + { + m_MessagingSystem.HandleIncomingData(0, new ArraySegment(writer.ToArray()), 0); + Assert.IsFalse(TestMessage.Deserialized); + Assert.IsEmpty(TestMessage.DeserializedValues); ; + } + } + } + + [Test] + public void WhenReceivingAMessageAndProcessingMessageQueue_ReceiveMethodIsCalled() + { + var batchHeader = new BatchHeader + { + BatchSize = 1 + }; + var messageHeader = new MessageHeader + { + MessageSize = (short)UnsafeUtility.SizeOf(), + MessageType = m_MessagingSystem.GetMessageType(typeof(TestMessage)), + }; + var message = GetMessage(); + + var writer = new FastBufferWriter(1300, Allocator.Temp); + using (writer) + { + writer.TryBeginWrite(FastBufferWriter.GetWriteSize(batchHeader) + + FastBufferWriter.GetWriteSize(messageHeader) + + FastBufferWriter.GetWriteSize(message)); + writer.WriteValue(batchHeader); + writer.WriteValue(messageHeader); + writer.WriteValue(message); + + var reader = new FastBufferReader(ref writer, Allocator.Temp); + using (reader) + { + m_MessagingSystem.HandleIncomingData(0, new ArraySegment(writer.ToArray()), 0); + m_MessagingSystem.ProcessIncomingMessageQueue(); + Assert.IsTrue(TestMessage.Deserialized); + Assert.AreEqual(1, TestMessage.DeserializedValues.Count); + Assert.AreEqual(message, TestMessage.DeserializedValues[0]); + } + } + } + + [Test] + public void WhenReceivingMultipleMessagesAndProcessingMessageQueue_ReceiveMethodIsCalledMultipleTimes() + { + var batchHeader = new BatchHeader + { + BatchSize = 2 + }; + var messageHeader = new MessageHeader + { + MessageSize = (short)UnsafeUtility.SizeOf(), + MessageType = m_MessagingSystem.GetMessageType(typeof(TestMessage)), + }; + var message = GetMessage(); + var message2 = GetMessage(); + + var writer = new FastBufferWriter(1300, Allocator.Temp); + using (writer) + { + writer.TryBeginWrite(FastBufferWriter.GetWriteSize(batchHeader) + + FastBufferWriter.GetWriteSize(messageHeader) * 2 + + FastBufferWriter.GetWriteSize(message) * 2); + writer.WriteValue(batchHeader); + writer.WriteValue(messageHeader); + writer.WriteValue(message); + writer.WriteValue(messageHeader); + writer.WriteValue(message2); + + var reader = new FastBufferReader(ref writer, Allocator.Temp); + using (reader) + { + m_MessagingSystem.HandleIncomingData(0, new ArraySegment(writer.ToArray()), 0); + Assert.IsFalse(TestMessage.Deserialized); + Assert.IsEmpty(TestMessage.DeserializedValues); + + m_MessagingSystem.ProcessIncomingMessageQueue(); + Assert.IsTrue(TestMessage.Deserialized); + Assert.AreEqual(2, TestMessage.DeserializedValues.Count); + Assert.AreEqual(message, TestMessage.DeserializedValues[0]); + Assert.AreEqual(message2, TestMessage.DeserializedValues[1]); + } + } + } + } +} diff --git a/com.unity.netcode.gameobjects/Tests/Editor/Messaging/MessageReceivingTests.cs.meta b/com.unity.netcode.gameobjects/Tests/Editor/Messaging/MessageReceivingTests.cs.meta new file mode 100644 index 0000000000..45178e3b8d --- /dev/null +++ b/com.unity.netcode.gameobjects/Tests/Editor/Messaging/MessageReceivingTests.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: cbc8fb6cf75f52d46a5d74971ce4b240 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/com.unity.netcode.gameobjects/Tests/Editor/Messaging/MessageRegistrationTests.cs b/com.unity.netcode.gameobjects/Tests/Editor/Messaging/MessageRegistrationTests.cs new file mode 100644 index 0000000000..5aba160e07 --- /dev/null +++ b/com.unity.netcode.gameobjects/Tests/Editor/Messaging/MessageRegistrationTests.cs @@ -0,0 +1,149 @@ + +using NUnit.Framework; + +namespace Unity.Netcode.EditorTests +{ + public class MessageRegistrationTests + { + private class MessagingSystemOwnerOne + { + + } + + private class MessagingSystemOwnerTwo + { + + } + + [Bind(typeof(MessagingSystemOwnerOne))] + private struct TestMessageOne : INetworkMessage + { + public int A; + public int B; + public int C; + + public void Serialize(ref FastBufferWriter writer) + { + writer.WriteValue(this); + } + + public static void Receive(ref FastBufferReader reader, NetworkContext context) + { + + } + } + + [Bind(typeof(MessagingSystemOwnerOne))] + private struct TestMessageTwo : INetworkMessage + { + public int A; + public int B; + public int C; + + public void Serialize(ref FastBufferWriter writer) + { + writer.WriteValue(this); + } + + public static void Receive(ref FastBufferReader reader, NetworkContext context) + { + + } + } + + [Bind(typeof(MessagingSystemOwnerTwo))] + private struct TestMessageThree : INetworkMessage + { + public int A; + public int B; + public int C; + + public void Serialize(ref FastBufferWriter writer) + { + writer.WriteValue(this); + } + + public static void Receive(ref FastBufferReader reader, NetworkContext context) + { + + } + } + + [Bind(null)] + private struct TestMessageFour : INetworkMessage + { + public int A; + public int B; + public int C; + + public void Serialize(ref FastBufferWriter writer) + { + writer.WriteValue(this); + } + + public static void Receive(ref FastBufferReader reader, NetworkContext context) + { + + } + } + + [Test] + public void WhenCreatingMessageSystem_OnlyBoundTypesAreRegistered() + { + var ownerOne = new MessagingSystemOwnerOne(); + var ownerTwo = new MessagingSystemOwnerTwo(); + var sender = new NopMessageSender(); + + var systemOne = new MessagingSystem(sender, ownerOne); + var systemTwo = new MessagingSystem(sender, ownerTwo); + var systemThree = new MessagingSystem(sender, null); + + using (systemOne) + using (systemTwo) + using (systemThree) + { + Assert.AreEqual(2, systemOne.MessageHandlerCount); + Assert.AreEqual(1, systemTwo.MessageHandlerCount); + Assert.AreEqual(1, systemThree.MessageHandlerCount); + + Assert.Contains(typeof(TestMessageOne), systemOne.MessageTypes); + Assert.Contains(typeof(TestMessageTwo), systemOne.MessageTypes); + Assert.Contains(typeof(TestMessageThree), systemTwo.MessageTypes); + Assert.Contains(typeof(TestMessageFour), systemThree.MessageTypes); + } + } + + [Test] + public void WhenCreatingMessageSystem_BoundTypeMessageHandlersAreRegistered() + { + var ownerOne = new MessagingSystemOwnerOne(); + var ownerTwo = new MessagingSystemOwnerTwo(); + var sender = new NopMessageSender(); + + var systemOne = new MessagingSystem(sender, ownerOne); + var systemTwo = new MessagingSystem(sender, ownerTwo); + var systemThree = new MessagingSystem(sender, null); + + using (systemOne) + using (systemTwo) + using (systemThree) + { + MessagingSystem.MessageHandler handlerOne = TestMessageOne.Receive; + MessagingSystem.MessageHandler handlerTwo = TestMessageTwo.Receive; + MessagingSystem.MessageHandler handlerThree = TestMessageThree.Receive; + MessagingSystem.MessageHandler handlerFour = TestMessageFour.Receive; + + var foundHandlerOne = systemOne.MessageHandlers[systemOne.GetMessageType(typeof(TestMessageOne))]; + + Assert.AreEqual(handlerOne, + systemOne.MessageHandlers[systemOne.GetMessageType(typeof(TestMessageOne))]); + Assert.AreEqual(handlerTwo, + systemOne.MessageHandlers[systemOne.GetMessageType(typeof(TestMessageTwo))]); + Assert.AreEqual(handlerThree, + systemTwo.MessageHandlers[systemTwo.GetMessageType(typeof(TestMessageThree))]); + Assert.AreEqual(handlerFour, + systemThree.MessageHandlers[systemThree.GetMessageType(typeof(TestMessageFour))]); + } + } + } +} diff --git a/com.unity.netcode.gameobjects/Tests/Editor/Messaging/MessageRegistrationTests.cs.meta b/com.unity.netcode.gameobjects/Tests/Editor/Messaging/MessageRegistrationTests.cs.meta new file mode 100644 index 0000000000..860ab64387 --- /dev/null +++ b/com.unity.netcode.gameobjects/Tests/Editor/Messaging/MessageRegistrationTests.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 03e7cc2b6f0c5c540a529429f48529f5 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/com.unity.netcode.gameobjects/Tests/Editor/Messaging/MessageSendingTests.cs b/com.unity.netcode.gameobjects/Tests/Editor/Messaging/MessageSendingTests.cs new file mode 100644 index 0000000000..419b2ba6b2 --- /dev/null +++ b/com.unity.netcode.gameobjects/Tests/Editor/Messaging/MessageSendingTests.cs @@ -0,0 +1,209 @@ +using System; +using System.Collections.Generic; +using NUnit.Framework; +using Unity.Collections; +using Unity.Collections.LowLevel.Unsafe; + +namespace Unity.Netcode.EditorTests +{ + public class MessageSendingTests + { + [Bind(typeof(MessageSendingTests))] + private struct TestMessage : INetworkMessage + { + public int A; + public int B; + public int C; + public static bool Serialized; + + public void Serialize(ref FastBufferWriter writer) + { + Serialized = true; + writer.WriteValueSafe(this); + } + + public static void Receive(ref FastBufferReader reader, NetworkContext context) + { + } + } + + private class TestMessageSender : IMessageSender + { + public List MessageQueue = new List(); + + public void Send(ulong clientId, NetworkDelivery delivery, ref FastBufferWriter batchData) + { + MessageQueue.Add(batchData.ToArray()); + } + } + + private TestMessageSender m_MessageSender; + private MessagingSystem m_MessagingSystem; + private ulong[] m_Clients = { 0 }; + + [SetUp] + public void SetUp() + { + TestMessage.Serialized = false; + + m_MessageSender = new TestMessageSender(); + m_MessagingSystem = new MessagingSystem(m_MessageSender, this); + m_MessagingSystem.ClientConnected(0); + } + + [TearDown] + public void TearDown() + { + m_MessagingSystem.Dispose(); + } + + private TestMessage GetMessage() + { + var random = new Random(); + return new TestMessage + { + A = random.Next(), + B = random.Next(), + C = random.Next(), + }; + } + + [Test] + public void WhenSendingMessage_SerializeIsCalled() + { + var message = GetMessage(); + m_MessagingSystem.SendMessage(message, NetworkDelivery.Reliable, m_Clients); + Assert.IsTrue(TestMessage.Serialized); + } + + [Test] + public void WhenSendingMessage_NothingIsSentBeforeProcessingSendQueue() + { + var message = GetMessage(); + m_MessagingSystem.SendMessage(message, NetworkDelivery.Reliable, m_Clients); + Assert.IsEmpty(m_MessageSender.MessageQueue); + } + + [Test] + public void WhenProcessingSendQueue_MessageIsSent() + { + var message = GetMessage(); + m_MessagingSystem.SendMessage(message, NetworkDelivery.Reliable, m_Clients); + + m_MessagingSystem.ProcessSendQueues(); + Assert.AreEqual(1, m_MessageSender.MessageQueue.Count); + } + + [Test] + public void WhenSendingMultipleMessages_MessagesAreBatched() + { + var message = GetMessage(); + m_MessagingSystem.SendMessage(message, NetworkDelivery.Reliable, m_Clients); + m_MessagingSystem.SendMessage(message, NetworkDelivery.Reliable, m_Clients); + m_MessagingSystem.SendMessage(message, NetworkDelivery.Reliable, m_Clients); + + m_MessagingSystem.ProcessSendQueues(); + Assert.AreEqual(1, m_MessageSender.MessageQueue.Count); + } + + [Test] + public void WhenNotExceedingBatchSize_NewBatchesAreNotCreated() + { + var message = GetMessage(); + var size = UnsafeUtility.SizeOf() + UnsafeUtility.SizeOf(); + for (var i = 0; i < 1300 / size; ++i) + { + m_MessagingSystem.SendMessage(message, NetworkDelivery.Reliable, m_Clients); + } + + m_MessagingSystem.ProcessSendQueues(); + Assert.AreEqual(1, m_MessageSender.MessageQueue.Count); + } + + [Test] + public void WhenExceedingBatchSize_NewBatchesAreCreated() + { + var message = GetMessage(); + var size = UnsafeUtility.SizeOf() + UnsafeUtility.SizeOf(); + for (var i = 0; i < (1300 / size) + 1; ++i) + { + m_MessagingSystem.SendMessage(message, NetworkDelivery.Reliable, m_Clients); + } + + m_MessagingSystem.ProcessSendQueues(); + Assert.AreEqual(2, m_MessageSender.MessageQueue.Count); + } + + [Test] + public void WhenExceedingMTUSizeWithFragmentedDelivery_NewBatchesAreNotCreated() + { + var message = GetMessage(); + var size = UnsafeUtility.SizeOf() + UnsafeUtility.SizeOf(); + for (var i = 0; i < (1300 / size) + 1; ++i) + { + m_MessagingSystem.SendMessage(message, NetworkDelivery.ReliableFragmentedSequenced, m_Clients); + } + + m_MessagingSystem.ProcessSendQueues(); + Assert.AreEqual(1, m_MessageSender.MessageQueue.Count); + } + + [Test] + public void WhenSwitchingDelivery_NewBatchesAreCreated() + { + var message = GetMessage(); + m_MessagingSystem.SendMessage(message, NetworkDelivery.Reliable, m_Clients); + m_MessagingSystem.SendMessage(message, NetworkDelivery.Reliable, m_Clients); + m_MessagingSystem.SendMessage(message, NetworkDelivery.Unreliable, m_Clients); + + m_MessagingSystem.ProcessSendQueues(); + Assert.AreEqual(2, m_MessageSender.MessageQueue.Count); + } + + [Test] + public void WhenSwitchingChannel_NewBatchesAreNotCreated() + { + var message = GetMessage(); + m_MessagingSystem.SendMessage(message, NetworkDelivery.Reliable, m_Clients); + m_MessagingSystem.SendMessage(message, NetworkDelivery.Reliable, m_Clients); + m_MessagingSystem.SendMessage(message, NetworkDelivery.Reliable, m_Clients); + + m_MessagingSystem.ProcessSendQueues(); + Assert.AreEqual(1, m_MessageSender.MessageQueue.Count); + } + + [Test] + public void WhenSendingMessaged_SentDataIsCorrect() + { + var message = GetMessage(); + var message2 = GetMessage(); + m_MessagingSystem.SendMessage(message, NetworkDelivery.Reliable, m_Clients); + m_MessagingSystem.SendMessage(message2, NetworkDelivery.Reliable, m_Clients); + + m_MessagingSystem.ProcessSendQueues(); + var reader = new FastBufferReader(m_MessageSender.MessageQueue[0], Allocator.Temp); + using (reader) + { + reader.TryBeginRead( + FastBufferWriter.GetWriteSize() + + FastBufferWriter.GetWriteSize() * 2 + + FastBufferWriter.GetWriteSize() * 2 + ); + reader.ReadValue(out BatchHeader header); + Assert.AreEqual(2, header.BatchSize); + + reader.ReadValue(out MessageHeader messageHeader); + Assert.AreEqual(m_MessagingSystem.GetMessageType(typeof(TestMessage)), messageHeader.MessageType); + Assert.AreEqual(UnsafeUtility.SizeOf(), messageHeader.MessageSize); + reader.ReadValue(out TestMessage receivedMessage); + Assert.AreEqual(message, receivedMessage); + + reader.ReadValue(out MessageHeader messageHeader2); + Assert.AreEqual(m_MessagingSystem.GetMessageType(typeof(TestMessage)), messageHeader2.MessageType); + Assert.AreEqual(UnsafeUtility.SizeOf(), messageHeader2.MessageSize); + reader.ReadValue(out TestMessage receivedMessage2); + Assert.AreEqual(message2, receivedMessage2); + } + } + } +} diff --git a/com.unity.netcode.gameobjects/Tests/Editor/Messaging/MessageSendingTests.cs.meta b/com.unity.netcode.gameobjects/Tests/Editor/Messaging/MessageSendingTests.cs.meta new file mode 100644 index 0000000000..858913264b --- /dev/null +++ b/com.unity.netcode.gameobjects/Tests/Editor/Messaging/MessageSendingTests.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: ea2fc218c5e07c54795fc9bed4a6a62c +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/com.unity.netcode.gameobjects/Tests/Editor/Messaging/NopMessageSender.cs b/com.unity.netcode.gameobjects/Tests/Editor/Messaging/NopMessageSender.cs new file mode 100644 index 0000000000..e6236196f4 --- /dev/null +++ b/com.unity.netcode.gameobjects/Tests/Editor/Messaging/NopMessageSender.cs @@ -0,0 +1,9 @@ +namespace Unity.Netcode.EditorTests +{ + internal class NopMessageSender : IMessageSender + { + public void Send(ulong clientId, NetworkDelivery delivery, ref FastBufferWriter batchData) + { + } + } +} diff --git a/com.unity.netcode.gameobjects/Tests/Editor/Messaging/NopMessageSender.cs.meta b/com.unity.netcode.gameobjects/Tests/Editor/Messaging/NopMessageSender.cs.meta new file mode 100644 index 0000000000..321f2a7771 --- /dev/null +++ b/com.unity.netcode.gameobjects/Tests/Editor/Messaging/NopMessageSender.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 817c58672ba39a74da57082ed176956e +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/com.unity.netcode.gameobjects/Tests/Editor/NetworkBufferTests.cs b/com.unity.netcode.gameobjects/Tests/Editor/NetworkBufferTests.cs deleted file mode 100644 index 7930176667..0000000000 --- a/com.unity.netcode.gameobjects/Tests/Editor/NetworkBufferTests.cs +++ /dev/null @@ -1,913 +0,0 @@ -using System; -using NUnit.Framework; -using System.Text; - -namespace Unity.Netcode.EditorTests -{ - public class NetworkBufferTests - { - [Test] - public void TestEmptyStream() - { - var networkBuffer = new NetworkBuffer(new byte[100]); - Assert.That(networkBuffer.Length, Is.EqualTo(100)); - } - - [Test] - public void TestBool() - { - var networkBuffer = new NetworkBuffer(new byte[100]); - networkBuffer.WriteBit(true); - Assert.That(networkBuffer.Length, Is.EqualTo(100)); - } - - [Test] - public void TestSetLength() - { - var networkBuffer = new NetworkBuffer(4); - networkBuffer.SetLength(100); - - Assert.That(networkBuffer.Capacity, Is.GreaterThanOrEqualTo(100)); - } - - [Test] - public void TestSetLength2() - { - var networkBuffer = new NetworkBuffer(4); - - networkBuffer.WriteByte(1); - networkBuffer.WriteByte(1); - networkBuffer.WriteByte(1); - networkBuffer.WriteByte(1); - - networkBuffer.SetLength(0); - - // position should never go beyond length - Assert.That(networkBuffer.Position, Is.EqualTo(0)); - } - - [Test] - public void TestGrow() - { - // stream should not grow when given a buffer - var networkBuffer = new NetworkBuffer(new byte[0]); - var networkWriter = new NetworkWriter(networkBuffer); - Assert.That(() => { networkWriter.WriteInt64(long.MaxValue); }, Throws.TypeOf()); - } - - [Test] - public void TestInOutBool() - { - var buffer = new byte[100]; - - var outNetworkBuffer = new NetworkBuffer(buffer); - outNetworkBuffer.WriteBit(true); - outNetworkBuffer.WriteBit(false); - outNetworkBuffer.WriteBit(true); - - - // the bit should now be stored in the buffer, lets see if it comes out - - var inNetworkBuffer = new NetworkBuffer(buffer); - - Assert.That(inNetworkBuffer.ReadBit(), Is.True); - Assert.That(inNetworkBuffer.ReadBit(), Is.False); - Assert.That(inNetworkBuffer.ReadBit(), Is.True); - } - - - [Test] - public void TestIntOutPacked16Bit() - { - short svalue = -31934; - ushort uvalue = 64893; - var outNetworkBuffer = new NetworkBuffer(); - var outNetworkWriter = new NetworkWriter(outNetworkBuffer); - outNetworkWriter.WriteInt16Packed(svalue); - outNetworkWriter.WriteUInt16Packed(uvalue); - - var inNetworkBuffer = new NetworkBuffer(outNetworkBuffer.GetBuffer()); - var inNetworkReader = new NetworkReader(inNetworkBuffer); - Assert.That(inNetworkReader.ReadInt16Packed(), Is.EqualTo(svalue)); - Assert.That(inNetworkReader.ReadUInt16Packed(), Is.EqualTo(uvalue)); - } - - - [Test] - public void TestIntOutPacked32Bit() - { - int svalue = -100913642; - uint uvalue = 1467867235; - var outNetworkBuffer = new NetworkBuffer(); - var outNetworkWriter = new NetworkWriter(outNetworkBuffer); - outNetworkWriter.WriteInt32Packed(svalue); - outNetworkWriter.WriteUInt32Packed(uvalue); - - var inNetworkBuffer = new NetworkBuffer(outNetworkBuffer.GetBuffer()); - var inNetworkReader = new NetworkReader(inNetworkBuffer); - Assert.That(inNetworkReader.ReadInt32Packed(), Is.EqualTo(svalue)); - Assert.That(inNetworkReader.ReadUInt32Packed(), Is.EqualTo(uvalue)); - } - - - [Test] - public void TestInOutPacked64Bit() - { - var buffer = new byte[100]; - - long someNumber = -1469598103934656037; - ulong uNumber = 81246971249124124; - ulong uNumber2 = 2287; - ulong uNumber3 = 235; - - var outNetworkBuffer = new NetworkBuffer(buffer); - var outNetworkWriter = new NetworkWriter(outNetworkBuffer); - outNetworkWriter.WriteInt64Packed(someNumber); - outNetworkWriter.WriteUInt64Packed(uNumber); - outNetworkWriter.WriteUInt64Packed(uNumber2); - outNetworkWriter.WriteUInt64Packed(uNumber3); - - // the bit should now be stored in the buffer, lets see if it comes out - - var inNetworkBuffer = new NetworkBuffer(buffer); - var inNetworkReader = new NetworkReader(inNetworkBuffer); - - Assert.That(inNetworkReader.ReadInt64Packed(), Is.EqualTo(someNumber)); - Assert.That(inNetworkReader.ReadUInt64Packed(), Is.EqualTo(uNumber)); - Assert.That(inNetworkReader.ReadUInt64Packed(), Is.EqualTo(uNumber2)); - Assert.That(inNetworkReader.ReadUInt64Packed(), Is.EqualTo(uNumber3)); - } - - [Test] - public void TestStreamCopy() - { - var inNetworkBuffer = new NetworkBuffer(); - var copyNetworkBuffer = new NetworkBuffer(); - - byte initialValue1 = 56; - byte initialValue2 = 24; - - inNetworkBuffer.WriteByte(initialValue1); - inNetworkBuffer.WriteByte(initialValue2); - - byte copyValue1 = 27; - byte copyValue2 = 100; - - copyNetworkBuffer.WriteByte(copyValue1); - copyNetworkBuffer.WriteByte(copyValue2); - - inNetworkBuffer.CopyFrom(copyNetworkBuffer, 2); - - var outNetworkBuffer = new NetworkBuffer(inNetworkBuffer.ToArray()); - - Assert.That(outNetworkBuffer.ReadByte(), Is.EqualTo(initialValue1)); - Assert.That(outNetworkBuffer.ReadByte(), Is.EqualTo(initialValue2)); - Assert.That(outNetworkBuffer.ReadByte(), Is.EqualTo(copyValue1)); - Assert.That(outNetworkBuffer.ReadByte(), Is.EqualTo(copyValue2)); - } - - [Test] - public void TestToArray() - { - var inNetworkBuffer = new NetworkBuffer(); - inNetworkBuffer.WriteByte(5); - inNetworkBuffer.WriteByte(6); - Assert.That(inNetworkBuffer.ToArray().Length, Is.EqualTo(2)); - } - - - [Test] - public void TestInOutBytes() - { - var buffer = new byte[100]; - byte someNumber = 0xff; - - var outNetworkBuffer = new NetworkBuffer(buffer); - outNetworkBuffer.WriteByte(someNumber); - - var inNetworkBuffer = new NetworkBuffer(buffer); - Assert.That(inNetworkBuffer.ReadByte(), Is.EqualTo(someNumber)); - } - - [Test] - public void TestInOutInt16() - { - var buffer = new byte[100]; - short someNumber = 23223; - - var outNetworkBuffer = new NetworkBuffer(buffer); - var outNetworkWriter = new NetworkWriter(outNetworkBuffer); - outNetworkWriter.WriteInt16(someNumber); - - var inNetworkBuffer = new NetworkBuffer(buffer); - var inNetworkReader = new NetworkReader(inNetworkBuffer); - short result = inNetworkReader.ReadInt16(); - - Assert.That(result, Is.EqualTo(someNumber)); - } - - [Test] - public void TestInOutInt32() - { - var buffer = new byte[100]; - int someNumber = 23234223; - - var outNetworkBuffer = new NetworkBuffer(buffer); - var outNetworkWriter = new NetworkWriter(outNetworkBuffer); - outNetworkWriter.WriteInt32(someNumber); - - var inNetworkBuffer = new NetworkBuffer(buffer); - var inNetworkReader = new NetworkReader(inNetworkBuffer); - int result = inNetworkReader.ReadInt32(); - - Assert.That(result, Is.EqualTo(someNumber)); - } - - [Test] - public void TestInOutInt64() - { - var buffer = new byte[100]; - long someNumber = 4614256656552045848; - - var outNetworkBuffer = new NetworkBuffer(buffer); - var outNetworkWriter = new NetworkWriter(outNetworkBuffer); - outNetworkWriter.WriteInt64(someNumber); - - var inNetworkBuffer = new NetworkBuffer(buffer); - var inNetworkReader = new NetworkReader(inNetworkBuffer); - long result = inNetworkReader.ReadInt64(); - - Assert.That(result, Is.EqualTo(someNumber)); - } - - [Test] - public void TestInOutMultiple() - { - var buffer = new byte[100]; - short someNumber = -12423; - short someNumber2 = 9322; - - var outNetworkBuffer = new NetworkBuffer(buffer); - var outNetworkWriter = new NetworkWriter(outNetworkBuffer); - outNetworkWriter.WriteInt16(someNumber); - outNetworkWriter.WriteInt16(someNumber2); - - - // the bit should now be stored in the buffer, lets see if it comes out - - var inNetworkBuffer = new NetworkBuffer(buffer); - var inNetworkReader = new NetworkReader(inNetworkBuffer); - short result = inNetworkReader.ReadInt16(); - short result2 = inNetworkReader.ReadInt16(); - - Assert.That(result, Is.EqualTo(someNumber)); - Assert.That(result2, Is.EqualTo(someNumber2)); - } - - [Test] - public void TestLength() - { - var inNetworkBuffer = new NetworkBuffer(4); - Assert.That(inNetworkBuffer.Length, Is.EqualTo(0)); - inNetworkBuffer.WriteByte(1); - Assert.That(inNetworkBuffer.Length, Is.EqualTo(1)); - inNetworkBuffer.WriteByte(2); - Assert.That(inNetworkBuffer.Length, Is.EqualTo(2)); - inNetworkBuffer.WriteByte(3); - Assert.That(inNetworkBuffer.Length, Is.EqualTo(3)); - inNetworkBuffer.WriteByte(4); - Assert.That(inNetworkBuffer.Length, Is.EqualTo(4)); - } - - [Test] - public void TestCapacityGrowth() - { - var inNetworkBuffer = new NetworkBuffer(4); - Assert.That(inNetworkBuffer.Capacity, Is.EqualTo(4)); - - inNetworkBuffer.WriteByte(1); - inNetworkBuffer.WriteByte(2); - inNetworkBuffer.WriteByte(3); - inNetworkBuffer.WriteByte(4); - inNetworkBuffer.WriteByte(5); - - // buffer should grow and the reported length - // should not waste any space - // note MemoryStream makes a distinction between Length and Capacity - Assert.That(inNetworkBuffer.Length, Is.EqualTo(5)); - Assert.That(inNetworkBuffer.Capacity, Is.GreaterThanOrEqualTo(5)); - } - - [Test] - public void TestWriteSingle() - { - float somenumber = 0.1f; - var outNetworkBuffer = new NetworkBuffer(); - var outNetworkWriter = new NetworkWriter(outNetworkBuffer); - - outNetworkWriter.WriteSingle(somenumber); - var outBuffer = outNetworkBuffer.GetBuffer(); - - var inNetworkBuffer = new NetworkBuffer(outBuffer); - var inNetworkReader = new NetworkReader(inNetworkBuffer); - - Assert.That(inNetworkReader.ReadSingle(), Is.EqualTo(somenumber)); - } - - [Test] - public void TestWriteDouble() - { - double somenumber = Math.PI; - var outNetworkBuffer = new NetworkBuffer(); - var outNetworkWriter = new NetworkWriter(outNetworkBuffer); - - outNetworkWriter.WriteDouble(somenumber); - var outBuffer = outNetworkBuffer.GetBuffer(); - - var inNetworkBuffer = new NetworkBuffer(outBuffer); - var inNetworkReader = new NetworkReader(inNetworkBuffer); - - Assert.That(inNetworkReader.ReadDouble(), Is.EqualTo(somenumber)); - } - - [Test] - public void TestRangedSingle() - { - const float rangeMinA = -180; - const float rangeMaxA = 180; - const int rangeBytesA = 2; - - float randFloatValueA = UnityEngine.Random.Range(rangeMinA, rangeMaxA); - - var outNetworkBufferA = new NetworkBuffer(); - var outNetworkWriterA = new NetworkWriter(outNetworkBufferA); - - outNetworkWriterA.WriteRangedSingle(randFloatValueA, rangeMinA, rangeMaxA, rangeBytesA); - outNetworkWriterA.WriteRangedSingle(rangeMinA, rangeMinA, rangeMaxA, rangeBytesA); - outNetworkWriterA.WriteRangedSingle(rangeMaxA, rangeMinA, rangeMaxA, rangeBytesA); - - var rawBufferBytesA = outNetworkBufferA.GetBuffer(); - - var inNetworkBufferA = new NetworkBuffer(rawBufferBytesA); - var inNetworkReaderA = new NetworkReader(inNetworkBufferA); - - Assert.That(Math.Abs(randFloatValueA - inNetworkReaderA.ReadRangedSingle(rangeMinA, rangeMaxA, rangeBytesA)), Is.LessThan(1.6f)); - Assert.That(Math.Abs(rangeMinA - inNetworkReaderA.ReadRangedSingle(rangeMinA, rangeMaxA, rangeBytesA)), Is.LessThan(1.6f)); - Assert.That(Math.Abs(rangeMaxA - inNetworkReaderA.ReadRangedSingle(rangeMinA, rangeMaxA, rangeBytesA)), Is.LessThan(1.6f)); - - - - const float rangeMinB = 0; - const float rangeMaxB = 360; - const int rangeBytesB = 4; - - float randFloatValueB = UnityEngine.Random.Range(rangeMinB, rangeMaxB); - - var outNetworkBufferB = new NetworkBuffer(); - var outNetworkWriterB = new NetworkWriter(outNetworkBufferB); - - outNetworkWriterB.WriteRangedSingle(randFloatValueB, rangeMinB, rangeMaxB, rangeBytesB); - outNetworkWriterB.WriteRangedSingle(rangeMinB, rangeMinB, rangeMaxB, rangeBytesB); - outNetworkWriterB.WriteRangedSingle(rangeMaxB, rangeMinB, rangeMaxB, rangeBytesB); - - var rawBufferBytesB = outNetworkBufferB.GetBuffer(); - - var inNetworkBufferB = new NetworkBuffer(rawBufferBytesB); - var inNetworkReaderB = new NetworkReader(inNetworkBufferB); - - Assert.That(Math.Abs(randFloatValueB - inNetworkReaderB.ReadRangedSingle(rangeMinB, rangeMaxB, rangeBytesB)), Is.LessThan(0.4f)); - Assert.That(Math.Abs(rangeMinB - inNetworkReaderB.ReadRangedSingle(rangeMinB, rangeMaxB, rangeBytesB)), Is.LessThan(0.4f)); - Assert.That(Math.Abs(rangeMaxB - inNetworkReaderB.ReadRangedSingle(rangeMinB, rangeMaxB, rangeBytesB)), Is.LessThan(0.4f)); - } - - [Test] - public void TestRangedDouble() - { - const double rangeMinA = -180; - const double rangeMaxA = 180; - const int rangeBytesA = 2; - - double randDoubleValueA = new Random().NextDouble() * (rangeMaxA - rangeMinA) + rangeMinA; - - var outNetworkBufferA = new NetworkBuffer(); - var outNetworkWriterA = new NetworkWriter(outNetworkBufferA); - - outNetworkWriterA.WriteRangedDouble(randDoubleValueA, rangeMinA, rangeMaxA, rangeBytesA); - outNetworkWriterA.WriteRangedDouble(rangeMinA, rangeMinA, rangeMaxA, rangeBytesA); - outNetworkWriterA.WriteRangedDouble(rangeMaxA, rangeMinA, rangeMaxA, rangeBytesA); - - var rawBufferBytesA = outNetworkBufferA.GetBuffer(); - - var inNetworkBufferA = new NetworkBuffer(rawBufferBytesA); - var inNetworkReaderA = new NetworkReader(inNetworkBufferA); - - Assert.That(Math.Abs(randDoubleValueA - inNetworkReaderA.ReadRangedDouble(rangeMinA, rangeMaxA, rangeBytesA)), Is.LessThan(1.6f)); - Assert.That(Math.Abs(rangeMinA - inNetworkReaderA.ReadRangedDouble(rangeMinA, rangeMaxA, rangeBytesA)), Is.LessThan(1.6f)); - Assert.That(Math.Abs(rangeMaxA - inNetworkReaderA.ReadRangedDouble(rangeMinA, rangeMaxA, rangeBytesA)), Is.LessThan(1.6f)); - - - - const double rangeMinB = 0; - const double rangeMaxB = 360; - const int rangeBytesB = 4; - - double randDoubleValueB = new Random().NextDouble() * (rangeMaxB - rangeMinB) + rangeMinB; - - var outNetworkBufferB = new NetworkBuffer(); - var outNetworkWriterB = new NetworkWriter(outNetworkBufferB); - - outNetworkWriterB.WriteRangedDouble(randDoubleValueB, rangeMinB, rangeMaxB, rangeBytesB); - outNetworkWriterB.WriteRangedDouble(rangeMinB, rangeMinB, rangeMaxB, rangeBytesB); - outNetworkWriterB.WriteRangedDouble(rangeMaxB, rangeMinB, rangeMaxB, rangeBytesB); - - var rawBufferBytesB = outNetworkBufferB.GetBuffer(); - - var inNetworkBufferB = new NetworkBuffer(rawBufferBytesB); - var inNetworkReaderB = new NetworkReader(inNetworkBufferB); - - Assert.That(Math.Abs(randDoubleValueB - inNetworkReaderB.ReadRangedDouble(rangeMinB, rangeMaxB, rangeBytesB)), Is.LessThan(0.4f)); - Assert.That(Math.Abs(rangeMinB - inNetworkReaderB.ReadRangedDouble(rangeMinB, rangeMaxB, rangeBytesB)), Is.LessThan(0.4f)); - Assert.That(Math.Abs(rangeMaxB - inNetworkReaderB.ReadRangedDouble(rangeMinB, rangeMaxB, rangeBytesB)), Is.LessThan(0.4f)); - } - - [Test] - public void TestWritePackedSingle() - { - float somenumber = (float)Math.PI; - var outNetworkBuffer = new NetworkBuffer(); - var outNetworkWriter = new NetworkWriter(outNetworkBuffer); - - outNetworkWriter.WriteSinglePacked(somenumber); - var buffer = outNetworkBuffer.GetBuffer(); - - var inNetworkBuffer = new NetworkBuffer(buffer); - var inNetworkReader = new NetworkReader(inNetworkBuffer); - - Assert.That(inNetworkReader.ReadSinglePacked(), Is.EqualTo(somenumber)); - } - - [Test] - public void TestWritePackedDouble() - { - double somenumber = Math.PI; - var outNetworkBuffer = new NetworkBuffer(); - var outNetworkWriter = new NetworkWriter(outNetworkBuffer); - - outNetworkWriter.WriteDoublePacked(somenumber); - var outBuffer = outNetworkBuffer.GetBuffer(); - - var inNetworkBuffer = new NetworkBuffer(outBuffer); - var inNetworkReader = new NetworkReader(inNetworkBuffer); - - Assert.That(inNetworkReader.ReadDoublePacked(), Is.EqualTo(somenumber)); - } - - [Test] - public void TestWriteMisaligned() - { - var outNetworkBuffer = new NetworkBuffer(); - var outNetworkWriter = new NetworkWriter(outNetworkBuffer); - outNetworkWriter.WriteBit(true); - outNetworkWriter.WriteBit(false); - // now the stream is misalligned, lets write some bytes - outNetworkWriter.WriteByte(244); - outNetworkWriter.WriteByte(123); - outNetworkWriter.WriteInt16(-5457); - outNetworkWriter.WriteUInt64(4773753249); - outNetworkWriter.WriteUInt64Packed(5435285812313212); - outNetworkWriter.WriteInt64Packed(-5435285812313212); - outNetworkWriter.WriteBit(true); - outNetworkWriter.WriteByte(1); - outNetworkWriter.WriteByte(0); - - var outBuffer = outNetworkBuffer.GetBuffer(); - - var inNetworkBuffer = new NetworkBuffer(outBuffer); - var inNetworkReader = new NetworkReader(inNetworkBuffer); - - Assert.That(inNetworkReader.ReadBit(), Is.True); - Assert.That(inNetworkReader.ReadBit(), Is.False); - Assert.That(inNetworkReader.ReadByte(), Is.EqualTo(244)); - Assert.That(inNetworkReader.ReadByte(), Is.EqualTo(123)); - Assert.That(inNetworkReader.ReadInt16(), Is.EqualTo(-5457)); - Assert.That(inNetworkReader.ReadUInt64(), Is.EqualTo(4773753249)); - Assert.That(inNetworkReader.ReadUInt64Packed(), Is.EqualTo(5435285812313212)); - Assert.That(inNetworkReader.ReadInt64Packed(), Is.EqualTo(-5435285812313212)); - Assert.That(inNetworkReader.ReadBit(), Is.True); - Assert.That(inNetworkReader.ReadByte(), Is.EqualTo(1)); - Assert.That(inNetworkReader.ReadByte(), Is.EqualTo(0)); - } - - [Test] - public void TestBits() - { - ulong somevalue = 0b1100101010011; - - var outNetworkBuffer = new NetworkBuffer(); - var outNetworkWriter = new NetworkWriter(outNetworkBuffer); - outNetworkWriter.WriteBits(somevalue, 5); - - var outBuffer = outNetworkBuffer.GetBuffer(); - - var inNetworkBuffer = new NetworkBuffer(outBuffer); - var inNetworkReader = new NetworkReader(inNetworkBuffer); - - Assert.That(inNetworkReader.ReadBits(5), Is.EqualTo(0b10011)); - } - - [Test] - public void TestNibble() - { - byte somevalue = 0b1010011; - - var outNetworkBuffer = new NetworkBuffer(); - var outNetworkWriter = new NetworkWriter(outNetworkBuffer); - outNetworkWriter.WriteNibble(somevalue); - - var outBuffer = outNetworkBuffer.GetBuffer(); - - var inNetworkBuffer = new NetworkBuffer(outBuffer); - var inNetworkReader = new NetworkReader(inNetworkBuffer); - - Assert.That(inNetworkReader.ReadNibble(), Is.EqualTo(0b0011)); - } - - [Test] - public void TestReadWriteMissaligned() - { - var outNetworkBuffer = new NetworkBuffer(); - var outNetworkWriter = new NetworkWriter(outNetworkBuffer); - outNetworkWriter.WriteBit(true); - var writeBuffer = new byte[16] - { - 0, - 5, - 2, - 54, - 192, - 60, - 214, - 65, - 95, - 2, - 43, - 62, - 252, - 190, - 45, - 2 - }; - outNetworkBuffer.Write(writeBuffer); - - var inNetworkBuffer = new NetworkBuffer(outNetworkBuffer.GetBuffer()); - Assert.That(inNetworkBuffer.ReadBit(), Is.True); - var readBuffer = new byte[16]; - inNetworkBuffer.Read(readBuffer, 0, 16); - Assert.That(readBuffer, Is.EquivalentTo(writeBuffer)); - } - - [Test] - public void TestArrays() - { - var outByteArray = new byte[] - { - 1, - 2, - 13, - 37, - 69 - }; - var outIntArray = new int[] - { - 1337, - 69420, - 12345, - 0, - 0, - 5 - }; - var outDoubleArray = new double[] - { - 0.02, - 0.06, - 1E40, - 256.0 - }; - - var outNetworkBuffer = new NetworkBuffer(); - var outNetworkWriter = new NetworkWriter(outNetworkBuffer); - outNetworkWriter.WriteByteArray(outByteArray); - outNetworkWriter.WriteIntArray(outIntArray); - outNetworkWriter.WriteDoubleArray(outDoubleArray); - - var inNetworkBuffer = new NetworkBuffer(outNetworkBuffer.GetBuffer()); - var inNetworkReader = new NetworkReader(inNetworkBuffer); - var inByteArray = inNetworkReader.ReadByteArray(); - var inIntArray = inNetworkReader.ReadIntArray(); - var inDoubleArray = inNetworkReader.ReadDoubleArray(); - - Assert.That(outByteArray, Is.EqualTo(inByteArray)); - Assert.That(outIntArray, Is.EqualTo(inIntArray)); - Assert.That(outDoubleArray, Is.EqualTo(inDoubleArray)); - } - - [Test] - public void TestArraysPacked() - { - var outShortArray = new short[] - { - 1, - 2, - 13, - 37, - 69 - }; - var outIntArray = new int[] - { - 1337, - 69420, - 12345, - 0, - 0, - 5 - }; - var outDoubleArray = new double[] - { - 0.02, - 0.06, - 1E40, - 256.0 - }; - - var outNetworkBuffer = new NetworkBuffer(); - var outNetworkWriter = new NetworkWriter(outNetworkBuffer); - outNetworkWriter.WriteShortArrayPacked(outShortArray); - outNetworkWriter.WriteIntArrayPacked(outIntArray); - outNetworkWriter.WriteDoubleArrayPacked(outDoubleArray); - - var inNetworkBuffer = new NetworkBuffer(outNetworkBuffer.GetBuffer()); - var inNetworkReader = new NetworkReader(inNetworkBuffer); - var inShortArray = inNetworkReader.ReadShortArrayPacked(); - var inIntArray = inNetworkReader.ReadIntArrayPacked(); - var inDoubleArray = inNetworkReader.ReadDoubleArrayPacked(); - - Assert.That(outShortArray, Is.EqualTo(inShortArray)); - Assert.That(outIntArray, Is.EqualTo(inIntArray)); - Assert.That(outDoubleArray, Is.EqualTo(inDoubleArray)); - } - - [Test] - public void TestArraysDiff() - { - // Values changed test - var byteOutDiffData = new byte[] - { - 1, - 2, - 13, - 29, - 44, - 15 - }; - var byteOutData = new byte[] - { - 1, - 2, - 13, - 37, - 69 - }; - - // No change test - var intOutDiffData = new int[] - { - 1337, - 69420, - 12345, - 0, - 0, - 5 - }; - var intOutData = new int[] - { - 1337, - 69420, - 12345, - 0, - 0, - 5 - }; - - // Array resize test - var doubleOutDiffData = new double[] - { - 0.2, - 6, - 1E39 - }; - var doubleOutData = new double[] - { - 0.02, - 0.06, - 1E40, - 256.0 - }; - - // Serialize - var outNetworkBuffer = new NetworkBuffer(); - var outNetworkWriter = new NetworkWriter(outNetworkBuffer); - outNetworkWriter.WriteByteArrayDiff(byteOutData, byteOutDiffData); - outNetworkWriter.WriteIntArrayDiff(intOutData, intOutDiffData); - outNetworkWriter.WriteDoubleArrayDiff(doubleOutData, doubleOutDiffData); - - // Deserialize - var inNetworkBuffer = new NetworkBuffer(outNetworkBuffer.GetBuffer()); - var inNetworkReader = new NetworkReader(inNetworkBuffer); - var byteInData = inNetworkReader.ReadByteArrayDiff(byteOutDiffData); - var intInData = inNetworkReader.ReadIntArrayDiff(intOutDiffData); - var doubleInData = inNetworkReader.ReadDoubleArrayDiff(doubleOutDiffData); - - // Compare - Assert.That(byteInData, Is.EqualTo(byteOutData)); - Assert.That(intInData, Is.EqualTo(intOutData)); - Assert.That(doubleInData, Is.EqualTo(doubleOutData)); - } - - [Test] - public void TestArraysPackedDiff() - { - // Values changed test - var longOutDiffData = new long[] - { - 1, - 2, - 13, - 29, - 44, - 15 - }; - var longOutData = new long[] - { - 1, - 2, - 13, - 37, - 69 - }; - - // No change test - var intOutDiffData = new int[] - { - 1337, - 69420, - 12345, - 0, - 0, - 5 - }; - var intOutData = new int[] - { - 1337, - 69420, - 12345, - 0, - 0, - 5 - }; - - // Array resize test - var doubleOutDiffData = new double[] - { - 0.2, - 6, - 1E39 - }; - var doubleOutData = new double[] - { - 0.02, - 0.06, - 1E40, - 256.0 - }; - - // Serialize - var outNetworkBuffer = new NetworkBuffer(); - var outNetworkWriter = new NetworkWriter(outNetworkBuffer); - outNetworkWriter.WriteLongArrayPackedDiff(longOutData, longOutDiffData); - outNetworkWriter.WriteIntArrayPackedDiff(intOutData, intOutDiffData); - outNetworkWriter.WriteDoubleArrayPackedDiff(doubleOutData, doubleOutDiffData); - - // Deserialize - var inNetworkBuffer = new NetworkBuffer(outNetworkBuffer.GetBuffer()); - var inNetworkReader = new NetworkReader(inNetworkBuffer); - var longInData = inNetworkReader.ReadLongArrayPackedDiff(longOutDiffData); - var intInData = inNetworkReader.ReadIntArrayPackedDiff(intOutDiffData); - var doubleInData = inNetworkReader.ReadDoubleArrayPackedDiff(doubleOutDiffData); - - // Compare - Assert.That(longInData, Is.EqualTo(longOutData)); - Assert.That(intInData, Is.EqualTo(intOutData)); - Assert.That(doubleInData, Is.EqualTo(doubleOutData)); - } - - [Test] - public void TestString() - { - var testString = "Hello, World"; - var outNetworkBuffer = new NetworkBuffer(); - var outNetworkWriter = new NetworkWriter(outNetworkBuffer); - outNetworkWriter.WriteString(testString); - outNetworkWriter.WriteString(testString, true); - - var inNetworkBuffer = new NetworkBuffer(outNetworkBuffer.GetBuffer()); - var inNetworkReader = new NetworkReader(inNetworkBuffer); - StringBuilder readBuilder = inNetworkReader.ReadString(); - StringBuilder readBuilderSingle = inNetworkReader.ReadString(true); - - Assert.That(readBuilder.ToString(), Is.EqualTo(testString)); - Assert.That(readBuilderSingle.ToString(), Is.EqualTo(testString)); - } - - [Test] - public void TestStringPacked() - { - var testString = "Hello, World"; - var outNetworkBuffer = new NetworkBuffer(); - var outNetworkWriter = new NetworkWriter(outNetworkBuffer); - outNetworkWriter.WriteStringPacked(testString); - - var inNetworkBuffer = new NetworkBuffer(outNetworkBuffer.GetBuffer()); - var inNetworkReader = new NetworkReader(inNetworkBuffer); - var readString = inNetworkReader.ReadStringPacked(); - - Assert.That(readString, Is.EqualTo(testString)); - } - - [Test] - public void TestStringDiff() - { - var testString = "Hello, World"; // The simulated "new" value of testString - var originalString = "Heyo, World"; // This is what testString supposedly changed *from* - var outNetworkBuffer = new NetworkBuffer(); - var outNetworkWriter = new NetworkWriter(outNetworkBuffer); - outNetworkWriter.WriteStringDiff(testString, originalString); - outNetworkWriter.WriteStringDiff(testString, originalString, true); - - var inNetworkBuffer = new NetworkBuffer(outNetworkBuffer.GetBuffer()); - var inNetworkReader = new NetworkReader(inNetworkBuffer); - // Read regular diff - StringBuilder readBuilder = inNetworkReader.ReadStringDiff(originalString); - - // Read diff directly to StringBuilder - inNetworkBuffer.BitPosition = 0; - var stringCompare = new StringBuilder(originalString); - inNetworkReader.ReadStringDiff(stringCompare); - - // Read single-byte diff - StringBuilder byteBuilder = inNetworkReader.ReadStringDiff(originalString, true); - - Assert.That(readBuilder.ToString(), Is.EqualTo(testString)); - Assert.That(stringCompare.ToString(), Is.EqualTo(testString)); - Assert.That(byteBuilder.ToString(), Is.EqualTo(testString)); - } - - [Test] - public void TestStringPackedDiff() - { - var testString = "Hello, World"; // The simulated "new" value of testString - var originalString = "Heyo, World"; // This is what testString supposedly changed *from* - var outNetworkBuffer = new NetworkBuffer(); - var outNetworkWriter = new NetworkWriter(outNetworkBuffer); - outNetworkWriter.WriteStringPackedDiff(testString, originalString); - - var inNetworkBuffer = new NetworkBuffer(outNetworkBuffer.GetBuffer()); - var inNetworkReader = new NetworkReader(inNetworkBuffer); - // Read regular diff - StringBuilder readBuilder = inNetworkReader.ReadStringPackedDiff(originalString); - - // Read diff directly to StringBuilder - inNetworkBuffer.BitPosition = 0; - var stringCompare = new StringBuilder(originalString); - inNetworkReader.ReadStringPackedDiff(stringCompare); - - Assert.That(readBuilder.ToString(), Is.EqualTo(testString)); - Assert.That(stringCompare.ToString(), Is.EqualTo(testString)); - } - - [Test] - public void TestSerializationPipelineBool() - { - var buffer = new NetworkBuffer(); - var writer = new NetworkWriter(buffer); - var reader = new NetworkReader(buffer); - - writer.WriteObjectPacked(true); - writer.WriteObjectPacked(false); - - buffer.BitPosition = 0; - - Assert.That(reader.ReadObjectPacked(typeof(bool)), Is.EqualTo(true)); - Assert.That(reader.ReadObjectPacked(typeof(bool)), Is.EqualTo(false)); - } - } -} diff --git a/com.unity.netcode.gameobjects/Tests/Editor/NetworkBufferTests.cs.meta b/com.unity.netcode.gameobjects/Tests/Editor/NetworkBufferTests.cs.meta deleted file mode 100644 index 04162c07fa..0000000000 --- a/com.unity.netcode.gameobjects/Tests/Editor/NetworkBufferTests.cs.meta +++ /dev/null @@ -1,11 +0,0 @@ -fileFormatVersion: 2 -guid: ed2672ae96472b0e487aae6e77522ee7 -MonoImporter: - externalObjects: {} - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: - assetBundleName: - assetBundleVariant: diff --git a/com.unity.netcode.gameobjects/Tests/Editor/NetworkManagerMessageHandlerTests.cs b/com.unity.netcode.gameobjects/Tests/Editor/NetworkManagerMessageHandlerTests.cs index c70e3d9ded..7ae72dbd17 100644 --- a/com.unity.netcode.gameobjects/Tests/Editor/NetworkManagerMessageHandlerTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Editor/NetworkManagerMessageHandlerTests.cs @@ -1,211 +1,8 @@ using System; -using NUnit.Framework; using Unity.Netcode.Editor; -using UnityEngine; -using UnityEngine.TestTools; -using Object = UnityEngine.Object; namespace Unity.Netcode.EditorTests { - public class NetworkManagerMessageHandlerTests - { - [Test] - public void MessageHandlerReceivedMessageServerClient() - { - // Init - var gameObject = new GameObject(nameof(MessageHandlerReceivedMessageServerClient)); - var networkManager = gameObject.AddComponent(); - var transport = gameObject.AddComponent(); - - networkManager.NetworkConfig = new NetworkConfig(); - // Set dummy transport that does nothing - networkManager.NetworkConfig.NetworkTransport = transport; - - // Replace the real message handler with a dummy one that just prints a result - networkManager.MessageHandler = new DummyMessageHandler(networkManager); - - // Have to force the update stage to something valid. It starts at Unset. - NetworkUpdateLoop.UpdateStage = NetworkUpdateStage.Update; - - using var inputBuffer = new NetworkBuffer(); - // Start server since pre-message-handler passes IsServer & IsClient checks - networkManager.StartServer(); - - // Disable batching to make the RPCs come straight through - // This has to be done post start - networkManager.MessageQueueContainer.EnableBatchedMessages(false); - - // Should cause log (server only) - // Everything should log MessageReceiveQueueItem even if ignored - LogAssert.Expect(LogType.Log, nameof(DummyMessageHandler.MessageReceiveQueueItem)); - LogAssert.Expect(LogType.Log, nameof(DummyMessageHandler.HandleConnectionRequest)); - using var messageStream0 = MessagePacker.WrapMessage(MessageQueueContainer.MessageType.ConnectionRequest, inputBuffer, networkManager.MessageQueueContainer.IsUsingBatching()); - networkManager.HandleIncomingData(0, new ArraySegment(messageStream0.GetBuffer(), 0, (int)messageStream0.Length), 0); - - // Should not cause log (client only) - // Everything should log MessageReceiveQueueItem even if ignored - LogAssert.Expect(LogType.Log, nameof(DummyMessageHandler.MessageReceiveQueueItem)); - using var messageStream1 = MessagePacker.WrapMessage(MessageQueueContainer.MessageType.ConnectionApproved, inputBuffer, networkManager.MessageQueueContainer.IsUsingBatching()); - networkManager.HandleIncomingData(0, new ArraySegment(messageStream1.GetBuffer(), 0, (int)messageStream1.Length), 0); - - // Should not cause log (client only) - // Everything should log MessageReceiveQueueItem even if ignored - LogAssert.Expect(LogType.Log, nameof(DummyMessageHandler.MessageReceiveQueueItem)); - using var messageStream2 = MessagePacker.WrapMessage(MessageQueueContainer.MessageType.CreateObject, inputBuffer, networkManager.MessageQueueContainer.IsUsingBatching()); - networkManager.HandleIncomingData(0, new ArraySegment(messageStream2.GetBuffer(), 0, (int)messageStream2.Length), 0); - - // Should not cause log (client only) - // Everything should log MessageReceiveQueueItem even if ignored - LogAssert.Expect(LogType.Log, nameof(DummyMessageHandler.MessageReceiveQueueItem)); - using var messageStream3 = MessagePacker.WrapMessage(MessageQueueContainer.MessageType.DestroyObject, inputBuffer, networkManager.MessageQueueContainer.IsUsingBatching()); - networkManager.HandleIncomingData(0, new ArraySegment(messageStream3.GetBuffer(), 0, (int)messageStream3.Length), 0); - - // Should not cause log (client only) - // Everything should log MessageReceiveQueueItem even if ignored - LogAssert.Expect(LogType.Log, nameof(DummyMessageHandler.MessageReceiveQueueItem)); - LogAssert.Expect(LogType.Log, nameof(DummyMessageHandler.HandleSceneEvent)); - using var messageStream4 = MessagePacker.WrapMessage(MessageQueueContainer.MessageType.SceneEvent, inputBuffer, networkManager.MessageQueueContainer.IsUsingBatching()); - networkManager.HandleIncomingData(0, new ArraySegment(messageStream4.GetBuffer(), 0, (int)messageStream4.Length), 0); - - // Should not cause log (client only) - // Everything should log MessageReceiveQueueItem even if ignored - LogAssert.Expect(LogType.Log, nameof(DummyMessageHandler.MessageReceiveQueueItem)); - using var messageStream5 = MessagePacker.WrapMessage(MessageQueueContainer.MessageType.ChangeOwner, inputBuffer, networkManager.MessageQueueContainer.IsUsingBatching()); - networkManager.HandleIncomingData(0, new ArraySegment(messageStream5.GetBuffer(), 0, (int)messageStream5.Length), 0); - - // Should not cause log (client only) - // Everything should log MessageReceiveQueueItem even if ignored - LogAssert.Expect(LogType.Log, nameof(DummyMessageHandler.MessageReceiveQueueItem)); - using var messageStream6 = MessagePacker.WrapMessage(MessageQueueContainer.MessageType.TimeSync, inputBuffer, networkManager.MessageQueueContainer.IsUsingBatching()); - networkManager.HandleIncomingData(0, new ArraySegment(messageStream6.GetBuffer(), 0, (int)messageStream6.Length), 0); - - // Should cause log (server and client) - // Everything should log MessageReceiveQueueItem even if ignored - LogAssert.Expect(LogType.Log, nameof(DummyMessageHandler.MessageReceiveQueueItem)); - LogAssert.Expect(LogType.Log, nameof(DummyMessageHandler.HandleNetworkVariableDelta)); - using var messageStream7 = MessagePacker.WrapMessage(MessageQueueContainer.MessageType.NetworkVariableDelta, inputBuffer, networkManager.MessageQueueContainer.IsUsingBatching()); - networkManager.HandleIncomingData(0, new ArraySegment(messageStream7.GetBuffer(), 0, (int)messageStream7.Length), 0); - - // Should cause log (server and client) - // Everything should log MessageReceiveQueueItem even if ignored - LogAssert.Expect(LogType.Log, nameof(DummyMessageHandler.MessageReceiveQueueItem)); - LogAssert.Expect(LogType.Log, nameof(DummyMessageHandler.HandleUnnamedMessage)); - using var messageStream8 = MessagePacker.WrapMessage(MessageQueueContainer.MessageType.UnnamedMessage, inputBuffer, networkManager.MessageQueueContainer.IsUsingBatching()); - networkManager.HandleIncomingData(0, new ArraySegment(messageStream8.GetBuffer(), 0, (int)messageStream8.Length), 0); - - // Should cause log (server and client) - // Everything should log MessageReceiveQueueItem even if ignored - LogAssert.Expect(LogType.Log, nameof(DummyMessageHandler.MessageReceiveQueueItem)); - LogAssert.Expect(LogType.Log, nameof(DummyMessageHandler.HandleNamedMessage)); - using var messageStream9 = MessagePacker.WrapMessage(MessageQueueContainer.MessageType.NamedMessage, inputBuffer, networkManager.MessageQueueContainer.IsUsingBatching()); - networkManager.HandleIncomingData(0, new ArraySegment(messageStream9.GetBuffer(), 0, (int)messageStream9.Length), 0); - - // Should cause log (server only) - // Everything should log MessageReceiveQueueItem even if ignored - LogAssert.Expect(LogType.Log, nameof(DummyMessageHandler.MessageReceiveQueueItem)); - LogAssert.Expect(LogType.Log, nameof(DummyMessageHandler.HandleNetworkLog)); - using var messageStream10 = MessagePacker.WrapMessage(MessageQueueContainer.MessageType.ServerLog, inputBuffer, networkManager.MessageQueueContainer.IsUsingBatching()); - networkManager.HandleIncomingData(0, new ArraySegment(messageStream10.GetBuffer(), 0, (int)messageStream10.Length), 0); - - // Stop server to trigger full shutdown - networkManager.Shutdown(); - - // Replace the real message handler with a dummy one that just prints a result - networkManager.MessageHandler = new DummyMessageHandler(networkManager); - - // Start client since pre-message-handler passes IsServer & IsClient checks - networkManager.StartClient(); - - // Disable batching to make the RPCs come straight through - // This has to be done post start (and post restart since the queue container is reset) - networkManager.MessageQueueContainer.EnableBatchedMessages(false); - - // Should not cause log (server only) - // Everything should log MessageReceiveQueueItem even if ignored - LogAssert.Expect(LogType.Log, nameof(DummyMessageHandler.MessageReceiveQueueItem)); - using var messageStream11 = MessagePacker.WrapMessage(MessageQueueContainer.MessageType.ConnectionRequest, inputBuffer, networkManager.MessageQueueContainer.IsUsingBatching()); - networkManager.HandleIncomingData(0, new ArraySegment(messageStream11.GetBuffer(), 0, (int)messageStream11.Length), 0); - - // Should cause log (client only) - // Everything should log MessageReceiveQueueItem even if ignored - LogAssert.Expect(LogType.Log, nameof(DummyMessageHandler.MessageReceiveQueueItem)); - LogAssert.Expect(LogType.Log, nameof(DummyMessageHandler.HandleConnectionApproved)); - using var messageStream12 = MessagePacker.WrapMessage(MessageQueueContainer.MessageType.ConnectionApproved, inputBuffer, networkManager.MessageQueueContainer.IsUsingBatching()); - networkManager.HandleIncomingData(0, new ArraySegment(messageStream12.GetBuffer(), 0, (int)messageStream12.Length), 0); - - // Should cause log (client only) - // Everything should log MessageReceiveQueueItem even if ignored - LogAssert.Expect(LogType.Log, nameof(DummyMessageHandler.MessageReceiveQueueItem)); - LogAssert.Expect(LogType.Log, nameof(DummyMessageHandler.HandleAddObject)); - using var messageStream13 = MessagePacker.WrapMessage(MessageQueueContainer.MessageType.CreateObject, inputBuffer, networkManager.MessageQueueContainer.IsUsingBatching()); - networkManager.HandleIncomingData(0, new ArraySegment(messageStream13.GetBuffer(), 0, (int)messageStream13.Length), 0); - - // Should cause log (client only) - // Everything should log MessageReceiveQueueItem even if ignored - LogAssert.Expect(LogType.Log, nameof(DummyMessageHandler.MessageReceiveQueueItem)); - LogAssert.Expect(LogType.Log, nameof(DummyMessageHandler.HandleDestroyObject)); - using var messageStream14 = MessagePacker.WrapMessage(MessageQueueContainer.MessageType.DestroyObject, inputBuffer, networkManager.MessageQueueContainer.IsUsingBatching()); - networkManager.HandleIncomingData(0, new ArraySegment(messageStream14.GetBuffer(), 0, (int)messageStream14.Length), 0); - - // Should cause log (client only) - // Everything should log MessageReceiveQueueItem even if ignored - LogAssert.Expect(LogType.Log, nameof(DummyMessageHandler.MessageReceiveQueueItem)); - LogAssert.Expect(LogType.Log, nameof(DummyMessageHandler.HandleSceneEvent)); - using var messageStream15 = MessagePacker.WrapMessage(MessageQueueContainer.MessageType.SceneEvent, inputBuffer, networkManager.MessageQueueContainer.IsUsingBatching()); - networkManager.HandleIncomingData(0, new ArraySegment(messageStream15.GetBuffer(), 0, (int)messageStream15.Length), 0); - - // Should cause log (client only) - // Everything should log MessageReceiveQueueItem even if ignored - LogAssert.Expect(LogType.Log, nameof(DummyMessageHandler.MessageReceiveQueueItem)); - LogAssert.Expect(LogType.Log, nameof(DummyMessageHandler.HandleChangeOwner)); - using var messageStream16 = MessagePacker.WrapMessage(MessageQueueContainer.MessageType.ChangeOwner, inputBuffer, networkManager.MessageQueueContainer.IsUsingBatching()); - networkManager.HandleIncomingData(0, new ArraySegment(messageStream16.GetBuffer(), 0, (int)messageStream16.Length), 0); - - // Should cause log (client only) - // Everything should log MessageReceiveQueueItem even if ignored - LogAssert.Expect(LogType.Log, nameof(DummyMessageHandler.MessageReceiveQueueItem)); - LogAssert.Expect(LogType.Log, nameof(DummyMessageHandler.HandleTimeSync)); - using var messageStream17 = MessagePacker.WrapMessage(MessageQueueContainer.MessageType.TimeSync, inputBuffer, networkManager.MessageQueueContainer.IsUsingBatching()); - networkManager.HandleIncomingData(0, new ArraySegment(messageStream17.GetBuffer(), 0, (int)messageStream17.Length), 0); - - // Should cause log (server and client) - // Everything should log MessageReceiveQueueItem even if ignored - LogAssert.Expect(LogType.Log, nameof(DummyMessageHandler.MessageReceiveQueueItem)); - LogAssert.Expect(LogType.Log, nameof(DummyMessageHandler.HandleNetworkVariableDelta)); - using var messageStream18 = MessagePacker.WrapMessage(MessageQueueContainer.MessageType.NetworkVariableDelta, inputBuffer, networkManager.MessageQueueContainer.IsUsingBatching()); - networkManager.HandleIncomingData(0, new ArraySegment(messageStream18.GetBuffer(), 0, (int)messageStream18.Length), 0); - - // Should cause log (server and client) - // Everything should log MessageReceiveQueueItem even if ignored - LogAssert.Expect(LogType.Log, nameof(DummyMessageHandler.MessageReceiveQueueItem)); - LogAssert.Expect(LogType.Log, nameof(DummyMessageHandler.HandleUnnamedMessage)); - using var messageStream19 = MessagePacker.WrapMessage(MessageQueueContainer.MessageType.UnnamedMessage, inputBuffer, networkManager.MessageQueueContainer.IsUsingBatching()); - networkManager.HandleIncomingData(0, new ArraySegment(messageStream19.GetBuffer(), 0, (int)messageStream19.Length), 0); - - // Should cause log (server and client) - // Everything should log MessageReceiveQueueItem even if ignored - LogAssert.Expect(LogType.Log, nameof(DummyMessageHandler.MessageReceiveQueueItem)); - LogAssert.Expect(LogType.Log, nameof(DummyMessageHandler.HandleNamedMessage)); - using var messageStream20 = MessagePacker.WrapMessage(MessageQueueContainer.MessageType.NamedMessage, inputBuffer, networkManager.MessageQueueContainer.IsUsingBatching()); - networkManager.HandleIncomingData(0, new ArraySegment(messageStream20.GetBuffer(), 0, (int)messageStream20.Length), 0); - - // Should not cause log (server only) - // Everything should log MessageReceiveQueueItem even if ignored - LogAssert.Expect(LogType.Log, nameof(DummyMessageHandler.MessageReceiveQueueItem)); - using var messageStream21 = MessagePacker.WrapMessage(MessageQueueContainer.MessageType.ServerLog, inputBuffer, networkManager.MessageQueueContainer.IsUsingBatching()); - networkManager.HandleIncomingData(0, new ArraySegment(messageStream21.GetBuffer(), 0, (int)messageStream21.Length), 0); - - // Full cleanup - networkManager.Shutdown(); - - // Ensure no missmatches with expectations - LogAssert.NoUnexpectedReceived(); - - // Cleanup - Object.DestroyImmediate(gameObject); - } - } - // Should probably have one of these for more files? In the future we could use the SIPTransport? [DontShowInTransportDropdown] internal class DummyTransport : NetworkTransport diff --git a/com.unity.netcode.gameobjects/Tests/Editor/NetworkManagerMessageHandlerTests.cs.meta b/com.unity.netcode.gameobjects/Tests/Editor/NetworkManagerMessageHandlerTests.cs.meta index b455436403..5a9c34f14b 100644 --- a/com.unity.netcode.gameobjects/Tests/Editor/NetworkManagerMessageHandlerTests.cs.meta +++ b/com.unity.netcode.gameobjects/Tests/Editor/NetworkManagerMessageHandlerTests.cs.meta @@ -1,3 +1,11 @@ -fileFormatVersion: 2 +fileFormatVersion: 2 guid: 976ca592c7fa4bcb854203dfbadc0ad9 -timeCreated: 1617913395 \ No newline at end of file +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/com.unity.netcode.gameobjects/Tests/Editor/NetworkSerializerTests.cs b/com.unity.netcode.gameobjects/Tests/Editor/NetworkSerializerTests.cs deleted file mode 100644 index 45e1734372..0000000000 --- a/com.unity.netcode.gameobjects/Tests/Editor/NetworkSerializerTests.cs +++ /dev/null @@ -1,1535 +0,0 @@ -using System; -using NUnit.Framework; -using UnityEngine; - -namespace Unity.Netcode.EditorTests -{ - public class NetworkSerializerTests - { - [Test] - public void SerializeUnspawnedNetworkObject() - { - var gameObject = new GameObject(nameof(SerializeUnspawnedNetworkObject)); - var networkObject = gameObject.AddComponent(); - - try - { - using var outStream = PooledNetworkBuffer.Get(); - using var outWriter = PooledNetworkWriter.Get(outStream); - outWriter.WriteObjectPacked(networkObject); - - // we expect the exception below - Assert.Fail(); - } - catch (ArgumentException exception) - { - Assert.True(exception.Message.IndexOf("NetworkWriter cannot write NetworkObject types that are not spawned") != -1); - } - } - - [Test] - public void SerializeBool() - { - using var outStream = PooledNetworkBuffer.Get(); - using var outWriter = PooledNetworkWriter.Get(outStream); - using var inStream = PooledNetworkBuffer.Get(); - using var inReader = PooledNetworkReader.Get(inStream); - // serialize - bool outValueA = true; - bool outValueB = false; - var outSerializer = new NetworkSerializer(outWriter); - outSerializer.Serialize(ref outValueA); - outSerializer.Serialize(ref outValueB); - - // deserialize - bool inValueA = default; - bool inValueB = default; - inStream.Write(outStream.ToArray()); - inStream.Position = 0; - var inSerializer = new NetworkSerializer(inReader); - inSerializer.Serialize(ref inValueA); - inSerializer.Serialize(ref inValueB); - - // validate - Assert.AreEqual(inValueA, outValueA); - Assert.AreEqual(inValueB, outValueB); - } - - [Test] - public void SerializeChar() - { - using var outStream = PooledNetworkBuffer.Get(); - using var outWriter = PooledNetworkWriter.Get(outStream); - using var inStream = PooledNetworkBuffer.Get(); - using var inReader = PooledNetworkReader.Get(inStream); - // serialize - char outValueA = 'U'; - char outValueB = char.MinValue; - char outValueC = char.MaxValue; - var outSerializer = new NetworkSerializer(outWriter); - outSerializer.Serialize(ref outValueA); - outSerializer.Serialize(ref outValueB); - outSerializer.Serialize(ref outValueC); - - // deserialize - char inValueA = default; - char inValueB = default; - char inValueC = default; - inStream.Write(outStream.ToArray()); - inStream.Position = 0; - var inSerializer = new NetworkSerializer(inReader); - inSerializer.Serialize(ref inValueA); - inSerializer.Serialize(ref inValueB); - inSerializer.Serialize(ref inValueC); - - // validate - Assert.AreEqual(inValueA, outValueA); - Assert.AreEqual(inValueB, outValueB); - Assert.AreEqual(inValueC, outValueC); - } - - [Test] - public void SerializeSbyte() - { - using var outStream = PooledNetworkBuffer.Get(); - using var outWriter = PooledNetworkWriter.Get(outStream); - using var inStream = PooledNetworkBuffer.Get(); - using var inReader = PooledNetworkReader.Get(inStream); - // serialize - sbyte outValueA = -123; - sbyte outValueB = sbyte.MinValue; - sbyte outValueC = sbyte.MaxValue; - var outSerializer = new NetworkSerializer(outWriter); - outSerializer.Serialize(ref outValueA); - outSerializer.Serialize(ref outValueB); - outSerializer.Serialize(ref outValueC); - - // deserialize - sbyte inValueA = default; - sbyte inValueB = default; - sbyte inValueC = default; - inStream.Write(outStream.ToArray()); - inStream.Position = 0; - var inSerializer = new NetworkSerializer(inReader); - inSerializer.Serialize(ref inValueA); - inSerializer.Serialize(ref inValueB); - inSerializer.Serialize(ref inValueC); - - // validate - Assert.AreEqual(inValueA, outValueA); - Assert.AreEqual(inValueB, outValueB); - Assert.AreEqual(inValueC, outValueC); - } - - [Test] - public void SerializeByte() - { - using var outStream = PooledNetworkBuffer.Get(); - using var outWriter = PooledNetworkWriter.Get(outStream); - using var inStream = PooledNetworkBuffer.Get(); - using var inReader = PooledNetworkReader.Get(inStream); - // serialize - byte outValueA = 123; - byte outValueB = byte.MinValue; - byte outValueC = byte.MaxValue; - var outSerializer = new NetworkSerializer(outWriter); - outSerializer.Serialize(ref outValueA); - outSerializer.Serialize(ref outValueB); - outSerializer.Serialize(ref outValueC); - - // deserialize - byte inValueA = default; - byte inValueB = default; - byte inValueC = default; - inStream.Write(outStream.ToArray()); - inStream.Position = 0; - var inSerializer = new NetworkSerializer(inReader); - inSerializer.Serialize(ref inValueA); - inSerializer.Serialize(ref inValueB); - inSerializer.Serialize(ref inValueC); - - // validate - Assert.AreEqual(inValueA, outValueA); - Assert.AreEqual(inValueB, outValueB); - Assert.AreEqual(inValueC, outValueC); - } - - [Test] - public void SerializeShort() - { - using var outStream = PooledNetworkBuffer.Get(); - using var outWriter = PooledNetworkWriter.Get(outStream); - using var inStream = PooledNetworkBuffer.Get(); - using var inReader = PooledNetworkReader.Get(inStream); - // serialize - short outValueA = 12345; - short outValueB = short.MinValue; - short outValueC = short.MaxValue; - var outSerializer = new NetworkSerializer(outWriter); - outSerializer.Serialize(ref outValueA); - outSerializer.Serialize(ref outValueB); - outSerializer.Serialize(ref outValueC); - - // deserialize - short inValueA = default; - short inValueB = default; - short inValueC = default; - inStream.Write(outStream.ToArray()); - inStream.Position = 0; - var inSerializer = new NetworkSerializer(inReader); - inSerializer.Serialize(ref inValueA); - inSerializer.Serialize(ref inValueB); - inSerializer.Serialize(ref inValueC); - - // validate - Assert.AreEqual(inValueA, outValueA); - Assert.AreEqual(inValueB, outValueB); - Assert.AreEqual(inValueC, outValueC); - } - - [Test] - public void SerializeUshort() - { - using var outStream = PooledNetworkBuffer.Get(); - using var outWriter = PooledNetworkWriter.Get(outStream); - using var inStream = PooledNetworkBuffer.Get(); - using var inReader = PooledNetworkReader.Get(inStream); - // serialize - ushort outValueA = 12345; - ushort outValueB = ushort.MinValue; - ushort outValueC = ushort.MaxValue; - var outSerializer = new NetworkSerializer(outWriter); - outSerializer.Serialize(ref outValueA); - outSerializer.Serialize(ref outValueB); - outSerializer.Serialize(ref outValueC); - - // deserialize - ushort inValueA = default; - ushort inValueB = default; - ushort inValueC = default; - inStream.Write(outStream.ToArray()); - inStream.Position = 0; - var inSerializer = new NetworkSerializer(inReader); - inSerializer.Serialize(ref inValueA); - inSerializer.Serialize(ref inValueB); - inSerializer.Serialize(ref inValueC); - - // validate - Assert.AreEqual(inValueA, outValueA); - Assert.AreEqual(inValueB, outValueB); - Assert.AreEqual(inValueC, outValueC); - } - - [Test] - public void SerializeInt() - { - using var outStream = PooledNetworkBuffer.Get(); - using var outWriter = PooledNetworkWriter.Get(outStream); - using var inStream = PooledNetworkBuffer.Get(); - using var inReader = PooledNetworkReader.Get(inStream); - // serialize - int outValueA = 1234567890; - int outValueB = int.MinValue; - int outValueC = int.MaxValue; - var outSerializer = new NetworkSerializer(outWriter); - outSerializer.Serialize(ref outValueA); - outSerializer.Serialize(ref outValueB); - outSerializer.Serialize(ref outValueC); - - // deserialize - int inValueA = default; - int inValueB = default; - int inValueC = default; - inStream.Write(outStream.ToArray()); - inStream.Position = 0; - var inSerializer = new NetworkSerializer(inReader); - inSerializer.Serialize(ref inValueA); - inSerializer.Serialize(ref inValueB); - inSerializer.Serialize(ref inValueC); - - // validate - Assert.AreEqual(inValueA, outValueA); - Assert.AreEqual(inValueB, outValueB); - Assert.AreEqual(inValueC, outValueC); - } - - [Test] - public void SerializeUint() - { - using var outStream = PooledNetworkBuffer.Get(); - using var outWriter = PooledNetworkWriter.Get(outStream); - using var inStream = PooledNetworkBuffer.Get(); - using var inReader = PooledNetworkReader.Get(inStream); - // serialize - uint outValueA = 1234567890; - uint outValueB = uint.MinValue; - uint outValueC = uint.MaxValue; - var outSerializer = new NetworkSerializer(outWriter); - outSerializer.Serialize(ref outValueA); - outSerializer.Serialize(ref outValueB); - outSerializer.Serialize(ref outValueC); - - // deserialize - uint inValueA = default; - uint inValueB = default; - uint inValueC = default; - inStream.Write(outStream.ToArray()); - inStream.Position = 0; - var inSerializer = new NetworkSerializer(inReader); - inSerializer.Serialize(ref inValueA); - inSerializer.Serialize(ref inValueB); - inSerializer.Serialize(ref inValueC); - - // validate - Assert.AreEqual(inValueA, outValueA); - Assert.AreEqual(inValueB, outValueB); - Assert.AreEqual(inValueC, outValueC); - } - - [Test] - public void SerializeLong() - { - using var outStream = PooledNetworkBuffer.Get(); - using var outWriter = PooledNetworkWriter.Get(outStream); - using var inStream = PooledNetworkBuffer.Get(); - using var inReader = PooledNetworkReader.Get(inStream); - // serialize - long outValueA = 9876543210; - long outValueB = long.MinValue; - long outValueC = long.MaxValue; - var outSerializer = new NetworkSerializer(outWriter); - outSerializer.Serialize(ref outValueA); - outSerializer.Serialize(ref outValueB); - outSerializer.Serialize(ref outValueC); - - // deserialize - long inValueA = default; - long inValueB = default; - long inValueC = default; - inStream.Write(outStream.ToArray()); - inStream.Position = 0; - var inSerializer = new NetworkSerializer(inReader); - inSerializer.Serialize(ref inValueA); - inSerializer.Serialize(ref inValueB); - inSerializer.Serialize(ref inValueC); - - // validate - Assert.AreEqual(inValueA, outValueA); - Assert.AreEqual(inValueB, outValueB); - Assert.AreEqual(inValueC, outValueC); - } - - [Test] - public void SerializeUlong() - { - using var outStream = PooledNetworkBuffer.Get(); - using var outWriter = PooledNetworkWriter.Get(outStream); - using var inStream = PooledNetworkBuffer.Get(); - using var inReader = PooledNetworkReader.Get(inStream); - // serialize - ulong outValueA = 9876543210; - ulong outValueB = ulong.MinValue; - ulong outValueC = ulong.MaxValue; - var outSerializer = new NetworkSerializer(outWriter); - outSerializer.Serialize(ref outValueA); - outSerializer.Serialize(ref outValueB); - outSerializer.Serialize(ref outValueC); - - // deserialize - ulong inValueA = default; - ulong inValueB = default; - ulong inValueC = default; - inStream.Write(outStream.ToArray()); - inStream.Position = 0; - var inSerializer = new NetworkSerializer(inReader); - inSerializer.Serialize(ref inValueA); - inSerializer.Serialize(ref inValueB); - inSerializer.Serialize(ref inValueC); - - // validate - Assert.AreEqual(inValueA, outValueA); - Assert.AreEqual(inValueB, outValueB); - Assert.AreEqual(inValueC, outValueC); - } - - [Test] - public void SerializeFloat() - { - using var outStream = PooledNetworkBuffer.Get(); - using var outWriter = PooledNetworkWriter.Get(outStream); - using var inStream = PooledNetworkBuffer.Get(); - using var inReader = PooledNetworkReader.Get(inStream); - // serialize - float outValueA = 12345.6789f; - float outValueB = float.MinValue; - float outValueC = float.MaxValue; - var outSerializer = new NetworkSerializer(outWriter); - outSerializer.Serialize(ref outValueA); - outSerializer.Serialize(ref outValueB); - outSerializer.Serialize(ref outValueC); - - // deserialize - float inValueA = default; - float inValueB = default; - float inValueC = default; - inStream.Write(outStream.ToArray()); - inStream.Position = 0; - var inSerializer = new NetworkSerializer(inReader); - inSerializer.Serialize(ref inValueA); - inSerializer.Serialize(ref inValueB); - inSerializer.Serialize(ref inValueC); - - // validate - Assert.AreEqual(inValueA, outValueA); - Assert.AreEqual(inValueB, outValueB); - Assert.AreEqual(inValueC, outValueC); - } - - [Test] - public void SerializeDouble() - { - using var outStream = PooledNetworkBuffer.Get(); - using var outWriter = PooledNetworkWriter.Get(outStream); - using var inStream = PooledNetworkBuffer.Get(); - using var inReader = PooledNetworkReader.Get(inStream); - // serialize - double outValueA = 12345.6789; - double outValueB = double.MinValue; - double outValueC = double.MaxValue; - var outSerializer = new NetworkSerializer(outWriter); - outSerializer.Serialize(ref outValueA); - outSerializer.Serialize(ref outValueB); - outSerializer.Serialize(ref outValueC); - - // deserialize - double inValueA = default; - double inValueB = default; - double inValueC = default; - inStream.Write(outStream.ToArray()); - inStream.Position = 0; - var inSerializer = new NetworkSerializer(inReader); - inSerializer.Serialize(ref inValueA); - inSerializer.Serialize(ref inValueB); - inSerializer.Serialize(ref inValueC); - - // validate - Assert.AreEqual(inValueA, outValueA); - Assert.AreEqual(inValueB, outValueB); - Assert.AreEqual(inValueC, outValueC); - } - - [Test] - public void SerializeString() - { - using var outStream = PooledNetworkBuffer.Get(); - using var outWriter = PooledNetworkWriter.Get(outStream); - using var inStream = PooledNetworkBuffer.Get(); - using var inReader = PooledNetworkReader.Get(inStream); - // serialize - string outValueA = Guid.NewGuid().ToString("N"); - string outValueB = string.Empty; - string outValueC = null; - var outSerializer = new NetworkSerializer(outWriter); - outSerializer.Serialize(ref outValueA); - outSerializer.Serialize(ref outValueB); - outSerializer.Serialize(ref outValueC); - - // deserialize - string inValueA = default; - string inValueB = default; - string inValueC = default; - inStream.Write(outStream.ToArray()); - inStream.Position = 0; - var inSerializer = new NetworkSerializer(inReader); - inSerializer.Serialize(ref inValueA); - inSerializer.Serialize(ref inValueB); - inSerializer.Serialize(ref inValueC); - - // validate - Assert.AreEqual(inValueA, outValueA); - Assert.AreEqual(inValueB, outValueB); - Assert.AreEqual(inValueC, outValueC); - } - - [Test] - public void SerializeColor() - { - using var outStream = PooledNetworkBuffer.Get(); - using var outWriter = PooledNetworkWriter.Get(outStream); - using var inStream = PooledNetworkBuffer.Get(); - using var inReader = PooledNetworkReader.Get(inStream); - // serialize - Color outValueA = Color.black; - Color outValueB = Color.white; - Color outValueC = Color.red; - var outSerializer = new NetworkSerializer(outWriter); - outSerializer.Serialize(ref outValueA); - outSerializer.Serialize(ref outValueB); - outSerializer.Serialize(ref outValueC); - - // deserialize - Color inValueA = default; - Color inValueB = default; - Color inValueC = default; - inStream.Write(outStream.ToArray()); - inStream.Position = 0; - var inSerializer = new NetworkSerializer(inReader); - inSerializer.Serialize(ref inValueA); - inSerializer.Serialize(ref inValueB); - inSerializer.Serialize(ref inValueC); - - // validate - Assert.AreEqual(inValueA, outValueA); - Assert.AreEqual(inValueB, outValueB); - Assert.AreEqual(inValueC, outValueC); - } - - [Test] - public void SerializeColor32() - { - using var outStream = PooledNetworkBuffer.Get(); - using var outWriter = PooledNetworkWriter.Get(outStream); - using var inStream = PooledNetworkBuffer.Get(); - using var inReader = PooledNetworkReader.Get(inStream); - // serialize - var outValueA = new Color32(0, 0, 0, byte.MaxValue); - var outValueB = new Color32(byte.MaxValue, byte.MaxValue, byte.MaxValue, byte.MaxValue); - var outValueC = new Color32(byte.MaxValue, 0, 0, byte.MaxValue); - var outSerializer = new NetworkSerializer(outWriter); - outSerializer.Serialize(ref outValueA); - outSerializer.Serialize(ref outValueB); - outSerializer.Serialize(ref outValueC); - - // deserialize - Color32 inValueA = default; - Color32 inValueB = default; - Color32 inValueC = default; - inStream.Write(outStream.ToArray()); - inStream.Position = 0; - var inSerializer = new NetworkSerializer(inReader); - inSerializer.Serialize(ref inValueA); - inSerializer.Serialize(ref inValueB); - inSerializer.Serialize(ref inValueC); - - // validate - Assert.AreEqual(inValueA, outValueA); - Assert.AreEqual(inValueB, outValueB); - Assert.AreEqual(inValueC, outValueC); - } - - [Test] - public void SerializeVector2() - { - using var outStream = PooledNetworkBuffer.Get(); - using var outWriter = PooledNetworkWriter.Get(outStream); - using var inStream = PooledNetworkBuffer.Get(); - using var inReader = PooledNetworkReader.Get(inStream); - // serialize - Vector2 outValueA = Vector2.up; - Vector2 outValueB = Vector2.negativeInfinity; - Vector2 outValueC = Vector2.positiveInfinity; - var outSerializer = new NetworkSerializer(outWriter); - outSerializer.Serialize(ref outValueA); - outSerializer.Serialize(ref outValueB); - outSerializer.Serialize(ref outValueC); - - // deserialize - Vector2 inValueA = default; - Vector2 inValueB = default; - Vector2 inValueC = default; - inStream.Write(outStream.ToArray()); - inStream.Position = 0; - var inSerializer = new NetworkSerializer(inReader); - inSerializer.Serialize(ref inValueA); - inSerializer.Serialize(ref inValueB); - inSerializer.Serialize(ref inValueC); - - // validate - Assert.AreEqual(inValueA, outValueA); - Assert.AreEqual(inValueB, outValueB); - Assert.AreEqual(inValueC, outValueC); - } - - [Test] - public void SerializeVector3() - { - using var outStream = PooledNetworkBuffer.Get(); - using var outWriter = PooledNetworkWriter.Get(outStream); - using var inStream = PooledNetworkBuffer.Get(); - using var inReader = PooledNetworkReader.Get(inStream); - // serialize - Vector3 outValueA = Vector3.forward; - Vector3 outValueB = Vector3.negativeInfinity; - Vector3 outValueC = Vector3.positiveInfinity; - var outSerializer = new NetworkSerializer(outWriter); - outSerializer.Serialize(ref outValueA); - outSerializer.Serialize(ref outValueB); - outSerializer.Serialize(ref outValueC); - - // deserialize - Vector3 inValueA = default; - Vector3 inValueB = default; - Vector3 inValueC = default; - inStream.Write(outStream.ToArray()); - inStream.Position = 0; - var inSerializer = new NetworkSerializer(inReader); - inSerializer.Serialize(ref inValueA); - inSerializer.Serialize(ref inValueB); - inSerializer.Serialize(ref inValueC); - - // validate - Assert.AreEqual(inValueA, outValueA); - Assert.AreEqual(inValueB, outValueB); - Assert.AreEqual(inValueC, outValueC); - } - - [Test] - public void SerializeVector4() - { - using var outStream = PooledNetworkBuffer.Get(); - using var outWriter = PooledNetworkWriter.Get(outStream); - using var inStream = PooledNetworkBuffer.Get(); - using var inReader = PooledNetworkReader.Get(inStream); - // serialize - Vector4 outValueA = Vector4.one; - Vector4 outValueB = Vector4.negativeInfinity; - Vector4 outValueC = Vector4.positiveInfinity; - var outSerializer = new NetworkSerializer(outWriter); - outSerializer.Serialize(ref outValueA); - outSerializer.Serialize(ref outValueB); - outSerializer.Serialize(ref outValueC); - - // deserialize - Vector4 inValueA = default; - Vector4 inValueB = default; - Vector4 inValueC = default; - inStream.Write(outStream.ToArray()); - inStream.Position = 0; - var inSerializer = new NetworkSerializer(inReader); - inSerializer.Serialize(ref inValueA); - inSerializer.Serialize(ref inValueB); - inSerializer.Serialize(ref inValueC); - - // validate - Assert.AreEqual(inValueA, outValueA); - Assert.AreEqual(inValueB, outValueB); - Assert.AreEqual(inValueC, outValueC); - } - - [Test] - public void SerializeQuaternion() - { - using var outStream = PooledNetworkBuffer.Get(); - using var outWriter = PooledNetworkWriter.Get(outStream); - using var inStream = PooledNetworkBuffer.Get(); - using var inReader = PooledNetworkReader.Get(inStream); - // serialize - Quaternion outValueA = Quaternion.identity; - var outValueB = Quaternion.Euler(new Vector3(30, 45, -60)); - var outValueC = Quaternion.Euler(new Vector3(90, -90, 180)); - var outSerializer = new NetworkSerializer(outWriter); - outSerializer.Serialize(ref outValueA); - outSerializer.Serialize(ref outValueB); - outSerializer.Serialize(ref outValueC); - - // deserialize - Quaternion inValueA = default; - Quaternion inValueB = default; - Quaternion inValueC = default; - inStream.Write(outStream.ToArray()); - inStream.Position = 0; - var inSerializer = new NetworkSerializer(inReader); - inSerializer.Serialize(ref inValueA); - inSerializer.Serialize(ref inValueB); - inSerializer.Serialize(ref inValueC); - - // validate - Assert.Greater(Mathf.Abs(Quaternion.Dot(inValueA, outValueA)), 0.999f); - Assert.Greater(Mathf.Abs(Quaternion.Dot(inValueB, outValueB)), 0.999f); - Assert.Greater(Mathf.Abs(Quaternion.Dot(inValueC, outValueC)), 0.999f); - } - - [Test] - public void SerializeRay() - { - using var outStream = PooledNetworkBuffer.Get(); - using var outWriter = PooledNetworkWriter.Get(outStream); - using var inStream = PooledNetworkBuffer.Get(); - using var inReader = PooledNetworkReader.Get(inStream); - // serialize - var outValueA = new Ray(Vector3.zero, Vector3.forward); - var outValueB = new Ray(Vector3.zero, Vector3.left); - var outValueC = new Ray(Vector3.zero, Vector3.up); - var outSerializer = new NetworkSerializer(outWriter); - outSerializer.Serialize(ref outValueA); - outSerializer.Serialize(ref outValueB); - outSerializer.Serialize(ref outValueC); - - // deserialize - Ray inValueA = default; - Ray inValueB = default; - Ray inValueC = default; - inStream.Write(outStream.ToArray()); - inStream.Position = 0; - var inSerializer = new NetworkSerializer(inReader); - inSerializer.Serialize(ref inValueA); - inSerializer.Serialize(ref inValueB); - inSerializer.Serialize(ref inValueC); - - // validate - Assert.AreEqual(inValueA, outValueA); - Assert.AreEqual(inValueB, outValueB); - Assert.AreEqual(inValueC, outValueC); - } - - [Test] - public void SerializeRay2D() - { - using var outStream = PooledNetworkBuffer.Get(); - using var outWriter = PooledNetworkWriter.Get(outStream); - using var inStream = PooledNetworkBuffer.Get(); - using var inReader = PooledNetworkReader.Get(inStream); - // serialize - var outValueA = new Ray2D(Vector2.zero, Vector2.up); - var outValueB = new Ray2D(Vector2.zero, Vector2.left); - var outValueC = new Ray2D(Vector2.zero, Vector2.right); - var outSerializer = new NetworkSerializer(outWriter); - outSerializer.Serialize(ref outValueA); - outSerializer.Serialize(ref outValueB); - outSerializer.Serialize(ref outValueC); - - // deserialize - Ray2D inValueA = default; - Ray2D inValueB = default; - Ray2D inValueC = default; - inStream.Write(outStream.ToArray()); - inStream.Position = 0; - var inSerializer = new NetworkSerializer(inReader); - inSerializer.Serialize(ref inValueA); - inSerializer.Serialize(ref inValueB); - inSerializer.Serialize(ref inValueC); - - // validate - Assert.AreEqual(inValueA, outValueA); - Assert.AreEqual(inValueB, outValueB); - Assert.AreEqual(inValueC, outValueC); - } - - private enum EnumA // int - { - A, - B, - C - } - - private enum EnumB : byte - { - X, - Y, - Z - } - - private enum EnumC : ushort - { - U, - N, - I, - T, - Y - } - - private enum EnumD : ulong - { - N, - E, - T - } - - [Test] - public void SerializeEnum() - { - using var outStream = PooledNetworkBuffer.Get(); - using var outWriter = PooledNetworkWriter.Get(outStream); - using var inStream = PooledNetworkBuffer.Get(); - using var inReader = PooledNetworkReader.Get(inStream); - // serialize - EnumA outValueA = EnumA.C; - EnumB outValueB = EnumB.X; - EnumC outValueC = EnumC.N; - EnumD outValueD = EnumD.T; - var outValueX = (EnumD)123; - var outSerializer = new NetworkSerializer(outWriter); - outSerializer.Serialize(ref outValueA); - outSerializer.Serialize(ref outValueB); - outSerializer.Serialize(ref outValueC); - outSerializer.Serialize(ref outValueD); - outSerializer.Serialize(ref outValueX); - - // deserialize - EnumA inValueA = default; - EnumB inValueB = default; - EnumC inValueC = default; - EnumD inValueD = default; - EnumD inValueX = default; - inStream.Write(outStream.ToArray()); - inStream.Position = 0; - var inSerializer = new NetworkSerializer(inReader); - inSerializer.Serialize(ref inValueA); - inSerializer.Serialize(ref inValueB); - inSerializer.Serialize(ref inValueC); - inSerializer.Serialize(ref inValueD); - inSerializer.Serialize(ref inValueX); - - // validate - Assert.AreEqual(inValueA, outValueA); - Assert.AreEqual(inValueB, outValueB); - Assert.AreEqual(inValueC, outValueC); - Assert.AreEqual(inValueD, outValueD); - Assert.AreEqual(inValueX, outValueX); - } - - [Test] - public void SerializeBoolArray() - { - using var outStream = PooledNetworkBuffer.Get(); - using var outWriter = PooledNetworkWriter.Get(outStream); - using var inStream = PooledNetworkBuffer.Get(); - using var inReader = PooledNetworkReader.Get(inStream); - // serialize - bool[] outArrayA = null; - bool[] outArrayB = new bool[0]; - bool[] outArrayC = { true, false, true }; - var outSerializer = new NetworkSerializer(outWriter); - outSerializer.Serialize(ref outArrayA); - outSerializer.Serialize(ref outArrayB); - outSerializer.Serialize(ref outArrayC); - - // deserialize - bool[] inArrayA = default; - bool[] inArrayB = default; - bool[] inArrayC = default; - inStream.Write(outStream.ToArray()); - inStream.Position = 0; - var inSerializer = new NetworkSerializer(inReader); - inSerializer.Serialize(ref inArrayA); - inSerializer.Serialize(ref inArrayB); - inSerializer.Serialize(ref inArrayC); - - // validate - Assert.AreEqual(inArrayA, outArrayA); - Assert.AreEqual(inArrayB, outArrayB); - Assert.AreEqual(inArrayC, outArrayC); - } - - [Test] - public void SerializeCharArray() - { - using var outStream = PooledNetworkBuffer.Get(); - using var outWriter = PooledNetworkWriter.Get(outStream); - using var inStream = PooledNetworkBuffer.Get(); - using var inReader = PooledNetworkReader.Get(inStream); - // serialize - char[] outArrayA = null; - char[] outArrayB = new char[0]; - char[] outArrayC = { 'U', 'N', 'I', 'T', 'Y', '\0' }; - var outSerializer = new NetworkSerializer(outWriter); - outSerializer.Serialize(ref outArrayA); - outSerializer.Serialize(ref outArrayB); - outSerializer.Serialize(ref outArrayC); - - // deserialize - char[] inArrayA = default; - char[] inArrayB = default; - char[] inArrayC = default; - inStream.Write(outStream.ToArray()); - inStream.Position = 0; - var inSerializer = new NetworkSerializer(inReader); - inSerializer.Serialize(ref inArrayA); - inSerializer.Serialize(ref inArrayB); - inSerializer.Serialize(ref inArrayC); - - // validate - Assert.AreEqual(inArrayA, outArrayA); - Assert.AreEqual(inArrayB, outArrayB); - Assert.AreEqual(inArrayC, outArrayC); - } - - [Test] - public void SerializeSbyteArray() - { - using var outStream = PooledNetworkBuffer.Get(); - using var outWriter = PooledNetworkWriter.Get(outStream); - using var inStream = PooledNetworkBuffer.Get(); - using var inReader = PooledNetworkReader.Get(inStream); - // serialize - sbyte[] outArrayA = null; - sbyte[] outArrayB = new sbyte[0]; - sbyte[] outArrayC = { -123, sbyte.MinValue, sbyte.MaxValue }; - var outSerializer = new NetworkSerializer(outWriter); - outSerializer.Serialize(ref outArrayA); - outSerializer.Serialize(ref outArrayB); - outSerializer.Serialize(ref outArrayC); - - // deserialize - sbyte[] inArrayA = default; - sbyte[] inArrayB = default; - sbyte[] inArrayC = default; - inStream.Write(outStream.ToArray()); - inStream.Position = 0; - var inSerializer = new NetworkSerializer(inReader); - inSerializer.Serialize(ref inArrayA); - inSerializer.Serialize(ref inArrayB); - inSerializer.Serialize(ref inArrayC); - - // validate - Assert.AreEqual(inArrayA, outArrayA); - Assert.AreEqual(inArrayB, outArrayB); - Assert.AreEqual(inArrayC, outArrayC); - } - - [Test] - public void SerializeByteArray() - { - using var outStream = PooledNetworkBuffer.Get(); - using var outWriter = PooledNetworkWriter.Get(outStream); - using var inStream = PooledNetworkBuffer.Get(); - using var inReader = PooledNetworkReader.Get(inStream); - // serialize - byte[] outArrayA = null; - byte[] outArrayB = new byte[0]; - byte[] outArrayC = { 123, byte.MinValue, byte.MaxValue }; - var outSerializer = new NetworkSerializer(outWriter); - outSerializer.Serialize(ref outArrayA); - outSerializer.Serialize(ref outArrayB); - outSerializer.Serialize(ref outArrayC); - - // deserialize - byte[] inArrayA = default; - byte[] inArrayB = default; - byte[] inArrayC = default; - inStream.Write(outStream.ToArray()); - inStream.Position = 0; - var inSerializer = new NetworkSerializer(inReader); - inSerializer.Serialize(ref inArrayA); - inSerializer.Serialize(ref inArrayB); - inSerializer.Serialize(ref inArrayC); - - // validate - Assert.AreEqual(inArrayA, outArrayA); - Assert.AreEqual(inArrayB, outArrayB); - Assert.AreEqual(inArrayC, outArrayC); - } - - [Test] - public void SerializeShortArray() - { - using var outStream = PooledNetworkBuffer.Get(); - using var outWriter = PooledNetworkWriter.Get(outStream); - using var inStream = PooledNetworkBuffer.Get(); - using var inReader = PooledNetworkReader.Get(inStream); - // serialize - short[] outArrayA = null; - short[] outArrayB = new short[0]; - short[] outArrayC = { 12345, short.MinValue, short.MaxValue }; - var outSerializer = new NetworkSerializer(outWriter); - outSerializer.Serialize(ref outArrayA); - outSerializer.Serialize(ref outArrayB); - outSerializer.Serialize(ref outArrayC); - - // deserialize - short[] inArrayA = default; - short[] inArrayB = default; - short[] inArrayC = default; - inStream.Write(outStream.ToArray()); - inStream.Position = 0; - var inSerializer = new NetworkSerializer(inReader); - inSerializer.Serialize(ref inArrayA); - inSerializer.Serialize(ref inArrayB); - inSerializer.Serialize(ref inArrayC); - - // validate - Assert.AreEqual(inArrayA, outArrayA); - Assert.AreEqual(inArrayB, outArrayB); - Assert.AreEqual(inArrayC, outArrayC); - } - - [Test] - public void SerializeUshortArray() - { - using var outStream = PooledNetworkBuffer.Get(); - using var outWriter = PooledNetworkWriter.Get(outStream); - using var inStream = PooledNetworkBuffer.Get(); - using var inReader = PooledNetworkReader.Get(inStream); - // serialize - ushort[] outArrayA = null; - ushort[] outArrayB = new ushort[0]; - ushort[] outArrayC = { 12345, ushort.MinValue, ushort.MaxValue }; - var outSerializer = new NetworkSerializer(outWriter); - outSerializer.Serialize(ref outArrayA); - outSerializer.Serialize(ref outArrayB); - outSerializer.Serialize(ref outArrayC); - - // deserialize - ushort[] inArrayA = default; - ushort[] inArrayB = default; - ushort[] inArrayC = default; - inStream.Write(outStream.ToArray()); - inStream.Position = 0; - var inSerializer = new NetworkSerializer(inReader); - inSerializer.Serialize(ref inArrayA); - inSerializer.Serialize(ref inArrayB); - inSerializer.Serialize(ref inArrayC); - - // validate - Assert.AreEqual(inArrayA, outArrayA); - Assert.AreEqual(inArrayB, outArrayB); - Assert.AreEqual(inArrayC, outArrayC); - } - - [Test] - public void SerializeIntArray() - { - using var outStream = PooledNetworkBuffer.Get(); - using var outWriter = PooledNetworkWriter.Get(outStream); - using var inStream = PooledNetworkBuffer.Get(); - using var inReader = PooledNetworkReader.Get(inStream); - // serialize - int[] outArrayA = null; - int[] outArrayB = new int[0]; - int[] outArrayC = { 1234567890, int.MinValue, int.MaxValue }; - var outSerializer = new NetworkSerializer(outWriter); - outSerializer.Serialize(ref outArrayA); - outSerializer.Serialize(ref outArrayB); - outSerializer.Serialize(ref outArrayC); - - // deserialize - int[] inArrayA = default; - int[] inArrayB = default; - int[] inArrayC = default; - inStream.Write(outStream.ToArray()); - inStream.Position = 0; - var inSerializer = new NetworkSerializer(inReader); - inSerializer.Serialize(ref inArrayA); - inSerializer.Serialize(ref inArrayB); - inSerializer.Serialize(ref inArrayC); - - // validate - Assert.AreEqual(inArrayA, outArrayA); - Assert.AreEqual(inArrayB, outArrayB); - Assert.AreEqual(inArrayC, outArrayC); - } - - [Test] - public void SerializeUintArray() - { - using var outStream = PooledNetworkBuffer.Get(); - using var outWriter = PooledNetworkWriter.Get(outStream); - using var inStream = PooledNetworkBuffer.Get(); - using var inReader = PooledNetworkReader.Get(inStream); - // serialize - uint[] outArrayA = null; - uint[] outArrayB = new uint[0]; - uint[] outArrayC = { 1234567890, uint.MinValue, uint.MaxValue }; - var outSerializer = new NetworkSerializer(outWriter); - outSerializer.Serialize(ref outArrayA); - outSerializer.Serialize(ref outArrayB); - outSerializer.Serialize(ref outArrayC); - - // deserialize - uint[] inArrayA = default; - uint[] inArrayB = default; - uint[] inArrayC = default; - inStream.Write(outStream.ToArray()); - inStream.Position = 0; - var inSerializer = new NetworkSerializer(inReader); - inSerializer.Serialize(ref inArrayA); - inSerializer.Serialize(ref inArrayB); - inSerializer.Serialize(ref inArrayC); - - // validate - Assert.AreEqual(inArrayA, outArrayA); - Assert.AreEqual(inArrayB, outArrayB); - Assert.AreEqual(inArrayC, outArrayC); - } - - [Test] - public void SerializeLongArray() - { - using var outStream = PooledNetworkBuffer.Get(); - using var outWriter = PooledNetworkWriter.Get(outStream); - using var inStream = PooledNetworkBuffer.Get(); - using var inReader = PooledNetworkReader.Get(inStream); - // serialize - long[] outArrayA = null; - long[] outArrayB = new long[0]; - long[] outArrayC = { 9876543210, long.MinValue, long.MaxValue }; - var outSerializer = new NetworkSerializer(outWriter); - outSerializer.Serialize(ref outArrayA); - outSerializer.Serialize(ref outArrayB); - outSerializer.Serialize(ref outArrayC); - - // deserialize - long[] inArrayA = default; - long[] inArrayB = default; - long[] inArrayC = default; - inStream.Write(outStream.ToArray()); - inStream.Position = 0; - var inSerializer = new NetworkSerializer(inReader); - inSerializer.Serialize(ref inArrayA); - inSerializer.Serialize(ref inArrayB); - inSerializer.Serialize(ref inArrayC); - - // validate - Assert.AreEqual(inArrayA, outArrayA); - Assert.AreEqual(inArrayB, outArrayB); - Assert.AreEqual(inArrayC, outArrayC); - } - - [Test] - public void SerializeUlongArray() - { - using var outStream = PooledNetworkBuffer.Get(); - using var outWriter = PooledNetworkWriter.Get(outStream); - using var inStream = PooledNetworkBuffer.Get(); - using var inReader = PooledNetworkReader.Get(inStream); - // serialize - ulong[] outArrayA = null; - ulong[] outArrayB = new ulong[0]; - ulong[] outArrayC = { 9876543210, ulong.MinValue, ulong.MaxValue }; - var outSerializer = new NetworkSerializer(outWriter); - outSerializer.Serialize(ref outArrayA); - outSerializer.Serialize(ref outArrayB); - outSerializer.Serialize(ref outArrayC); - - // deserialize - ulong[] inArrayA = default; - ulong[] inArrayB = default; - ulong[] inArrayC = default; - inStream.Write(outStream.ToArray()); - inStream.Position = 0; - var inSerializer = new NetworkSerializer(inReader); - inSerializer.Serialize(ref inArrayA); - inSerializer.Serialize(ref inArrayB); - inSerializer.Serialize(ref inArrayC); - - // validate - Assert.AreEqual(inArrayA, outArrayA); - Assert.AreEqual(inArrayB, outArrayB); - Assert.AreEqual(inArrayC, outArrayC); - } - - [Test] - public void SerializeFloatArray() - { - using var outStream = PooledNetworkBuffer.Get(); - using var outWriter = PooledNetworkWriter.Get(outStream); - using var inStream = PooledNetworkBuffer.Get(); - using var inReader = PooledNetworkReader.Get(inStream); - // serialize - float[] outArrayA = null; - float[] outArrayB = new float[0]; - float[] outArrayC = { 12345.6789f, float.MinValue, float.MaxValue }; - var outSerializer = new NetworkSerializer(outWriter); - outSerializer.Serialize(ref outArrayA); - outSerializer.Serialize(ref outArrayB); - outSerializer.Serialize(ref outArrayC); - - // deserialize - float[] inArrayA = default; - float[] inArrayB = default; - float[] inArrayC = default; - inStream.Write(outStream.ToArray()); - inStream.Position = 0; - var inSerializer = new NetworkSerializer(inReader); - inSerializer.Serialize(ref inArrayA); - inSerializer.Serialize(ref inArrayB); - inSerializer.Serialize(ref inArrayC); - - // validate - Assert.AreEqual(inArrayA, outArrayA); - Assert.AreEqual(inArrayB, outArrayB); - Assert.AreEqual(inArrayC, outArrayC); - } - - [Test] - public void SerializeDoubleArray() - { - using var outStream = PooledNetworkBuffer.Get(); - using var outWriter = PooledNetworkWriter.Get(outStream); - using var inStream = PooledNetworkBuffer.Get(); - using var inReader = PooledNetworkReader.Get(inStream); - // serialize - double[] outArrayA = null; - double[] outArrayB = new double[0]; - double[] outArrayC = { 12345.6789, double.MinValue, double.MaxValue }; - var outSerializer = new NetworkSerializer(outWriter); - outSerializer.Serialize(ref outArrayA); - outSerializer.Serialize(ref outArrayB); - outSerializer.Serialize(ref outArrayC); - - // deserialize - double[] inArrayA = default; - double[] inArrayB = default; - double[] inArrayC = default; - inStream.Write(outStream.ToArray()); - inStream.Position = 0; - var inSerializer = new NetworkSerializer(inReader); - inSerializer.Serialize(ref inArrayA); - inSerializer.Serialize(ref inArrayB); - inSerializer.Serialize(ref inArrayC); - - // validate - Assert.AreEqual(inArrayA, outArrayA); - Assert.AreEqual(inArrayB, outArrayB); - Assert.AreEqual(inArrayC, outArrayC); - } - - [Test] - public void SerializeStringArray() - { - using var outStream = PooledNetworkBuffer.Get(); - using var outWriter = PooledNetworkWriter.Get(outStream); - using var inStream = PooledNetworkBuffer.Get(); - using var inReader = PooledNetworkReader.Get(inStream); - // serialize - string[] outArrayA = null; - string[] outArrayB = new string[0]; - string[] outArrayC = { Guid.NewGuid().ToString("N"), string.Empty, null }; - var outSerializer = new NetworkSerializer(outWriter); - outSerializer.Serialize(ref outArrayA); - outSerializer.Serialize(ref outArrayB); - outSerializer.Serialize(ref outArrayC); - - // deserialize - string[] inArrayA = default; - string[] inArrayB = default; - string[] inArrayC = default; - inStream.Write(outStream.ToArray()); - inStream.Position = 0; - var inSerializer = new NetworkSerializer(inReader); - inSerializer.Serialize(ref inArrayA); - inSerializer.Serialize(ref inArrayB); - inSerializer.Serialize(ref inArrayC); - - // validate - Assert.AreEqual(inArrayA, outArrayA); - Assert.AreEqual(inArrayB, outArrayB); - Assert.AreEqual(inArrayC, outArrayC); - } - - [Test] - public void SerializeColorArray() - { - using var outStream = PooledNetworkBuffer.Get(); - using var outWriter = PooledNetworkWriter.Get(outStream); - using var inStream = PooledNetworkBuffer.Get(); - using var inReader = PooledNetworkReader.Get(inStream); - // serialize - Color[] outArrayA = null; - var outArrayB = new Color[0]; - Color[] outArrayC = { Color.black, Color.red, Color.white }; - var outSerializer = new NetworkSerializer(outWriter); - outSerializer.Serialize(ref outArrayA); - outSerializer.Serialize(ref outArrayB); - outSerializer.Serialize(ref outArrayC); - - // deserialize - Color[] inArrayA = default; - Color[] inArrayB = default; - Color[] inArrayC = default; - inStream.Write(outStream.ToArray()); - inStream.Position = 0; - var inSerializer = new NetworkSerializer(inReader); - inSerializer.Serialize(ref inArrayA); - inSerializer.Serialize(ref inArrayB); - inSerializer.Serialize(ref inArrayC); - - // validate - Assert.AreEqual(inArrayA, outArrayA); - Assert.AreEqual(inArrayB, outArrayB); - Assert.AreEqual(inArrayC, outArrayC); - } - - [Test] - public void SerializeColor32Array() - { - using var outStream = PooledNetworkBuffer.Get(); - using var outWriter = PooledNetworkWriter.Get(outStream); - using var inStream = PooledNetworkBuffer.Get(); - using var inReader = PooledNetworkReader.Get(inStream); - // serialize - Color32[] outArrayA = null; - var outArrayB = new Color32[0]; - Color32[] outArrayC = - { - new Color32(0, 0, 0, byte.MaxValue), - new Color32(byte.MaxValue, byte.MaxValue, byte.MaxValue, byte.MaxValue), - new Color32(byte.MaxValue, 0, 0, byte.MaxValue) - }; - var outSerializer = new NetworkSerializer(outWriter); - outSerializer.Serialize(ref outArrayA); - outSerializer.Serialize(ref outArrayB); - outSerializer.Serialize(ref outArrayC); - - // deserialize - Color32[] inArrayA = default; - Color32[] inArrayB = default; - Color32[] inArrayC = default; - inStream.Write(outStream.ToArray()); - inStream.Position = 0; - var inSerializer = new NetworkSerializer(inReader); - inSerializer.Serialize(ref inArrayA); - inSerializer.Serialize(ref inArrayB); - inSerializer.Serialize(ref inArrayC); - - // validate - Assert.AreEqual(inArrayA, outArrayA); - Assert.AreEqual(inArrayB, outArrayB); - Assert.AreEqual(inArrayC, outArrayC); - } - - [Test] - public void SerializeVector2Array() - { - using var outStream = PooledNetworkBuffer.Get(); - using var outWriter = PooledNetworkWriter.Get(outStream); - using var inStream = PooledNetworkBuffer.Get(); - using var inReader = PooledNetworkReader.Get(inStream); - // serialize - Vector2[] outArrayA = null; - var outArrayB = new Vector2[0]; - Vector2[] outArrayC = { Vector2.up, Vector2.negativeInfinity, Vector2.positiveInfinity }; - var outSerializer = new NetworkSerializer(outWriter); - outSerializer.Serialize(ref outArrayA); - outSerializer.Serialize(ref outArrayB); - outSerializer.Serialize(ref outArrayC); - - // deserialize - Vector2[] inArrayA = default; - Vector2[] inArrayB = default; - Vector2[] inArrayC = default; - inStream.Write(outStream.ToArray()); - inStream.Position = 0; - var inSerializer = new NetworkSerializer(inReader); - inSerializer.Serialize(ref inArrayA); - inSerializer.Serialize(ref inArrayB); - inSerializer.Serialize(ref inArrayC); - - // validate - Assert.AreEqual(inArrayA, outArrayA); - Assert.AreEqual(inArrayB, outArrayB); - Assert.AreEqual(inArrayC, outArrayC); - } - - [Test] - public void SerializeVector3Array() - { - using var outStream = PooledNetworkBuffer.Get(); - using var outWriter = PooledNetworkWriter.Get(outStream); - using var inStream = PooledNetworkBuffer.Get(); - using var inReader = PooledNetworkReader.Get(inStream); - // serialize - Vector3[] outArrayA = null; - var outArrayB = new Vector3[0]; - Vector3[] outArrayC = { Vector3.forward, Vector3.negativeInfinity, Vector3.positiveInfinity }; - var outSerializer = new NetworkSerializer(outWriter); - outSerializer.Serialize(ref outArrayA); - outSerializer.Serialize(ref outArrayB); - outSerializer.Serialize(ref outArrayC); - - // deserialize - Vector3[] inArrayA = default; - Vector3[] inArrayB = default; - Vector3[] inArrayC = default; - inStream.Write(outStream.ToArray()); - inStream.Position = 0; - var inSerializer = new NetworkSerializer(inReader); - inSerializer.Serialize(ref inArrayA); - inSerializer.Serialize(ref inArrayB); - inSerializer.Serialize(ref inArrayC); - - // validate - Assert.AreEqual(inArrayA, outArrayA); - Assert.AreEqual(inArrayB, outArrayB); - Assert.AreEqual(inArrayC, outArrayC); - } - - [Test] - public void SerializeVector4Array() - { - using var outStream = PooledNetworkBuffer.Get(); - using var outWriter = PooledNetworkWriter.Get(outStream); - using var inStream = PooledNetworkBuffer.Get(); - using var inReader = PooledNetworkReader.Get(inStream); - // serialize - Vector4[] outArrayA = null; - var outArrayB = new Vector4[0]; - Vector4[] outArrayC = { Vector4.one, Vector4.negativeInfinity, Vector4.positiveInfinity }; - var outSerializer = new NetworkSerializer(outWriter); - outSerializer.Serialize(ref outArrayA); - outSerializer.Serialize(ref outArrayB); - outSerializer.Serialize(ref outArrayC); - - // deserialize - Vector4[] inArrayA = default; - Vector4[] inArrayB = default; - Vector4[] inArrayC = default; - inStream.Write(outStream.ToArray()); - inStream.Position = 0; - var inSerializer = new NetworkSerializer(inReader); - inSerializer.Serialize(ref inArrayA); - inSerializer.Serialize(ref inArrayB); - inSerializer.Serialize(ref inArrayC); - - // validate - Assert.AreEqual(inArrayA, outArrayA); - Assert.AreEqual(inArrayB, outArrayB); - Assert.AreEqual(inArrayC, outArrayC); - } - - [Test] - public void SerializeQuaternionArray() - { - using var outStream = PooledNetworkBuffer.Get(); - using var outWriter = PooledNetworkWriter.Get(outStream); - using var inStream = PooledNetworkBuffer.Get(); - using var inReader = PooledNetworkReader.Get(inStream); - // serialize - Quaternion[] outArrayA = null; - var outArrayB = new Quaternion[0]; - Quaternion[] outArrayC = { Quaternion.identity, Quaternion.Euler(new Vector3(30, 45, -60)), Quaternion.Euler(new Vector3(90, -90, 180)) }; - var outSerializer = new NetworkSerializer(outWriter); - outSerializer.Serialize(ref outArrayA); - outSerializer.Serialize(ref outArrayB); - outSerializer.Serialize(ref outArrayC); - - // deserialize - Quaternion[] inArrayA = default; - Quaternion[] inArrayB = default; - Quaternion[] inArrayC = default; - inStream.Write(outStream.ToArray()); - inStream.Position = 0; - var inSerializer = new NetworkSerializer(inReader); - inSerializer.Serialize(ref inArrayA); - inSerializer.Serialize(ref inArrayB); - inSerializer.Serialize(ref inArrayC); - - // validate - Assert.Null(inArrayA); - Assert.AreEqual(inArrayB, outArrayB); - for (int i = 0; i < outArrayC.Length; ++i) - { - Assert.Greater(Mathf.Abs(Quaternion.Dot(inArrayC[i], outArrayC[i])), 0.999f); - } - } - - [Test] - public void SerializeRayArray() - { - using var outStream = PooledNetworkBuffer.Get(); - using var outWriter = PooledNetworkWriter.Get(outStream); - using var inStream = PooledNetworkBuffer.Get(); - using var inReader = PooledNetworkReader.Get(inStream); - // serialize - Ray[] outArrayA = null; - var outArrayB = new Ray[0]; - Ray[] outArrayC = - { - new Ray(Vector3.zero, Vector3.forward), - new Ray(Vector3.zero, Vector3.left), - new Ray(Vector3.zero, Vector3.up) - }; - var outSerializer = new NetworkSerializer(outWriter); - outSerializer.Serialize(ref outArrayA); - outSerializer.Serialize(ref outArrayB); - outSerializer.Serialize(ref outArrayC); - - // deserialize - Ray[] inArrayA = default; - Ray[] inArrayB = default; - Ray[] inArrayC = default; - inStream.Write(outStream.ToArray()); - inStream.Position = 0; - var inSerializer = new NetworkSerializer(inReader); - inSerializer.Serialize(ref inArrayA); - inSerializer.Serialize(ref inArrayB); - inSerializer.Serialize(ref inArrayC); - - // validate - Assert.AreEqual(inArrayA, outArrayA); - Assert.AreEqual(inArrayB, outArrayB); - Assert.AreEqual(inArrayC, outArrayC); - } - - [Test] - public void SerializeRay2DArray() - { - using var outStream = PooledNetworkBuffer.Get(); - using var outWriter = PooledNetworkWriter.Get(outStream); - using var inStream = PooledNetworkBuffer.Get(); - using var inReader = PooledNetworkReader.Get(inStream); - // serialize - Ray2D[] outArrayA = null; - var outArrayB = new Ray2D[0]; - Ray2D[] outArrayC = - { - new Ray2D(Vector2.zero, Vector2.up), - new Ray2D(Vector2.zero, Vector2.left), - new Ray2D(Vector2.zero, Vector2.right) - }; - var outSerializer = new NetworkSerializer(outWriter); - outSerializer.Serialize(ref outArrayA); - outSerializer.Serialize(ref outArrayB); - outSerializer.Serialize(ref outArrayC); - - // deserialize - Ray2D[] inArrayA = default; - Ray2D[] inArrayB = default; - Ray2D[] inArrayC = default; - inStream.Write(outStream.ToArray()); - inStream.Position = 0; - var inSerializer = new NetworkSerializer(inReader); - inSerializer.Serialize(ref inArrayA); - inSerializer.Serialize(ref inArrayB); - inSerializer.Serialize(ref inArrayC); - - // validate - Assert.AreEqual(inArrayA, outArrayA); - Assert.AreEqual(inArrayB, outArrayB); - Assert.AreEqual(inArrayC, outArrayC); - } - - [Test] - public void SerializeEnumArray() - { - using var outStream = PooledNetworkBuffer.Get(); - using var outWriter = PooledNetworkWriter.Get(outStream); - using var inStream = PooledNetworkBuffer.Get(); - using var inReader = PooledNetworkReader.Get(inStream); - // serialize - EnumA[] outArrayA = null; - var outArrayB = new EnumB[0]; - EnumC[] outArrayC = { EnumC.U, EnumC.N, EnumC.I, EnumC.T, EnumC.Y, (EnumC)128 }; - var outSerializer = new NetworkSerializer(outWriter); - outSerializer.Serialize(ref outArrayA); - outSerializer.Serialize(ref outArrayB); - outSerializer.Serialize(ref outArrayC); - - // deserialize - EnumA[] inArrayA = default; - EnumB[] inArrayB = default; - EnumC[] inArrayC = default; - inStream.Write(outStream.ToArray()); - inStream.Position = 0; - var inSerializer = new NetworkSerializer(inReader); - inSerializer.Serialize(ref inArrayA); - inSerializer.Serialize(ref inArrayB); - inSerializer.Serialize(ref inArrayC); - - // validate - Assert.AreEqual(inArrayA, outArrayA); - Assert.AreEqual(inArrayB, outArrayB); - Assert.AreEqual(inArrayC, outArrayC); - } - } -} diff --git a/com.unity.netcode.gameobjects/Tests/Editor/NetworkSerializerTests.cs.meta b/com.unity.netcode.gameobjects/Tests/Editor/NetworkSerializerTests.cs.meta deleted file mode 100644 index 4f109557cd..0000000000 --- a/com.unity.netcode.gameobjects/Tests/Editor/NetworkSerializerTests.cs.meta +++ /dev/null @@ -1,11 +0,0 @@ -fileFormatVersion: 2 -guid: 48b252004f20b4e28a4025ef8d0237fe -MonoImporter: - externalObjects: {} - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: - assetBundleName: - assetBundleVariant: diff --git a/com.unity.netcode.gameobjects/Tests/Editor/Profiling/InternalMessageHandlerProfilingDecoratorTests.cs b/com.unity.netcode.gameobjects/Tests/Editor/Profiling/InternalMessageHandlerProfilingDecoratorTests.cs deleted file mode 100644 index 2758d1a154..0000000000 --- a/com.unity.netcode.gameobjects/Tests/Editor/Profiling/InternalMessageHandlerProfilingDecoratorTests.cs +++ /dev/null @@ -1,105 +0,0 @@ -using NUnit.Framework; -using UnityEngine; -using UnityEngine.TestTools; - -namespace Unity.Netcode.EditorTests -{ - public class InternalMessageHandlerProfilingDecoratorTests - { - private InternalMessageHandlerProfilingDecorator m_Decorator; - - [SetUp] - public void Setup() - { - m_Decorator = new InternalMessageHandlerProfilingDecorator(new DummyMessageHandler(null)); - } - - [Test] - public void HandleConnectionRequestCallsUnderlyingHandler() - { - m_Decorator.HandleConnectionRequest(0, null); - - LogAssert.Expect(LogType.Log, nameof(m_Decorator.HandleConnectionRequest)); - } - - [Test] - public void HandleConnectionApprovedCallsUnderlyingHandler() - { - m_Decorator.HandleConnectionApproved(0, null, 0.0f); - - LogAssert.Expect(LogType.Log, nameof(m_Decorator.HandleConnectionApproved)); - } - - [Test] - public void HandleAddObjectCallsUnderlyingHandler() - { - m_Decorator.HandleAddObject(0, null); - - LogAssert.Expect(LogType.Log, nameof(m_Decorator.HandleAddObject)); - } - - [Test] - public void HandleDestroyObjectCallsUnderlyingHandler() - { - m_Decorator.HandleDestroyObject(0, null); - - LogAssert.Expect(LogType.Log, nameof(m_Decorator.HandleDestroyObject)); - } - - [Test] - public void HandleSceneEventCallsUnderlyingHandler() - { - m_Decorator.HandleSceneEvent(0, null); - - LogAssert.Expect(LogType.Log, nameof(m_Decorator.HandleSceneEvent)); - } - - [Test] - public void HandleChangeOwnerCallsUnderlyingHandler() - { - m_Decorator.HandleChangeOwner(0, null); - - LogAssert.Expect(LogType.Log, nameof(m_Decorator.HandleChangeOwner)); - } - - [Test] - public void HandleNetworkVariableDeltaCallsUnderlyingHandler() - { - m_Decorator.HandleNetworkVariableDelta(0, null); - - LogAssert.Expect(LogType.Log, nameof(m_Decorator.HandleNetworkVariableDelta)); - } - - [Test] - public void HandleUnnamedMessageCallsUnderlyingHandler() - { - m_Decorator.HandleUnnamedMessage(0, null); - - LogAssert.Expect(LogType.Log, nameof(m_Decorator.HandleUnnamedMessage)); - } - - [Test] - public void HandleNamedMessageCallsUnderlyingHandler() - { - m_Decorator.HandleNamedMessage(0, null); - - LogAssert.Expect(LogType.Log, nameof(m_Decorator.HandleNamedMessage)); - } - - [Test] - public void HandleNetworkLogCallsUnderlyingHandler() - { - m_Decorator.HandleNetworkLog(0, null); - - LogAssert.Expect(LogType.Log, nameof(m_Decorator.HandleNetworkLog)); - } - - [Test] - public void MessageReceiveQueueItemCallsUnderlyingHandler() - { - m_Decorator.MessageReceiveQueueItem(0, null, 0.0f, MessageQueueContainer.MessageType.None); - - LogAssert.Expect(LogType.Log, nameof(m_Decorator.MessageReceiveQueueItem)); - } - } -} diff --git a/com.unity.netcode.gameobjects/Tests/Editor/Profiling/InternalMessageHandlerProfilingDecoratorTests.cs.meta b/com.unity.netcode.gameobjects/Tests/Editor/Profiling/InternalMessageHandlerProfilingDecoratorTests.cs.meta deleted file mode 100644 index 47c5f0cbc0..0000000000 --- a/com.unity.netcode.gameobjects/Tests/Editor/Profiling/InternalMessageHandlerProfilingDecoratorTests.cs.meta +++ /dev/null @@ -1,11 +0,0 @@ -fileFormatVersion: 2 -guid: 3212378d267799e40a84b65d2ed9591a -MonoImporter: - externalObjects: {} - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: - assetBundleName: - assetBundleVariant: diff --git a/com.unity.netcode.gameobjects/Tests/Editor/Serialization.meta b/com.unity.netcode.gameobjects/Tests/Editor/Serialization.meta new file mode 100644 index 0000000000..b3c7beee0b --- /dev/null +++ b/com.unity.netcode.gameobjects/Tests/Editor/Serialization.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: e12c4be6e89f459aa2826abba8c8d301 +timeCreated: 1628799671 \ No newline at end of file diff --git a/com.unity.netcode.gameobjects/Tests/Editor/Serialization/BaseFastBufferReaderWriterTest.cs b/com.unity.netcode.gameobjects/Tests/Editor/Serialization/BaseFastBufferReaderWriterTest.cs new file mode 100644 index 0000000000..fa067d73a6 --- /dev/null +++ b/com.unity.netcode.gameobjects/Tests/Editor/Serialization/BaseFastBufferReaderWriterTest.cs @@ -0,0 +1,664 @@ +using System; +using NUnit.Framework; +using Unity.Netcode.EditorTests; +using UnityEngine; +using Random = System.Random; + +namespace Unity.Netcode +{ + public abstract class BaseFastBufferReaderWriterTest + { + + #region Test Types + protected enum ByteEnum : byte + { + A, + B, + C + }; + protected enum SByteEnum : sbyte + { + A, + B, + C + }; + protected enum ShortEnum : short + { + A, + B, + C + }; + protected enum UShortEnum : ushort + { + A, + B, + C + }; + protected enum IntEnum : int + { + A, + B, + C + }; + protected enum UIntEnum : uint + { + A, + B, + C + }; + protected enum LongEnum : long + { + A, + B, + C + }; + protected enum ULongEnum : ulong + { + A, + B, + C + }; + + protected struct TestStruct + { + 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; + } + + public enum WriteType + { + WriteDirect, + WriteSafe, + WriteAsObject + } + #endregion + + + protected abstract void RunTypeTest(T valueToTest) where T : unmanaged; + + protected abstract void RunTypeTestSafe(T valueToTest) where T : unmanaged; + + protected abstract void RunObjectTypeTest(T valueToTest) where T : unmanaged; + + protected abstract void RunTypeArrayTest(T[] valueToTest) where T : unmanaged; + + protected abstract void RunTypeArrayTestSafe(T[] valueToTest) where T : unmanaged; + + protected abstract void RunObjectTypeArrayTest(T[] valueToTest) where T : unmanaged; + + #region Helpers + protected TestStruct GetTestStruct() + { + var random = new Random(); + + var testStruct = new TestStruct + { + A = (byte)random.Next(), + B = (short)random.Next(), + C = (ushort)random.Next(), + D = (int)random.Next(), + E = (uint)random.Next(), + F = ((long)random.Next() << 32) + random.Next(), + G = ((ulong)random.Next() << 32) + (ulong)random.Next(), + H = true, + I = '\u263a', + J = (float)random.NextDouble(), + K = random.NextDouble(), + }; + + return testStruct; + } + + protected delegate void GameObjectTestDelegate(GameObject obj, NetworkBehaviour networkBehaviour, + NetworkObject networkObject); + protected void RunGameObjectTest(GameObjectTestDelegate testCode) + { + var obj = new GameObject("Object"); + var networkBehaviour = obj.AddComponent(); + var networkObject = obj.AddComponent(); + // Create networkManager component + var networkManager = obj.AddComponent(); + networkManager.SetSingleton(); + networkObject.NetworkManagerOwner = networkManager; + + // Set the NetworkConfig + networkManager.NetworkConfig = new NetworkConfig() + { + // Set transport + NetworkTransport = obj.AddComponent() + }; + + networkManager.StartHost(); + + try + { + testCode(obj, networkBehaviour, networkObject); + } + finally + { + UnityEngine.Object.DestroyImmediate(obj); + networkManager.Shutdown(); + } + } + #endregion + + public void BaseTypeTest(Type testType, WriteType writeType) + { + var random = new Random(); + + void RunTypeTestLocal(T val, WriteType wt) where T : unmanaged + { + switch (wt) + { + case WriteType.WriteDirect: + RunTypeTest(val); + break; + case WriteType.WriteSafe: + RunTypeTestSafe(val); + break; + default: + RunObjectTypeTest(val); + break; + } + } + + if (testType == typeof(byte)) + { + RunTypeTestLocal((byte)random.Next(), writeType); + } + else if (testType == typeof(sbyte)) + { + RunTypeTestLocal((sbyte)random.Next(), writeType); + } + else if (testType == typeof(short)) + { + RunTypeTestLocal((short)random.Next(), writeType); + } + else if (testType == typeof(ushort)) + { + RunTypeTestLocal((ushort)random.Next(), writeType); + } + else if (testType == typeof(int)) + { + RunTypeTestLocal((int)random.Next(), writeType); + } + else if (testType == typeof(uint)) + { + RunTypeTestLocal((uint)random.Next(), writeType); + } + else if (testType == typeof(long)) + { + RunTypeTestLocal(((long)random.Next() << 32) + random.Next(), writeType); + } + else if (testType == typeof(ulong)) + { + RunTypeTestLocal(((ulong)random.Next() << 32) + (ulong)random.Next(), writeType); + } + else if (testType == typeof(bool)) + { + RunTypeTestLocal(true, writeType); + } + else if (testType == typeof(char)) + { + RunTypeTestLocal('a', writeType); + RunTypeTestLocal('\u263a', writeType); + } + else if (testType == typeof(float)) + { + RunTypeTestLocal((float)random.NextDouble(), writeType); + } + else if (testType == typeof(double)) + { + RunTypeTestLocal(random.NextDouble(), writeType); + } + else if (testType == typeof(ByteEnum)) + { + RunTypeTestLocal(ByteEnum.C, writeType); + } + else if (testType == typeof(SByteEnum)) + { + RunTypeTestLocal(SByteEnum.C, writeType); + } + else if (testType == typeof(ShortEnum)) + { + RunTypeTestLocal(ShortEnum.C, writeType); + } + else if (testType == typeof(UShortEnum)) + { + RunTypeTestLocal(UShortEnum.C, writeType); + } + else if (testType == typeof(IntEnum)) + { + RunTypeTestLocal(IntEnum.C, writeType); + } + else if (testType == typeof(UIntEnum)) + { + RunTypeTestLocal(UIntEnum.C, writeType); + } + else if (testType == typeof(LongEnum)) + { + RunTypeTestLocal(LongEnum.C, writeType); + } + else if (testType == typeof(ULongEnum)) + { + RunTypeTestLocal(ULongEnum.C, writeType); + } + else if (testType == typeof(Vector2)) + { + RunTypeTestLocal(new Vector2((float)random.NextDouble(), (float)random.NextDouble()), writeType); + } + else if (testType == typeof(Vector3)) + { + RunTypeTestLocal(new Vector3((float)random.NextDouble(), (float)random.NextDouble(), (float)random.NextDouble()), writeType); + } + else if (testType == typeof(Vector4)) + { + RunTypeTestLocal(new Vector4((float)random.NextDouble(), (float)random.NextDouble(), (float)random.NextDouble(), (float)random.NextDouble()), writeType); + } + else if (testType == typeof(Quaternion)) + { + RunTypeTestLocal(new Quaternion((float)random.NextDouble(), (float)random.NextDouble(), (float)random.NextDouble(), (float)random.NextDouble()), writeType); + } + else if (testType == typeof(Color)) + { + RunTypeTestLocal(new Color((float)random.NextDouble(), (float)random.NextDouble(), (float)random.NextDouble(), (float)random.NextDouble()), writeType); + } + else if (testType == typeof(Color32)) + { + RunTypeTestLocal(new Color32((byte)random.Next(), (byte)random.Next(), (byte)random.Next(), (byte)random.Next()), writeType); + } + else if (testType == typeof(Ray)) + { + RunTypeTestLocal(new Ray( + new Vector3((float)random.NextDouble(), (float)random.NextDouble(), (float)random.NextDouble()), + new Vector3((float)random.NextDouble(), (float)random.NextDouble(), (float)random.NextDouble())), writeType); + } + else if (testType == typeof(Ray2D)) + { + RunTypeTestLocal(new Ray2D( + new Vector2((float)random.NextDouble(), (float)random.NextDouble()), + new Vector2((float)random.NextDouble(), (float)random.NextDouble())), writeType); + } + else if (testType == typeof(TestStruct)) + { + SerializationTypeTable.Serializers[typeof(TestStruct)] = (ref FastBufferWriter writer, object obj) => + { + writer.WriteValueSafe((TestStruct)obj); + }; + SerializationTypeTable.Deserializers[typeof(TestStruct)] = (ref FastBufferReader reader, out object obj) => + { + reader.ReadValueSafe(out TestStruct value); + obj = value; + }; + try + { + RunTypeTestLocal(GetTestStruct(), writeType); + } + finally + { + SerializationTypeTable.Serializers.Remove(typeof(TestStruct)); + SerializationTypeTable.Deserializers.Remove(typeof(TestStruct)); + } + } + else + { + Assert.Fail("No type handler was provided for this type in the test!"); + } + } + + public void BaseArrayTypeTest(Type testType, WriteType writeType) + { + var random = new Random(); + void RunTypeTestLocal(T[] val, WriteType wt) where T : unmanaged + { + switch (wt) + { + case WriteType.WriteDirect: + RunTypeArrayTest(val); + break; + case WriteType.WriteSafe: + RunTypeArrayTestSafe(val); + break; + default: + RunObjectTypeArrayTest(val); + break; + } + } + + if (testType == typeof(byte)) + { + RunTypeTestLocal(new[]{ + (byte) random.Next(), + (byte) random.Next(), + (byte) random.Next(), + (byte) random.Next(), + (byte) random.Next(), + (byte) random.Next(), + (byte) random.Next() + }, writeType); + } + else if (testType == typeof(sbyte)) + { + RunTypeTestLocal(new[]{ + (sbyte) random.Next(), + (sbyte) random.Next(), + (sbyte) random.Next(), + (sbyte) random.Next(), + (sbyte) random.Next(), + (sbyte) random.Next(), + (sbyte) random.Next() + }, writeType); + } + else if (testType == typeof(short)) + { + RunTypeTestLocal(new[]{ + (short) random.Next(), + (short) random.Next(), + (short) random.Next(), + (short) random.Next(), + (short) random.Next() + }, writeType); + } + else if (testType == typeof(ushort)) + { + RunTypeTestLocal(new[]{ + (ushort) random.Next(), + (ushort) random.Next(), + (ushort) random.Next(), + (ushort) random.Next(), + (ushort) random.Next(), + (ushort) random.Next() + }, writeType); + } + else if (testType == typeof(int)) + { + RunTypeTestLocal(new[]{ + random.Next(), + random.Next(), + random.Next(), + random.Next(), + random.Next(), + random.Next(), + random.Next(), + random.Next() + }, writeType); + } + else if (testType == typeof(uint)) + { + RunTypeTestLocal(new[]{ + (uint) random.Next(), + (uint) random.Next(), + (uint) random.Next(), + (uint) random.Next(), + (uint) random.Next(), + (uint) random.Next() + }, writeType); + } + else if (testType == typeof(long)) + { + RunTypeTestLocal(new[]{ + ((long)random.Next() << 32) + (long)random.Next(), + ((long)random.Next() << 32) + (long)random.Next(), + ((long)random.Next() << 32) + (long)random.Next(), + ((long)random.Next() << 32) + (long)random.Next() + }, writeType); + } + else if (testType == typeof(ulong)) + { + RunTypeTestLocal(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() + }, writeType); + } + else if (testType == typeof(bool)) + { + RunTypeTestLocal(new[]{ + true, + false, + true, + true, + false, + false, + true, + false, + true + }, writeType); + } + else if (testType == typeof(char)) + { + RunTypeTestLocal(new[]{ + 'a', + '\u263a' + }, writeType); + } + else if (testType == typeof(float)) + { + RunTypeTestLocal(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() + }, writeType); + } + else if (testType == typeof(double)) + { + RunTypeTestLocal(new[]{ + random.NextDouble(), + random.NextDouble(), + random.NextDouble(), + random.NextDouble(), + random.NextDouble(), + random.NextDouble(), + random.NextDouble(), + random.NextDouble(), + random.NextDouble() + }, writeType); + } + else if (testType == typeof(ByteEnum)) + { + RunTypeTestLocal(new[]{ + ByteEnum.C, + ByteEnum.A, + ByteEnum.B + }, writeType); + } + else if (testType == typeof(SByteEnum)) + { + RunTypeTestLocal(new[]{ + SByteEnum.C, + SByteEnum.A, + SByteEnum.B + }, writeType); + } + else if (testType == typeof(ShortEnum)) + { + RunTypeTestLocal(new[]{ + ShortEnum.C, + ShortEnum.A, + ShortEnum.B + }, writeType); + } + else if (testType == typeof(UShortEnum)) + { + RunTypeTestLocal(new[]{ + UShortEnum.C, + UShortEnum.A, + UShortEnum.B + }, writeType); + } + else if (testType == typeof(IntEnum)) + { + RunTypeTestLocal(new[]{ + IntEnum.C, + IntEnum.A, + IntEnum.B + }, writeType); + } + else if (testType == typeof(UIntEnum)) + { + RunTypeTestLocal(new[]{ + UIntEnum.C, + UIntEnum.A, + UIntEnum.B + }, writeType); + } + else if (testType == typeof(LongEnum)) + { + RunTypeTestLocal(new[]{ + LongEnum.C, + LongEnum.A, + LongEnum.B + }, writeType); + } + else if (testType == typeof(ULongEnum)) + { + RunTypeTestLocal(new[]{ + ULongEnum.C, + ULongEnum.A, + ULongEnum.B + }, writeType); + } + else if (testType == typeof(Vector2)) + { + RunTypeTestLocal(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()), + }, writeType); + } + else if (testType == typeof(Vector3)) + { + RunTypeTestLocal(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()), + }, writeType); + } + else if (testType == typeof(Vector4)) + { + RunTypeTestLocal(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()), + }, writeType); + } + else if (testType == typeof(Quaternion)) + { + RunTypeTestLocal(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()), + }, writeType); + } + else if (testType == typeof(Color)) + { + RunTypeTestLocal(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()), + }, writeType); + } + else if (testType == typeof(Color32)) + { + RunTypeTestLocal(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()), + }, writeType); + } + else if (testType == typeof(Ray)) + { + RunTypeTestLocal(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())), + }, writeType); + } + else if (testType == typeof(Ray2D)) + { + RunTypeTestLocal(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())), + }, writeType); + } + else if (testType == typeof(TestStruct)) + { + SerializationTypeTable.Serializers[typeof(TestStruct)] = (ref FastBufferWriter writer, object obj) => + { + writer.WriteValueSafe((TestStruct)obj); + }; + SerializationTypeTable.Deserializers[typeof(TestStruct)] = (ref FastBufferReader reader, out object obj) => + { + reader.ReadValueSafe(out TestStruct value); + obj = value; + }; + try + { + RunTypeTestLocal(new[] { + GetTestStruct(), + GetTestStruct(), + GetTestStruct(), + }, writeType); + } + finally + { + SerializationTypeTable.Serializers.Remove(typeof(TestStruct)); + SerializationTypeTable.Deserializers.Remove(typeof(TestStruct)); + } + } + else + { + Assert.Fail("No type handler was provided for this type in the test!"); + } + } + } +} diff --git a/com.unity.netcode.gameobjects/Tests/Editor/Serialization/BaseFastBufferReaderWriterTest.cs.meta b/com.unity.netcode.gameobjects/Tests/Editor/Serialization/BaseFastBufferReaderWriterTest.cs.meta new file mode 100644 index 0000000000..f0b683d274 --- /dev/null +++ b/com.unity.netcode.gameobjects/Tests/Editor/Serialization/BaseFastBufferReaderWriterTest.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 573b1f36caed496a9c6e0eaa788d0c29 +timeCreated: 1629917174 \ No newline at end of file diff --git a/com.unity.netcode.gameobjects/Tests/Editor/Serialization/BitCounterTests.cs b/com.unity.netcode.gameobjects/Tests/Editor/Serialization/BitCounterTests.cs new file mode 100644 index 0000000000..5e0f4c8a12 --- /dev/null +++ b/com.unity.netcode.gameobjects/Tests/Editor/Serialization/BitCounterTests.cs @@ -0,0 +1,71 @@ +using NUnit.Framework; + +namespace Unity.Netcode.EditorTests +{ + public class BitCounterTests + { + [Test] + public void WhenCountingUsedBitsIn64BitValue_ResultMatchesHighBitSetPlusOne([Range(0, 63)] int highBit) + { + if (highBit == 0) + { + ulong value = 0; + // 0 is a special case. All values are considered at least 1 bit. + Assert.AreEqual(1, BitCounter.GetUsedBitCount(value)); + } + else + { + ulong value = 1UL << highBit; + Assert.AreEqual(highBit + 1, BitCounter.GetUsedBitCount(value)); + } + } + + [Test] + public void WhenCountingUsedBitsIn32BitValue_ResultMatchesHighBitSetPlusOne([Range(0, 31)] int highBit) + { + if (highBit == 0) + { + uint value = 0; + // 0 is a special case. All values are considered at least 1 bit. + Assert.AreEqual(1, BitCounter.GetUsedBitCount(value)); + } + else + { + uint value = 1U << highBit; + Assert.AreEqual(highBit + 1, BitCounter.GetUsedBitCount(value)); + } + } + + [Test] + public void WhenCountingUsedBytesIn64BitValue_ResultMatchesHighBitSetOver8PlusOne([Range(0, 63)] int highBit) + { + if (highBit == 0) + { + ulong value = 0; + // 0 is a special case. All values are considered at least 1 byte. + Assert.AreEqual(1, BitCounter.GetUsedByteCount(value)); + } + else + { + ulong value = 1UL << highBit; + Assert.AreEqual(highBit / 8 + 1, BitCounter.GetUsedByteCount(value)); + } + } + + [Test] + public void WhenCountingUsedBytesIn32BitValue_ResultMatchesHighBitSetOver8PlusOne([Range(0, 31)] int highBit) + { + if (highBit == 0) + { + uint value = 0; + // 0 is a special case. All values are considered at least 1 byte. + Assert.AreEqual(1, BitCounter.GetUsedByteCount(value)); + } + else + { + uint value = 1U << highBit; + Assert.AreEqual(highBit / 8 + 1, BitCounter.GetUsedByteCount(value)); + } + } + } +} diff --git a/com.unity.netcode.gameobjects/Tests/Editor/Serialization/BitCounterTests.cs.meta b/com.unity.netcode.gameobjects/Tests/Editor/Serialization/BitCounterTests.cs.meta new file mode 100644 index 0000000000..64a137560b --- /dev/null +++ b/com.unity.netcode.gameobjects/Tests/Editor/Serialization/BitCounterTests.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 76e459b9c2aeea94ebf448c237061485 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/com.unity.netcode.gameobjects/Tests/Editor/Serialization/BitReaderTests.cs b/com.unity.netcode.gameobjects/Tests/Editor/Serialization/BitReaderTests.cs new file mode 100644 index 0000000000..e8c4cf3f29 --- /dev/null +++ b/com.unity.netcode.gameobjects/Tests/Editor/Serialization/BitReaderTests.cs @@ -0,0 +1,359 @@ +using System; +using NUnit.Framework; +using Unity.Collections; + +namespace Unity.Netcode.EditorTests +{ + public class BitReaderTests + { + [Test] + public void TestReadingOneBit() + { + var writer = new FastBufferWriter(4, Allocator.Temp); + using (writer) + { + Assert.IsTrue(writer.TryBeginWrite(3)); + using (var bitWriter = writer.EnterBitwiseContext()) + { + bitWriter.WriteBit(true); + + bitWriter.WriteBit(true); + + bitWriter.WriteBit(false); + bitWriter.WriteBit(true); + + bitWriter.WriteBit(false); + bitWriter.WriteBit(false); + bitWriter.WriteBit(false); + bitWriter.WriteBit(true); + + bitWriter.WriteBit(false); + bitWriter.WriteBit(true); + bitWriter.WriteBit(false); + bitWriter.WriteBit(true); + } + + writer.WriteByte(0b11111111); + + var reader = new FastBufferReader(ref writer, Allocator.Temp); + using (reader) + { + Assert.IsTrue(reader.TryBeginRead(3)); + using (var bitReader = reader.EnterBitwiseContext()) + { + bool b; + bitReader.ReadBit(out b); + Assert.IsTrue(b); + bitReader.ReadBit(out b); + Assert.IsTrue(b); + bitReader.ReadBit(out b); + Assert.IsFalse(b); + bitReader.ReadBit(out b); + Assert.IsTrue(b); + + bitReader.ReadBit(out b); + Assert.IsFalse(b); + bitReader.ReadBit(out b); + Assert.IsFalse(b); + bitReader.ReadBit(out b); + Assert.IsFalse(b); + bitReader.ReadBit(out b); + Assert.IsTrue(b); + + bitReader.ReadBit(out b); + Assert.IsFalse(b); + bitReader.ReadBit(out b); + Assert.IsTrue(b); + bitReader.ReadBit(out b); + Assert.IsFalse(b); + bitReader.ReadBit(out b); + Assert.IsTrue(b); + } + + reader.ReadByte(out byte lastByte); + Assert.AreEqual(0b11111111, lastByte); + } + } + } + [Test] + public unsafe void TestTryBeginReadBits() + { + var nativeArray = new NativeArray(4, Allocator.Temp); + var reader = new FastBufferReader(nativeArray, Allocator.Temp); + nativeArray.Dispose(); + using (reader) + { + int* asInt = (int*)reader.GetUnsafePtr(); + *asInt = 0b11111111_00001010_10101011; + + using (var bitReader = reader.EnterBitwiseContext()) + { + Assert.Throws(() => reader.TryBeginRead(1)); + Assert.Throws(() => reader.TryBeginReadValue(1)); + Assert.IsTrue(bitReader.TryBeginReadBits(1)); + bitReader.ReadBit(out bool b); + Assert.IsTrue(b); + + // Can't use Assert.Throws() because ref struct BitWriter can't be captured in a lambda + try + { + bitReader.ReadBit(out b); + } + catch (OverflowException e) + { + // Should get called here. + } + catch (Exception e) + { + throw e; + } + Assert.IsTrue(bitReader.TryBeginReadBits(3)); + bitReader.ReadBit(out b); + Assert.IsTrue(b); + bitReader.ReadBit(out b); + Assert.IsFalse(b); + bitReader.ReadBit(out b); + Assert.IsTrue(b); + + byte byteVal; + try + { + bitReader.ReadBits(out byteVal, 4); + } + catch (OverflowException e) + { + // Should get called here. + } + catch (Exception e) + { + throw e; + } + + try + { + bitReader.ReadBits(out byteVal, 1); + } + catch (OverflowException e) + { + // Should get called here. + } + catch (Exception e) + { + throw e; + } + Assert.IsTrue(bitReader.TryBeginReadBits(3)); + + try + { + bitReader.ReadBits(out byteVal, 4); + } + catch (OverflowException e) + { + // Should get called here. + } + catch (Exception e) + { + throw e; + } + Assert.IsTrue(bitReader.TryBeginReadBits(4)); + bitReader.ReadBits(out byteVal, 3); + Assert.AreEqual(0b010, byteVal); + + Assert.IsTrue(bitReader.TryBeginReadBits(5)); + + bitReader.ReadBits(out byteVal, 5); + Assert.AreEqual(0b10101, byteVal); + } + + Assert.AreEqual(2, reader.Position); + + Assert.IsTrue(reader.TryBeginRead(1)); + reader.ReadByte(out byte nextByte); + Assert.AreEqual(0b11111111, nextByte); + + Assert.IsTrue(reader.TryBeginRead(1)); + reader.ReadByte(out nextByte); + Assert.AreEqual(0b00000000, nextByte); + + Assert.IsFalse(reader.TryBeginRead(1)); + using (var bitReader = reader.EnterBitwiseContext()) + { + Assert.IsFalse(bitReader.TryBeginReadBits(1)); + } + } + } + + [Test] + public void TestReadingMultipleBits() + { + var writer = new FastBufferWriter(4, Allocator.Temp); + using (writer) + { + Assert.IsTrue(writer.TryBeginWrite(3)); + using (var bitWriter = writer.EnterBitwiseContext()) + { + bitWriter.WriteBits(0b11111111, 1); + bitWriter.WriteBits(0b11111111, 1); + bitWriter.WriteBits(0b11111110, 2); + bitWriter.WriteBits(0b11111000, 4); + bitWriter.WriteBits(0b11111010, 4); + } + writer.WriteByte(0b11111111); + + + var reader = new FastBufferReader(ref writer, Allocator.Temp); + using (reader) + { + Assert.IsTrue(reader.TryBeginRead(3)); + using (var bitReader = reader.EnterBitwiseContext()) + { + byte b; + bitReader.ReadBits(out b, 1); + Assert.AreEqual(0b1, b); + + bitReader.ReadBits(out b, 1); + Assert.AreEqual(0b1, b); + + bitReader.ReadBits(out b, 2); + Assert.AreEqual(0b10, b); + + bitReader.ReadBits(out b, 4); + Assert.AreEqual(0b1000, b); + + bitReader.ReadBits(out b, 4); + Assert.AreEqual(0b1010, b); + } + + reader.ReadByte(out byte lastByte); + Assert.AreEqual(0b11111111, lastByte); + } + } + } + + [Test] + public void TestReadingMultipleBitsToLongs() + { + var writer = new FastBufferWriter(4, Allocator.Temp); + using (writer) + { + Assert.IsTrue(writer.TryBeginWrite(3)); + using (var bitWriter = writer.EnterBitwiseContext()) + { + bitWriter.WriteBits(0b11111111UL, 1); + bitWriter.WriteBits(0b11111111UL, 1); + bitWriter.WriteBits(0b11111110UL, 2); + bitWriter.WriteBits(0b11111000UL, 4); + bitWriter.WriteBits(0b11111010UL, 4); + } + + writer.WriteByte(0b11111111); + + var reader = new FastBufferReader(ref writer, Allocator.Temp); + using (reader) + { + Assert.IsTrue(reader.TryBeginRead(3)); + using (var bitReader = reader.EnterBitwiseContext()) + { + ulong ul; + bitReader.ReadBits(out ul, 1); + Assert.AreEqual(0b1, ul); + + bitReader.ReadBits(out ul, 1); + Assert.AreEqual(0b1, ul); + + bitReader.ReadBits(out ul, 2); + Assert.AreEqual(0b10, ul); + + bitReader.ReadBits(out ul, 4); + Assert.AreEqual(0b1000, ul); + + bitReader.ReadBits(out ul, 4); + Assert.AreEqual(0b1010, ul); + } + + reader.ReadByte(out byte lastByte); + Assert.AreEqual(0b11111111, lastByte); + } + } + } + + [Test] + public unsafe void TestReadingMultipleBytesToLongs([Range(1U, 64U)] uint numBits) + { + ulong value = 0xFFFFFFFFFFFFFFFF; + var reader = new FastBufferReader((byte*)&value, Allocator.Temp, sizeof(ulong)); + using (reader) + { + ulong* asUlong = (ulong*)reader.GetUnsafePtr(); + + Assert.AreEqual(value, *asUlong); + var mask = 0UL; + for (var i = 0; i < numBits; ++i) + { + mask |= (1UL << i); + } + + ulong readValue; + + Assert.IsTrue(reader.TryBeginRead(sizeof(ulong))); + using (var bitReader = reader.EnterBitwiseContext()) + { + bitReader.ReadBits(out readValue, numBits); + } + Assert.AreEqual(value & mask, readValue); + } + } + + [Test] + public unsafe void TestReadingBitsThrowsIfTryBeginReadNotCalled() + { + var nativeArray = new NativeArray(4, Allocator.Temp); + var reader = new FastBufferReader(nativeArray, Allocator.Temp); + nativeArray.Dispose(); + using (reader) + { + int* asInt = (int*)reader.GetUnsafePtr(); + *asInt = 0b11111111_00001010_10101011; + + Assert.Throws(() => + { + using var bitReader = reader.EnterBitwiseContext(); + bitReader.ReadBit(out bool b); + }); + + Assert.Throws(() => + { + using var bitReader = reader.EnterBitwiseContext(); + bitReader.ReadBits(out byte b, 1); + }); + + Assert.Throws(() => + { + using var bitReader = reader.EnterBitwiseContext(); + bitReader.ReadBits(out ulong ul, 1); + }); + + Assert.AreEqual(0, reader.Position); + + Assert.Throws(() => + { + Assert.IsTrue(reader.TryBeginRead(1)); + using var bitReader = reader.EnterBitwiseContext(); + ulong ul; + try + { + bitReader.ReadBits(out ul, 4); + bitReader.ReadBits(out ul, 4); + } + catch (OverflowException e) + { + Assert.Fail("Overflow exception was thrown too early."); + throw; + } + bitReader.ReadBits(out ul, 4); + }); + + } + } + } +} diff --git a/com.unity.netcode.gameobjects/Tests/Editor/Serialization/BitReaderTests.cs.meta b/com.unity.netcode.gameobjects/Tests/Editor/Serialization/BitReaderTests.cs.meta new file mode 100644 index 0000000000..0dc0f36136 --- /dev/null +++ b/com.unity.netcode.gameobjects/Tests/Editor/Serialization/BitReaderTests.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 67df11865abcd5843a4e142cf6bbd901 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/com.unity.netcode.gameobjects/Tests/Editor/Serialization/BitWriterTests.cs b/com.unity.netcode.gameobjects/Tests/Editor/Serialization/BitWriterTests.cs new file mode 100644 index 0000000000..ab98d06da9 --- /dev/null +++ b/com.unity.netcode.gameobjects/Tests/Editor/Serialization/BitWriterTests.cs @@ -0,0 +1,322 @@ +using System; +using NUnit.Framework; +using Unity.Collections; + +namespace Unity.Netcode.EditorTests +{ + public class BitWriterTests + { + [Test] + public unsafe void TestWritingOneBit() + { + var writer = new FastBufferWriter(4, Allocator.Temp); + using (writer) + { + int* asInt = (int*)writer.GetUnsafePtr(); + + Assert.AreEqual(0, *asInt); + + Assert.IsTrue(writer.TryBeginWrite(3)); + using (var bitWriter = writer.EnterBitwiseContext()) + { + bitWriter.WriteBit(true); + Assert.AreEqual(0b1, *asInt); + + bitWriter.WriteBit(true); + Assert.AreEqual(0b11, *asInt); + + bitWriter.WriteBit(false); + bitWriter.WriteBit(true); + Assert.AreEqual(0b1011, *asInt); + + bitWriter.WriteBit(false); + bitWriter.WriteBit(false); + bitWriter.WriteBit(false); + bitWriter.WriteBit(true); + Assert.AreEqual(0b10001011, *asInt); + + bitWriter.WriteBit(false); + bitWriter.WriteBit(true); + bitWriter.WriteBit(false); + bitWriter.WriteBit(true); + Assert.AreEqual(0b1010_10001011, *asInt); + } + + Assert.AreEqual(2, writer.Position); + Assert.AreEqual(0b1010_10001011, *asInt); + + writer.WriteByte(0b11111111); + Assert.AreEqual(0b11111111_00001010_10001011, *asInt); + } + } + [Test] + public unsafe void TestTryBeginWriteBits() + { + var writer = new FastBufferWriter(4, Allocator.Temp); + using (writer) + { + int* asInt = (int*)writer.GetUnsafePtr(); + + Assert.AreEqual(0, *asInt); + + using (var bitWriter = writer.EnterBitwiseContext()) + { + Assert.Throws(() => writer.TryBeginWrite(1)); + Assert.Throws(() => writer.TryBeginWriteValue(1)); + Assert.IsTrue(bitWriter.TryBeginWriteBits(1)); + bitWriter.WriteBit(true); + Assert.AreEqual(0b1, *asInt); + + // Can't use Assert.Throws() because ref struct BitWriter can't be captured in a lambda + try + { + bitWriter.WriteBit(true); + } + catch (OverflowException e) + { + // Should get called here. + } + catch (Exception e) + { + throw e; + } + Assert.IsTrue(bitWriter.TryBeginWriteBits(3)); + bitWriter.WriteBit(true); + Assert.AreEqual(0b11, *asInt); + + bitWriter.WriteBit(false); + bitWriter.WriteBit(true); + Assert.AreEqual(0b1011, *asInt); + + try + { + bitWriter.WriteBits(0b11111111, 4); + } + catch (OverflowException e) + { + // Should get called here. + } + catch (Exception e) + { + throw e; + } + + try + { + bitWriter.WriteBits(0b11111111, 1); + } + catch (OverflowException e) + { + // Should get called here. + } + catch (Exception e) + { + throw e; + } + Assert.IsTrue(bitWriter.TryBeginWriteBits(3)); + + try + { + bitWriter.WriteBits(0b11111111, 4); + } + catch (OverflowException e) + { + // Should get called here. + } + catch (Exception e) + { + throw e; + } + Assert.IsTrue(bitWriter.TryBeginWriteBits(4)); + + bitWriter.WriteBits(0b11111010, 3); + + Assert.AreEqual(0b00101011, *asInt); + + Assert.IsTrue(bitWriter.TryBeginWriteBits(5)); + + bitWriter.WriteBits(0b11110101, 5); + Assert.AreEqual(0b1010_10101011, *asInt); + } + + Assert.AreEqual(2, writer.Position); + Assert.AreEqual(0b1010_10101011, *asInt); + + Assert.IsTrue(writer.TryBeginWrite(1)); + writer.WriteByte(0b11111111); + Assert.AreEqual(0b11111111_00001010_10101011, *asInt); + + Assert.IsTrue(writer.TryBeginWrite(1)); + writer.WriteByte(0b00000000); + Assert.AreEqual(0b11111111_00001010_10101011, *asInt); + + Assert.IsFalse(writer.TryBeginWrite(1)); + using (var bitWriter = writer.EnterBitwiseContext()) + { + Assert.IsFalse(bitWriter.TryBeginWriteBits(1)); + } + } + } + + [Test] + public unsafe void TestWritingMultipleBits() + { + var writer = new FastBufferWriter(4, Allocator.Temp); + using (writer) + { + int* asInt = (int*)writer.GetUnsafePtr(); + + Assert.AreEqual(0, *asInt); + + Assert.IsTrue(writer.TryBeginWrite(3)); + using (var bitWriter = writer.EnterBitwiseContext()) + { + bitWriter.WriteBits(0b11111111, 1); + Assert.AreEqual(0b1, *asInt); + + bitWriter.WriteBits(0b11111111, 1); + Assert.AreEqual(0b11, *asInt); + + bitWriter.WriteBits(0b11111110, 2); + Assert.AreEqual(0b1011, *asInt); + + bitWriter.WriteBits(0b11111000, 4); + Assert.AreEqual(0b10001011, *asInt); + + bitWriter.WriteBits(0b11111010, 4); + Assert.AreEqual(0b1010_10001011, *asInt); + } + + Assert.AreEqual(2, writer.Position); + Assert.AreEqual(0b1010_10001011, *asInt); + + writer.WriteByte(0b11111111); + Assert.AreEqual(0b11111111_00001010_10001011, *asInt); + } + } + + [Test] + public unsafe void TestWritingMultipleBitsFromLongs() + { + var writer = new FastBufferWriter(4, Allocator.Temp); + using (writer) + { + int* asInt = (int*)writer.GetUnsafePtr(); + + Assert.AreEqual(0, *asInt); + + Assert.IsTrue(writer.TryBeginWrite(3)); + using (var bitWriter = writer.EnterBitwiseContext()) + { + bitWriter.WriteBits(0b11111111UL, 1); + Assert.AreEqual(0b1, *asInt); + + bitWriter.WriteBits(0b11111111UL, 1); + Assert.AreEqual(0b11, *asInt); + + bitWriter.WriteBits(0b11111110UL, 2); + Assert.AreEqual(0b1011, *asInt); + + bitWriter.WriteBits(0b11111000UL, 4); + Assert.AreEqual(0b10001011, *asInt); + + bitWriter.WriteBits(0b11111010UL, 4); + Assert.AreEqual(0b1010_10001011, *asInt); + } + + Assert.AreEqual(2, writer.Position); + Assert.AreEqual(0b1010_10001011, *asInt); + + writer.WriteByte(0b11111111); + Assert.AreEqual(0b11111111_00001010_10001011, *asInt); + } + } + + [Test] + public unsafe void TestWritingMultipleBytesFromLongs([Range(1U, 64U)] uint numBits) + { + var writer = new FastBufferWriter(sizeof(ulong), Allocator.Temp); + using (writer) + { + ulong* asUlong = (ulong*)writer.GetUnsafePtr(); + + Assert.AreEqual(0, *asUlong); + var mask = 0UL; + for (var i = 0; i < numBits; ++i) + { + mask |= (1UL << i); + } + + ulong value = 0xFFFFFFFFFFFFFFFF; + + Assert.IsTrue(writer.TryBeginWrite(sizeof(ulong))); + using (var bitWriter = writer.EnterBitwiseContext()) + { + bitWriter.WriteBits(value, numBits); + } + Assert.AreEqual(value & mask, *asUlong); + } + } + + [Test] + public unsafe void TestWritingBitsThrowsIfTryBeginWriteNotCalled() + { + var writer = new FastBufferWriter(4, Allocator.Temp); + using (writer) + { + int* asInt = (int*)writer.GetUnsafePtr(); + + Assert.AreEqual(0, *asInt); + + Assert.Throws(() => + { + using var bitWriter = writer.EnterBitwiseContext(); + bitWriter.WriteBit(true); + }); + + Assert.Throws(() => + { + using var bitWriter = writer.EnterBitwiseContext(); + bitWriter.WriteBit(false); + }); + + Assert.Throws(() => + { + using var bitWriter = writer.EnterBitwiseContext(); + bitWriter.WriteBits(0b11111111, 1); + }); + + Assert.Throws(() => + { + using var bitWriter = writer.EnterBitwiseContext(); + bitWriter.WriteBits(0b11111111UL, 1); + }); + + Assert.AreEqual(0, writer.Position); + Assert.AreEqual(0, *asInt); + + writer.WriteByteSafe(0b11111111); + Assert.AreEqual(0b11111111, *asInt); + + + Assert.Throws(() => + { + Assert.IsTrue(writer.TryBeginWrite(1)); + using var bitWriter = writer.EnterBitwiseContext(); + try + { + bitWriter.WriteBits(0b11111111UL, 4); + bitWriter.WriteBits(0b11111111UL, 4); + } + catch (OverflowException e) + { + Assert.Fail("Overflow exception was thrown too early."); + throw; + } + bitWriter.WriteBits(0b11111111UL, 1); + }); + + } + } + } +} diff --git a/com.unity.netcode.gameobjects/Tests/Editor/Serialization/BitWriterTests.cs.meta b/com.unity.netcode.gameobjects/Tests/Editor/Serialization/BitWriterTests.cs.meta new file mode 100644 index 0000000000..3a8da0e2cc --- /dev/null +++ b/com.unity.netcode.gameobjects/Tests/Editor/Serialization/BitWriterTests.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: fed657e0516a72f469fbf886e3e5149a +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/com.unity.netcode.gameobjects/Tests/Editor/Serialization/BufferSerializerTests.cs b/com.unity.netcode.gameobjects/Tests/Editor/Serialization/BufferSerializerTests.cs new file mode 100644 index 0000000000..1847dd0954 --- /dev/null +++ b/com.unity.netcode.gameobjects/Tests/Editor/Serialization/BufferSerializerTests.cs @@ -0,0 +1,644 @@ +using System; +using NUnit.Framework; +using Unity.Collections; +using UnityEngine; +using Random = System.Random; + +namespace Unity.Netcode.EditorTests +{ + public class BufferSerializerTests + { + [Test] + public void TestIsReaderIsWriter() + { + var writer = new FastBufferWriter(4, Allocator.Temp); + using (writer) + { + var serializer = + new BufferSerializer(new BufferSerializerWriter(ref writer)); + Assert.IsFalse(serializer.IsReader); + Assert.IsTrue(serializer.IsWriter); + } + byte[] readBuffer = new byte[4]; + var reader = new FastBufferReader(readBuffer, Allocator.Temp); + using (reader) + { + var serializer = + new BufferSerializer(new BufferSerializerReader(ref reader)); + Assert.IsTrue(serializer.IsReader); + Assert.IsFalse(serializer.IsWriter); + } + } + [Test] + public unsafe void TestGetUnderlyingStructs() + { + var writer = new FastBufferWriter(4, Allocator.Temp); + using (writer) + { + var serializer = + new BufferSerializer(new BufferSerializerWriter(ref writer)); + ref FastBufferWriter underlyingWriter = ref serializer.GetFastBufferWriter(); + fixed (FastBufferWriter* ptr = &underlyingWriter) + { + Assert.IsTrue(ptr == &writer); + } + // Can't use Assert.Throws() because ref structs can't be passed into lambdas. + try + { + serializer.GetFastBufferReader(); + } + catch (InvalidOperationException) + { + // pass + } + + } + byte[] readBuffer = new byte[4]; + var reader = new FastBufferReader(readBuffer, Allocator.Temp); + using (reader) + { + var serializer = + new BufferSerializer(new BufferSerializerReader(ref reader)); + ref FastBufferReader underlyingReader = ref serializer.GetFastBufferReader(); + fixed (FastBufferReader* ptr = &underlyingReader) + { + Assert.IsTrue(ptr == &reader); + } + // Can't use Assert.Throws() because ref structs can't be passed into lambdas. + try + { + serializer.GetFastBufferWriter(); + } + catch (InvalidOperationException) + { + // pass + } + } + } + + // Not reimplementing the entire suite of all value tests for BufferSerializer since they're already tested + // for the underlying structures. These are just basic tests to make sure the correct underlying functions + // are being called. + [Test] + public void TestSerializingObjects() + { + var random = new Random(); + int value = random.Next(); + object asObj = value; + + var writer = new FastBufferWriter(100, Allocator.Temp); + using (writer) + { + var serializer = + new BufferSerializer(new BufferSerializerWriter(ref writer)); + serializer.SerializeValue(ref asObj, typeof(int)); + + var reader = new FastBufferReader(ref writer, Allocator.Temp); + using (reader) + { + var deserializer = + new BufferSerializer(new BufferSerializerReader(ref reader)); + object readValue = 0; + deserializer.SerializeValue(ref readValue, typeof(int)); + + Assert.AreEqual(value, readValue); + } + } + } + + [Test] + public void TestSerializingValues() + { + var random = new Random(); + int value = random.Next(); + + var writer = new FastBufferWriter(100, Allocator.Temp); + using (writer) + { + var serializer = + new BufferSerializer(new BufferSerializerWriter(ref writer)); + serializer.SerializeValue(ref value); + + var reader = new FastBufferReader(ref writer, Allocator.Temp); + using (reader) + { + var deserializer = + new BufferSerializer(new BufferSerializerReader(ref reader)); + int readValue = 0; + deserializer.SerializeValue(ref readValue); + + Assert.AreEqual(value, readValue); + } + } + } + [Test] + public void TestSerializingBytes() + { + var random = new Random(); + byte value = (byte)random.Next(); + + var writer = new FastBufferWriter(100, Allocator.Temp); + using (writer) + { + var serializer = + new BufferSerializer(new BufferSerializerWriter(ref writer)); + serializer.SerializeValue(ref value); + + var reader = new FastBufferReader(ref writer, Allocator.Temp); + using (reader) + { + var deserializer = + new BufferSerializer(new BufferSerializerReader(ref reader)); + byte readValue = 0; + deserializer.SerializeValue(ref readValue); + + Assert.AreEqual(value, readValue); + } + } + } + [Test] + public void TestSerializingArrays() + { + var random = new Random(); + int[] value = { random.Next(), random.Next(), random.Next() }; + + var writer = new FastBufferWriter(100, Allocator.Temp); + using (writer) + { + var serializer = + new BufferSerializer(new BufferSerializerWriter(ref writer)); + serializer.SerializeValue(ref value); + + var reader = new FastBufferReader(ref writer, Allocator.Temp); + using (reader) + { + var deserializer = + new BufferSerializer(new BufferSerializerReader(ref reader)); + int[] readValue = null; + deserializer.SerializeValue(ref readValue); + + Assert.AreEqual(value, readValue); + } + } + } + [Test] + public void TestSerializingStrings([Values] bool oneBytChars) + { + string value = "I am a test string"; + + var writer = new FastBufferWriter(100, Allocator.Temp); + using (writer) + { + var serializer = + new BufferSerializer(new BufferSerializerWriter(ref writer)); + serializer.SerializeValue(ref value, oneBytChars); + + var reader = new FastBufferReader(ref writer, Allocator.Temp); + using (reader) + { + var deserializer = + new BufferSerializer(new BufferSerializerReader(ref reader)); + string readValue = null; + deserializer.SerializeValue(ref readValue, oneBytChars); + + Assert.AreEqual(value, readValue); + } + } + } + + + [Test] + public void TestSerializingValuesPreChecked() + { + var random = new Random(); + int value = random.Next(); + + var writer = new FastBufferWriter(100, Allocator.Temp); + using (writer) + { + var serializer = + new BufferSerializer(new BufferSerializerWriter(ref writer)); + try + { + serializer.SerializeValuePreChecked(ref value); + } + catch (OverflowException e) + { + // Pass + } + + Assert.IsTrue(serializer.PreCheck(FastBufferWriter.GetWriteSize(value))); + serializer.SerializeValuePreChecked(ref value); + + var reader = new FastBufferReader(ref writer, Allocator.Temp); + using (reader) + { + var deserializer = + new BufferSerializer(new BufferSerializerReader(ref reader)); + int readValue = 0; + try + { + deserializer.SerializeValuePreChecked(ref readValue); + } + catch (OverflowException e) + { + // Pass + } + + Assert.IsTrue(deserializer.PreCheck(FastBufferWriter.GetWriteSize(value))); + deserializer.SerializeValuePreChecked(ref readValue); + + Assert.AreEqual(value, readValue); + } + } + } + [Test] + public void TestSerializingBytesPreChecked() + { + var random = new Random(); + byte value = (byte)random.Next(); + + var writer = new FastBufferWriter(100, Allocator.Temp); + using (writer) + { + var serializer = + new BufferSerializer(new BufferSerializerWriter(ref writer)); + try + { + serializer.SerializeValuePreChecked(ref value); + } + catch (OverflowException e) + { + // Pass + } + + Assert.IsTrue(serializer.PreCheck(FastBufferWriter.GetWriteSize(value))); + serializer.SerializeValuePreChecked(ref value); + + var reader = new FastBufferReader(ref writer, Allocator.Temp); + using (reader) + { + var deserializer = + new BufferSerializer(new BufferSerializerReader(ref reader)); + byte readValue = 0; + try + { + deserializer.SerializeValuePreChecked(ref readValue); + } + catch (OverflowException e) + { + // Pass + } + + Assert.IsTrue(deserializer.PreCheck(FastBufferWriter.GetWriteSize(value))); + deserializer.SerializeValuePreChecked(ref readValue); + + Assert.AreEqual(value, readValue); + } + } + } + [Test] + public void TestSerializingArraysPreChecked() + { + var random = new Random(); + int[] value = { random.Next(), random.Next(), random.Next() }; + + var writer = new FastBufferWriter(100, Allocator.Temp); + using (writer) + { + var serializer = + new BufferSerializer(new BufferSerializerWriter(ref writer)); + try + { + serializer.SerializeValuePreChecked(ref value); + } + catch (OverflowException e) + { + // Pass + } + + Assert.IsTrue(serializer.PreCheck(FastBufferWriter.GetWriteSize(value))); + serializer.SerializeValuePreChecked(ref value); + + var reader = new FastBufferReader(ref writer, Allocator.Temp); + using (reader) + { + var deserializer = + new BufferSerializer(new BufferSerializerReader(ref reader)); + int[] readValue = null; + try + { + deserializer.SerializeValuePreChecked(ref readValue); + } + catch (OverflowException e) + { + // Pass + } + + Assert.IsTrue(deserializer.PreCheck(FastBufferWriter.GetWriteSize(value))); + deserializer.SerializeValuePreChecked(ref readValue); + + Assert.AreEqual(value, readValue); + } + } + } + [Test] + public void TestSerializingStringsPreChecked([Values] bool oneBytChars) + { + string value = "I am a test string"; + + var writer = new FastBufferWriter(100, Allocator.Temp); + using (writer) + { + var serializer = + new BufferSerializer(new BufferSerializerWriter(ref writer)); + try + { + serializer.SerializeValuePreChecked(ref value, oneBytChars); + } + catch (OverflowException e) + { + // Pass + } + + Assert.IsTrue(serializer.PreCheck(FastBufferWriter.GetWriteSize(value, oneBytChars))); + serializer.SerializeValuePreChecked(ref value, oneBytChars); + + var reader = new FastBufferReader(ref writer, Allocator.Temp); + using (reader) + { + var deserializer = + new BufferSerializer(new BufferSerializerReader(ref reader)); + string readValue = null; + try + { + deserializer.SerializeValuePreChecked(ref readValue, oneBytChars); + } + catch (OverflowException e) + { + // Pass + } + + Assert.IsTrue(deserializer.PreCheck(FastBufferWriter.GetWriteSize(value, oneBytChars))); + deserializer.SerializeValuePreChecked(ref readValue, oneBytChars); + + Assert.AreEqual(value, readValue); + } + } + } + + private delegate void GameObjectTestDelegate(GameObject obj, NetworkBehaviour networkBehaviour, + NetworkObject networkObject); + private void RunGameObjectTest(GameObjectTestDelegate testCode) + { + var obj = new GameObject("Object"); + var networkBehaviour = obj.AddComponent(); + var networkObject = obj.AddComponent(); + // Create networkManager component + var networkManager = obj.AddComponent(); + networkManager.SetSingleton(); + networkObject.NetworkManagerOwner = networkManager; + + // Set the NetworkConfig + networkManager.NetworkConfig = new NetworkConfig() + { + // Set transport + NetworkTransport = obj.AddComponent() + }; + + networkManager.StartServer(); + + try + { + testCode(obj, networkBehaviour, networkObject); + } + finally + { + UnityEngine.Object.DestroyImmediate(obj); + networkManager.Shutdown(); + } + } + + [Test] + public void TestSerializingGameObjects() + { + RunGameObjectTest((obj, networkBehaviour, networkObject) => + { + var writer = new FastBufferWriter(100, Allocator.Temp); + using (writer) + { + var serializer = + new BufferSerializer(new BufferSerializerWriter(ref writer)); + serializer.SerializeValue(ref obj); + + var reader = new FastBufferReader(ref writer, Allocator.Temp); + using (reader) + { + var deserializer = + new BufferSerializer(new BufferSerializerReader(ref reader)); + GameObject readValue = null; + deserializer.SerializeValue(ref readValue); + + Assert.AreEqual(obj, readValue); + } + } + } + ); + } + + [Test] + public void TestSerializingNetworkObjects() + { + RunGameObjectTest((obj, networkBehaviour, networkObject) => + { + var writer = new FastBufferWriter(100, Allocator.Temp); + using (writer) + { + var serializer = + new BufferSerializer(new BufferSerializerWriter(ref writer)); + serializer.SerializeValue(ref networkObject); + + var reader = new FastBufferReader(ref writer, Allocator.Temp); + using (reader) + { + var deserializer = + new BufferSerializer(new BufferSerializerReader(ref reader)); + NetworkObject readValue = null; + deserializer.SerializeValue(ref readValue); + + Assert.AreEqual(networkObject, readValue); + } + } + } + ); + } + + [Test] + public void TestSerializingNetworkBehaviours() + { + RunGameObjectTest((obj, networkBehaviour, networkObject) => + { + var writer = new FastBufferWriter(100, Allocator.Temp); + using (writer) + { + var serializer = + new BufferSerializer(new BufferSerializerWriter(ref writer)); + serializer.SerializeValue(ref networkBehaviour); + + var reader = new FastBufferReader(ref writer, Allocator.Temp); + using (reader) + { + var deserializer = + new BufferSerializer(new BufferSerializerReader(ref reader)); + NetworkBehaviour readValue = null; + deserializer.SerializeValue(ref readValue); + + Assert.AreEqual(networkBehaviour, readValue); + } + } + } + ); + } + + [Test] + public void TestSerializingGameObjectsPreChecked() + { + RunGameObjectTest((obj, networkBehaviour, networkObject) => + { + var writer = new FastBufferWriter(100, Allocator.Temp); + using (writer) + { + var serializer = + new BufferSerializer(new BufferSerializerWriter(ref writer)); + try + { + serializer.SerializeValuePreChecked(ref obj); + } + catch (OverflowException e) + { + // Pass + } + + Assert.IsTrue(serializer.PreCheck(FastBufferWriterExtensions.GetWriteSize(obj))); + serializer.SerializeValuePreChecked(ref obj); + + var reader = new FastBufferReader(ref writer, Allocator.Temp); + using (reader) + { + var deserializer = + new BufferSerializer(new BufferSerializerReader(ref reader)); + GameObject readValue = null; + try + { + deserializer.SerializeValuePreChecked(ref readValue); + } + catch (OverflowException e) + { + // Pass + } + + Assert.IsTrue(deserializer.PreCheck(FastBufferWriterExtensions.GetWriteSize(readValue))); + deserializer.SerializeValuePreChecked(ref readValue); + + Assert.AreEqual(obj, readValue); + } + } + } + ); + } + + [Test] + public void TestSerializingNetworkObjectsPreChecked() + { + RunGameObjectTest((obj, networkBehaviour, networkObject) => + { + var writer = new FastBufferWriter(100, Allocator.Temp); + using (writer) + { + var serializer = + new BufferSerializer(new BufferSerializerWriter(ref writer)); + try + { + serializer.SerializeValuePreChecked(ref networkObject); + } + catch (OverflowException e) + { + // Pass + } + + Assert.IsTrue(serializer.PreCheck(FastBufferWriterExtensions.GetWriteSize(networkObject))); + serializer.SerializeValuePreChecked(ref networkObject); + + var reader = new FastBufferReader(ref writer, Allocator.Temp); + using (reader) + { + var deserializer = + new BufferSerializer(new BufferSerializerReader(ref reader)); + NetworkObject readValue = null; + try + { + deserializer.SerializeValuePreChecked(ref readValue); + } + catch (OverflowException e) + { + // Pass + } + + Assert.IsTrue(deserializer.PreCheck(FastBufferWriterExtensions.GetWriteSize(readValue))); + deserializer.SerializeValuePreChecked(ref readValue); + + Assert.AreEqual(networkObject, readValue); + } + } + } + ); + } + + [Test] + public void TestSerializingNetworkBehavioursPreChecked() + { + RunGameObjectTest((obj, networkBehaviour, networkObject) => + { + var writer = new FastBufferWriter(100, Allocator.Temp); + using (writer) + { + var serializer = + new BufferSerializer(new BufferSerializerWriter(ref writer)); + try + { + serializer.SerializeValuePreChecked(ref networkBehaviour); + } + catch (OverflowException e) + { + // Pass + } + + Assert.IsTrue(serializer.PreCheck(FastBufferWriterExtensions.GetWriteSize(networkBehaviour))); + serializer.SerializeValuePreChecked(ref networkBehaviour); + + var reader = new FastBufferReader(ref writer, Allocator.Temp); + using (reader) + { + var deserializer = + new BufferSerializer(new BufferSerializerReader(ref reader)); + NetworkBehaviour readValue = null; + try + { + deserializer.SerializeValuePreChecked(ref readValue); + } + catch (OverflowException e) + { + // Pass + } + + Assert.IsTrue(deserializer.PreCheck(FastBufferWriterExtensions.GetWriteSize(readValue))); + deserializer.SerializeValuePreChecked(ref readValue); + + Assert.AreEqual(networkBehaviour, readValue); + } + } + } + ); + } + } +} diff --git a/com.unity.netcode.gameobjects/Tests/Editor/Serialization/BufferSerializerTests.cs.meta b/com.unity.netcode.gameobjects/Tests/Editor/Serialization/BufferSerializerTests.cs.meta new file mode 100644 index 0000000000..cb62c7e2d2 --- /dev/null +++ b/com.unity.netcode.gameobjects/Tests/Editor/Serialization/BufferSerializerTests.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 0cf899c97866c76498b71585a61a8142 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/com.unity.netcode.gameobjects/Tests/Editor/Serialization/BytePackerTests.cs b/com.unity.netcode.gameobjects/Tests/Editor/Serialization/BytePackerTests.cs new file mode 100644 index 0000000000..1c2ff159e9 --- /dev/null +++ b/com.unity.netcode.gameobjects/Tests/Editor/Serialization/BytePackerTests.cs @@ -0,0 +1,1268 @@ +using System; +using System.Linq; +using System.Reflection; +using NUnit.Framework; +using Unity.Collections; +using UnityEngine; +using Random = System.Random; + +namespace Unity.Netcode.EditorTests +{ + public class BytePackerTests + { + #region Test Types + + private enum ByteEnum : byte + { + A, + B, + C + } + + private enum SByteEnum : sbyte + { + A, + B, + C + } + + private enum ShortEnum : short + { + A, + B, + C + } + + private enum UShortEnum : ushort + { + A, + B, + C + } + + private enum IntEnum + { + A, + B, + C + } + + private enum UIntEnum : uint + { + A, + B, + C + } + + private enum LongEnum : long + { + A, + B, + C + } + + private enum ULongEnum : ulong + { + A, + B, + C + } + + public enum WriteType + { + WriteDirect, + WriteAsObject + } + + #endregion + + private void CheckUnsignedPackedSize64(ref FastBufferWriter writer, ulong value) + { + + if (value <= 240) + { + Assert.AreEqual(1, writer.Position); + } + else if (value <= 2287) + { + Assert.AreEqual(2, writer.Position); + } + else + { + Assert.AreEqual(BitCounter.GetUsedByteCount(value) + 1, writer.Position); + } + } + + private void CheckUnsignedPackedValue64(ref FastBufferWriter writer, ulong value) + { + var reader = new FastBufferReader(ref writer, Allocator.Temp); + using (reader) + { + ByteUnpacker.ReadValuePacked(ref reader, out ulong readValue); + Assert.AreEqual(readValue, value); + } + } + + private void CheckUnsignedPackedSize32(ref FastBufferWriter writer, uint value) + { + + if (value <= 240) + { + Assert.AreEqual(1, writer.Position); + } + else if (value <= 2287) + { + Assert.AreEqual(2, writer.Position); + } + else + { + Assert.AreEqual(BitCounter.GetUsedByteCount(value) + 1, writer.Position); + } + } + + private void CheckUnsignedPackedValue32(ref FastBufferWriter writer, uint value) + { + var reader = new FastBufferReader(ref writer, Allocator.Temp); + using (reader) + { + ByteUnpacker.ReadValuePacked(ref reader, out uint readValue); + Assert.AreEqual(readValue, value); + } + } + + private void CheckSignedPackedSize64(ref FastBufferWriter writer, long value) + { + ulong asUlong = Arithmetic.ZigZagEncode(value); + + if (asUlong <= 240) + { + Assert.AreEqual(1, writer.Position); + } + else if (asUlong <= 2287) + { + Assert.AreEqual(2, writer.Position); + } + else + { + Assert.AreEqual(BitCounter.GetUsedByteCount(asUlong) + 1, writer.Position); + } + } + + private void CheckSignedPackedValue64(ref FastBufferWriter writer, long value) + { + var reader = new FastBufferReader(ref writer, Allocator.Temp); + using (reader) + { + ByteUnpacker.ReadValuePacked(ref reader, out long readValue); + Assert.AreEqual(readValue, value); + } + } + + private void CheckSignedPackedSize32(ref FastBufferWriter writer, int value) + { + ulong asUlong = Arithmetic.ZigZagEncode(value); + + if (asUlong <= 240) + { + Assert.AreEqual(1, writer.Position); + } + else if (asUlong <= 2287) + { + Assert.AreEqual(2, writer.Position); + } + else + { + Assert.AreEqual(BitCounter.GetUsedByteCount(asUlong) + 1, writer.Position); + } + } + + private void CheckSignedPackedValue32(ref FastBufferWriter writer, int value) + { + var reader = new FastBufferReader(ref writer, Allocator.Temp); + using (reader) + { + ByteUnpacker.ReadValuePacked(ref reader, out int readValue); + Assert.AreEqual(readValue, value); + } + } + + private unsafe void VerifyBytewiseEquality(T value, T otherValue) where T : unmanaged + { + byte* asBytePointer = (byte*)&value; + byte* otherBytePointer = (byte*)&otherValue; + for (var i = 0; i < sizeof(T); ++i) + { + Assert.AreEqual(asBytePointer[i], otherBytePointer[i]); + } + } + + private unsafe void RunTypeTest(T value) where T : unmanaged + { + var writer = new FastBufferWriter(sizeof(T) * 2, Allocator.Temp); + using (writer) + { + BytePacker.WriteValuePacked(ref writer, (dynamic)value); + var reader = new FastBufferReader(ref writer, Allocator.Temp); + using (reader) + { + + var outVal = new T(); + MethodInfo method; + if (value is Enum) + { + method = typeof(ByteUnpacker).GetMethods().Single(x => + x.Name == "ReadValuePacked" && x.IsGenericMethodDefinition) + .MakeGenericMethod(typeof(T)); + } + else + { + method = typeof(ByteUnpacker).GetMethod("ReadValuePacked", + new[] { typeof(FastBufferReader).MakeByRefType(), typeof(T).MakeByRefType() }); + } + + object[] args = { reader, outVal }; + method.Invoke(null, args); + outVal = (T)args[1]; + Assert.AreEqual(value, outVal); + VerifyBytewiseEquality(value, outVal); + } + } + } + + private unsafe void RunObjectTypeTest(T value) where T : unmanaged + { + var writer = new FastBufferWriter(sizeof(T) * 2, Allocator.Temp); + using (writer) + { + BytePacker.WriteObjectPacked(ref writer, value); + var reader = new FastBufferReader(ref writer, Allocator.Temp); + using (reader) + { + + ByteUnpacker.ReadObjectPacked(ref reader, out object outVal, typeof(T)); + Assert.AreEqual(value, outVal); + VerifyBytewiseEquality(value, (T)outVal); + } + } + } + + + + [Test] + public void TestPacking64BitsUnsigned() + { + var writer = new FastBufferWriter(9, Allocator.Temp); + + using (writer) + { + writer.TryBeginWrite(9); + ulong value = 0; + BytePacker.WriteValuePacked(ref writer, value); + Assert.AreEqual(1, writer.Position); + + for (var i = 0; i < 64; ++i) + { + value = 1UL << i; + writer.Seek(0); + writer.Truncate(); + BytePacker.WriteValuePacked(ref writer, value); + CheckUnsignedPackedSize64(ref writer, value); + CheckUnsignedPackedValue64(ref writer, value); + for (var j = 0; j < 8; ++j) + { + value = (1UL << i) | (1UL << j); + writer.Seek(0); + writer.Truncate(); + BytePacker.WriteValuePacked(ref writer, value); + CheckUnsignedPackedSize64(ref writer, value); + CheckUnsignedPackedValue64(ref writer, value); + } + } + } + } + + [Test] + public void TestPacking32BitsUnsigned() + { + var writer = new FastBufferWriter(9, Allocator.Temp); + + using (writer) + { + writer.TryBeginWrite(9); + uint value = 0; + BytePacker.WriteValuePacked(ref writer, value); + Assert.AreEqual(1, writer.Position); + + for (var i = 0; i < 64; ++i) + { + value = 1U << i; + writer.Seek(0); + writer.Truncate(); + BytePacker.WriteValuePacked(ref writer, value); + CheckUnsignedPackedSize32(ref writer, value); + CheckUnsignedPackedValue32(ref writer, value); + for (var j = 0; j < 8; ++j) + { + value = (1U << i) | (1U << j); + writer.Seek(0); + writer.Truncate(); + BytePacker.WriteValuePacked(ref writer, value); + CheckUnsignedPackedSize32(ref writer, value); + CheckUnsignedPackedValue32(ref writer, value); + } + } + } + } + + [Test] + public void TestPacking64BitsSigned() + { + var writer = new FastBufferWriter(9, Allocator.Temp); + + using (writer) + { + writer.TryBeginWrite(9); + long value = 0; + BytePacker.WriteValuePacked(ref writer, value); + Assert.AreEqual(1, writer.Position); + + for (var i = 0; i < 64; ++i) + { + value = 1L << i; + writer.Seek(0); + writer.Truncate(); + BytePacker.WriteValuePacked(ref writer, value); + CheckSignedPackedSize64(ref writer, value); + CheckSignedPackedValue64(ref writer, value); + + writer.Seek(0); + writer.Truncate(); + BytePacker.WriteValuePacked(ref writer, -value); + CheckSignedPackedSize64(ref writer, -value); + CheckSignedPackedValue64(ref writer, -value); + for (var j = 0; j < 8; ++j) + { + value = (1L << i) | (1L << j); + writer.Seek(0); + writer.Truncate(); + BytePacker.WriteValuePacked(ref writer, value); + CheckSignedPackedSize64(ref writer, value); + CheckSignedPackedValue64(ref writer, value); + + writer.Seek(0); + writer.Truncate(); + BytePacker.WriteValuePacked(ref writer, -value); + CheckSignedPackedSize64(ref writer, -value); + CheckSignedPackedValue64(ref writer, -value); + } + } + } + } + + [Test] + public void TestPacking32BitsSigned() + { + var writer = new FastBufferWriter(9, Allocator.Temp); + + using (writer) + { + writer.TryBeginWrite(5); + int value = 0; + BytePacker.WriteValuePacked(ref writer, value); + Assert.AreEqual(1, writer.Position); + + for (var i = 0; i < 64; ++i) + { + value = 1 << i; + writer.Seek(0); + writer.Truncate(); + BytePacker.WriteValuePacked(ref writer, value); + CheckSignedPackedSize32(ref writer, value); + CheckSignedPackedValue32(ref writer, value); + + writer.Seek(0); + writer.Truncate(); + BytePacker.WriteValuePacked(ref writer, -value); + CheckSignedPackedSize32(ref writer, -value); + CheckSignedPackedValue32(ref writer, -value); + for (var j = 0; j < 8; ++j) + { + value = (1 << i) | (1 << j); + writer.Seek(0); + writer.Truncate(); + BytePacker.WriteValuePacked(ref writer, value); + CheckSignedPackedSize32(ref writer, value); + CheckSignedPackedValue32(ref writer, value); + + writer.Seek(0); + writer.Truncate(); + BytePacker.WriteValuePacked(ref writer, -value); + CheckSignedPackedSize32(ref writer, -value); + CheckSignedPackedValue32(ref writer, -value); + } + } + } + } + + private int GetByteCount61Bits(ulong value) + { + + if (value <= 0b0001_1111) + { + return 1; + } + + if (value <= 0b0001_1111_1111_1111) + { + return 2; + } + + if (value <= 0b0001_1111_1111_1111_1111_1111) + { + return 3; + } + + if (value <= 0b0001_1111_1111_1111_1111_1111_1111_1111) + { + return 4; + } + + if (value <= 0b0001_1111_1111_1111_1111_1111_1111_1111_1111_1111) + { + return 5; + } + + if (value <= 0b0001_1111_1111_1111_1111_1111_1111_1111_1111_1111_1111_1111) + { + return 6; + } + + if (value <= 0b0001_1111_1111_1111_1111_1111_1111_1111_1111_1111_1111_1111_1111_1111) + { + return 7; + } + + return 8; + } + + private int GetByteCount30Bits(uint value) + { + + if (value <= 0b0011_1111) + { + return 1; + } + + if (value <= 0b0011_1111_1111_1111) + { + return 2; + } + + if (value <= 0b0011_1111_1111_1111_1111_1111) + { + return 3; + } + + return 4; + } + + private int GetByteCount15Bits(ushort value) + { + + if (value <= 0b0111_1111) + { + return 1; + } + + return 2; + } + + private ulong Get61BitEncodedValue(ref FastBufferWriter writer) + { + var reader = new FastBufferReader(ref writer, Allocator.Temp); + using (reader) + { + ByteUnpacker.ReadValueBitPacked(ref reader, out ulong value); + return value; + } + } + + private long Get60BitSignedEncodedValue(ref FastBufferWriter writer) + { + var reader = new FastBufferReader(ref writer, Allocator.Temp); + using (reader) + { + ByteUnpacker.ReadValueBitPacked(ref reader, out long value); + return value; + } + } + + private uint Get30BitEncodedValue(ref FastBufferWriter writer) + { + var reader = new FastBufferReader(ref writer, Allocator.Temp); + using (reader) + { + ByteUnpacker.ReadValueBitPacked(ref reader, out uint value); + return value; + } + } + + private int Get29BitSignedEncodedValue(ref FastBufferWriter writer) + { + var reader = new FastBufferReader(ref writer, Allocator.Temp); + using (reader) + { + ByteUnpacker.ReadValueBitPacked(ref reader, out int value); + return value; + } + } + + private ushort Get15BitEncodedValue(ref FastBufferWriter writer) + { + var reader = new FastBufferReader(ref writer, Allocator.Temp); + using (reader) + { + ByteUnpacker.ReadValueBitPacked(ref reader, out ushort value); + return value; + } + } + + private short Get14BitSignedEncodedValue(ref FastBufferWriter writer) + { + var reader = new FastBufferReader(ref writer, Allocator.Temp); + using (reader) + { + ByteUnpacker.ReadValueBitPacked(ref reader, out short value); + return value; + } + } + + [Test] + public void TestBitPacking61BitsUnsigned() + { + var writer = new FastBufferWriter(9, Allocator.Temp); + + using (writer) + { + writer.TryBeginWrite(8); + ulong value = 0; + BytePacker.WriteValueBitPacked(ref writer, value); + Assert.AreEqual(1, writer.Position); + Assert.AreEqual(0, writer.ToArray()[0] & 0b111); + Assert.AreEqual(value, Get61BitEncodedValue(ref writer)); + + for (var i = 0; i < 61; ++i) + { + value = 1UL << i; + writer.Seek(0); + writer.Truncate(); + BytePacker.WriteValueBitPacked(ref writer, value); + Assert.AreEqual(GetByteCount61Bits(value), writer.Position, $"Failed on {value} ({i})"); + Assert.AreEqual(GetByteCount61Bits(value) - 1, writer.ToArray()[0] & 0b111, $"Failed on {value} ({i})"); + Assert.AreEqual(value, Get61BitEncodedValue(ref writer)); + + for (var j = 0; j < 8; ++j) + { + value = (1UL << i) | (1UL << j); + writer.Seek(0); + writer.Truncate(); + BytePacker.WriteValueBitPacked(ref writer, value); + Assert.AreEqual(GetByteCount61Bits(value), writer.Position, $"Failed on {value} ({i}, {j})"); + Assert.AreEqual(GetByteCount61Bits(value) - 1, writer.ToArray()[0] & 0b111, $"Failed on {value} ({i}, {j})"); + Assert.AreEqual(value, Get61BitEncodedValue(ref writer)); + } + } + + Assert.Throws(() => { BytePacker.WriteValueBitPacked(ref writer, 1UL << 61); }); + } + } + + [Test] + public void TestBitPacking60BitsSigned() + { + var writer = new FastBufferWriter(9, Allocator.Temp); + + using (writer) + { + writer.TryBeginWrite(8); + long value = 0; + BytePacker.WriteValueBitPacked(ref writer, value); + Assert.AreEqual(1, writer.Position); + Assert.AreEqual(0, writer.ToArray()[0] & 0b111); + Assert.AreEqual(value, Get60BitSignedEncodedValue(ref writer)); + + for (var i = 0; i < 61; ++i) + { + value = 1U << i; + ulong zzvalue = Arithmetic.ZigZagEncode(value); + writer.Seek(0); + writer.Truncate(); + BytePacker.WriteValueBitPacked(ref writer, value); + Assert.AreEqual(GetByteCount61Bits(zzvalue), writer.Position, $"Failed on {value} ({i})"); + Assert.AreEqual(GetByteCount61Bits(zzvalue) - 1, writer.ToArray()[0] & 0b111, $"Failed on {value} ({i})"); + Assert.AreEqual(value, Get60BitSignedEncodedValue(ref writer)); + + value = -value; + zzvalue = Arithmetic.ZigZagEncode(value); + writer.Seek(0); + writer.Truncate(); + BytePacker.WriteValueBitPacked(ref writer, value); + Assert.AreEqual(GetByteCount61Bits(zzvalue), writer.Position, $"Failed on {value} ({i})"); + Assert.AreEqual(GetByteCount61Bits(zzvalue) - 1, writer.ToArray()[0] & 0b111, $"Failed on {value} ({i})"); + Assert.AreEqual(value, Get60BitSignedEncodedValue(ref writer)); + + for (var j = 0; j < 8; ++j) + { + value = (1U << i) | (1U << j); + zzvalue = Arithmetic.ZigZagEncode(value); + writer.Seek(0); + writer.Truncate(); + BytePacker.WriteValueBitPacked(ref writer, value); + Assert.AreEqual(GetByteCount61Bits(zzvalue), writer.Position, $"Failed on {value} ({i}, {j})"); + Assert.AreEqual(GetByteCount61Bits(zzvalue) - 1, writer.ToArray()[0] & 0b111, $"Failed on {value} ({i}, {j})"); + Assert.AreEqual(value, Get60BitSignedEncodedValue(ref writer)); + + value = -value; + zzvalue = Arithmetic.ZigZagEncode(value); + writer.Seek(0); + writer.Truncate(); + BytePacker.WriteValueBitPacked(ref writer, value); + Assert.AreEqual(GetByteCount61Bits(zzvalue), writer.Position, $"Failed on {value} ({i}, {j})"); + Assert.AreEqual(GetByteCount61Bits(zzvalue) - 1, writer.ToArray()[0] & 0b111, $"Failed on {value} ({i}, {j})"); + Assert.AreEqual(value, Get60BitSignedEncodedValue(ref writer)); + } + } + + Assert.Throws(() => { BytePacker.WriteValueBitPacked(ref writer, 1UL << 61); }); + } + } + + [Test] + public void TestBitPacking30BitsUnsigned() + { + var writer = new FastBufferWriter(9, Allocator.Temp); + + using (writer) + { + writer.TryBeginWrite(4); + uint value = 0; + BytePacker.WriteValueBitPacked(ref writer, value); + Assert.AreEqual(1, writer.Position); + Assert.AreEqual(0, writer.ToArray()[0] & 0b11); + Assert.AreEqual(value, Get30BitEncodedValue(ref writer)); + + for (var i = 0; i < 30; ++i) + { + value = 1U << i; + writer.Seek(0); + writer.Truncate(); + BytePacker.WriteValueBitPacked(ref writer, value); + Assert.AreEqual(GetByteCount30Bits(value), writer.Position, $"Failed on {value} ({i})"); + Assert.AreEqual(GetByteCount30Bits(value) - 1, writer.ToArray()[0] & 0b11, $"Failed on {value} ({i})"); + Assert.AreEqual(value, Get30BitEncodedValue(ref writer)); + + for (var j = 0; j < 8; ++j) + { + value = (1U << i) | (1U << j); + writer.Seek(0); + writer.Truncate(); + BytePacker.WriteValueBitPacked(ref writer, value); + Assert.AreEqual(GetByteCount30Bits(value), writer.Position, $"Failed on {value} ({i}, {j})"); + Assert.AreEqual(GetByteCount30Bits(value) - 1, writer.ToArray()[0] & 0b11, $"Failed on {value} ({i}, {j})"); + Assert.AreEqual(value, Get30BitEncodedValue(ref writer)); + } + } + + Assert.Throws(() => { BytePacker.WriteValueBitPacked(ref writer, 1U << 30); }); + } + } + + [Test] + public void TestBitPacking29BitsSigned() + { + var writer = new FastBufferWriter(9, Allocator.Temp); + + using (writer) + { + writer.TryBeginWrite(4); + int value = 0; + BytePacker.WriteValueBitPacked(ref writer, value); + Assert.AreEqual(1, writer.Position); + Assert.AreEqual(0, writer.ToArray()[0] & 0b11); + Assert.AreEqual(value, Get30BitEncodedValue(ref writer)); + + for (var i = 0; i < 29; ++i) + { + value = 1 << i; + uint zzvalue = (uint)Arithmetic.ZigZagEncode(value); + writer.Seek(0); + writer.Truncate(); + BytePacker.WriteValueBitPacked(ref writer, value); + Assert.AreEqual(GetByteCount30Bits(zzvalue), writer.Position, $"Failed on {value} ({i})"); + Assert.AreEqual(GetByteCount30Bits(zzvalue) - 1, writer.ToArray()[0] & 0b11, $"Failed on {value} ({i})"); + Assert.AreEqual(value, Get29BitSignedEncodedValue(ref writer)); + + value = -value; + zzvalue = (uint)Arithmetic.ZigZagEncode(value); + writer.Seek(0); + writer.Truncate(); + BytePacker.WriteValueBitPacked(ref writer, value); + Assert.AreEqual(GetByteCount30Bits(zzvalue), writer.Position, $"Failed on {value} ({i})"); + Assert.AreEqual(GetByteCount30Bits(zzvalue) - 1, writer.ToArray()[0] & 0b11, $"Failed on {value} ({i})"); + Assert.AreEqual(value, Get29BitSignedEncodedValue(ref writer)); + + for (var j = 0; j < 8; ++j) + { + value = (1 << i) | (1 << j); + zzvalue = (uint)Arithmetic.ZigZagEncode(value); + writer.Seek(0); + writer.Truncate(); + BytePacker.WriteValueBitPacked(ref writer, value); + Assert.AreEqual(GetByteCount30Bits(zzvalue), writer.Position, $"Failed on {value} ({i}, {j})"); + Assert.AreEqual(GetByteCount30Bits(zzvalue) - 1, writer.ToArray()[0] & 0b11, $"Failed on {value} ({i}, {j})"); + Assert.AreEqual(value, Get29BitSignedEncodedValue(ref writer)); + + value = -value; + zzvalue = (uint)Arithmetic.ZigZagEncode(value); + writer.Seek(0); + writer.Truncate(); + BytePacker.WriteValueBitPacked(ref writer, value); + Assert.AreEqual(GetByteCount30Bits(zzvalue), writer.Position, $"Failed on {value} ({i}, {j})"); + Assert.AreEqual(GetByteCount30Bits(zzvalue) - 1, writer.ToArray()[0] & 0b11, $"Failed on {value} ({i}, {j})"); + Assert.AreEqual(value, Get29BitSignedEncodedValue(ref writer)); + } + } + } + } + + [Test] + public void TestBitPacking15BitsUnsigned() + { + var writer = new FastBufferWriter(9, Allocator.Temp); + + using (writer) + { + writer.TryBeginWrite(2); + ushort value = 0; + BytePacker.WriteValueBitPacked(ref writer, value); + Assert.AreEqual(1, writer.Position); + Assert.AreEqual(0, writer.ToArray()[0] & 0b1); + Assert.AreEqual(value, Get15BitEncodedValue(ref writer)); + + for (var i = 0; i < 15; ++i) + { + value = (ushort)(1U << i); + writer.Seek(0); + writer.Truncate(); + BytePacker.WriteValueBitPacked(ref writer, value); + Assert.AreEqual(GetByteCount15Bits(value), writer.Position, $"Failed on {value} ({i})"); + Assert.AreEqual(GetByteCount15Bits(value) - 1, writer.ToArray()[0] & 0b1, $"Failed on {value} ({i})"); + Assert.AreEqual(value, Get15BitEncodedValue(ref writer)); + + for (var j = 0; j < 8; ++j) + { + value = (ushort)((1U << i) | (1U << j)); + writer.Seek(0); + writer.Truncate(); + BytePacker.WriteValueBitPacked(ref writer, value); + Assert.AreEqual(GetByteCount15Bits(value), writer.Position, $"Failed on {value} ({i}, {j})"); + Assert.AreEqual(GetByteCount15Bits(value) - 1, writer.ToArray()[0] & 0b1, $"Failed on {value} ({i}, {j})"); + Assert.AreEqual(value, Get15BitEncodedValue(ref writer)); + } + } + + Assert.Throws(() => { BytePacker.WriteValueBitPacked(ref writer, (ushort)(1U << 15)); }); + } + } + [Test] + public void TestBitPacking14BitsSigned() + { + var writer = new FastBufferWriter(9, Allocator.Temp); + + using (writer) + { + writer.TryBeginWrite(2); + short value = 0; + BytePacker.WriteValueBitPacked(ref writer, value); + Assert.AreEqual(1, writer.Position); + Assert.AreEqual(0, writer.ToArray()[0] & 0b1); + Assert.AreEqual(value, Get15BitEncodedValue(ref writer)); + + for (var i = 0; i < 14; ++i) + { + value = (short)(1 << i); + ushort zzvalue = (ushort)Arithmetic.ZigZagEncode(value); + writer.Seek(0); + writer.Truncate(); + BytePacker.WriteValueBitPacked(ref writer, value); + Assert.AreEqual(GetByteCount15Bits(zzvalue), writer.Position, $"Failed on {value} ({i})"); + Assert.AreEqual(GetByteCount15Bits(zzvalue) - 1, writer.ToArray()[0] & 0b1, $"Failed on {value} ({i})"); + Assert.AreEqual(value, Get14BitSignedEncodedValue(ref writer)); + + value = (short)-value; + zzvalue = (ushort)Arithmetic.ZigZagEncode(value); + writer.Seek(0); + writer.Truncate(); + BytePacker.WriteValueBitPacked(ref writer, value); + Assert.AreEqual(GetByteCount15Bits(zzvalue), writer.Position, $"Failed on {value} ({i})"); + Assert.AreEqual(GetByteCount15Bits(zzvalue) - 1, writer.ToArray()[0] & 0b1, $"Failed on {value} ({i})"); + Assert.AreEqual(value, Get14BitSignedEncodedValue(ref writer)); + + for (var j = 0; j < 8; ++j) + { + value = (short)((1 << i) | (1 << j)); + zzvalue = (ushort)Arithmetic.ZigZagEncode(value); + writer.Seek(0); + writer.Truncate(); + BytePacker.WriteValueBitPacked(ref writer, value); + Assert.AreEqual(GetByteCount15Bits(zzvalue), writer.Position, $"Failed on {value} ({i}, {j})"); + Assert.AreEqual(GetByteCount15Bits(zzvalue) - 1, writer.ToArray()[0] & 0b1, $"Failed on {value} ({i}, {j})"); + Assert.AreEqual(value, Get14BitSignedEncodedValue(ref writer)); + + value = (short)-value; + zzvalue = (ushort)Arithmetic.ZigZagEncode(value); + writer.Seek(0); + writer.Truncate(); + BytePacker.WriteValueBitPacked(ref writer, value); + Assert.AreEqual(GetByteCount15Bits(zzvalue), writer.Position, $"Failed on {value} ({i}, {j})"); + Assert.AreEqual(GetByteCount15Bits(zzvalue) - 1, writer.ToArray()[0] & 0b1, $"Failed on {value} ({i}, {j})"); + Assert.AreEqual(value, Get14BitSignedEncodedValue(ref writer)); + } + } + } + } + + [Test] + public void TestPackingBasicTypes( + [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(Vector4), + typeof(Quaternion), typeof(Color), typeof(Color32), typeof(Ray), typeof(Ray2D))] + Type testType, + [Values] WriteType writeType) + { + var random = new Random(); + + if (testType == typeof(byte)) + { + byte b = (byte)random.Next(); + if (writeType == WriteType.WriteDirect) + { + RunTypeTest(b); + } + else + { + RunObjectTypeTest(b); + } + } + else if (testType == typeof(sbyte)) + { + sbyte sb = (sbyte)random.Next(); + if (writeType == WriteType.WriteDirect) + { + RunTypeTest(sb); + } + else + { + RunObjectTypeTest(sb); + } + } + else if (testType == typeof(short)) + { + short s = (short)random.Next(); + if (writeType == WriteType.WriteDirect) + { + RunTypeTest(s); + } + else + { + RunObjectTypeTest(s); + } + } + else if (testType == typeof(ushort)) + { + ushort us = (ushort)random.Next(); + if (writeType == WriteType.WriteDirect) + { + RunTypeTest(us); + } + else + { + RunObjectTypeTest(us); + } + } + else if (testType == typeof(int)) + { + int i = random.Next(); + if (writeType == WriteType.WriteDirect) + { + RunTypeTest(i); + } + else + { + RunObjectTypeTest(i); + } + } + else if (testType == typeof(uint)) + { + uint ui = (uint)random.Next(); + if (writeType == WriteType.WriteDirect) + { + RunTypeTest(ui); + } + else + { + RunObjectTypeTest(ui); + } + } + else if (testType == typeof(long)) + { + long l = ((long)random.Next() << 32) + random.Next(); + if (writeType == WriteType.WriteDirect) + { + RunTypeTest(l); + } + else + { + RunObjectTypeTest(l); + } + } + else if (testType == typeof(ulong)) + { + ulong ul = ((ulong)random.Next() << 32) + (ulong)random.Next(); + if (writeType == WriteType.WriteDirect) + { + RunTypeTest(ul); + } + else + { + RunObjectTypeTest(ul); + } + } + else if (testType == typeof(bool)) + { + if (writeType == WriteType.WriteDirect) + { + RunTypeTest(true); + } + else + { + RunObjectTypeTest(true); + } + } + else if (testType == typeof(char)) + { + char c = 'a'; + if (writeType == WriteType.WriteDirect) + { + RunTypeTest(c); + } + else + { + RunObjectTypeTest(c); + } + + c = '\u263a'; + if (writeType == WriteType.WriteDirect) + { + RunTypeTest(c); + } + else + { + RunObjectTypeTest(c); + } + } + else if (testType == typeof(float)) + { + float f = (float)random.NextDouble(); + if (writeType == WriteType.WriteDirect) + { + RunTypeTest(f); + } + else + { + RunObjectTypeTest(f); + } + } + else if (testType == typeof(double)) + { + double d = random.NextDouble(); + if (writeType == WriteType.WriteDirect) + { + RunTypeTest(d); + } + else + { + RunObjectTypeTest(d); + } + } + else if (testType == typeof(ByteEnum)) + { + ByteEnum e = ByteEnum.C; + if (writeType == WriteType.WriteDirect) + { + RunTypeTest(e); + } + else + { + RunObjectTypeTest(e); + } + } + else if (testType == typeof(SByteEnum)) + { + SByteEnum e = SByteEnum.C; + if (writeType == WriteType.WriteDirect) + { + RunTypeTest(e); + } + else + { + RunObjectTypeTest(e); + } + } + else if (testType == typeof(ShortEnum)) + { + ShortEnum e = ShortEnum.C; + if (writeType == WriteType.WriteDirect) + { + RunTypeTest(e); + } + else + { + RunObjectTypeTest(e); + } + } + else if (testType == typeof(UShortEnum)) + { + UShortEnum e = UShortEnum.C; + if (writeType == WriteType.WriteDirect) + { + RunTypeTest(e); + } + else + { + RunObjectTypeTest(e); + } + } + else if (testType == typeof(IntEnum)) + { + IntEnum e = IntEnum.C; + if (writeType == WriteType.WriteDirect) + { + RunTypeTest(e); + } + else + { + RunObjectTypeTest(e); + } + } + else if (testType == typeof(UIntEnum)) + { + UIntEnum e = UIntEnum.C; + if (writeType == WriteType.WriteDirect) + { + RunTypeTest(e); + } + else + { + RunObjectTypeTest(e); + } + } + else if (testType == typeof(LongEnum)) + { + LongEnum e = LongEnum.C; + if (writeType == WriteType.WriteDirect) + { + RunTypeTest(e); + } + else + { + RunObjectTypeTest(e); + } + } + else if (testType == typeof(ULongEnum)) + { + ULongEnum e = ULongEnum.C; + if (writeType == WriteType.WriteDirect) + { + RunTypeTest(e); + } + else + { + RunObjectTypeTest(e); + } + } + else if (testType == typeof(Vector2)) + { + var v = new Vector2((float)random.NextDouble(), (float)random.NextDouble()); + if (writeType == WriteType.WriteDirect) + { + RunTypeTest(v); + } + else + { + RunObjectTypeTest(v); + } + } + else if (testType == typeof(Vector3)) + { + var v = new Vector3((float)random.NextDouble(), (float)random.NextDouble(), (float)random.NextDouble()); + if (writeType == WriteType.WriteDirect) + { + RunTypeTest(v); + } + else + { + RunObjectTypeTest(v); + } + } + else if (testType == typeof(Vector4)) + { + var v = new Vector4((float)random.NextDouble(), (float)random.NextDouble(), (float)random.NextDouble(), (float)random.NextDouble()); + if (writeType == WriteType.WriteDirect) + { + RunTypeTest(v); + } + else + { + RunObjectTypeTest(v); + } + } + else if (testType == typeof(Quaternion)) + { + var v = new Quaternion((float)random.NextDouble(), (float)random.NextDouble(), (float)random.NextDouble(), (float)random.NextDouble()); + if (writeType == WriteType.WriteDirect) + { + RunTypeTest(v); + } + else + { + RunObjectTypeTest(v); + } + } + else if (testType == typeof(Color)) + { + var v = new Color((float)random.NextDouble(), (float)random.NextDouble(), (float)random.NextDouble(), (float)random.NextDouble()); + if (writeType == WriteType.WriteDirect) + { + RunTypeTest(v); + } + else + { + RunObjectTypeTest(v); + } + } + else if (testType == typeof(Color32)) + { + var v = new Color32((byte)random.Next(), (byte)random.Next(), (byte)random.Next(), (byte)random.Next()); + if (writeType == WriteType.WriteDirect) + { + RunTypeTest(v); + } + else + { + RunObjectTypeTest(v); + } + } + else if (testType == typeof(Ray)) + { + // Rays need special handling on the equality checks because the constructor normalizes direction + // Which can cause slight variations in the result + var v = new Ray( + new Vector3((float)random.NextDouble(), (float)random.NextDouble(), (float)random.NextDouble()), + new Vector3((float)random.NextDouble(), (float)random.NextDouble(), (float)random.NextDouble())); + if (writeType == WriteType.WriteDirect) + { + unsafe + { + var writer = new FastBufferWriter(sizeof(Ray) * 2, Allocator.Temp); + using (writer) + { + BytePacker.WriteValuePacked(ref writer, v); + var reader = new FastBufferReader(ref writer, Allocator.Temp); + using (reader) + { + ByteUnpacker.ReadValuePacked(ref reader, out Ray outVal); + Assert.AreEqual(v.origin, outVal.origin); + Assert.AreEqual(v.direction.x, outVal.direction.x, 0.00001); + Assert.AreEqual(v.direction.y, outVal.direction.y, 0.00001); + Assert.AreEqual(v.direction.z, outVal.direction.z, 0.00001); + } + } + } + } + else + { + unsafe + { + var writer = new FastBufferWriter(sizeof(Ray) * 2, Allocator.Temp); + using (writer) + { + BytePacker.WriteObjectPacked(ref writer, v); + var reader = new FastBufferReader(ref writer, Allocator.Temp); + using (reader) + { + ByteUnpacker.ReadObjectPacked(ref reader, out object outVal, typeof(Ray)); + Assert.AreEqual(v.origin, ((Ray)outVal).origin); + Assert.AreEqual(v.direction.x, ((Ray)outVal).direction.x, 0.00001); + Assert.AreEqual(v.direction.y, ((Ray)outVal).direction.y, 0.00001); + Assert.AreEqual(v.direction.z, ((Ray)outVal).direction.z, 0.00001); + } + } + } + } + } + else if (testType == typeof(Ray2D)) + { + // Rays need special handling on the equality checks because the constructor normalizes direction + // Which can cause slight variations in the result + var v = new Ray2D( + new Vector2((float)random.NextDouble(), (float)random.NextDouble()), + new Vector2((float)random.NextDouble(), (float)random.NextDouble())); + if (writeType == WriteType.WriteDirect) + { + unsafe + { + var writer = new FastBufferWriter(sizeof(Ray2D) * 2, Allocator.Temp); + using (writer) + { + BytePacker.WriteValuePacked(ref writer, v); + var reader = new FastBufferReader(ref writer, Allocator.Temp); + using (reader) + { + ByteUnpacker.ReadValuePacked(ref reader, out Ray2D outVal); + Assert.AreEqual(v.origin, outVal.origin); + Assert.AreEqual(v.direction.x, outVal.direction.x, 0.00001); + Assert.AreEqual(v.direction.y, outVal.direction.y, 0.00001); + } + } + } + } + else + { + unsafe + { + var writer = new FastBufferWriter(sizeof(Ray2D) * 2, Allocator.Temp); + using (writer) + { + BytePacker.WriteObjectPacked(ref writer, v); + var reader = new FastBufferReader(ref writer, Allocator.Temp); + using (reader) + { + ByteUnpacker.ReadObjectPacked(ref reader, out object outVal, typeof(Ray2D)); + Assert.AreEqual(v.origin, ((Ray2D)outVal).origin); + Assert.AreEqual(v.direction.x, ((Ray2D)outVal).direction.x, 0.00001); + Assert.AreEqual(v.direction.y, ((Ray2D)outVal).direction.y, 0.00001); + } + } + } + } + } + else + { + Assert.Fail("No type handler was provided for this type in the test!"); + } + } + } +} diff --git a/com.unity.netcode.gameobjects/Tests/Editor/Serialization/BytePackerTests.cs.meta b/com.unity.netcode.gameobjects/Tests/Editor/Serialization/BytePackerTests.cs.meta new file mode 100644 index 0000000000..4c07179b82 --- /dev/null +++ b/com.unity.netcode.gameobjects/Tests/Editor/Serialization/BytePackerTests.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: b50db056cd7443b4eb2e00b603d4c15c +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/com.unity.netcode.gameobjects/Tests/Editor/Serialization/FastBufferReaderTests.cs b/com.unity.netcode.gameobjects/Tests/Editor/Serialization/FastBufferReaderTests.cs new file mode 100644 index 0000000000..0f3e36e2e8 --- /dev/null +++ b/com.unity.netcode.gameobjects/Tests/Editor/Serialization/FastBufferReaderTests.cs @@ -0,0 +1,1027 @@ +using System; +using NUnit.Framework; +using Unity.Collections; +using UnityEngine; +using Random = System.Random; + +namespace Unity.Netcode.EditorTests +{ + public class FastBufferReaderTests : BaseFastBufferReaderWriterTest + { + #region Common Checks + private void WriteCheckBytes(ref FastBufferWriter writer, int writeSize, string failMessage = "") + { + Assert.IsTrue(writer.TryBeginWrite(2), "Writer denied write permission"); + writer.WriteValue((byte)0x80); + Assert.AreEqual(writeSize + 1, writer.Position, failMessage); + Assert.AreEqual(writeSize + 1, writer.Length, failMessage); + writer.WriteValue((byte)0xFF); + Assert.AreEqual(writeSize + 2, writer.Position, failMessage); + Assert.AreEqual(writeSize + 2, writer.Length, failMessage); + } + + private void VerifyCheckBytes(ref FastBufferReader reader, int checkPosition, string failMessage = "") + { + reader.Seek(checkPosition); + reader.TryBeginRead(2); + + reader.ReadByte(out byte value); + Assert.AreEqual(0x80, value, failMessage); + reader.ReadByte(out value); + Assert.AreEqual(0xFF, value, failMessage); + } + + private void VerifyPositionAndLength(ref FastBufferReader reader, int length, string failMessage = "") + { + Assert.AreEqual(0, reader.Position, failMessage); + Assert.AreEqual(length, reader.Length, failMessage); + } + + private FastBufferReader CommonChecks(ref FastBufferWriter writer, T valueToTest, int writeSize, string failMessage = "") where T : unmanaged + { + WriteCheckBytes(ref writer, writeSize, failMessage); + + var reader = new FastBufferReader(ref writer, Allocator.Temp); + + VerifyPositionAndLength(ref reader, writer.Length, failMessage); + + VerifyCheckBytes(ref reader, writeSize, failMessage); + + reader.Seek(0); + + return reader; + } + #endregion + + #region Generic Checks + protected override unsafe void RunTypeTest(T valueToTest) + { + var writeSize = FastBufferWriter.GetWriteSize(valueToTest); + Assert.AreEqual(sizeof(T), writeSize); + var writer = new FastBufferWriter(writeSize + 2, Allocator.Temp); + + using (writer) + { + Assert.IsTrue(writer.TryBeginWrite(writeSize + 2), "Writer denied write permission"); + + var failMessage = $"RunTypeTest failed with type {typeof(T)} and value {valueToTest}"; + + writer.WriteValue(valueToTest); + + var reader = CommonChecks(ref writer, valueToTest, writeSize, failMessage); + + using (reader) + { + Assert.IsTrue(reader.TryBeginRead(FastBufferWriter.GetWriteSize())); + reader.ReadValue(out T result); + Assert.AreEqual(valueToTest, result); + } + } + } + protected override unsafe void RunTypeTestSafe(T valueToTest) + { + var writeSize = FastBufferWriter.GetWriteSize(valueToTest); + var writer = new FastBufferWriter(writeSize + 2, Allocator.Temp); + + using (writer) + { + Assert.AreEqual(sizeof(T), writeSize); + + var failMessage = $"RunTypeTest failed with type {typeof(T)} and value {valueToTest}"; + + writer.WriteValueSafe(valueToTest); + + + var reader = CommonChecks(ref writer, valueToTest, writeSize, failMessage); + + using (reader) + { + reader.ReadValueSafe(out T result); + Assert.AreEqual(valueToTest, result); + } + } + } + + protected override unsafe void RunObjectTypeTest(T valueToTest) + { + var writeSize = FastBufferWriter.GetWriteSize(valueToTest); + var writer = new FastBufferWriter(writeSize + 2, Allocator.Temp); + + using (writer) + { + Assert.AreEqual(sizeof(T), writeSize); + + var failMessage = $"RunObjectTypeTest failed with type {typeof(T)} and value {valueToTest}"; + writer.WriteObject(valueToTest); + + var reader = CommonChecks(ref writer, valueToTest, writeSize, failMessage); + + using (reader) + { + reader.ReadObject(out object result, typeof(T)); + Assert.AreEqual(valueToTest, result); + } + } + } + + private void VerifyArrayEquality(T[] value, T[] 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); + 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"); + + writer.WriteValue(valueToTest); + + WriteCheckBytes(ref writer, writeSize); + + var reader = new FastBufferReader(ref writer, Allocator.Temp); + using (reader) + { + VerifyPositionAndLength(ref reader, writer.Length); + + Assert.IsTrue(reader.TryBeginRead(writeSize)); + reader.ReadValue(out T[] result); + VerifyArrayEquality(valueToTest, result, 0); + + VerifyCheckBytes(ref reader, writeSize); + } + } + } + + protected override unsafe void RunTypeArrayTestSafe(T[] 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); + + writer.WriteValueSafe(valueToTest); + + WriteCheckBytes(ref writer, writeSize); + + var reader = new FastBufferReader(ref writer, Allocator.Temp); + using (reader) + { + VerifyPositionAndLength(ref reader, writer.Length); + + reader.ReadValueSafe(out T[] result); + VerifyArrayEquality(valueToTest, result, 0); + + VerifyCheckBytes(ref reader, writeSize); + } + } + } + + protected override unsafe void RunObjectTypeArrayTest(T[] valueToTest) + { + var writeSize = FastBufferWriter.GetWriteSize(valueToTest); + // Extra byte for WriteObject adding isNull flag + var writer = new FastBufferWriter(writeSize + 3, Allocator.Temp); + using (writer) + { + Assert.AreEqual(sizeof(int) + sizeof(T) * valueToTest.Length, writeSize); + + writer.WriteObject(valueToTest); + + WriteCheckBytes(ref writer, writeSize + 1); + + var reader = new FastBufferReader(ref writer, Allocator.Temp); + using (reader) + { + VerifyPositionAndLength(ref reader, writer.Length); + + reader.ReadObject(out object result, typeof(T[])); + VerifyArrayEquality(valueToTest, (T[])result, 0); + + VerifyCheckBytes(ref reader, writeSize + 1); + } + } + } + #endregion + + #region Tests + [Test] + public void GivenFastBufferWriterContainingValue_WhenReadingUnmanagedType_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(Vector4), + typeof(Quaternion), typeof(Color), typeof(Color32), typeof(Ray), typeof(Ray2D), typeof(TestStruct))] + Type testType, + [Values] WriteType writeType) + { + BaseTypeTest(testType, writeType); + } + + [Test] + public void GivenFastBufferWriterContainingValue_WhenReadingArrayOfUnmanagedElementType_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(Vector4), + typeof(Quaternion), typeof(Color), typeof(Color32), typeof(Ray), typeof(Ray2D), typeof(TestStruct))] + Type testType, + [Values] WriteType writeType) + { + BaseArrayTypeTest(testType, writeType); + } + + [TestCase(false, WriteType.WriteDirect)] + [TestCase(false, WriteType.WriteSafe)] + [TestCase(false, WriteType.WriteAsObject)] + [TestCase(true, WriteType.WriteDirect)] + [TestCase(true, WriteType.WriteSafe)] + public void GivenFastBufferWriterContainingValue_WhenReadingString_ValueMatchesWhatWasWritten(bool oneByteChars, WriteType writeType) + { + string valueToTest = "Hello, I am a test string!"; + + var serializedValueSize = FastBufferWriter.GetWriteSize(valueToTest, oneByteChars); + + var writer = new FastBufferWriter(serializedValueSize + 3, Allocator.Temp); + using (writer) + { + switch (writeType) + { + case WriteType.WriteDirect: + Assert.IsTrue(writer.TryBeginWrite(serializedValueSize + 2), "Writer denied write permission"); + writer.WriteValue(valueToTest, oneByteChars); + break; + case WriteType.WriteSafe: + writer.WriteValueSafe(valueToTest, oneByteChars); + break; + case WriteType.WriteAsObject: + writer.WriteObject(valueToTest); + serializedValueSize += 1; + break; + } + + WriteCheckBytes(ref writer, serializedValueSize); + + var reader = new FastBufferReader(ref writer, Allocator.Temp); + using (reader) + { + VerifyPositionAndLength(ref reader, writer.Length); + + string result = null; + switch (writeType) + { + case WriteType.WriteDirect: + Assert.IsTrue(reader.TryBeginRead(serializedValueSize + 2), "Reader denied read permission"); + reader.ReadValue(out result, oneByteChars); + break; + case WriteType.WriteSafe: + reader.ReadValueSafe(out result, oneByteChars); + break; + case WriteType.WriteAsObject: + reader.ReadObject(out object resultObj, typeof(string), oneByteChars); + result = (string)resultObj; + break; + } + Assert.AreEqual(valueToTest, result); + + VerifyCheckBytes(ref reader, serializedValueSize); + } + } + } + + + [TestCase(1, 0)] + [TestCase(2, 0)] + [TestCase(3, 0)] + [TestCase(4, 0)] + [TestCase(5, 0)] + [TestCase(6, 0)] + [TestCase(7, 0)] + [TestCase(8, 0)] + + [TestCase(1, 1)] + [TestCase(2, 1)] + [TestCase(3, 1)] + [TestCase(4, 1)] + [TestCase(5, 1)] + [TestCase(6, 1)] + [TestCase(7, 1)] + + [TestCase(1, 2)] + [TestCase(2, 2)] + [TestCase(3, 2)] + [TestCase(4, 2)] + [TestCase(5, 2)] + [TestCase(6, 2)] + + [TestCase(1, 3)] + [TestCase(2, 3)] + [TestCase(3, 3)] + [TestCase(4, 3)] + [TestCase(5, 3)] + + [TestCase(1, 4)] + [TestCase(2, 4)] + [TestCase(3, 4)] + [TestCase(4, 4)] + + [TestCase(1, 5)] + [TestCase(2, 5)] + [TestCase(3, 5)] + + [TestCase(1, 6)] + [TestCase(2, 6)] + + [TestCase(1, 7)] + public void GivenFastBufferWriterContainingValue_WhenReadingPartialValue_ValueMatchesWhatWasWritten(int count, int offset) + { + var random = new Random(); + var valueToTest = ((ulong)random.Next() << 32) + (ulong)random.Next(); + var writer = new FastBufferWriter(sizeof(ulong) + 2, Allocator.Temp); + using (writer) + { + Assert.IsTrue(writer.TryBeginWrite(count + 2), "Writer denied write permission"); + writer.WritePartialValue(valueToTest, count, offset); + + var failMessage = $"TestReadingPartialValues failed with value {valueToTest}"; + WriteCheckBytes(ref writer, count, failMessage); + + var reader = new FastBufferReader(ref writer, Allocator.Temp); + using (reader) + { + VerifyPositionAndLength(ref reader, writer.Length, failMessage); + Assert.IsTrue(reader.TryBeginRead(count + 2), "Reader denied read permission"); + + ulong mask = 0; + for (var i = 0; i < count; ++i) + { + mask = (mask << 8) | 0b11111111; + } + + mask <<= (offset * 8); + + reader.ReadPartialValue(out ulong result, count, offset); + Assert.AreEqual(valueToTest & mask, result & mask, failMessage); + VerifyCheckBytes(ref reader, count, failMessage); + } + } + } + + + [Test] + public unsafe void GivenFastBufferReaderInitializedFromFastBufferWriterContainingValue_WhenCallingToArray_ReturnedArrayMatchesContentOfWriter() + { + var testStruct = GetTestStruct(); + var requiredSize = FastBufferWriter.GetWriteSize(testStruct); + var writer = new FastBufferWriter(requiredSize, Allocator.Temp); + + using (writer) + { + writer.TryBeginWrite(requiredSize); + writer.WriteValue(testStruct); + + var reader = new FastBufferReader(ref writer, Allocator.Temp); + using (reader) + { + var array = reader.ToArray(); + var underlyingArray = writer.GetUnsafePtr(); + for (var i = 0; i < array.Length; ++i) + { + Assert.AreEqual(array[i], underlyingArray[i]); + } + } + } + } + + + [Test] + public void WhenCallingReadByteWithoutCallingTryBeingReadFirst_OverflowExceptionIsThrown() + { + var nativeArray = new NativeArray(100, Allocator.Temp); + var emptyReader = new FastBufferReader(nativeArray, Allocator.Temp); + + using (emptyReader) + { + Assert.Throws(() => { emptyReader.ReadByte(out byte b); }); + } + } + + [Test] + public void WhenCallingReadBytesWithoutCallingTryBeingReadFirst_OverflowExceptionIsThrown() + { + var nativeArray = new NativeArray(100, Allocator.Temp); + var emptyReader = new FastBufferReader(nativeArray, Allocator.Temp); + + byte[] b = { 0, 1, 2 }; + using (emptyReader) + { + Assert.Throws(() => { emptyReader.ReadBytes(ref b, 3); }); + } + } + + [Test] + public void WhenCallingReadValueWithUnmanagedTypeWithoutCallingTryBeingReadFirst_OverflowExceptionIsThrown() + { + var nativeArray = new NativeArray(100, Allocator.Temp); + var emptyReader = new FastBufferReader(nativeArray, Allocator.Temp); + + using (emptyReader) + { + Assert.Throws(() => { emptyReader.ReadValue(out int i); }); + } + } + + [Test] + public void WhenCallingReadValueWithByteArrayWithoutCallingTryBeingReadFirst_OverflowExceptionIsThrown() + { + var nativeArray = new NativeArray(100, Allocator.Temp); + var emptyReader = new FastBufferReader(nativeArray, Allocator.Temp); + + using (emptyReader) + { + Assert.Throws(() => { emptyReader.ReadValue(out byte[] b); }); + } + } + + [Test] + public void WhenCallingReadValueWithStringWithoutCallingTryBeingReadFirst_OverflowExceptionIsThrown() + { + var nativeArray = new NativeArray(100, Allocator.Temp); + var emptyReader = new FastBufferReader(nativeArray, Allocator.Temp); + + using (emptyReader) + { + Assert.Throws(() => { emptyReader.ReadValue(out string s); }); + } + } + + [Test] + public void WhenCallingReadValueAfterCallingTryBeginWriteWithTooFewBytes_OverflowExceptionIsThrown() + { + var nativeArray = new NativeArray(100, Allocator.Temp); + var emptyReader = new FastBufferReader(nativeArray, Allocator.Temp); + + using (emptyReader) + { + emptyReader.TryBeginRead(sizeof(int) - 1); + Assert.Throws(() => { emptyReader.ReadValue(out int i); }); + } + } + + [Test] + public void WhenCallingReadBytePastBoundaryMarkedByTryBeginWrite_OverflowExceptionIsThrown() + { + var nativeArray = new NativeArray(100, Allocator.Temp); + var emptyReader = new FastBufferReader(nativeArray, Allocator.Temp); + + using (emptyReader) + { + emptyReader.TryBeginRead(sizeof(int) - 1); + emptyReader.ReadByte(out byte b); + emptyReader.ReadByte(out b); + emptyReader.ReadByte(out b); + Assert.Throws(() => { emptyReader.ReadByte(out b); }); + } + } + + [Test] + public void WhenCallingReadByteDuringBitwiseContext_InvalidOperationExceptionIsThrown() + { + var nativeArray = new NativeArray(100, Allocator.Temp); + var emptyReader = new FastBufferReader(nativeArray, Allocator.Temp); + + using (emptyReader) + { + using var context = emptyReader.EnterBitwiseContext(); + Assert.Throws(() => { emptyReader.ReadByte(out byte b); }); + } + } + + [Test] + public void WhenCallingReadBytesDuringBitwiseContext_InvalidOperationExceptionIsThrown() + { + var nativeArray = new NativeArray(100, Allocator.Temp); + var emptyReader = new FastBufferReader(nativeArray, Allocator.Temp); + + using (emptyReader) + { + using var context = emptyReader.EnterBitwiseContext(); + byte[] b = { 0, 1, 2 }; + Assert.Throws(() => { emptyReader.ReadBytes(ref b, 3); }); + } + } + + [Test] + public void WhenCallingReadValueWithUnmanagedTypeDuringBitwiseContext_InvalidOperationExceptionIsThrown() + { + var nativeArray = new NativeArray(100, Allocator.Temp); + var emptyReader = new FastBufferReader(nativeArray, Allocator.Temp); + + using (emptyReader) + { + using var context = emptyReader.EnterBitwiseContext(); + Assert.Throws(() => { emptyReader.ReadValue(out int i); }); + } + } + + [Test] + public void WhenCallingReadValueWithByteArrayDuringBitwiseContext_InvalidOperationExceptionIsThrown() + { + var nativeArray = new NativeArray(100, Allocator.Temp); + var emptyReader = new FastBufferReader(nativeArray, Allocator.Temp); + + using (emptyReader) + { + using var context = emptyReader.EnterBitwiseContext(); + Assert.Throws(() => { emptyReader.ReadValue(out byte[] b); }); + } + } + + [Test] + public void WhenCallingReadValueWithStringDuringBitwiseContext_InvalidOperationExceptionIsThrown() + { + var nativeArray = new NativeArray(100, Allocator.Temp); + var emptyReader = new FastBufferReader(nativeArray, Allocator.Temp); + + using (emptyReader) + { + using var context = emptyReader.EnterBitwiseContext(); + Assert.Throws(() => { emptyReader.ReadValue(out string s); }); + } + } + + [Test] + public void WhenCallingReadByteSafeDuringBitwiseContext_InvalidOperationExceptionIsThrown() + { + var nativeArray = new NativeArray(100, Allocator.Temp); + var emptyReader = new FastBufferReader(nativeArray, Allocator.Temp); + + using (emptyReader) + { + using var context = emptyReader.EnterBitwiseContext(); + Assert.Throws(() => { emptyReader.ReadByteSafe(out byte b); }); + } + } + + [Test] + public void WhenCallingReadBytesSafeDuringBitwiseContext_InvalidOperationExceptionIsThrown() + { + var nativeArray = new NativeArray(100, Allocator.Temp); + var emptyReader = new FastBufferReader(nativeArray, Allocator.Temp); + + using (emptyReader) + { + using var context = emptyReader.EnterBitwiseContext(); + byte[] b = { 0, 1, 2 }; + Assert.Throws(() => { emptyReader.ReadBytesSafe(ref b, 3); }); + } + } + + [Test] + public void WhenCallingReadValueSafeWithUnmanagedTypeDuringBitwiseContext_InvalidOperationExceptionIsThrown() + { + var nativeArray = new NativeArray(100, Allocator.Temp); + var emptyReader = new FastBufferReader(nativeArray, Allocator.Temp); + + using (emptyReader) + { + using var context = emptyReader.EnterBitwiseContext(); + Assert.Throws(() => { emptyReader.ReadValueSafe(out int i); }); + } + } + + [Test] + public void WhenCallingReadValueSafeWithByteArrayDuringBitwiseContext_InvalidOperationExceptionIsThrown() + { + var nativeArray = new NativeArray(100, Allocator.Temp); + var emptyReader = new FastBufferReader(nativeArray, Allocator.Temp); + + using (emptyReader) + { + using var context = emptyReader.EnterBitwiseContext(); + Assert.Throws(() => { emptyReader.ReadValueSafe(out byte[] b); }); + } + } + + [Test] + public void WhenCallingReadValueSafeWithStringDuringBitwiseContext_InvalidOperationExceptionIsThrown() + { + var nativeArray = new NativeArray(100, Allocator.Temp); + var emptyReader = new FastBufferReader(nativeArray, Allocator.Temp); + + using (emptyReader) + { + using var context = emptyReader.EnterBitwiseContext(); + Assert.Throws(() => { emptyReader.ReadValueSafe(out string s); }); + } + } + + [Test] + public void WhenCallingReadByteAfterExitingBitwiseContext_InvalidOperationExceptionIsNotThrown() + { + var nativeArray = new NativeArray(100, Allocator.Temp); + var emptyReader = new FastBufferReader(nativeArray, Allocator.Temp); + + using (emptyReader) + { + emptyReader.TryBeginRead(100); + using (var context = emptyReader.EnterBitwiseContext()) + { + context.ReadBit(out bool theBit); + } + emptyReader.ReadByte(out byte theByte); + } + } + + [Test] + public void WhenCallingReadBytesAfterExitingBitwiseContext_InvalidOperationExceptionIsNotThrown() + { + var nativeArray = new NativeArray(100, Allocator.Temp); + var emptyReader = new FastBufferReader(nativeArray, Allocator.Temp); + + using (emptyReader) + { + emptyReader.TryBeginRead(100); + using (var context = emptyReader.EnterBitwiseContext()) + { + context.ReadBit(out bool theBit); + } + + byte[] theBytes = { 0, 1, 2 }; + emptyReader.ReadBytes(ref theBytes, 3); + } + } + + [Test] + public void WhenCallingReadValueWithUnmanagedTypeAfterExitingBitwiseContext_InvalidOperationExceptionIsNotThrown() + { + var nativeArray = new NativeArray(100, Allocator.Temp); + var emptyReader = new FastBufferReader(nativeArray, Allocator.Temp); + + using (emptyReader) + { + emptyReader.TryBeginRead(100); + using (var context = emptyReader.EnterBitwiseContext()) + { + context.ReadBit(out bool theBit); + } + emptyReader.ReadValue(out int i); + } + } + + [Test] + public void WhenCallingReadValueWithByteArrayAfterExitingBitwiseContext_InvalidOperationExceptionIsNotThrown() + { + var nativeArray = new NativeArray(100, Allocator.Temp); + var emptyReader = new FastBufferReader(nativeArray, Allocator.Temp); + + using (emptyReader) + { + emptyReader.TryBeginRead(100); + using (var context = emptyReader.EnterBitwiseContext()) + { + context.ReadBit(out bool theBit); + } + emptyReader.ReadValue(out byte[] theBytes); + } + } + + [Test] + public void WhenCallingReadValueWithStringAfterExitingBitwiseContext_InvalidOperationExceptionIsNotThrown() + { + var nativeArray = new NativeArray(100, Allocator.Temp); + var emptyReader = new FastBufferReader(nativeArray, Allocator.Temp); + + using (emptyReader) + { + emptyReader.TryBeginRead(100); + using (var context = emptyReader.EnterBitwiseContext()) + { + context.ReadBit(out bool theBit); + } + emptyReader.ReadValue(out string s); + } + } + + [Test] + public void WhenCallingReadByteSafeAfterExitingBitwiseContext_InvalidOperationExceptionIsNotThrown() + { + var nativeArray = new NativeArray(100, Allocator.Temp); + var emptyReader = new FastBufferReader(nativeArray, Allocator.Temp); + + using (emptyReader) + { + emptyReader.TryBeginRead(100); + using (var context = emptyReader.EnterBitwiseContext()) + { + context.ReadBit(out bool theBit); + } + emptyReader.ReadByteSafe(out byte theByte); + } + } + + [Test] + public void WhenCallingReadBytesSafeAfterExitingBitwiseContext_InvalidOperationExceptionIsNotThrown() + { + var nativeArray = new NativeArray(100, Allocator.Temp); + var emptyReader = new FastBufferReader(nativeArray, Allocator.Temp); + + using (emptyReader) + { + emptyReader.TryBeginRead(100); + using (var context = emptyReader.EnterBitwiseContext()) + { + context.ReadBit(out bool theBit); + } + + byte[] theBytes = { 0, 1, 2 }; + emptyReader.ReadBytesSafe(ref theBytes, 3); + } + } + + [Test] + public void WhenCallingReadValueSafeWithUnmanagedTypeAfterExitingBitwiseContext_InvalidOperationExceptionIsNotThrown() + { + var nativeArray = new NativeArray(100, Allocator.Temp); + var emptyReader = new FastBufferReader(nativeArray, Allocator.Temp); + + using (emptyReader) + { + emptyReader.TryBeginRead(100); + using (var context = emptyReader.EnterBitwiseContext()) + { + context.ReadBit(out bool theBit); + } + emptyReader.ReadValueSafe(out int i); + } + } + + [Test] + public void WhenCallingReadValueSafeWithByteArrayAfterExitingBitwiseContext_InvalidOperationExceptionIsNotThrown() + { + var nativeArray = new NativeArray(100, Allocator.Temp); + var emptyReader = new FastBufferReader(nativeArray, Allocator.Temp); + + using (emptyReader) + { + emptyReader.TryBeginRead(100); + using (var context = emptyReader.EnterBitwiseContext()) + { + context.ReadBit(out bool theBit); + } + emptyReader.ReadValueSafe(out byte[] theBytes); + } + } + + [Test] + public void WhenCallingReadValueSafeWithStringAfterExitingBitwiseContext_InvalidOperationExceptionIsNotThrown() + { + var nativeArray = new NativeArray(100, Allocator.Temp); + var emptyReader = new FastBufferReader(nativeArray, Allocator.Temp); + + using (emptyReader) + { + emptyReader.TryBeginRead(100); + using (var context = emptyReader.EnterBitwiseContext()) + { + context.ReadBit(out bool theBit); + } + emptyReader.ReadValueSafe(out string s); + } + } + + [Test] + public void WhenCallingTryBeginRead_TheAllowedReadPositionIsMarkedRelativeToCurrentPosition() + { + var nativeArray = new NativeArray(100, Allocator.Temp); + var emptyReader = new FastBufferReader(nativeArray, Allocator.Temp); + + using (emptyReader) + { + emptyReader.TryBeginRead(100); + emptyReader.ReadByte(out byte b); + emptyReader.TryBeginRead(1); + emptyReader.ReadByte(out b); + Assert.Throws(() => { emptyReader.ReadByte(out byte b); }); + } + } + + [Test] + public void WhenReadingAfterSeeking_TheNewReadComesFromTheCorrectPosition() + { + var writer = new FastBufferWriter(100, Allocator.Temp); + using (writer) + { + writer.WriteByteSafe(1); + writer.WriteByteSafe(3); + writer.WriteByteSafe(2); + writer.WriteByteSafe(5); + writer.WriteByteSafe(4); + writer.WriteByteSafe(0); + + var reader = new FastBufferReader(ref writer, Allocator.Temp); + using (reader) + { + reader.Seek(5); + reader.ReadByteSafe(out byte b); + Assert.AreEqual(reader.Position, 6); + Assert.AreEqual(reader.Length, writer.Length); + Assert.AreEqual(0, b); + + reader.Seek(0); + reader.ReadByteSafe(out b); + Assert.AreEqual(reader.Position, 1); + Assert.AreEqual(reader.Length, writer.Length); + Assert.AreEqual(1, b); + + reader.Seek(10); + Assert.AreEqual(reader.Position, writer.Length); + Assert.AreEqual(reader.Length, writer.Length); + + reader.Seek(2); + reader.ReadByteSafe(out b); + Assert.AreEqual(2, b); + + reader.Seek(1); + reader.ReadByteSafe(out b); + Assert.AreEqual(3, b); + + reader.Seek(4); + reader.ReadByteSafe(out b); + Assert.AreEqual(4, b); + + reader.Seek(3); + reader.ReadByteSafe(out b); + Assert.AreEqual(5, b); + + Assert.AreEqual(reader.Position, 4); + Assert.AreEqual(reader.Length, writer.Length); + } + } + } + + [Test] + public void GivenFastBufferWriterWithNetworkObjectWritten_WhenReadingNetworkObject_TheSameObjectIsRetrieved([Values] WriteType writeType) + { + RunGameObjectTest((obj, networkBehaviour, networkObject) => + { + var writer = new FastBufferWriter(FastBufferWriterExtensions.GetWriteSize(networkObject) + 1, Allocator.Temp); + using (writer) + { + switch (writeType) + { + case WriteType.WriteDirect: + Assert.IsTrue(writer.TryBeginWrite(FastBufferWriterExtensions.GetWriteSize(networkObject))); + writer.WriteValue(networkObject); + break; + case WriteType.WriteSafe: + writer.WriteValueSafe(networkObject); + break; + case WriteType.WriteAsObject: + writer.WriteObject(networkObject); + break; + } + + var reader = new FastBufferReader(ref writer, Allocator.Temp); + using (reader) + { + Assert.IsTrue(reader.TryBeginRead(FastBufferWriterExtensions.GetNetworkObjectWriteSize())); + NetworkObject result = null; + switch (writeType) + { + case WriteType.WriteDirect: + Assert.IsTrue(reader.TryBeginRead(FastBufferWriterExtensions.GetWriteSize(networkObject))); + reader.ReadValue(out result); + break; + case WriteType.WriteSafe: + reader.ReadValueSafe(out result); + break; + case WriteType.WriteAsObject: + reader.ReadObject(out object resultObj, typeof(NetworkObject)); + result = (NetworkObject)resultObj; + break; + } + Assert.AreSame(result, networkObject); + } + } + }); + } + + [Test] + public void GivenFastBufferWriterWithGameObjectWritten_WhenReadingGameObject_TheSameObjectIsRetrieved([Values] WriteType writeType) + { + RunGameObjectTest((obj, networkBehaviour, networkObject) => + { + var writer = new FastBufferWriter(FastBufferWriterExtensions.GetWriteSize(obj) + 1, Allocator.Temp); + using (writer) + { + switch (writeType) + { + case WriteType.WriteDirect: + Assert.IsTrue(writer.TryBeginWrite(FastBufferWriterExtensions.GetWriteSize(obj))); + writer.WriteValue(obj); + break; + case WriteType.WriteSafe: + writer.WriteValueSafe(obj); + break; + case WriteType.WriteAsObject: + writer.WriteObject(obj); + break; + } + + var reader = new FastBufferReader(ref writer, Allocator.Temp); + using (reader) + { + Assert.IsTrue(reader.TryBeginRead(FastBufferWriterExtensions.GetGameObjectWriteSize())); + GameObject result = null; + switch (writeType) + { + case WriteType.WriteDirect: + Assert.IsTrue(reader.TryBeginRead(FastBufferWriterExtensions.GetWriteSize(obj))); + reader.ReadValue(out result); + break; + case WriteType.WriteSafe: + reader.ReadValueSafe(out result); + break; + case WriteType.WriteAsObject: + reader.ReadObject(out object resultObj, typeof(GameObject)); + result = (GameObject)resultObj; + break; + } + Assert.AreSame(result, obj); + } + } + }); + } + + [Test] + public void GivenFastBufferWriterWithNetworkBehaviourWritten_WhenReadingNetworkBehaviour_TheSameObjectIsRetrieved([Values] WriteType writeType) + { + RunGameObjectTest((obj, networkBehaviour, networkObject) => + { + var writer = new FastBufferWriter(FastBufferWriterExtensions.GetWriteSize(networkBehaviour) + 1, Allocator.Temp); + using (writer) + { + switch (writeType) + { + case WriteType.WriteDirect: + Assert.IsTrue(writer.TryBeginWrite(FastBufferWriterExtensions.GetWriteSize(networkBehaviour))); + writer.WriteValue(networkBehaviour); + break; + case WriteType.WriteSafe: + writer.WriteValueSafe(networkBehaviour); + break; + case WriteType.WriteAsObject: + writer.WriteObject(networkBehaviour); + break; + } + + var reader = new FastBufferReader(ref writer, Allocator.Temp); + using (reader) + { + Assert.IsTrue(reader.TryBeginRead(FastBufferWriterExtensions.GetNetworkBehaviourWriteSize())); + NetworkBehaviour result = null; + switch (writeType) + { + case WriteType.WriteDirect: + Assert.IsTrue(reader.TryBeginRead(FastBufferWriterExtensions.GetWriteSize(networkBehaviour))); + reader.ReadValue(out result); + break; + case WriteType.WriteSafe: + reader.ReadValueSafe(out result); + break; + case WriteType.WriteAsObject: + reader.ReadObject(out object resultObj, typeof(NetworkBehaviour)); + result = (NetworkBehaviour)resultObj; + break; + } + Assert.AreSame(result, networkBehaviour); + } + } + }); + } + + [Test] + public void WhenCallingTryBeginReadInternal_AllowedReadPositionDoesNotMoveBackward() + { + var reader = new FastBufferReader(new NativeArray(100, Allocator.Temp), Allocator.Temp); + using (reader) + { + reader.TryBeginRead(25); + reader.TryBeginReadInternal(5); + Assert.AreEqual(reader.AllowedReadMark, 25); + } + } + + #endregion + } +} diff --git a/com.unity.netcode.gameobjects/Tests/Editor/Serialization/FastBufferReaderTests.cs.meta b/com.unity.netcode.gameobjects/Tests/Editor/Serialization/FastBufferReaderTests.cs.meta new file mode 100644 index 0000000000..52af796039 --- /dev/null +++ b/com.unity.netcode.gameobjects/Tests/Editor/Serialization/FastBufferReaderTests.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 2881f8138b479c34389b76687e5307ab +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/com.unity.netcode.gameobjects/Tests/Editor/Serialization/FastBufferWriterTests.cs b/com.unity.netcode.gameobjects/Tests/Editor/Serialization/FastBufferWriterTests.cs new file mode 100644 index 0000000000..f1d3897737 --- /dev/null +++ b/com.unity.netcode.gameobjects/Tests/Editor/Serialization/FastBufferWriterTests.cs @@ -0,0 +1,1215 @@ +using System; +using NUnit.Framework; +using Unity.Collections; +using UnityEngine; +using Random = System.Random; + +namespace Unity.Netcode.EditorTests +{ + public class FastBufferWriterTests : BaseFastBufferReaderWriterTest + { + + #region Common Checks + private void WriteCheckBytes(ref FastBufferWriter writer, int writeSize, string failMessage = "") + { + Assert.IsTrue(writer.TryBeginWrite(2), "Writer denied write permission"); + writer.WriteValue((byte)0x80); + Assert.AreEqual(writeSize + 1, writer.Position, failMessage); + Assert.AreEqual(writeSize + 1, writer.Length, failMessage); + writer.WriteValue((byte)0xFF); + Assert.AreEqual(writeSize + 2, writer.Position, failMessage); + Assert.AreEqual(writeSize + 2, writer.Length, failMessage); + } + + private void VerifyCheckBytes(byte[] underlyingArray, int writeSize, string failMessage = "") + { + Assert.AreEqual(0x80, underlyingArray[writeSize], failMessage); + Assert.AreEqual(0xFF, underlyingArray[writeSize + 1], failMessage); + } + + private unsafe void VerifyBytewiseEquality(T value, byte[] underlyingArray, int valueOffset, int bufferOffset, int size, string failMessage = "") where T : unmanaged + { + byte* asBytePointer = (byte*)&value; + for (var i = 0; i < size; ++i) + { + Assert.AreEqual(asBytePointer[i + valueOffset], underlyingArray[i + bufferOffset], failMessage); + } + } + + private unsafe void VerifyTypedEquality(T value, byte* unsafePtr) where T : unmanaged + { + var checkValue = (T*)unsafePtr; + Assert.AreEqual(value, *checkValue); + } + + private void VerifyPositionAndLength(ref FastBufferWriter writer, int position, string failMessage = "") + { + Assert.AreEqual(position, writer.Position, failMessage); + Assert.AreEqual(position, writer.Length, failMessage); + } + + private unsafe void CommonChecks(ref FastBufferWriter writer, T valueToTest, int writeSize, string failMessage = "") where T : unmanaged + { + + VerifyPositionAndLength(ref writer, writeSize, failMessage); + + WriteCheckBytes(ref writer, writeSize, failMessage); + + var underlyingArray = writer.ToArray(); + + VerifyBytewiseEquality(valueToTest, underlyingArray, 0, 0, writeSize, failMessage); + + VerifyCheckBytes(underlyingArray, writeSize, failMessage); + + VerifyTypedEquality(valueToTest, writer.GetUnsafePtr()); + } + #endregion + + #region Generic Checks + protected override unsafe void RunTypeTest(T valueToTest) + { + var writeSize = FastBufferWriter.GetWriteSize(valueToTest); + var alternateWriteSize = FastBufferWriter.GetWriteSize(); + Assert.AreEqual(sizeof(T), writeSize); + Assert.AreEqual(sizeof(T), alternateWriteSize); + + var writer = new FastBufferWriter(writeSize + 2, Allocator.Temp); + + using (writer) + { + + Assert.IsTrue(writer.TryBeginWrite(writeSize + 2), "Writer denied write permission"); + + var failMessage = $"RunTypeTest failed with type {typeof(T)} and value {valueToTest}"; + + writer.WriteValue(valueToTest); + + CommonChecks(ref writer, valueToTest, writeSize, failMessage); + } + } + protected override unsafe void RunTypeTestSafe(T valueToTest) + { + var writeSize = FastBufferWriter.GetWriteSize(valueToTest); + var writer = new FastBufferWriter(writeSize + 2, Allocator.Temp); + + using (writer) + { + Assert.AreEqual(sizeof(T), writeSize); + + var failMessage = $"RunTypeTest failed with type {typeof(T)} and value {valueToTest}"; + + writer.WriteValueSafe(valueToTest); + + CommonChecks(ref writer, valueToTest, writeSize, failMessage); + } + } + + protected override unsafe void RunObjectTypeTest(T valueToTest) + { + var writeSize = FastBufferWriter.GetWriteSize(valueToTest); + var writer = new FastBufferWriter(writeSize + 2, Allocator.Temp); + + using (writer) + { + Assert.AreEqual(sizeof(T), writeSize); + + var failMessage = $"RunObjectTypeTest failed with type {typeof(T)} and value {valueToTest}"; + writer.WriteObject(valueToTest); + + CommonChecks(ref writer, valueToTest, writeSize, failMessage); + } + } + + private unsafe void VerifyArrayEquality(T[] value, byte* unsafePtr, int offset) where T : unmanaged + { + int* sizeValue = (int*)(unsafePtr + offset); + Assert.AreEqual(value.Length, *sizeValue); + + fixed (T* asTPointer = value) + { + 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); + 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"); + + writer.WriteValue(valueToTest); + VerifyPositionAndLength(ref writer, writeSize); + + WriteCheckBytes(ref writer, writeSize); + + VerifyArrayEquality(valueToTest, writer.GetUnsafePtr(), 0); + + var underlyingArray = writer.ToArray(); + VerifyCheckBytes(underlyingArray, writeSize); + } + } + + protected override unsafe void RunTypeArrayTestSafe(T[] 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); + + writer.WriteValueSafe(valueToTest); + VerifyPositionAndLength(ref writer, writeSize); + + WriteCheckBytes(ref writer, writeSize); + + VerifyArrayEquality(valueToTest, writer.GetUnsafePtr(), 0); + + var underlyingArray = writer.ToArray(); + VerifyCheckBytes(underlyingArray, writeSize); + } + } + + protected override unsafe void RunObjectTypeArrayTest(T[] valueToTest) + { + var writeSize = FastBufferWriter.GetWriteSize(valueToTest); + // Extra byte for WriteObject adding isNull flag + var writer = new FastBufferWriter(writeSize + 3, Allocator.Temp); + using (writer) + { + + Assert.AreEqual(sizeof(int) + sizeof(T) * valueToTest.Length, writeSize); + + writer.WriteObject(valueToTest); + Assert.AreEqual(0, writer.ToArray()[0]); + VerifyPositionAndLength(ref writer, writeSize + sizeof(byte)); + + WriteCheckBytes(ref writer, writeSize + sizeof(byte)); + + VerifyArrayEquality(valueToTest, writer.GetUnsafePtr(), sizeof(byte)); + + var underlyingArray = writer.ToArray(); + VerifyCheckBytes(underlyingArray, writeSize + sizeof(byte)); + } + } + #endregion + + + #region Tests + [Test, Description("Tests ")] + public void WhenWritingUnmanagedType_ValueIsWrittenCorrectly( + [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(Vector4), + typeof(Quaternion), typeof(Color), typeof(Color32), typeof(Ray), typeof(Ray2D), typeof(TestStruct))] + Type testType, + [Values] WriteType writeType) + { + BaseTypeTest(testType, writeType); + } + + [Test] + public void WhenWritingArrayOfUnmanagedElementType_ArrayIsWrittenCorrectly( + [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(Vector4), + typeof(Quaternion), typeof(Color), typeof(Color32), typeof(Ray), typeof(Ray2D), typeof(TestStruct))] + Type testType, + [Values] WriteType writeType) + { + BaseArrayTypeTest(testType, writeType); + } + + [TestCase(false, WriteType.WriteDirect)] + [TestCase(false, WriteType.WriteSafe)] + [TestCase(false, WriteType.WriteAsObject)] + [TestCase(true, WriteType.WriteDirect)] + [TestCase(true, WriteType.WriteSafe)] + public unsafe void WhenWritingString_ValueIsWrittenCorrectly(bool oneByteChars, WriteType writeType) + { + string valueToTest = "Hello, I am a test string!"; + + var serializedValueSize = FastBufferWriter.GetWriteSize(valueToTest, oneByteChars); + + var writer = new FastBufferWriter(serializedValueSize + 3, Allocator.Temp); + using (writer) + { + var offset = 0; + switch (writeType) + { + case WriteType.WriteDirect: + Assert.IsTrue(writer.TryBeginWrite(serializedValueSize + 2), "Writer denied write permission"); + writer.WriteValue(valueToTest, oneByteChars); + break; + case WriteType.WriteSafe: + writer.WriteValueSafe(valueToTest, oneByteChars); + break; + case WriteType.WriteAsObject: + writer.WriteObject(valueToTest); + // account for isNull byte + offset = sizeof(byte); + break; + + } + + VerifyPositionAndLength(ref writer, serializedValueSize + offset); + WriteCheckBytes(ref writer, serializedValueSize + offset); + + int* sizeValue = (int*)(writer.GetUnsafePtr() + offset); + Assert.AreEqual(valueToTest.Length, *sizeValue); + + fixed (char* asCharPointer = valueToTest) + { + if (oneByteChars) + { + byte* underlyingByteArray = writer.GetUnsafePtr() + sizeof(int) + offset; + for (var i = 0; i < valueToTest.Length; ++i) + { + Assert.AreEqual((byte)asCharPointer[i], underlyingByteArray[i]); + } + + } + else + { + char* underlyingCharArray = (char*)(writer.GetUnsafePtr() + sizeof(int) + offset); + for (var i = 0; i < valueToTest.Length; ++i) + { + Assert.AreEqual(asCharPointer[i], underlyingCharArray[i]); + } + } + } + + var underlyingArray = writer.ToArray(); + VerifyCheckBytes(underlyingArray, serializedValueSize + offset); + } + } + + [TestCase(1, 0)] + [TestCase(2, 0)] + [TestCase(3, 0)] + [TestCase(4, 0)] + [TestCase(5, 0)] + [TestCase(6, 0)] + [TestCase(7, 0)] + [TestCase(8, 0)] + + [TestCase(1, 1)] + [TestCase(2, 1)] + [TestCase(3, 1)] + [TestCase(4, 1)] + [TestCase(5, 1)] + [TestCase(6, 1)] + [TestCase(7, 1)] + + [TestCase(1, 2)] + [TestCase(2, 2)] + [TestCase(3, 2)] + [TestCase(4, 2)] + [TestCase(5, 2)] + [TestCase(6, 2)] + + [TestCase(1, 3)] + [TestCase(2, 3)] + [TestCase(3, 3)] + [TestCase(4, 3)] + [TestCase(5, 3)] + + [TestCase(1, 4)] + [TestCase(2, 4)] + [TestCase(3, 4)] + [TestCase(4, 4)] + + [TestCase(1, 5)] + [TestCase(2, 5)] + [TestCase(3, 5)] + + [TestCase(1, 6)] + [TestCase(2, 6)] + + [TestCase(1, 7)] + public unsafe void WhenWritingPartialValueWithCountAndOffset_ValueIsWrittenCorrectly(int count, int offset) + { + var random = new Random(); + var valueToTest = ((ulong)random.Next() << 32) + (ulong)random.Next(); + var writer = new FastBufferWriter(sizeof(ulong) + 2, Allocator.Temp); + using (writer) + { + + Assert.IsTrue(writer.TryBeginWrite(count + 2), "Writer denied write permission"); + writer.WritePartialValue(valueToTest, count, offset); + + var failMessage = $"TestWritingPartialValues failed with value {valueToTest}"; + VerifyPositionAndLength(ref writer, count, failMessage); + WriteCheckBytes(ref writer, count, failMessage); + var underlyingArray = writer.ToArray(); + VerifyBytewiseEquality(valueToTest, underlyingArray, offset, 0, count, failMessage); + VerifyCheckBytes(underlyingArray, count, failMessage); + + ulong mask = 0; + for (var i = 0; i < count; ++i) + { + mask = (mask << 8) | 0b11111111; + } + + ulong* checkValue = (ulong*)writer.GetUnsafePtr(); + Assert.AreEqual((valueToTest >> (offset * 8)) & mask, *checkValue & mask); + } + } + + [Test] + public void WhenCallingToArray_ReturnedArrayContainsCorrectData() + { + var testStruct = GetTestStruct(); + var requiredSize = FastBufferWriter.GetWriteSize(testStruct); + var writer = new FastBufferWriter(requiredSize, Allocator.Temp); + + using (writer) + { + writer.TryBeginWrite(requiredSize); + writer.WriteValue(testStruct); + var array = writer.ToArray(); + var underlyingArray = writer.ToArray(); + for (var i = 0; i < array.Length; ++i) + { + Assert.AreEqual(array[i], underlyingArray[i]); + } + } + } + + [Test] + public void WhenCallingWriteByteWithoutCallingTryBeingWriteFirst_OverflowExceptionIsThrown() + { + var writer = new FastBufferWriter(100, Allocator.Temp); + + using (writer) + { + Assert.Throws(() => { writer.WriteByte(1); }); + } + } + + [Test] + public void WhenCallingWriteBytesWithoutCallingTryBeingWriteFirst_OverflowExceptionIsThrown() + { + var writer = new FastBufferWriter(100, Allocator.Temp); + + using (writer) + { + var bytes = new byte[] { 0, 1, 2 }; + Assert.Throws(() => { writer.WriteBytes(bytes, bytes.Length); }); + } + } + + [Test] + public void WhenCallingWriteValueWithUnmanagedTypeWithoutCallingTryBeingWriteFirst_OverflowExceptionIsThrown() + { + var writer = new FastBufferWriter(100, Allocator.Temp); + + using (writer) + { + int i = 1; + Assert.Throws(() => { writer.WriteValue(i); }); + } + } + + [Test] + public void WhenCallingWriteValueWithByteArrayWithoutCallingTryBeingWriteFirst_OverflowExceptionIsThrown() + { + var writer = new FastBufferWriter(100, Allocator.Temp); + + using (writer) + { + var bytes = new byte[] { 0, 1, 2 }; + Assert.Throws(() => { writer.WriteValue(bytes); }); + } + } + + [Test] + public void WhenCallingWriteValueWithStringWithoutCallingTryBeingWriteFirst_OverflowExceptionIsThrown() + { + var writer = new FastBufferWriter(100, Allocator.Temp); + + using (writer) + { + Assert.Throws(() => { writer.WriteValue(""); }); + } + } + + [Test] + public void WhenCallingWriteValueAfterCallingTryBeginWriteWithTooFewBytes_OverflowExceptionIsThrown() + { + var writer = new FastBufferWriter(100, Allocator.Temp); + + using (writer) + { + int i = 0; + writer.TryBeginWrite(sizeof(int) - 1); + Assert.Throws(() => { writer.WriteValue(i); }); + } + } + + [Test] + public void WhenCallingWriteBytePastBoundaryMarkedByTryBeginWrite_OverflowExceptionIsThrown() + { + var writer = new FastBufferWriter(100, Allocator.Temp); + + using (writer) + { + writer.TryBeginWrite(sizeof(int) - 1); + writer.WriteByte(1); + writer.WriteByte(2); + writer.WriteByte(3); + Assert.Throws(() => { writer.WriteByte(4); }); + } + } + + [Test] + public void WhenCallingWriteByteDuringBitwiseContext_InvalidOperationExceptionIsThrown() + { + var writer = new FastBufferWriter(100, Allocator.Temp); + + using (writer) + { + writer.TryBeginWrite(100); + using var context = writer.EnterBitwiseContext(); + Assert.Throws(() => { writer.WriteByte(1); }); + } + } + + [Test] + public void WhenCallingWriteBytesDuringBitwiseContext_InvalidOperationExceptionIsThrown() + { + var writer = new FastBufferWriter(100, Allocator.Temp); + + using (writer) + { + writer.TryBeginWrite(100); + var bytes = new byte[] { 0, 1, 2 }; + using var context = writer.EnterBitwiseContext(); + Assert.Throws(() => { writer.WriteBytes(bytes, bytes.Length); }); + } + } + + [Test] + public void WhenCallingWriteValueWithUnmanagedTypeDuringBitwiseContext_InvalidOperationExceptionIsThrown() + { + var writer = new FastBufferWriter(100, Allocator.Temp); + + using (writer) + { + writer.TryBeginWrite(100); + int i = 1; + using var context = writer.EnterBitwiseContext(); + Assert.Throws(() => { writer.WriteValue(i); }); + } + } + + [Test] + public void WhenCallingWriteValueWithByteArrayDuringBitwiseContext_InvalidOperationExceptionIsThrown() + { + var writer = new FastBufferWriter(100, Allocator.Temp); + + using (writer) + { + writer.TryBeginWrite(100); + var bytes = new byte[] { 0, 1, 2 }; + using var context = writer.EnterBitwiseContext(); + Assert.Throws(() => { writer.WriteBytes(bytes, bytes.Length); }); + } + } + + [Test] + public void WhenCallingWriteValueWithStringDuringBitwiseContext_InvalidOperationExceptionIsThrown() + { + var writer = new FastBufferWriter(100, Allocator.Temp); + + using (writer) + { + writer.TryBeginWrite(100); + var bytes = new byte[] { 0, 1, 2 }; + int i = 1; + using var context = writer.EnterBitwiseContext(); + Assert.Throws(() => { writer.WriteValue(""); }); + } + } + + [Test] + public void WhenCallingWriteByteSafeDuringBitwiseContext_InvalidOperationExceptionIsThrown() + { + var writer = new FastBufferWriter(100, Allocator.Temp); + + using (writer) + { + writer.TryBeginWrite(100); + using var context = writer.EnterBitwiseContext(); + Assert.Throws(() => { writer.WriteByteSafe(1); }); + } + } + + [Test] + public void WhenCallingWriteBytesSafeDuringBitwiseContext_InvalidOperationExceptionIsThrown() + { + var writer = new FastBufferWriter(100, Allocator.Temp); + + using (writer) + { + writer.TryBeginWrite(100); + var bytes = new byte[] { 0, 1, 2 }; + using var context = writer.EnterBitwiseContext(); + Assert.Throws(() => { writer.WriteBytesSafe(bytes, bytes.Length); }); + } + } + + [Test] + public void WhenCallingWriteValueSafeWithUnmanagedTypeDuringBitwiseContext_InvalidOperationExceptionIsThrown() + { + var writer = new FastBufferWriter(100, Allocator.Temp); + + using (writer) + { + writer.TryBeginWrite(100); + int i = 1; + using var context = writer.EnterBitwiseContext(); + Assert.Throws(() => { writer.WriteValueSafe(i); }); + } + } + + [Test] + public void WhenCallingWriteValueSafeWithByteArrayDuringBitwiseContext_InvalidOperationExceptionIsThrown() + { + var writer = new FastBufferWriter(100, Allocator.Temp); + + using (writer) + { + writer.TryBeginWrite(100); + var bytes = new byte[] { 0, 1, 2 }; + using var context = writer.EnterBitwiseContext(); + Assert.Throws(() => { writer.WriteBytesSafe(bytes, bytes.Length); }); + } + } + + [Test] + public void WhenCallingWriteValueSafeWithStringDuringBitwiseContext_InvalidOperationExceptionIsThrown() + { + var writer = new FastBufferWriter(100, Allocator.Temp); + + using (writer) + { + writer.TryBeginWrite(100); + using var context = writer.EnterBitwiseContext(); + Assert.Throws(() => { writer.WriteValueSafe(""); }); + } + } + + [Test] + public void WhenCallingWriteByteAfterExitingBitwiseContext_InvalidOperationExceptionIsNotThrown() + { + var writer = new FastBufferWriter(100, Allocator.Temp); + + using (writer) + { + writer.TryBeginWrite(100); + using (var context = writer.EnterBitwiseContext()) + { + context.WriteBit(true); + } + writer.WriteByte(1); + } + } + + [Test] + public void WhenCallingWriteBytesAfterExitingBitwiseContext_InvalidOperationExceptionIsNotThrown() + { + var writer = new FastBufferWriter(100, Allocator.Temp); + + using (writer) + { + writer.TryBeginWrite(100); + var bytes = new byte[] { 0, 1, 2 }; + using (var context = writer.EnterBitwiseContext()) + { + context.WriteBit(true); + } + writer.WriteBytes(bytes, bytes.Length); + } + } + + [Test] + public void WhenCallingWriteValueWithUnmanagedTypeAfterExitingBitwiseContext_InvalidOperationExceptionIsNotThrown() + { + var writer = new FastBufferWriter(100, Allocator.Temp); + + using (writer) + { + writer.TryBeginWrite(100); + var bytes = new byte[] { 0, 1, 2 }; + int i = 1; + using (var context = writer.EnterBitwiseContext()) + { + context.WriteBit(true); + } + writer.WriteValue(i); + } + } + + [Test] + public void WhenCallingWriteValueWithByteArrayAfterExitingBitwiseContext_InvalidOperationExceptionIsNotThrown() + { + var writer = new FastBufferWriter(100, Allocator.Temp); + + using (writer) + { + writer.TryBeginWrite(100); + var bytes = new byte[] { 0, 1, 2 }; + int i = 1; + using (var context = writer.EnterBitwiseContext()) + { + context.WriteBit(true); + } + writer.WriteValue(bytes); + } + } + + [Test] + public void WhenCallingWriteValueWithStringAfterExitingBitwiseContext_InvalidOperationExceptionIsNotThrown() + { + var writer = new FastBufferWriter(100, Allocator.Temp); + + using (writer) + { + writer.TryBeginWrite(100); + using (var context = writer.EnterBitwiseContext()) + { + context.WriteBit(true); + } + writer.WriteValue(""); + } + } + + [Test] + public void WhenCallingWriteByteSafeAfterExitingBitwiseContext_InvalidOperationExceptionIsNotThrown() + { + var writer = new FastBufferWriter(100, Allocator.Temp); + + using (writer) + { + writer.TryBeginWrite(100); + using (var context = writer.EnterBitwiseContext()) + { + context.WriteBit(true); + } + writer.WriteByteSafe(1); + } + } + + [Test] + public void WhenCallingWriteBytesSafeAfterExitingBitwiseContext_InvalidOperationExceptionIsNotThrown() + { + var writer = new FastBufferWriter(100, Allocator.Temp); + + using (writer) + { + writer.TryBeginWrite(100); + var bytes = new byte[] { 0, 1, 2 }; + using (var context = writer.EnterBitwiseContext()) + { + context.WriteBit(true); + } + writer.WriteBytesSafe(bytes, bytes.Length); + } + } + + [Test] + public void WhenCallingWriteValueSafeWithUnmanagedTypeAfterExitingBitwiseContext_InvalidOperationExceptionIsNotThrown() + { + var writer = new FastBufferWriter(100, Allocator.Temp); + + using (writer) + { + writer.TryBeginWrite(100); + var bytes = new byte[] { 0, 1, 2 }; + int i = 1; + using (var context = writer.EnterBitwiseContext()) + { + context.WriteBit(true); + } + writer.WriteValueSafe(i); + } + } + + [Test] + public void WhenCallingWriteValueSafeWithByteArrayAfterExitingBitwiseContext_InvalidOperationExceptionIsNotThrown() + { + var writer = new FastBufferWriter(100, Allocator.Temp); + + using (writer) + { + writer.TryBeginWrite(100); + var bytes = new byte[] { 0, 1, 2 }; + int i = 1; + using (var context = writer.EnterBitwiseContext()) + { + context.WriteBit(true); + } + writer.WriteValueSafe(bytes); + } + } + + [Test] + public void WhenCallingWriteValueSafeWithStringAfterExitingBitwiseContext_InvalidOperationExceptionIsNotThrown() + { + var writer = new FastBufferWriter(100, Allocator.Temp); + + using (writer) + { + writer.TryBeginWrite(100); + using (var context = writer.EnterBitwiseContext()) + { + context.WriteBit(true); + } + writer.WriteValueSafe(""); + } + } + + [Test] + public void WhenCallingTryBeginWrite_TheAllowedWritePositionIsMarkedRelativeToCurrentPosition() + { + var writer = new FastBufferWriter(100, Allocator.Temp); + using (writer) + { + writer.TryBeginWrite(100); + writer.WriteByte(1); + writer.TryBeginWrite(1); + writer.WriteByte(1); + Assert.Throws(() => { writer.WriteByte(1); }); + } + } + + [Test] + public void WhenWritingAfterSeeking_TheNewWriteGoesToTheCorrectPosition() + { + var writer = new FastBufferWriter(100, Allocator.Temp); + using (writer) + { + writer.Seek(5); + writer.WriteByteSafe(0); + Assert.AreEqual(writer.Position, 6); + + writer.Seek(0); + writer.WriteByteSafe(1); + Assert.AreEqual(writer.Position, 1); + + writer.Seek(10); + Assert.AreEqual(writer.Position, 10); + + writer.Seek(2); + writer.WriteByteSafe(2); + + writer.Seek(1); + writer.WriteByteSafe(3); + + writer.Seek(4); + writer.WriteByteSafe(4); + + writer.Seek(3); + writer.WriteByteSafe(5); + + Assert.AreEqual(writer.Position, 4); + + var expected = new byte[] { 1, 3, 2, 5, 4, 0 }; + var underlyingArray = writer.ToArray(); + for (var i = 0; i < expected.Length; ++i) + { + Assert.AreEqual(expected[i], underlyingArray[i]); + } + } + } + + [Test] + public void WhenSeekingForward_LengthUpdatesToNewPosition() + { + var writer = new FastBufferWriter(100, Allocator.Temp); + using (writer) + { + Assert.AreEqual(writer.Length, 0); + writer.Seek(5); + Assert.AreEqual(writer.Length, 5); + writer.Seek(10); + Assert.AreEqual(writer.Length, 10); + } + } + + [Test] + public void WhenSeekingBackward_LengthDoesNotChange() + { + var writer = new FastBufferWriter(100, Allocator.Temp); + using (writer) + { + Assert.AreEqual(writer.Length, 0); + writer.Seek(5); + Assert.AreEqual(writer.Length, 5); + writer.Seek(0); + Assert.AreEqual(writer.Length, 5); + } + } + + [Test] + public void WhenTruncatingToSpecificPositionAheadOfWritePosition_LengthIsUpdatedAndPositionIsNot() + { + var writer = new FastBufferWriter(100, Allocator.Temp); + using (writer) + { + writer.Seek(10); + Assert.AreEqual(writer.Position, 10); + Assert.AreEqual(writer.Length, 10); + + writer.Seek(5); + Assert.AreEqual(writer.Position, 5); + Assert.AreEqual(writer.Length, 10); + + writer.Truncate(8); + Assert.AreEqual(writer.Position, 5); + Assert.AreEqual(writer.Length, 8); + } + } + + [Test] + public void WhenTruncatingToSpecificPositionBehindWritePosition_BothLengthAndPositionAreUpdated() + { + var writer = new FastBufferWriter(100, Allocator.Temp); + using (writer) + { + writer.Seek(10); + Assert.AreEqual(writer.Position, 10); + Assert.AreEqual(writer.Length, 10); + + writer.Truncate(8); + Assert.AreEqual(writer.Position, 8); + Assert.AreEqual(writer.Length, 8); + } + } + + [Test] + public void WhenTruncatingToCurrentPosition_LengthIsUpdated() + { + var writer = new FastBufferWriter(100, Allocator.Temp); + using (writer) + { + writer.Seek(10); + Assert.AreEqual(writer.Position, 10); + Assert.AreEqual(writer.Length, 10); + + writer.Seek(5); + writer.Truncate(); + Assert.AreEqual(writer.Position, 5); + Assert.AreEqual(writer.Length, 5); + } + } + + [Test] + public void WhenCreatingNewFastBufferWriter_CapacityIsCorrect() + { + var writer = new FastBufferWriter(100, Allocator.Temp); + Assert.AreEqual(100, writer.Capacity); + writer.Dispose(); + + writer = new FastBufferWriter(200, Allocator.Temp); + Assert.AreEqual(200, writer.Capacity); + writer.Dispose(); + } + + [Test] + public void WhenCreatingNewFastBufferWriter_MaxCapacityIsCorrect() + { + var writer = new FastBufferWriter(100, Allocator.Temp); + Assert.AreEqual(100, writer.MaxCapacity); + writer.Dispose(); + + writer = new FastBufferWriter(100, Allocator.Temp, 200); + Assert.AreEqual(200, writer.MaxCapacity); + writer.Dispose(); + } + + [Test] + public void WhenRequestingWritePastBoundsForNonGrowingWriter_TryBeginWriteReturnsFalse() + { + var writer = new FastBufferWriter(150, Allocator.Temp); + using (writer) + { + var testStruct = GetTestStruct(); + writer.TryBeginWriteValue(testStruct); + writer.WriteValue(testStruct); + + // Seek to exactly where the write would cross the buffer boundary + writer.Seek(150 - FastBufferWriter.GetWriteSize(testStruct) + 1); + + // Writer isn't allowed to grow because it didn't specify a maxSize + Assert.IsFalse(writer.TryBeginWriteValue(testStruct)); + } + } + + [Test] + public void WhenTryBeginWriteReturnsFalse_WritingThrowsOverflowException() + { + var writer = new FastBufferWriter(150, Allocator.Temp); + using (writer) + { + var testStruct = GetTestStruct(); + writer.TryBeginWriteValue(testStruct); + writer.WriteValue(testStruct); + + // Seek to exactly where the write would cross the buffer boundary + writer.Seek(150 - FastBufferWriter.GetWriteSize(testStruct) + 1); + + // Writer isn't allowed to grow because it didn't specify a maxSize + Assert.IsFalse(writer.TryBeginWriteValue(testStruct)); + Assert.Throws(() => writer.WriteValue(testStruct)); + } + } + + [Test] + public void WhenTryBeginWriteReturnsFalseAndOverflowExceptionIsThrown_DataIsNotAffected() + { + var writer = new FastBufferWriter(150, Allocator.Temp); + using (writer) + { + var testStruct = GetTestStruct(); + writer.TryBeginWriteValue(testStruct); + writer.WriteValue(testStruct); + + // Seek to exactly where the write would cross the buffer boundary + writer.Seek(150 - FastBufferWriter.GetWriteSize(testStruct) + 1); + + // Writer isn't allowed to grow because it didn't specify a maxSize + Assert.IsFalse(writer.TryBeginWriteValue(testStruct)); + Assert.Throws(() => writer.WriteValue(testStruct)); + VerifyBytewiseEquality(testStruct, writer.ToArray(), 0, 0, FastBufferWriter.GetWriteSize(testStruct)); + } + } + + [Test] + public void WhenRequestingWritePastBoundsForGrowingWriter_BufferGrowsWithoutLosingData() + { + var growingWriter = new FastBufferWriter(150, Allocator.Temp, 500); + using (growingWriter) + { + var testStruct = GetTestStruct(); + growingWriter.TryBeginWriteValue(testStruct); + growingWriter.WriteValue(testStruct); + + // Seek to exactly where the write would cross the buffer boundary + growingWriter.Seek(150 - FastBufferWriter.GetWriteSize(testStruct) + 1); + + Assert.IsTrue(growingWriter.TryBeginWriteValue(testStruct)); + + // Growth doubles the size + Assert.AreEqual(300, growingWriter.Capacity); + Assert.AreEqual(growingWriter.Position, 150 - FastBufferWriter.GetWriteSize(testStruct) + 1); + growingWriter.WriteValue(testStruct); + + // Verify the growth properly copied the existing data + VerifyBytewiseEquality(testStruct, growingWriter.ToArray(), 0, 0, FastBufferWriter.GetWriteSize(testStruct)); + VerifyBytewiseEquality(testStruct, growingWriter.ToArray(), 0, 150 - FastBufferWriter.GetWriteSize(testStruct) + 1, FastBufferWriter.GetWriteSize(testStruct)); + } + } + + [Test] + public void WhenRequestingWriteExactlyAtBoundsForGrowingWriter_BufferDoesntGrow() + { + var growingWriter = new FastBufferWriter(300, Allocator.Temp, 500); + using (growingWriter) + { + var testStruct = GetTestStruct(); + growingWriter.TryBeginWriteValue(testStruct); + growingWriter.WriteValue(testStruct); + + growingWriter.Seek(300 - FastBufferWriter.GetWriteSize(testStruct)); + Assert.IsTrue(growingWriter.TryBeginWriteValue(testStruct)); + Assert.AreEqual(300, growingWriter.Capacity); + Assert.AreEqual(growingWriter.Position, growingWriter.ToArray().Length); + growingWriter.WriteValue(testStruct); + Assert.AreEqual(300, growingWriter.Position); + + VerifyBytewiseEquality(testStruct, growingWriter.ToArray(), 0, 0, FastBufferWriter.GetWriteSize(testStruct)); + VerifyBytewiseEquality(testStruct, growingWriter.ToArray(), 0, 300 - FastBufferWriter.GetWriteSize(testStruct), FastBufferWriter.GetWriteSize(testStruct)); + } + } + + [Test] + public void WhenBufferGrows_MaxCapacityIsNotExceeded() + { + var growingWriter = new FastBufferWriter(300, Allocator.Temp, 500); + using (growingWriter) + { + var testStruct = GetTestStruct(); + growingWriter.TryBeginWriteValue(testStruct); + growingWriter.WriteValue(testStruct); + + growingWriter.Seek(300); + Assert.IsTrue(growingWriter.TryBeginWriteValue(testStruct)); + + Assert.AreEqual(500, growingWriter.Capacity); + Assert.AreEqual(growingWriter.Position, 300); + + growingWriter.WriteValue(testStruct); + + VerifyBytewiseEquality(testStruct, growingWriter.ToArray(), 0, 0, FastBufferWriter.GetWriteSize(testStruct)); + VerifyBytewiseEquality(testStruct, growingWriter.ToArray(), 0, 300, FastBufferWriter.GetWriteSize(testStruct)); + } + } + + [Test] + public void WhenBufferGrowthRequiredIsMoreThanDouble_BufferGrowsEnoughToContainRequestedValue() + { + var growingWriter = new FastBufferWriter(1, Allocator.Temp, 500); + using (growingWriter) + { + var testStruct = GetTestStruct(); + Assert.IsTrue(growingWriter.TryBeginWriteValue(testStruct)); + + // Buffer size doubles with each growth, so since we're starting with a size of 1, that means + // the resulting size should be the next power of 2 above the size of testStruct. + Assert.AreEqual(Math.Pow(2, Math.Ceiling(Mathf.Log(FastBufferWriter.GetWriteSize(testStruct), 2))), + growingWriter.Capacity); + Assert.AreEqual(growingWriter.Position, 0); + + growingWriter.WriteValue(testStruct); + + VerifyBytewiseEquality(testStruct, growingWriter.ToArray(), 0, 0, FastBufferWriter.GetWriteSize(testStruct)); + } + } + + [Test] + public void WhenTryingToWritePastMaxCapacity_GrowthDoesNotOccurAndTryBeginWriteReturnsFalse() + { + var growingWriter = new FastBufferWriter(300, Allocator.Temp, 500); + using (growingWriter) + { + Assert.IsFalse(growingWriter.TryBeginWrite(501)); + + Assert.AreEqual(300, growingWriter.Capacity); + Assert.AreEqual(growingWriter.Position, 0); + } + } + + [Test] + public void WhenWritingNetworkBehaviour_ObjectIdAndBehaviourIdAreWritten([Values] WriteType writeType) + { + RunGameObjectTest((obj, networkBehaviour, networkObject) => + { + var writer = new FastBufferWriter(FastBufferWriterExtensions.GetWriteSize(networkBehaviour) + 1, Allocator.Temp); + using (writer) + { + Assert.IsTrue(writer.TryBeginWrite(FastBufferWriterExtensions.GetWriteSize(networkBehaviour))); + + var offset = 0; + switch (writeType) + { + case WriteType.WriteDirect: + writer.WriteValue(networkBehaviour); + break; + case WriteType.WriteSafe: + writer.WriteValueSafe(networkBehaviour); + break; + case WriteType.WriteAsObject: + writer.WriteObject(networkBehaviour); + // account for isNull byte + offset = 1; + break; + } + + Assert.AreEqual(FastBufferWriterExtensions.GetWriteSize(networkBehaviour) + offset, writer.Position); + VerifyBytewiseEquality(networkObject.NetworkObjectId, writer.ToArray(), 0, offset, sizeof(ulong)); + VerifyBytewiseEquality(networkBehaviour.NetworkBehaviourId, writer.ToArray(), 0, + sizeof(ulong) + offset, sizeof(ushort)); + } + }); + } + + [Test] + public void WhenWritingNetworkObject_NetworkObjectIdIsWritten([Values] WriteType writeType) + { + RunGameObjectTest((obj, networkBehaviour, networkObject) => + { + var writer = new FastBufferWriter(FastBufferWriterExtensions.GetWriteSize(networkObject) + 1, Allocator.Temp); + using (writer) + { + Assert.IsTrue(writer.TryBeginWrite(FastBufferWriterExtensions.GetWriteSize(networkObject))); + + var offset = 0; + switch (writeType) + { + case WriteType.WriteDirect: + writer.WriteValue(networkObject); + break; + case WriteType.WriteSafe: + writer.WriteValueSafe(networkObject); + break; + case WriteType.WriteAsObject: + writer.WriteObject(networkObject); + // account for isNull byte + offset = 1; + break; + } + + Assert.AreEqual(FastBufferWriterExtensions.GetWriteSize(networkObject) + offset, writer.Position); + VerifyBytewiseEquality(networkObject.NetworkObjectId, writer.ToArray(), 0, offset, sizeof(ulong)); + } + }); + } + + [Test] + public void WhenWritingGameObject_NetworkObjectIdIsWritten([Values] WriteType writeType) + { + RunGameObjectTest((obj, networkBehaviour, networkObject) => + { + var writer = new FastBufferWriter(FastBufferWriterExtensions.GetWriteSize(obj) + 1, Allocator.Temp); + using (writer) + { + Assert.IsTrue(writer.TryBeginWrite(FastBufferWriterExtensions.GetWriteSize(obj))); + + var offset = 0; + switch (writeType) + { + case WriteType.WriteDirect: + writer.WriteValue(obj); + break; + case WriteType.WriteSafe: + writer.WriteValueSafe(obj); + break; + case WriteType.WriteAsObject: + writer.WriteObject(obj); + // account for isNull byte + offset = 1; + break; + } + + Assert.AreEqual(FastBufferWriterExtensions.GetWriteSize(obj) + offset, writer.Position); + VerifyBytewiseEquality(networkObject.NetworkObjectId, writer.ToArray(), 0, offset, sizeof(ulong)); + } + }); + } + + [Test] + public void WhenCallingTryBeginWriteInternal_AllowedWritePositionDoesNotMoveBackward() + { + var writer = new FastBufferWriter(100, Allocator.Temp); + using (writer) + { + writer.TryBeginWrite(25); + writer.TryBeginWriteInternal(5); + Assert.AreEqual(writer.AllowedWriteMark, 25); + } + } + #endregion + } +} diff --git a/com.unity.netcode.gameobjects/Tests/Editor/Serialization/FastBufferWriterTests.cs.meta b/com.unity.netcode.gameobjects/Tests/Editor/Serialization/FastBufferWriterTests.cs.meta new file mode 100644 index 0000000000..b549311462 --- /dev/null +++ b/com.unity.netcode.gameobjects/Tests/Editor/Serialization/FastBufferWriterTests.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 1cef42b60935e29469ed1404fb30ba2d +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/com.unity.netcode.gameobjects/Tests/Editor/com.unity.netcode.editortests.asmdef b/com.unity.netcode.gameobjects/Tests/Editor/com.unity.netcode.editortests.asmdef index 1c8e0294d9..075e769b1a 100644 --- a/com.unity.netcode.gameobjects/Tests/Editor/com.unity.netcode.editortests.asmdef +++ b/com.unity.netcode.gameobjects/Tests/Editor/com.unity.netcode.editortests.asmdef @@ -13,7 +13,19 @@ "includePlatforms": [ "Editor" ], - "versionDefines": [ + "excludePlatforms": [], + "allowUnsafeCode": true, + "overrideReferences": true, + "precompiledReferences": [ + "nunit.framework.dll" + ], + "autoReferenced": false, + "defineConstraints": [ + "UNITY_INCLUDE_TESTS" + ], + "versionDefines": [], + "noEngineReferences": false, + "versionDefines": [ { "name": "com.unity.multiplayer.tools", "expression": "", diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/Components/BufferDataValidationComponent.cs b/com.unity.netcode.gameobjects/Tests/Runtime/Components/BufferDataValidationComponent.cs index 90f7c515b2..759cce10b0 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/Components/BufferDataValidationComponent.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/Components/BufferDataValidationComponent.cs @@ -18,7 +18,7 @@ public class BufferDataValidationComponent : NetworkBehaviour /// /// The maximum size of the buffer to send /// - public int MaximumBufferSize = 1 << 20; + public int MaximumBufferSize = 1 << 15; /// /// The rate at which the buffer size increases until it reaches MaximumBufferSize diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/Components/NetworkUpdateStagesComponent.cs b/com.unity.netcode.gameobjects/Tests/Runtime/Components/NetworkUpdateStagesComponent.cs deleted file mode 100644 index d592845c7a..0000000000 --- a/com.unity.netcode.gameobjects/Tests/Runtime/Components/NetworkUpdateStagesComponent.cs +++ /dev/null @@ -1,191 +0,0 @@ -using System; -using System.Collections.Generic; -using UnityEngine; - -namespace Unity.Netcode.RuntimeTests -{ - /// - /// Used in conjunction with the RpcQueueTest to validate that Rpcs are being invoked at the proper NetworkUpdateStage - /// - public class NetworkUpdateStagesComponent : NetworkBehaviour - { - /// - /// Allows the external RPCQueueTest to begin testing or stop it - /// - public bool EnableTesting; - - /// - /// How many times will we iterate through the various NetworkUpdateStage values? - /// (defaults to 1) - /// - public int MaxIterations = 1; - - private bool m_ClientReceivedRpc; - private int m_Counter; - private int m_MaxStagesSent; - private int m_MaxStages; - private ServerRpcParams m_ServerParams; - private ClientRpcParams m_ClientParams; - private NetworkUpdateStage m_LastUpdateStage; - - // Start is called before the first frame update - private void Start() - { - m_ServerParams.Send.UpdateStage = NetworkUpdateStage.EarlyUpdate; - m_ClientParams.Send.UpdateStage = NetworkUpdateStage.Update; - - //This assures the ILPP Codegen side of things is working by making sure that the Receive.UpdateStage - //is not always NetworkUpdateStage.EarlyUpdate as it should be whatever NetworkUpdateStage the RPC - //was assigned within the Send.UpdateStage - m_ServerParams.Receive.UpdateStage = NetworkUpdateStage.EarlyUpdate; - m_ClientParams.Receive.UpdateStage = NetworkUpdateStage.EarlyUpdate; - - m_MaxStages = (Enum.GetValues(typeof(NetworkUpdateStage)).Length); - m_MaxStagesSent = m_MaxStages * MaxIterations; - - //Start out with this being true (for first sequence) - m_ClientReceivedRpc = true; - } - - /// - /// Determine if we have iterated over more than our maximum stages we want to test - /// - /// true or false (did we exceed the max iterations or not?) - public bool ExceededMaxIterations() - { - if (m_StagesSent.Count > m_MaxStagesSent && m_MaxStagesSent > 0) - { - return true; - } - - return false; - } - - /// - /// Returns back whether the test has completed the total number of iterations - /// - /// - public bool IsTestComplete() - { - if (m_Counter >= MaxIterations) - { - return true; - } - - return false; - } - - // Update is called once per frame - private void Update() - { - if (NetworkManager.Singleton.IsListening && EnableTesting && m_ClientReceivedRpc) - { - //Reset this for the next sequence of rpcs - m_ClientReceivedRpc = false; - - //As long as testing isn't completed, keep testing - if (!IsTestComplete() && m_StagesSent.Count < m_MaxStagesSent) - { - m_LastUpdateStage = m_ServerParams.Send.UpdateStage; - m_StagesSent.Add(m_LastUpdateStage); - - PingMySelfServerRpc(m_StagesSent.Count, m_ServerParams); - - switch (m_ServerParams.Send.UpdateStage) - { - case NetworkUpdateStage.EarlyUpdate: - m_ServerParams.Send.UpdateStage = NetworkUpdateStage.FixedUpdate; - break; - case NetworkUpdateStage.FixedUpdate: - m_ServerParams.Send.UpdateStage = NetworkUpdateStage.PreUpdate; - break; - case NetworkUpdateStage.PreUpdate: - m_ServerParams.Send.UpdateStage = NetworkUpdateStage.Update; - break; - case NetworkUpdateStage.Update: - m_ServerParams.Send.UpdateStage = NetworkUpdateStage.PreLateUpdate; - break; - case NetworkUpdateStage.PreLateUpdate: - m_ServerParams.Send.UpdateStage = NetworkUpdateStage.PostLateUpdate; - break; - case NetworkUpdateStage.PostLateUpdate: - m_ServerParams.Send.UpdateStage = NetworkUpdateStage.EarlyUpdate; - break; - } - } - } - } - - private readonly List m_ServerStagesReceived = new List(); - private readonly List m_ClientStagesReceived = new List(); - private readonly List m_StagesSent = new List(); - - /// - /// Assures all update stages were in alginment with one another - /// - /// true or false - public bool ValidateUpdateStages() - { - var validated = false; - if (m_ServerStagesReceived.Count == m_ClientStagesReceived.Count && m_ClientStagesReceived.Count == m_StagesSent.Count) - { - for (int i = 0; i < m_StagesSent.Count; i++) - { - var currentStage = m_StagesSent[i]; - if (m_ServerStagesReceived[i] != currentStage) - { - Debug.Log($"ServerRpc Update Stage ({m_ServerStagesReceived[i]}) is not equal to the current update stage ({currentStage})"); - - return validated; - } - - if (m_ClientStagesReceived[i] != currentStage) - { - Debug.Log($"ClientRpc Update Stage ({m_ClientStagesReceived[i]}) is not equal to the current update stage ({currentStage})"); - - return validated; - } - } - - validated = true; - } - - return validated; - } - - /// - /// Server side RPC for testing - /// - /// server rpc parameters - [ServerRpc] - private void PingMySelfServerRpc(int currentCount, ServerRpcParams parameters = default) - { - Debug.Log($"{nameof(PingMySelfServerRpc)}: [HostClient][ServerRpc][{currentCount}] invoked during the {parameters.Receive.UpdateStage} stage."); - - m_ClientParams.Send.UpdateStage = parameters.Receive.UpdateStage; - m_ServerStagesReceived.Add(parameters.Receive.UpdateStage); - - PingMySelfClientRpc(currentCount, m_ClientParams); - } - - /// - /// Client Side RPC called by PingMySelfServerRPC to validate both Client->Server and Server-Client pipeline is working - /// - /// client rpc parameters - [ClientRpc] - private void PingMySelfClientRpc(int currentCount, ClientRpcParams parameters = default) - { - Debug.Log($"{nameof(PingMySelfClientRpc)}: [HostServer][ClientRpc][{currentCount}] invoked during the {parameters.Receive.UpdateStage} stage. (previous output line should confirm this)"); - - m_ClientStagesReceived.Add(parameters.Receive.UpdateStage); - - //If we reached the last update state, then go ahead and increment our iteration counter - if (parameters.Receive.UpdateStage == NetworkUpdateStage.PostLateUpdate) - { - m_Counter++; - } - - m_ClientReceivedRpc = true; - } - } -} diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/Components/NetworkUpdateStagesComponent.cs.meta b/com.unity.netcode.gameobjects/Tests/Runtime/Components/NetworkUpdateStagesComponent.cs.meta deleted file mode 100644 index a765b6ca4b..0000000000 --- a/com.unity.netcode.gameobjects/Tests/Runtime/Components/NetworkUpdateStagesComponent.cs.meta +++ /dev/null @@ -1,11 +0,0 @@ -fileFormatVersion: 2 -guid: 2526efd7ad17baa4d953811c4c6520c7 -MonoImporter: - externalObjects: {} - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: - assetBundleName: - assetBundleVariant: diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/Messaging/NamedMessageTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/Messaging/NamedMessageTests.cs index c3e397b278..f22ad31e64 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/Messaging/NamedMessageTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/Messaging/NamedMessageTests.cs @@ -1,9 +1,8 @@ using System; using System.Collections; using System.Collections.Generic; -using System.IO; -using System.Text; using NUnit.Framework; +using Unity.Collections; using UnityEngine; using UnityEngine.TestTools; @@ -20,30 +19,30 @@ public class NamedMessageTests : BaseMultiInstanceTest public IEnumerator NamedMessageIsReceivedOnClientWithContent() { var messageName = Guid.NewGuid().ToString(); - var messageContent = Guid.NewGuid().ToString(); - using var messageStream = new MemoryStream(Encoding.UTF8.GetBytes(messageContent)); - - m_ServerNetworkManager.CustomMessagingManager.SendNamedMessage( - messageName, - FirstClient.LocalClientId, - messageStream); + var messageContent = Guid.NewGuid(); + var writer = new FastBufferWriter(1300, Allocator.Temp); + using (writer) + { + writer.WriteValueSafe(messageContent); + m_ServerNetworkManager.CustomMessagingManager.SendNamedMessage( + messageName, + FirstClient.LocalClientId, + ref writer); + } ulong receivedMessageSender = 0; - string receivedMessageContent = null; + Guid receivedMessageContent; FirstClient.CustomMessagingManager.RegisterNamedMessageHandler( messageName, - (sender, stream) => + (ulong sender, ref FastBufferReader reader) => { receivedMessageSender = sender; - using var memoryStream = new MemoryStream(); - stream.CopyTo(memoryStream); - receivedMessageContent = Encoding.UTF8.GetString(memoryStream.ToArray()); + reader.ReadValueSafe(out receivedMessageContent); }); yield return new WaitForSeconds(0.2f); - Assert.NotNull(receivedMessageContent); Assert.AreEqual(messageContent, receivedMessageContent); Assert.AreEqual(m_ServerNetworkManager.LocalClientId, receivedMessageSender); } @@ -52,47 +51,44 @@ public IEnumerator NamedMessageIsReceivedOnClientWithContent() public IEnumerator NamedMessageIsReceivedOnMultipleClientsWithContent() { var messageName = Guid.NewGuid().ToString(); - var messageContent = Guid.NewGuid().ToString(); - using var messageStream = new MemoryStream(Encoding.UTF8.GetBytes(messageContent)); - - m_ServerNetworkManager.CustomMessagingManager.SendNamedMessage( - messageName, - new List { FirstClient.LocalClientId, SecondClient.LocalClientId }, - messageStream); + var messageContent = Guid.NewGuid(); + var writer = new FastBufferWriter(1300, Allocator.Temp); + using (writer) + { + writer.WriteValueSafe(messageContent); + m_ServerNetworkManager.CustomMessagingManager.SendNamedMessage( + messageName, + new List { FirstClient.LocalClientId, SecondClient.LocalClientId }, + ref writer); + } ulong firstReceivedMessageSender = 0; - string firstReceivedMessageContent = null; + Guid firstReceivedMessageContent; FirstClient.CustomMessagingManager.RegisterNamedMessageHandler( messageName, - (sender, stream) => + (ulong sender, ref FastBufferReader reader) => { firstReceivedMessageSender = sender; - using var memoryStream = new MemoryStream(); - stream.CopyTo(memoryStream); - firstReceivedMessageContent = Encoding.UTF8.GetString(memoryStream.ToArray()); + reader.ReadValueSafe(out firstReceivedMessageContent); }); ulong secondReceivedMessageSender = 0; - string secondReceivedMessageContent = null; + Guid secondReceivedMessageContent; SecondClient.CustomMessagingManager.RegisterNamedMessageHandler( messageName, - (sender, stream) => + (ulong sender, ref FastBufferReader reader) => { secondReceivedMessageSender = sender; - using var memoryStream = new MemoryStream(); - stream.CopyTo(memoryStream); - secondReceivedMessageContent = Encoding.UTF8.GetString(memoryStream.ToArray()); + reader.ReadValueSafe(out secondReceivedMessageContent); }); yield return new WaitForSeconds(0.2f); - Assert.NotNull(firstReceivedMessageContent); Assert.AreEqual(messageContent, firstReceivedMessageContent); Assert.AreEqual(m_ServerNetworkManager.LocalClientId, firstReceivedMessageSender); - Assert.NotNull(secondReceivedMessageContent); Assert.AreEqual(messageContent, secondReceivedMessageContent); Assert.AreEqual(m_ServerNetworkManager.LocalClientId, secondReceivedMessageSender); } diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/Messaging/UnnamedMessageTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/Messaging/UnnamedMessageTests.cs index f7d27b62fa..ca0ad1a9dc 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/Messaging/UnnamedMessageTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/Messaging/UnnamedMessageTests.cs @@ -1,9 +1,8 @@ using System; using System.Collections; using System.Collections.Generic; -using System.IO; -using System.Text; using NUnit.Framework; +using Unity.Collections; using UnityEngine; using UnityEngine.TestTools; @@ -19,25 +18,28 @@ public class UnnamedMessageTests : BaseMultiInstanceTest [UnityTest] public IEnumerator UnnamedMessageIsReceivedOnClientWithContent() { - var messageContent = Guid.NewGuid().ToString(); - var buffer = new NetworkBuffer(Encoding.UTF8.GetBytes(messageContent)); - - m_ServerNetworkManager.CustomMessagingManager.SendUnnamedMessage(FirstClient.LocalClientId, buffer); + var messageContent = Guid.NewGuid(); + var writer = new FastBufferWriter(1300, Allocator.Temp); + using (writer) + { + writer.WriteValueSafe(messageContent); + m_ServerNetworkManager.CustomMessagingManager.SendUnnamedMessage( + FirstClient.LocalClientId, + ref writer); + } ulong receivedMessageSender = 0; - string receivedMessageContent = null; - FirstClient.CustomMessagingManager.OnUnnamedMessage += (sender, stream) => - { - receivedMessageSender = sender; + Guid receivedMessageContent; + FirstClient.CustomMessagingManager.OnUnnamedMessage += + (ulong sender, ref FastBufferReader reader) => + { + receivedMessageSender = sender; - using var memoryStream = new MemoryStream(); - stream.CopyTo(memoryStream); - receivedMessageContent = Encoding.UTF8.GetString(memoryStream.ToArray()); - }; + reader.ReadValueSafe(out receivedMessageContent); + }; yield return new WaitForSeconds(0.2f); - Assert.NotNull(receivedMessageContent); Assert.AreEqual(messageContent, receivedMessageContent); Assert.AreEqual(m_ServerNetworkManager.LocalClientId, receivedMessageSender); } @@ -45,40 +47,41 @@ public IEnumerator UnnamedMessageIsReceivedOnClientWithContent() [UnityTest] public IEnumerator UnnamedMessageIsReceivedOnMultipleClientsWithContent() { - var messageContent = Guid.NewGuid().ToString(); - var buffer = new NetworkBuffer(Encoding.UTF8.GetBytes(messageContent)); - - m_ServerNetworkManager.CustomMessagingManager.SendUnnamedMessage(new List { FirstClient.LocalClientId, SecondClient.LocalClientId }, buffer); + var messageContent = Guid.NewGuid(); + var writer = new FastBufferWriter(1300, Allocator.Temp); + using (writer) + { + writer.WriteValueSafe(messageContent); + m_ServerNetworkManager.CustomMessagingManager.SendUnnamedMessage( + new List { FirstClient.LocalClientId, SecondClient.LocalClientId }, + ref writer); + } ulong firstReceivedMessageSender = 0; - string firstReceivedMessageContent = null; - FirstClient.CustomMessagingManager.OnUnnamedMessage += (sender, stream) => - { - firstReceivedMessageSender = sender; + Guid firstReceivedMessageContent; + FirstClient.CustomMessagingManager.OnUnnamedMessage += + (ulong sender, ref FastBufferReader reader) => + { + firstReceivedMessageSender = sender; - using var memoryStream = new MemoryStream(); - stream.CopyTo(memoryStream); - firstReceivedMessageContent = Encoding.UTF8.GetString(memoryStream.ToArray()); - }; + reader.ReadValueSafe(out firstReceivedMessageContent); + }; ulong secondReceivedMessageSender = 0; - string secondReceivedMessageContent = null; - SecondClient.CustomMessagingManager.OnUnnamedMessage += (sender, stream) => - { - secondReceivedMessageSender = sender; + Guid secondReceivedMessageContent; + SecondClient.CustomMessagingManager.OnUnnamedMessage += + (ulong sender, ref FastBufferReader reader) => + { + secondReceivedMessageSender = sender; - using var memoryStream = new MemoryStream(); - stream.CopyTo(memoryStream); - secondReceivedMessageContent = Encoding.UTF8.GetString(memoryStream.ToArray()); - }; + reader.ReadValueSafe(out secondReceivedMessageContent); + }; yield return new WaitForSeconds(0.2f); - Assert.NotNull(firstReceivedMessageContent); Assert.AreEqual(messageContent, firstReceivedMessageContent); Assert.AreEqual(m_ServerNetworkManager.LocalClientId, firstReceivedMessageSender); - Assert.NotNull(secondReceivedMessageContent); Assert.AreEqual(messageContent, secondReceivedMessageContent); Assert.AreEqual(m_ServerNetworkManager.LocalClientId, secondReceivedMessageSender); } diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkObject/NetworkObjectSceneSerializationTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkObject/NetworkObjectSceneSerializationTests.cs index 6984baf2cc..3f75f0767d 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkObject/NetworkObjectSceneSerializationTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkObject/NetworkObjectSceneSerializationTests.cs @@ -2,6 +2,7 @@ using UnityEngine; using UnityEngine.SceneManagement; using NUnit.Framework; +using Unity.Collections; namespace Unity.Netcode.RuntimeTests { @@ -18,150 +19,168 @@ public void NetworkObjectSceneSerializationFailure() { var networkObjectsToTest = new List(); - var pooledBuffer = PooledNetworkBuffer.Get(); - var writer = PooledNetworkWriter.Get(pooledBuffer); - var reader = PooledNetworkReader.Get(pooledBuffer); + var writer = new FastBufferWriter(1300, Allocator.Temp, 4096000); var invalidNetworkObjectOffsets = new List(); var invalidNetworkObjectIdCount = new List(); var invalidNetworkObjects = new List(); var invalidNetworkObjectFrequency = 3; - - // Construct 50 NetworkObjects - for (int i = 0; i < 50; i++) + using (writer) { - // Inject an invalid NetworkObject every [invalidNetworkObjectFrequency] entry - if ((i % invalidNetworkObjectFrequency) == 0) + // Construct 50 NetworkObjects + for (int i = 0; i < 50; i++) { - // Create the invalid NetworkObject - var gameObject = new GameObject($"InvalidTestObject{i}"); + // Inject an invalid NetworkObject every [invalidNetworkObjectFrequency] entry + if ((i % invalidNetworkObjectFrequency) == 0) + { + // Create the invalid NetworkObject + var gameObject = new GameObject($"InvalidTestObject{i}"); - Assert.IsNotNull(gameObject); + Assert.IsNotNull(gameObject); - var networkObject = gameObject.AddComponent(); + var networkObject = gameObject.AddComponent(); - Assert.IsNotNull(networkObject); + Assert.IsNotNull(networkObject); - var networkVariableComponent = gameObject.AddComponent(); - Assert.IsNotNull(networkVariableComponent); + var networkVariableComponent = gameObject.AddComponent(); + Assert.IsNotNull(networkVariableComponent); - // Add invalid NetworkObject's starting position before serialization to handle trapping for the Debug.LogError message - // that we know will be thrown - invalidNetworkObjectOffsets.Add(pooledBuffer.Position); + // Add invalid NetworkObject's starting position before serialization to handle trapping for the Debug.LogError message + // that we know will be thrown + invalidNetworkObjectOffsets.Add(writer.Position); - networkObject.GlobalObjectIdHash = (uint)(i); - invalidNetworkObjectIdCount.Add(i); + networkObject.GlobalObjectIdHash = (uint)(i); + invalidNetworkObjectIdCount.Add(i); - invalidNetworkObjects.Add(gameObject); + invalidNetworkObjects.Add(gameObject); - writer.WriteInt32Packed(networkObject.gameObject.scene.handle); - // Serialize the invalid NetworkObject - networkObject.SerializeSceneObject(writer, 0); + writer.WriteValueSafe((int)networkObject.gameObject.scene.handle); + // Serialize the invalid NetworkObject + var sceneObject = networkObject.GetMessageSceneObject(0); + var prePosition = writer.Position; + sceneObject.Serialize(ref writer); - Debug.Log($"Invalid {nameof(NetworkObject)} Size {pooledBuffer.Position - invalidNetworkObjectOffsets[invalidNetworkObjectOffsets.Count - 1]}"); + Debug.Log( + $"Invalid {nameof(NetworkObject)} Size {writer.Position - prePosition}"); - // Now adjust how frequent we will inject invalid NetworkObjects - invalidNetworkObjectFrequency = Random.Range(2, 5); + // Now adjust how frequent we will inject invalid NetworkObjects + invalidNetworkObjectFrequency = Random.Range(2, 5); - } - else - { - // Create a valid NetworkObject - var gameObject = new GameObject($"TestObject{i}"); + } + else + { + // Create a valid NetworkObject + var gameObject = new GameObject($"TestObject{i}"); - Assert.IsNotNull(gameObject); + Assert.IsNotNull(gameObject); - var networkObject = gameObject.AddComponent(); + var networkObject = gameObject.AddComponent(); - var networkVariableComponent = gameObject.AddComponent(); - Assert.IsNotNull(networkVariableComponent); + var networkVariableComponent = gameObject.AddComponent(); + Assert.IsNotNull(networkVariableComponent); - Assert.IsNotNull(networkObject); + Assert.IsNotNull(networkObject); - networkObject.GlobalObjectIdHash = (uint)(i + 4096); + networkObject.GlobalObjectIdHash = (uint)(i + 4096); - networkObjectsToTest.Add(gameObject); + networkObjectsToTest.Add(gameObject); - writer.WriteInt32Packed(networkObject.gameObject.scene.handle); + writer.WriteValueSafe((int)networkObject.gameObject.scene.handle); - // Handle populating the scenes loaded list - var scene = networkObject.gameObject.scene; + // Handle populating the scenes loaded list + var scene = networkObject.gameObject.scene; - if (!NetworkManagerHelper.NetworkManagerObject.SceneManager.ScenesLoaded.ContainsKey(scene.handle)) - { - NetworkManagerHelper.NetworkManagerObject.SceneManager.ScenesLoaded.Add(scene.handle, scene); - } + if (!NetworkManagerHelper.NetworkManagerObject.SceneManager.ScenesLoaded.ContainsKey( + scene.handle)) + { + NetworkManagerHelper.NetworkManagerObject.SceneManager.ScenesLoaded + .Add(scene.handle, scene); + } - // Since this is a unit test, we will fake the server to client handle lookup by just adding the same handle key and value - if (!NetworkManagerHelper.NetworkManagerObject.SceneManager.ServerSceneHandleToClientSceneHandle.ContainsKey(networkObject.gameObject.scene.handle)) - { - NetworkManagerHelper.NetworkManagerObject.SceneManager.ServerSceneHandleToClientSceneHandle.Add(networkObject.gameObject.scene.handle, networkObject.gameObject.scene.handle); - } + // Since this is a unit test, we will fake the server to client handle lookup by just adding the same handle key and value + if (!NetworkManagerHelper.NetworkManagerObject.SceneManager.ServerSceneHandleToClientSceneHandle + .ContainsKey(networkObject.gameObject.scene.handle)) + { + NetworkManagerHelper.NetworkManagerObject.SceneManager.ServerSceneHandleToClientSceneHandle + .Add(networkObject.gameObject.scene.handle, networkObject.gameObject.scene.handle); + } - // Serialize the valid NetworkObject - networkObject.SerializeSceneObject(writer, 0); + // Serialize the valid NetworkObject + var sceneObject = networkObject.GetMessageSceneObject(0); + sceneObject.Serialize(ref writer); - if (!NetworkManagerHelper.NetworkManagerObject.SceneManager.ScenePlacedObjects.ContainsKey(networkObject.GlobalObjectIdHash)) - { - NetworkManagerHelper.NetworkManagerObject.SceneManager.ScenePlacedObjects.Add(networkObject.GlobalObjectIdHash, new Dictionary()); + if (!NetworkManagerHelper.NetworkManagerObject.SceneManager.ScenePlacedObjects.ContainsKey( + networkObject.GlobalObjectIdHash)) + { + NetworkManagerHelper.NetworkManagerObject.SceneManager.ScenePlacedObjects.Add( + networkObject.GlobalObjectIdHash, new Dictionary()); + } + + // Add this valid NetworkObject into the ScenePlacedObjects list + NetworkManagerHelper.NetworkManagerObject.SceneManager + .ScenePlacedObjects[networkObject.GlobalObjectIdHash] + .Add(SceneManager.GetActiveScene().handle, networkObject); } - // Add this valid NetworkObject into the ScenePlacedObjects list - NetworkManagerHelper.NetworkManagerObject.SceneManager.ScenePlacedObjects[networkObject.GlobalObjectIdHash].Add(SceneManager.GetActiveScene().handle, networkObject); } - } - var totalBufferSize = pooledBuffer.Position; - // Reset the position for reading this information - pooledBuffer.Position = 0; + var totalBufferSize = writer.Position; - var networkObjectsDeSerialized = new List(); - var currentLogLevel = NetworkManager.Singleton.LogLevel; - var invalidNetworkObjectCount = 0; - while (pooledBuffer.Position != totalBufferSize) - { - // If we reach the point where we expect it to fail, then make sure we let TestRunner know it should expect this log error message - if (invalidNetworkObjectOffsets.Count > 0 && pooledBuffer.Position == invalidNetworkObjectOffsets[0]) + var reader = new FastBufferReader(ref writer, Allocator.Temp); + using (reader) { - invalidNetworkObjectOffsets.RemoveAt(0); - - // Turn off Network Logging to avoid other errors that we know will happen after the below LogAssert.Expect message occurs. - NetworkManager.Singleton.LogLevel = LogLevel.Nothing; - - // Trap for this specific error message so we don't make Test Runner think we failed (it will fail on Debug.LogError) - UnityEngine.TestTools.LogAssert.Expect(LogType.Error, $"Failed to spawn {nameof(NetworkObject)} for Hash {invalidNetworkObjectIdCount[invalidNetworkObjectCount]}."); - invalidNetworkObjectCount++; - } - - - NetworkManagerHelper.NetworkManagerObject.SceneManager.SetTheSceneBeingSynchronized(reader.ReadInt32Packed()); + var networkObjectsDeSerialized = new List(); + var currentLogLevel = NetworkManager.Singleton.LogLevel; + var invalidNetworkObjectCount = 0; + while (reader.Position != totalBufferSize) + { + // If we reach the point where we expect it to fail, then make sure we let TestRunner know it should expect this log error message + if (invalidNetworkObjectOffsets.Count > 0 && + reader.Position == invalidNetworkObjectOffsets[0]) + { + invalidNetworkObjectOffsets.RemoveAt(0); + + // Turn off Network Logging to avoid other errors that we know will happen after the below LogAssert.Expect message occurs. + NetworkManager.Singleton.LogLevel = LogLevel.Nothing; + + // Trap for this specific error message so we don't make Test Runner think we failed (it will fail on Debug.LogError) + UnityEngine.TestTools.LogAssert.Expect(LogType.Error, + $"Failed to spawn {nameof(NetworkObject)} for Hash {invalidNetworkObjectIdCount[invalidNetworkObjectCount]}."); + + invalidNetworkObjectCount++; + } + + + reader.ReadValueSafe(out int handle); + NetworkManagerHelper.NetworkManagerObject.SceneManager.SetTheSceneBeingSynchronized(handle); + var sceneObject = new NetworkObject.SceneObject(); + sceneObject.Deserialize(ref reader); + + var deserializedNetworkObject = NetworkObject.AddSceneObject(sceneObject, ref reader, + NetworkManagerHelper.NetworkManagerObject); + if (deserializedNetworkObject != null) + { + networkObjectsDeSerialized.Add(deserializedNetworkObject); + } + else + { + // Under this condition, we are expecting null (i.e. no NetworkObject instantiated) + // and will set our log level back to the original value to assure the valid NetworkObjects + // aren't causing any log Errors to occur + NetworkManager.Singleton.LogLevel = currentLogLevel; + } + } - var deserializedNetworkObject = NetworkObject.DeserializeSceneObject(pooledBuffer, reader, NetworkManagerHelper.NetworkManagerObject); - if (deserializedNetworkObject != null) - { - networkObjectsDeSerialized.Add(deserializedNetworkObject); - } - else - { - // Under this condition, we are expecting null (i.e. no NetworkObject instantiated) - // and will set our log level back to the original value to assure the valid NetworkObjects - // aren't causing any log Errors to occur - NetworkManager.Singleton.LogLevel = currentLogLevel; + // Now validate all NetworkObjects returned against the original NetworkObjects we created + // after they validate, destroy the objects + foreach (var entry in networkObjectsToTest) + { + var entryNetworkObject = entry.GetComponent(); + Assert.IsTrue(networkObjectsDeSerialized.Contains(entryNetworkObject)); + Object.Destroy(entry); + } } } - reader.Dispose(); - writer.Dispose(); - NetworkBufferPool.PutBackInPool(pooledBuffer); - - // Now validate all NetworkObjects returned against the original NetworkObjects we created - // after they validate, destroy the objects - foreach (var entry in networkObjectsToTest) - { - var entryNetworkObject = entry.GetComponent(); - Assert.IsTrue(networkObjectsDeSerialized.Contains(entryNetworkObject)); - Object.Destroy(entry); - } - // Destroy the invalid network objects foreach (var entry in invalidNetworkObjects) { diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkVarBufferCopyTest.cs b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkVarBufferCopyTest.cs index 2c57832285..360a05dd9b 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkVarBufferCopyTest.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkVarBufferCopyTest.cs @@ -1,5 +1,4 @@ using System.Collections; -using System.IO; using NUnit.Framework; using UnityEngine.TestTools; @@ -26,38 +25,54 @@ public override bool IsDirty() return Dirty; } - public override void WriteDelta(Stream stream) + public override void WriteDelta(ref FastBufferWriter writer) { - using var writer = PooledNetworkWriter.Get(stream); - writer.WriteBits((byte)1, 1); - writer.WriteInt32(k_DummyValue); + writer.TryBeginWrite(FastBufferWriter.GetWriteSize(k_DummyValue) + 1); + using (var bitWriter = writer.EnterBitwiseContext()) + { + bitWriter.WriteBits((byte)1, 1); + } + writer.WriteValue(k_DummyValue); DeltaWritten = true; } - public override void WriteField(Stream stream) + public override void WriteField(ref FastBufferWriter writer) { - using var writer = PooledNetworkWriter.Get(stream); - writer.WriteBits((byte)1, 1); - writer.WriteInt32(k_DummyValue); + writer.TryBeginWrite(FastBufferWriter.GetWriteSize(k_DummyValue) + 1); + using (var bitWriter = writer.EnterBitwiseContext()) + { + bitWriter.WriteBits((byte)1, 1); + } + writer.WriteValue(k_DummyValue); FieldWritten = true; } - public override void ReadField(Stream stream) + public override void ReadField(ref FastBufferReader reader) { - using var reader = PooledNetworkReader.Get(stream); - reader.ReadBits(1); - Assert.AreEqual(k_DummyValue, reader.ReadInt32()); + reader.TryBeginRead(FastBufferWriter.GetWriteSize(k_DummyValue) + 1); + using (var bitReader = reader.EnterBitwiseContext()) + { + bitReader.ReadBits(out byte b, 1); + } + + reader.ReadValue(out int i); + Assert.AreEqual(k_DummyValue, i); FieldRead = true; } - public override void ReadDelta(Stream stream, bool keepDirtyDelta) + public override void ReadDelta(ref FastBufferReader reader, bool keepDirtyDelta) { - using var reader = PooledNetworkReader.Get(stream); - reader.ReadBits(1); - Assert.AreEqual(k_DummyValue, reader.ReadInt32()); + reader.TryBeginRead(FastBufferWriter.GetWriteSize(k_DummyValue) + 1); + using (var bitReader = reader.EnterBitwiseContext()) + { + bitReader.ReadBits(out byte b, 1); + } + + reader.ReadValue(out int i); + Assert.AreEqual(k_DummyValue, i); DeltaRead = true; } diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkVariableTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkVariableTests.cs index 09f15e7d41..b7ed5f1737 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkVariableTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkVariableTests.cs @@ -10,24 +10,10 @@ namespace Unity.Netcode.RuntimeTests public struct FixedString32Struct : INetworkSerializable { public FixedString32Bytes FixedString; - public void NetworkSerialize(NetworkSerializer serializer) + + public void NetworkSerialize(BufferSerializer serializer) where T : IBufferSerializerImplementation { - if (serializer.IsReading) - { - var stringArraySize = 0; - serializer.Serialize(ref stringArraySize); - var stringArray = new char[stringArraySize]; - serializer.Serialize(ref stringArray); - var asString = new string(stringArray); - FixedString.CopyFrom(asString); - } - else - { - var stringArray = FixedString.Value.ToCharArray(); - var stringArraySize = stringArray.Length; - serializer.Serialize(ref stringArraySize); - serializer.Serialize(ref stringArray); - } + serializer.SerializeValue(ref FixedString); } } @@ -36,10 +22,10 @@ public struct TestStruct : INetworkSerializable public uint SomeInt; public bool SomeBool; - public void NetworkSerialize(NetworkSerializer serializer) + public void NetworkSerialize(BufferSerializer serializer) where T : IBufferSerializerImplementation { - serializer.Serialize(ref SomeInt); - serializer.Serialize(ref SomeBool); + serializer.SerializeValue(ref SomeInt); + serializer.SerializeValue(ref SomeBool); } } diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/RpcPipelineTestComponent.cs b/com.unity.netcode.gameobjects/Tests/Runtime/RpcPipelineTestComponent.cs index aac8709ae0..07e7fee279 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/RpcPipelineTestComponent.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/RpcPipelineTestComponent.cs @@ -26,12 +26,6 @@ public class RpcPipelineTestComponent : NetworkBehaviour // Start is called before the first frame update private void Start() { - m_ServerParams.Send.UpdateStage = NetworkUpdateStage.EarlyUpdate; - m_ClientParams.Send.UpdateStage = NetworkUpdateStage.Update; - - m_ServerParams.Receive.UpdateStage = NetworkUpdateStage.EarlyUpdate; - m_ClientParams.Receive.UpdateStage = NetworkUpdateStage.EarlyUpdate; - m_MaxStagesSent = Enum.GetValues(typeof(NetworkUpdateStage)).Length * MaxIterations; //Start out with this being true (for first sequence) @@ -82,34 +76,9 @@ private void Update() m_ClientReceivedRpc = false; //As long as testing isn't completed, keep testing - if (!IsTestComplete() && m_StagesSent.Count < m_MaxStagesSent) + if (!IsTestComplete()) { - m_LastUpdateStage = m_ServerParams.Send.UpdateStage; - m_StagesSent.Add(m_LastUpdateStage); - PingMySelfServerRpc(m_StagesSent.Count, m_ServerParams); - - switch (m_ServerParams.Send.UpdateStage) - { - case NetworkUpdateStage.EarlyUpdate: - m_ServerParams.Send.UpdateStage = NetworkUpdateStage.FixedUpdate; - break; - case NetworkUpdateStage.FixedUpdate: - m_ServerParams.Send.UpdateStage = NetworkUpdateStage.PreUpdate; - break; - case NetworkUpdateStage.PreUpdate: - m_ServerParams.Send.UpdateStage = NetworkUpdateStage.Update; - break; - case NetworkUpdateStage.Update: - m_ServerParams.Send.UpdateStage = NetworkUpdateStage.PreLateUpdate; - break; - case NetworkUpdateStage.PreLateUpdate: - m_ServerParams.Send.UpdateStage = NetworkUpdateStage.PostLateUpdate; - break; - case NetworkUpdateStage.PostLateUpdate: - m_ServerParams.Send.UpdateStage = NetworkUpdateStage.EarlyUpdate; - break; - } } } } @@ -159,10 +128,7 @@ public bool ValidateUpdateStages() [ServerRpc] private void PingMySelfServerRpc(int currentCount, ServerRpcParams parameters = default) { - Debug.Log($"{nameof(PingMySelfServerRpc)}: [HostClient][ServerRpc][{currentCount}] invoked during the {parameters.Receive.UpdateStage} stage."); - - m_ClientParams.Send.UpdateStage = parameters.Receive.UpdateStage; - m_ServerStagesReceived.Add(parameters.Receive.UpdateStage); + Debug.Log($"{nameof(PingMySelfServerRpc)}: [HostClient][ServerRpc][{currentCount}] invoked."); PingMySelfClientRpc(currentCount, m_ClientParams); } @@ -174,15 +140,7 @@ private void PingMySelfServerRpc(int currentCount, ServerRpcParams parameters = [ClientRpc] private void PingMySelfClientRpc(int currentCount, ClientRpcParams parameters = default) { - Debug.Log($"{nameof(PingMySelfClientRpc)}: [HostServer][ClientRpc][{currentCount}] invoked during the {parameters.Receive.UpdateStage} stage. (previous output line should confirm this)"); - - m_ClientStagesReceived.Add(parameters.Receive.UpdateStage); - - //If we reached the last update state, then go ahead and increment our iteration counter - if (parameters.Receive.UpdateStage == NetworkUpdateStage.PostLateUpdate) - { - m_Counter++; - } + Debug.Log($"{nameof(PingMySelfClientRpc)}: [HostServer][ClientRpc][{currentCount}] invoked. (previous output line should confirm this)"); m_ClientReceivedRpc = true; } diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/RpcQueueTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/RpcQueueTests.cs index f64da22995..ae0e7d4923 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/RpcQueueTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/RpcQueueTests.cs @@ -1,6 +1,5 @@ using System; using System.Collections; -using System.Collections.Generic; using UnityEngine; using UnityEngine.TestTools; using NUnit.Framework; @@ -22,48 +21,6 @@ public void Setup() Assert.IsTrue(NetworkManagerHelper.StartNetworkManager(out _)); } - /// - /// Tests to make sure providing different - /// ** This does not include any of the Netcode to Transport code ** - /// - /// IEnumerator - [UnityTest, Order(1)] - public IEnumerator UpdateStagesInvocation() - { - Guid updateStagesTestId = NetworkManagerHelper.AddGameNetworkObject("UpdateStagesTest"); - var rpcPipelineTestComponent = NetworkManagerHelper.AddComponentToObject(updateStagesTestId); - - NetworkManagerHelper.SpawnNetworkObject(updateStagesTestId); - - var testsAreComplete = rpcPipelineTestComponent.IsTestComplete(); - var exceededMaximumStageIterations = rpcPipelineTestComponent.ExceededMaxIterations(); - - // Start testing - rpcPipelineTestComponent.EnableTesting = true; - - Debug.Log("Running TestNetworkUpdateStages: "); - - // Wait for the RPC pipeline test to complete or if we exceeded the maximum iterations bail - while (!testsAreComplete && !exceededMaximumStageIterations) - { - yield return new WaitForSeconds(0.003f); - - testsAreComplete = rpcPipelineTestComponent.IsTestComplete(); - Assert.IsFalse(rpcPipelineTestComponent.ExceededMaxIterations()); - } - var testsAreValidated = rpcPipelineTestComponent.ValidateUpdateStages(); - - // Stop testing - rpcPipelineTestComponent.EnableTesting = false; - - Debug.Log($"Exiting status => {nameof(testsAreComplete)}: {testsAreComplete} - {nameof(testsAreValidated)}: {testsAreValidated} -{nameof(exceededMaximumStageIterations)}: {exceededMaximumStageIterations}"); - - Assert.IsTrue(testsAreComplete && testsAreValidated); - - // Disable this so it isn't running any longer. - rpcPipelineTestComponent.gameObject.SetActive(false); - } - /// /// This tests the RPC Queue outbound and inbound buffer capabilities. /// @@ -99,70 +56,6 @@ public IEnumerator BufferDataValidation() Assert.IsTrue(testsAreComplete); } - /// - /// This tests the RpcQueueContainer and RpcQueueHistoryFrame - /// ***NOTE: We want to run this test always LAST! - /// - [Test, Order(3)] - public void RpcQueueContainerClass() - { - // Create a testing rpcQueueContainer that doesn't get added to the network update loop so we don't try to send or process during the test - var rpcQueueContainer = new MessageQueueContainer(NetworkManagerHelper.NetworkManagerObject, 0, true); - - // Make sure we set testing mode so we don't try to invoke RPCs - rpcQueueContainer.SetTestingState(true); - - const int maxRpcEntries = 8; - const int messageChunkSize = 2048; - - var preCalculatedBufferValues = new List(messageChunkSize); - for (int i = 0; i < messageChunkSize; i++) - { - preCalculatedBufferValues.AddRange(BitConverter.GetBytes(UnityEngine.Random.Range(0, ulong.MaxValue))); - } - - ulong senderNetworkId = 1; - - var randomGeneratedDataArray = preCalculatedBufferValues.ToArray(); - // Create fictitious list of clients to send to - var pseudoClients = new ulong[] { 0 }; - // Testing outbound side of the RpcQueueContainer - for (int i = 0; i < maxRpcEntries; i++) - { - // Increment our offset into our randomly generated data for next entry; - - var writer = rpcQueueContainer.BeginAddQueueItemToFrame(MessageQueueContainer.MessageType.ServerRpc, Time.realtimeSinceStartup, NetworkDelivery.Reliable, - senderNetworkId, pseudoClients, MessageQueueHistoryFrame.QueueFrameType.Outbound, NetworkUpdateStage.PostLateUpdate); - writer.WriteByteArray(randomGeneratedDataArray, messageChunkSize); - - rpcQueueContainer.EndAddQueueItemToFrame(writer, MessageQueueHistoryFrame.QueueFrameType.Outbound, NetworkUpdateStage.PostLateUpdate); - } - - // Now verify the data by obtaining the RpcQueueHistoryFrame we just wrote to - var currentFrame = rpcQueueContainer.GetLoopBackHistoryFrame(MessageQueueHistoryFrame.QueueFrameType.Outbound, NetworkUpdateStage.PostLateUpdate); - - // Reset our index offset - // Parse through the entries written to the current RpcQueueHistoryFrame - var currentQueueItem = currentFrame.GetFirstQueueItem(); - while (currentQueueItem.MessageType != MessageQueueContainer.MessageType.None) - { - // Check to make sure the wrapper information is accurate for the entry - Assert.AreEqual(currentQueueItem.NetworkId, senderNetworkId); - Assert.AreEqual(currentQueueItem.MessageType, MessageQueueContainer.MessageType.ServerRpc); - Assert.AreEqual(currentQueueItem.UpdateStage, NetworkUpdateStage.PostLateUpdate); - Assert.AreEqual(currentQueueItem.Delivery, NetworkDelivery.Reliable); - - // Validate the data in the queue - Assert.IsTrue(NetworkManagerHelper.BuffersMatch(currentQueueItem.MessageData.Offset, messageChunkSize, currentQueueItem.MessageData.Array, randomGeneratedDataArray)); - - // Prepare for next queue item - currentQueueItem = currentFrame.GetNextQueueItem(); - } - - rpcQueueContainer.Dispose(); - } - - [TearDown] public void TearDown() { diff --git a/testproject/.gitignore b/testproject/.gitignore index acbbe841e6..fbe0ca9b9e 100644 --- a/testproject/.gitignore +++ b/testproject/.gitignore @@ -69,5 +69,7 @@ crashlytics-build.properties # Temporary auto-generated Android Assets /[Aa]ssets/[Ss]treamingAssets/aa.meta /[Aa]ssets/[Ss]treamingAssets/aa/* +/[Aa]ssets/[Ss]treamingAssets/BuildInfo.json +/[Aa]ssets/[Ss]treamingAssets/BuildInfo.json.meta InitTestScene* diff --git a/testproject/Assets/Samples/PrefabPool/NetworkPrefabHandlerObjectPoolOverride.cs b/testproject/Assets/Samples/PrefabPool/NetworkPrefabHandlerObjectPoolOverride.cs index f2d0f32679..6fb29632c9 100644 --- a/testproject/Assets/Samples/PrefabPool/NetworkPrefabHandlerObjectPoolOverride.cs +++ b/testproject/Assets/Samples/PrefabPool/NetworkPrefabHandlerObjectPoolOverride.cs @@ -124,12 +124,6 @@ private GameObject GetNextSpawnObject(int synchronizedIndex = -1) return null; } - public void OnSynchronizeWrite(NetworkWriter networkWriter, NetworkObject networkObject) - { - var genericObjectPooledBehaviour = NetworkObject.GetComponent(); - networkWriter.WriteInt32Packed(genericObjectPooledBehaviour.SyncrhonizedObjectTypeIndex); - } - public NetworkObject Instantiate(ulong ownerClientId, Vector3 position, Quaternion rotation) { var gameObject = GetNextSpawnObject(); diff --git a/testproject/Assets/StreamingAssets/.gitignore b/testproject/Assets/StreamingAssets/.gitignore new file mode 100644 index 0000000000..3b64b774a1 --- /dev/null +++ b/testproject/Assets/StreamingAssets/.gitignore @@ -0,0 +1,2 @@ +/BuildInfo.json +/BuildInfo.json.meta diff --git a/testproject/Assets/Tests/Manual/DontDestroyOnLoad/ObjectToNotDestroyBehaviour.cs b/testproject/Assets/Tests/Manual/DontDestroyOnLoad/ObjectToNotDestroyBehaviour.cs index 694ae3724b..9aeb89bf8f 100644 --- a/testproject/Assets/Tests/Manual/DontDestroyOnLoad/ObjectToNotDestroyBehaviour.cs +++ b/testproject/Assets/Tests/Manual/DontDestroyOnLoad/ObjectToNotDestroyBehaviour.cs @@ -85,7 +85,7 @@ private IEnumerator SendContinualPing() { m_PingCounter++; PingUpdateClientRpc(m_PingCounter); - yield return new WaitForSeconds(1); + yield return new WaitForSeconds(0.1f); } yield return null; } diff --git a/testproject/Assets/Tests/Manual/HybridScripts/RpcQueueManualTests.cs b/testproject/Assets/Tests/Manual/HybridScripts/RpcQueueManualTests.cs index e599b080bb..9a54aa25dc 100644 --- a/testproject/Assets/Tests/Manual/HybridScripts/RpcQueueManualTests.cs +++ b/testproject/Assets/Tests/Manual/HybridScripts/RpcQueueManualTests.cs @@ -281,7 +281,6 @@ private void InitializeNetworkManager() NetworkManager.StartClient(); Screen.SetResolution(800, 80, FullScreenMode.Windowed); } - m_ServerRpcParams.Send.UpdateStage = NetworkUpdateStage.Update; break; } @@ -292,7 +291,6 @@ private void InitializeNetworkManager() NetworkManager.StartHost(); Screen.SetResolution(800, 480, FullScreenMode.Windowed); } - m_ClientRpcParams.Send.UpdateStage = NetworkUpdateStage.PreUpdate; break; } @@ -305,8 +303,6 @@ private void InitializeNetworkManager() m_ClientProgressBar.enabled = false; } - m_ClientRpcParams.Send.UpdateStage = NetworkUpdateStage.PostLateUpdate; - break; } } @@ -645,7 +641,6 @@ private void OnSendCounterServerRpc(int counter, ulong clientId, ServerRpcParams private void OnSendNoParametersServerRpc(ServerRpcParams parameters = default) { m_ClientRpcParamsMultiParameter.Send.TargetClientIds[0] = parameters.Receive.SenderClientId; - m_ClientRpcParamsMultiParameter.Send.UpdateStage = NetworkUpdateStage.Update; OnSendNoParametersClientRpc(m_ClientRpcParamsMultiParameter); } @@ -658,7 +653,6 @@ private void OnSendNoParametersServerRpc(ServerRpcParams parameters = default) private void OnSendMultiParametersServerRpc(int count, float floatValue, long longValue, ServerRpcParams parameters = default) { m_ClientRpcParamsMultiParameter.Send.TargetClientIds[0] = parameters.Receive.SenderClientId; - m_ClientRpcParamsMultiParameter.Send.UpdateStage = NetworkUpdateStage.EarlyUpdate; OnSendMultiParametersClientRpc(count, floatValue, longValue, m_ClientRpcParamsMultiParameter); } diff --git a/testproject/Assets/Tests/Manual/Scripts/RandomMovement.cs b/testproject/Assets/Tests/Manual/Scripts/RandomMovement.cs index 610e63e264..024a811eac 100644 --- a/testproject/Assets/Tests/Manual/Scripts/RandomMovement.cs +++ b/testproject/Assets/Tests/Manual/Scripts/RandomMovement.cs @@ -3,6 +3,7 @@ namespace TestProject.ManualTests { + /// /// Used with GenericObjects to randomly move them around /// @@ -54,6 +55,7 @@ public void Move(int speed) } } + // We just apply our current direction with magnitude to our current position during fixed update private void FixedUpdate() { diff --git a/testproject/Assets/Tests/Manual/Scripts/StatsInfoContainer.cs b/testproject/Assets/Tests/Manual/Scripts/StatsInfoContainer.cs index 0818c85834..ec95df6703 100644 --- a/testproject/Assets/Tests/Manual/Scripts/StatsInfoContainer.cs +++ b/testproject/Assets/Tests/Manual/Scripts/StatsInfoContainer.cs @@ -11,18 +11,18 @@ public struct StatsInfoContainer : INetworkSerializable { public List StatValues; - public void NetworkSerialize(NetworkSerializer serializer) + public void NetworkSerialize(BufferSerializer serializer) where T : IBufferSerializerImplementation { - if (serializer.IsReading) + if (serializer.IsReader) { float[] statValuesArray = null; - serializer.Serialize(ref statValuesArray); + serializer.SerializeValue(ref statValuesArray); StatValues = new List(statValuesArray); } else { float[] statValuesArray = StatValues?.ToArray() ?? new float[0]; - serializer.Serialize(ref statValuesArray); + serializer.SerializeValue(ref statValuesArray); } } } diff --git a/testproject/Assets/Tests/Runtime/DontDestroyOnLoadTests.cs b/testproject/Assets/Tests/Runtime/DontDestroyOnLoadTests.cs index 8c1fdcd944..7bd3b8f828 100644 --- a/testproject/Assets/Tests/Runtime/DontDestroyOnLoadTests.cs +++ b/testproject/Assets/Tests/Runtime/DontDestroyOnLoadTests.cs @@ -121,8 +121,8 @@ public IEnumerator ValidateNetworkObjectSynchronization([Values(true, false)] bo { Assert.IsTrue(spawnedObject.gameObject.scene.name == "DontDestroyOnLoad"); var objectToNotDestroyBehaviour = spawnedObject.gameObject.GetComponent(); - Assert.IsTrue(objectToNotDestroyBehaviour.CurrentPing > 0); - Assert.IsTrue(objectToNotDestroyBehaviour.CurrentPing == serverobjectToNotDestroyBehaviour.CurrentPing); + Assert.Greater(objectToNotDestroyBehaviour.CurrentPing, 0); + Assert.AreEqual(serverobjectToNotDestroyBehaviour.CurrentPing, objectToNotDestroyBehaviour.CurrentPing); } } } diff --git a/testproject/Assets/Tests/Runtime/FixedUpdateMessagesAreOnlyProcessedOnceTest.cs b/testproject/Assets/Tests/Runtime/FixedUpdateMessagesAreOnlyProcessedOnceTest.cs deleted file mode 100644 index edab517e84..0000000000 --- a/testproject/Assets/Tests/Runtime/FixedUpdateMessagesAreOnlyProcessedOnceTest.cs +++ /dev/null @@ -1,120 +0,0 @@ -using System.Collections; -using Unity.Netcode; -using Unity.Netcode.RuntimeTests; -using NUnit.Framework; -using TestProject.RuntimeTests.Support; -using UnityEngine; -using UnityEngine.TestTools; - -namespace TestProject.RuntimeTests -{ - public class FixedUpdateMessagesAreOnlyProcessedOnceTest - { - private GameObject m_Prefab; - private int m_OriginalFrameRate; - - - [UnityTearDown] - public IEnumerator Teardown() - { - // Shutdown and clean up both of our NetworkManager instances - if (m_Prefab) - { - MultiInstanceHelpers.Destroy(); - Object.Destroy(m_Prefab); - m_Prefab = null; - SpawnRpcDespawn.ClientUpdateCount = 0; - SpawnRpcDespawn.ServerUpdateCount = 0; - Application.targetFrameRate = m_OriginalFrameRate; - } - yield break; - } - - [UnitySetUp] - public IEnumerator Setup() - { - m_OriginalFrameRate = Application.targetFrameRate; - yield break; - } - - [UnityTest] - public IEnumerator TestFixedUpdateMessagesAreOnlyProcessedOnce() - { - NetworkUpdateStage testStage = NetworkUpdateStage.FixedUpdate; - - // Must be 1 for this test. - const int numClients = 1; - Assert.True(MultiInstanceHelpers.Create(numClients, out NetworkManager server, out NetworkManager[] clients)); - m_Prefab = new GameObject("Object"); - m_Prefab.AddComponent(); - SpawnRpcDespawn.TestStage = testStage; - var networkObject = m_Prefab.AddComponent(); - - // Make it a prefab - MultiInstanceHelpers.MakeNetworkObjectTestPrefab(networkObject); - var handler = new SpawnRpcDespawnInstanceHandler(networkObject.GlobalObjectIdHash); - foreach (var client in clients) - { - client.PrefabHandler.AddHandler(networkObject, handler); - } - - var validNetworkPrefab = new NetworkPrefab(); - validNetworkPrefab.Prefab = m_Prefab; - server.NetworkConfig.NetworkPrefabs.Add(validNetworkPrefab); - foreach (var client in clients) - { - client.NetworkConfig.NetworkPrefabs.Add(validNetworkPrefab); - } - - // Start the instances - if (!MultiInstanceHelpers.Start(true, server, clients)) - { - Debug.LogError("Failed to start instances"); - Assert.Fail("Failed to start instances"); - } - - // [Client-Side] Wait for a connection to the server - yield return MultiInstanceHelpers.Run(MultiInstanceHelpers.WaitForClientsConnected(clients, null, 512)); - - // [Host-Side] Check to make sure all clients are connected - yield return MultiInstanceHelpers.Run( - MultiInstanceHelpers.WaitForClientsConnectedToServer(server, clients.Length + 1, null, 512)); - - // Setting targetFrameRate to 1 will cause FixedUpdate to get called multiple times. - // We should only see ClientUpdateCount increment once because only one RPC is being sent. - Application.targetFrameRate = 1; - var serverObject = Object.Instantiate(m_Prefab, Vector3.zero, Quaternion.identity); - NetworkObject serverNetworkObject = serverObject.GetComponent(); - serverNetworkObject.NetworkManagerOwner = server; - SpawnRpcDespawn srdComponent = serverObject.GetComponent(); - srdComponent.Activate(); - - // Wait until all objects have spawned. - int expectedCount = SpawnRpcDespawn.ClientUpdateCount + 1; - const int maxFrames = 240; - var doubleCheckTime = Time.realtimeSinceStartup + 1.0f; - while (SpawnRpcDespawn.ClientUpdateCount < expectedCount) - { - if (Time.frameCount > maxFrames) - { - // This is here in the event a platform is running at a higher - // frame rate than expected - if (doubleCheckTime < Time.realtimeSinceStartup) - { - Assert.Fail("Did not successfully call all expected client RPCs"); - break; - } - } - var nextFrameNumber = Time.frameCount + 1; - yield return new WaitUntil(() => Time.frameCount >= nextFrameNumber); - } - - Assert.AreEqual(testStage, SpawnRpcDespawn.StageExecutedByReceiver); - Assert.AreEqual(SpawnRpcDespawn.ServerUpdateCount, SpawnRpcDespawn.ClientUpdateCount); - Assert.True(handler.WasSpawned); - var lastFrameNumber = Time.frameCount + 1; - yield return new WaitUntil(() => Time.frameCount >= lastFrameNumber); - Assert.True(handler.WasDestroyed); - } - } -} diff --git a/testproject/Assets/Tests/Runtime/FixedUpdateMessagesAreOnlyProcessedOnceTest.cs.meta b/testproject/Assets/Tests/Runtime/FixedUpdateMessagesAreOnlyProcessedOnceTest.cs.meta deleted file mode 100644 index 99b85a0f53..0000000000 --- a/testproject/Assets/Tests/Runtime/FixedUpdateMessagesAreOnlyProcessedOnceTest.cs.meta +++ /dev/null @@ -1,3 +0,0 @@ -fileFormatVersion: 2 -guid: f00a39af37a64b65b30bfd8ee23a825d -timeCreated: 1627075514 \ No newline at end of file diff --git a/testproject/Assets/Tests/Runtime/MessageOrdering.cs b/testproject/Assets/Tests/Runtime/MessageOrdering.cs index 99beb6ec62..30d7b7ae40 100644 --- a/testproject/Assets/Tests/Runtime/MessageOrdering.cs +++ b/testproject/Assets/Tests/Runtime/MessageOrdering.cs @@ -97,19 +97,14 @@ public IEnumerator SpawnChangeOwnership() } [UnityTest] - public IEnumerator SpawnRpcDespawn([Values] NetworkUpdateStage testStage) + public IEnumerator SpawnRpcDespawn() { - // Neither of these is supported for sending RPCs. - if (testStage == NetworkUpdateStage.Unset || testStage == NetworkUpdateStage.Initialization || testStage == NetworkUpdateStage.FixedUpdate) - { - yield break; - } // Must be 1 for this test. const int numClients = 1; Assert.True(MultiInstanceHelpers.Create(numClients, out NetworkManager server, out NetworkManager[] clients)); m_Prefab = new GameObject("Object"); m_Prefab.AddComponent(); - Support.SpawnRpcDespawn.TestStage = testStage; + Support.SpawnRpcDespawn.TestStage = NetworkUpdateStage.EarlyUpdate; var networkObject = m_Prefab.AddComponent(); // Make it a prefab @@ -168,7 +163,7 @@ public IEnumerator SpawnRpcDespawn([Values] NetworkUpdateStage testStage) yield return new WaitUntil(() => Time.frameCount >= nextFrameNumber); } - Assert.AreEqual(testStage, Support.SpawnRpcDespawn.StageExecutedByReceiver); + Assert.AreEqual(NetworkUpdateStage.EarlyUpdate, Support.SpawnRpcDespawn.StageExecutedByReceiver); Assert.AreEqual(Support.SpawnRpcDespawn.ServerUpdateCount, Support.SpawnRpcDespawn.ClientUpdateCount); Assert.True(handler.WasSpawned); var lastFrameNumber = Time.frameCount + 1; diff --git a/testproject/Assets/Tests/Runtime/MultiClientConnectionApproval.cs b/testproject/Assets/Tests/Runtime/MultiClientConnectionApproval.cs index 99af046da1..49871dba41 100644 --- a/testproject/Assets/Tests/Runtime/MultiClientConnectionApproval.cs +++ b/testproject/Assets/Tests/Runtime/MultiClientConnectionApproval.cs @@ -227,8 +227,8 @@ public IEnumerator ClientConnectedCallbackTest([Values(true, false)] bool enable // [Host-Side] Check to make sure all clients are connected yield return MultiInstanceHelpers.Run(MultiInstanceHelpers.WaitForClientsConnectedToServer(server, clients.Length + 1, null, 512)); - Assert.True(m_ClientConnectedInvocations == 3); - Assert.True(m_ServerClientConnectedInvocations == 4); + Assert.AreEqual(3, m_ClientConnectedInvocations); + Assert.AreEqual(4, m_ServerClientConnectedInvocations); } private void Client_OnClientConnectedCallback(ulong clientId) @@ -276,7 +276,7 @@ public IEnumerator ConnectionApprovalMismatchTest([Values(true, false)] bool ena var nextFrameNumber = Time.frameCount + 5; yield return new WaitUntil(() => Time.frameCount >= nextFrameNumber); - Assert.True(m_ServerClientDisconnectedInvocations == 3); + Assert.AreEqual(3, m_ServerClientDisconnectedInvocations); } private void Server_OnClientDisconnectedCallback(ulong clientId) diff --git a/testproject/Assets/Tests/Runtime/RpcINetworkSerializable.cs b/testproject/Assets/Tests/Runtime/RpcINetworkSerializable.cs deleted file mode 100644 index bda3c1c7cb..0000000000 --- a/testproject/Assets/Tests/Runtime/RpcINetworkSerializable.cs +++ /dev/null @@ -1,406 +0,0 @@ -using System.Collections; -using System.Collections.Generic; -using NUnit.Framework; -using UnityEngine; -using UnityEngine.TestTools; -using Unity.Netcode; -using Unity.Netcode.RuntimeTests; - -namespace TestProject.RuntimeTests -{ - public class RpcINetworkSerializable : BaseMultiInstanceTest - { - private UserSerializableClass m_UserSerializableClass; - private List m_UserSerializableClassArray; - - private bool m_FinishedTest; - - private bool m_IsSendingNull; - private bool m_IsArrayEmpty; - - protected override int NbClients => 1; - - [UnitySetUp] - public override IEnumerator Setup() - { - yield break; // ignore - } - - /// - /// Tests that INetworkSerializable can be used through RPCs by a user - /// - /// - [UnityTest] - public IEnumerator NetworkSerializableTest() - { - m_FinishedTest = false; - var startTime = Time.realtimeSinceStartup; - - yield return StartSomeClientsAndServerWithPlayers(true, NbClients, playerPrefab => - { - playerPrefab.AddComponent(); - }); - - // [Client-Side] We only need to get the client side Player's NetworkObject so we can grab that instance of the TestSerializationComponent - var clientClientPlayerResult = new MultiInstanceHelpers.CoroutineResultWrapper(); - yield return MultiInstanceHelpers.Run(MultiInstanceHelpers.GetNetworkObjectByRepresentation((x => x.IsPlayerObject && x.OwnerClientId == m_ClientNetworkManagers[0].LocalClientId), m_ClientNetworkManagers[0], clientClientPlayerResult)); - var clientSideNetworkBehaviourClass = clientClientPlayerResult.Result.gameObject.GetComponent(); - clientSideNetworkBehaviourClass.OnSerializableClassUpdated = OnClientReceivedUserSerializableClassUpdated; - - - var userSerializableClass = new UserSerializableClass(); - for (int i = 0; i < 32; i++) - { - userSerializableClass.MyByteListValues.Add((byte)i); - } - - userSerializableClass.MyintValue = 1; - userSerializableClass.MyulongValue = 100; - - clientSideNetworkBehaviourClass.ClientStartTest(userSerializableClass); - - // Wait until the test has finished or we time out - var timeOutPeriod = Time.realtimeSinceStartup + 5; - var timedOut = false; - while (!m_FinishedTest) - { - if (Time.realtimeSinceStartup > timeOutPeriod) - { - timedOut = true; - break; - } - - yield return new WaitForSeconds(0.1f); - } - - // Verify the test passed - Assert.False(timedOut); - Assert.IsNotNull(m_UserSerializableClass); - Assert.AreEqual(m_UserSerializableClass.MyintValue, userSerializableClass.MyintValue + 1); - Assert.AreEqual(m_UserSerializableClass.MyulongValue, userSerializableClass.MyulongValue + 1); - Assert.AreEqual(m_UserSerializableClass.MyByteListValues.Count, 64); - - // Validate the list is being sent in order on both sides. - for (int i = 0; i < 32; i++) - { - Assert.AreEqual(m_UserSerializableClass.MyByteListValues[i], i); - } - - // End of test - m_ClientNetworkManagers[0].Shutdown(); - m_ServerNetworkManager.Shutdown(); - } - - /// - /// Delegate handler invoked towards the end of the when the NetworkSerializableTest - /// - /// - private void OnClientReceivedUserSerializableClassUpdated(UserSerializableClass userSerializableClass) - { - m_UserSerializableClass = userSerializableClass; - m_FinishedTest = true; - } - - /// - /// Tests that an array of the same type of class that implements the - /// INetworkSerializable interface will be received in the same order - /// that it was sent. - /// - /// - [UnityTest] - public IEnumerator NetworkSerializableArrayTest() - { - return NetworkSerializableArrayTestHandler(32); - } - - /// - /// Tests that an array of the same type of class that implements the - /// INetworkSerializable interface can send an empty array - /// - /// - [UnityTest] - public IEnumerator NetworkSerializableEmptyArrayTest() - { - return NetworkSerializableArrayTestHandler(0); - } - - /// - /// Tests that an array of the same type of class that implements the - /// INetworkSerializable interface can send a null value for the array - /// - /// - [UnityTest] - public IEnumerator NetworkSerializableNULLArrayTest() - { - return NetworkSerializableArrayTestHandler(0, true); - } - - /// - /// Handles the various tests for INetworkSerializable arrays - /// - /// how many elements - /// force to send a null as the array value - /// - public IEnumerator NetworkSerializableArrayTestHandler(int arraySize, bool sendNullArray = false) - { - m_IsSendingNull = sendNullArray; - m_FinishedTest = false; - m_IsArrayEmpty = false; - - if (arraySize == 0) - { - m_IsArrayEmpty = true; - } - - var startTime = Time.realtimeSinceStartup; - - yield return StartSomeClientsAndServerWithPlayers(true, NbClients, playerPrefab => - { - playerPrefab.AddComponent(); - }); - - // [Host-Side] Get the host-server side Player's NetworkObject so we can grab that instance of the TestCustomTypesArrayComponent - var serverClientPlayerResult = new MultiInstanceHelpers.CoroutineResultWrapper(); - yield return MultiInstanceHelpers.Run(MultiInstanceHelpers.GetNetworkObjectByRepresentation((x => x.IsPlayerObject && x.OwnerClientId == m_ClientNetworkManagers[0].LocalClientId), m_ServerNetworkManager, serverClientPlayerResult)); - var serverSideNetworkBehaviourClass = serverClientPlayerResult.Result.gameObject.GetComponent(); - serverSideNetworkBehaviourClass.OnSerializableClassesUpdatedServerRpc = OnServerReceivedUserSerializableClassesUpdated; - - // [Client-Side] Get the client side Player's NetworkObject so we can grab that instance of the TestCustomTypesArrayComponent - var clientClientPlayerResult = new MultiInstanceHelpers.CoroutineResultWrapper(); - yield return MultiInstanceHelpers.Run(MultiInstanceHelpers.GetNetworkObjectByRepresentation((x => x.IsPlayerObject && x.OwnerClientId == m_ClientNetworkManagers[0].LocalClientId), m_ClientNetworkManagers[0], clientClientPlayerResult)); - var clientSideNetworkBehaviourClass = clientClientPlayerResult.Result.gameObject.GetComponent(); - clientSideNetworkBehaviourClass.OnSerializableClassesUpdatedClientRpc = OnClientReceivedUserSerializableClassesUpdated; - - m_UserSerializableClassArray = new List(); - - if (!m_IsSendingNull) - { - // Create an array of userSerializableClass instances - for (int i = 0; i < arraySize; i++) - { - var userSerializableClass = new UserSerializableClass(); - //Used for testing order of the array - userSerializableClass.MyintValue = i; - m_UserSerializableClassArray.Add(userSerializableClass); - } - - clientSideNetworkBehaviourClass.ClientStartTest(m_UserSerializableClassArray.ToArray()); - } - else - { - clientSideNetworkBehaviourClass.ClientStartTest(null); - } - - // Wait until the test has finished or we time out - var timeOutPeriod = Time.realtimeSinceStartup + 5; - var timedOut = false; - while (!m_FinishedTest) - { - if (Time.realtimeSinceStartup > timeOutPeriod) - { - timedOut = true; - break; - } - - yield return new WaitForSeconds(0.1f); - } - - // Verify the test passed - Assert.False(timedOut); - - // End of test - m_ClientNetworkManagers[0].Shutdown(); - m_ServerNetworkManager.Shutdown(); - - } - - /// - /// Verifies that the UserSerializableClass array is in the same order - /// that it was sent. - /// - /// - private void ValidateUserSerializableClasses(UserSerializableClass[] userSerializableClass) - { - if (m_IsSendingNull) - { - Assert.IsNull(userSerializableClass); - } - else if (m_IsArrayEmpty) - { - Assert.AreEqual(userSerializableClass.Length, 0); - } - else - { - var indexCount = 0; - // Check the order of the array - foreach (var customTypeEntry in userSerializableClass) - { - Assert.AreEqual(customTypeEntry.MyintValue, indexCount); - indexCount++; - } - } - } - - /// - /// Delegate handler invoked when the server sends the client - /// the UserSerializableClass array during the NetworkSerializableArrayTest - /// - /// - private void OnClientReceivedUserSerializableClassesUpdated(UserSerializableClass[] userSerializableClass) - { - ValidateUserSerializableClasses(userSerializableClass); - m_FinishedTest = true; - } - - /// - /// Delegate handler invoked when the client sends the server - /// the UserSerializableClass array during the NetworkSerializableArrayTest - /// - /// - private void OnServerReceivedUserSerializableClassesUpdated(UserSerializableClass[] userSerializableClass) - { - ValidateUserSerializableClasses(userSerializableClass); - } - - } - - /// - /// Component used with NetworkSerializableTest that houses the - /// client and server RPC calls. - /// - public class TestSerializationComponent : NetworkBehaviour - { - public delegate void OnSerializableClassUpdatedDelgateHandler(UserSerializableClass userSerializableClass); - - public OnSerializableClassUpdatedDelgateHandler OnSerializableClassUpdated; - - /// - /// Starts the unit test and passes the UserSerializableClass from the client to the server - /// - /// - public void ClientStartTest(UserSerializableClass userSerializableClass) - { - SendServerSerializedDataServerRpc(userSerializableClass); - } - - /// - /// Server receives the UserSerializableClass, modifies it, and sends it back - /// - /// - [ServerRpc(RequireOwnership = false)] - private void SendServerSerializedDataServerRpc(UserSerializableClass userSerializableClass) - { - userSerializableClass.MyintValue++; - userSerializableClass.MyulongValue++; - - for (int i = 0; i < 32; i++) - { - Assert.AreEqual(userSerializableClass.MyByteListValues[i], i); - } - - for (int i = 32; i < 64; i++) - { - userSerializableClass.MyByteListValues.Add((byte)i); - } - SendClientSerializedDataClientRpc(userSerializableClass); - } - - /// - /// Client receives the UserSerializableClass and then invokes the OnSerializableClassUpdated (if set) - /// - /// - [ClientRpc] - private void SendClientSerializedDataClientRpc(UserSerializableClass userSerializableClass) - { - if (OnSerializableClassUpdated != null) - { - OnSerializableClassUpdated.Invoke(userSerializableClass); - } - } - } - - /// - /// Component used with NetworkSerializableArrayTest that - /// houses the client and server RPC calls that pass an - /// array of UserSerializableClass between the client and - /// the server. - /// - public class TestCustomTypesArrayComponent : NetworkBehaviour - { - public delegate void OnSerializableClassesUpdatedDelgateHandler(UserSerializableClass[] userSerializableClasses); - - public OnSerializableClassesUpdatedDelgateHandler OnSerializableClassesUpdatedServerRpc; - public OnSerializableClassesUpdatedDelgateHandler OnSerializableClassesUpdatedClientRpc; - - /// - /// Starts the unit test and passes the userSerializableClasses array - /// from the client to the server - /// - /// - public void ClientStartTest(UserSerializableClass[] userSerializableClasses) - { - SendServerSerializedDataServerRpc(userSerializableClasses); - } - - /// - /// Server receives the UserSerializableClasses array, invokes the callback - /// that checks the order, and then passes it back to the client - /// - /// - [ServerRpc(RequireOwnership = false)] - private void SendServerSerializedDataServerRpc(UserSerializableClass[] userSerializableClasses) - { - if (OnSerializableClassesUpdatedServerRpc != null) - { - OnSerializableClassesUpdatedServerRpc.Invoke(userSerializableClasses); - } - SendClientSerializedDataClientRpc(userSerializableClasses); - } - - /// - /// Client receives the UserSerializableClasses array and invokes the callback - /// for verification and signaling the test is complete. - /// - /// - [ClientRpc] - private void SendClientSerializedDataClientRpc(UserSerializableClass[] userSerializableClasses) - { - if (OnSerializableClassesUpdatedClientRpc != null) - { - OnSerializableClassesUpdatedClientRpc.Invoke(userSerializableClasses); - } - } - } - - /// - /// The test version of a custom user-defined class that implements INetworkSerializable - /// - public class UserSerializableClass : INetworkSerializable - { - public int MyintValue; - public ulong MyulongValue; - public List MyByteListValues; - - public void NetworkSerialize(NetworkSerializer serializer) - { - if (serializer.IsReading) - { - MyintValue = serializer.Reader.ReadInt32Packed(); - MyulongValue = serializer.Reader.ReadUInt64Packed(); - MyByteListValues = new List(serializer.Reader.ReadByteArray()); - } - else - { - serializer.Writer.WriteInt32Packed(MyintValue); - serializer.Writer.WriteUInt64Packed(MyulongValue); - serializer.Writer.WriteByteArray(MyByteListValues.ToArray()); - } - } - - public UserSerializableClass() - { - MyByteListValues = new List(); - } - } -} - diff --git a/testproject/Assets/Tests/Runtime/RpcTestsAutomated.cs b/testproject/Assets/Tests/Runtime/RpcTestsAutomated.cs index 31f71ec1b4..e5389ce4f1 100644 --- a/testproject/Assets/Tests/Runtime/RpcTestsAutomated.cs +++ b/testproject/Assets/Tests/Runtime/RpcTestsAutomated.cs @@ -24,25 +24,12 @@ public override IEnumerator Setup() yield break; } - /// - /// Default Mode (Batched RPCs Enabled) - /// - /// [UnityTest] public IEnumerator ManualRpcTestsAutomated() { return AutomatedRpcTestsHandler(9); } - /// - /// Same test with Batched RPC turned off - /// - /// - [UnityTest] - public IEnumerator ManualRpcTestsAutomatedNoBatching() - { - return AutomatedRpcTestsHandler(3, false); - } /// /// This just helps to simplify any further tests that can leverage from @@ -52,9 +39,8 @@ public IEnumerator ManualRpcTestsAutomatedNoBatching() /// RPC Batching is enabled or not. /// /// - /// /// - private IEnumerator AutomatedRpcTestsHandler(int numClients, bool useBatching = true) + private IEnumerator AutomatedRpcTestsHandler(int numClients) { var startFrameCount = Time.frameCount; var startTime = Time.realtimeSinceStartup; @@ -68,14 +54,6 @@ private IEnumerator AutomatedRpcTestsHandler(int numClients, bool useBatching = playerPrefab.AddComponent(); }); - // Set the RPC Batch sending mode - m_ServerNetworkManager.MessageQueueContainer.EnableBatchedMessages(useBatching); - - for (int i = 0; i < m_ClientNetworkManagers.Length; i++) - { - m_ClientNetworkManagers[i].MessageQueueContainer.EnableBatchedMessages(useBatching); - } - // [Host-Side] Get the Host owned instance of the RpcQueueManualTests var serverClientPlayerResult = new MultiInstanceHelpers.CoroutineResultWrapper(); yield return MultiInstanceHelpers.Run(MultiInstanceHelpers.GetNetworkObjectByRepresentation((x => x.IsPlayerObject && x.OwnerClientId == m_ClientNetworkManagers[0].LocalClientId), m_ServerNetworkManager, serverClientPlayerResult)); diff --git a/testproject/Assets/Tests/Runtime/RpcUserSerializableTypesTest.cs b/testproject/Assets/Tests/Runtime/RpcUserSerializableTypesTest.cs new file mode 100644 index 0000000000..5459fa29bb --- /dev/null +++ b/testproject/Assets/Tests/Runtime/RpcUserSerializableTypesTest.cs @@ -0,0 +1,771 @@ +using System.Collections; +using System.Collections.Generic; +using NUnit.Framework; +using UnityEngine; +using UnityEngine.TestTools; +using Unity.Netcode; +using Unity.Netcode.RuntimeTests; + +namespace TestProject.RuntimeTests +{ + public class RpcUserSerializableTypesTest : BaseMultiInstanceTest + { + private UserSerializableClass m_UserSerializableClass; + private List m_UserSerializableClassArray; + + private bool m_FinishedTest; + + private bool m_IsSendingNull; + private bool m_IsArrayEmpty; + + protected override int NbClients => 1; + + [UnitySetUp] + public override IEnumerator Setup() + { + yield break; // ignore + } + + /// + /// Tests that INetworkSerializable can be used through RPCs by a user + /// + /// + [UnityTest] + public IEnumerator NetworkSerializableTest() + { + m_FinishedTest = false; + var startTime = Time.realtimeSinceStartup; + + yield return StartSomeClientsAndServerWithPlayers(true, NbClients, playerPrefab => + { + playerPrefab.AddComponent(); + }); + + // [Client-Side] We only need to get the client side Player's NetworkObject so we can grab that instance of the TestSerializationComponent + var clientClientPlayerResult = new MultiInstanceHelpers.CoroutineResultWrapper(); + yield return MultiInstanceHelpers.Run(MultiInstanceHelpers.GetNetworkObjectByRepresentation((x => x.IsPlayerObject && x.OwnerClientId == m_ClientNetworkManagers[0].LocalClientId), m_ClientNetworkManagers[0], clientClientPlayerResult)); + var clientSideNetworkBehaviourClass = clientClientPlayerResult.Result.gameObject.GetComponent(); + clientSideNetworkBehaviourClass.OnSerializableClassUpdated = OnClientReceivedUserSerializableClassUpdated; + + + var userSerializableClass = new UserSerializableClass(); + for (int i = 0; i < 32; i++) + { + userSerializableClass.MyByteListValues.Add((byte)i); + } + + userSerializableClass.MyintValue = 1; + userSerializableClass.MyulongValue = 100; + + clientSideNetworkBehaviourClass.ClientStartTest(userSerializableClass); + + // Wait until the test has finished or we time out + var timeOutPeriod = Time.realtimeSinceStartup + 5; + var timedOut = false; + while (!m_FinishedTest) + { + if (Time.realtimeSinceStartup > timeOutPeriod) + { + timedOut = true; + break; + } + + yield return new WaitForSeconds(0.1f); + } + + // Verify the test passed + Assert.False(timedOut); + Assert.IsNotNull(m_UserSerializableClass); + Assert.AreEqual(m_UserSerializableClass.MyintValue, userSerializableClass.MyintValue + 1); + Assert.AreEqual(m_UserSerializableClass.MyulongValue, userSerializableClass.MyulongValue + 1); + Assert.AreEqual(m_UserSerializableClass.MyByteListValues.Count, 64); + + // Validate the list is being sent in order on both sides. + for (int i = 0; i < 32; i++) + { + Assert.AreEqual(m_UserSerializableClass.MyByteListValues[i], i); + } + + // End of test + m_ClientNetworkManagers[0].Shutdown(); + m_ServerNetworkManager.Shutdown(); + } + + /// + /// Tests that INetworkSerializable can be used through RPCs by a user + /// + /// + [UnityTest] + public IEnumerator ExtensionMethodRpcTest() + { + m_FinishedTest = false; + var startTime = Time.realtimeSinceStartup; + + yield return StartSomeClientsAndServerWithPlayers(true, NbClients, playerPrefab => + { + playerPrefab.AddComponent(); + }); + + // [Client-Side] We only need to get the client side Player's NetworkObject so we can grab that instance of the TestSerializationComponent + var clientClientPlayerResult = new MultiInstanceHelpers.CoroutineResultWrapper(); + yield return MultiInstanceHelpers.Run(MultiInstanceHelpers.GetNetworkObjectByRepresentation((x => x.IsPlayerObject && x.OwnerClientId == m_ClientNetworkManagers[0].LocalClientId), m_ClientNetworkManagers[0], clientClientPlayerResult)); + var clientSideNetworkBehaviourClass = clientClientPlayerResult.Result.gameObject.GetComponent(); + + var serverClientPlayerResult = new MultiInstanceHelpers.CoroutineResultWrapper(); + yield return MultiInstanceHelpers.Run(MultiInstanceHelpers.GetNetworkObjectByRepresentation((x => x.IsPlayerObject && x.OwnerClientId == m_ClientNetworkManagers[0].LocalClientId), m_ServerNetworkManager, serverClientPlayerResult)); + var serverSideNetworkBehaviourClass = serverClientPlayerResult.Result.gameObject.GetComponent(); + + var obj = new MyObject(256); + var obj2 = new MySharedObjectReferencedById(256); + bool clientMyObjCalled = false; + bool clientMySharedObjCalled = true; + bool serverMyObjCalled = false; + bool serverMySharedObjCalled = true; + clientSideNetworkBehaviourClass.OnMyObjectUpdated = (receivedObj) => + { + Assert.AreEqual(obj.I, receivedObj.I); + Assert.AreNotSame(obj, receivedObj); + clientMyObjCalled = true; + m_FinishedTest = clientMyObjCalled && clientMySharedObjCalled && serverMyObjCalled && + serverMySharedObjCalled; + }; + serverSideNetworkBehaviourClass.OnMyObjectUpdated = (receivedObj) => + { + Assert.AreEqual(obj.I, receivedObj.I); + Assert.AreNotSame(obj, receivedObj); + serverMyObjCalled = true; + m_FinishedTest = clientMyObjCalled && clientMySharedObjCalled && serverMyObjCalled && + serverMySharedObjCalled; + }; + clientSideNetworkBehaviourClass.OnMySharedObjectReferencedByIdUpdated = (receivedObj) => + { + Assert.AreSame(obj2, receivedObj); + clientMySharedObjCalled = true; + m_FinishedTest = clientMyObjCalled && clientMySharedObjCalled && serverMyObjCalled && + serverMySharedObjCalled; + }; + serverSideNetworkBehaviourClass.OnMySharedObjectReferencedByIdUpdated = (receivedObj) => + { + Assert.AreSame(obj2, receivedObj); + serverMySharedObjCalled = true; + m_FinishedTest = clientMyObjCalled && clientMySharedObjCalled && serverMyObjCalled && + serverMySharedObjCalled; + }; + + clientSideNetworkBehaviourClass.SendMyObjectServerRpc(obj); + clientSideNetworkBehaviourClass.SendMySharedObjectReferencedByIdServerRpc(obj2); + + // Wait until the test has finished or we time out + var timeOutPeriod = Time.realtimeSinceStartup + 5; + var timedOut = false; + while (!m_FinishedTest) + { + if (Time.realtimeSinceStartup > timeOutPeriod) + { + timedOut = true; + break; + } + + yield return new WaitForSeconds(0.1f); + } + + // Verify the test passed + Assert.False(timedOut); + + // End of test + m_ClientNetworkManagers[0].Shutdown(); + m_ServerNetworkManager.Shutdown(); + } + + /// + /// Tests that INetworkSerializable can be used through RPCs by a user + /// + /// + [UnityTest] + public IEnumerator ExtensionMethodArrayRpcTest() + { + m_FinishedTest = false; + var startTime = Time.realtimeSinceStartup; + + yield return StartSomeClientsAndServerWithPlayers(true, NbClients, playerPrefab => + { + playerPrefab.AddComponent(); + }); + + // [Client-Side] We only need to get the client side Player's NetworkObject so we can grab that instance of the TestSerializationComponent + var clientClientPlayerResult = new MultiInstanceHelpers.CoroutineResultWrapper(); + yield return MultiInstanceHelpers.Run(MultiInstanceHelpers.GetNetworkObjectByRepresentation((x => x.IsPlayerObject && x.OwnerClientId == m_ClientNetworkManagers[0].LocalClientId), m_ClientNetworkManagers[0], clientClientPlayerResult)); + var clientSideNetworkBehaviourClass = clientClientPlayerResult.Result.gameObject.GetComponent(); + + var serverClientPlayerResult = new MultiInstanceHelpers.CoroutineResultWrapper(); + yield return MultiInstanceHelpers.Run(MultiInstanceHelpers.GetNetworkObjectByRepresentation((x => x.IsPlayerObject && x.OwnerClientId == m_ClientNetworkManagers[0].LocalClientId), m_ServerNetworkManager, serverClientPlayerResult)); + var serverSideNetworkBehaviourClass = serverClientPlayerResult.Result.gameObject.GetComponent(); + + var objs = new[]{new MyObject(256), new MyObject(512)}; + var objs2 = new[]{new MySharedObjectReferencedById(256), new MySharedObjectReferencedById(512)}; + bool clientMyObjCalled = false; + bool clientMySharedObjCalled = true; + bool serverMyObjCalled = false; + bool serverMySharedObjCalled = true; + clientSideNetworkBehaviourClass.OnMyObjectUpdated = (receivedObjs) => + { + Assert.AreEqual(receivedObjs.Length, objs2.Length); + for (var i = 0; i < receivedObjs.Length; ++i) + { + Assert.AreEqual(objs[i].I, receivedObjs[i].I); + Assert.AreNotSame(objs[i], receivedObjs[i]); + } + clientMyObjCalled = true; + m_FinishedTest = clientMyObjCalled && clientMySharedObjCalled && serverMyObjCalled && + serverMySharedObjCalled; + }; + serverSideNetworkBehaviourClass.OnMyObjectUpdated = (receivedObjs) => + { + Assert.AreEqual(receivedObjs.Length, objs2.Length); + for (var i = 0; i < receivedObjs.Length; ++i) + { + Assert.AreEqual(objs[i].I, receivedObjs[i].I); + Assert.AreNotSame(objs[i], receivedObjs[i]); + } + serverMyObjCalled = true; + m_FinishedTest = clientMyObjCalled && clientMySharedObjCalled && serverMyObjCalled && + serverMySharedObjCalled; + }; + clientSideNetworkBehaviourClass.OnMySharedObjectReferencedByIdUpdated = (receivedObjs) => + { + Assert.AreEqual(receivedObjs.Length, objs2.Length); + for (var i = 0; i < receivedObjs.Length; ++i) + { + Assert.AreSame(objs2[i], receivedObjs[i]); + } + clientMySharedObjCalled = true; + m_FinishedTest = clientMyObjCalled && clientMySharedObjCalled && serverMyObjCalled && + serverMySharedObjCalled; + }; + serverSideNetworkBehaviourClass.OnMySharedObjectReferencedByIdUpdated = (receivedObjs) => + { + Assert.AreEqual(receivedObjs.Length, objs2.Length); + for (var i = 0; i < receivedObjs.Length; ++i) + { + Assert.AreSame(objs2[i], receivedObjs[i]); + } + serverMySharedObjCalled = true; + m_FinishedTest = clientMyObjCalled && clientMySharedObjCalled && serverMyObjCalled && + serverMySharedObjCalled; + }; + + clientSideNetworkBehaviourClass.SendMyObjectServerRpc(objs); + clientSideNetworkBehaviourClass.SendMySharedObjectReferencedByIdServerRpc(objs2); + + // Wait until the test has finished or we time out + var timeOutPeriod = Time.realtimeSinceStartup + 5; + var timedOut = false; + while (!m_FinishedTest) + { + if (Time.realtimeSinceStartup > timeOutPeriod) + { + timedOut = true; + break; + } + + yield return new WaitForSeconds(0.1f); + } + + // Verify the test passed + Assert.False(timedOut); + + // End of test + m_ClientNetworkManagers[0].Shutdown(); + m_ServerNetworkManager.Shutdown(); + } + + /// + /// Delegate handler invoked towards the end of the when the NetworkSerializableTest + /// + /// + private void OnClientReceivedUserSerializableClassUpdated(UserSerializableClass userSerializableClass) + { + m_UserSerializableClass = userSerializableClass; + m_FinishedTest = true; + } + + /// + /// Tests that an array of the same type of class that implements the + /// INetworkSerializable interface will be received in the same order + /// that it was sent. + /// + /// + [UnityTest] + public IEnumerator NetworkSerializableArrayTest() + { + return NetworkSerializableArrayTestHandler(32); + } + + /// + /// Tests that an array of the same type of class that implements the + /// INetworkSerializable interface can send an empty array + /// + /// + [UnityTest] + public IEnumerator NetworkSerializableEmptyArrayTest() + { + return NetworkSerializableArrayTestHandler(0); + } + + /// + /// Tests that an array of the same type of class that implements the + /// INetworkSerializable interface can send a null value for the array + /// + /// + [UnityTest] + public IEnumerator NetworkSerializableNULLArrayTest() + { + return NetworkSerializableArrayTestHandler(0, true); + } + + /// + /// Handles the various tests for INetworkSerializable arrays + /// + /// how many elements + /// force to send a null as the array value + /// + public IEnumerator NetworkSerializableArrayTestHandler(int arraySize, bool sendNullArray = false) + { + m_IsSendingNull = sendNullArray; + m_FinishedTest = false; + m_IsArrayEmpty = false; + + if (arraySize == 0) + { + m_IsArrayEmpty = true; + } + + var startTime = Time.realtimeSinceStartup; + + yield return StartSomeClientsAndServerWithPlayers(true, NbClients, playerPrefab => + { + playerPrefab.AddComponent(); + }); + + // [Host-Side] Get the host-server side Player's NetworkObject so we can grab that instance of the TestCustomTypesArrayComponent + var serverClientPlayerResult = new MultiInstanceHelpers.CoroutineResultWrapper(); + yield return MultiInstanceHelpers.Run(MultiInstanceHelpers.GetNetworkObjectByRepresentation((x => x.IsPlayerObject && x.OwnerClientId == m_ClientNetworkManagers[0].LocalClientId), m_ServerNetworkManager, serverClientPlayerResult)); + var serverSideNetworkBehaviourClass = serverClientPlayerResult.Result.gameObject.GetComponent(); + serverSideNetworkBehaviourClass.OnSerializableClassesUpdatedServerRpc = OnServerReceivedUserSerializableClassesUpdated; + + // [Client-Side] Get the client side Player's NetworkObject so we can grab that instance of the TestCustomTypesArrayComponent + var clientClientPlayerResult = new MultiInstanceHelpers.CoroutineResultWrapper(); + yield return MultiInstanceHelpers.Run(MultiInstanceHelpers.GetNetworkObjectByRepresentation((x => x.IsPlayerObject && x.OwnerClientId == m_ClientNetworkManagers[0].LocalClientId), m_ClientNetworkManagers[0], clientClientPlayerResult)); + var clientSideNetworkBehaviourClass = clientClientPlayerResult.Result.gameObject.GetComponent(); + clientSideNetworkBehaviourClass.OnSerializableClassesUpdatedClientRpc = OnClientReceivedUserSerializableClassesUpdated; + + m_UserSerializableClassArray = new List(); + + if (!m_IsSendingNull) + { + // Create an array of userSerializableClass instances + for (int i = 0; i < arraySize; i++) + { + var userSerializableClass = new UserSerializableClass(); + //Used for testing order of the array + userSerializableClass.MyintValue = i; + m_UserSerializableClassArray.Add(userSerializableClass); + } + + clientSideNetworkBehaviourClass.ClientStartTest(m_UserSerializableClassArray.ToArray()); + } + else + { + clientSideNetworkBehaviourClass.ClientStartTest(null); + } + + // Wait until the test has finished or we time out + var timeOutPeriod = Time.realtimeSinceStartup + 5; + var timedOut = false; + while (!m_FinishedTest) + { + if (Time.realtimeSinceStartup > timeOutPeriod) + { + timedOut = true; + break; + } + + yield return new WaitForSeconds(0.1f); + } + + // Verify the test passed + Assert.False(timedOut); + + // End of test + m_ClientNetworkManagers[0].Shutdown(); + m_ServerNetworkManager.Shutdown(); + + } + + /// + /// Verifies that the UserSerializableClass array is in the same order + /// that it was sent. + /// + /// + private void ValidateUserSerializableClasses(UserSerializableClass[] userSerializableClass) + { + if (m_IsSendingNull) + { + Assert.IsNull(userSerializableClass); + } + else if (m_IsArrayEmpty) + { + Assert.AreEqual(userSerializableClass.Length, 0); + } + else + { + var indexCount = 0; + // Check the order of the array + foreach (var customTypeEntry in userSerializableClass) + { + Assert.AreEqual(customTypeEntry.MyintValue, indexCount); + indexCount++; + } + } + } + + /// + /// Delegate handler invoked when the server sends the client + /// the UserSerializableClass array during the NetworkSerializableArrayTest + /// + /// + private void OnClientReceivedUserSerializableClassesUpdated(UserSerializableClass[] userSerializableClass) + { + ValidateUserSerializableClasses(userSerializableClass); + m_FinishedTest = true; + } + + /// + /// Delegate handler invoked when the client sends the server + /// the UserSerializableClass array during the NetworkSerializableArrayTest + /// + /// + private void OnServerReceivedUserSerializableClassesUpdated(UserSerializableClass[] userSerializableClass) + { + ValidateUserSerializableClasses(userSerializableClass); + } + + } + + /// + /// Component used with NetworkSerializableTest that houses the + /// client and server RPC calls. + /// + public class TestSerializationComponent : NetworkBehaviour + { + public delegate void OnSerializableClassUpdatedDelgateHandler(UserSerializableClass userSerializableClass); + public OnSerializableClassUpdatedDelgateHandler OnSerializableClassUpdated; + + public delegate void OnMySharedObjectReferencedByIdUpdatedDelgateHandler(MySharedObjectReferencedById obj); + public OnMySharedObjectReferencedByIdUpdatedDelgateHandler OnMySharedObjectReferencedByIdUpdated; + + public delegate void OnMyObjectUpdatedDelgateHandler(MyObject obj); + public OnMyObjectUpdatedDelgateHandler OnMyObjectUpdated; + + /// + /// Starts the unit test and passes the UserSerializableClass from the client to the server + /// + /// + public void ClientStartTest(UserSerializableClass userSerializableClass) + { + SendServerSerializedDataServerRpc(userSerializableClass); + } + + /// + /// Server receives the UserSerializableClass, modifies it, and sends it back + /// + /// + [ServerRpc(RequireOwnership = false)] + private void SendServerSerializedDataServerRpc(UserSerializableClass userSerializableClass) + { + userSerializableClass.MyintValue++; + userSerializableClass.MyulongValue++; + + for (int i = 0; i < 32; i++) + { + Assert.AreEqual(userSerializableClass.MyByteListValues[i], i); + } + + for (int i = 32; i < 64; i++) + { + userSerializableClass.MyByteListValues.Add((byte)i); + } + SendClientSerializedDataClientRpc(userSerializableClass); + } + + /// + /// Client receives the UserSerializableClass and then invokes the OnSerializableClassUpdated (if set) + /// + /// + [ClientRpc] + private void SendClientSerializedDataClientRpc(UserSerializableClass userSerializableClass) + { + if (OnSerializableClassUpdated != null) + { + OnSerializableClassUpdated.Invoke(userSerializableClass); + } + } + + [ClientRpc] + public void SendMyObjectClientRpc(MyObject obj) + { + if (OnMyObjectUpdated != null) + { + OnMyObjectUpdated.Invoke(obj); + } + } + + [ClientRpc] + public void SendMySharedObjectReferencedByIdClientRpc(MySharedObjectReferencedById obj) + { + if (OnMySharedObjectReferencedByIdUpdated != null) + { + OnMySharedObjectReferencedByIdUpdated.Invoke(obj); + } + } + + [ServerRpc] + public void SendMyObjectServerRpc(MyObject obj) + { + if (OnMyObjectUpdated != null) + { + OnMyObjectUpdated.Invoke(obj); + } + SendMyObjectClientRpc(obj); + } + + [ServerRpc] + public void SendMySharedObjectReferencedByIdServerRpc(MySharedObjectReferencedById obj) + { + if (OnMySharedObjectReferencedByIdUpdated != null) + { + OnMySharedObjectReferencedByIdUpdated.Invoke(obj); + } + SendMySharedObjectReferencedByIdClientRpc(obj); + } + } + + /// + /// Component used with NetworkSerializableArrayTest that + /// houses the client and server RPC calls that pass an + /// array of UserSerializableClass between the client and + /// the server. + /// + public class TestCustomTypesArrayComponent : NetworkBehaviour + { + public delegate void OnSerializableClassesUpdatedDelgateHandler(UserSerializableClass[] userSerializableClasses); + + public delegate void OnMySharedObjectReferencedByIdUpdatedDelgateHandler(MySharedObjectReferencedById[] obj); + public OnMySharedObjectReferencedByIdUpdatedDelgateHandler OnMySharedObjectReferencedByIdUpdated; + + public delegate void OnMyObjectUpdatedDelgateHandler(MyObject[] obj); + public OnMyObjectUpdatedDelgateHandler OnMyObjectUpdated; + + public OnSerializableClassesUpdatedDelgateHandler OnSerializableClassesUpdatedServerRpc; + public OnSerializableClassesUpdatedDelgateHandler OnSerializableClassesUpdatedClientRpc; + + /// + /// Starts the unit test and passes the userSerializableClasses array + /// from the client to the server + /// + /// + public void ClientStartTest(UserSerializableClass[] userSerializableClasses) + { + SendServerSerializedDataServerRpc(userSerializableClasses); + } + + /// + /// Server receives the UserSerializableClasses array, invokes the callback + /// that checks the order, and then passes it back to the client + /// + /// + [ServerRpc(RequireOwnership = false)] + private void SendServerSerializedDataServerRpc(UserSerializableClass[] userSerializableClasses) + { + if (OnSerializableClassesUpdatedServerRpc != null) + { + OnSerializableClassesUpdatedServerRpc.Invoke(userSerializableClasses); + } + SendClientSerializedDataClientRpc(userSerializableClasses); + } + + /// + /// Client receives the UserSerializableClasses array and invokes the callback + /// for verification and signaling the test is complete. + /// + /// + [ClientRpc] + private void SendClientSerializedDataClientRpc(UserSerializableClass[] userSerializableClasses) + { + if (OnSerializableClassesUpdatedClientRpc != null) + { + OnSerializableClassesUpdatedClientRpc.Invoke(userSerializableClasses); + } + } + + [ClientRpc] + public void SendMyObjectClientRpc(MyObject[] objs) + { + if (OnMyObjectUpdated != null) + { + OnMyObjectUpdated.Invoke(objs); + } + } + + [ClientRpc] + public void SendMySharedObjectReferencedByIdClientRpc(MySharedObjectReferencedById[] objs) + { + if (OnMySharedObjectReferencedByIdUpdated != null) + { + OnMySharedObjectReferencedByIdUpdated.Invoke(objs); + } + } + + [ServerRpc] + public void SendMyObjectServerRpc(MyObject[] objs) + { + if (OnMyObjectUpdated != null) + { + OnMyObjectUpdated.Invoke(objs); + } + SendMyObjectClientRpc(objs); + } + + + [ServerRpc] + public void SendMySharedObjectReferencedByIdServerRpc(MySharedObjectReferencedById[] objs) + { + if (OnMySharedObjectReferencedByIdUpdated != null) + { + OnMySharedObjectReferencedByIdUpdated.Invoke(objs); + } + SendMySharedObjectReferencedByIdClientRpc(objs); + } + } + + /// + /// The test version of a custom user-defined class that implements INetworkSerializable + /// + public class UserSerializableClass : INetworkSerializable + { + public int MyintValue; + public ulong MyulongValue; + public List MyByteListValues; + + public void NetworkSerialize(BufferSerializer serializer) where T : IBufferSerializerImplementation + { + serializer.SerializeValue(ref MyintValue); + serializer.SerializeValue(ref MyulongValue); + int size = MyByteListValues.Count; + serializer.SerializeValue(ref size); + if (serializer.IsReader) + { + var b = new byte[size]; + serializer.GetFastBufferReader().ReadBytesSafe(ref b, size); + MyByteListValues = new List(b); + } + else + { + + serializer.GetFastBufferWriter().WriteBytesSafe(MyByteListValues.ToArray()); + } + } + + public UserSerializableClass() + { + MyByteListValues = new List(); + } + } + + public class MyObject + { + public int I; + + public MyObject(int i) + { + I = i; + } + } + + public class MySharedObjectReferencedById + { + public static Dictionary Values = + new Dictionary(); + public int I; + + public MySharedObjectReferencedById(int i) + { + I = i; + Values[I] = this; + } + } + + public static class TestSerializationExtensions + { + public static void ReadValueSafe(this ref FastBufferReader reader, out MyObject value) + { + reader.ReadValueSafe(out int i); + value = new MyObject(i); + } + + public static void WriteValueSafe(this ref FastBufferWriter writer, in MyObject value) + { + writer.WriteValueSafe(value.I); + } + + public static void ReadValueSafe(this ref FastBufferReader reader, out MySharedObjectReferencedById value) + { + reader.ReadValueSafe(out int i); + value = MySharedObjectReferencedById.Values[i]; + } + + public static void WriteValueSafe(this ref FastBufferWriter writer, MySharedObjectReferencedById value) + { + writer.WriteValueSafe(value.I); + } + public static void ReadValueSafe(this ref FastBufferReader reader, out MyObject[] values) + { + reader.ReadValueSafe(out int length); + values = new MyObject[length]; + for (var i = 0; i < length; ++i) + { + reader.ReadValueSafe(out values[i]); + } + } + + public static void WriteValueSafe(this ref FastBufferWriter writer, in MyObject[] values) + { + writer.WriteValueSafe(values.Length); + for (var i = 0; i < values.Length; ++i) + { + writer.WriteValueSafe(values[i]); + } + } + + public static void ReadValueSafe(this ref FastBufferReader reader, out MySharedObjectReferencedById[] values) + { + reader.ReadValueSafe(out int length); + values = new MySharedObjectReferencedById[length]; + for (var i = 0; i < length; ++i) + { + reader.ReadValueSafe(out values[i]); + } + } + + public static void WriteValueSafe(this ref FastBufferWriter writer, MySharedObjectReferencedById[] values) + { + writer.WriteValueSafe(values.Length); + for (var i = 0; i < values.Length; ++i) + { + writer.WriteValueSafe(values[i]); + } + } + + } +} + diff --git a/testproject/Assets/Tests/Runtime/RpcINetworkSerializable.cs.meta b/testproject/Assets/Tests/Runtime/RpcUserSerializableTypesTest.cs.meta similarity index 100% rename from testproject/Assets/Tests/Runtime/RpcINetworkSerializable.cs.meta rename to testproject/Assets/Tests/Runtime/RpcUserSerializableTypesTest.cs.meta diff --git a/testproject/Assets/Tests/Runtime/Support/SpawnRpcDespawn.cs b/testproject/Assets/Tests/Runtime/Support/SpawnRpcDespawn.cs index 0b7ac56369..948a89ef83 100644 --- a/testproject/Assets/Tests/Runtime/Support/SpawnRpcDespawn.cs +++ b/testproject/Assets/Tests/Runtime/Support/SpawnRpcDespawn.cs @@ -17,7 +17,7 @@ public class SpawnRpcDespawn : NetworkBehaviour, INetworkUpdateSystem [ClientRpc] public void SendIncrementUpdateCountClientRpc() { - Assert.AreEqual(TestStage, NetworkUpdateLoop.UpdateStage); + Assert.AreEqual(NetworkUpdateStage.EarlyUpdate, NetworkUpdateLoop.UpdateStage); StageExecutedByReceiver = NetworkUpdateLoop.UpdateStage; ++ClientUpdateCount; @@ -40,7 +40,7 @@ public void Activate() public void NetworkStart() { Debug.Log($"Network Start on client {NetworkManager.LocalClientId.ToString()}"); - Assert.AreEqual(TestStage, NetworkUpdateLoop.UpdateStage); + Assert.AreEqual(NetworkUpdateStage.EarlyUpdate, NetworkUpdateLoop.UpdateStage); } public void Awake() diff --git a/testproject/Assets/Tests/Runtime/Support/SpawnRpcDespawnInstanceHandler.cs b/testproject/Assets/Tests/Runtime/Support/SpawnRpcDespawnInstanceHandler.cs index 5e2c5b2990..c1f8496062 100644 --- a/testproject/Assets/Tests/Runtime/Support/SpawnRpcDespawnInstanceHandler.cs +++ b/testproject/Assets/Tests/Runtime/Support/SpawnRpcDespawnInstanceHandler.cs @@ -19,7 +19,7 @@ public SpawnRpcDespawnInstanceHandler(uint prefabHash) public NetworkObject Instantiate(ulong ownerClientId, Vector3 position, Quaternion rotation) { WasSpawned = true; - Assert.AreEqual(SpawnRpcDespawn.TestStage, NetworkUpdateLoop.UpdateStage); + Assert.AreEqual(NetworkUpdateStage.EarlyUpdate, NetworkUpdateLoop.UpdateStage); // See if there is a valid registered NetworkPrefabOverrideLink associated with the provided prefabHash @@ -60,7 +60,7 @@ public void Destroy(NetworkObject networkObject) WasDestroyed = true; if (networkObject.NetworkManager.IsClient) { - Assert.AreEqual(NetworkUpdateStage.PostLateUpdate, NetworkUpdateLoop.UpdateStage); + Assert.AreEqual(NetworkUpdateStage.EarlyUpdate, NetworkUpdateLoop.UpdateStage); } Object.Destroy(networkObject.gameObject);