From cd11ac6a36e4c31504ab069a6733cd118c920c0b Mon Sep 17 00:00:00 2001 From: Emma Date: Fri, 31 Oct 2025 13:23:05 -0400 Subject: [PATCH 1/7] chore: optimize bitflags in NetworkTransformState --- .../Runtime/Components/NetworkTransform.cs | 425 +++++++----------- .../NetworkTransformStateTests.cs | 310 +++++++------ .../Runtime/TestHelpers/NUnitExtensions.meta | 3 + ...fServiceEnvironmentVariableSetAttribute.cs | 26 ++ ...iceEnvironmentVariableSetAttribute.cs.meta | 3 + .../NetcodeIntegrationTestHelpers.cs | 4 +- 6 files changed, 384 insertions(+), 387 deletions(-) create mode 100644 com.unity.netcode.gameobjects/Tests/Runtime/TestHelpers/NUnitExtensions.meta create mode 100644 com.unity.netcode.gameobjects/Tests/Runtime/TestHelpers/NUnitExtensions/IgnoreIfServiceEnvironmentVariableSetAttribute.cs create mode 100644 com.unity.netcode.gameobjects/Tests/Runtime/TestHelpers/NUnitExtensions/IgnoreIfServiceEnvironmentVariableSetAttribute.cs.meta diff --git a/com.unity.netcode.gameobjects/Runtime/Components/NetworkTransform.cs b/com.unity.netcode.gameobjects/Runtime/Components/NetworkTransform.cs index 1c2ed5dadb..2cd74137bc 100644 --- a/com.unity.netcode.gameobjects/Runtime/Components/NetworkTransform.cs +++ b/com.unity.netcode.gameobjects/Runtime/Components/NetworkTransform.cs @@ -26,6 +26,14 @@ public class NetworkTransform : NetworkBehaviour internal bool NetworkTransformExpanded; #endif + internal enum Axis { X, Y, Z } + + internal enum AxialType + { + Position, + Rotation, + Scale + } #region NETWORK TRANSFORM STATE /// /// Data structure used to synchronize the @@ -65,14 +73,6 @@ public struct NetworkTransformState : INetworkSerializable // (Internal Debugging) When set each state update will contain a state identifier private const int k_TrackStateId = 0x10000000; - // Stores persistent and state relative flags - private uint m_Bitset; - internal uint BitSet - { - get { return m_Bitset; } - set { m_Bitset = value; } - } - // Used to store the tick calculated sent time internal double SentTime; @@ -131,74 +131,33 @@ internal uint BitSet /// world and local space. /// /// - internal bool SwitchTransformSpaceWhenParented - { - get => GetFlag(k_SwitchTransformSpaceWhenParented); - set - { - SetFlag(value, k_SwitchTransformSpaceWhenParented); - } - } + internal bool SwitchTransformSpaceWhenParented; /// /// When set, the is operates in local space /// - public bool InLocalSpace - { - get => GetFlag(k_InLocalSpaceBit); - internal set - { - SetFlag(value, k_InLocalSpaceBit); - } - } + public bool InLocalSpace { get; internal set; } // Position /// /// When set, the X-Axis position value has changed /// - public bool HasPositionX - { - get => GetFlag(k_PositionXBit); - internal set - { - SetFlag(value, k_PositionXBit); - } - } + public bool HasPositionX { get; internal set; } /// /// When set, the Y-Axis position value has changed /// - public bool HasPositionY - { - get => GetFlag(k_PositionYBit); - internal set - { - SetFlag(value, k_PositionYBit); - } - } + public bool HasPositionY { get; internal set; } /// /// When set, the Z-Axis position value has changed /// - public bool HasPositionZ - { - get => GetFlag(k_PositionZBit); - internal set - { - SetFlag(value, k_PositionZBit); - } - } + public bool HasPositionZ { get; internal set; } /// /// When set, at least one of the position axis values has changed. /// - public bool HasPositionChange - { - get - { - return HasPositionX || HasPositionY || HasPositionZ; - } - } + public bool HasPositionChange { get; internal set; } // RotAngles /// @@ -207,14 +166,7 @@ public bool HasPositionChange /// /// When quaternion synchronization is enabled all axis are always updated. /// - public bool HasRotAngleX - { - get => GetFlag(k_RotAngleXBit); - internal set - { - SetFlag(value, k_RotAngleXBit); - } - } + public bool HasRotAngleX { get; internal set; } /// /// When set, the Euler rotation Y-Axis value has changed. @@ -222,14 +174,7 @@ internal set /// /// When quaternion synchronization is enabled all axis are always updated. /// - public bool HasRotAngleY - { - get => GetFlag(k_RotAngleYBit); - internal set - { - SetFlag(value, k_RotAngleYBit); - } - } + public bool HasRotAngleY { get; internal set; } /// /// When set, the Euler rotation Z-Axis value has changed. @@ -237,14 +182,7 @@ internal set /// /// When quaternion synchronization is enabled all axis are always updated. /// - public bool HasRotAngleZ - { - get => GetFlag(k_RotAngleZBit); - internal set - { - SetFlag(value, k_RotAngleZBit); - } - } + public bool HasRotAngleZ { get; internal set; } /// /// When set, at least one of the rotation axis values has changed. @@ -252,73 +190,74 @@ internal set /// /// When quaternion synchronization is enabled all axis are always updated. /// - public bool HasRotAngleChange - { - get - { - return HasRotAngleX || HasRotAngleY || HasRotAngleZ; - } - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal bool HasScale(int axisIndex) - { - return GetFlag(k_ScaleXBit << axisIndex); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal void SetHasScale(int axisIndex, bool isSet) - { - SetFlag(isSet, k_ScaleXBit << axisIndex); - } + public bool HasRotAngleChange{ get; internal set; } // Scale /// /// When set, the X-Axis scale value has changed. /// - public bool HasScaleX - { - get => GetFlag(k_ScaleXBit); - internal set - { - SetFlag(value, k_ScaleXBit); - } - } + public bool HasScaleX { get; internal set; } /// /// When set, the Y-Axis scale value has changed. /// - public bool HasScaleY - { - get => GetFlag(k_ScaleYBit); - internal set - { - SetFlag(value, k_ScaleYBit); - } - } + public bool HasScaleY { get; internal set; } /// /// When set, the Z-Axis scale value has changed. /// - public bool HasScaleZ - { - get => GetFlag(k_ScaleZBit); - internal set - { - SetFlag(value, k_ScaleZBit); - } - } + public bool HasScaleZ { get; internal set; } /// /// When set, at least one of the scale axis values has changed. /// - public bool HasScaleChange + public bool HasScaleChange { get; internal set; } + + internal void MarkChanged(AxialType axialType, bool changed) { - get + switch (axialType) { - return HasScaleX || HasScaleY || HasScaleZ; + case AxialType.Position: + HasPositionX = changed; + HasPositionY = changed; + HasPositionZ = changed; + HasPositionChange = changed; + break; + case AxialType.Rotation: + HasRotAngleX = changed; + HasRotAngleY = changed; + HasRotAngleZ = changed; + HasRotAngleChange = changed; + break; + case AxialType.Scale: + HasScaleX = changed; + HasScaleY = changed; + HasScaleZ = changed; + HasScaleChange = changed; + break; } } + internal void SetHasScale(Axis axis, bool changed) + { + switch (axis) + { + case Axis.X: + HasScaleX = changed; + break; + case Axis.Y: + HasScaleY = changed; + break; + case Axis.Z: + HasScaleZ = changed; + break; + } + HasScaleChange = HasScaleX || HasScaleY || HasScaleZ; + } + + internal bool HasScale(Axis axis) + { + return axis == Axis.X ? HasScaleX : axis == Axis.Y ? HasScaleY : HasScaleZ; + } /// /// When set, the current state will be treated as a teleport. @@ -329,14 +268,7 @@ public bool HasScaleChange /// - If using half precision, full precision values are used. /// - All axis marked to be synchronized will be updated. /// - public bool IsTeleportingNextFrame - { - get => GetFlag(k_TeleportingBit); - internal set - { - SetFlag(value, k_TeleportingBit); - } - } + public bool IsTeleportingNextFrame { get; internal set; } /// /// When overriding , if the state that was pushed was a teleport then this will be set to true. @@ -353,14 +285,7 @@ internal set /// Authority does not apply interpolation via . /// Authority should handle its own motion/rotation/scale smoothing locally. /// - public bool UseInterpolation - { - get => GetFlag(k_Interpolate); - internal set - { - SetFlag(value, k_Interpolate); - } - } + public bool UseInterpolation { get; internal set; } /// /// When enabled, this instance uses synchronization. @@ -370,14 +295,7 @@ internal set /// When quaternion synchronization is enabled, the entire quaternion is updated when there are any changes to any axial values. /// You can use half float precision or quaternion compression to reduce the bandwidth cost. /// - public bool QuaternionSync - { - get => GetFlag(k_QuaternionSync); - internal set - { - SetFlag(value, k_QuaternionSync); - } - } + public bool QuaternionSync { get; internal set; } /// /// When set s will be compressed down to 4 bytes using a smallest three implementation. @@ -388,14 +306,7 @@ internal set /// - Quaternion Compression: 4 bytes per delta update /// - Half float precision: 8 bytes per delta update /// - public bool QuaternionCompression - { - get => GetFlag(k_QuaternionCompress); - internal set - { - SetFlag(value, k_QuaternionCompress); - } - } + public bool QuaternionCompression { get; internal set; } /// /// When set, the will use half float precision for position, rotation, and scale. @@ -404,40 +315,19 @@ internal set /// Postion is synchronized through delta position updates in order to reduce precision loss/drift and to extend to positions beyond the limitation of half float maximum values. /// Rotation and scale both use half float precision ( and ) /// - public bool UseHalfFloatPrecision - { - get => GetFlag(k_UseHalfFloats); - internal set - { - SetFlag(value, k_UseHalfFloats); - } - } + public bool UseHalfFloatPrecision { get; internal set; } /// /// When set, this indicates it is the first state being synchronized. /// Typically when the associate is spawned or a client is being synchronized after connecting to a network session in progress. /// - public bool IsSynchronizing - { - get => GetFlag(k_Synchronization); - internal set - { - SetFlag(value, k_Synchronization); - } - } + public bool IsSynchronizing { get; internal set; } /// /// Determines if position interpolation will Slerp towards its target position. /// This is only really useful if you are moving around a point in a circular pattern. /// - public bool UsePositionSlerp - { - get => GetFlag(k_PositionSlerp); - internal set - { - SetFlag(value, k_PositionSlerp); - } - } + public bool UsePositionSlerp { get; internal set; } /// /// Returns whether this state update was a frame synchronization when @@ -463,78 +353,35 @@ public bool IsReliableStateUpdate() return ReliableSequenced; } - internal bool IsParented - { - get => GetFlag(k_IsParented); - set - { - SetFlag(value, k_IsParented); - } - } - - internal bool SynchronizeBaseHalfFloat - { - get => GetFlag(k_SynchBaseHalfFloat); - set - { - SetFlag(value, k_SynchBaseHalfFloat); - } - } - - internal bool ReliableSequenced - { - get => GetFlag(k_ReliableSequenced); - set - { - SetFlag(value, k_ReliableSequenced); - } - } + internal bool IsParented; - internal bool UseUnreliableDeltas - { - get => GetFlag(k_UseUnreliableDeltas); - set - { - SetFlag(value, k_UseUnreliableDeltas); - } - } + internal bool SynchronizeBaseHalfFloat; - internal bool UnreliableFrameSync - { - get => GetFlag(k_UnreliableFrameSync); - set - { - SetFlag(value, k_UnreliableFrameSync); - } - } + internal bool ReliableSequenced; - internal bool TrackByStateId - { - get => GetFlag(k_TrackStateId); - set - { - SetFlag(value, k_TrackStateId); - } - } + internal bool UseUnreliableDeltas; - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private bool GetFlag(int flag) - { - return (m_Bitset & flag) != 0; - } + internal bool UnreliableFrameSync; - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private void SetFlag(bool set, int flag) - { - if (set) { m_Bitset = m_Bitset | (uint)flag; } - else { m_Bitset = m_Bitset & (uint)~flag; } - } + internal bool TrackByStateId; + /// + /// Clear everything but flags that should persist between state updates until changed by authority. + /// Persistent (non-cleared) flags are , , , + /// , , , + /// internal void ClearBitSetForNextTick() { - // Clear everything but flags that should persist between state updates until changed by authority - m_Bitset &= k_InLocalSpaceBit | k_Interpolate | k_UseHalfFloats | k_QuaternionSync | k_QuaternionCompress | k_PositionSlerp | k_UseUnreliableDeltas | k_SwitchTransformSpaceWhenParented; - IsDirty = false; + MarkChanged(AxialType.Position, false); + MarkChanged(AxialType.Rotation, false); + MarkChanged(AxialType.Scale, false); + IsTeleportingNextFrame = false; + IsSynchronizing = false; + IsParented = false; + SynchronizeBaseHalfFloat = false; + ReliableSequenced = false; + UnreliableFrameSync = false; + TrackByStateId = false; } /// @@ -687,14 +534,14 @@ public void NetworkSerialize(BufferSerializer serializer) where T : IReade ReliableSequenced = true; } - BytePacker.WriteValueBitPacked(m_Writer, m_Bitset); + SerializeBitset(ref m_Writer); // We use network ticks as opposed to absolute time as the authoritative // side updates on every new tick. BytePacker.WriteValueBitPacked(m_Writer, NetworkTick); } else { - ByteUnpacker.ReadValueBitPacked(m_Reader, out m_Bitset); + DeserializeBitset(ref m_Reader); // We use network ticks as opposed to absolute time as the authoritative // side updates on every new tick. ByteUnpacker.ReadValueBitPacked(m_Reader, out NetworkTick); @@ -997,6 +844,72 @@ public void NetworkSerialize(BufferSerializer serializer) where T : IReade #endif } } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void SerializeBitset(ref FastBufferWriter writer) + { + uint bitset = 0; + + if (InLocalSpace) { bitset |= k_InLocalSpaceBit; } + if (HasPositionX) { bitset |= k_PositionXBit; } + if (HasPositionY) { bitset |= k_PositionYBit; } + if (HasPositionZ) { bitset |= k_PositionZBit; } + if (HasRotAngleX) { bitset |= k_RotAngleXBit; } + if (HasRotAngleY) { bitset |= k_RotAngleYBit; } + if (HasRotAngleZ) { bitset |= k_RotAngleZBit; } + if (HasScaleX) { bitset |= k_ScaleXBit; } + if (HasScaleY) { bitset |= k_ScaleYBit; } + if (HasScaleZ) { bitset |= k_ScaleZBit; } + if (IsTeleportingNextFrame) { bitset |= k_TeleportingBit; } + if (UseInterpolation) { bitset |= k_Interpolate; } + if (QuaternionSync) { bitset |= k_QuaternionSync; } + if (QuaternionCompression) { bitset |= k_QuaternionCompress; } + if (UseHalfFloatPrecision) { bitset |= k_UseHalfFloats; } + if (IsSynchronizing) { bitset |= k_Synchronization; } + if (UsePositionSlerp) { bitset |= k_PositionSlerp; } + if (IsParented) { bitset |= k_IsParented; } + if (SynchronizeBaseHalfFloat) { bitset |= k_SynchBaseHalfFloat; } + if (ReliableSequenced) { bitset |= k_ReliableSequenced; } + if (UseUnreliableDeltas) { bitset |= k_UseUnreliableDeltas; } + if (UnreliableFrameSync) { bitset |= k_UnreliableFrameSync; } + if (SwitchTransformSpaceWhenParented) { bitset |= k_SwitchTransformSpaceWhenParented; } + if (TrackByStateId) { bitset |= k_TrackStateId; } + + BytePacker.WriteValueBitPacked(writer, bitset); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void DeserializeBitset(ref FastBufferReader reader) + { + ByteUnpacker.ReadValueBitPacked(reader, out uint bitset); + + InLocalSpace = (bitset & k_InLocalSpaceBit) != 0; + HasPositionX = (bitset & k_PositionXBit) != 0; + HasPositionY = (bitset & k_PositionYBit) != 0; + HasPositionZ = (bitset & k_PositionZBit) != 0; + HasPositionChange = HasPositionX || HasPositionY || HasPositionZ; + HasRotAngleX = (bitset & k_RotAngleXBit) != 0; + HasRotAngleY = (bitset & k_RotAngleYBit) != 0; + HasRotAngleZ = (bitset & k_RotAngleZBit) != 0; + HasRotAngleChange = HasRotAngleX || HasRotAngleY || HasRotAngleZ; + SetHasScale(Axis.X, (bitset & k_ScaleXBit) != 0); + SetHasScale(Axis.Y, (bitset & k_ScaleYBit) != 0); + SetHasScale(Axis.Z, (bitset & k_ScaleZBit) != 0); + IsTeleportingNextFrame = (bitset & k_TeleportingBit) != 0; + UseInterpolation = (bitset & k_Interpolate) != 0; + QuaternionSync = (bitset & k_QuaternionSync) != 0; + QuaternionCompression = (bitset & k_QuaternionCompress) != 0; + UseHalfFloatPrecision = (bitset & k_UseHalfFloats) != 0; + IsSynchronizing = (bitset & k_Synchronization) != 0; + UsePositionSlerp = (bitset & k_PositionSlerp) != 0; + IsParented = (bitset & k_IsParented) != 0; + SynchronizeBaseHalfFloat = (bitset & k_SynchBaseHalfFloat) != 0; + ReliableSequenced = (bitset & k_ReliableSequenced) != 0; + UseUnreliableDeltas = (bitset & k_UseUnreliableDeltas) != 0; + UnreliableFrameSync = (bitset & k_UnreliableFrameSync) != 0; + SwitchTransformSpaceWhenParented = (bitset & k_SwitchTransformSpaceWhenParented) != 0; + TrackByStateId = (bitset & k_TrackStateId) != 0; + } } #endregion @@ -2281,6 +2194,7 @@ private bool CheckForStateChange(ref NetworkTransformState networkState, ref Tra networkState.HasPositionZ = true; isPositionDirty = true; } + networkState.HasPositionChange = isPositionDirty; } else if (SynchronizePosition) { @@ -2386,6 +2300,7 @@ private bool CheckForStateChange(ref NetworkTransformState networkState, ref Tra networkState.HasPositionX = SyncPositionX; networkState.HasPositionY = SyncPositionY; networkState.HasPositionZ = SyncPositionZ; + networkState.HasPositionChange = SyncPositionX || SyncPositionY || SyncPositionZ; } } @@ -2411,6 +2326,7 @@ private bool CheckForStateChange(ref NetworkTransformState networkState, ref Tra networkState.HasRotAngleZ = true; isRotationDirty = true; } + networkState.HasRotAngleChange = isRotationDirty; } else if (SynchronizeRotation) { @@ -2432,9 +2348,7 @@ private bool CheckForStateChange(ref NetworkTransformState networkState, ref Tra if (isRotationDirty) { networkState.Rotation = rotation; - networkState.HasRotAngleX = true; - networkState.HasRotAngleY = true; - networkState.HasRotAngleZ = true; + networkState.MarkChanged(AxialType.Rotation, true); } } @@ -2474,6 +2388,7 @@ private bool CheckForStateChange(ref NetworkTransformState networkState, ref Tra networkState.HasScaleZ = true; isScaleDirty = true; } + networkState.HasScaleChange = isScaleDirty; } else if (SynchronizeScale) { @@ -2484,7 +2399,7 @@ private bool CheckForStateChange(ref NetworkTransformState networkState, ref Tra { isScaleDirty = true; networkState.Scale[i] = scale[i]; - networkState.SetHasScale(i, i == 0 ? SyncScaleX : i == 1 ? SyncScaleY : SyncScaleZ); + networkState.SetHasScale((Axis)i, i == 0 ? SyncScaleX : i == 1 ? SyncScaleY : SyncScaleZ); } } } @@ -2502,9 +2417,7 @@ private bool CheckForStateChange(ref NetworkTransformState networkState, ref Tra { networkState.Scale = transform.localScale; } - networkState.HasScaleX = true; - networkState.HasScaleY = true; - networkState.HasScaleZ = true; + networkState.MarkChanged(AxialType.Scale, true); isScaleDirty = true; } isDirty |= isPositionDirty || isRotationDirty || isScaleDirty; @@ -2735,7 +2648,7 @@ protected internal void ApplyAuthoritativeState() { for (int i = 0; i < 3; i++) { - if (m_LocalAuthoritativeNetworkState.HasScale(i)) + if (m_LocalAuthoritativeNetworkState.HasScale((Axis)i)) { adjustedScale[i] = m_LocalAuthoritativeNetworkState.Scale[i]; } @@ -3190,7 +3103,7 @@ internal void ApplyUpdatedState(NetworkTransformState newState) { for (int i = 0; i < 3; i++) { - if (m_LocalAuthoritativeNetworkState.HasScale(i)) + if (m_LocalAuthoritativeNetworkState.HasScale((Axis)i)) { currentScale[i] = m_LocalAuthoritativeNetworkState.Scale[i]; } diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/NetworkTransformStateTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/NetworkTransformStateTests.cs index 6ab913866b..51278d605d 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/NetworkTransformStateTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/NetworkTransformStateTests.cs @@ -1,13 +1,190 @@ -#if !MULTIPLAYER_TOOLS +using System; using NUnit.Framework; +using Unity.Collections; using Unity.Netcode.Components; using Unity.Netcode.TestHelpers.Runtime; using UnityEngine; +using Object = UnityEngine.Object; namespace Unity.Netcode.RuntimeTests { + // These tests do not need to run against the Rust server. + [IgnoreIfServiceEnvironmentVariableSet] + internal class NetworkTransformStateTests + { + [Test] + public void NetworkTransformStateFlags() + { + // The current number of flags on the NetworkTransformState + var numFlags = 23; + + var indexValues = new uint[numFlags]; + + var currentFlag = (uint)0x00000001; + for (int j = 0; j < numFlags - 1; j++) + { + indexValues[j] = currentFlag; + currentFlag = currentFlag << 1; + } + + // TrackByStateId is unique + indexValues[numFlags - 1] = 0x10000000; + + var boolSet = new bool[numFlags]; + + InlinedBitmathSerialization(ref numFlags, ref indexValues, ref boolSet); + } + + + private void InlinedBitmathSerialization(ref int numFlags, ref uint[] indexValues, ref bool[] boolSet) + { + NetworkTransform.NetworkTransformState transformState; + FastBufferWriter writer; + FastBufferReader reader; + // Test setting one at a time. + for (int j = 0; j < numFlags; j++) + { + // reset previous test if needed + if (j > 0) + { + boolSet[j - 1] = false; + } + + boolSet[j] = true; + + transformState = new NetworkTransform.NetworkTransformState() + { + InLocalSpace = boolSet[0], + HasPositionX = boolSet[1], + HasPositionY = boolSet[2], + HasPositionZ = boolSet[3], + HasRotAngleX = boolSet[4], + HasRotAngleY = boolSet[5], + HasRotAngleZ = boolSet[6], + HasScaleX = boolSet[7], + HasScaleY = boolSet[8], + HasScaleZ = boolSet[9], + IsTeleportingNextFrame = boolSet[10], + UseInterpolation = boolSet[11], + QuaternionSync = boolSet[12], + QuaternionCompression = boolSet[13], + UseHalfFloatPrecision = boolSet[14], + IsSynchronizing = boolSet[15], + UsePositionSlerp = boolSet[16], + IsParented = boolSet[17], + SynchronizeBaseHalfFloat = boolSet[18], + ReliableSequenced = boolSet[19], + UseUnreliableDeltas = boolSet[20], + UnreliableFrameSync = boolSet[21], + TrackByStateId = boolSet[22], + }; + + writer = new FastBufferWriter(64, Allocator.Temp); + transformState.SerializeBitset(ref writer); + + // Test the bitset representation of the serialization matches the pre-refactor serialization + reader = new FastBufferReader(writer, Allocator.None); + reader.ReadValueSafe(out uint serializedBitset); + + Assert.True((serializedBitset & indexValues[j]) == indexValues[j], $"[FlagTest][Individual] Set flag value {indexValues[j]} at index {j}, but BitSet value did not match!"); + + // reset the reader to the beginning of the buffer + reader.Seek(0); + + // Test the deserialized values match the original values + var deserialized = new NetworkTransform.NetworkTransformState(); + deserialized.DeserializeBitset(ref reader); + + AssertTransformStateEquals(boolSet, deserialized, "Flag serialization"); + } + + // Test setting all flag values + transformState = new NetworkTransform.NetworkTransformState() + { + InLocalSpace = true, + HasPositionX = true, + HasPositionY = true, + HasPositionZ = true, + HasRotAngleX = true, + HasRotAngleY = true, + HasRotAngleZ = true, + HasScaleX = true, + HasScaleY = true, + HasScaleZ = true, + IsTeleportingNextFrame = true, + UseInterpolation = true, + QuaternionSync = true, + QuaternionCompression = true, + UseHalfFloatPrecision = true, + IsSynchronizing = true, + UsePositionSlerp = true, + IsParented = true, + SynchronizeBaseHalfFloat = true, + ReliableSequenced = true, + UseUnreliableDeltas = true, + UnreliableFrameSync = true, + TrackByStateId = true, + }; + + writer = new FastBufferWriter(64, Allocator.Temp); + transformState.SerializeBitset(ref writer); + var serializedBuffer = writer.ToArray(); + + // Use a uint to set all bits to true in a legacy style bitset + uint bitset = 0; + for (int i = 0; i < numFlags; i++) + { + bitset |= indexValues[i]; + } + + var legacyBitsetWriter = new FastBufferWriter(64, Allocator.Temp); + legacyBitsetWriter.WriteValueSafe(bitset); + + // Test refactored serialization matches pre-refactor flag serialization + Assert.AreEqual(legacyBitsetWriter.ToArray(), serializedBuffer, "[Flag serialization] Serialized NetworkTransformState doesn't match original serialization!"); + + + var deserializedState = new NetworkTransform.NetworkTransformState(); + + reader = new FastBufferReader(legacyBitsetWriter, Allocator.None); + deserializedState.DeserializeBitset(ref reader); + + Array.Fill(boolSet, true); + AssertTransformStateEquals(boolSet, deserializedState, "Read bitset"); + } + private void AssertTransformStateEquals(bool[] expected, NetworkTransform.NetworkTransformState actual, string testName) + { + Assert.AreEqual(expected[0], actual.InLocalSpace, $"{testName} Flag {nameof(NetworkTransform.NetworkTransformState.InLocalSpace)} is incorrect!"); + Assert.AreEqual(expected[1], actual.HasPositionX, $"{testName} Flag {nameof(NetworkTransform.NetworkTransformState.HasPositionX)} is incorrect!"); + Assert.AreEqual(expected[2], actual.HasPositionY, $"{testName} Flag {nameof(NetworkTransform.NetworkTransformState.HasPositionY)} is incorrect!"); + Assert.AreEqual(expected[3], actual.HasPositionZ, $"{testName} Flag {nameof(NetworkTransform.NetworkTransformState.HasPositionZ)} is incorrect!"); + Assert.AreEqual(expected[4], actual.HasRotAngleX, $"{testName} Flag {nameof(NetworkTransform.NetworkTransformState.HasRotAngleX)} is incorrect!"); + Assert.AreEqual(expected[5], actual.HasRotAngleY, $"{testName} Flag {nameof(NetworkTransform.NetworkTransformState.HasRotAngleY)} is incorrect!"); + Assert.AreEqual(expected[6], actual.HasRotAngleZ, $"{testName} Flag {nameof(NetworkTransform.NetworkTransformState.HasRotAngleZ)} is incorrect!"); + Assert.AreEqual(expected[7], actual.HasScaleX, $"{testName} Flag {nameof(NetworkTransform.NetworkTransformState.HasScaleX)} is incorrect!"); + Assert.AreEqual(expected[8], actual.HasScaleY, $"{testName} Flag {nameof(NetworkTransform.NetworkTransformState.HasScaleY)} is incorrect!"); + Assert.AreEqual(expected[9], actual.HasScaleZ, $"{testName} Flag {nameof(NetworkTransform.NetworkTransformState.HasScaleZ)} is incorrect!"); + Assert.AreEqual(expected[10], actual.IsTeleportingNextFrame, $"{testName} Flag {nameof(NetworkTransform.NetworkTransformState.IsTeleportingNextFrame)} is incorrect!"); + Assert.AreEqual(expected[11], actual.UseInterpolation, $"{testName} Flag {nameof(NetworkTransform.NetworkTransformState.UseInterpolation)} is incorrect!"); + Assert.AreEqual(expected[12], actual.QuaternionSync, $"{testName} Flag {nameof(NetworkTransform.NetworkTransformState.QuaternionSync)} is incorrect!"); + Assert.AreEqual(expected[13], actual.QuaternionCompression, $"{testName} Flag {nameof(NetworkTransform.NetworkTransformState.QuaternionCompression)} is incorrect!"); + Assert.AreEqual(expected[14], actual.UseHalfFloatPrecision, $"{testName} Flag {nameof(NetworkTransform.NetworkTransformState.UseHalfFloatPrecision)} is incorrect!"); + Assert.AreEqual(expected[15], actual.IsSynchronizing, $"{testName} Flag {nameof(NetworkTransform.NetworkTransformState.IsSynchronizing)} is incorrect!"); + Assert.AreEqual(expected[16], actual.UsePositionSlerp, $"{testName} Flag {nameof(NetworkTransform.NetworkTransformState.UsePositionSlerp)} is incorrect!"); + Assert.AreEqual(expected[17], actual.IsParented, $"{testName} Flag {nameof(NetworkTransform.NetworkTransformState.IsParented)} is incorrect!"); + Assert.AreEqual(expected[18], actual.SynchronizeBaseHalfFloat, $"{testName} Flag {nameof(NetworkTransform.NetworkTransformState.SynchronizeBaseHalfFloat)} is incorrect!"); + Assert.AreEqual(expected[19], actual.ReliableSequenced, $"{testName} Flag {nameof(NetworkTransform.NetworkTransformState.ReliableSequenced)} is incorrect!"); + Assert.AreEqual(expected[20], actual.UseUnreliableDeltas, $"{testName} Flag {nameof(NetworkTransform.NetworkTransformState.UseUnreliableDeltas)} is incorrect!"); + Assert.AreEqual(expected[21], actual.UnreliableFrameSync, $"{testName} Flag {nameof(NetworkTransform.NetworkTransformState.UnreliableFrameSync)} is incorrect!"); + Assert.AreEqual(expected[22], actual.TrackByStateId, $"{testName} Flag {nameof(NetworkTransform.NetworkTransformState.TrackByStateId)} is incorrect!"); + } + + } + + // These tests do not need to run against the Rust server. + [IgnoreIfServiceEnvironmentVariableSet] [TestFixture(TransformSpace.World, Precision.Full, Rotation.Euler)] [TestFixture(TransformSpace.World, Precision.Half, Rotation.Euler)] [TestFixture(TransformSpace.Local, Precision.Full, Rotation.Euler)] @@ -16,7 +193,7 @@ namespace Unity.Netcode.RuntimeTests [TestFixture(TransformSpace.World, Precision.Half, Rotation.Quaternion)] [TestFixture(TransformSpace.Local, Precision.Full, Rotation.Quaternion)] [TestFixture(TransformSpace.Local, Precision.Half, Rotation.Quaternion)] - internal class NetworkTransformStateTests + internal class NetworkTransformStateConfigurationTests { public enum SyncAxis { @@ -78,14 +255,7 @@ public enum Precision private Precision m_Precision; private Rotation m_Rotation; - [OneTimeSetUp] - public void OneTimeSetup() - { - // This test does not need to run against the Rust server. - NetcodeIntegrationTestHelpers.IgnoreIfServiceEnviromentVariableSet(); - } - - public NetworkTransformStateTests(TransformSpace transformSpace, Precision precision, Rotation rotation) + public NetworkTransformStateConfigurationTests(TransformSpace transformSpace, Precision precision, Rotation rotation) { m_TransformSpace = transformSpace; m_Precision = precision; @@ -99,125 +269,6 @@ private bool WillAnAxisBeSynchronized(ref NetworkTransform networkTransform) networkTransform.SyncPositionX || networkTransform.SyncPositionY || networkTransform.SyncPositionZ; } - [Test] - public void NetworkTransformStateFlags() - { - var indexValues = new System.Collections.Generic.List(); - var currentFlag = (uint)0x00000001; - for (int j = 0; j < 18; j++) - { - indexValues.Add(currentFlag); - currentFlag = currentFlag << 1; - } - - // TrackByStateId is unique - indexValues.Add(0x10000000); - - var boolSet = new System.Collections.Generic.List(); - var transformState = new NetworkTransform.NetworkTransformState(); - // Test setting one at a time. - for (int j = 0; j < 19; j++) - { - boolSet = new System.Collections.Generic.List(); - for (int i = 0; i < 19; i++) - { - if (i == j) - { - boolSet.Add(true); - } - else - { - boolSet.Add(false); - } - } - transformState = new NetworkTransform.NetworkTransformState() - { - InLocalSpace = boolSet[0], - HasPositionX = boolSet[1], - HasPositionY = boolSet[2], - HasPositionZ = boolSet[3], - HasRotAngleX = boolSet[4], - HasRotAngleY = boolSet[5], - HasRotAngleZ = boolSet[6], - HasScaleX = boolSet[7], - HasScaleY = boolSet[8], - HasScaleZ = boolSet[9], - IsTeleportingNextFrame = boolSet[10], - UseInterpolation = boolSet[11], - QuaternionSync = boolSet[12], - QuaternionCompression = boolSet[13], - UseHalfFloatPrecision = boolSet[14], - IsSynchronizing = boolSet[15], - UsePositionSlerp = boolSet[16], - IsParented = boolSet[17], - TrackByStateId = boolSet[18], - }; - Assert.True((transformState.BitSet & indexValues[j]) == indexValues[j], $"[FlagTest][Individual] Set flag value {indexValues[j]} at index {j}, but BitSet value did not match!"); - } - - // Test setting all flag values - boolSet = new System.Collections.Generic.List(); - for (int i = 0; i < 19; i++) - { - boolSet.Add(true); - } - - transformState = new NetworkTransform.NetworkTransformState() - { - InLocalSpace = boolSet[0], - HasPositionX = boolSet[1], - HasPositionY = boolSet[2], - HasPositionZ = boolSet[3], - HasRotAngleX = boolSet[4], - HasRotAngleY = boolSet[5], - HasRotAngleZ = boolSet[6], - HasScaleX = boolSet[7], - HasScaleY = boolSet[8], - HasScaleZ = boolSet[9], - IsTeleportingNextFrame = boolSet[10], - UseInterpolation = boolSet[11], - QuaternionSync = boolSet[12], - QuaternionCompression = boolSet[13], - UseHalfFloatPrecision = boolSet[14], - IsSynchronizing = boolSet[15], - UsePositionSlerp = boolSet[16], - IsParented = boolSet[17], - TrackByStateId = boolSet[18], - }; - - for (int j = 0; j < 19; j++) - { - Assert.True((transformState.BitSet & indexValues[j]) == indexValues[j], $"[FlagTest][All] All flag values are set but failed to detect flag value {indexValues[j]}!"); - } - - // Test getting all flag values - transformState = new NetworkTransform.NetworkTransformState(); - for (int i = 0; i < 19; i++) - { - transformState.BitSet |= indexValues[i]; - } - - Assert.True(transformState.InLocalSpace, $"[FlagTest][Get] Failed to detect {nameof(NetworkTransform.NetworkTransformState.InLocalSpace)}!"); - Assert.True(transformState.HasPositionX, $"[FlagTest][Get] Failed to detect {nameof(NetworkTransform.NetworkTransformState.HasPositionX)}!"); - Assert.True(transformState.HasPositionY, $"[FlagTest][Get] Failed to detect {nameof(NetworkTransform.NetworkTransformState.HasPositionY)}!"); - Assert.True(transformState.HasPositionZ, $"[FlagTest][Get] Failed to detect {nameof(NetworkTransform.NetworkTransformState.HasPositionZ)}!"); - Assert.True(transformState.HasRotAngleX, $"[FlagTest][Get] Failed to detect {nameof(NetworkTransform.NetworkTransformState.HasRotAngleX)}!"); - Assert.True(transformState.HasRotAngleY, $"[FlagTest][Get] Failed to detect {nameof(NetworkTransform.NetworkTransformState.HasRotAngleY)}!"); - Assert.True(transformState.HasRotAngleZ, $"[FlagTest][Get] Failed to detect {nameof(NetworkTransform.NetworkTransformState.HasRotAngleZ)}!"); - Assert.True(transformState.HasScaleX, $"[FlagTest][Get] Failed to detect {nameof(NetworkTransform.NetworkTransformState.HasScaleX)}!"); - Assert.True(transformState.HasScaleY, $"[FlagTest][Get] Failed to detect {nameof(NetworkTransform.NetworkTransformState.HasScaleY)}!"); - Assert.True(transformState.HasScaleZ, $"[FlagTest][Get] Failed to detect {nameof(NetworkTransform.NetworkTransformState.HasScaleZ)}!"); - Assert.True(transformState.IsTeleportingNextFrame, $"[FlagTest][Get] Failed to detect {nameof(NetworkTransform.NetworkTransformState.IsTeleportingNextFrame)}!"); - Assert.True(transformState.UseInterpolation, $"[FlagTest][Get] Failed to detect {nameof(NetworkTransform.NetworkTransformState.UseInterpolation)}!"); - Assert.True(transformState.QuaternionSync, $"[FlagTest][Get] Failed to detect {nameof(NetworkTransform.NetworkTransformState.QuaternionSync)}!"); - Assert.True(transformState.QuaternionCompression, $"[FlagTest][Get] Failed to detect {nameof(NetworkTransform.NetworkTransformState.QuaternionCompression)}!"); - Assert.True(transformState.UseHalfFloatPrecision, $"[FlagTest][Get] Failed to detect {nameof(NetworkTransform.NetworkTransformState.UseHalfFloatPrecision)}!"); - Assert.True(transformState.IsSynchronizing, $"[FlagTest][Get] Failed to detect {nameof(NetworkTransform.NetworkTransformState.IsSynchronizing)}!"); - Assert.True(transformState.UsePositionSlerp, $"[FlagTest][Get] Failed to detect {nameof(NetworkTransform.NetworkTransformState.UsePositionSlerp)}!"); - Assert.True(transformState.IsParented, $"[FlagTest][Get] Failed to detect {nameof(NetworkTransform.NetworkTransformState.IsParented)}!"); - Assert.True(transformState.TrackByStateId, $"[FlagTest][Get] Failed to detect {nameof(NetworkTransform.NetworkTransformState.TrackByStateId)}!"); - } - [Test] public void TestSyncAxes([Values] SynchronizationType synchronizationType, [Values] SyncAxis syncAxis) @@ -916,4 +967,3 @@ public void TestThresholds( } } } -#endif diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/TestHelpers/NUnitExtensions.meta b/com.unity.netcode.gameobjects/Tests/Runtime/TestHelpers/NUnitExtensions.meta new file mode 100644 index 0000000000..6313602724 --- /dev/null +++ b/com.unity.netcode.gameobjects/Tests/Runtime/TestHelpers/NUnitExtensions.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: d44194b68d19479ab0a4427dc5211591 +timeCreated: 1757013308 \ No newline at end of file diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/TestHelpers/NUnitExtensions/IgnoreIfServiceEnvironmentVariableSetAttribute.cs b/com.unity.netcode.gameobjects/Tests/Runtime/TestHelpers/NUnitExtensions/IgnoreIfServiceEnvironmentVariableSetAttribute.cs new file mode 100644 index 0000000000..7af65fe8d1 --- /dev/null +++ b/com.unity.netcode.gameobjects/Tests/Runtime/TestHelpers/NUnitExtensions/IgnoreIfServiceEnvironmentVariableSetAttribute.cs @@ -0,0 +1,26 @@ +using System; +using NUnit.Framework; +using NUnit.Framework.Interfaces; +using NUnit.Framework.Internal; + +namespace Unity.Netcode.TestHelpers.Runtime +{ + [AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = true)] + public class IgnoreIfServiceEnvironmentVariableSetAttribute : NUnitAttribute, IApplyToTest + { + public void ApplyToTest(Test test) + { + // NotRunnable is the more weighty status, always respect it first + if (test.RunState == RunState.NotRunnable) + { + return; + } + + if (bool.TryParse(NetcodeIntegrationTestHelpers.GetCMBServiceEnvironentVariable(), out var isTrue) && isTrue) + { + test.RunState = RunState.Ignored; + test.Properties.Set("_SKIPREASON", NetcodeIntegrationTestHelpers.IgnoredForCmbServiceReason); + } + } + } +} diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/TestHelpers/NUnitExtensions/IgnoreIfServiceEnvironmentVariableSetAttribute.cs.meta b/com.unity.netcode.gameobjects/Tests/Runtime/TestHelpers/NUnitExtensions/IgnoreIfServiceEnvironmentVariableSetAttribute.cs.meta new file mode 100644 index 0000000000..c4efe43d74 --- /dev/null +++ b/com.unity.netcode.gameobjects/Tests/Runtime/TestHelpers/NUnitExtensions/IgnoreIfServiceEnvironmentVariableSetAttribute.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: d27ee61f70254bc68185f63867a8dd3f +timeCreated: 1757013351 \ No newline at end of file diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/TestHelpers/NetcodeIntegrationTestHelpers.cs b/com.unity.netcode.gameobjects/Tests/Runtime/TestHelpers/NetcodeIntegrationTestHelpers.cs index c8e40ae256..d09d1da9b6 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/TestHelpers/NetcodeIntegrationTestHelpers.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/TestHelpers/NetcodeIntegrationTestHelpers.cs @@ -201,6 +201,8 @@ internal static string GetCMBServiceEnvironentVariable() #endif } + internal static readonly string IgnoredForCmbServiceReason = "[CMB-Service Test Run] Skipping non-distributed authority test."; + /// /// Use for non derived integration tests to automatically ignore the /// test if running against a CMB server. @@ -209,7 +211,7 @@ internal static void IgnoreIfServiceEnviromentVariableSet() { if (bool.TryParse(GetCMBServiceEnvironentVariable(), out bool isTrue) ? isTrue : false) { - Assert.Ignore("[CMB-Server Test Run] Skipping non-distributed authority test."); + Assert.Ignore(IgnoredForCmbServiceReason); } } From c938e7f0aad225f7b65dfc49d61712fdfc4218b7 Mon Sep 17 00:00:00 2001 From: Emma Date: Fri, 31 Oct 2025 13:26:24 -0400 Subject: [PATCH 2/7] Update CHANGELOG --- com.unity.netcode.gameobjects/CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/com.unity.netcode.gameobjects/CHANGELOG.md b/com.unity.netcode.gameobjects/CHANGELOG.md index 6585a7dc93..8b7403532d 100644 --- a/com.unity.netcode.gameobjects/CHANGELOG.md +++ b/com.unity.netcode.gameobjects/CHANGELOG.md @@ -13,6 +13,8 @@ Additional documentation and release notes are available at [Multiplayer Documen ### Changed +- Improve performance of `NetworkTransformState`. (#3770) + ### Deprecated From a34111950dcd8241c008bbed4d6750872ef4db6b Mon Sep 17 00:00:00 2001 From: Noel Stephens Date: Fri, 31 Oct 2025 12:48:06 -0500 Subject: [PATCH 3/7] Update IgnoreIfServiceEnvironmentVariableSetAttribute.cs --- ...fServiceEnvironmentVariableSetAttribute.cs | 34 +++++++++---------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/TestHelpers/NUnitExtensions/IgnoreIfServiceEnvironmentVariableSetAttribute.cs b/com.unity.netcode.gameobjects/Tests/Runtime/TestHelpers/NUnitExtensions/IgnoreIfServiceEnvironmentVariableSetAttribute.cs index 7af65fe8d1..d8971aecd2 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/TestHelpers/NUnitExtensions/IgnoreIfServiceEnvironmentVariableSetAttribute.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/TestHelpers/NUnitExtensions/IgnoreIfServiceEnvironmentVariableSetAttribute.cs @@ -5,22 +5,22 @@ namespace Unity.Netcode.TestHelpers.Runtime { - [AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = true)] - public class IgnoreIfServiceEnvironmentVariableSetAttribute : NUnitAttribute, IApplyToTest - { - public void ApplyToTest(Test test) - { - // NotRunnable is the more weighty status, always respect it first - if (test.RunState == RunState.NotRunnable) - { - return; - } + [AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = true)] + public class IgnoreIfServiceEnvironmentVariableSetAttribute : NUnitAttribute, IApplyToTest + { + public void ApplyToTest(Test test) + { + // NotRunnable is the more weighty status, always respect it first + if (test.RunState == RunState.NotRunnable) + { + return; + } - if (bool.TryParse(NetcodeIntegrationTestHelpers.GetCMBServiceEnvironentVariable(), out var isTrue) && isTrue) - { - test.RunState = RunState.Ignored; - test.Properties.Set("_SKIPREASON", NetcodeIntegrationTestHelpers.IgnoredForCmbServiceReason); - } - } - } + if (bool.TryParse(NetcodeIntegrationTestHelpers.GetCMBServiceEnvironentVariable(), out var isTrue) && isTrue) + { + test.RunState = RunState.Ignored; + test.Properties.Set("_SKIPREASON", NetcodeIntegrationTestHelpers.IgnoredForCmbServiceReason); + } + } + } } From 85785bcded07b3a96452ee3508c02ca9b5ae3adb Mon Sep 17 00:00:00 2001 From: Emma Date: Mon, 3 Nov 2025 12:17:57 -0500 Subject: [PATCH 4/7] Apply suggestions from code review Co-authored-by: Noel Stephens --- .../Runtime/Components/NetworkTransform.cs | 4 ++-- .../IgnoreIfServiceEnvironmentVariableSetAttribute.cs | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/com.unity.netcode.gameobjects/Runtime/Components/NetworkTransform.cs b/com.unity.netcode.gameobjects/Runtime/Components/NetworkTransform.cs index 2cd74137bc..da61cd9f04 100644 --- a/com.unity.netcode.gameobjects/Runtime/Components/NetworkTransform.cs +++ b/com.unity.netcode.gameobjects/Runtime/Components/NetworkTransform.cs @@ -846,7 +846,7 @@ public void NetworkSerialize(BufferSerializer serializer) where T : IReade } [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void SerializeBitset(ref FastBufferWriter writer) + internal void SerializeBitset(ref FastBufferWriter writer) { uint bitset = 0; @@ -879,7 +879,7 @@ public void SerializeBitset(ref FastBufferWriter writer) } [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void DeserializeBitset(ref FastBufferReader reader) + internal void DeserializeBitset(ref FastBufferReader reader) { ByteUnpacker.ReadValueBitPacked(reader, out uint bitset); diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/TestHelpers/NUnitExtensions/IgnoreIfServiceEnvironmentVariableSetAttribute.cs b/com.unity.netcode.gameobjects/Tests/Runtime/TestHelpers/NUnitExtensions/IgnoreIfServiceEnvironmentVariableSetAttribute.cs index d8971aecd2..8f48a20666 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/TestHelpers/NUnitExtensions/IgnoreIfServiceEnvironmentVariableSetAttribute.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/TestHelpers/NUnitExtensions/IgnoreIfServiceEnvironmentVariableSetAttribute.cs @@ -6,7 +6,7 @@ namespace Unity.Netcode.TestHelpers.Runtime { [AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = true)] - public class IgnoreIfServiceEnvironmentVariableSetAttribute : NUnitAttribute, IApplyToTest + internal class IgnoreIfServiceEnvironmentVariableSetAttribute : NUnitAttribute, IApplyToTest { public void ApplyToTest(Test test) { From cc5d84b74fca64142cd9bd3f295bd7e8acf9d5bd Mon Sep 17 00:00:00 2001 From: Noel Stephens Date: Mon, 3 Nov 2025 12:48:16 -0600 Subject: [PATCH 5/7] style removing and adding whitespaces. --- .../Runtime/Components/NetworkTransform.cs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/com.unity.netcode.gameobjects/Runtime/Components/NetworkTransform.cs b/com.unity.netcode.gameobjects/Runtime/Components/NetworkTransform.cs index da61cd9f04..4e473b7bf3 100644 --- a/com.unity.netcode.gameobjects/Runtime/Components/NetworkTransform.cs +++ b/com.unity.netcode.gameobjects/Runtime/Components/NetworkTransform.cs @@ -190,7 +190,7 @@ public struct NetworkTransformState : INetworkSerializable /// /// When quaternion synchronization is enabled all axis are always updated. /// - public bool HasRotAngleChange{ get; internal set; } + public bool HasRotAngleChange { get; internal set; } // Scale /// @@ -885,12 +885,12 @@ internal void DeserializeBitset(ref FastBufferReader reader) InLocalSpace = (bitset & k_InLocalSpaceBit) != 0; HasPositionX = (bitset & k_PositionXBit) != 0; - HasPositionY = (bitset & k_PositionYBit) != 0; - HasPositionZ = (bitset & k_PositionZBit) != 0; + HasPositionY = (bitset & k_PositionYBit) != 0; + HasPositionZ = (bitset & k_PositionZBit) != 0; HasPositionChange = HasPositionX || HasPositionY || HasPositionZ; - HasRotAngleX = (bitset & k_RotAngleXBit) != 0; - HasRotAngleY = (bitset & k_RotAngleYBit) != 0; - HasRotAngleZ = (bitset & k_RotAngleZBit) != 0; + HasRotAngleX = (bitset & k_RotAngleXBit) != 0; + HasRotAngleY = (bitset & k_RotAngleYBit) != 0; + HasRotAngleZ = (bitset & k_RotAngleZBit) != 0; HasRotAngleChange = HasRotAngleX || HasRotAngleY || HasRotAngleZ; SetHasScale(Axis.X, (bitset & k_ScaleXBit) != 0); SetHasScale(Axis.Y, (bitset & k_ScaleYBit) != 0); From 8cae595d681c7342ba02c647641aff8ac853c7e9 Mon Sep 17 00:00:00 2001 From: Noel Stephens Date: Mon, 3 Nov 2025 12:51:46 -0600 Subject: [PATCH 6/7] update Merge fix. --- .../Tests/Runtime/NetworkTransform/NetworkTransformStateTests.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/NetworkTransformStateTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/NetworkTransformStateTests.cs index 8423b94b65..51278d605d 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/NetworkTransformStateTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/NetworkTransformStateTests.cs @@ -1,3 +1,4 @@ +using System; using NUnit.Framework; using Unity.Collections; using Unity.Netcode.Components; From 3ac14391b748f6641a2b72f0ffc5ddeacf6f11d5 Mon Sep 17 00:00:00 2001 From: Emma Date: Mon, 3 Nov 2025 18:27:35 -0500 Subject: [PATCH 7/7] Fix cumulative state --- .../Runtime/Components/NetworkTransform.cs | 172 +++++++++++++----- .../NetworkTransformGeneral.cs | 6 +- .../NetworkTransformStateTests.cs | 13 +- 3 files changed, 142 insertions(+), 49 deletions(-) diff --git a/com.unity.netcode.gameobjects/Runtime/Components/NetworkTransform.cs b/com.unity.netcode.gameobjects/Runtime/Components/NetworkTransform.cs index 512e62c524..311522aad7 100644 --- a/com.unity.netcode.gameobjects/Runtime/Components/NetworkTransform.cs +++ b/com.unity.netcode.gameobjects/Runtime/Components/NetworkTransform.cs @@ -69,7 +69,7 @@ public struct NetworkTransformState : INetworkSerializable private const int k_ReliableSequenced = 0x00080000; private const int k_UseUnreliableDeltas = 0x00100000; private const int k_UnreliableFrameSync = 0x00200000; - private const int k_SwitchTransformSpaceWhenParented = 0x0400000; + private const int k_SwitchTransformSpaceWhenParented = 0x00400000; // (Internal Debugging) When set each state update will contain a state identifier private const int k_TrackStateId = 0x10000000; @@ -190,7 +190,7 @@ public struct NetworkTransformState : INetworkSerializable /// /// When quaternion synchronization is enabled all axis are always updated. /// - public bool HasRotAngleChange { get; internal set; } + public bool HasRotAngleChange { get; internal set; } // Scale /// @@ -221,22 +221,54 @@ internal void MarkChanged(AxialType axialType, bool changed) HasPositionX = changed; HasPositionY = changed; HasPositionZ = changed; - HasPositionChange = changed; break; case AxialType.Rotation: HasRotAngleX = changed; HasRotAngleY = changed; HasRotAngleZ = changed; - HasRotAngleChange = changed; break; case AxialType.Scale: HasScaleX = changed; HasScaleY = changed; HasScaleZ = changed; - HasScaleChange = changed; break; } } + internal void SetHasPosition(Axis axis, bool changed) + { + switch (axis) + { + case Axis.X: + HasPositionX = changed; + break; + case Axis.Y: + HasPositionY = changed; + break; + case Axis.Z: + HasPositionZ = changed; + break; + } + HasPositionChange = HasPositionX || HasPositionY || HasPositionZ; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal void SetHasRotation(Axis axis, bool changed) + { + switch (axis) + { + case Axis.X: + HasRotAngleX = changed; + break; + case Axis.Y: + HasRotAngleY = changed; + break; + case Axis.Z: + HasRotAngleZ = changed; + break; + } + HasRotAngleChange = HasRotAngleX || HasRotAngleY || HasRotAngleZ; + } + internal void SetHasScale(Axis axis, bool changed) { switch (axis) @@ -367,8 +399,8 @@ public bool IsReliableStateUpdate() /// /// Clear everything but flags that should persist between state updates until changed by authority. - /// Persistent (non-cleared) flags are , , , - /// , , , + /// Persistent (non-cleared) flags are , , , , + /// , , /// internal void ClearBitSetForNextTick() { @@ -382,6 +414,7 @@ internal void ClearBitSetForNextTick() ReliableSequenced = false; UnreliableFrameSync = false; TrackByStateId = false; + IsDirty = false; } /// @@ -554,6 +587,11 @@ public void NetworkSerialize(BufferSerializer serializer) where T : IReade bitSetAndTickSize = m_Writer.Position - positionStart; lastPosition = m_Writer.Position; } + else + { + bitSetAndTickSize = m_Reader.Position - positionStart; + lastPosition = m_Reader.Position; + } #endif // If debugging states and track by state identifier is enabled, serialize the current state identifier @@ -630,6 +668,13 @@ public void NetworkSerialize(BufferSerializer serializer) where T : IReade { positionSize = m_Writer.Position - lastPosition; lastPosition = m_Writer.Position; + Debug.Log($"[Write][bitsAndTick={bitSetAndTickSize}][position={positionSize}]"); + } + else + { + positionSize = m_Reader.Position - lastPosition; + lastPosition = m_Reader.Position; + Debug.Log($"[Read][bitsAndTick={bitSetAndTickSize}][position={positionSize}]"); } #endif @@ -749,6 +794,11 @@ public void NetworkSerialize(BufferSerializer serializer) where T : IReade rotationSize = m_Writer.Position - lastPosition; lastPosition = m_Writer.Position; } + else + { + rotationSize = m_Reader.Position - lastPosition; + lastPosition = m_Reader.Position; + } #endif // Synchronize Scale @@ -827,6 +877,11 @@ public void NetworkSerialize(BufferSerializer serializer) where T : IReade scaleSize = m_Writer.Position - lastPosition; lastPosition = m_Writer.Position; } + else + { + scaleSize = m_Reader.Position - lastPosition; + lastPosition = m_Reader.Position; + } #endif // Only if we are receiving state @@ -839,10 +894,12 @@ public void NetworkSerialize(BufferSerializer serializer) where T : IReade else { LastSerializedSize = m_Writer.Position - positionStart; + } + #if NGO_NETWORKTRANSFORMSTATE_LOGWRITESIZE - Debug.Log($"[NT-WriteSize][BitsAndTick: {bitSetAndTickSize}][position: {positionSize}][rotation: {rotationSize}][scale: {scaleSize}]"); + var type = isWriting ? "Write" : "Read"; + Debug.Log($"[NT-{type}][BitsAndTick: {bitSetAndTickSize}][position: {positionSize}][rotation: {rotationSize}][scale: {scaleSize}]"); #endif - } } [MethodImpl(MethodImplOptions.AggressiveInlining)] @@ -850,16 +907,33 @@ internal void SerializeBitset(ref FastBufferWriter writer) { uint bitset = 0; + Debug.Log($"[NT] Serializing has changes: HasPositionChange: {HasPositionChange}, HasRotAngleChange: {HasRotAngleChange}, HasScaleChange: {HasScaleChange}"); + Debug.Log($"[NT] Serializing: HasRotAngleX: {HasRotAngleX}, HasRotAngleY: {HasRotAngleY}, HasRotAngleZ: {HasRotAngleZ}"); + if (InLocalSpace) { bitset |= k_InLocalSpaceBit; } - if (HasPositionX) { bitset |= k_PositionXBit; } - if (HasPositionY) { bitset |= k_PositionYBit; } - if (HasPositionZ) { bitset |= k_PositionZBit; } - if (HasRotAngleX) { bitset |= k_RotAngleXBit; } - if (HasRotAngleY) { bitset |= k_RotAngleYBit; } - if (HasRotAngleZ) { bitset |= k_RotAngleZBit; } - if (HasScaleX) { bitset |= k_ScaleXBit; } - if (HasScaleY) { bitset |= k_ScaleYBit; } - if (HasScaleZ) { bitset |= k_ScaleZBit; } + + // if (HasPositionChange) + // { + if (HasPositionX) { bitset |= k_PositionXBit; } + if (HasPositionY) { bitset |= k_PositionYBit; } + if (HasPositionZ) { bitset |= k_PositionZBit; } + // } + // + // if (HasRotAngleChange) + // { + if (HasRotAngleX) { bitset |= k_RotAngleXBit; } + if (HasRotAngleY) { bitset |= k_RotAngleYBit; } + if (HasRotAngleZ) { bitset |= k_RotAngleZBit; } + // } + // + // if (HasScaleChange) + // { + if (HasScaleX) { bitset |= k_ScaleXBit; } + if (HasScaleY) { bitset |= k_ScaleYBit; } + if (HasScaleZ) { bitset |= k_ScaleZBit; } + + // } + if (IsTeleportingNextFrame) { bitset |= k_TeleportingBit; } if (UseInterpolation) { bitset |= k_Interpolate; } if (QuaternionSync) { bitset |= k_QuaternionSync; } @@ -884,14 +958,12 @@ internal void DeserializeBitset(ref FastBufferReader reader) ByteUnpacker.ReadValueBitPacked(reader, out uint bitset); InLocalSpace = (bitset & k_InLocalSpaceBit) != 0; - HasPositionX = (bitset & k_PositionXBit) != 0; - HasPositionY = (bitset & k_PositionYBit) != 0; - HasPositionZ = (bitset & k_PositionZBit) != 0; - HasPositionChange = HasPositionX || HasPositionY || HasPositionZ; - HasRotAngleX = (bitset & k_RotAngleXBit) != 0; - HasRotAngleY = (bitset & k_RotAngleYBit) != 0; - HasRotAngleZ = (bitset & k_RotAngleZBit) != 0; - HasRotAngleChange = HasRotAngleX || HasRotAngleY || HasRotAngleZ; + SetHasPosition(Axis.X, (bitset & k_PositionXBit) != 0); + SetHasPosition(Axis.Y, (bitset & k_PositionYBit) != 0); + SetHasPosition(Axis.Z, (bitset & k_PositionZBit) != 0); + SetHasRotation(Axis.X, (bitset & k_RotAngleXBit) != 0); + SetHasRotation(Axis.Y, (bitset & k_RotAngleYBit) != 0); + SetHasRotation(Axis.Z, (bitset & k_RotAngleZBit) != 0); SetHasScale(Axis.X, (bitset & k_ScaleXBit) != 0); SetHasScale(Axis.Y, (bitset & k_ScaleYBit) != 0); SetHasScale(Axis.Z, (bitset & k_ScaleZBit) != 0); @@ -909,6 +981,9 @@ internal void DeserializeBitset(ref FastBufferReader reader) UnreliableFrameSync = (bitset & k_UnreliableFrameSync) != 0; SwitchTransformSpaceWhenParented = (bitset & k_SwitchTransformSpaceWhenParented) != 0; TrackByStateId = (bitset & k_TrackStateId) != 0; + + Debug.Log($"Deserialized has changes: HasPositionChange: {HasPositionChange}, HasRotAngleChange: {HasRotAngleChange}, HasScaleChange: {HasScaleChange}"); + } } #endregion @@ -1750,6 +1825,7 @@ protected override void OnSynchronize(ref BufferSerializer serializer) var transformToCommit = transform; // If we are using Half Float Precision, then we want to only synchronize the authority's m_HalfPositionState.FullPosition in order for // for the non-authority side to be able to properly synchronize delta position updates. + Debug.Log($"[Object-{NetworkObjectId}][CheckForStateChange][OnSynchronize] HasRotAngleX: {SynchronizeState.HasRotAngleX}, HasRotAngleChange: {SynchronizeState.HasRotAngleChange}"); CheckForStateChange(ref SynchronizeState, ref transformToCommit, true, targetClientId); SynchronizeState.NetworkSerialize(serializer); LastTickSync = SynchronizeState.GetNetworkTick(); @@ -1872,6 +1948,7 @@ private void TryCommitTransform(ref Transform transformToCommit, bool synchroniz } #endif // If the transform has deltas (returns dirty) or if an explicitly set state is pending + Debug.Log($"[Object-{NetworkObjectId}][CheckForStateChange][TryCommitTransform][#1] HasRotAngleX: {m_LocalAuthoritativeNetworkState.HasRotAngleX}, HasRotAngleChange: {m_LocalAuthoritativeNetworkState.HasRotAngleChange}"); if (m_LocalAuthoritativeNetworkState.ExplicitSet || CheckForStateChange(ref m_LocalAuthoritativeNetworkState, ref transformToCommit, synchronize, forceState: settingState)) { // If the state was explicitly set, then update the network tick to match the locally calculate tick @@ -1884,6 +1961,7 @@ private void TryCommitTransform(ref Transform transformToCommit, bool synchroniz if (SwitchTransformSpaceWhenParented && m_LocalAuthoritativeNetworkState.ExplicitSet && m_LocalAuthoritativeNetworkState.IsDirty && transform.parent != null && !m_LocalAuthoritativeNetworkState.InLocalSpace) { InLocalSpace = true; + Debug.Log($"[Object-{NetworkObjectId}][CheckForStateChange][TryCommitTransform][#2] HasRotAngleX: {m_LocalAuthoritativeNetworkState.HasRotAngleX}, HasRotAngleChange: {m_LocalAuthoritativeNetworkState.HasRotAngleChange}"); CheckForStateChange(ref m_LocalAuthoritativeNetworkState, ref transformToCommit, synchronize, forceState: true); } } @@ -1982,6 +2060,7 @@ internal NetworkTransformState ApplyLocalNetworkState(Transform transform) m_LocalAuthoritativeNetworkState.ClearBitSetForNextTick(); // Now check the transform for any threshold value changes + Debug.Log($"[Object-{NetworkObjectId}][CheckForStateChange][ApplyLocalNetworkState] HasRotAngleX: {m_LocalAuthoritativeNetworkState.HasRotAngleX}, HasRotAngleChange: {m_LocalAuthoritativeNetworkState.HasRotAngleChange}"); CheckForStateChange(ref m_LocalAuthoritativeNetworkState, ref transform); // Return the entire state to be used by the integration test @@ -2002,6 +2081,7 @@ internal bool ApplyTransformToNetworkState(ref NetworkTransformState networkStat networkState.UseUnreliableDeltas = UseUnreliableDeltas; m_HalfPositionState = new NetworkDeltaPosition(Vector3.zero, 0, math.bool3(SyncPositionX, SyncPositionY, SyncPositionZ)); + Debug.Log($"[Object-{NetworkObjectId}][CheckForStateChange][ApplyTransformToNetworkState] HasRotAngleX: {networkState.HasRotAngleX}, HasRotAngleChange: {networkState.HasRotAngleChange}"); return CheckForStateChange(ref networkState, ref transformToUse); } @@ -2037,9 +2117,19 @@ private bool CheckForStateChange(ref NetworkTransformState networkState, ref Tra var isTeleportingAndNotSynchronizing = networkState.IsTeleportingNextFrame && !isSynchronization; var isDirty = false; - var isPositionDirty = isTeleportingAndNotSynchronizing ? networkState.HasPositionChange : false; - var isRotationDirty = isTeleportingAndNotSynchronizing ? networkState.HasRotAngleChange : false; - var isScaleDirty = isTeleportingAndNotSynchronizing ? networkState.HasScaleChange : false; + + var isPositionDirty = networkState.HasPositionChange; + var isRotationDirty = networkState.HasRotAngleChange; + var isScaleDirty = networkState.HasScaleChange; + if (!isTeleportingAndNotSynchronizing) + { + isPositionDirty = false; + isRotationDirty = false; + isScaleDirty = false; + networkState.MarkChanged(AxialType.Position,false); + networkState.MarkChanged(AxialType.Rotation,false); + networkState.MarkChanged(AxialType.Scale,false); + } networkState.SwitchTransformSpaceWhenParented = SwitchTransformSpaceWhenParented; // All of the checks below, up to the delta position checking portion, are to determine if the @@ -2177,24 +2267,23 @@ private bool CheckForStateChange(ref NetworkTransformState networkState, ref Tra if (SyncPositionX && (Mathf.Abs(networkState.PositionX - position.x) >= positionThreshold.x || networkState.IsTeleportingNextFrame || isAxisSync || forceState)) { networkState.PositionX = position.x; - networkState.HasPositionX = true; + networkState.SetHasPosition(Axis.X, true); isPositionDirty = true; } if (SyncPositionY && (Mathf.Abs(networkState.PositionY - position.y) >= positionThreshold.y || networkState.IsTeleportingNextFrame || isAxisSync || forceState)) { networkState.PositionY = position.y; - networkState.HasPositionY = true; + networkState.SetHasPosition(Axis.Y, true); isPositionDirty = true; } if (SyncPositionZ && (Mathf.Abs(networkState.PositionZ - position.z) >= positionThreshold.z || networkState.IsTeleportingNextFrame || isAxisSync || forceState)) { networkState.PositionZ = position.z; - networkState.HasPositionZ = true; + networkState.SetHasPosition(Axis.Z, true); isPositionDirty = true; } - networkState.HasPositionChange = isPositionDirty; } else if (SynchronizePosition) { @@ -2309,24 +2398,23 @@ private bool CheckForStateChange(ref NetworkTransformState networkState, ref Tra if (SyncRotAngleX && (Mathf.Abs(Mathf.DeltaAngle(networkState.RotAngleX, rotAngles.x)) >= rotationThreshold.x || networkState.IsTeleportingNextFrame || isAxisSync || forceState)) { networkState.RotAngleX = rotAngles.x; - networkState.HasRotAngleX = true; + networkState.SetHasRotation(Axis.X, true); isRotationDirty = true; } if (SyncRotAngleY && (Mathf.Abs(Mathf.DeltaAngle(networkState.RotAngleY, rotAngles.y)) >= rotationThreshold.y || networkState.IsTeleportingNextFrame || isAxisSync || forceState)) { networkState.RotAngleY = rotAngles.y; - networkState.HasRotAngleY = true; + networkState.SetHasRotation(Axis.Y, true); isRotationDirty = true; } if (SyncRotAngleZ && (Mathf.Abs(Mathf.DeltaAngle(networkState.RotAngleZ, rotAngles.z)) >= rotationThreshold.z || networkState.IsTeleportingNextFrame || isAxisSync || forceState)) { networkState.RotAngleZ = rotAngles.z; - networkState.HasRotAngleZ = true; + networkState.SetHasRotation(Axis.Z, true); isRotationDirty = true; } - networkState.HasRotAngleChange = isRotationDirty; } else if (SynchronizeRotation) { @@ -2371,24 +2459,23 @@ private bool CheckForStateChange(ref NetworkTransformState networkState, ref Tra if (SyncScaleX && (Mathf.Abs(networkState.ScaleX - scale.x) >= ScaleThreshold || networkState.IsTeleportingNextFrame || isAxisSync || forceState)) { networkState.ScaleX = scale.x; - networkState.HasScaleX = true; + networkState.SetHasScale(Axis.X, true); isScaleDirty = true; } if (SyncScaleY && (Mathf.Abs(networkState.ScaleY - scale.y) >= ScaleThreshold || networkState.IsTeleportingNextFrame || isAxisSync || forceState)) { networkState.ScaleY = scale.y; - networkState.HasScaleY = true; + networkState.SetHasScale(Axis.Y, true); isScaleDirty = true; } if (SyncScaleZ && (Mathf.Abs(networkState.ScaleZ - scale.z) >= ScaleThreshold || networkState.IsTeleportingNextFrame || isAxisSync || forceState)) { networkState.ScaleZ = scale.z; - networkState.HasScaleZ = true; + networkState.SetHasScale(Axis.Z, true); isScaleDirty = true; } - networkState.HasScaleChange = isScaleDirty; } else if (SynchronizeScale) { @@ -3988,7 +4075,10 @@ private void SetStateInternal(Vector3 pos, Quaternion rot, Vector3 scale, bool s var explicitState = m_LocalAuthoritativeNetworkState.ExplicitSet; // Apply any delta states to the m_LocalAuthoritativeNetworkState + Debug.Log($"[Object-{NetworkObjectId}][CheckForStateChange][SetStateInternal] HasRotAngleX: {m_LocalAuthoritativeNetworkState.HasRotAngleX}, HasRotAngleChange: {m_LocalAuthoritativeNetworkState.HasRotAngleChange}"); var isDirty = CheckForStateChange(ref m_LocalAuthoritativeNetworkState, ref transformToCommit); + Debug.Log($"[After][Object-{NetworkObjectId}][CheckForStateChange][SetStateInternal] HasRotAngleX: {m_LocalAuthoritativeNetworkState.HasRotAngleX}, HasRotAngleChange: {m_LocalAuthoritativeNetworkState.HasRotAngleChange}"); + // If we were dirty and the explicit state was set (prior to checking for deltas) or the current explicit state is dirty, // then we set the explicit state flag. diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/NetworkTransformGeneral.cs b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/NetworkTransformGeneral.cs index 04afd12cb5..acbfd3d37e 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/NetworkTransformGeneral.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/NetworkTransformGeneral.cs @@ -60,7 +60,7 @@ public void TestMultipleStateSynchronization([Values] bool isLocal, [Values] boo // Simulate a state update localState.UseInterpolation = false; localState.CurrentPosition = new Vector3(5.0f, 0.0f, 0.0f); - localState.HasPositionX = true; + localState.SetHasPosition(NetworkTransform.Axis.X, true); localState.PositionX = 5.0f; localState.NetworkTick++; @@ -85,8 +85,8 @@ public void TestMultipleStateSynchronization([Values] bool isLocal, [Values] boo Assert.IsTrue(localState.NetworkTick == lastStateTick, $"Previous Non-authority state tick was {lastStateTick} but is now {localState.NetworkTick}. Authority pushed a state update."); // Simualate a 2nd state update on a different position axis - localState.HasPositionX = false; - localState.HasPositionZ = true; + localState.SetHasPosition(NetworkTransform.Axis.X, false); + localState.SetHasPosition(NetworkTransform.Axis.Z, true); localState.PositionZ = -5.0f; localState.NetworkTick++; m_NonAuthoritativeTransform.ApplyUpdatedState(localState); diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/NetworkTransformStateTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/NetworkTransformStateTests.cs index 51278d605d..79edaece53 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/NetworkTransformStateTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/NetworkTransformStateTests.cs @@ -17,7 +17,7 @@ internal class NetworkTransformStateTests public void NetworkTransformStateFlags() { // The current number of flags on the NetworkTransformState - var numFlags = 23; + var numFlags = 24; var indexValues = new uint[numFlags]; @@ -77,7 +77,8 @@ private void InlinedBitmathSerialization(ref int numFlags, ref uint[] indexValue ReliableSequenced = boolSet[19], UseUnreliableDeltas = boolSet[20], UnreliableFrameSync = boolSet[21], - TrackByStateId = boolSet[22], + SwitchTransformSpaceWhenParented = boolSet[22], + TrackByStateId = boolSet[23], }; writer = new FastBufferWriter(64, Allocator.Temp); @@ -85,7 +86,7 @@ private void InlinedBitmathSerialization(ref int numFlags, ref uint[] indexValue // Test the bitset representation of the serialization matches the pre-refactor serialization reader = new FastBufferReader(writer, Allocator.None); - reader.ReadValueSafe(out uint serializedBitset); + ByteUnpacker.ReadValueBitPacked(reader, out uint serializedBitset); Assert.True((serializedBitset & indexValues[j]) == indexValues[j], $"[FlagTest][Individual] Set flag value {indexValues[j]} at index {j}, but BitSet value did not match!"); @@ -124,6 +125,7 @@ private void InlinedBitmathSerialization(ref int numFlags, ref uint[] indexValue ReliableSequenced = true, UseUnreliableDeltas = true, UnreliableFrameSync = true, + SwitchTransformSpaceWhenParented = true, TrackByStateId = true, }; @@ -139,7 +141,7 @@ private void InlinedBitmathSerialization(ref int numFlags, ref uint[] indexValue } var legacyBitsetWriter = new FastBufferWriter(64, Allocator.Temp); - legacyBitsetWriter.WriteValueSafe(bitset); + BytePacker.WriteValueBitPacked(legacyBitsetWriter, bitset); // Test refactored serialization matches pre-refactor flag serialization Assert.AreEqual(legacyBitsetWriter.ToArray(), serializedBuffer, "[Flag serialization] Serialized NetworkTransformState doesn't match original serialization!"); @@ -178,7 +180,8 @@ private void AssertTransformStateEquals(bool[] expected, NetworkTransform.Networ Assert.AreEqual(expected[19], actual.ReliableSequenced, $"{testName} Flag {nameof(NetworkTransform.NetworkTransformState.ReliableSequenced)} is incorrect!"); Assert.AreEqual(expected[20], actual.UseUnreliableDeltas, $"{testName} Flag {nameof(NetworkTransform.NetworkTransformState.UseUnreliableDeltas)} is incorrect!"); Assert.AreEqual(expected[21], actual.UnreliableFrameSync, $"{testName} Flag {nameof(NetworkTransform.NetworkTransformState.UnreliableFrameSync)} is incorrect!"); - Assert.AreEqual(expected[22], actual.TrackByStateId, $"{testName} Flag {nameof(NetworkTransform.NetworkTransformState.TrackByStateId)} is incorrect!"); + Assert.AreEqual(expected[22], actual.SwitchTransformSpaceWhenParented, $"{testName} Flag {nameof(NetworkTransform.NetworkTransformState.SwitchTransformSpaceWhenParented)} is incorrect!"); + Assert.AreEqual(expected[23], actual.TrackByStateId, $"{testName} Flag {nameof(NetworkTransform.NetworkTransformState.TrackByStateId)} is incorrect!"); } }