diff --git a/com.unity.netcode.gameobjects/CHANGELOG.md b/com.unity.netcode.gameobjects/CHANGELOG.md index 425d9dd1a1..5cd304969a 100644 --- a/com.unity.netcode.gameobjects/CHANGELOG.md +++ b/com.unity.netcode.gameobjects/CHANGELOG.md @@ -13,6 +13,10 @@ Additional documentation and release notes are available at [Multiplayer Documen - Added `NetworkObject` auto-add helper and Multiplayer Tools install reminder settings to Project Settings. (#2285) - Added `public string DisconnectReason` getter to `NetworkManager` and `string Reason` to `ConnectionApprovalResponse`. Allows connection approval to communicate back a reason. Also added `public void DisconnectClient(ulong clientId, string reason)` allowing setting a disconnection reason, when explicitly disconnecting a client. +### Changed + +- Optimized bandwidth usage by encoding most integer fields using variable-length encoding. (#2276) + ### Fixed - Fixed issue where the host would receive more than one event completed notification when loading or unloading a scene only when no clients were connected. (#2292) diff --git a/com.unity.netcode.gameobjects/Editor/CodeGen/NetworkBehaviourILPP.cs b/com.unity.netcode.gameobjects/Editor/CodeGen/NetworkBehaviourILPP.cs index ce7d3bb08f..4c1fe206fa 100644 --- a/com.unity.netcode.gameobjects/Editor/CodeGen/NetworkBehaviourILPP.cs +++ b/com.unity.netcode.gameobjects/Editor/CodeGen/NetworkBehaviourILPP.cs @@ -139,6 +139,19 @@ private bool IsMemcpyableType(TypeReference type) return false; } + private bool IsSpecialCaseType(TypeReference type) + { + foreach (var supportedType in SpecialCaseTypes) + { + if (type.FullName == supportedType.FullName) + { + return true; + } + } + + return false; + } + private void CreateNetworkVariableTypeInitializers(AssemblyDefinition assembly) { foreach (var typeDefinition in assembly.MainModule.Types) @@ -153,6 +166,11 @@ private void CreateNetworkVariableTypeInitializers(AssemblyDefinition assembly) foreach (var type in m_WrappedNetworkVariableTypes) { + if (IsSpecialCaseType(type)) + { + continue; + } + // If a serializable type isn't found, FallbackSerializer will be used automatically, which will // call into UserNetworkVariableSerialization, giving the user a chance to define their own serializaiton // for types that aren't in our official supported types list. @@ -257,6 +275,20 @@ private void CreateNetworkVariableTypeInitializers(AssemblyDefinition assembly) private MethodReference m_NetworkVariableSerializationTypes_InitializeEqualityChecker_UnmanagedValueEquals_MethodRef; private MethodReference m_NetworkVariableSerializationTypes_InitializeEqualityChecker_ManagedClassEquals_MethodRef; + private MethodReference m_BytePacker_WriteValueBitPacked_Short_MethodRef; + private MethodReference m_BytePacker_WriteValueBitPacked_UShort_MethodRef; + private MethodReference m_BytePacker_WriteValueBitPacked_Int_MethodRef; + private MethodReference m_BytePacker_WriteValueBitPacked_UInt_MethodRef; + private MethodReference m_BytePacker_WriteValueBitPacked_Long_MethodRef; + private MethodReference m_BytePacker_WriteValueBitPacked_ULong_MethodRef; + + private MethodReference m_ByteUnpacker_ReadValueBitPacked_Short_MethodRef; + private MethodReference m_ByteUnpacker_ReadValueBitPacked_UShort_MethodRef; + private MethodReference m_ByteUnpacker_ReadValueBitPacked_Int_MethodRef; + private MethodReference m_ByteUnpacker_ReadValueBitPacked_UInt_MethodRef; + private MethodReference m_ByteUnpacker_ReadValueBitPacked_Long_MethodRef; + private MethodReference m_ByteUnpacker_ReadValueBitPacked_ULong_MethodRef; + private TypeReference m_FastBufferWriter_TypeRef; private readonly Dictionary m_FastBufferWriter_WriteValue_MethodRefs = new Dictionary(); private readonly List m_FastBufferWriter_ExtensionMethodRefs = new List(); @@ -276,12 +308,13 @@ private void CreateNetworkVariableTypeInitializers(AssemblyDefinition assembly) typeof(decimal), typeof(double), typeof(float), - typeof(int), + // the following types have special handling + /*typeof(int), typeof(uint), typeof(long), typeof(ulong), typeof(short), - typeof(ushort), + typeof(ushort),*/ typeof(Vector2), typeof(Vector3), typeof(Vector2Int), @@ -293,6 +326,16 @@ private void CreateNetworkVariableTypeInitializers(AssemblyDefinition assembly) typeof(Ray), typeof(Ray2D) }; + internal static readonly Type[] SpecialCaseTypes = new[] + { + // the following types have special handling + typeof(int), + typeof(uint), + typeof(long), + typeof(ulong), + typeof(short), + typeof(ushort), + }; private const string k_Debug_LogError = nameof(Debug.LogError); private const string k_NetworkManager_LocalClientId = nameof(NetworkManager.LocalClientId); @@ -343,6 +386,8 @@ private bool ImportReferences(ModuleDefinition moduleDefinition) TypeDefinition fastBufferWriterTypeDef = null; TypeDefinition fastBufferReaderTypeDef = null; TypeDefinition networkVariableSerializationTypesTypeDef = null; + TypeDefinition bytePackerTypeDef = null; + TypeDefinition byteUnpackerTypeDef = null; foreach (var netcodeTypeDef in m_NetcodeModule.GetAllTypes()) { if (networkManagerTypeDef == null && netcodeTypeDef.Name == nameof(NetworkManager)) @@ -398,6 +443,18 @@ private bool ImportReferences(ModuleDefinition moduleDefinition) networkVariableSerializationTypesTypeDef = netcodeTypeDef; continue; } + + if (bytePackerTypeDef == null && netcodeTypeDef.Name == nameof(BytePacker)) + { + bytePackerTypeDef = netcodeTypeDef; + continue; + } + + if (byteUnpackerTypeDef == null && netcodeTypeDef.Name == nameof(ByteUnpacker)) + { + byteUnpackerTypeDef = netcodeTypeDef; + continue; + } } foreach (var methodDef in debugTypeDef.Methods) @@ -652,6 +709,82 @@ private bool ImportReferences(ModuleDefinition moduleDefinition) } } + foreach (var method in bytePackerTypeDef.Methods) + { + if (!method.IsStatic) + { + continue; + } + + switch (method.Name) + { + case nameof(BytePacker.WriteValueBitPacked): + if (method.Parameters[1].ParameterType.FullName == typeof(short).FullName) + { + m_BytePacker_WriteValueBitPacked_Short_MethodRef = m_MainModule.ImportReference(method); + } + else if (method.Parameters[1].ParameterType.FullName == typeof(ushort).FullName) + { + m_BytePacker_WriteValueBitPacked_UShort_MethodRef = m_MainModule.ImportReference(method); + } + else if (method.Parameters[1].ParameterType.FullName == typeof(int).FullName) + { + m_BytePacker_WriteValueBitPacked_Int_MethodRef = m_MainModule.ImportReference(method); + } + else if (method.Parameters[1].ParameterType.FullName == typeof(uint).FullName) + { + m_BytePacker_WriteValueBitPacked_UInt_MethodRef = m_MainModule.ImportReference(method); + } + else if (method.Parameters[1].ParameterType.FullName == typeof(long).FullName) + { + m_BytePacker_WriteValueBitPacked_Long_MethodRef = m_MainModule.ImportReference(method); + } + else if (method.Parameters[1].ParameterType.FullName == typeof(ulong).FullName) + { + m_BytePacker_WriteValueBitPacked_ULong_MethodRef = m_MainModule.ImportReference(method); + } + break; + } + } + + foreach (var method in byteUnpackerTypeDef.Methods) + { + if (!method.IsStatic) + { + continue; + } + + switch (method.Name) + { + case nameof(ByteUnpacker.ReadValueBitPacked): + if (method.Parameters[1].ParameterType.FullName == typeof(short).MakeByRefType().FullName) + { + m_ByteUnpacker_ReadValueBitPacked_Short_MethodRef = m_MainModule.ImportReference(method); + } + else if (method.Parameters[1].ParameterType.FullName == typeof(ushort).MakeByRefType().FullName) + { + m_ByteUnpacker_ReadValueBitPacked_UShort_MethodRef = m_MainModule.ImportReference(method); + } + else if (method.Parameters[1].ParameterType.FullName == typeof(int).MakeByRefType().FullName) + { + m_ByteUnpacker_ReadValueBitPacked_Int_MethodRef = m_MainModule.ImportReference(method); + } + else if (method.Parameters[1].ParameterType.FullName == typeof(uint).MakeByRefType().FullName) + { + m_ByteUnpacker_ReadValueBitPacked_UInt_MethodRef = m_MainModule.ImportReference(method); + } + else if (method.Parameters[1].ParameterType.FullName == typeof(long).MakeByRefType().FullName) + { + m_ByteUnpacker_ReadValueBitPacked_Long_MethodRef = m_MainModule.ImportReference(method); + } + else if (method.Parameters[1].ParameterType.FullName == typeof(ulong).MakeByRefType().FullName) + { + m_ByteUnpacker_ReadValueBitPacked_ULong_MethodRef = m_MainModule.ImportReference(method); + } + break; + } + } + return true; } @@ -1008,6 +1141,36 @@ private MethodReference GetFastBufferWriterWriteMethod(string name, TypeReferenc private bool GetWriteMethodForParameter(TypeReference paramType, out MethodReference methodRef) { + if (paramType.FullName == typeof(short).FullName) + { + methodRef = m_BytePacker_WriteValueBitPacked_Short_MethodRef; + return true; + } + if (paramType.FullName == typeof(ushort).FullName) + { + methodRef = m_BytePacker_WriteValueBitPacked_UShort_MethodRef; + return true; + } + if (paramType.FullName == typeof(int).FullName) + { + methodRef = m_BytePacker_WriteValueBitPacked_Int_MethodRef; + return true; + } + if (paramType.FullName == typeof(uint).FullName) + { + methodRef = m_BytePacker_WriteValueBitPacked_UInt_MethodRef; + return true; + } + if (paramType.FullName == typeof(long).FullName) + { + methodRef = m_BytePacker_WriteValueBitPacked_Long_MethodRef; + return true; + } + if (paramType.FullName == typeof(ulong).FullName) + { + methodRef = m_BytePacker_WriteValueBitPacked_ULong_MethodRef; + return true; + } var assemblyQualifiedName = paramType.FullName + ", " + paramType.Resolve().Module.Assembly.FullName; var foundMethodRef = m_FastBufferWriter_WriteValue_MethodRefs.TryGetValue(assemblyQualifiedName, out methodRef); @@ -1154,6 +1317,36 @@ private MethodReference GetFastBufferReaderReadMethod(string name, TypeReference private bool GetReadMethodForParameter(TypeReference paramType, out MethodReference methodRef) { + if (paramType.FullName == typeof(short).FullName) + { + methodRef = m_ByteUnpacker_ReadValueBitPacked_Short_MethodRef; + return true; + } + if (paramType.FullName == typeof(ushort).FullName) + { + methodRef = m_ByteUnpacker_ReadValueBitPacked_UShort_MethodRef; + return true; + } + if (paramType.FullName == typeof(int).FullName) + { + methodRef = m_ByteUnpacker_ReadValueBitPacked_Int_MethodRef; + return true; + } + if (paramType.FullName == typeof(uint).FullName) + { + methodRef = m_ByteUnpacker_ReadValueBitPacked_UInt_MethodRef; + return true; + } + if (paramType.FullName == typeof(long).FullName) + { + methodRef = m_ByteUnpacker_ReadValueBitPacked_Long_MethodRef; + return true; + } + if (paramType.FullName == typeof(ulong).FullName) + { + methodRef = m_ByteUnpacker_ReadValueBitPacked_ULong_MethodRef; + return true; + } var assemblyQualifiedName = paramType.FullName + ", " + paramType.Resolve().Module.Assembly.FullName; var foundMethodRef = m_FastBufferReader_ReadValue_MethodRefs.TryGetValue(assemblyQualifiedName, out methodRef); diff --git a/com.unity.netcode.gameobjects/Runtime/Core/NetworkBehaviour.cs b/com.unity.netcode.gameobjects/Runtime/Core/NetworkBehaviour.cs index 8289676383..2aff48d93e 100644 --- a/com.unity.netcode.gameobjects/Runtime/Core/NetworkBehaviour.cs +++ b/com.unity.netcode.gameobjects/Runtime/Core/NetworkBehaviour.cs @@ -21,6 +21,7 @@ internal enum __RpcExecStage Client = 2 } + // NetworkBehaviourILPP will override this in derived classes to return the name of the concrete type internal virtual string __getTypeName() => nameof(NetworkBehaviour); @@ -776,6 +777,11 @@ internal void WriteNetworkVariableData(FastBufferWriter writer, ulong targetClie if (canClientRead) { var writePos = writer.Position; + // Note: This value can't be packed because we don't know how large it will be in advance + // we reserve space for it, then write the data, then come back and fill in the space + // to pack here, we'd have to write data to a temporary buffer and copy it in - which + // isn't worth possibly saving one byte if and only if the data is less than 63 bytes long... + // The way we do packing, any value > 63 in a ushort will use the full 2 bytes to represent. writer.WriteValueSafe((ushort)0); var startPos = writer.Position; NetworkVariableFields[j].WriteField(writer); diff --git a/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs b/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs index 6ffbb27fca..6cae419dae 100644 --- a/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs +++ b/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs @@ -2185,14 +2185,11 @@ internal void HandleConnectionApproval(ulong ownerClientId, ConnectionApprovalRe // Generate a SceneObject for the player object to spawn var sceneObject = new NetworkObject.SceneObject { - Header = new NetworkObject.SceneObject.HeaderData - { - IsPlayerObject = true, - OwnerClientId = ownerClientId, - IsSceneObject = false, - HasTransform = true, - Hash = playerPrefabHash, - }, + OwnerClientId = ownerClientId, + IsPlayerObject = true, + IsSceneObject = false, + HasTransform = true, + Hash = playerPrefabHash, TargetClientId = ownerClientId, Transform = new NetworkObject.SceneObject.TransformData { @@ -2309,11 +2306,11 @@ internal void ApprovedPlayerSpawn(ulong clientId, uint playerPrefabHash) { ObjectInfo = ConnectedClients[clientId].PlayerObject.GetMessageSceneObject(clientPair.Key) }; - message.ObjectInfo.Header.Hash = playerPrefabHash; - message.ObjectInfo.Header.IsSceneObject = false; - message.ObjectInfo.Header.HasParent = false; - message.ObjectInfo.Header.IsPlayerObject = true; - message.ObjectInfo.Header.OwnerClientId = clientId; + message.ObjectInfo.Hash = playerPrefabHash; + message.ObjectInfo.IsSceneObject = false; + message.ObjectInfo.HasParent = false; + message.ObjectInfo.IsPlayerObject = true; + message.ObjectInfo.OwnerClientId = clientId; var size = SendMessage(ref message, NetworkDelivery.ReliableFragmentedSequenced, clientPair.Key); NetworkMetrics.TrackObjectSpawnSent(clientPair.Key, ConnectedClients[clientId].PlayerObject, size); } diff --git a/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs b/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs index 8b9d6b5f52..5bd7411bd0 100644 --- a/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs +++ b/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs @@ -1075,19 +1075,43 @@ internal NetworkBehaviour GetNetworkBehaviourAtOrderIndex(ushort index) internal struct SceneObject { - public struct HeaderData : INetworkSerializeByMemcpy + private byte m_BitField; + public uint Hash; + public ulong NetworkObjectId; + public ulong OwnerClientId; + + public bool IsPlayerObject + { + get => ByteUtility.GetBit(m_BitField, 0); + set => ByteUtility.SetBit(ref m_BitField, 0, value); + } + public bool HasParent + { + get => ByteUtility.GetBit(m_BitField, 1); + set => ByteUtility.SetBit(ref m_BitField, 1, value); + } + public bool IsSceneObject { - public ulong NetworkObjectId; - public ulong OwnerClientId; - public uint Hash; + get => ByteUtility.GetBit(m_BitField, 2); + set => ByteUtility.SetBit(ref m_BitField, 2, value); + } + public bool HasTransform + { + get => ByteUtility.GetBit(m_BitField, 3); + set => ByteUtility.SetBit(ref m_BitField, 3, value); + } - public bool IsPlayerObject; - public bool HasParent; - public bool IsSceneObject; - public bool HasTransform; + public bool IsLatestParentSet + { + get => ByteUtility.GetBit(m_BitField, 4); + set => ByteUtility.SetBit(ref m_BitField, 4, value); } - public HeaderData Header; + public bool WorldPositionStays + { + get => ByteUtility.GetBit(m_BitField, 5); + set => ByteUtility.SetBit(ref m_BitField, 5, value); + } //If(Metadata.HasParent) public ulong ParentObjectId; @@ -1103,7 +1127,6 @@ public struct TransformData : INetworkSerializeByMemcpy public TransformData Transform; //If(Metadata.IsReparented) - public bool IsLatestParentSet; //If(IsLatestParentSet) public ulong? LatestParent; @@ -1113,40 +1136,33 @@ public struct TransformData : INetworkSerializeByMemcpy public int NetworkSceneHandle; - public bool WorldPositionStays; - public unsafe void Serialize(FastBufferWriter writer) + public void Serialize(FastBufferWriter writer) { - var writeSize = sizeof(HeaderData); - if (Header.HasParent) - { - writeSize += FastBufferWriter.GetWriteSize(ParentObjectId); - writeSize += FastBufferWriter.GetWriteSize(WorldPositionStays); - writeSize += FastBufferWriter.GetWriteSize(IsLatestParentSet); - writeSize += IsLatestParentSet ? FastBufferWriter.GetWriteSize() : 0; - } - writeSize += Header.HasTransform ? FastBufferWriter.GetWriteSize() : 0; - writeSize += Header.IsSceneObject ? FastBufferWriter.GetWriteSize() : 0; + writer.WriteValueSafe(m_BitField); + writer.WriteValueSafe(Hash); + BytePacker.WriteValueBitPacked(writer, NetworkObjectId); + BytePacker.WriteValueBitPacked(writer, OwnerClientId); - if (!writer.TryBeginWrite(writeSize)) + if (HasParent) { - throw new OverflowException("Could not serialize SceneObject: Out of buffer space."); + BytePacker.WriteValueBitPacked(writer, ParentObjectId); + if (IsLatestParentSet) + { + BytePacker.WriteValueBitPacked(writer, LatestParent.Value); + } } - writer.WriteValue(Header); + var writeSize = 0; + writeSize += HasTransform ? FastBufferWriter.GetWriteSize() : 0; + writeSize += IsSceneObject ? FastBufferWriter.GetWriteSize() : 0; - if (Header.HasParent) + if (!writer.TryBeginWrite(writeSize)) { - writer.WriteValue(ParentObjectId); - writer.WriteValue(WorldPositionStays); - writer.WriteValue(IsLatestParentSet); - if (IsLatestParentSet) - { - writer.WriteValue(LatestParent.Value); - } + throw new OverflowException("Could not serialize SceneObject: Out of buffer space."); } - if (Header.HasTransform) + if (HasTransform) { writer.WriteValue(Transform); } @@ -1156,7 +1172,7 @@ public unsafe void Serialize(FastBufferWriter writer) // handle, it provides us with a unique and persistent "scene prefab asset" instance. // This is only set on in-scene placed NetworkObjects to reduce the over-all packet // sizes for dynamically spawned NetworkObjects. - if (Header.IsSceneObject) + if (IsSceneObject) { writer.WriteValue(OwnerObject.GetSceneOriginHandle()); } @@ -1164,51 +1180,34 @@ public unsafe void Serialize(FastBufferWriter writer) OwnerObject.WriteNetworkVariableData(writer, TargetClientId); } - public unsafe void Deserialize(FastBufferReader reader) + public void Deserialize(FastBufferReader reader) { - if (!reader.TryBeginRead(sizeof(HeaderData))) - { - throw new OverflowException("Could not deserialize SceneObject: Out of buffer space."); - } - reader.ReadValue(out Header); - var readSize = 0; - if (Header.HasParent) + reader.ReadValueSafe(out m_BitField); + reader.ReadValueSafe(out Hash); + ByteUnpacker.ReadValueBitPacked(reader, out NetworkObjectId); + ByteUnpacker.ReadValueBitPacked(reader, out OwnerClientId); + + if (HasParent) { - readSize += FastBufferWriter.GetWriteSize(ParentObjectId); - readSize += FastBufferWriter.GetWriteSize(WorldPositionStays); - readSize += FastBufferWriter.GetWriteSize(IsLatestParentSet); - // We need to read at this point in order to get the IsLatestParentSet value - if (!reader.TryBeginRead(readSize)) + ByteUnpacker.ReadValueBitPacked(reader, out ParentObjectId); + if (IsLatestParentSet) { - throw new OverflowException("Could not deserialize SceneObject: Out of buffer space."); + ByteUnpacker.ReadValueBitPacked(reader, out ulong latestParent); + LatestParent = latestParent; } - - // Read the initial parenting related properties - reader.ReadValue(out ParentObjectId); - reader.ReadValue(out WorldPositionStays); - reader.ReadValue(out IsLatestParentSet); - - // Now calculate the remaining bytes to read - readSize = 0; - readSize += IsLatestParentSet ? FastBufferWriter.GetWriteSize() : 0; } - readSize += Header.HasTransform ? FastBufferWriter.GetWriteSize() : 0; - readSize += Header.IsSceneObject ? FastBufferWriter.GetWriteSize() : 0; + var readSize = 0; + readSize += HasTransform ? FastBufferWriter.GetWriteSize() : 0; + readSize += IsSceneObject ? FastBufferWriter.GetWriteSize() : 0; // Try to begin reading the remaining bytes if (!reader.TryBeginRead(readSize)) { - throw new OverflowException("Could not deserialize SceneObject: Out of buffer space."); + throw new OverflowException("Could not deserialize SceneObject: Reading past the end of the buffer"); } - if (IsLatestParentSet) - { - reader.ReadValueSafe(out ulong latestParent); - LatestParent = latestParent; - } - - if (Header.HasTransform) + if (HasTransform) { reader.ReadValue(out Transform); } @@ -1218,9 +1217,9 @@ public unsafe void Deserialize(FastBufferReader reader) // handle, it provides us with a unique and persistent "scene prefab asset" instance. // Client-side NetworkSceneManagers use this to locate their local instance of the // NetworkObject instance. - if (Header.IsSceneObject) + if (IsSceneObject) { - reader.ReadValueSafe(out NetworkSceneHandle); + reader.ReadValue(out NetworkSceneHandle); } } } @@ -1237,14 +1236,11 @@ internal SceneObject GetMessageSceneObject(ulong targetClientId) { var obj = new SceneObject { - Header = new SceneObject.HeaderData - { - IsPlayerObject = IsPlayerObject, - NetworkObjectId = NetworkObjectId, - OwnerClientId = OwnerClientId, - IsSceneObject = IsSceneObject ?? true, - Hash = HostCheckForGlobalObjectIdHashOverride(), - }, + NetworkObjectId = NetworkObjectId, + OwnerClientId = OwnerClientId, + IsPlayerObject = IsPlayerObject, + IsSceneObject = IsSceneObject ?? true, + Hash = HostCheckForGlobalObjectIdHashOverride(), OwnerObject = this, TargetClientId = targetClientId }; @@ -1256,16 +1252,16 @@ internal SceneObject GetMessageSceneObject(ulong targetClientId) parentNetworkObject = transform.parent.GetComponent(); // In-scene placed NetworkObjects parented under GameObjects with no NetworkObject // should set the has parent flag and preserve the world position stays value - if (parentNetworkObject == null && obj.Header.IsSceneObject) + if (parentNetworkObject == null && obj.IsSceneObject) { - obj.Header.HasParent = true; + obj.HasParent = true; obj.WorldPositionStays = m_CachedWorldPositionStays; } } if (parentNetworkObject != null) { - obj.Header.HasParent = true; + obj.HasParent = true; obj.ParentObjectId = parentNetworkObject.NetworkObjectId; obj.WorldPositionStays = m_CachedWorldPositionStays; var latestParent = GetNetworkParenting(); @@ -1279,12 +1275,12 @@ internal SceneObject GetMessageSceneObject(ulong targetClientId) if (IncludeTransformWhenSpawning == null || IncludeTransformWhenSpawning(OwnerClientId)) { - obj.Header.HasTransform = true; + obj.HasTransform = true; // We start with the default AutoObjectParentSync values to determine which transform space we will // be synchronizing clients with. - var syncRotationPositionLocalSpaceRelative = obj.Header.HasParent && !m_CachedWorldPositionStays; - var syncScaleLocalSpaceRelative = obj.Header.HasParent && !m_CachedWorldPositionStays; + var syncRotationPositionLocalSpaceRelative = obj.HasParent && !m_CachedWorldPositionStays; + var syncScaleLocalSpaceRelative = obj.HasParent && !m_CachedWorldPositionStays; // If auto object synchronization is turned off if (!AutoObjectParentSync) @@ -1294,7 +1290,7 @@ internal SceneObject GetMessageSceneObject(ulong targetClientId) // Scale is special, it synchronizes local space relative if it has a // parent since applying the world space scale under a parent with scale // will result in the improper scale for the child - syncScaleLocalSpaceRelative = obj.Header.HasParent; + syncScaleLocalSpaceRelative = obj.HasParent; } @@ -1333,7 +1329,7 @@ internal static NetworkObject AddSceneObject(in SceneObject sceneObject, FastBuf if (networkObject == null) { // Log the error that the NetworkObject failed to construct - Debug.LogError($"Failed to spawn {nameof(NetworkObject)} for Hash {sceneObject.Header.Hash}."); + Debug.LogError($"Failed to spawn {nameof(NetworkObject)} for Hash {sceneObject.Hash}."); // If we failed to load this NetworkObject, then skip past the network variable data variableData.ReadValueSafe(out ushort varSize); diff --git a/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/ChangeOwnershipMessage.cs b/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/ChangeOwnershipMessage.cs index 19f84bc000..6ab471904c 100644 --- a/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/ChangeOwnershipMessage.cs +++ b/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/ChangeOwnershipMessage.cs @@ -7,7 +7,8 @@ internal struct ChangeOwnershipMessage : INetworkMessage, INetworkSerializeByMem public void Serialize(FastBufferWriter writer) { - writer.WriteValueSafe(this); + BytePacker.WriteValueBitPacked(writer, NetworkObjectId); + BytePacker.WriteValueBitPacked(writer, OwnerClientId); } public bool Deserialize(FastBufferReader reader, ref NetworkContext context) @@ -17,7 +18,8 @@ public bool Deserialize(FastBufferReader reader, ref NetworkContext context) { return false; } - reader.ReadValueSafe(out this); + ByteUnpacker.ReadValueBitPacked(reader, out NetworkObjectId); + ByteUnpacker.ReadValueBitPacked(reader, out OwnerClientId); if (!networkManager.SpawnManager.SpawnedObjects.ContainsKey(NetworkObjectId)) { networkManager.DeferredMessageManager.DeferMessage(IDeferredMessageManager.TriggerType.OnSpawn, NetworkObjectId, reader, ref context); diff --git a/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/ConnectionApprovedMessage.cs b/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/ConnectionApprovedMessage.cs index 0a685a731d..e721334ffa 100644 --- a/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/ConnectionApprovedMessage.cs +++ b/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/ConnectionApprovedMessage.cs @@ -19,8 +19,8 @@ public void Serialize(FastBufferWriter writer) { throw new OverflowException($"Not enough space in the write buffer to serialize {nameof(ConnectionApprovedMessage)}"); } - writer.WriteValue(OwnerClientId); - writer.WriteValue(NetworkTick); + BytePacker.WriteValueBitPacked(writer, OwnerClientId); + BytePacker.WriteValueBitPacked(writer, NetworkTick); uint sceneObjectCount = 0; if (SpawnedObjectsList != null) @@ -39,13 +39,15 @@ public void Serialize(FastBufferWriter writer) ++sceneObjectCount; } } + writer.Seek(pos); - writer.WriteValue(sceneObjectCount); + // Can't pack this value because its space is reserved, so it needs to always use all the reserved space. + writer.WriteValueSafe(sceneObjectCount); writer.Seek(writer.Length); } else { - writer.WriteValue(sceneObjectCount); + writer.WriteValueSafe(sceneObjectCount); } } @@ -57,13 +59,8 @@ public bool Deserialize(FastBufferReader reader, ref NetworkContext context) return false; } - if (!reader.TryBeginRead(sizeof(ulong) + sizeof(int) + sizeof(int))) - { - throw new OverflowException($"Not enough space in the buffer to read {nameof(ConnectionApprovedMessage)}"); - } - - reader.ReadValue(out OwnerClientId); - reader.ReadValue(out NetworkTick); + ByteUnpacker.ReadValueBitPacked(reader, out OwnerClientId); + ByteUnpacker.ReadValueBitPacked(reader, out NetworkTick); m_ReceivedSceneObjectData = reader; return true; } @@ -85,7 +82,7 @@ public void Handle(ref NetworkContext context) if (!networkManager.NetworkConfig.EnableSceneManagement) { networkManager.SpawnManager.DestroySceneObjects(); - m_ReceivedSceneObjectData.ReadValue(out uint sceneObjectCount); + m_ReceivedSceneObjectData.ReadValueSafe(out uint sceneObjectCount); // 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. diff --git a/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/CreateObjectMessage.cs b/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/CreateObjectMessage.cs index 547197da99..f27227cba8 100644 --- a/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/CreateObjectMessage.cs +++ b/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/CreateObjectMessage.cs @@ -21,7 +21,7 @@ public bool Deserialize(FastBufferReader reader, ref NetworkContext context) ObjectInfo.Deserialize(reader); if (!networkManager.NetworkConfig.ForceSamePrefabs && !networkManager.SpawnManager.HasPrefab(ObjectInfo)) { - networkManager.DeferredMessageManager.DeferMessage(IDeferredMessageManager.TriggerType.OnAddPrefab, ObjectInfo.Header.Hash, reader, ref context); + networkManager.DeferredMessageManager.DeferMessage(IDeferredMessageManager.TriggerType.OnAddPrefab, ObjectInfo.Hash, reader, ref context); return false; } m_ReceivedNetworkVariableData = reader; diff --git a/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/DestroyObjectMessage.cs b/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/DestroyObjectMessage.cs index 2730088944..7e13f9cc72 100644 --- a/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/DestroyObjectMessage.cs +++ b/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/DestroyObjectMessage.cs @@ -7,7 +7,8 @@ internal struct DestroyObjectMessage : INetworkMessage, INetworkSerializeByMemcp public void Serialize(FastBufferWriter writer) { - writer.WriteValueSafe(this); + BytePacker.WriteValueBitPacked(writer, NetworkObjectId); + writer.WriteValueSafe(DestroyGameObject); } public bool Deserialize(FastBufferReader reader, ref NetworkContext context) @@ -18,7 +19,8 @@ public bool Deserialize(FastBufferReader reader, ref NetworkContext context) return false; } - reader.ReadValueSafe(out this); + ByteUnpacker.ReadValueBitPacked(reader, out NetworkObjectId); + reader.ReadValueSafe(out DestroyGameObject); if (!networkManager.SpawnManager.SpawnedObjects.TryGetValue(NetworkObjectId, out var networkObject)) { diff --git a/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/NetworkVariableDeltaMessage.cs b/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/NetworkVariableDeltaMessage.cs index 22588216bd..8cb44ad1a2 100644 --- a/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/NetworkVariableDeltaMessage.cs +++ b/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/NetworkVariableDeltaMessage.cs @@ -28,8 +28,8 @@ public void Serialize(FastBufferWriter writer) throw new OverflowException($"Not enough space in the buffer to write {nameof(NetworkVariableDeltaMessage)}"); } - writer.WriteValue(NetworkObjectId); - writer.WriteValue(NetworkBehaviourIndex); + BytePacker.WriteValueBitPacked(writer, NetworkObjectId); + BytePacker.WriteValueBitPacked(writer, NetworkBehaviourIndex); for (int i = 0; i < NetworkBehaviour.NetworkVariableFields.Count; i++) { @@ -38,7 +38,7 @@ public void Serialize(FastBufferWriter writer) // This var does not belong to the currently iterating delivery group. if (NetworkBehaviour.NetworkManager.NetworkConfig.EnsureNetworkVariableLengthSafety) { - writer.WriteValueSafe((ushort)0); + BytePacker.WriteValueBitPacked(writer, (ushort)0); } else { @@ -66,7 +66,7 @@ public void Serialize(FastBufferWriter writer) { if (!shouldWrite) { - BytePacker.WriteValueBitPacked(writer, 0); + BytePacker.WriteValueBitPacked(writer, (ushort)0); } } else @@ -112,13 +112,8 @@ public void Serialize(FastBufferWriter writer) public bool Deserialize(FastBufferReader reader, ref NetworkContext context) { - if (!reader.TryBeginRead(FastBufferWriter.GetWriteSize(NetworkObjectId) + FastBufferWriter.GetWriteSize(NetworkBehaviourIndex))) - { - throw new OverflowException($"Not enough data in the buffer to read {nameof(NetworkVariableDeltaMessage)}"); - } - - reader.ReadValue(out NetworkObjectId); - reader.ReadValue(out NetworkBehaviourIndex); + ByteUnpacker.ReadValueBitPacked(reader, out NetworkObjectId); + ByteUnpacker.ReadValueBitPacked(reader, out NetworkBehaviourIndex); m_ReceivedNetworkVariableData = reader; diff --git a/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/ParentSyncMessage.cs b/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/ParentSyncMessage.cs index 0023b1a66c..4a68d02e7a 100644 --- a/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/ParentSyncMessage.cs +++ b/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/ParentSyncMessage.cs @@ -6,16 +6,30 @@ internal struct ParentSyncMessage : INetworkMessage { public ulong NetworkObjectId; - public bool WorldPositionStays; + private byte m_BitField; + + public bool WorldPositionStays + { + get => ByteUtility.GetBit(m_BitField, 0); + set => ByteUtility.SetBit(ref m_BitField, 0, value); + } //If(Metadata.IsReparented) - public bool IsLatestParentSet; + public bool IsLatestParentSet + { + get => ByteUtility.GetBit(m_BitField, 1); + set => ByteUtility.SetBit(ref m_BitField, 1, value); + } //If(IsLatestParentSet) public ulong? LatestParent; // Is set when the parent should be removed (similar to IsReparented functionality but only for removing the parent) - public bool RemoveParent; + public bool RemoveParent + { + get => ByteUtility.GetBit(m_BitField, 2); + set => ByteUtility.SetBit(ref m_BitField, 2, value); + } // These additional properties are used to synchronize clients with the current position, // rotation, and scale after parenting/de-parenting (world/local space relative). This @@ -27,16 +41,13 @@ internal struct ParentSyncMessage : INetworkMessage public void Serialize(FastBufferWriter writer) { - BytePacker.WriteValuePacked(writer, NetworkObjectId); - writer.WriteValueSafe(RemoveParent); - writer.WriteValueSafe(WorldPositionStays); + BytePacker.WriteValueBitPacked(writer, NetworkObjectId); + writer.WriteValueSafe(m_BitField); if (!RemoveParent) { - writer.WriteValueSafe(IsLatestParentSet); - if (IsLatestParentSet) { - BytePacker.WriteValueBitPacked(writer, (ulong)LatestParent); + BytePacker.WriteValueBitPacked(writer, LatestParent.Value); } } @@ -54,13 +65,10 @@ public bool Deserialize(FastBufferReader reader, ref NetworkContext context) return false; } - ByteUnpacker.ReadValuePacked(reader, out NetworkObjectId); - reader.ReadValueSafe(out RemoveParent); - reader.ReadValueSafe(out WorldPositionStays); + ByteUnpacker.ReadValueBitPacked(reader, out NetworkObjectId); + reader.ReadValueSafe(out m_BitField); if (!RemoveParent) { - reader.ReadValueSafe(out IsLatestParentSet); - if (IsLatestParentSet) { ByteUnpacker.ReadValueBitPacked(reader, out ulong latestParent); diff --git a/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/RpcMessages.cs b/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/RpcMessages.cs index 1ab0492a93..b13db21aa4 100644 --- a/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/RpcMessages.cs +++ b/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/RpcMessages.cs @@ -8,24 +8,17 @@ internal static class RpcMessageHelpers { public static unsafe void Serialize(ref FastBufferWriter writer, ref RpcMetadata metadata, ref FastBufferWriter payload) { - if (!writer.TryBeginWrite(FastBufferWriter.GetWriteSize() + payload.Length)) - { - throw new OverflowException("Not enough space in the buffer to store RPC data."); - } - - writer.WriteValue(metadata); - writer.WriteBytes(payload.GetUnsafePtr(), payload.Length); + BytePacker.WriteValueBitPacked(writer, metadata.NetworkObjectId); + BytePacker.WriteValueBitPacked(writer, metadata.NetworkBehaviourId); + BytePacker.WriteValueBitPacked(writer, metadata.NetworkRpcMethodId); + writer.WriteBytesSafe(payload.GetUnsafePtr(), payload.Length); } public static unsafe bool Deserialize(ref FastBufferReader reader, ref NetworkContext context, ref RpcMetadata metadata, ref FastBufferReader payload) { - int metadataSize = FastBufferWriter.GetWriteSize(); - if (!reader.TryBeginRead(metadataSize)) - { - throw new InvalidOperationException("Not enough data in the buffer to read RPC meta."); - } - - reader.ReadValue(out metadata); + ByteUnpacker.ReadValueBitPacked(reader, out metadata.NetworkObjectId); + ByteUnpacker.ReadValueBitPacked(reader, out metadata.NetworkBehaviourId); + ByteUnpacker.ReadValueBitPacked(reader, out metadata.NetworkRpcMethodId); var networkManager = (NetworkManager)context.SystemOwner; if (!networkManager.SpawnManager.SpawnedObjects.ContainsKey(metadata.NetworkObjectId)) @@ -46,7 +39,7 @@ public static unsafe bool Deserialize(ref FastBufferReader reader, ref NetworkCo return false; } - payload = new FastBufferReader(reader.GetUnsafePtr() + metadataSize, Allocator.None, reader.Length - metadataSize); + payload = new FastBufferReader(reader.GetUnsafePtrAtCurrentPosition(), Allocator.None, reader.Length - reader.Position); #if DEVELOPMENT_BUILD || UNITY_EDITOR if (NetworkManager.__rpc_name_table.TryGetValue(metadata.NetworkRpcMethodId, out var rpcMethodName)) diff --git a/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/TimeSyncMessage.cs b/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/TimeSyncMessage.cs index 613bb90f75..24fcbc0563 100644 --- a/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/TimeSyncMessage.cs +++ b/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/TimeSyncMessage.cs @@ -6,7 +6,7 @@ internal struct TimeSyncMessage : INetworkMessage, INetworkSerializeByMemcpy public void Serialize(FastBufferWriter writer) { - writer.WriteValueSafe(this); + BytePacker.WriteValueBitPacked(writer, Tick); } public bool Deserialize(FastBufferReader reader, ref NetworkContext context) @@ -16,7 +16,7 @@ public bool Deserialize(FastBufferReader reader, ref NetworkContext context) { return false; } - reader.ReadValueSafe(out this); + ByteUnpacker.ReadValueBitPacked(reader, out Tick); return true; } diff --git a/com.unity.netcode.gameobjects/Runtime/Messaging/MessagingSystem.cs b/com.unity.netcode.gameobjects/Runtime/Messaging/MessagingSystem.cs index 82917a1fe9..f5d379dd02 100644 --- a/com.unity.netcode.gameobjects/Runtime/Messaging/MessagingSystem.cs +++ b/com.unity.netcode.gameobjects/Runtime/Messaging/MessagingSystem.cs @@ -74,7 +74,7 @@ internal uint GetMessageType(Type t) } public const int NON_FRAGMENTED_MESSAGE_MAX_SIZE = 1300; - public const int FRAGMENTED_MESSAGE_MAX_SIZE = BytePacker.BitPackedIntMax; + public const int FRAGMENTED_MESSAGE_MAX_SIZE = int.MaxValue; internal struct MessageWithHandler { diff --git a/com.unity.netcode.gameobjects/Runtime/NetworkVariable/NetworkVariableSerialization.cs b/com.unity.netcode.gameobjects/Runtime/NetworkVariable/NetworkVariableSerialization.cs index 83c4de2de7..463f50244d 100644 --- a/com.unity.netcode.gameobjects/Runtime/NetworkVariable/NetworkVariableSerialization.cs +++ b/com.unity.netcode.gameobjects/Runtime/NetworkVariable/NetworkVariableSerialization.cs @@ -1,6 +1,8 @@ using System; using Unity.Collections; using Unity.Collections.LowLevel.Unsafe; +using UnityEditor; +using UnityEngine; namespace Unity.Netcode { @@ -20,6 +22,96 @@ internal interface INetworkVariableSerializer public void Read(FastBufferReader reader, ref T value); } + /// + /// Packing serializer for shorts + /// + internal class ShortSerializer : INetworkVariableSerializer + { + public void Write(FastBufferWriter writer, ref short value) + { + BytePacker.WriteValueBitPacked(writer, value); + } + public void Read(FastBufferReader reader, ref short value) + { + ByteUnpacker.ReadValueBitPacked(reader, out value); + } + } + + /// + /// Packing serializer for shorts + /// + internal class UshortSerializer : INetworkVariableSerializer + { + public void Write(FastBufferWriter writer, ref ushort value) + { + BytePacker.WriteValueBitPacked(writer, value); + } + public void Read(FastBufferReader reader, ref ushort value) + { + ByteUnpacker.ReadValueBitPacked(reader, out value); + } + } + + /// + /// Packing serializer for ints + /// + internal class IntSerializer : INetworkVariableSerializer + { + public void Write(FastBufferWriter writer, ref int value) + { + BytePacker.WriteValueBitPacked(writer, value); + } + public void Read(FastBufferReader reader, ref int value) + { + ByteUnpacker.ReadValueBitPacked(reader, out value); + } + } + + /// + /// Packing serializer for ints + /// + internal class UintSerializer : INetworkVariableSerializer + { + public void Write(FastBufferWriter writer, ref uint value) + { + BytePacker.WriteValueBitPacked(writer, value); + } + public void Read(FastBufferReader reader, ref uint value) + { + ByteUnpacker.ReadValueBitPacked(reader, out value); + } + } + + /// + /// Packing serializer for longs + /// + internal class LongSerializer : INetworkVariableSerializer + { + public void Write(FastBufferWriter writer, ref long value) + { + BytePacker.WriteValueBitPacked(writer, value); + } + public void Read(FastBufferReader reader, ref long value) + { + ByteUnpacker.ReadValueBitPacked(reader, out value); + } + } + + /// + /// Packing serializer for longs + /// + internal class UlongSerializer : INetworkVariableSerializer + { + public void Write(FastBufferWriter writer, ref ulong value) + { + BytePacker.WriteValueBitPacked(writer, value); + } + public void Read(FastBufferReader reader, ref ulong value) + { + ByteUnpacker.ReadValueBitPacked(reader, out value); + } + } + /// /// Basic serializer for unmanaged types. /// This covers primitives, built-in unity types, and IForceSerializeByMemcpy @@ -188,6 +280,26 @@ public void Read(FastBufferReader reader, ref T value) /// public static class NetworkVariableSerializationTypes { + [RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.AfterAssembliesLoaded)] +#if UNITY_EDITOR + [InitializeOnLoadMethod] +#endif + internal static void InitializeIntegerSerialization() + { + NetworkVariableSerialization.Serializer = new ShortSerializer(); + NetworkVariableSerialization.AreEqual = NetworkVariableSerialization.ValueEquals; + NetworkVariableSerialization.Serializer = new UshortSerializer(); + NetworkVariableSerialization.AreEqual = NetworkVariableSerialization.ValueEquals; + NetworkVariableSerialization.Serializer = new IntSerializer(); + NetworkVariableSerialization.AreEqual = NetworkVariableSerialization.ValueEquals; + NetworkVariableSerialization.Serializer = new UintSerializer(); + NetworkVariableSerialization.AreEqual = NetworkVariableSerialization.ValueEquals; + NetworkVariableSerialization.Serializer = new LongSerializer(); + NetworkVariableSerialization.AreEqual = NetworkVariableSerialization.ValueEquals; + NetworkVariableSerialization.Serializer = new UlongSerializer(); + NetworkVariableSerialization.AreEqual = NetworkVariableSerialization.ValueEquals; + } + /// /// Registeres an unmanaged type that will be serialized by a direct memcpy into a buffer /// diff --git a/com.unity.netcode.gameobjects/Runtime/SceneManagement/SceneEventData.cs b/com.unity.netcode.gameobjects/Runtime/SceneManagement/SceneEventData.cs index 222e437ca2..0b8936d332 100644 --- a/com.unity.netcode.gameobjects/Runtime/SceneManagement/SceneEventData.cs +++ b/com.unity.netcode.gameobjects/Runtime/SceneManagement/SceneEventData.cs @@ -380,7 +380,7 @@ internal void Serialize(FastBufferWriter writer) writer.WriteValueSafe(SceneEventType); // Write the scene loading mode - writer.WriteValueSafe(LoadSceneMode); + writer.WriteValueSafe((byte)LoadSceneMode); // Write the scene event progress Guid if (SceneEventType != SceneEventType.Synchronize) @@ -533,7 +533,8 @@ internal void SerializeScenePlacedObjects(FastBufferWriter writer) internal void Deserialize(FastBufferReader reader) { reader.ReadValueSafe(out SceneEventType); - reader.ReadValueSafe(out LoadSceneMode); + reader.ReadValueSafe(out byte loadSceneMode); + LoadSceneMode = (LoadSceneMode)loadSceneMode; if (SceneEventType != SceneEventType.Synchronize) { diff --git a/com.unity.netcode.gameobjects/Runtime/Serialization/BytePacker.cs b/com.unity.netcode.gameobjects/Runtime/Serialization/BytePacker.cs index 737faf7b49..58b7ea1e33 100644 --- a/com.unity.netcode.gameobjects/Runtime/Serialization/BytePacker.cs +++ b/com.unity.netcode.gameobjects/Runtime/Serialization/BytePacker.cs @@ -50,7 +50,7 @@ public static unsafe void WriteValuePacked(FastBufferWriter writer, TEnum [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void WriteValuePacked(FastBufferWriter writer, float value) { - WriteUInt32Packed(writer, ToUint(value)); + WriteValueBitPacked(writer, ToUint(value)); } /// @@ -61,7 +61,7 @@ public static void WriteValuePacked(FastBufferWriter writer, float value) [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void WriteValuePacked(FastBufferWriter writer, double value) { - WriteUInt64Packed(writer, ToUlong(value)); + WriteValueBitPacked(writer, ToUlong(value)); } /// @@ -98,7 +98,7 @@ public static void WriteValuePacked(FastBufferWriter writer, double value) /// The writer to write to /// Value to write [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void WriteValuePacked(FastBufferWriter writer, short value) => WriteUInt32Packed(writer, (ushort)Arithmetic.ZigZagEncode(value)); + public static void WriteValuePacked(FastBufferWriter writer, short value) => WriteValueBitPacked(writer, value); /// /// Write an unsigned short (UInt16) as a varint to the buffer. @@ -109,7 +109,7 @@ public static void WriteValuePacked(FastBufferWriter writer, double value) /// The writer to write to /// Value to write [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void WriteValuePacked(FastBufferWriter writer, ushort value) => WriteUInt32Packed(writer, value); + public static void WriteValuePacked(FastBufferWriter writer, ushort value) => WriteValueBitPacked(writer, value); /// /// Write a two-byte character as a varint to the buffer. @@ -120,7 +120,7 @@ public static void WriteValuePacked(FastBufferWriter writer, double value) /// The writer to write to /// Value to write [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void WriteValuePacked(FastBufferWriter writer, char c) => WriteUInt32Packed(writer, c); + public static void WriteValuePacked(FastBufferWriter writer, char c) => WriteValueBitPacked(writer, c); /// /// Write a signed int (Int32) as a ZigZag encoded varint to the buffer. @@ -128,7 +128,7 @@ public static void WriteValuePacked(FastBufferWriter writer, double value) /// The writer to write to /// Value to write [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void WriteValuePacked(FastBufferWriter writer, int value) => WriteUInt32Packed(writer, (uint)Arithmetic.ZigZagEncode(value)); + public static void WriteValuePacked(FastBufferWriter writer, int value) => WriteValueBitPacked(writer, value); /// /// Write an unsigned int (UInt32) to the buffer. @@ -136,7 +136,7 @@ public static void WriteValuePacked(FastBufferWriter writer, double value) /// The writer to write to /// Value to write [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void WriteValuePacked(FastBufferWriter writer, uint value) => WriteUInt32Packed(writer, value); + public static void WriteValuePacked(FastBufferWriter writer, uint value) => WriteValueBitPacked(writer, value); /// /// Write an unsigned long (UInt64) to the buffer. @@ -144,7 +144,7 @@ public static void WriteValuePacked(FastBufferWriter writer, double value) /// The writer to write to /// Value to write [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void WriteValuePacked(FastBufferWriter writer, ulong value) => WriteUInt64Packed(writer, value); + public static void WriteValuePacked(FastBufferWriter writer, ulong value) => WriteValueBitPacked(writer, value); /// /// Write a signed long (Int64) as a ZigZag encoded varint to the buffer. @@ -152,7 +152,7 @@ public static void WriteValuePacked(FastBufferWriter writer, double value) /// The writer to write to /// Value to write [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void WriteValuePacked(FastBufferWriter writer, long value) => WriteUInt64Packed(writer, Arithmetic.ZigZagEncode(value)); + public static void WriteValuePacked(FastBufferWriter writer, long value) => WriteValueBitPacked(writer, value); /// /// Convenience method that writes two packed Vector3 from the ray to the buffer @@ -282,231 +282,183 @@ public static void WriteValuePacked(FastBufferWriter writer, string s) [MethodImpl(MethodImplOptions.AggressiveInlining)] public void WriteValueBitPacked(FastBufferWriter writer, T value) where T: unmanaged => writer.WriteValueSafe(value); #else - /// - /// Maximum serializable value for a BitPacked ushort (minimum for unsigned is 0) + /// Obsolete value that no longer carries meaning. Do not use. /// public const ushort BitPackedUshortMax = (1 << 15) - 1; /// - /// Maximum serializable value for a BitPacked short + /// Obsolete value that no longer carries meaning. Do not use. /// public const short BitPackedShortMax = (1 << 14) - 1; /// - /// Minimum serializable value size for a BitPacked ushort + /// Obsolete value that no longer carries meaning. Do not use. /// public const short BitPackedShortMin = -(1 << 14); /// - /// Maximum serializable value for a BitPacked uint (minimum for unsigned is 0) + /// Obsolete value that no longer carries meaning. Do not use. /// public const uint BitPackedUintMax = (1 << 30) - 1; /// - /// Maximum serializable value for a BitPacked int + /// Obsolete value that no longer carries meaning. Do not use. /// public const int BitPackedIntMax = (1 << 29) - 1; /// - /// Minimum serializable value size for a BitPacked int + /// Obsolete value that no longer carries meaning. Do not use. /// public const int BitPackedIntMin = -(1 << 29); /// - /// Maximum serializable value for a BitPacked ulong (minimum for unsigned is 0) + /// Obsolete value that no longer carries meaning. Do not use. /// public const ulong BitPackedULongMax = (1L << 61) - 1; /// - /// Maximum serializable value for a BitPacked long + /// Obsolete value that no longer carries meaning. Do not use. /// public const long BitPackedLongMax = (1L << 60) - 1; /// - /// Minimum serializable value size for a BitPacked long + /// Obsolete value that no longer carries meaning. Do not use. /// public const long BitPackedLongMin = -(1L << 60); /// - /// 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. + /// Writes a 16-bit signed short to the buffer in a bit-encoded packed format. + /// Zig-zag encoding is used to move the sign bit to the least significant bit, so that negative values + /// are still able to be compressed. + /// The first two bits indicate whether the value is 1, 2, or 3 bytes. + /// If the value uses 14 bits or less, the remaining 14 bits contain the value. + /// For performance, reasons, if the value is 15 bits or more, there will be six 0 bits, followed + /// by the original unmodified 16-bit value in the next 2 bytes. /// /// The writer to write to /// The value to pack public static void WriteValueBitPacked(FastBufferWriter writer, short value) => WriteValueBitPacked(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. + /// Writes a 16-bit unsigned short to the buffer in a bit-encoded packed format. + /// The first two bits indicate whether the value is 1, 2, or 3 bytes. + /// If the value uses 14 bits or less, the remaining 14 bits contain the value. + /// For performance, reasons, if the value is 15 bits or more, there will be six 0 bits, followed + /// by the original unmodified 16-bit value in the next 2 bytes. /// /// The writer to write to /// The value to pack public static void WriteValueBitPacked(FastBufferWriter writer, ushort value) { -#if DEVELOPMENT_BUILD || UNITY_EDITOR - if (value >= BitPackedUshortMax) + if (value > (1 << 14) - 1) { - throw new ArgumentException("BitPacked ushorts must be <= 15 bits"); - } -#endif - - if (value <= 0b0111_1111) - { - if (!writer.TryBeginWriteInternal(1)) + if (!writer.TryBeginWriteInternal(3)) { throw new OverflowException("Writing past the end of the buffer"); } - writer.WriteByte((byte)(value << 1)); + writer.WriteByte(3); + writer.WriteValue(value); return; } - if (!writer.TryBeginWriteInternal(2)) + value <<= 2; + var numBytes = BitCounter.GetUsedByteCount(value); + if (!writer.TryBeginWriteInternal(numBytes)) { throw new OverflowException("Writing past the end of the buffer"); } - writer.WriteValue((ushort)((value << 1) | 0b1)); + writer.WritePartialValue(value | (ushort)(numBytes), numBytes); } /// - /// 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. + /// Writes a 32-bit signed int to the buffer in a bit-encoded packed format. + /// Zig-zag encoding is used to move the sign bit to the least significant bit, so that negative values + /// are still able to be compressed. + /// The first three bits indicate whether the value is 1, 2, 3, 4, or 5 bytes. + /// If the value uses 29 bits or less, the remaining 29 bits contain the value. + /// For performance, reasons, if the value is 30 bits or more, there will be five 0 bits, followed + /// by the original unmodified 32-bit value in the next 4 bytes. /// /// The writer to write to /// The value to pack public static void WriteValueBitPacked(FastBufferWriter writer, int value) => WriteValueBitPacked(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. + /// Writes a 32-bit unsigned int to the buffer in a bit-encoded packed format. + /// The first three bits indicate whether the value is 1, 2, 3, 4, or 5 bytes. + /// If the value uses 29 bits or less, the remaining 29 bits contain the value. + /// For performance, reasons, if the value is 30 bits or more, there will be five 0 bits, followed + /// by the original unmodified 32-bit value in the next 4 bytes. /// /// The writer to write to /// The value to pack public static void WriteValueBitPacked(FastBufferWriter writer, uint value) { -#if DEVELOPMENT_BUILD || UNITY_EDITOR - if (value > BitPackedUintMax) + if (value > (1 << 29) - 1) { - throw new ArgumentException("BitPacked uints must be <= 30 bits"); + if (!writer.TryBeginWriteInternal(5)) + { + throw new OverflowException("Writing past the end of the buffer"); + } + writer.WriteByte(5); + writer.WriteValue(value); + return; } -#endif - value <<= 2; + + 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); + writer.WritePartialValue(value | (uint)(numBytes), 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. + /// Writes a 64-bit signed long to the buffer in a bit-encoded packed format. + /// Zig-zag encoding is used to move the sign bit to the least significant bit, so that negative values + /// are still able to be compressed. + /// The first four bits indicate whether the value is 1, 2, 3, 4, 5, 6, 7, 8, or 9 bytes. + /// If the value uses 60 bits or less, the remaining 60 bits contain the value. + /// For performance, reasons, if the value is 61 bits or more, there will be four 0 bits, followed + /// by the original unmodified 64-bit value in the next 8 bytes. /// /// The writer to write to /// The value to pack public static void WriteValueBitPacked(FastBufferWriter writer, long value) => WriteValueBitPacked(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. + /// Writes a 64-bit unsigned long to the buffer in a bit-encoded packed format. + /// The first four bits indicate whether the value is 1, 2, 3, 4, 5, 6, 7, 8, or 9 bytes. + /// If the value uses 60 bits or less, the remaining 60 bits contain the value. + /// For performance, reasons, if the value is 61 bits or more, there will be four 0 bits, followed + /// by the original unmodified 64-bit value in the next 8 bytes. /// /// The writer to write to /// The value to pack public static void WriteValueBitPacked(FastBufferWriter writer, ulong value) { -#if DEVELOPMENT_BUILD || UNITY_EDITOR - if (value > BitPackedULongMax) + if (value > (1L << 60) - 1) { - throw new ArgumentException("BitPacked ulongs must be <= 61 bits"); + if (!writer.TryBeginWriteInternal(9)) + { + throw new OverflowException("Writing past the end of the buffer"); + } + writer.WriteByte(9); + writer.WriteValue(value); + return; } -#endif - value <<= 3; + + value <<= 4; 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); + writer.WritePartialValue(value | (uint)(numBytes), numBytes); } #endif - - private static void WriteUInt64Packed(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(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 { diff --git a/com.unity.netcode.gameobjects/Runtime/Serialization/ByteUnpacker.cs b/com.unity.netcode.gameobjects/Runtime/Serialization/ByteUnpacker.cs index a84a0d97bc..2d2cd88716 100644 --- a/com.unity.netcode.gameobjects/Runtime/Serialization/ByteUnpacker.cs +++ b/com.unity.netcode.gameobjects/Runtime/Serialization/ByteUnpacker.cs @@ -11,7 +11,6 @@ namespace Unity.Netcode /// public static class ByteUnpacker { - #if UNITY_NETCODE_DEBUG_NO_PACKING [MethodImpl(MethodImplOptions.AggressiveInlining)] @@ -58,7 +57,7 @@ public static unsafe void ReadValuePacked(FastBufferReader reader, out TE [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void ReadValuePacked(FastBufferReader reader, out float value) { - ReadUInt32Packed(reader, out uint asUInt); + ReadValueBitPacked(reader, out uint asUInt); value = ToSingle(asUInt); } @@ -70,7 +69,7 @@ public static void ReadValuePacked(FastBufferReader reader, out float value) [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void ReadValuePacked(FastBufferReader reader, out double value) { - ReadUInt64Packed(reader, out ulong asULong); + ReadValueBitPacked(reader, out ulong asULong); value = ToDouble(asULong); } @@ -109,11 +108,7 @@ public static void ReadValuePacked(FastBufferReader reader, out sbyte value) /// The reader to read from /// Value to read [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void ReadValuePacked(FastBufferReader reader, out short value) - { - ReadUInt32Packed(reader, out uint readValue); - value = (short)Arithmetic.ZigZagDecode(readValue); - } + public static void ReadValuePacked(FastBufferReader reader, out short value) => ReadValueBitPacked(reader, out value); /// /// Read an unsigned short (UInt16) as a varint from the stream. @@ -121,11 +116,7 @@ public static void ReadValuePacked(FastBufferReader reader, out short value) /// The reader to read from /// Value to read [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void ReadValuePacked(FastBufferReader reader, out ushort value) - { - ReadUInt32Packed(reader, out uint readValue); - value = (ushort)readValue; - } + public static void ReadValuePacked(FastBufferReader reader, out ushort value) => ReadValueBitPacked(reader, out value); /// /// Read a two-byte character as a varint from the stream. @@ -135,7 +126,7 @@ public static void ReadValuePacked(FastBufferReader reader, out ushort value) [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void ReadValuePacked(FastBufferReader reader, out char c) { - ReadUInt32Packed(reader, out uint readValue); + ReadValueBitPacked(reader, out ushort readValue); c = (char)readValue; } @@ -145,11 +136,7 @@ public static void ReadValuePacked(FastBufferReader reader, out char c) /// The reader to read from /// Value to read [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void ReadValuePacked(FastBufferReader reader, out int value) - { - ReadUInt32Packed(reader, out uint readValue); - value = (int)Arithmetic.ZigZagDecode(readValue); - } + public static void ReadValuePacked(FastBufferReader reader, out int value) => ReadValueBitPacked(reader, out value); /// /// Read an unsigned int (UInt32) from the stream. @@ -157,7 +144,7 @@ public static void ReadValuePacked(FastBufferReader reader, out int value) /// The reader to read from /// Value to read [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void ReadValuePacked(FastBufferReader reader, out uint value) => ReadUInt32Packed(reader, out value); + public static void ReadValuePacked(FastBufferReader reader, out uint value) => ReadValueBitPacked(reader, out value); /// /// Read an unsigned long (UInt64) from the stream. @@ -165,7 +152,7 @@ public static void ReadValuePacked(FastBufferReader reader, out int value) /// The reader to read from /// Value to read [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void ReadValuePacked(FastBufferReader reader, out ulong value) => ReadUInt64Packed(reader, out value); + public static void ReadValuePacked(FastBufferReader reader, out ulong value) => ReadValueBitPacked(reader, out value); /// /// Read a signed long (Int64) as a ZigZag encoded varint from the stream. @@ -175,8 +162,7 @@ public static void ReadValuePacked(FastBufferReader reader, out int value) [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void ReadValuePacked(FastBufferReader reader, out long value) { - ReadUInt64Packed(reader, out ulong readValue); - value = Arithmetic.ZigZagDecode(readValue); + ReadValueBitPacked(reader, out value); } /// @@ -341,7 +327,9 @@ public static unsafe void ReadValueBitPacked(FastBufferReader reader, out ushort ushort returnValue = 0; byte* ptr = ((byte*)&returnValue); byte* data = reader.GetUnsafePtrAtCurrentPosition(); - int numBytes = (data[0] & 0b1) + 1; + // Mask out the first two bits - they contain the total byte count + // (1, 2, or 3) + int numBytes = (data[0] & 0b11); if (!reader.TryBeginReadInternal(numBytes)) { throw new OverflowException("Reading past the end of the buffer"); @@ -350,17 +338,23 @@ public static unsafe void ReadValueBitPacked(FastBufferReader reader, out ushort switch (numBytes) { case 1: - *ptr = *data; + ptr[0] = data[0]; break; case 2: - *ptr = *data; - *(ptr + 1) = *(data + 1); + ptr[0] = data[0]; + ptr[1] = data[1]; break; + case 3: + // First byte contains no data, it's just a marker. The data is in the remaining two bytes. + ptr[0] = data[1]; + ptr[1] = data[2]; + value = returnValue; + return; default: throw new InvalidOperationException("Could not read bit-packed value: impossible byte count"); } - value = (ushort)(returnValue >> 1); + value = (ushort)(returnValue >> 2); } /// @@ -386,7 +380,8 @@ public static unsafe void ReadValueBitPacked(FastBufferReader reader, out uint v uint returnValue = 0; byte* ptr = ((byte*)&returnValue); byte* data = reader.GetUnsafePtrAtCurrentPosition(); - int numBytes = (data[0] & 0b11) + 1; + // Mask out the first three bits - they contain the total byte count (1-5) + int numBytes = (data[0] & 0b111); if (!reader.TryBeginReadInternal(numBytes)) { throw new OverflowException("Reading past the end of the buffer"); @@ -395,26 +390,34 @@ public static unsafe void ReadValueBitPacked(FastBufferReader reader, out uint v switch (numBytes) { case 1: - *ptr = *data; + ptr[0] = data[0]; break; case 2: - *ptr = *data; - *(ptr + 1) = *(data + 1); + ptr[0] = data[0]; + ptr[1] = data[1]; break; case 3: - *ptr = *data; - *(ptr + 1) = *(data + 1); - *(ptr + 2) = *(data + 2); + ptr[0] = data[0]; + 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); + ptr[0] = data[0]; + ptr[1] = data[1]; + ptr[2] = data[2]; + ptr[3] = data[3]; break; + case 5: + // First byte contains no data, it's just a marker. The data is in the remaining two bytes. + ptr[0] = data[1]; + ptr[1] = data[2]; + ptr[2] = data[3]; + ptr[3] = data[4]; + value = returnValue; + return; } - value = returnValue >> 2; + value = returnValue >> 3; } /// @@ -440,7 +443,8 @@ public static unsafe void ReadValueBitPacked(FastBufferReader reader, out ulong ulong returnValue = 0; byte* ptr = ((byte*)&returnValue); byte* data = reader.GetUnsafePtrAtCurrentPosition(); - int numBytes = (data[0] & 0b111) + 1; + // Mask out the first four bits - they contain the total byte count (1-9) + int numBytes = (data[0] & 0b1111); if (!reader.TryBeginReadInternal(numBytes)) { throw new OverflowException("Reading past the end of the buffer"); @@ -449,109 +453,74 @@ public static unsafe void ReadValueBitPacked(FastBufferReader reader, out ulong switch (numBytes) { case 1: - *ptr = *data; + ptr[0] = data[0]; break; case 2: - *ptr = *data; - *(ptr + 1) = *(data + 1); + ptr[0] = data[0]; + ptr[1] = data[1]; break; case 3: - *ptr = *data; - *(ptr + 1) = *(data + 1); - *(ptr + 2) = *(data + 2); + ptr[0] = data[0]; + 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); + ptr[0] = data[0]; + 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); + ptr[0] = data[0]; + 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); + ptr[0] = data[0]; + 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); + ptr[0] = data[0]; + 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); + ptr[0] = data[0]; + 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; + case 9: + // First byte contains no data, it's just a marker. The data is in the remaining two bytes. + ptr[0] = data[1]; + ptr[1] = data[2]; + ptr[2] = data[3]; + ptr[3] = data[4]; + ptr[4] = data[5]; + ptr[5] = data[6]; + ptr[6] = data[7]; + ptr[7] = data[8]; + value = returnValue; + return; } - value = returnValue >> 3; + value = returnValue >> 4; } #endif - private static void ReadUInt64Packed(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(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 diff --git a/com.unity.netcode.gameobjects/Runtime/Serialization/ByteUtility.cs b/com.unity.netcode.gameobjects/Runtime/Serialization/ByteUtility.cs new file mode 100644 index 0000000000..45ad1c7e3d --- /dev/null +++ b/com.unity.netcode.gameobjects/Runtime/Serialization/ByteUtility.cs @@ -0,0 +1,58 @@ +using System.Runtime.CompilerServices; + +namespace Unity.Netcode +{ + internal class ByteUtility + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static unsafe byte ToByte(bool b) => *(byte*)&b; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static bool GetBit(byte bitField, ushort bitPosition) + { + return (bitField & (1 << bitPosition)) != 0; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static void SetBit(ref byte bitField, ushort bitPosition, bool value) + { + bitField = (byte)((bitField & ~(1 << bitPosition)) | (ToByte(value) << bitPosition)); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static bool GetBit(ushort bitField, ushort bitPosition) + { + return (bitField & (1 << bitPosition)) != 0; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static void SetBit(ref ushort bitField, ushort bitPosition, bool value) + { + bitField = (ushort)((bitField & ~(1 << bitPosition)) | (ToByte(value) << bitPosition)); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static bool GetBit(uint bitField, ushort bitPosition) + { + return (bitField & (1 << bitPosition)) != 0; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static void SetBit(ref uint bitField, ushort bitPosition, bool value) + { + bitField = (uint)((bitField & ~(1 << bitPosition)) | ((uint)ToByte(value) << bitPosition)); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static bool GetBit(ulong bitField, ushort bitPosition) + { + return (bitField & (ulong)(1 << bitPosition)) != 0; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static void SetBit(ref ulong bitField, ushort bitPosition, bool value) + { + bitField = ((bitField & (ulong)~(1 << bitPosition)) | ((ulong)ToByte(value) << bitPosition)); + } + } +} diff --git a/com.unity.netcode.gameobjects/Runtime/Serialization/ByteUtility.cs.meta b/com.unity.netcode.gameobjects/Runtime/Serialization/ByteUtility.cs.meta new file mode 100644 index 0000000000..972bc25eb1 --- /dev/null +++ b/com.unity.netcode.gameobjects/Runtime/Serialization/ByteUtility.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 25bb0dd7157c423b8cfe0ecf06e15ae5 +timeCreated: 1666711082 \ No newline at end of file diff --git a/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkSpawnManager.cs b/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkSpawnManager.cs index 8e21e532c0..945d6247e1 100644 --- a/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkSpawnManager.cs +++ b/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkSpawnManager.cs @@ -291,13 +291,13 @@ internal void ChangeOwnership(NetworkObject networkObject, ulong clientId) internal bool HasPrefab(NetworkObject.SceneObject sceneObject) { - if (!NetworkManager.NetworkConfig.EnableSceneManagement || !sceneObject.Header.IsSceneObject) + if (!NetworkManager.NetworkConfig.EnableSceneManagement || !sceneObject.IsSceneObject) { - if (NetworkManager.PrefabHandler.ContainsHandler(sceneObject.Header.Hash)) + if (NetworkManager.PrefabHandler.ContainsHandler(sceneObject.Hash)) { return true; } - if (NetworkManager.NetworkConfig.NetworkPrefabOverrideLinks.TryGetValue(sceneObject.Header.Hash, out var networkPrefab)) + if (NetworkManager.NetworkConfig.NetworkPrefabOverrideLinks.TryGetValue(sceneObject.Hash, out var networkPrefab)) { switch (networkPrefab.Override) { @@ -312,7 +312,7 @@ internal bool HasPrefab(NetworkObject.SceneObject sceneObject) return false; } - var networkObject = NetworkManager.SceneManager.GetSceneRelativeInSceneNetworkObject(sceneObject.Header.Hash, sceneObject.NetworkSceneHandle); + var networkObject = NetworkManager.SceneManager.GetSceneRelativeInSceneNetworkObject(sceneObject.Hash, sceneObject.NetworkSceneHandle); return networkObject != null; } @@ -326,22 +326,22 @@ internal bool HasPrefab(NetworkObject.SceneObject sceneObject) internal NetworkObject CreateLocalNetworkObject(NetworkObject.SceneObject sceneObject) { NetworkObject networkObject = null; - var globalObjectIdHash = sceneObject.Header.Hash; - var position = sceneObject.Header.HasTransform ? sceneObject.Transform.Position : default; - var rotation = sceneObject.Header.HasTransform ? sceneObject.Transform.Rotation : default; - var scale = sceneObject.Header.HasTransform ? sceneObject.Transform.Scale : default; - var parentNetworkId = sceneObject.Header.HasParent ? sceneObject.ParentObjectId : default; - var worldPositionStays = sceneObject.Header.HasParent ? sceneObject.WorldPositionStays : true; + var globalObjectIdHash = sceneObject.Hash; + var position = sceneObject.HasTransform ? sceneObject.Transform.Position : default; + var rotation = sceneObject.HasTransform ? sceneObject.Transform.Rotation : default; + var scale = sceneObject.HasTransform ? sceneObject.Transform.Scale : default; + var parentNetworkId = sceneObject.HasParent ? sceneObject.ParentObjectId : default; + var worldPositionStays = (!sceneObject.HasParent) || sceneObject.WorldPositionStays; var isSpawnedByPrefabHandler = false; // If scene management is disabled or the NetworkObject was dynamically spawned - if (!NetworkManager.NetworkConfig.EnableSceneManagement || !sceneObject.Header.IsSceneObject) + if (!NetworkManager.NetworkConfig.EnableSceneManagement || !sceneObject.IsSceneObject) { // If the prefab hash has a registered INetworkPrefabInstanceHandler derived class if (NetworkManager.PrefabHandler.ContainsHandler(globalObjectIdHash)) { // Let the handler spawn the NetworkObject - networkObject = NetworkManager.PrefabHandler.HandleNetworkPrefabSpawn(globalObjectIdHash, sceneObject.Header.OwnerClientId, position, rotation); + networkObject = NetworkManager.PrefabHandler.HandleNetworkPrefabSpawn(globalObjectIdHash, sceneObject.OwnerClientId, position, rotation); networkObject.NetworkManagerOwner = NetworkManager; isSpawnedByPrefabHandler = true; } @@ -407,7 +407,7 @@ internal NetworkObject CreateLocalNetworkObject(NetworkObject.SceneObject sceneO // more scenes that contain nested in-scene placed NetworkObject children yet the server's // synchronization information does not indicate the NetworkObject in question has a parent. // Under this scenario, we want to remove the parent before spawning and setting the transform values. - if (sceneObject.Header.IsSceneObject && !sceneObject.Header.HasParent && networkObject.transform.parent != null) + if (sceneObject.IsSceneObject && !sceneObject.HasParent && networkObject.transform.parent != null) { // if the in-scene placed NetworkObject has a parent NetworkObject but the synchronization information does not // include parenting, then we need to force the removal of that parent @@ -421,7 +421,7 @@ internal NetworkObject CreateLocalNetworkObject(NetworkObject.SceneObject sceneO // Set the transform unless we were spawned by a prefab handler // Note: prefab handlers are provided the position and rotation // but it is up to the user to set those values - if (sceneObject.Header.HasTransform && !isSpawnedByPrefabHandler) + if (sceneObject.HasTransform && !isSpawnedByPrefabHandler) { // If world position stays is true or we have auto object parent synchronization disabled // then we want to apply the position and rotation values world space relative @@ -443,7 +443,7 @@ internal NetworkObject CreateLocalNetworkObject(NetworkObject.SceneObject sceneO // the network prefab used to represent the player. // Note: not doing this would set the player's scale to zero since // that is the default value of Vector3. - if (!sceneObject.Header.IsPlayerObject) + if (!sceneObject.IsPlayerObject) { // Since scale is always applied to local space scale, we do the transform // space logic during serialization such that it works out whether AutoObjectParentSync @@ -452,7 +452,7 @@ internal NetworkObject CreateLocalNetworkObject(NetworkObject.SceneObject sceneO } } - if (sceneObject.Header.HasParent) + if (sceneObject.HasParent) { // Go ahead and set network parenting properties networkObject.SetNetworkParenting(parentNetworkId, worldPositionStays); @@ -461,7 +461,7 @@ internal NetworkObject CreateLocalNetworkObject(NetworkObject.SceneObject sceneO // Dynamically spawned NetworkObjects that occur during a LoadSceneMode.Single load scene event are migrated into the DDOL // until the scene is loaded. They are then migrated back into the newly loaded and currently active scene. - if (!sceneObject.Header.IsSceneObject && NetworkSceneManager.IsSpawnedObjectsPendingInDontDestroyOnLoad) + if (!sceneObject.IsSceneObject && NetworkSceneManager.IsSpawnedObjectsPendingInDontDestroyOnLoad) { UnityEngine.Object.DontDestroyOnLoad(networkObject.gameObject); } @@ -510,7 +510,7 @@ internal void SpawnNetworkObjectLocally(NetworkObject networkObject, in NetworkO networkObject.SetNetworkVariableData(variableData); - SpawnNetworkObjectLocallyCommon(networkObject, sceneObject.Header.NetworkObjectId, sceneObject.Header.IsSceneObject, sceneObject.Header.IsPlayerObject, sceneObject.Header.OwnerClientId, destroyWithScene); + SpawnNetworkObjectLocallyCommon(networkObject, sceneObject.NetworkObjectId, sceneObject.IsSceneObject, sceneObject.IsPlayerObject, sceneObject.OwnerClientId, destroyWithScene); } private void SpawnNetworkObjectLocallyCommon(NetworkObject networkObject, ulong networkId, bool sceneObject, bool playerObject, ulong ownerClientId, bool destroyWithScene) diff --git a/com.unity.netcode.gameobjects/TestHelpers/Runtime/DebugNetworkHooks.cs b/com.unity.netcode.gameobjects/TestHelpers/Runtime/DebugNetworkHooks.cs new file mode 100644 index 0000000000..480a1c081d --- /dev/null +++ b/com.unity.netcode.gameobjects/TestHelpers/Runtime/DebugNetworkHooks.cs @@ -0,0 +1,63 @@ +using System; +using UnityEngine; + +namespace Unity.Netcode.TestHelpers.Runtime +{ + internal class DebugNetworkHooks : INetworkHooks + { + public void OnBeforeSendMessage(ulong clientId, ref T message, NetworkDelivery delivery) where T : INetworkMessage + { + } + + public void OnAfterSendMessage(ulong clientId, ref T message, NetworkDelivery delivery, int messageSizeBytes) where T : INetworkMessage + { + Debug.Log($"Sending message of type {typeof(T).Name} to {clientId} ({messageSizeBytes} bytes)"); + } + + public void OnBeforeReceiveMessage(ulong senderId, Type messageType, int messageSizeBytes) + { + Debug.Log($"Receiving message of type {messageType.Name} from {senderId}"); + } + + public void OnAfterReceiveMessage(ulong senderId, Type messageType, int messageSizeBytes) + { + } + + public void OnBeforeSendBatch(ulong clientId, int messageCount, int batchSizeInBytes, NetworkDelivery delivery) + { + Debug.Log($"==> Sending a batch of {messageCount} messages ({batchSizeInBytes} bytes) to {clientId} ({delivery})"); + } + + public void OnAfterSendBatch(ulong clientId, int messageCount, int batchSizeInBytes, NetworkDelivery delivery) + { + } + + public void OnBeforeReceiveBatch(ulong senderId, int messageCount, int batchSizeInBytes) + { + Debug.Log($"<== Receiving a batch of {messageCount} messages ({batchSizeInBytes} bytes) from {senderId}"); + } + + 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, FastBufferReader messageContent, ref NetworkContext context) + { + return true; + } + + public void OnBeforeHandleMessage(ref T message, ref NetworkContext context) where T : INetworkMessage + { + Debug.Log($"Handling a message of type {typeof(T).Name}"); + } + + public void OnAfterHandleMessage(ref T message, ref NetworkContext context) where T : INetworkMessage + { + } + } +} diff --git a/com.unity.netcode.gameobjects/TestHelpers/Runtime/DebugNetworkHooks.cs.meta b/com.unity.netcode.gameobjects/TestHelpers/Runtime/DebugNetworkHooks.cs.meta new file mode 100644 index 0000000000..ed3cc37016 --- /dev/null +++ b/com.unity.netcode.gameobjects/TestHelpers/Runtime/DebugNetworkHooks.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: b5f8098e713443eeba116a25de551cf8 +timeCreated: 1666626883 \ No newline at end of file diff --git a/com.unity.netcode.gameobjects/TestHelpers/Runtime/NetcodeIntegrationTest.cs b/com.unity.netcode.gameobjects/TestHelpers/Runtime/NetcodeIntegrationTest.cs index aad8529813..e91eb28a1e 100644 --- a/com.unity.netcode.gameobjects/TestHelpers/Runtime/NetcodeIntegrationTest.cs +++ b/com.unity.netcode.gameobjects/TestHelpers/Runtime/NetcodeIntegrationTest.cs @@ -474,6 +474,8 @@ protected void ClientNetworkManagerPostStartInit() } } + protected virtual bool LogAllMessages => false; + /// /// This starts the server and clients as long as /// returns true. @@ -492,6 +494,11 @@ protected IEnumerator StartServerAndClients() Assert.Fail("Failed to start instances"); } + if (LogAllMessages) + { + EnableMessageLogging(); + } + RegisterSceneManagerHandler(); // Notification that the server and clients have been started @@ -722,6 +729,18 @@ protected void DestroySceneNetworkObjects() } } + /// + /// For debugging purposes, this will turn on verbose logging of all messages and batches sent and received + /// + protected void EnableMessageLogging() + { + m_ServerNetworkManager.MessagingSystem.Hook(new DebugNetworkHooks()); + foreach (var client in m_ClientNetworkManagers) + { + client.MessagingSystem.Hook(new DebugNetworkHooks()); + } + } + /// /// Waits for the function condition to return true or it will time out. /// This will operate at the current m_ServerNetworkManager.NetworkConfig.TickRate diff --git a/com.unity.netcode.gameobjects/Tests/Editor/Serialization/BytePackerTests.cs b/com.unity.netcode.gameobjects/Tests/Editor/Serialization/BytePackerTests.cs index c4e039bf2a..3c072b4bec 100644 --- a/com.unity.netcode.gameobjects/Tests/Editor/Serialization/BytePackerTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Editor/Serialization/BytePackerTests.cs @@ -76,116 +76,6 @@ public enum WriteType #endregion - private void CheckUnsignedPackedSize64(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(FastBufferWriter writer, ulong value) - { - var reader = new FastBufferReader(writer, Allocator.Temp); - using (reader) - { - ByteUnpacker.ReadValuePacked(reader, out ulong readValue); - Assert.AreEqual(readValue, value); - } - } - - private void CheckUnsignedPackedSize32(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(FastBufferWriter writer, uint value) - { - var reader = new FastBufferReader(writer, Allocator.Temp); - using (reader) - { - ByteUnpacker.ReadValuePacked(reader, out uint readValue); - Assert.AreEqual(readValue, value); - } - } - - private void CheckSignedPackedSize64(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(FastBufferWriter writer, long value) - { - var reader = new FastBufferReader(writer, Allocator.Temp); - using (reader) - { - ByteUnpacker.ReadValuePacked(reader, out long readValue); - Assert.AreEqual(readValue, value); - } - } - - private void CheckSignedPackedSize32(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(FastBufferWriter writer, int value) - { - var reader = new FastBufferReader(writer, Allocator.Temp); - using (reader) - { - ByteUnpacker.ReadValuePacked(reader, out int readValue); - Assert.AreEqual(readValue, value); - } - } - private unsafe void VerifyBytewiseEquality(T value, T otherValue) where T : unmanaged { byte* asBytePointer = (byte*)&value; @@ -229,237 +119,94 @@ private unsafe void RunTypeTest(T value) where T : unmanaged } } - - [Test] - public void TestPacking64BitsUnsigned() + private int GetByteCount64Bits(ulong value) { - var writer = new FastBufferWriter(9, Allocator.Temp); - using (writer) - { - writer.TryBeginWrite(9); - ulong value = 0; - BytePacker.WriteValuePacked(writer, value); - Assert.AreEqual(1, writer.Position); - - for (var i = 0; i < 64; ++i) - { - value = 1UL << i; - writer.Seek(0); - writer.Truncate(); - BytePacker.WriteValuePacked(writer, value); - CheckUnsignedPackedSize64(writer, value); - CheckUnsignedPackedValue64(writer, value); - for (var j = 0; j < 8; ++j) - { - value = (1UL << i) | (1UL << j); - writer.Seek(0); - writer.Truncate(); - BytePacker.WriteValuePacked(writer, value); - CheckUnsignedPackedSize64(writer, value); - CheckUnsignedPackedValue64(writer, value); - } - } - } - } - - [Test] - public void TestPacking32BitsUnsigned() - { - var writer = new FastBufferWriter(9, Allocator.Temp); - - using (writer) - { - writer.TryBeginWrite(9); - uint value = 0; - BytePacker.WriteValuePacked(writer, value); - Assert.AreEqual(1, writer.Position); - - for (var i = 0; i < 64; ++i) - { - value = 1U << i; - writer.Seek(0); - writer.Truncate(); - BytePacker.WriteValuePacked(writer, value); - CheckUnsignedPackedSize32(writer, value); - CheckUnsignedPackedValue32(writer, value); - for (var j = 0; j < 8; ++j) - { - value = (1U << i) | (1U << j); - writer.Seek(0); - writer.Truncate(); - BytePacker.WriteValuePacked(writer, value); - CheckUnsignedPackedSize32(writer, value); - CheckUnsignedPackedValue32(writer, value); - } - } - } - } - - [Test] - public void TestPacking64BitsSigned() - { - var writer = new FastBufferWriter(9, Allocator.Temp); - - using (writer) - { - writer.TryBeginWrite(9); - long value = 0; - BytePacker.WriteValuePacked(writer, value); - Assert.AreEqual(1, writer.Position); - - for (var i = 0; i < 64; ++i) - { - value = 1L << i; - writer.Seek(0); - writer.Truncate(); - BytePacker.WriteValuePacked(writer, value); - CheckSignedPackedSize64(writer, value); - CheckSignedPackedValue64(writer, value); - - writer.Seek(0); - writer.Truncate(); - BytePacker.WriteValuePacked(writer, -value); - CheckSignedPackedSize64(writer, -value); - CheckSignedPackedValue64(writer, -value); - for (var j = 0; j < 8; ++j) - { - value = (1L << i) | (1L << j); - writer.Seek(0); - writer.Truncate(); - BytePacker.WriteValuePacked(writer, value); - CheckSignedPackedSize64(writer, value); - CheckSignedPackedValue64(writer, value); - - writer.Seek(0); - writer.Truncate(); - BytePacker.WriteValuePacked(writer, -value); - CheckSignedPackedSize64(writer, -value); - CheckSignedPackedValue64(writer, -value); - } - } - } - } - - [Test] - public void TestPacking32BitsSigned() - { - var writer = new FastBufferWriter(9, Allocator.Temp); - - using (writer) - { - writer.TryBeginWrite(5); - int value = 0; - BytePacker.WriteValuePacked(writer, value); - Assert.AreEqual(1, writer.Position); - - for (var i = 0; i < 64; ++i) - { - value = 1 << i; - writer.Seek(0); - writer.Truncate(); - BytePacker.WriteValuePacked(writer, value); - CheckSignedPackedSize32(writer, value); - CheckSignedPackedValue32(writer, value); - - writer.Seek(0); - writer.Truncate(); - BytePacker.WriteValuePacked(writer, -value); - CheckSignedPackedSize32(writer, -value); - CheckSignedPackedValue32(writer, -value); - for (var j = 0; j < 8; ++j) - { - value = (1 << i) | (1 << j); - writer.Seek(0); - writer.Truncate(); - BytePacker.WriteValuePacked(writer, value); - CheckSignedPackedSize32(writer, value); - CheckSignedPackedValue32(writer, value); - - writer.Seek(0); - writer.Truncate(); - BytePacker.WriteValuePacked(writer, -value); - CheckSignedPackedSize32(writer, -value); - CheckSignedPackedValue32(writer, -value); - } - } - } - } - - private int GetByteCount61Bits(ulong value) - { - - if (value <= 0b0001_1111) + if (value <= 0b0000_1111) { return 1; } - if (value <= 0b0001_1111_1111_1111) + if (value <= 0b0000_1111_1111_1111) { return 2; } - if (value <= 0b0001_1111_1111_1111_1111_1111) + if (value <= 0b0000_1111_1111_1111_1111_1111) { return 3; } - if (value <= 0b0001_1111_1111_1111_1111_1111_1111_1111) + if (value <= 0b0000_1111_1111_1111_1111_1111_1111_1111) { return 4; } - if (value <= 0b0001_1111_1111_1111_1111_1111_1111_1111_1111_1111) + if (value <= 0b0000_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) + if (value <= 0b0000_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) + if (value <= 0b0000_1111_1111_1111_1111_1111_1111_1111_1111_1111_1111_1111_1111_1111) { return 7; } - return 8; + if (value <= 0b0000_1111_1111_1111_1111_1111_1111_1111_1111_1111_1111_1111_1111_1111_1111_1111) + { + return 8; + } + + return 9; } - private int GetByteCount30Bits(uint value) + private int GetByteCount32Bits(uint value) { - if (value <= 0b0011_1111) + if (value <= 0b0001_1111) { return 1; } - if (value <= 0b0011_1111_1111_1111) + if (value <= 0b0001_1111_1111_1111) { return 2; } - if (value <= 0b0011_1111_1111_1111_1111_1111) + if (value <= 0b0001_1111_1111_1111_1111_1111) { return 3; } - return 4; + if (value <= 0b0001_1111_1111_1111_1111_1111_1111_1111) + { + return 4; + } + + return 5; } - private int GetByteCount15Bits(ushort value) + private int GetByteCount16Bits(ushort value) { - if (value <= 0b0111_1111) + if (value <= 0b0011_1111) { return 1; } + if (value <= 0b0011_1111_1111_1111) + { + return 2; + } - return 2; + return 3; } - private ulong Get61BitEncodedValue(FastBufferWriter writer) + private ulong Get64BitEncodedValue(FastBufferWriter writer) { var reader = new FastBufferReader(writer, Allocator.Temp); using (reader) @@ -469,7 +216,7 @@ private ulong Get61BitEncodedValue(FastBufferWriter writer) } } - private long Get60BitSignedEncodedValue(FastBufferWriter writer) + private long Get64BitSignedEncodedValue(FastBufferWriter writer) { var reader = new FastBufferReader(writer, Allocator.Temp); using (reader) @@ -479,7 +226,7 @@ private long Get60BitSignedEncodedValue(FastBufferWriter writer) } } - private uint Get30BitEncodedValue(FastBufferWriter writer) + private uint Get32BitEncodedValue(FastBufferWriter writer) { var reader = new FastBufferReader(writer, Allocator.Temp); using (reader) @@ -489,7 +236,7 @@ private uint Get30BitEncodedValue(FastBufferWriter writer) } } - private int Get29BitSignedEncodedValue(FastBufferWriter writer) + private int Get32BitSignedEncodedValue(FastBufferWriter writer) { var reader = new FastBufferReader(writer, Allocator.Temp); using (reader) @@ -499,7 +246,7 @@ private int Get29BitSignedEncodedValue(FastBufferWriter writer) } } - private ushort Get15BitEncodedValue(FastBufferWriter writer) + private ushort Get16BitEncodedValue(FastBufferWriter writer) { var reader = new FastBufferReader(writer, Allocator.Temp); using (reader) @@ -509,7 +256,7 @@ private ushort Get15BitEncodedValue(FastBufferWriter writer) } } - private short Get14BitSignedEncodedValue(FastBufferWriter writer) + private short Get16BitSignedEncodedValue(FastBufferWriter writer) { var reader = new FastBufferReader(writer, Allocator.Temp); using (reader) @@ -520,7 +267,7 @@ private short Get14BitSignedEncodedValue(FastBufferWriter writer) } [Test] - public void TestBitPacking61BitsUnsigned() + public void TestBitPacking64BitsUnsigned() { var writer = new FastBufferWriter(9, Allocator.Temp); @@ -530,18 +277,18 @@ public void TestBitPacking61BitsUnsigned() ulong value = 0; BytePacker.WriteValueBitPacked(writer, value); Assert.AreEqual(1, writer.Position); - Assert.AreEqual(0, writer.ToArray()[0] & 0b111); - Assert.AreEqual(value, Get61BitEncodedValue(writer)); + Assert.AreEqual(1, writer.ToArray()[0] & 0b1111); + Assert.AreEqual(value, Get64BitEncodedValue(writer)); - for (var i = 0; i < 61; ++i) + for (var i = 0; i < 64; ++i) { value = 1UL << i; writer.Seek(0); writer.Truncate(); BytePacker.WriteValueBitPacked(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(writer)); + Assert.AreEqual(GetByteCount64Bits(value), writer.Position, $"Failed on {value} ({i})"); + Assert.AreEqual(GetByteCount64Bits(value), writer.ToArray()[0] & 0b1111, $"Failed on {value} ({i})"); + Assert.AreEqual(value, Get64BitEncodedValue(writer)); for (var j = 0; j < 8; ++j) { @@ -549,18 +296,16 @@ public void TestBitPacking61BitsUnsigned() writer.Seek(0); writer.Truncate(); BytePacker.WriteValueBitPacked(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(writer)); + Assert.AreEqual(GetByteCount64Bits(value), writer.Position, $"Failed on {value} ({i}, {j})"); + Assert.AreEqual(GetByteCount64Bits(value), writer.ToArray()[0] & 0b1111, $"Failed on {value} ({i}, {j})"); + Assert.AreEqual(value, Get64BitEncodedValue(writer)); } } - - Assert.Throws(() => { BytePacker.WriteValueBitPacked(writer, 1UL << 61); }); } } [Test] - public void TestBitPacking60BitsSigned() + public void TestBitPacking64BitsSigned() { var writer = new FastBufferWriter(9, Allocator.Temp); @@ -570,28 +315,28 @@ public void TestBitPacking60BitsSigned() long value = 0; BytePacker.WriteValueBitPacked(writer, value); Assert.AreEqual(1, writer.Position); - Assert.AreEqual(0, writer.ToArray()[0] & 0b111); - Assert.AreEqual(value, Get60BitSignedEncodedValue(writer)); + Assert.AreEqual(1, writer.ToArray()[0] & 0b1111); + Assert.AreEqual(value, Get64BitSignedEncodedValue(writer)); - for (var i = 0; i < 61; ++i) + for (var i = 0; i < 64; ++i) { value = 1U << i; ulong zzvalue = Arithmetic.ZigZagEncode(value); writer.Seek(0); writer.Truncate(); BytePacker.WriteValueBitPacked(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(writer)); + Assert.AreEqual(GetByteCount64Bits(zzvalue), writer.Position, $"Failed on {value} ({i})"); + Assert.AreEqual(GetByteCount64Bits(zzvalue), writer.ToArray()[0] & 0b1111, $"Failed on {value} ({i})"); + Assert.AreEqual(value, Get64BitSignedEncodedValue(writer)); value = -value; zzvalue = Arithmetic.ZigZagEncode(value); writer.Seek(0); writer.Truncate(); BytePacker.WriteValueBitPacked(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(writer)); + Assert.AreEqual(GetByteCount64Bits(zzvalue), writer.Position, $"Failed on {value} ({i})"); + Assert.AreEqual(GetByteCount64Bits(zzvalue), writer.ToArray()[0] & 0b1111, $"Failed on {value} ({i})"); + Assert.AreEqual(value, Get64BitSignedEncodedValue(writer)); for (var j = 0; j < 8; ++j) { @@ -600,27 +345,25 @@ public void TestBitPacking60BitsSigned() writer.Seek(0); writer.Truncate(); BytePacker.WriteValueBitPacked(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(writer)); + Assert.AreEqual(GetByteCount64Bits(zzvalue), writer.Position, $"Failed on {value} ({i}, {j})"); + Assert.AreEqual(GetByteCount64Bits(zzvalue), writer.ToArray()[0] & 0b1111, $"Failed on {value} ({i}, {j})"); + Assert.AreEqual(value, Get64BitSignedEncodedValue(writer)); value = -value; zzvalue = Arithmetic.ZigZagEncode(value); writer.Seek(0); writer.Truncate(); BytePacker.WriteValueBitPacked(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(writer)); + Assert.AreEqual(GetByteCount64Bits(zzvalue), writer.Position, $"Failed on {value} ({i}, {j})"); + Assert.AreEqual(GetByteCount64Bits(zzvalue), writer.ToArray()[0] & 0b1111, $"Failed on {value} ({i}, {j})"); + Assert.AreEqual(value, Get64BitSignedEncodedValue(writer)); } } - - Assert.Throws(() => { BytePacker.WriteValueBitPacked(writer, 1UL << 61); }); } } [Test] - public void TestBitPacking30BitsUnsigned() + public void TestBitPacking32BitsUnsigned() { var writer = new FastBufferWriter(9, Allocator.Temp); @@ -630,18 +373,18 @@ public void TestBitPacking30BitsUnsigned() uint value = 0; BytePacker.WriteValueBitPacked(writer, value); Assert.AreEqual(1, writer.Position); - Assert.AreEqual(0, writer.ToArray()[0] & 0b11); - Assert.AreEqual(value, Get30BitEncodedValue(writer)); + Assert.AreEqual(1, writer.ToArray()[0] & 0b111); + Assert.AreEqual(value, Get32BitEncodedValue(writer)); - for (var i = 0; i < 30; ++i) + for (var i = 0; i < 32; ++i) { value = 1U << i; writer.Seek(0); writer.Truncate(); BytePacker.WriteValueBitPacked(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(writer)); + Assert.AreEqual(GetByteCount32Bits(value), writer.Position, $"Failed on {value} ({i})"); + Assert.AreEqual(GetByteCount32Bits(value), writer.ToArray()[0] & 0b111, $"Failed on {value} ({i})"); + Assert.AreEqual(value, Get32BitEncodedValue(writer)); for (var j = 0; j < 8; ++j) { @@ -649,18 +392,16 @@ public void TestBitPacking30BitsUnsigned() writer.Seek(0); writer.Truncate(); BytePacker.WriteValueBitPacked(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(writer)); + Assert.AreEqual(GetByteCount32Bits(value), writer.Position, $"Failed on {value} ({i}, {j})"); + Assert.AreEqual(GetByteCount32Bits(value), writer.ToArray()[0] & 0b111, $"Failed on {value} ({i}, {j})"); + Assert.AreEqual(value, Get32BitEncodedValue(writer)); } } - - Assert.Throws(() => { BytePacker.WriteValueBitPacked(writer, 1U << 30); }); } } [Test] - public void TestBitPacking29BitsSigned() + public void TestBitPacking32BitsSigned() { var writer = new FastBufferWriter(9, Allocator.Temp); @@ -670,28 +411,28 @@ public void TestBitPacking29BitsSigned() int value = 0; BytePacker.WriteValueBitPacked(writer, value); Assert.AreEqual(1, writer.Position); - Assert.AreEqual(0, writer.ToArray()[0] & 0b11); - Assert.AreEqual(value, Get30BitEncodedValue(writer)); + Assert.AreEqual(1, writer.ToArray()[0] & 0b111); + Assert.AreEqual(value, Get32BitEncodedValue(writer)); - for (var i = 0; i < 29; ++i) + for (var i = 0; i < 32; ++i) { value = 1 << i; uint zzvalue = (uint)Arithmetic.ZigZagEncode(value); writer.Seek(0); writer.Truncate(); BytePacker.WriteValueBitPacked(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(writer)); + Assert.AreEqual(GetByteCount32Bits(zzvalue), writer.Position, $"Failed on {value} ({i})"); + Assert.AreEqual(GetByteCount32Bits(zzvalue), writer.ToArray()[0] & 0b111, $"Failed on {value} ({i})"); + Assert.AreEqual(value, Get32BitSignedEncodedValue(writer)); value = -value; zzvalue = (uint)Arithmetic.ZigZagEncode(value); writer.Seek(0); writer.Truncate(); BytePacker.WriteValueBitPacked(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(writer)); + Assert.AreEqual(GetByteCount32Bits(zzvalue), writer.Position, $"Failed on {value} ({i})"); + Assert.AreEqual(GetByteCount32Bits(zzvalue), writer.ToArray()[0] & 0b111, $"Failed on {value} ({i})"); + Assert.AreEqual(value, Get32BitSignedEncodedValue(writer)); for (var j = 0; j < 8; ++j) { @@ -700,25 +441,25 @@ public void TestBitPacking29BitsSigned() writer.Seek(0); writer.Truncate(); BytePacker.WriteValueBitPacked(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(writer)); + Assert.AreEqual(GetByteCount32Bits(zzvalue), writer.Position, $"Failed on {value} ({i}, {j})"); + Assert.AreEqual(GetByteCount32Bits(zzvalue), writer.ToArray()[0] & 0b111, $"Failed on {value} ({i}, {j})"); + Assert.AreEqual(value, Get32BitSignedEncodedValue(writer)); value = -value; zzvalue = (uint)Arithmetic.ZigZagEncode(value); writer.Seek(0); writer.Truncate(); BytePacker.WriteValueBitPacked(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(writer)); + Assert.AreEqual(GetByteCount32Bits(zzvalue), writer.Position, $"Failed on {value} ({i}, {j})"); + Assert.AreEqual(GetByteCount32Bits(zzvalue), writer.ToArray()[0] & 0b111, $"Failed on {value} ({i}, {j})"); + Assert.AreEqual(value, Get32BitSignedEncodedValue(writer)); } } } } [Test] - public void TestBitPacking15BitsUnsigned() + public void TestBitPacking16BitsUnsigned() { var writer = new FastBufferWriter(9, Allocator.Temp); @@ -728,18 +469,18 @@ public void TestBitPacking15BitsUnsigned() ushort value = 0; BytePacker.WriteValueBitPacked(writer, value); Assert.AreEqual(1, writer.Position); - Assert.AreEqual(0, writer.ToArray()[0] & 0b1); - Assert.AreEqual(value, Get15BitEncodedValue(writer)); + Assert.AreEqual(1, writer.ToArray()[0] & 0b11); + Assert.AreEqual(value, Get16BitEncodedValue(writer)); - for (var i = 0; i < 15; ++i) + for (var i = 0; i < 16; ++i) { value = (ushort)(1U << i); writer.Seek(0); writer.Truncate(); BytePacker.WriteValueBitPacked(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(writer)); + Assert.AreEqual(GetByteCount16Bits(value), writer.Position, $"Failed on {value} ({i})"); + Assert.AreEqual(GetByteCount16Bits(value), writer.ToArray()[0] & 0b11, $"Failed on {value} ({i})"); + Assert.AreEqual(value, Get16BitEncodedValue(writer)); for (var j = 0; j < 8; ++j) { @@ -747,17 +488,15 @@ public void TestBitPacking15BitsUnsigned() writer.Seek(0); writer.Truncate(); BytePacker.WriteValueBitPacked(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(writer)); + Assert.AreEqual(GetByteCount16Bits(value), writer.Position, $"Failed on {value} ({i}, {j})"); + Assert.AreEqual(GetByteCount16Bits(value), writer.ToArray()[0] & 0b11, $"Failed on {value} ({i}, {j})"); + Assert.AreEqual(value, Get16BitEncodedValue(writer)); } } - - Assert.Throws(() => { BytePacker.WriteValueBitPacked(writer, (ushort)(1U << 15)); }); } } [Test] - public void TestBitPacking14BitsSigned() + public void TestBitPacking16BitsSigned() { var writer = new FastBufferWriter(9, Allocator.Temp); @@ -767,28 +506,28 @@ public void TestBitPacking14BitsSigned() short value = 0; BytePacker.WriteValueBitPacked(writer, value); Assert.AreEqual(1, writer.Position); - Assert.AreEqual(0, writer.ToArray()[0] & 0b1); - Assert.AreEqual(value, Get15BitEncodedValue(writer)); + Assert.AreEqual(1, writer.ToArray()[0] & 0b11); + Assert.AreEqual(value, Get16BitEncodedValue(writer)); - for (var i = 0; i < 14; ++i) + for (var i = 0; i < 16; ++i) { value = (short)(1 << i); ushort zzvalue = (ushort)Arithmetic.ZigZagEncode(value); writer.Seek(0); writer.Truncate(); BytePacker.WriteValueBitPacked(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(writer)); + Assert.AreEqual(GetByteCount16Bits(zzvalue), writer.Position, $"Failed on {value} ({i})"); + Assert.AreEqual(GetByteCount16Bits(zzvalue), writer.ToArray()[0] & 0b11, $"Failed on {value} ({i})"); + Assert.AreEqual(value, Get16BitSignedEncodedValue(writer)); value = (short)-value; zzvalue = (ushort)Arithmetic.ZigZagEncode(value); writer.Seek(0); writer.Truncate(); BytePacker.WriteValueBitPacked(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(writer)); + Assert.AreEqual(GetByteCount16Bits(zzvalue), writer.Position, $"Failed on {value} ({i})"); + Assert.AreEqual(GetByteCount16Bits(zzvalue), writer.ToArray()[0] & 0b11, $"Failed on {value} ({i})"); + Assert.AreEqual(value, Get16BitSignedEncodedValue(writer)); for (var j = 0; j < 8; ++j) { @@ -797,18 +536,18 @@ public void TestBitPacking14BitsSigned() writer.Seek(0); writer.Truncate(); BytePacker.WriteValueBitPacked(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(writer)); + Assert.AreEqual(GetByteCount16Bits(zzvalue), writer.Position, $"Failed on {value} ({i}, {j})"); + Assert.AreEqual(GetByteCount16Bits(zzvalue), writer.ToArray()[0] & 0b11, $"Failed on {value} ({i}, {j})"); + Assert.AreEqual(value, Get16BitSignedEncodedValue(writer)); value = (short)-value; zzvalue = (ushort)Arithmetic.ZigZagEncode(value); writer.Seek(0); writer.Truncate(); BytePacker.WriteValueBitPacked(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(writer)); + Assert.AreEqual(GetByteCount16Bits(zzvalue), writer.Position, $"Failed on {value} ({i}, {j})"); + Assert.AreEqual(GetByteCount16Bits(zzvalue), writer.ToArray()[0] & 0b11, $"Failed on {value} ({i}, {j})"); + Assert.AreEqual(value, Get16BitSignedEncodedValue(writer)); } } } diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/Metrics/OwnershipChangeMetricsTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/Metrics/OwnershipChangeMetricsTests.cs index bfcfdc23eb..c6d723a4df 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/Metrics/OwnershipChangeMetricsTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/Metrics/OwnershipChangeMetricsTests.cs @@ -2,6 +2,7 @@ using System.Collections; using System.Linq; using NUnit.Framework; +using Unity.Collections; using Unity.Multiplayer.Tools.MetricTypes; using UnityEngine; using UnityEngine.TestTools; @@ -43,6 +44,18 @@ private NetworkObject SpawnNetworkObject() return networkObject; } + private int GetWriteSizeForOwnerChange(NetworkObject networkObject, ulong newOwner) + { + var message = new ChangeOwnershipMessage + { + NetworkObjectId = networkObject.NetworkObjectId, + OwnerClientId = newOwner + }; + using var writer = new FastBufferWriter(1024, Allocator.Temp); + message.Serialize(writer); + return writer.Length; + } + [UnityTest] public IEnumerator TrackOwnershipChangeSentMetric() { @@ -68,7 +81,9 @@ public IEnumerator TrackOwnershipChangeSentMetric() ownershipChangeSent = metricValues.Last(); Assert.AreEqual(networkObject.NetworkObjectId, ownershipChangeSent.NetworkId.NetworkId); Assert.AreEqual(Client.LocalClientId, ownershipChangeSent.Connection.Id); - Assert.AreEqual(FastBufferWriter.GetWriteSize() + k_MessageHeaderSize, ownershipChangeSent.BytesCount); + + var serializedLength = GetWriteSizeForOwnerChange(networkObject, 1); + Assert.AreEqual(serializedLength + k_MessageHeaderSize, ownershipChangeSent.BytesCount); } [UnityTest] @@ -89,7 +104,9 @@ public IEnumerator TrackOwnershipChangeReceivedMetric() var ownershipChangeReceived = metricValues.First(); Assert.AreEqual(networkObject.NetworkObjectId, ownershipChangeReceived.NetworkId.NetworkId); - Assert.AreEqual(FastBufferWriter.GetWriteSize(), ownershipChangeReceived.BytesCount); + + var serializedLength = GetWriteSizeForOwnerChange(networkObject, 1); + Assert.AreEqual(serializedLength, ownershipChangeReceived.BytesCount); } } } diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/Metrics/ServerLogsMetricTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/Metrics/ServerLogsMetricTests.cs index 78d70bc342..9c5dae1acf 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/Metrics/ServerLogsMetricTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/Metrics/ServerLogsMetricTests.cs @@ -3,6 +3,7 @@ using System.Collections; using System.Linq; using NUnit.Framework; +using Unity.Collections; using Unity.Multiplayer.Tools.MetricTypes; using UnityEngine.TestTools; using Unity.Netcode.TestHelpers.Runtime.Metrics; @@ -11,10 +12,8 @@ namespace Unity.Netcode.RuntimeTests.Metrics { internal class ServerLogsMetricTests : SingleClientMetricTestBase { - // Header is dynamically sized due to packing, will be 2 bytes for all test messages. - private const int k_MessageHeaderSize = 2; - private static readonly int k_ServerLogSentMessageOverhead = 2 + k_MessageHeaderSize; - private static readonly int k_ServerLogReceivedMessageOverhead = 2; + // Header is dynamically sized due to packing, will be 3 bytes for all test messages. + private const int k_MessageHeaderSize = 3; protected override IEnumerator OnSetup() { @@ -22,6 +21,19 @@ protected override IEnumerator OnSetup() return base.OnSetup(); } + + private int GetWriteSizeForLog(NetworkLog.LogType logType, string logMessage) + { + var message = new ServerLogMessage + { + LogType = logType, + Message = logMessage + }; + using var writer = new FastBufferWriter(1024, Allocator.Temp); + message.Serialize(writer); + return writer.Length; + } + [UnityTest] public IEnumerator TrackServerLogSentMetric() { @@ -41,7 +53,9 @@ public IEnumerator TrackServerLogSentMetric() var sentMetric = sentMetrics.First(); Assert.AreEqual(Server.LocalClientId, sentMetric.Connection.Id); Assert.AreEqual((uint)NetworkLog.LogType.Warning, (uint)sentMetric.LogLevel); - Assert.AreEqual(message.Length + k_ServerLogSentMessageOverhead, sentMetric.BytesCount); + + var serializedLength = GetWriteSizeForLog(NetworkLog.LogType.Warning, message); + Assert.AreEqual(serializedLength + k_MessageHeaderSize, sentMetric.BytesCount); } [UnityTest] @@ -64,7 +78,9 @@ public IEnumerator TrackServerLogReceivedMetric() var receivedMetric = receivedMetrics.First(); Assert.AreEqual(Client.LocalClientId, receivedMetric.Connection.Id); Assert.AreEqual((uint)NetworkLog.LogType.Warning, (uint)receivedMetric.LogLevel); - Assert.AreEqual(message.Length + k_ServerLogReceivedMessageOverhead, receivedMetric.BytesCount); + + var serializedLength = GetWriteSizeForLog(NetworkLog.LogType.Warning, message); + Assert.AreEqual(serializedLength, receivedMetric.BytesCount); } } } diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/NetworkTransformTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/NetworkTransformTests.cs index 9486bdf26d..f3af7bdfd1 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/NetworkTransformTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/NetworkTransformTests.cs @@ -734,7 +734,6 @@ public IEnumerator TeleportTest([Values] Interpolation interpolation) Assert.IsTrue(m_DetectedPotentialInterpolatedTeleport == 0.0f, $"Detected possible interpolation on non-authority side! NonAuthority distance: {m_DetectedPotentialInterpolatedTeleport} | Target distance: {targetDistance}"); } - /// /// This test validates the method /// usage for the non-authoritative side. It will either be the owner or the server making/requesting state changes. @@ -842,7 +841,7 @@ private bool RotationMatchesValue(Vector3 rotationEulerToMatch) } if (!nonauthorityIsEqual) { - VerboseDebug($"NonAuthority position {nonAuthorityRotationEuler} != rotation to match: {rotationEulerToMatch}!"); + VerboseDebug($"NonAuthority rotation {nonAuthorityRotationEuler} != rotation to match: {rotationEulerToMatch}!"); } return auhtorityIsEqual && nonauthorityIsEqual; }