From cd11ac6a36e4c31504ab069a6733cd118c920c0b Mon Sep 17 00:00:00 2001 From: Emma Date: Fri, 31 Oct 2025 13:23:05 -0400 Subject: [PATCH 01/19] 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 02/19] 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 03/19] 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 04/19] 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 05/19] 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 06/19] 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 07/19] 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!"); } } From 25ca97195e0a5f5080674af04b9cf2d0199e316b Mon Sep 17 00:00:00 2001 From: Emma Date: Fri, 21 Nov 2025 08:39:00 -0500 Subject: [PATCH 08/19] Fix whitespace issue --- .../Runtime/Components/NetworkTransform.cs | 95 ++++--------------- 1 file changed, 18 insertions(+), 77 deletions(-) diff --git a/com.unity.netcode.gameobjects/Runtime/Components/NetworkTransform.cs b/com.unity.netcode.gameobjects/Runtime/Components/NetworkTransform.cs index 311522aad7..f624727bac 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 /// @@ -221,16 +221,19 @@ 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; } } @@ -587,11 +590,6 @@ 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 @@ -668,13 +666,6 @@ 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 @@ -794,11 +785,6 @@ 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 @@ -877,11 +863,6 @@ 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 @@ -894,12 +875,10 @@ public void NetworkSerialize(BufferSerializer serializer) where T : IReade else { LastSerializedSize = m_Writer.Position - positionStart; - } - #if NGO_NETWORKTRANSFORMSTATE_LOGWRITESIZE - var type = isWriting ? "Write" : "Read"; - Debug.Log($"[NT-{type}][BitsAndTick: {bitSetAndTickSize}][position: {positionSize}][rotation: {rotationSize}][scale: {scaleSize}]"); + Debug.Log($"[NT-WriteSize][BitsAndTick: {bitSetAndTickSize}][position: {positionSize}][rotation: {rotationSize}][scale: {scaleSize}]"); #endif + } } [MethodImpl(MethodImplOptions.AggressiveInlining)] @@ -907,33 +886,16 @@ 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 (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 (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; } @@ -981,9 +943,6 @@ 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 @@ -1825,7 +1784,6 @@ 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(); @@ -1948,7 +1906,6 @@ 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 @@ -1961,7 +1918,6 @@ 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); } } @@ -2060,7 +2016,6 @@ 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 @@ -2081,7 +2036,6 @@ 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); } @@ -2117,19 +2071,9 @@ private bool CheckForStateChange(ref NetworkTransformState networkState, ref Tra var isTeleportingAndNotSynchronizing = networkState.IsTeleportingNextFrame && !isSynchronization; var isDirty = 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); - } + var isPositionDirty = isTeleportingAndNotSynchronizing ? networkState.HasPositionChange : false; + var isRotationDirty = isTeleportingAndNotSynchronizing ? networkState.HasRotAngleChange : false; + var isScaleDirty = isTeleportingAndNotSynchronizing ? networkState.HasScaleChange : false; networkState.SwitchTransformSpaceWhenParented = SwitchTransformSpaceWhenParented; // All of the checks below, up to the delta position checking portion, are to determine if the @@ -4075,10 +4019,7 @@ 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. From 72ab763764184bb279c135d1e4f388b6eb171750 Mon Sep 17 00:00:00 2001 From: Noel Stephens Date: Fri, 21 Nov 2025 15:51:55 -0600 Subject: [PATCH 09/19] fix Fixing AnticipatedNetworkTransform m_NetworkManager issue. --- .../Components/AnticipatedNetworkTransform.cs | 52 +++++++++---------- 1 file changed, 25 insertions(+), 27 deletions(-) diff --git a/com.unity.netcode.gameobjects/Runtime/Components/AnticipatedNetworkTransform.cs b/com.unity.netcode.gameobjects/Runtime/Components/AnticipatedNetworkTransform.cs index eb2abeb288..ce277533c7 100644 --- a/com.unity.netcode.gameobjects/Runtime/Components/AnticipatedNetworkTransform.cs +++ b/com.unity.netcode.gameobjects/Runtime/Components/AnticipatedNetworkTransform.cs @@ -86,8 +86,6 @@ public struct TransformState private bool m_OutstandingAuthorityChange = false; - private NetworkManager m_NetworkManager; - #if UNITY_EDITOR private void Reset() { @@ -159,7 +157,7 @@ public bool ShouldReanticipate /// The anticipated position public void AnticipateMove(Vector3 newPosition) { - if (m_NetworkManager == null || m_NetworkManager.ShutdownInProgress || !m_NetworkManager.IsListening) + if (m_CachedNetworkManager == null || m_CachedNetworkManager.ShutdownInProgress || !m_CachedNetworkManager.IsListening) { return; } @@ -172,7 +170,7 @@ public void AnticipateMove(Vector3 newPosition) m_PreviousAnticipatedTransform = m_AnticipatedTransform; - m_LastAnticipaionCounter = m_NetworkManager.AnticipationSystem.AnticipationCounter; + m_LastAnticipaionCounter = m_CachedNetworkManager.AnticipationSystem.AnticipationCounter; m_SmoothDuration = 0; m_CurrentSmoothTime = 0; @@ -185,7 +183,7 @@ public void AnticipateMove(Vector3 newPosition) /// The anticipated rotation public void AnticipateRotate(Quaternion newRotation) { - if (m_NetworkManager == null || m_NetworkManager.ShutdownInProgress || !m_NetworkManager.IsListening) + if (m_CachedNetworkManager == null || m_CachedNetworkManager.ShutdownInProgress || !m_CachedNetworkManager.IsListening) { return; } @@ -198,7 +196,7 @@ public void AnticipateRotate(Quaternion newRotation) m_PreviousAnticipatedTransform = m_AnticipatedTransform; - m_LastAnticipaionCounter = m_NetworkManager.AnticipationSystem.AnticipationCounter; + m_LastAnticipaionCounter = m_CachedNetworkManager.AnticipationSystem.AnticipationCounter; m_SmoothDuration = 0; m_CurrentSmoothTime = 0; @@ -211,7 +209,7 @@ public void AnticipateRotate(Quaternion newRotation) /// The anticipated scale public void AnticipateScale(Vector3 newScale) { - if (m_NetworkManager == null || m_NetworkManager.ShutdownInProgress || !m_NetworkManager.IsListening) + if (m_CachedNetworkManager == null || m_CachedNetworkManager.ShutdownInProgress || !m_CachedNetworkManager.IsListening) { return; } @@ -224,7 +222,7 @@ public void AnticipateScale(Vector3 newScale) m_PreviousAnticipatedTransform = m_AnticipatedTransform; - m_LastAnticipaionCounter = m_NetworkManager.AnticipationSystem.AnticipationCounter; + m_LastAnticipaionCounter = m_CachedNetworkManager.AnticipationSystem.AnticipationCounter; m_SmoothDuration = 0; m_CurrentSmoothTime = 0; @@ -237,7 +235,7 @@ public void AnticipateScale(Vector3 newScale) /// The anticipated transform state public void AnticipateState(TransformState newState) { - if (m_NetworkManager == null || m_NetworkManager.ShutdownInProgress || !m_NetworkManager.IsListening) + if (m_CachedNetworkManager == null || m_CachedNetworkManager.ShutdownInProgress || !m_CachedNetworkManager.IsListening) { return; } @@ -266,7 +264,7 @@ private void ProcessSmoothing() if (m_CurrentSmoothTime < m_SmoothDuration) { - m_CurrentSmoothTime += m_NetworkManager.RealTimeProvider.DeltaTime; + m_CurrentSmoothTime += m_CachedNetworkManager.RealTimeProvider.DeltaTime; var transform_ = transform; var pct = math.min(m_CurrentSmoothTime / m_SmoothDuration, 1f); @@ -399,8 +397,8 @@ protected internal override void InternalOnNetworkSessionSynchronized() ResetAnticipatedState(); m_AnticipatedObject = new AnticipatedObject { Transform = this }; - m_NetworkManager.AnticipationSystem.RegisterForAnticipationEvents(m_AnticipatedObject); - m_NetworkManager.AnticipationSystem.AllAnticipatedObjects.Add(m_AnticipatedObject); + m_CachedNetworkManager.AnticipationSystem.RegisterForAnticipationEvents(m_AnticipatedObject); + m_CachedNetworkManager.AnticipationSystem.AllAnticipatedObjects.Add(m_AnticipatedObject); } } @@ -412,23 +410,23 @@ protected internal override void InternalOnNetworkSessionSynchronized() protected internal override void InternalOnNetworkPostSpawn() { base.InternalOnNetworkPostSpawn(); - if (!CanCommitToTransform && m_NetworkManager.IsConnectedClient && !SynchronizeState.IsSynchronizing) + if (!CanCommitToTransform && m_CachedNetworkManager.IsConnectedClient && !SynchronizeState.IsSynchronizing) { m_OutstandingAuthorityChange = true; ApplyAuthoritativeState(); ResetAnticipatedState(); m_AnticipatedObject = new AnticipatedObject { Transform = this }; - m_NetworkManager.AnticipationSystem.RegisterForAnticipationEvents(m_AnticipatedObject); - m_NetworkManager.AnticipationSystem.AllAnticipatedObjects.Add(m_AnticipatedObject); + m_CachedNetworkManager.AnticipationSystem.RegisterForAnticipationEvents(m_AnticipatedObject); + m_CachedNetworkManager.AnticipationSystem.AllAnticipatedObjects.Add(m_AnticipatedObject); } } /// public override void OnNetworkSpawn() { - m_NetworkManager = NetworkManager; + m_CachedNetworkManager = NetworkManager; - if (m_NetworkManager.DistributedAuthorityMode) + if (m_CachedNetworkManager.DistributedAuthorityMode) { Debug.LogWarning($"This component is not currently supported in distributed authority."); } @@ -445,8 +443,8 @@ public override void OnNetworkSpawn() ResetAnticipatedState(); m_AnticipatedObject = new AnticipatedObject { Transform = this }; - m_NetworkManager.AnticipationSystem.RegisterForAnticipationEvents(m_AnticipatedObject); - m_NetworkManager.AnticipationSystem.AllAnticipatedObjects.Add(m_AnticipatedObject); + m_CachedNetworkManager.AnticipationSystem.RegisterForAnticipationEvents(m_AnticipatedObject); + m_CachedNetworkManager.AnticipationSystem.AllAnticipatedObjects.Add(m_AnticipatedObject); } /// @@ -454,9 +452,9 @@ public override void OnNetworkDespawn() { if (m_AnticipatedObject != null) { - m_NetworkManager.AnticipationSystem.DeregisterForAnticipationEvents(m_AnticipatedObject); - m_NetworkManager.AnticipationSystem.AllAnticipatedObjects.Remove(m_AnticipatedObject); - m_NetworkManager.AnticipationSystem.ObjectsToReanticipate.Remove(m_AnticipatedObject); + m_CachedNetworkManager.AnticipationSystem.DeregisterForAnticipationEvents(m_AnticipatedObject); + m_CachedNetworkManager.AnticipationSystem.AllAnticipatedObjects.Remove(m_AnticipatedObject); + m_CachedNetworkManager.AnticipationSystem.ObjectsToReanticipate.Remove(m_AnticipatedObject); m_AnticipatedObject = null; } ResetAnticipatedState(); @@ -469,9 +467,9 @@ public override void OnDestroy() { if (m_AnticipatedObject != null) { - m_NetworkManager.AnticipationSystem.DeregisterForAnticipationEvents(m_AnticipatedObject); - m_NetworkManager.AnticipationSystem.AllAnticipatedObjects.Remove(m_AnticipatedObject); - m_NetworkManager.AnticipationSystem.ObjectsToReanticipate.Remove(m_AnticipatedObject); + m_CachedNetworkManager.AnticipationSystem.DeregisterForAnticipationEvents(m_AnticipatedObject); + m_CachedNetworkManager.AnticipationSystem.AllAnticipatedObjects.Remove(m_AnticipatedObject); + m_CachedNetworkManager.AnticipationSystem.ObjectsToReanticipate.Remove(m_AnticipatedObject); m_AnticipatedObject = null; } @@ -518,7 +516,7 @@ public void Smooth(TransformState from, TransformState to, float durationSeconds protected override void OnBeforeUpdateTransformState() { // this is called when new data comes from the server - m_LastAuthorityUpdateCounter = m_NetworkManager.AnticipationSystem.LastAnticipationAck; + m_LastAuthorityUpdateCounter = m_CachedNetworkManager.AnticipationSystem.LastAnticipationAck; m_OutstandingAuthorityChange = true; } @@ -571,7 +569,7 @@ protected override void OnTransformUpdated() m_AnticipatedTransform = m_AuthoritativeTransform; ShouldReanticipate = true; - m_NetworkManager.AnticipationSystem.ObjectsToReanticipate.Add(m_AnticipatedObject); + m_CachedNetworkManager.AnticipationSystem.ObjectsToReanticipate.Add(m_AnticipatedObject); } } } From 349f83e2e895561d73d2d22d4a5a60ddb86134bc Mon Sep 17 00:00:00 2001 From: Noel Stephens Date: Fri, 21 Nov 2025 15:52:57 -0600 Subject: [PATCH 10/19] refactor Refactoring how we handle updating public facing flags. --- .../Runtime/Components/NetworkTransform.cs | 394 ++++++++++++------ 1 file changed, 258 insertions(+), 136 deletions(-) diff --git a/com.unity.netcode.gameobjects/Runtime/Components/NetworkTransform.cs b/com.unity.netcode.gameobjects/Runtime/Components/NetworkTransform.cs index f624727bac..b7c3df9453 100644 --- a/com.unity.netcode.gameobjects/Runtime/Components/NetworkTransform.cs +++ b/com.unity.netcode.gameobjects/Runtime/Components/NetworkTransform.cs @@ -35,6 +35,32 @@ internal enum AxialType Scale } #region NETWORK TRANSFORM STATE + + internal struct PublicFlagStates + { + internal bool InLocalSpace; + internal bool HasPositionX; + internal bool HasPositionY; + internal bool HasPositionZ; + internal bool HasPositionChange; + internal bool HasRotAngleX; + internal bool HasRotAngleY; + internal bool HasRotAngleZ; + internal bool HasRotAngleChange; + internal bool HasScaleX; + internal bool HasScaleY; + internal bool HasScaleZ; + internal bool HasScaleChange; + internal bool IsTeleportingNextFrame; + internal bool WasTeleported; + internal bool UseInterpolation; + internal bool QuaternionSync; + internal bool QuaternionCompression; + internal bool UseHalfFloatPrecision; + internal bool IsSynchronizing; + internal bool UsePositionSlerp; + } + /// /// Data structure used to synchronize the /// @@ -70,6 +96,7 @@ public struct NetworkTransformState : INetworkSerializable private const int k_UseUnreliableDeltas = 0x00100000; private const int k_UnreliableFrameSync = 0x00200000; private const int k_SwitchTransformSpaceWhenParented = 0x00400000; + // (Internal Debugging) When set each state update will contain a state identifier private const int k_TrackStateId = 0x10000000; @@ -126,6 +153,8 @@ public struct NetworkTransformState : INetworkSerializable private FastBufferReader m_Reader; private FastBufferWriter m_Writer; + internal PublicFlagStates FlagStates; + /// /// When set, non-authority instances will smoothly transition between /// world and local space. @@ -136,28 +165,48 @@ public struct NetworkTransformState : INetworkSerializable /// /// When set, the is operates in local space /// - public bool InLocalSpace { get; internal set; } + public bool InLocalSpace + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get { return FlagStates.InLocalSpace; } + } // Position /// /// When set, the X-Axis position value has changed /// - public bool HasPositionX { get; internal set; } + public bool HasPositionX + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get { return FlagStates.HasPositionX; } + } /// /// When set, the Y-Axis position value has changed /// - public bool HasPositionY { get; internal set; } + public bool HasPositionY + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get { return FlagStates.HasPositionY; } + } /// /// When set, the Z-Axis position value has changed /// - public bool HasPositionZ { get; internal set; } + public bool HasPositionZ + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get { return FlagStates.HasPositionZ; } + } /// /// When set, at least one of the position axis values has changed. /// - public bool HasPositionChange { get; internal set; } + public bool HasPositionChange + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get { return FlagStates.HasPositionChange; } + } // RotAngles /// @@ -166,7 +215,11 @@ public struct NetworkTransformState : INetworkSerializable /// /// When quaternion synchronization is enabled all axis are always updated. /// - public bool HasRotAngleX { get; internal set; } + public bool HasRotAngleX + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get { return FlagStates.HasRotAngleX; } + } /// /// When set, the Euler rotation Y-Axis value has changed. @@ -174,7 +227,11 @@ public struct NetworkTransformState : INetworkSerializable /// /// When quaternion synchronization is enabled all axis are always updated. /// - public bool HasRotAngleY { get; internal set; } + public bool HasRotAngleY + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get { return FlagStates.HasRotAngleY; } + } /// /// When set, the Euler rotation Z-Axis value has changed. @@ -182,7 +239,11 @@ public struct NetworkTransformState : INetworkSerializable /// /// When quaternion synchronization is enabled all axis are always updated. /// - public bool HasRotAngleZ { get; internal set; } + public bool HasRotAngleZ + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get { return FlagStates.HasRotAngleZ; } + } /// /// When set, at least one of the rotation axis values has changed. @@ -190,50 +251,70 @@ public struct NetworkTransformState : INetworkSerializable /// /// When quaternion synchronization is enabled all axis are always updated. /// - public bool HasRotAngleChange { get; internal set; } + public bool HasRotAngleChange + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get { return FlagStates.HasRotAngleChange; } + } // Scale /// /// When set, the X-Axis scale value has changed. /// - public bool HasScaleX { get; internal set; } + public bool HasScaleX + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get { return FlagStates.HasScaleX; } + } /// /// When set, the Y-Axis scale value has changed. /// - public bool HasScaleY { get; internal set; } + public bool HasScaleY + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get { return FlagStates.HasScaleY; } + } /// /// When set, the Z-Axis scale value has changed. /// - public bool HasScaleZ { get; internal set; } + public bool HasScaleZ + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get { return FlagStates.HasScaleZ; } + } /// /// When set, at least one of the scale axis values has changed. /// - public bool HasScaleChange { get; internal set; } + public bool HasScaleChange + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get { return FlagStates.HasScaleChange; } + } internal void MarkChanged(AxialType axialType, bool changed) { switch (axialType) { case AxialType.Position: - HasPositionX = changed; - HasPositionY = changed; - HasPositionZ = changed; - HasPositionChange = changed; + FlagStates.HasPositionX = changed; + FlagStates.HasPositionY = changed; + FlagStates.HasPositionZ = changed; + FlagStates.HasPositionChange = changed; break; case AxialType.Rotation: - HasRotAngleX = changed; - HasRotAngleY = changed; - HasRotAngleZ = changed; - HasRotAngleChange = changed; + FlagStates.HasRotAngleX = changed; + FlagStates.HasRotAngleY = changed; + FlagStates.HasRotAngleZ = changed; + FlagStates.HasRotAngleChange = changed; break; case AxialType.Scale: - HasScaleX = changed; - HasScaleY = changed; - HasScaleZ = changed; - HasScaleChange = changed; + FlagStates.HasScaleX = changed; + FlagStates.HasScaleY = changed; + FlagStates.HasScaleZ = changed; + FlagStates.HasScaleChange = changed; break; } } @@ -242,16 +323,16 @@ internal void SetHasPosition(Axis axis, bool changed) switch (axis) { case Axis.X: - HasPositionX = changed; + FlagStates.HasPositionX = changed; break; case Axis.Y: - HasPositionY = changed; + FlagStates.HasPositionY = changed; break; case Axis.Z: - HasPositionZ = changed; + FlagStates.HasPositionZ = changed; break; } - HasPositionChange = HasPositionX || HasPositionY || HasPositionZ; + FlagStates.HasPositionChange = HasPositionX || HasPositionY || HasPositionZ; } [MethodImpl(MethodImplOptions.AggressiveInlining)] @@ -260,16 +341,16 @@ internal void SetHasRotation(Axis axis, bool changed) switch (axis) { case Axis.X: - HasRotAngleX = changed; + FlagStates.HasRotAngleX = changed; break; case Axis.Y: - HasRotAngleY = changed; + FlagStates.HasRotAngleY = changed; break; case Axis.Z: - HasRotAngleZ = changed; + FlagStates.HasRotAngleZ = changed; break; } - HasRotAngleChange = HasRotAngleX || HasRotAngleY || HasRotAngleZ; + FlagStates.HasRotAngleChange = HasRotAngleX || HasRotAngleY || HasRotAngleZ; } internal void SetHasScale(Axis axis, bool changed) @@ -277,16 +358,16 @@ internal void SetHasScale(Axis axis, bool changed) switch (axis) { case Axis.X: - HasScaleX = changed; + FlagStates.HasScaleX = changed; break; case Axis.Y: - HasScaleY = changed; + FlagStates.HasScaleY = changed; break; case Axis.Z: - HasScaleZ = changed; + FlagStates.HasScaleZ = changed; break; } - HasScaleChange = HasScaleX || HasScaleY || HasScaleZ; + FlagStates.HasScaleChange = HasScaleX || HasScaleY || HasScaleZ; } internal bool HasScale(Axis axis) @@ -303,7 +384,11 @@ internal bool HasScale(Axis axis) /// - If using half precision, full precision values are used. /// - All axis marked to be synchronized will be updated. /// - public bool IsTeleportingNextFrame { get; internal set; } + public bool IsTeleportingNextFrame + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get { return FlagStates.IsTeleportingNextFrame; } + } /// /// When overriding , if the state that was pushed was a teleport then this will be set to true. @@ -311,7 +396,11 @@ internal bool HasScale(Axis axis) /// /// Note that will be reset in the event you need to do multiple teleports spread out accoss multiple ticks. /// - public bool WasTeleported { get; internal set; } + public bool WasTeleported + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get { return FlagStates.WasTeleported; } + } /// /// When set the is uses interpolation. @@ -320,7 +409,11 @@ internal bool HasScale(Axis axis) /// Authority does not apply interpolation via . /// Authority should handle its own motion/rotation/scale smoothing locally. /// - public bool UseInterpolation { get; internal set; } + public bool UseInterpolation + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get { return FlagStates.UseInterpolation; } + } /// /// When enabled, this instance uses synchronization. @@ -330,7 +423,11 @@ internal bool HasScale(Axis axis) /// 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; internal set; } + public bool QuaternionSync + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get { return FlagStates.QuaternionSync; } + } /// /// When set s will be compressed down to 4 bytes using a smallest three implementation. @@ -341,7 +438,11 @@ internal bool HasScale(Axis axis) /// - Quaternion Compression: 4 bytes per delta update /// - Half float precision: 8 bytes per delta update /// - public bool QuaternionCompression { get; internal set; } + public bool QuaternionCompression + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get { return FlagStates.QuaternionCompression; } + } /// /// When set, the will use half float precision for position, rotation, and scale. @@ -350,19 +451,31 @@ internal bool HasScale(Axis axis) /// 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; internal set; } + public bool UseHalfFloatPrecision + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get { return FlagStates.UseHalfFloatPrecision; } + } /// /// 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; internal set; } + public bool IsSynchronizing + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get { return FlagStates.IsSynchronizing; } + } /// /// 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; internal set; } + public bool UsePositionSlerp + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get { return FlagStates.UsePositionSlerp; } + } /// /// Returns whether this state update was a frame synchronization when @@ -410,8 +523,8 @@ internal void ClearBitSetForNextTick() MarkChanged(AxialType.Position, false); MarkChanged(AxialType.Rotation, false); MarkChanged(AxialType.Scale, false); - IsTeleportingNextFrame = false; - IsSynchronizing = false; + FlagStates.IsTeleportingNextFrame = false; + FlagStates.IsSynchronizing = false; IsParented = false; SynchronizeBaseHalfFloat = false; ReliableSequenced = false; @@ -885,24 +998,25 @@ public void NetworkSerialize(BufferSerializer serializer) where T : IReade internal 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; } + var flagStates = FlagStates; + + if (flagStates.InLocalSpace) { bitset |= k_InLocalSpaceBit; } + if (flagStates.HasPositionX) { bitset |= k_PositionXBit; } + if (flagStates.HasPositionY) { bitset |= k_PositionYBit; } + if (flagStates.HasPositionZ) { bitset |= k_PositionZBit; } + if (flagStates.HasRotAngleX) { bitset |= k_RotAngleXBit; } + if (flagStates.HasRotAngleY) { bitset |= k_RotAngleYBit; } + if (flagStates.HasRotAngleZ) { bitset |= k_RotAngleZBit; } + if (flagStates.HasScaleX) { bitset |= k_ScaleXBit; } + if (flagStates.HasScaleY) { bitset |= k_ScaleYBit; } + if (flagStates.HasScaleZ) { bitset |= k_ScaleZBit; } + if (flagStates.IsTeleportingNextFrame) { bitset |= k_TeleportingBit; } + if (flagStates.UseInterpolation) { bitset |= k_Interpolate; } + if (flagStates.QuaternionSync) { bitset |= k_QuaternionSync; } + if (flagStates.QuaternionCompression) { bitset |= k_QuaternionCompress; } + if (flagStates.UseHalfFloatPrecision) { bitset |= k_UseHalfFloats; } + if (flagStates.IsSynchronizing) { bitset |= k_Synchronization; } + if (flagStates.UsePositionSlerp) { bitset |= k_PositionSlerp; } if (IsParented) { bitset |= k_IsParented; } if (SynchronizeBaseHalfFloat) { bitset |= k_SynchBaseHalfFloat; } if (ReliableSequenced) { bitset |= k_ReliableSequenced; } @@ -918,8 +1032,7 @@ internal void SerializeBitset(ref FastBufferWriter writer) internal void DeserializeBitset(ref FastBufferReader reader) { ByteUnpacker.ReadValueBitPacked(reader, out uint bitset); - - InLocalSpace = (bitset & k_InLocalSpaceBit) != 0; + FlagStates.InLocalSpace = (bitset & k_InLocalSpaceBit) != 0; SetHasPosition(Axis.X, (bitset & k_PositionXBit) != 0); SetHasPosition(Axis.Y, (bitset & k_PositionYBit) != 0); SetHasPosition(Axis.Z, (bitset & k_PositionZBit) != 0); @@ -929,13 +1042,13 @@ internal void DeserializeBitset(ref FastBufferReader reader) 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; + FlagStates.IsTeleportingNextFrame = (bitset & k_TeleportingBit) != 0; + FlagStates.UseInterpolation = (bitset & k_Interpolate) != 0; + FlagStates.QuaternionSync = (bitset & k_QuaternionSync) != 0; + FlagStates.QuaternionCompression = (bitset & k_QuaternionCompress) != 0; + FlagStates.UseHalfFloatPrecision = (bitset & k_UseHalfFloats) != 0; + FlagStates.IsSynchronizing = (bitset & k_Synchronization) != 0; + FlagStates.UsePositionSlerp = (bitset & k_PositionSlerp) != 0; IsParented = (bitset & k_IsParented) != 0; SynchronizeBaseHalfFloat = (bitset & k_SynchBaseHalfFloat) != 0; ReliableSequenced = (bitset & k_ReliableSequenced) != 0; @@ -1780,7 +1893,7 @@ protected override void OnSynchronize(ref BufferSerializer serializer) if (serializer.IsWriter) { - SynchronizeState.IsTeleportingNextFrame = true; + SynchronizeState.FlagStates.IsTeleportingNextFrame = true; 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. @@ -1813,9 +1926,9 @@ private void ApplySynchronization() // Teleport/Fully Initialize based on the state ApplyTeleportingState(SynchronizeState); m_LocalAuthoritativeNetworkState = SynchronizeState; - m_LocalAuthoritativeNetworkState.IsTeleportingNextFrame = false; - m_LocalAuthoritativeNetworkState.IsSynchronizing = false; - SynchronizeState.IsSynchronizing = false; + m_LocalAuthoritativeNetworkState.FlagStates.IsTeleportingNextFrame = false; + m_LocalAuthoritativeNetworkState.FlagStates.IsSynchronizing = false; + SynchronizeState.FlagStates.IsSynchronizing = false; } #endregion @@ -1929,11 +2042,11 @@ private void TryCommitTransform(ref Transform transformToCommit, bool synchroniz m_OldState = m_LocalAuthoritativeNetworkState; // Preserve our teleporting flag in order to know if the state pushed was a teleport - m_LocalAuthoritativeNetworkState.WasTeleported = m_LocalAuthoritativeNetworkState.IsTeleportingNextFrame; + m_LocalAuthoritativeNetworkState.FlagStates.WasTeleported = m_LocalAuthoritativeNetworkState.IsTeleportingNextFrame; // Reset the teleport and explicit state flags after we have sent the state update. // These could be set again in the below OnAuthorityPushTransformState virtual method - m_LocalAuthoritativeNetworkState.IsTeleportingNextFrame = false; + m_LocalAuthoritativeNetworkState.FlagStates.IsTeleportingNextFrame = false; m_LocalAuthoritativeNetworkState.ExplicitSet = false; try @@ -2029,10 +2142,10 @@ internal bool ApplyTransformToNetworkState(ref NetworkTransformState networkStat { m_CachedNetworkManager = NetworkManager; // Apply the interpolate and PostionDeltaCompression flags, otherwise we get false positives whether something changed or not. - networkState.UseInterpolation = Interpolate; - networkState.QuaternionSync = UseQuaternionSynchronization; - networkState.UseHalfFloatPrecision = UseHalfFloatPrecision; - networkState.QuaternionCompression = UseQuaternionCompression; + networkState.FlagStates.UseInterpolation = Interpolate; + networkState.FlagStates.QuaternionSync = UseQuaternionSynchronization; + networkState.FlagStates.UseHalfFloatPrecision = UseHalfFloatPrecision; + networkState.FlagStates.QuaternionCompression = UseQuaternionCompression; networkState.UseUnreliableDeltas = UseUnreliableDeltas; m_HalfPositionState = new NetworkDeltaPosition(Vector3.zero, 0, math.bool3(SyncPositionX, SyncPositionY, SyncPositionZ)); @@ -2066,27 +2179,33 @@ private bool CheckForStateChange(ref NetworkTransformState networkState, ref Tra // send at least 1 unreliable state update after this fame synch or teleport m_DeltaSynch = false; } + + var flagStates = networkState.FlagStates; + // This is used to determine if we need to send the state update reliably (if we are doing an axial sync) networkState.UnreliableFrameSync = isAxisSync; - var isTeleportingAndNotSynchronizing = networkState.IsTeleportingNextFrame && !isSynchronization; + var isTeleportingAndNotSynchronizing = flagStates.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 = isTeleportingAndNotSynchronizing ? flagStates.HasPositionChange : false; + var isRotationDirty = isTeleportingAndNotSynchronizing ? flagStates.HasRotAngleChange : false; + var isScaleDirty = isTeleportingAndNotSynchronizing ? flagStates.HasScaleChange : false; + networkState.SwitchTransformSpaceWhenParented = SwitchTransformSpaceWhenParented; + + // All of the checks below, up to the delta position checking portion, are to determine if the // authority changed a property during runtime that requires a full synchronizing. #if COM_UNITY_MODULES_PHYSICS || COM_UNITY_MODULES_PHYSICS2D - if ((InLocalSpace != networkState.InLocalSpace || isSynchronization) && !m_UseRigidbodyForMotion) + if ((InLocalSpace != flagStates.InLocalSpace || isSynchronization) && !m_UseRigidbodyForMotion) #else if (InLocalSpace != networkState.InLocalSpace) #endif { // When SwitchTransformSpaceWhenParented is set we automatically set our local space based on whether // we are parented or not. - networkState.InLocalSpace = SwitchTransformSpaceWhenParented ? transform.parent != null : InLocalSpace; + flagStates.InLocalSpace = SwitchTransformSpaceWhenParented ? transform.parent != null : InLocalSpace; if (SwitchTransformSpaceWhenParented) { InLocalSpace = networkState.InLocalSpace; @@ -2096,7 +2215,7 @@ private bool CheckForStateChange(ref NetworkTransformState networkState, ref Tra // If we are already teleporting preserve the teleport flag. // If we don't have SwitchTransformSpaceWhenParented set or we are synchronizing, // then set the teleport flag. - networkState.IsTeleportingNextFrame |= !SwitchTransformSpaceWhenParented || isSynchronization; + flagStates.IsTeleportingNextFrame |= !SwitchTransformSpaceWhenParented || isSynchronization; // Otherwise, if SwitchTransformSpaceWhenParented is set we force a full state update. // If interpolation is enabled, then any non-authority instance will update any pending @@ -2127,7 +2246,7 @@ private bool CheckForStateChange(ref NetworkTransformState networkState, ref Tra #endif var rotAngles = rotation.eulerAngles; var scale = transformToUse.localScale; - networkState.IsSynchronizing = isSynchronization; + flagStates.IsSynchronizing = isSynchronization; // Check for parenting when synchronizing and/or teleporting if (isSynchronization || networkState.IsTeleportingNextFrame || forceState) @@ -2162,67 +2281,67 @@ private bool CheckForStateChange(ref NetworkTransformState networkState, ref Tra networkState.IsParented = hasParentNetworkObject; } - if (Interpolate != networkState.UseInterpolation) + if (Interpolate != flagStates.UseInterpolation) { - networkState.UseInterpolation = Interpolate; + flagStates.UseInterpolation = Interpolate; isDirty = true; // When we change from interpolating to not interpolating (or vice versa) we need to synchronize/reset everything - networkState.IsTeleportingNextFrame = true; + flagStates.IsTeleportingNextFrame = true; } - if (UseQuaternionSynchronization != networkState.QuaternionSync) + if (UseQuaternionSynchronization != flagStates.QuaternionSync) { - networkState.QuaternionSync = UseQuaternionSynchronization; + flagStates.QuaternionSync = UseQuaternionSynchronization; isDirty = true; - networkState.IsTeleportingNextFrame = true; + flagStates.IsTeleportingNextFrame = true; } - if (UseQuaternionCompression != networkState.QuaternionCompression) + if (UseQuaternionCompression != flagStates.QuaternionCompression) { - networkState.QuaternionCompression = UseQuaternionCompression; + flagStates.QuaternionCompression = UseQuaternionCompression; isDirty = true; - networkState.IsTeleportingNextFrame = true; + flagStates.IsTeleportingNextFrame = true; } - if (UseHalfFloatPrecision != networkState.UseHalfFloatPrecision) + if (UseHalfFloatPrecision != flagStates.UseHalfFloatPrecision) { - networkState.UseHalfFloatPrecision = UseHalfFloatPrecision; + flagStates.UseHalfFloatPrecision = UseHalfFloatPrecision; isDirty = true; - networkState.IsTeleportingNextFrame = true; + flagStates.IsTeleportingNextFrame = true; } - if (SlerpPosition != networkState.UsePositionSlerp) + if (SlerpPosition != flagStates.UsePositionSlerp) { - networkState.UsePositionSlerp = SlerpPosition; + flagStates.UsePositionSlerp = SlerpPosition; isDirty = true; - networkState.IsTeleportingNextFrame = true; + flagStates.IsTeleportingNextFrame = true; } if (UseUnreliableDeltas != networkState.UseUnreliableDeltas) { networkState.UseUnreliableDeltas = UseUnreliableDeltas; isDirty = true; - networkState.IsTeleportingNextFrame = true; + flagStates.IsTeleportingNextFrame = true; } // Begin delta checks against last sent state update if (!UseHalfFloatPrecision) { - if (SyncPositionX && (Mathf.Abs(networkState.PositionX - position.x) >= positionThreshold.x || networkState.IsTeleportingNextFrame || isAxisSync || forceState)) + if (SyncPositionX && (Mathf.Abs(networkState.PositionX - position.x) >= positionThreshold.x || flagStates.IsTeleportingNextFrame || isAxisSync || forceState)) { networkState.PositionX = position.x; networkState.SetHasPosition(Axis.X, true); isPositionDirty = true; } - if (SyncPositionY && (Mathf.Abs(networkState.PositionY - position.y) >= positionThreshold.y || networkState.IsTeleportingNextFrame || isAxisSync || forceState)) + if (SyncPositionY && (Mathf.Abs(networkState.PositionY - position.y) >= positionThreshold.y || flagStates.IsTeleportingNextFrame || isAxisSync || forceState)) { networkState.PositionY = position.y; networkState.SetHasPosition(Axis.Y, true); isPositionDirty = true; } - if (SyncPositionZ && (Mathf.Abs(networkState.PositionZ - position.z) >= positionThreshold.z || networkState.IsTeleportingNextFrame || isAxisSync || forceState)) + if (SyncPositionZ && (Mathf.Abs(networkState.PositionZ - position.z) >= positionThreshold.z || flagStates.IsTeleportingNextFrame || isAxisSync || forceState)) { networkState.PositionZ = position.z; networkState.SetHasPosition(Axis.Z, true); @@ -2232,7 +2351,7 @@ private bool CheckForStateChange(ref NetworkTransformState networkState, ref Tra else if (SynchronizePosition) { // If we are teleporting then we can skip the delta threshold check - isPositionDirty = networkState.IsTeleportingNextFrame || isAxisSync || forceState; + isPositionDirty = flagStates.IsTeleportingNextFrame || isAxisSync || forceState; if (m_HalfFloatTargetTickOwnership > CurrentTick) { isPositionDirty = true; @@ -2265,7 +2384,7 @@ private bool CheckForStateChange(ref NetworkTransformState networkState, ref Tra // With global teleporting (broadcast to all non-authority instances) // we re-initialize authority's NetworkDeltaPosition and synchronize all // non-authority instances with the new full precision position - if (networkState.IsTeleportingNextFrame) + if (flagStates.IsTeleportingNextFrame) { m_HalfPositionState = new NetworkDeltaPosition(position, networkState.NetworkTick, math.bool3(SyncPositionX, SyncPositionY, SyncPositionZ)); networkState.CurrentPosition = position; @@ -2279,7 +2398,7 @@ private bool CheckForStateChange(ref NetworkTransformState networkState, ref Tra networkState.NetworkDeltaPosition = m_HalfPositionState; // If ownership offset is greater or we are doing an axial synchronization then synchronize the base position - if ((m_HalfFloatTargetTickOwnership > CurrentTick || isAxisSync) && !networkState.IsTeleportingNextFrame) + if ((m_HalfFloatTargetTickOwnership > CurrentTick || isAxisSync) && !flagStates.IsTeleportingNextFrame) { networkState.SynchronizeBaseHalfFloat = true; } @@ -2330,30 +2449,30 @@ private bool CheckForStateChange(ref NetworkTransformState networkState, ref Tra // Add log entry for this update relative to the client being synchronized AddLogEntry(ref networkState, targetClientId, true); } - networkState.HasPositionX = SyncPositionX; - networkState.HasPositionY = SyncPositionY; - networkState.HasPositionZ = SyncPositionZ; - networkState.HasPositionChange = SyncPositionX || SyncPositionY || SyncPositionZ; + flagStates.HasPositionX = SyncPositionX; + flagStates.HasPositionY = SyncPositionY; + flagStates.HasPositionZ = SyncPositionZ; + flagStates.HasPositionChange = SyncPositionX || SyncPositionY || SyncPositionZ; } } if (!UseQuaternionSynchronization) { - if (SyncRotAngleX && (Mathf.Abs(Mathf.DeltaAngle(networkState.RotAngleX, rotAngles.x)) >= rotationThreshold.x || networkState.IsTeleportingNextFrame || isAxisSync || forceState)) + if (SyncRotAngleX && (Mathf.Abs(Mathf.DeltaAngle(networkState.RotAngleX, rotAngles.x)) >= rotationThreshold.x || flagStates.IsTeleportingNextFrame || isAxisSync || forceState)) { networkState.RotAngleX = rotAngles.x; networkState.SetHasRotation(Axis.X, true); isRotationDirty = true; } - if (SyncRotAngleY && (Mathf.Abs(Mathf.DeltaAngle(networkState.RotAngleY, rotAngles.y)) >= rotationThreshold.y || networkState.IsTeleportingNextFrame || isAxisSync || forceState)) + if (SyncRotAngleY && (Mathf.Abs(Mathf.DeltaAngle(networkState.RotAngleY, rotAngles.y)) >= rotationThreshold.y || flagStates.IsTeleportingNextFrame || isAxisSync || forceState)) { networkState.RotAngleY = rotAngles.y; networkState.SetHasRotation(Axis.Y, true); isRotationDirty = true; } - if (SyncRotAngleZ && (Mathf.Abs(Mathf.DeltaAngle(networkState.RotAngleZ, rotAngles.z)) >= rotationThreshold.z || networkState.IsTeleportingNextFrame || isAxisSync || forceState)) + if (SyncRotAngleZ && (Mathf.Abs(Mathf.DeltaAngle(networkState.RotAngleZ, rotAngles.z)) >= rotationThreshold.z || flagStates.IsTeleportingNextFrame || isAxisSync || forceState)) { networkState.RotAngleZ = rotAngles.z; networkState.SetHasRotation(Axis.Z, true); @@ -2363,7 +2482,7 @@ private bool CheckForStateChange(ref NetworkTransformState networkState, ref Tra else if (SynchronizeRotation) { // If we are teleporting then we can skip the delta threshold check - isRotationDirty = networkState.IsTeleportingNextFrame || isAxisSync || forceState; + isRotationDirty = flagStates.IsTeleportingNextFrame || isAxisSync || forceState; // For quaternion synchronization, if one angle is dirty we send a full update if (!isRotationDirty) { @@ -2385,7 +2504,7 @@ private bool CheckForStateChange(ref NetworkTransformState networkState, ref Tra } // For scale, we need to check for parenting when synchronizing and/or teleporting (synchronization is always teleporting) - if (networkState.IsTeleportingNextFrame) + if (flagStates.IsTeleportingNextFrame) { // If we are synchronizing and the associated NetworkObject has a parent then we want to send the // LossyScale if the NetworkObject has a parent since NetworkObject spawn order is not guaranteed @@ -2400,21 +2519,21 @@ private bool CheckForStateChange(ref NetworkTransformState networkState, ref Tra { if (!UseHalfFloatPrecision) { - if (SyncScaleX && (Mathf.Abs(networkState.ScaleX - scale.x) >= ScaleThreshold || networkState.IsTeleportingNextFrame || isAxisSync || forceState)) + if (SyncScaleX && (Mathf.Abs(networkState.ScaleX - scale.x) >= ScaleThreshold || flagStates.IsTeleportingNextFrame || isAxisSync || forceState)) { networkState.ScaleX = scale.x; networkState.SetHasScale(Axis.X, true); isScaleDirty = true; } - if (SyncScaleY && (Mathf.Abs(networkState.ScaleY - scale.y) >= ScaleThreshold || networkState.IsTeleportingNextFrame || isAxisSync || forceState)) + if (SyncScaleY && (Mathf.Abs(networkState.ScaleY - scale.y) >= ScaleThreshold || flagStates.IsTeleportingNextFrame || isAxisSync || forceState)) { networkState.ScaleY = scale.y; networkState.SetHasScale(Axis.Y, true); isScaleDirty = true; } - if (SyncScaleZ && (Mathf.Abs(networkState.ScaleZ - scale.z) >= ScaleThreshold || networkState.IsTeleportingNextFrame || isAxisSync || forceState)) + if (SyncScaleZ && (Mathf.Abs(networkState.ScaleZ - scale.z) >= ScaleThreshold || flagStates.IsTeleportingNextFrame || isAxisSync || forceState)) { networkState.ScaleZ = scale.z; networkState.SetHasScale(Axis.Z, true); @@ -2426,7 +2545,7 @@ private bool CheckForStateChange(ref NetworkTransformState networkState, ref Tra var previousScale = networkState.Scale; for (int i = 0; i < 3; i++) { - if (Mathf.Abs(scale[i] - previousScale[i]) >= ScaleThreshold || networkState.IsTeleportingNextFrame || isAxisSync || forceState) + if (Mathf.Abs(scale[i] - previousScale[i]) >= ScaleThreshold || flagStates.IsTeleportingNextFrame || isAxisSync || forceState) { isScaleDirty = true; networkState.Scale[i] = scale[i]; @@ -2466,6 +2585,9 @@ private bool CheckForStateChange(ref NetworkTransformState networkState, ref Tra // Mark the state dirty for the next network tick update to clear out the bitset values networkState.IsDirty |= isDirty; + + // Apply any changes to the flag states once + networkState.FlagStates = flagStates; return isDirty; } @@ -3356,7 +3478,7 @@ private void AxisChangedDeltaPositionCheck() needsToTeleport = Mathf.Abs(relativePosition.z - positionState.z) >= NetworkDeltaPosition.MaxDeltaBeforeAdjustment; } // If needed, force a teleport as the delta is outside of the valid delta boundary - m_LocalAuthoritativeNetworkState.IsTeleportingNextFrame = needsToTeleport; + m_LocalAuthoritativeNetworkState.FlagStates.IsTeleportingNextFrame = needsToTeleport; } } } @@ -3436,12 +3558,12 @@ protected internal override void InternalOnNetworkSessionSynchronized() private void ApplyPlayerTransformState() { - SynchronizeState.InLocalSpace = InLocalSpace; - SynchronizeState.UseInterpolation = Interpolate; - SynchronizeState.QuaternionSync = UseQuaternionSynchronization; - SynchronizeState.UseHalfFloatPrecision = UseHalfFloatPrecision; - SynchronizeState.QuaternionCompression = UseQuaternionCompression; - SynchronizeState.UsePositionSlerp = SlerpPosition; + SynchronizeState.FlagStates.InLocalSpace = InLocalSpace; + SynchronizeState.FlagStates.UseInterpolation = Interpolate; + SynchronizeState.FlagStates.QuaternionSync = UseQuaternionSynchronization; + SynchronizeState.FlagStates.UseHalfFloatPrecision = UseHalfFloatPrecision; + SynchronizeState.FlagStates.QuaternionCompression = UseQuaternionCompression; + SynchronizeState.FlagStates.UsePositionSlerp = SlerpPosition; } /// @@ -3465,7 +3587,7 @@ protected internal override void InternalOnNetworkPostSpawn() if (m_IsFirstNetworkTransform) { // Assure the NetworkTransform has the synchronizing flag set so the server runs through the final post initialization steps - SynchronizeState.IsSynchronizing = true; + SynchronizeState.FlagStates.IsSynchronizing = true; // Assure the SynchronizeState matches the initial prefab's values for each associated NetworkTransfrom (this includes root + all children) foreach (var child in NetworkObject.NetworkTransforms) @@ -4008,7 +4130,7 @@ private void SetStateInternal(Vector3 pos, Quaternion rot, Vector3 scale, bool s } transform.localScale = scale; - m_LocalAuthoritativeNetworkState.IsTeleportingNextFrame = shouldTeleport; + m_LocalAuthoritativeNetworkState.FlagStates.IsTeleportingNextFrame = shouldTeleport; var transformToCommit = transform; From d09cb920976c86c6db3e41ef051afc6149196d2c Mon Sep 17 00:00:00 2001 From: Noel Stephens Date: Fri, 21 Nov 2025 16:00:52 -0600 Subject: [PATCH 11/19] update adding comment --- .../Runtime/Components/NetworkTransform.cs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/com.unity.netcode.gameobjects/Runtime/Components/NetworkTransform.cs b/com.unity.netcode.gameobjects/Runtime/Components/NetworkTransform.cs index b7c3df9453..406ec55a60 100644 --- a/com.unity.netcode.gameobjects/Runtime/Components/NetworkTransform.cs +++ b/com.unity.netcode.gameobjects/Runtime/Components/NetworkTransform.cs @@ -36,6 +36,12 @@ internal enum AxialType } #region NETWORK TRANSFORM STATE + /// + /// Any public facing bool value is + /// represented in this struct. + /// This is what is used internally to get and set the current state's + /// public flags/bools. + /// internal struct PublicFlagStates { internal bool InLocalSpace; @@ -294,6 +300,7 @@ public bool HasScaleChange get { return FlagStates.HasScaleChange; } } + [MethodImpl(MethodImplOptions.AggressiveInlining)] internal void MarkChanged(AxialType axialType, bool changed) { switch (axialType) @@ -318,6 +325,8 @@ internal void MarkChanged(AxialType axialType, bool changed) break; } } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] internal void SetHasPosition(Axis axis, bool changed) { switch (axis) @@ -353,6 +362,7 @@ internal void SetHasRotation(Axis axis, bool changed) FlagStates.HasRotAngleChange = HasRotAngleX || HasRotAngleY || HasRotAngleZ; } + [MethodImpl(MethodImplOptions.AggressiveInlining)] internal void SetHasScale(Axis axis, bool changed) { switch (axis) From d15b193fb6590e0e423fedc24663a9fe6a75c4d0 Mon Sep 17 00:00:00 2001 From: Noel Stephens Date: Fri, 21 Nov 2025 16:29:27 -0600 Subject: [PATCH 12/19] update Updating some tests with these changes (WIP) Migrating away from direct transform access and using a cached transform approach. --- .../Runtime/Components/NetworkTransform.cs | 45 ++-- .../NetworkTransformGeneral.cs | 4 +- .../NetworkTransformStateTests.cs | 219 ++++++++++++------ 3 files changed, 169 insertions(+), 99 deletions(-) diff --git a/com.unity.netcode.gameobjects/Runtime/Components/NetworkTransform.cs b/com.unity.netcode.gameobjects/Runtime/Components/NetworkTransform.cs index 406ec55a60..edf99c0b57 100644 --- a/com.unity.netcode.gameobjects/Runtime/Components/NetworkTransform.cs +++ b/com.unity.netcode.gameobjects/Runtime/Components/NetworkTransform.cs @@ -1719,7 +1719,7 @@ public Quaternion GetSpaceRelativeRotation(bool getCurrentState = false) { if (!getCurrentState || CanCommitToTransform) { - return InLocalSpace ? transform.localRotation : transform.rotation; + return InLocalSpace ? CachedTransform.localRotation : CachedTransform.rotation; } else { @@ -1750,7 +1750,7 @@ public Vector3 GetScale(bool getCurrentState = false) { if (!getCurrentState || CanCommitToTransform) { - return transform.localScale; + return CachedTransform.localScale; } else { @@ -1904,10 +1904,9 @@ protected override void OnSynchronize(ref BufferSerializer serializer) if (serializer.IsWriter) { SynchronizeState.FlagStates.IsTeleportingNextFrame = true; - 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. - CheckForStateChange(ref SynchronizeState, ref transformToCommit, true, targetClientId); + CheckForStateChange(ref SynchronizeState, true, targetClientId); SynchronizeState.NetworkSerialize(serializer); LastTickSync = SynchronizeState.GetNetworkTick(); OnAuthorityPushTransformState(ref SynchronizeState); @@ -1972,7 +1971,7 @@ internal void TryCommitTransformToServer(Transform transformToCommit, double dir // If we are authority, update the authoritative state if (CanCommitToTransform) { - OnUpdateAuthoritativeState(ref transformToCommit); + OnUpdateAuthoritativeState(); } else // Non-Authority { @@ -2012,7 +2011,7 @@ protected virtual void OnAuthorityPushTransformState(ref NetworkTransformState n /// If there are any transform delta states, this method will synchronize the /// state with all non-authority instances. /// - private void TryCommitTransform(ref Transform transformToCommit, bool synchronize = false, bool settingState = false) + private void TryCommitTransform(bool synchronize = false, bool settingState = false) { // Only the server or the owner is allowed to commit a transform if (!IsServer && !IsOwner) @@ -2029,7 +2028,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 - if (m_LocalAuthoritativeNetworkState.ExplicitSet || CheckForStateChange(ref m_LocalAuthoritativeNetworkState, ref transformToCommit, synchronize, forceState: settingState)) + if (m_LocalAuthoritativeNetworkState.ExplicitSet || CheckForStateChange(ref m_LocalAuthoritativeNetworkState, synchronize, forceState: settingState)) { // If the state was explicitly set, then update the network tick to match the locally calculate tick if (m_LocalAuthoritativeNetworkState.ExplicitSet) @@ -2041,7 +2040,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; - CheckForStateChange(ref m_LocalAuthoritativeNetworkState, ref transformToCommit, synchronize, forceState: true); + CheckForStateChange(ref m_LocalAuthoritativeNetworkState, synchronize, forceState: true); } } @@ -2139,7 +2138,7 @@ internal NetworkTransformState ApplyLocalNetworkState(Transform transform) m_LocalAuthoritativeNetworkState.ClearBitSetForNextTick(); // Now check the transform for any threshold value changes - CheckForStateChange(ref m_LocalAuthoritativeNetworkState, ref transform); + CheckForStateChange(ref m_LocalAuthoritativeNetworkState); // Return the entire state to be used by the integration test return m_LocalAuthoritativeNetworkState; @@ -2150,6 +2149,7 @@ internal NetworkTransformState ApplyLocalNetworkState(Transform transform) /// internal bool ApplyTransformToNetworkState(ref NetworkTransformState networkState, double dirtyTime, Transform transformToUse) { + CachedTransform = transformToUse; m_CachedNetworkManager = NetworkManager; // Apply the interpolate and PostionDeltaCompression flags, otherwise we get false positives whether something changed or not. networkState.FlagStates.UseInterpolation = Interpolate; @@ -2159,14 +2159,14 @@ internal bool ApplyTransformToNetworkState(ref NetworkTransformState networkStat networkState.UseUnreliableDeltas = UseUnreliableDeltas; m_HalfPositionState = new NetworkDeltaPosition(Vector3.zero, 0, math.bool3(SyncPositionX, SyncPositionY, SyncPositionZ)); - return CheckForStateChange(ref networkState, ref transformToUse); + return CheckForStateChange(ref networkState); } /// /// Applies the transform to the specified. /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - private bool CheckForStateChange(ref NetworkTransformState networkState, ref Transform transformToUse, bool isSynchronization = false, ulong targetClientId = 0, bool forceState = false) + private bool CheckForStateChange(ref NetworkTransformState networkState, bool isSynchronization = false, ulong targetClientId = 0, bool forceState = false) { // As long as we are not doing our first synchronization and we are sending unreliable deltas, each // NetworkTransform will stagger its full transfom synchronization over a 1 second period based on the @@ -2235,8 +2235,8 @@ private bool CheckForStateChange(ref NetworkTransformState networkState, ref Tra #if COM_UNITY_MODULES_PHYSICS || COM_UNITY_MODULES_PHYSICS2D - var position = m_UseRigidbodyForMotion ? m_NetworkRigidbodyInternal.GetPosition() : InLocalSpace ? transformToUse.localPosition : transformToUse.position; - var rotation = m_UseRigidbodyForMotion ? m_NetworkRigidbodyInternal.GetRotation() : InLocalSpace ? transformToUse.localRotation : transformToUse.rotation; + var position = m_UseRigidbodyForMotion ? m_NetworkRigidbodyInternal.GetPosition() : InLocalSpace ? CachedTransform.localPosition : CachedTransform.position; + var rotation = m_UseRigidbodyForMotion ? m_NetworkRigidbodyInternal.GetRotation() : InLocalSpace ? CachedTransform.localRotation : CachedTransform.rotation; var positionThreshold = Vector3.one * PositionThreshold; var rotationThreshold = Vector3.one * RotAngleThreshold; @@ -2255,7 +2255,7 @@ private bool CheckForStateChange(ref NetworkTransformState networkState, ref Tra var rotationThreshold = Vector3.one * RotAngleThreshold; #endif var rotAngles = rotation.eulerAngles; - var scale = transformToUse.localScale; + var scale = CachedTransform.localScale; flagStates.IsSynchronizing = isSynchronization; // Check for parenting when synchronizing and/or teleporting @@ -2636,9 +2636,8 @@ private void OnNetworkTick(bool isCalledFromParent = false) } #endif - // Update any changes to the transform - var transformSource = transform; - OnUpdateAuthoritativeState(ref transformSource, isCalledFromParent); + // Update any changes to the transform based on the current state + OnUpdateAuthoritativeState(isCalledFromParent); #if COM_UNITY_MODULES_PHYSICS || COM_UNITY_MODULES_PHYSICS2D m_InternalCurrentPosition = m_LastStateTargetPosition = m_UseRigidbodyForMotion ? m_NetworkRigidbodyInternal.GetPosition() : GetSpaceRelativePosition(); m_InternalCurrentRotation = m_UseRigidbodyForMotion ? m_NetworkRigidbodyInternal.GetRotation() : GetSpaceRelativeRotation(); @@ -3497,7 +3496,7 @@ private void AxisChangedDeltaPositionCheck() /// Called by authority to check for deltas and update non-authoritative instances /// if any are found. /// - internal void OnUpdateAuthoritativeState(ref Transform transformSource, bool settingState = false) + internal void OnUpdateAuthoritativeState(bool settingState = false) { // If our replicated state is not dirty and our local authority state is dirty, clear it. if (!m_LocalAuthoritativeNetworkState.ExplicitSet && m_LocalAuthoritativeNetworkState.IsDirty && !m_LocalAuthoritativeNetworkState.IsTeleportingNextFrame) @@ -3517,7 +3516,7 @@ internal void OnUpdateAuthoritativeState(ref Transform transformSource, bool set AxisChangedDeltaPositionCheck(); - TryCommitTransform(ref transformSource, settingState: settingState); + TryCommitTransform(settingState: settingState); } #endregion @@ -3633,6 +3632,8 @@ protected internal override void InternalOnNetworkPostSpawn() internal static bool AssignDefaultInterpolationType; internal static InterpolationTypes DefaultInterpolationType; + internal Transform CachedTransform; + /// /// Create interpolators when first instantiated to avoid memory allocations if the /// associated NetworkObject persists (i.e. despawned but not destroyed or pools) @@ -3655,6 +3656,8 @@ protected virtual void Awake() { InLocalSpace = false; } + + CachedTransform = transform; } /// @@ -4142,8 +4145,6 @@ private void SetStateInternal(Vector3 pos, Quaternion rot, Vector3 scale, bool s transform.localScale = scale; m_LocalAuthoritativeNetworkState.FlagStates.IsTeleportingNextFrame = shouldTeleport; - var transformToCommit = transform; - // Explicit set states are cumulative during a fractional tick period of time (i.e. each SetState invocation will // update the axial deltas to whatever changes are applied). As such, we need to preserve the dirty and explicit // state flags. @@ -4151,7 +4152,7 @@ private void SetStateInternal(Vector3 pos, Quaternion rot, Vector3 scale, bool s var explicitState = m_LocalAuthoritativeNetworkState.ExplicitSet; // Apply any delta states to the m_LocalAuthoritativeNetworkState - var isDirty = CheckForStateChange(ref m_LocalAuthoritativeNetworkState, ref transformToCommit); + var isDirty = CheckForStateChange(ref m_LocalAuthoritativeNetworkState); // 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 acbfd3d37e..7306d15b28 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/NetworkTransformGeneral.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/NetworkTransformGeneral.cs @@ -55,10 +55,10 @@ public void TestMultipleStateSynchronization([Values] bool isLocal, [Values] boo var localState = m_NonAuthoritativeTransform.LocalAuthoritativeNetworkState; // Assure this is not set to avoid a false positive result with teleporting - localState.IsTeleportingNextFrame = false; + localState.FlagStates.IsTeleportingNextFrame = false; // Simulate a state update - localState.UseInterpolation = false; + localState.FlagStates.UseInterpolation = false; localState.CurrentPosition = new Vector3(5.0f, 0.0f, 0.0f); localState.SetHasPosition(NetworkTransform.Axis.X, true); localState.PositionX = 5.0f; diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/NetworkTransformStateTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/NetworkTransformStateTests.cs index 79edaece53..e48f878978 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/NetworkTransformStateTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/NetworkTransformStateTests.cs @@ -55,23 +55,26 @@ private void InlinedBitmathSerialization(ref int numFlags, ref uint[] indexValue 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], + FlagStates = new NetworkTransform.PublicFlagStates() + { + 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], @@ -103,23 +106,26 @@ private void InlinedBitmathSerialization(ref int numFlags, ref uint[] indexValue // 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, + FlagStates = new NetworkTransform.PublicFlagStates() + { + 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, @@ -326,8 +332,11 @@ public void TestSyncAxes([Values] SynchronizationType synchronizationType, [Valu // needed when applying a transform to a NetworkTransformState var networkTransformState = new NetworkTransform.NetworkTransformState { - InLocalSpace = inLocalSpace, - IsTeleportingNextFrame = isTeleporting, + FlagStates = new NetworkTransform.PublicFlagStates() + { + InLocalSpace = inLocalSpace, + IsTeleportingNextFrame = isTeleporting, + }, NetworkDeltaPosition = new NetworkDeltaPosition(Vector3.zero, 0) }; @@ -348,8 +357,11 @@ public void TestSyncAxes([Values] SynchronizationType synchronizationType, [Valu // information from the last time we applied the transform networkTransformState = new NetworkTransform.NetworkTransformState { - InLocalSpace = inLocalSpace, - IsTeleportingNextFrame = isTeleporting, + FlagStates = new NetworkTransform.PublicFlagStates() + { + InLocalSpace = inLocalSpace, + IsTeleportingNextFrame = isTeleporting, + } }; var position = networkTransform.transform.position; var rotAngles = networkTransform.transform.eulerAngles; @@ -362,8 +374,11 @@ public void TestSyncAxes([Values] SynchronizationType synchronizationType, [Valu { networkTransformState = new NetworkTransform.NetworkTransformState { - InLocalSpace = inLocalSpace, - IsTeleportingNextFrame = isTeleporting, + FlagStates = new NetworkTransform.PublicFlagStates() + { + InLocalSpace = inLocalSpace, + IsTeleportingNextFrame = isTeleporting, + } }; // SyncPositionX @@ -496,8 +511,11 @@ public void TestSyncAxes([Values] SynchronizationType synchronizationType, [Valu // any dirty values networkTransformState = new NetworkTransform.NetworkTransformState { - InLocalSpace = inLocalSpace, - IsTeleportingNextFrame = isTeleporting, + FlagStates = new NetworkTransform.PublicFlagStates() + { + InLocalSpace = inLocalSpace, + IsTeleportingNextFrame = isTeleporting, + } }; position = networkTransform.transform.position; @@ -530,8 +548,11 @@ public void TestSyncAxes([Values] SynchronizationType synchronizationType, [Valu // any dirty values networkTransformState = new NetworkTransform.NetworkTransformState { - InLocalSpace = inLocalSpace, - IsTeleportingNextFrame = isTeleporting, + FlagStates = new NetworkTransform.PublicFlagStates() + { + InLocalSpace = inLocalSpace, + IsTeleportingNextFrame = isTeleporting, + } }; // SyncPositionY if (syncPosY) @@ -546,8 +567,11 @@ public void TestSyncAxes([Values] SynchronizationType synchronizationType, [Valu // information from the last time we applied the transform networkTransformState = new NetworkTransform.NetworkTransformState { - InLocalSpace = inLocalSpace, - IsTeleportingNextFrame = isTeleporting, + FlagStates = new NetworkTransform.PublicFlagStates() + { + InLocalSpace = inLocalSpace, + IsTeleportingNextFrame = isTeleporting, + }, }; Assert.IsTrue(networkTransform.ApplyTransformToNetworkState(ref networkTransformState, 0, networkTransform.transform)); Assert.IsFalse(networkTransformState.HasPositionY); @@ -562,8 +586,11 @@ public void TestSyncAxes([Values] SynchronizationType synchronizationType, [Valu // any dirty values networkTransformState = new NetworkTransform.NetworkTransformState { - InLocalSpace = inLocalSpace, - IsTeleportingNextFrame = isTeleporting, + FlagStates = new NetworkTransform.PublicFlagStates() + { + InLocalSpace = inLocalSpace, + IsTeleportingNextFrame = isTeleporting, + }, }; // SyncPositionZ if (syncPosZ) @@ -578,8 +605,11 @@ public void TestSyncAxes([Values] SynchronizationType synchronizationType, [Valu // information from the last time we applied the transform networkTransformState = new NetworkTransform.NetworkTransformState { - InLocalSpace = inLocalSpace, - IsTeleportingNextFrame = isTeleporting, + FlagStates = new NetworkTransform.PublicFlagStates() + { + InLocalSpace = inLocalSpace, + IsTeleportingNextFrame = isTeleporting, + }, }; Assert.IsTrue(networkTransform.ApplyTransformToNetworkState(ref networkTransformState, 0, networkTransform.transform)); Assert.IsFalse(networkTransformState.HasPositionZ); @@ -594,8 +624,11 @@ public void TestSyncAxes([Values] SynchronizationType synchronizationType, [Valu // any dirty values networkTransformState = new NetworkTransform.NetworkTransformState { - InLocalSpace = inLocalSpace, - IsTeleportingNextFrame = isTeleporting, + FlagStates = new NetworkTransform.PublicFlagStates() + { + InLocalSpace = inLocalSpace, + IsTeleportingNextFrame = isTeleporting, + }, }; // SyncRotAngleX - Now test that we don't synchronize this specific axis as long as we are not using quaternion synchronization if (syncRotX && m_Rotation == Rotation.Euler) @@ -610,8 +643,11 @@ public void TestSyncAxes([Values] SynchronizationType synchronizationType, [Valu // information from the last time we applied the transform networkTransformState = new NetworkTransform.NetworkTransformState { - InLocalSpace = inLocalSpace, - IsTeleportingNextFrame = isTeleporting, + FlagStates = new NetworkTransform.PublicFlagStates() + { + InLocalSpace = inLocalSpace, + IsTeleportingNextFrame = isTeleporting, + }, }; Assert.IsTrue(networkTransform.ApplyTransformToNetworkState(ref networkTransformState, 0, networkTransform.transform)); Assert.IsFalse(networkTransformState.HasRotAngleX); @@ -626,8 +662,11 @@ public void TestSyncAxes([Values] SynchronizationType synchronizationType, [Valu // any dirty values networkTransformState = new NetworkTransform.NetworkTransformState { - InLocalSpace = inLocalSpace, - IsTeleportingNextFrame = isTeleporting, + FlagStates = new NetworkTransform.PublicFlagStates() + { + InLocalSpace = inLocalSpace, + IsTeleportingNextFrame = isTeleporting, + }, }; // SyncRotAngleY - Now test that we don't synchronize this specific axis as long as we are not using quaternion synchronization if (syncRotY && m_Rotation == Rotation.Euler) @@ -642,8 +681,11 @@ public void TestSyncAxes([Values] SynchronizationType synchronizationType, [Valu // information from the last time we applied the transform networkTransformState = new NetworkTransform.NetworkTransformState { - InLocalSpace = inLocalSpace, - IsTeleportingNextFrame = isTeleporting, + FlagStates = new NetworkTransform.PublicFlagStates() + { + InLocalSpace = inLocalSpace, + IsTeleportingNextFrame = isTeleporting, + }, }; Assert.IsTrue(networkTransform.ApplyTransformToNetworkState(ref networkTransformState, 0, networkTransform.transform)); Assert.IsFalse(networkTransformState.HasRotAngleY); @@ -658,8 +700,11 @@ public void TestSyncAxes([Values] SynchronizationType synchronizationType, [Valu // any dirty values networkTransformState = new NetworkTransform.NetworkTransformState { - InLocalSpace = inLocalSpace, - IsTeleportingNextFrame = isTeleporting, + FlagStates = new NetworkTransform.PublicFlagStates() + { + InLocalSpace = inLocalSpace, + IsTeleportingNextFrame = isTeleporting, + }, }; // SyncRotAngleZ - Now test that we don't synchronize this specific axis as long as we are not using quaternion synchronization if (syncRotZ && m_Rotation == Rotation.Euler) @@ -674,8 +719,11 @@ public void TestSyncAxes([Values] SynchronizationType synchronizationType, [Valu // information from the last time we applied the transform networkTransformState = new NetworkTransform.NetworkTransformState { - InLocalSpace = inLocalSpace, - IsTeleportingNextFrame = isTeleporting, + FlagStates = new NetworkTransform.PublicFlagStates() + { + InLocalSpace = inLocalSpace, + IsTeleportingNextFrame = isTeleporting, + }, }; Assert.IsTrue(networkTransform.ApplyTransformToNetworkState(ref networkTransformState, 0, networkTransform.transform)); Assert.IsFalse(networkTransformState.HasRotAngleZ); @@ -690,8 +738,11 @@ public void TestSyncAxes([Values] SynchronizationType synchronizationType, [Valu // any dirty values networkTransformState = new NetworkTransform.NetworkTransformState { - InLocalSpace = inLocalSpace, - IsTeleportingNextFrame = isTeleporting, + FlagStates = new NetworkTransform.PublicFlagStates() + { + InLocalSpace = inLocalSpace, + IsTeleportingNextFrame = isTeleporting, + }, }; // SyncScaleX if (syncScaX) @@ -706,8 +757,11 @@ public void TestSyncAxes([Values] SynchronizationType synchronizationType, [Valu // information from the last time we applied the transform networkTransformState = new NetworkTransform.NetworkTransformState { - InLocalSpace = inLocalSpace, - IsTeleportingNextFrame = isTeleporting, + FlagStates = new NetworkTransform.PublicFlagStates() + { + InLocalSpace = inLocalSpace, + IsTeleportingNextFrame = isTeleporting, + }, }; Assert.IsTrue(networkTransform.ApplyTransformToNetworkState(ref networkTransformState, 0, networkTransform.transform)); Assert.IsFalse(networkTransformState.HasScaleX); @@ -722,8 +776,11 @@ public void TestSyncAxes([Values] SynchronizationType synchronizationType, [Valu // any dirty values networkTransformState = new NetworkTransform.NetworkTransformState { - InLocalSpace = inLocalSpace, - IsTeleportingNextFrame = isTeleporting, + FlagStates = new NetworkTransform.PublicFlagStates() + { + InLocalSpace = inLocalSpace, + IsTeleportingNextFrame = isTeleporting, + }, }; // SyncScaleY if (syncScaY) @@ -738,8 +795,11 @@ public void TestSyncAxes([Values] SynchronizationType synchronizationType, [Valu // information from the last time we applied the transform networkTransformState = new NetworkTransform.NetworkTransformState { - InLocalSpace = inLocalSpace, - IsTeleportingNextFrame = isTeleporting, + FlagStates = new NetworkTransform.PublicFlagStates() + { + InLocalSpace = inLocalSpace, + IsTeleportingNextFrame = isTeleporting, + }, }; Assert.IsTrue(networkTransform.ApplyTransformToNetworkState(ref networkTransformState, 0, networkTransform.transform)); Assert.IsFalse(networkTransformState.HasScaleY); @@ -754,8 +814,11 @@ public void TestSyncAxes([Values] SynchronizationType synchronizationType, [Valu // any dirty values networkTransformState = new NetworkTransform.NetworkTransformState { - InLocalSpace = inLocalSpace, - IsTeleportingNextFrame = isTeleporting, + FlagStates = new NetworkTransform.PublicFlagStates() + { + InLocalSpace = inLocalSpace, + IsTeleportingNextFrame = isTeleporting, + }, }; // SyncScaleZ if (syncScaZ) @@ -770,8 +833,11 @@ public void TestSyncAxes([Values] SynchronizationType synchronizationType, [Valu // information from the last time we applied the transform networkTransformState = new NetworkTransform.NetworkTransformState { - InLocalSpace = inLocalSpace, - IsTeleportingNextFrame = isTeleporting, + FlagStates = new NetworkTransform.PublicFlagStates() + { + InLocalSpace = inLocalSpace, + IsTeleportingNextFrame = isTeleporting, + } }; Assert.IsTrue(networkTransform.ApplyTransformToNetworkState(ref networkTransformState, 0, networkTransform.transform)); Assert.IsFalse(networkTransformState.HasScaleZ); @@ -832,7 +898,10 @@ public void TestThresholds( ScaleX = initialScale.x, ScaleY = initialScale.y, ScaleZ = initialScale.z, - InLocalSpace = inLocalSpace + FlagStates = new NetworkTransform.PublicFlagStates() + { + InLocalSpace = inLocalSpace, + }, }; // Step 1: change properties, expect state to be dirty From 3f98489ed2a971b23253b6e26307ff2fd53ab9ce Mon Sep 17 00:00:00 2001 From: Noel Stephens Date: Fri, 21 Nov 2025 18:42:12 -0600 Subject: [PATCH 13/19] refactor Migrating all of the flags into FlagStates. Migrating the bit serialization into FlagStates. Migrating flag helper methods into FlagStates. --- .../Runtime/Components/NetworkTransform.cs | 721 +++++++++--------- .../NetworkTransform/NetworkTransformBase.cs | 4 +- .../NetworkTransformGeneral.cs | 7 +- .../NetworkTransformStateTests.cs | 199 ++--- 4 files changed, 476 insertions(+), 455 deletions(-) diff --git a/com.unity.netcode.gameobjects/Runtime/Components/NetworkTransform.cs b/com.unity.netcode.gameobjects/Runtime/Components/NetworkTransform.cs index edf99c0b57..ac47b42947 100644 --- a/com.unity.netcode.gameobjects/Runtime/Components/NetworkTransform.cs +++ b/com.unity.netcode.gameobjects/Runtime/Components/NetworkTransform.cs @@ -42,35 +42,7 @@ internal enum AxialType /// This is what is used internally to get and set the current state's /// public flags/bools. /// - internal struct PublicFlagStates - { - internal bool InLocalSpace; - internal bool HasPositionX; - internal bool HasPositionY; - internal bool HasPositionZ; - internal bool HasPositionChange; - internal bool HasRotAngleX; - internal bool HasRotAngleY; - internal bool HasRotAngleZ; - internal bool HasRotAngleChange; - internal bool HasScaleX; - internal bool HasScaleY; - internal bool HasScaleZ; - internal bool HasScaleChange; - internal bool IsTeleportingNextFrame; - internal bool WasTeleported; - internal bool UseInterpolation; - internal bool QuaternionSync; - internal bool QuaternionCompression; - internal bool UseHalfFloatPrecision; - internal bool IsSynchronizing; - internal bool UsePositionSlerp; - } - - /// - /// Data structure used to synchronize the - /// - public struct NetworkTransformState : INetworkSerializable + internal struct FlagStates { // Persists between state updates (authority dictates if this is set) private const int k_InLocalSpaceBit = 0x00000001; @@ -106,6 +78,219 @@ public struct NetworkTransformState : INetworkSerializable // (Internal Debugging) When set each state update will contain a state identifier private const int k_TrackStateId = 0x10000000; + internal bool InLocalSpace; + internal bool HasPositionX; + internal bool HasPositionY; + internal bool HasPositionZ; + internal bool HasPositionChange; + internal bool HasRotAngleX; + internal bool HasRotAngleY; + internal bool HasRotAngleZ; + internal bool HasRotAngleChange; + internal bool HasScaleX; + internal bool HasScaleY; + internal bool HasScaleZ; + internal bool HasScaleChange; + internal bool IsTeleportingNextFrame; + internal bool WasTeleported; + internal bool UseInterpolation; + internal bool QuaternionSync; + internal bool QuaternionCompression; + internal bool UseHalfFloatPrecision; + internal bool IsSynchronizing; + internal bool UsePositionSlerp; + internal bool IsParented; + internal bool SynchronizeBaseHalfFloat; + internal bool ReliableSequenced; + internal bool UseUnreliableDeltas; + internal bool UnreliableFrameSync; + internal bool SwitchTransformSpaceWhenParented; + internal bool TrackByStateId; + // Authoritative and non-authoritative sides use this to determine if a NetworkTransformState is + // dirty or not. + internal bool IsDirty; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal void MarkChanged(AxialType axialType, bool changed) + { + switch (axialType) + { + 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; + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + 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; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + 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; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal bool HasScale(Axis axis) + { + return axis == Axis.X ? HasScaleX : axis == Axis.Y ? HasScaleY : HasScaleZ; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal uint GetFlags() + { + 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; } + return bitset; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal void SetFlags(uint bitset) + { + InLocalSpace = (bitset & k_InLocalSpaceBit) != 0; + 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); + 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; + } + + /// + /// Clear everything but flags that should persist between state updates until changed by authority. + /// Persistent (non-cleared) flags are , , , , + /// , , + /// + internal void ClearBitSetForNextTick() + { + HasPositionX = false; + HasPositionY = false; + HasPositionZ = false; + HasPositionChange = false; + HasRotAngleX = false; + HasRotAngleY = false; + HasRotAngleZ = false; + HasRotAngleChange = false; + HasScaleX = false; + HasScaleY = false; + HasScaleZ = false; + HasScaleChange = false; + IsTeleportingNextFrame = false; + IsSynchronizing = false; + IsParented = false; + SynchronizeBaseHalfFloat = false; + ReliableSequenced = false; + UnreliableFrameSync = false; + TrackByStateId = false; + IsDirty = false; + } + } + + /// + /// Data structure used to synchronize the + /// + public struct NetworkTransformState : INetworkSerializable + { // Used to store the tick calculated sent time internal double SentTime; @@ -137,10 +322,6 @@ public struct NetworkTransformState : INetworkSerializable // Used to store a compressed quaternion internal uint QuaternionCompressed; - // Authoritative and non-authoritative sides use this to determine if a NetworkTransformState is - // dirty or not. - internal bool IsDirty { get; set; } - /// /// The last byte size of the updated. /// @@ -159,7 +340,7 @@ public struct NetworkTransformState : INetworkSerializable private FastBufferReader m_Reader; private FastBufferWriter m_Writer; - internal PublicFlagStates FlagStates; + internal FlagStates FlagStates; /// /// When set, non-authority instances will smoothly transition between @@ -300,91 +481,6 @@ public bool HasScaleChange get { return FlagStates.HasScaleChange; } } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal void MarkChanged(AxialType axialType, bool changed) - { - switch (axialType) - { - case AxialType.Position: - FlagStates.HasPositionX = changed; - FlagStates.HasPositionY = changed; - FlagStates.HasPositionZ = changed; - FlagStates.HasPositionChange = changed; - break; - case AxialType.Rotation: - FlagStates.HasRotAngleX = changed; - FlagStates.HasRotAngleY = changed; - FlagStates.HasRotAngleZ = changed; - FlagStates.HasRotAngleChange = changed; - break; - case AxialType.Scale: - FlagStates.HasScaleX = changed; - FlagStates.HasScaleY = changed; - FlagStates.HasScaleZ = changed; - FlagStates.HasScaleChange = changed; - break; - } - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal void SetHasPosition(Axis axis, bool changed) - { - switch (axis) - { - case Axis.X: - FlagStates.HasPositionX = changed; - break; - case Axis.Y: - FlagStates.HasPositionY = changed; - break; - case Axis.Z: - FlagStates.HasPositionZ = changed; - break; - } - FlagStates.HasPositionChange = HasPositionX || HasPositionY || HasPositionZ; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal void SetHasRotation(Axis axis, bool changed) - { - switch (axis) - { - case Axis.X: - FlagStates.HasRotAngleX = changed; - break; - case Axis.Y: - FlagStates.HasRotAngleY = changed; - break; - case Axis.Z: - FlagStates.HasRotAngleZ = changed; - break; - } - FlagStates.HasRotAngleChange = HasRotAngleX || HasRotAngleY || HasRotAngleZ; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal void SetHasScale(Axis axis, bool changed) - { - switch (axis) - { - case Axis.X: - FlagStates.HasScaleX = changed; - break; - case Axis.Y: - FlagStates.HasScaleY = changed; - break; - case Axis.Z: - FlagStates.HasScaleZ = changed; - break; - } - FlagStates.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. /// @@ -495,52 +591,17 @@ public bool UsePositionSlerp /// true or false as to whether this state update was an unreliable frame synchronization. public bool IsUnreliableFrameSync() { - return UnreliableFrameSync; + return FlagStates.UnreliableFrameSync; } /// /// Returns true if this state was sent with reliable delivery. /// If false, then it was sent with unreliable delivery. /// - /// - /// Unreliable delivery will only be used if is set. - /// /// true or false as to whether this state update was sent with reliable delivery. public bool IsReliableStateUpdate() { - return ReliableSequenced; - } - - internal bool IsParented; - - internal bool SynchronizeBaseHalfFloat; - - internal bool ReliableSequenced; - - internal bool UseUnreliableDeltas; - - internal bool UnreliableFrameSync; - - 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() - { - MarkChanged(AxialType.Position, false); - MarkChanged(AxialType.Rotation, false); - MarkChanged(AxialType.Scale, false); - FlagStates.IsTeleportingNextFrame = false; - FlagStates.IsSynchronizing = false; - IsParented = false; - SynchronizeBaseHalfFloat = false; - ReliableSequenced = false; - UnreliableFrameSync = false; - TrackByStateId = false; - IsDirty = false; + return FlagStates.ReliableSequenced; } /// @@ -675,32 +736,39 @@ public void NetworkSerialize(BufferSerializer serializer) where T : IReade { if (isWriting) { - if (UseUnreliableDeltas) + if (FlagStates.UseUnreliableDeltas) { // If teleporting, synchronizing, doing an axial frame sync, or using half float precision and we collapsed a delta into the base position - if (IsTeleportingNextFrame || IsSynchronizing || UnreliableFrameSync || (UseHalfFloatPrecision && NetworkDeltaPosition.CollapsedDeltaIntoBase)) + if (FlagStates.IsTeleportingNextFrame || FlagStates.IsSynchronizing || FlagStates.UnreliableFrameSync + || (FlagStates.UseHalfFloatPrecision && NetworkDeltaPosition.CollapsedDeltaIntoBase)) { // Send the message reliably - ReliableSequenced = true; + FlagStates.ReliableSequenced = true; } else { - ReliableSequenced = false; + FlagStates.ReliableSequenced = false; } } else // If not using UseUnreliableDeltas, then always use reliable fragmented sequenced { - ReliableSequenced = true; + FlagStates.ReliableSequenced = true; } - SerializeBitset(ref m_Writer); + // Serialize the flags as an unsigned int + BytePacker.WriteValueBitPacked(m_Writer, FlagStates.GetFlags()); + // We use network ticks as opposed to absolute time as the authoritative // side updates on every new tick. BytePacker.WriteValueBitPacked(m_Writer, NetworkTick); } else { - DeserializeBitset(ref m_Reader); + // Deserialize the flags + ByteUnpacker.ReadValueBitPacked(m_Reader, out uint bitset); + // Set the flags + FlagStates.SetFlags(bitset); + // We use network ticks as opposed to absolute time as the authoritative // side updates on every new tick. ByteUnpacker.ReadValueBitPacked(m_Reader, out NetworkTick); @@ -716,7 +784,7 @@ public void NetworkSerialize(BufferSerializer serializer) where T : IReade #endif // If debugging states and track by state identifier is enabled, serialize the current state identifier - if (TrackByStateId) + if (FlagStates.TrackByStateId) { serializer.SerializeValue(ref StateId); } @@ -726,7 +794,7 @@ public void NetworkSerialize(BufferSerializer serializer) where T : IReade { if (UseHalfFloatPrecision) { - NetworkDeltaPosition.SynchronizeBase = SynchronizeBaseHalfFloat; + NetworkDeltaPosition.SynchronizeBase = FlagStates.SynchronizeBaseHalfFloat; // Apply which axis should be updated for both write/read (teleporting, synchronizing, or just updating) NetworkDeltaPosition.HalfVector3.AxisToSynchronize[0] = HasPositionX; @@ -915,7 +983,7 @@ public void NetworkSerialize(BufferSerializer serializer) where T : IReade { // If we are teleporting (which includes synchronizing) and the associated NetworkObject has a parent // then we want to serialize the LossyScale since NetworkObject spawn order is not guaranteed - if (IsTeleportingNextFrame && IsParented) + if (IsTeleportingNextFrame && FlagStates.IsParented) { serializer.SerializeValue(ref LossyScale); } @@ -992,7 +1060,7 @@ public void NetworkSerialize(BufferSerializer serializer) where T : IReade if (!isWriting) { // Go ahead and mark the local state dirty - IsDirty = HasPositionChange || HasRotAngleChange || HasScaleChange; + FlagStates.IsDirty = HasPositionChange || HasRotAngleChange || HasScaleChange; LastSerializedSize = m_Reader.Position - positionStart; } else @@ -1004,69 +1072,6 @@ public void NetworkSerialize(BufferSerializer serializer) where T : IReade } } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal void SerializeBitset(ref FastBufferWriter writer) - { - uint bitset = 0; - var flagStates = FlagStates; - - if (flagStates.InLocalSpace) { bitset |= k_InLocalSpaceBit; } - if (flagStates.HasPositionX) { bitset |= k_PositionXBit; } - if (flagStates.HasPositionY) { bitset |= k_PositionYBit; } - if (flagStates.HasPositionZ) { bitset |= k_PositionZBit; } - if (flagStates.HasRotAngleX) { bitset |= k_RotAngleXBit; } - if (flagStates.HasRotAngleY) { bitset |= k_RotAngleYBit; } - if (flagStates.HasRotAngleZ) { bitset |= k_RotAngleZBit; } - if (flagStates.HasScaleX) { bitset |= k_ScaleXBit; } - if (flagStates.HasScaleY) { bitset |= k_ScaleYBit; } - if (flagStates.HasScaleZ) { bitset |= k_ScaleZBit; } - if (flagStates.IsTeleportingNextFrame) { bitset |= k_TeleportingBit; } - if (flagStates.UseInterpolation) { bitset |= k_Interpolate; } - if (flagStates.QuaternionSync) { bitset |= k_QuaternionSync; } - if (flagStates.QuaternionCompression) { bitset |= k_QuaternionCompress; } - if (flagStates.UseHalfFloatPrecision) { bitset |= k_UseHalfFloats; } - if (flagStates.IsSynchronizing) { bitset |= k_Synchronization; } - if (flagStates.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)] - internal void DeserializeBitset(ref FastBufferReader reader) - { - ByteUnpacker.ReadValueBitPacked(reader, out uint bitset); - FlagStates.InLocalSpace = (bitset & k_InLocalSpaceBit) != 0; - 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); - FlagStates.IsTeleportingNextFrame = (bitset & k_TeleportingBit) != 0; - FlagStates.UseInterpolation = (bitset & k_Interpolate) != 0; - FlagStates.QuaternionSync = (bitset & k_QuaternionSync) != 0; - FlagStates.QuaternionCompression = (bitset & k_QuaternionCompress) != 0; - FlagStates.UseHalfFloatPrecision = (bitset & k_UseHalfFloats) != 0; - FlagStates.IsSynchronizing = (bitset & k_Synchronization) != 0; - FlagStates.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 @@ -1670,7 +1675,7 @@ public Vector3 GetSpaceRelativePosition(bool getCurrentState = false) { if (!getCurrentState || CanCommitToTransform) { - return InLocalSpace ? transform.localPosition : transform.position; + return InLocalSpace ? CachedTransform.localPosition : CachedTransform.position; } else { @@ -2037,7 +2042,7 @@ private void TryCommitTransform(bool synchronize = false, bool settingState = fa // that is outside of the normal internal tick flow. m_LocalAuthoritativeNetworkState.NetworkTick = m_CachedNetworkManager.NetworkTickSystem.ServerTime.Tick; - if (SwitchTransformSpaceWhenParented && m_LocalAuthoritativeNetworkState.ExplicitSet && m_LocalAuthoritativeNetworkState.IsDirty && transform.parent != null && !m_LocalAuthoritativeNetworkState.InLocalSpace) + if (SwitchTransformSpaceWhenParented && m_LocalAuthoritativeNetworkState.ExplicitSet && m_LocalAuthoritativeNetworkState.FlagStates.IsDirty && transform.parent != null && !m_LocalAuthoritativeNetworkState.InLocalSpace) { InLocalSpace = true; CheckForStateChange(ref m_LocalAuthoritativeNetworkState, synchronize, forceState: true); @@ -2076,7 +2081,7 @@ private void TryCommitTransform(bool synchronize = false, bool settingState = fa // frame synch "tick slot". Once we send a frame synch, if no other deltas occur after that // (i.e. the object is at rest) then we will stop sending frame synch's until the object begins // moving, rotating, or scaling again. - if (UseUnreliableDeltas && !m_LocalAuthoritativeNetworkState.UnreliableFrameSync && !synchronize) + if (UseUnreliableDeltas && !m_LocalAuthoritativeNetworkState.FlagStates.UnreliableFrameSync && !synchronize) { m_DeltaSynch = true; } @@ -2135,7 +2140,7 @@ internal NetworkTransformState ApplyLocalNetworkState(Transform transform) { // Since we never commit these changes, we need to simulate that any changes were committed previously and the bitset // value would already be reset prior to having the state applied - m_LocalAuthoritativeNetworkState.ClearBitSetForNextTick(); + m_LocalAuthoritativeNetworkState.FlagStates.ClearBitSetForNextTick(); // Now check the transform for any threshold value changes CheckForStateChange(ref m_LocalAuthoritativeNetworkState); @@ -2156,7 +2161,7 @@ internal bool ApplyTransformToNetworkState(ref NetworkTransformState networkStat networkState.FlagStates.QuaternionSync = UseQuaternionSynchronization; networkState.FlagStates.UseHalfFloatPrecision = UseHalfFloatPrecision; networkState.FlagStates.QuaternionCompression = UseQuaternionCompression; - networkState.UseUnreliableDeltas = UseUnreliableDeltas; + networkState.FlagStates.UseUnreliableDeltas = UseUnreliableDeltas; m_HalfPositionState = new NetworkDeltaPosition(Vector3.zero, 0, math.bool3(SyncPositionX, SyncPositionY, SyncPositionZ)); return CheckForStateChange(ref networkState); @@ -2193,7 +2198,7 @@ private bool CheckForStateChange(ref NetworkTransformState networkState, bool is var flagStates = networkState.FlagStates; // This is used to determine if we need to send the state update reliably (if we are doing an axial sync) - networkState.UnreliableFrameSync = isAxisSync; + flagStates.UnreliableFrameSync = isAxisSync; var isTeleportingAndNotSynchronizing = flagStates.IsTeleportingNextFrame && !isSynchronization; var isDirty = false; @@ -2259,7 +2264,7 @@ private bool CheckForStateChange(ref NetworkTransformState networkState, bool is flagStates.IsSynchronizing = isSynchronization; // Check for parenting when synchronizing and/or teleporting - if (isSynchronization || networkState.IsTeleportingNextFrame || forceState) + if (isSynchronization || flagStates.IsTeleportingNextFrame || forceState) { // This all has to do with complex nested hierarchies and how it impacts scale // when set for the first time or teleporting and depends upon whether the @@ -2288,7 +2293,7 @@ private bool CheckForStateChange(ref NetworkTransformState networkState, bool is } } - networkState.IsParented = hasParentNetworkObject; + flagStates.IsParented = hasParentNetworkObject; } if (Interpolate != flagStates.UseInterpolation) @@ -2327,9 +2332,9 @@ private bool CheckForStateChange(ref NetworkTransformState networkState, bool is flagStates.IsTeleportingNextFrame = true; } - if (UseUnreliableDeltas != networkState.UseUnreliableDeltas) + if (UseUnreliableDeltas != flagStates.UseUnreliableDeltas) { - networkState.UseUnreliableDeltas = UseUnreliableDeltas; + flagStates.UseUnreliableDeltas = UseUnreliableDeltas; isDirty = true; flagStates.IsTeleportingNextFrame = true; } @@ -2340,21 +2345,21 @@ private bool CheckForStateChange(ref NetworkTransformState networkState, bool is if (SyncPositionX && (Mathf.Abs(networkState.PositionX - position.x) >= positionThreshold.x || flagStates.IsTeleportingNextFrame || isAxisSync || forceState)) { networkState.PositionX = position.x; - networkState.SetHasPosition(Axis.X, true); + flagStates.SetHasPosition(Axis.X, true); isPositionDirty = true; } if (SyncPositionY && (Mathf.Abs(networkState.PositionY - position.y) >= positionThreshold.y || flagStates.IsTeleportingNextFrame || isAxisSync || forceState)) { networkState.PositionY = position.y; - networkState.SetHasPosition(Axis.Y, true); + flagStates.SetHasPosition(Axis.Y, true); isPositionDirty = true; } if (SyncPositionZ && (Mathf.Abs(networkState.PositionZ - position.z) >= positionThreshold.z || flagStates.IsTeleportingNextFrame || isAxisSync || forceState)) { networkState.PositionZ = position.z; - networkState.SetHasPosition(Axis.Z, true); + flagStates.SetHasPosition(Axis.Z, true); isPositionDirty = true; } } @@ -2410,11 +2415,11 @@ private bool CheckForStateChange(ref NetworkTransformState networkState, bool is // If ownership offset is greater or we are doing an axial synchronization then synchronize the base position if ((m_HalfFloatTargetTickOwnership > CurrentTick || isAxisSync) && !flagStates.IsTeleportingNextFrame) { - networkState.SynchronizeBaseHalfFloat = true; + flagStates.SynchronizeBaseHalfFloat = true; } else { - networkState.SynchronizeBaseHalfFloat = UseUnreliableDeltas ? m_HalfPositionState.CollapsedDeltaIntoBase : false; + flagStates.SynchronizeBaseHalfFloat = UseUnreliableDeltas ? m_HalfPositionState.CollapsedDeltaIntoBase : false; } } else // If synchronizing is set, then use the current full position value on the server side @@ -2471,21 +2476,21 @@ private bool CheckForStateChange(ref NetworkTransformState networkState, bool is if (SyncRotAngleX && (Mathf.Abs(Mathf.DeltaAngle(networkState.RotAngleX, rotAngles.x)) >= rotationThreshold.x || flagStates.IsTeleportingNextFrame || isAxisSync || forceState)) { networkState.RotAngleX = rotAngles.x; - networkState.SetHasRotation(Axis.X, true); + flagStates.SetHasRotation(Axis.X, true); isRotationDirty = true; } if (SyncRotAngleY && (Mathf.Abs(Mathf.DeltaAngle(networkState.RotAngleY, rotAngles.y)) >= rotationThreshold.y || flagStates.IsTeleportingNextFrame || isAxisSync || forceState)) { networkState.RotAngleY = rotAngles.y; - networkState.SetHasRotation(Axis.Y, true); + flagStates.SetHasRotation(Axis.Y, true); isRotationDirty = true; } if (SyncRotAngleZ && (Mathf.Abs(Mathf.DeltaAngle(networkState.RotAngleZ, rotAngles.z)) >= rotationThreshold.z || flagStates.IsTeleportingNextFrame || isAxisSync || forceState)) { networkState.RotAngleZ = rotAngles.z; - networkState.SetHasRotation(Axis.Z, true); + flagStates.SetHasRotation(Axis.Z, true); isRotationDirty = true; } } @@ -2509,7 +2514,7 @@ private bool CheckForStateChange(ref NetworkTransformState networkState, bool is if (isRotationDirty) { networkState.Rotation = rotation; - networkState.MarkChanged(AxialType.Rotation, true); + flagStates.MarkChanged(AxialType.Rotation, true); } } @@ -2518,7 +2523,7 @@ private bool CheckForStateChange(ref NetworkTransformState networkState, bool is { // If we are synchronizing and the associated NetworkObject has a parent then we want to send the // LossyScale if the NetworkObject has a parent since NetworkObject spawn order is not guaranteed - if (networkState.IsParented) + if (networkState.FlagStates.IsParented) { networkState.LossyScale = transform.lossyScale; } @@ -2532,21 +2537,21 @@ private bool CheckForStateChange(ref NetworkTransformState networkState, bool is if (SyncScaleX && (Mathf.Abs(networkState.ScaleX - scale.x) >= ScaleThreshold || flagStates.IsTeleportingNextFrame || isAxisSync || forceState)) { networkState.ScaleX = scale.x; - networkState.SetHasScale(Axis.X, true); + flagStates.SetHasScale(Axis.X, true); isScaleDirty = true; } if (SyncScaleY && (Mathf.Abs(networkState.ScaleY - scale.y) >= ScaleThreshold || flagStates.IsTeleportingNextFrame || isAxisSync || forceState)) { networkState.ScaleY = scale.y; - networkState.SetHasScale(Axis.Y, true); + flagStates.SetHasScale(Axis.Y, true); isScaleDirty = true; } if (SyncScaleZ && (Mathf.Abs(networkState.ScaleZ - scale.z) >= ScaleThreshold || flagStates.IsTeleportingNextFrame || isAxisSync || forceState)) { networkState.ScaleZ = scale.z; - networkState.SetHasScale(Axis.Z, true); + flagStates.SetHasScale(Axis.Z, true); isScaleDirty = true; } } @@ -2559,7 +2564,7 @@ private bool CheckForStateChange(ref NetworkTransformState networkState, bool is { isScaleDirty = true; networkState.Scale[i] = scale[i]; - networkState.SetHasScale((Axis)i, i == 0 ? SyncScaleX : i == 1 ? SyncScaleY : SyncScaleZ); + flagStates.SetHasScale((Axis)i, i == 0 ? SyncScaleX : i == 1 ? SyncScaleY : SyncScaleZ); } } } @@ -2577,7 +2582,7 @@ private bool CheckForStateChange(ref NetworkTransformState networkState, bool is { networkState.Scale = transform.localScale; } - networkState.MarkChanged(AxialType.Scale, true); + flagStates.MarkChanged(AxialType.Scale, true); isScaleDirty = true; } isDirty |= isPositionDirty || isRotationDirty || isScaleDirty; @@ -2594,7 +2599,7 @@ private bool CheckForStateChange(ref NetworkTransformState networkState, bool is } // Mark the state dirty for the next network tick update to clear out the bitset values - networkState.IsDirty |= isDirty; + flagStates.IsDirty |= isDirty; // Apply any changes to the flag states once networkState.FlagStates = flagStates; @@ -2698,6 +2703,7 @@ protected internal void ApplyAuthoritativeState() } #endif var networkState = m_LocalAuthoritativeNetworkState; + var flagStates = m_LocalAuthoritativeNetworkState.FlagStates; // The m_InternalCurrentPosition, m_InternalCurrentRotation, and m_InternalCurrentScale values are continually updated // at the end of this method and assure that when not interpolating the non-authoritative side // cannot make adjustments to any portions the transform not being synchronized. @@ -2727,19 +2733,19 @@ protected internal void ApplyAuthoritativeState() // setting. if (!SwitchTransformSpaceWhenParented) { - InLocalSpace = networkState.InLocalSpace; + InLocalSpace = flagStates.InLocalSpace; } // Non-Authority Preservers the authority's transform state update modes - Interpolate = networkState.UseInterpolation; - UseHalfFloatPrecision = networkState.UseHalfFloatPrecision; - UseQuaternionSynchronization = networkState.QuaternionSync; - UseQuaternionCompression = networkState.QuaternionCompression; - UseUnreliableDeltas = networkState.UseUnreliableDeltas; + Interpolate = flagStates.UseInterpolation; + UseHalfFloatPrecision = flagStates.UseHalfFloatPrecision; + UseQuaternionSynchronization = flagStates.QuaternionSync; + UseQuaternionCompression = flagStates.QuaternionCompression; + UseUnreliableDeltas = flagStates.UseUnreliableDeltas; - if (SlerpPosition != networkState.UsePositionSlerp) + if (SlerpPosition != flagStates.UsePositionSlerp) { - SlerpPosition = networkState.UsePositionSlerp; + SlerpPosition = flagStates.UsePositionSlerp; UpdatePositionSlerp(); } @@ -2801,16 +2807,16 @@ protected internal void ApplyAuthoritativeState() // Non-Interpolated Position and Scale if (UseHalfFloatPrecision) { - if (networkState.HasPositionChange && SynchronizePosition) + if (flagStates.HasPositionChange && SynchronizePosition) { adjustedPosition = m_LastStateTargetPosition; } - if (networkState.HasScaleChange && SynchronizeScale) + if (flagStates.HasScaleChange && SynchronizeScale) { for (int i = 0; i < 3; i++) { - if (m_LocalAuthoritativeNetworkState.HasScale((Axis)i)) + if (m_LocalAuthoritativeNetworkState.FlagStates.HasScale((Axis)i)) { adjustedScale[i] = m_LocalAuthoritativeNetworkState.Scale[i]; } @@ -2819,26 +2825,26 @@ protected internal void ApplyAuthoritativeState() } else { - if (networkState.HasPositionX) { adjustedPosition.x = networkState.PositionX; } - if (networkState.HasPositionY) { adjustedPosition.y = networkState.PositionY; } - if (networkState.HasPositionZ) { adjustedPosition.z = networkState.PositionZ; } - if (networkState.HasScaleX) { adjustedScale.x = networkState.ScaleX; } - if (networkState.HasScaleY) { adjustedScale.y = networkState.ScaleY; } - if (networkState.HasScaleZ) { adjustedScale.z = networkState.ScaleZ; } + if (flagStates.HasPositionX) { adjustedPosition.x = networkState.PositionX; } + if (flagStates.HasPositionY) { adjustedPosition.y = networkState.PositionY; } + if (flagStates.HasPositionZ) { adjustedPosition.z = networkState.PositionZ; } + if (flagStates.HasScaleX) { adjustedScale.x = networkState.ScaleX; } + if (flagStates.HasScaleY) { adjustedScale.y = networkState.ScaleY; } + if (flagStates.HasScaleZ) { adjustedScale.z = networkState.ScaleZ; } } // Non-interpolated rotation if (SynchronizeRotation) { - if (networkState.QuaternionSync && networkState.HasRotAngleChange) + if (flagStates.QuaternionSync && flagStates.HasRotAngleChange) { adjustedRotation = networkState.Rotation; } else { - if (networkState.HasRotAngleX) { adjustedRotAngles.x = networkState.RotAngleX; } - if (networkState.HasRotAngleY) { adjustedRotAngles.y = networkState.RotAngleY; } - if (networkState.HasRotAngleZ) { adjustedRotAngles.z = networkState.RotAngleZ; } + if (flagStates.HasRotAngleX) { adjustedRotAngles.x = networkState.RotAngleX; } + if (flagStates.HasRotAngleY) { adjustedRotAngles.y = networkState.RotAngleY; } + if (flagStates.HasRotAngleZ) { adjustedRotAngles.z = networkState.RotAngleZ; } adjustedRotation.eulerAngles = adjustedRotAngles; } } @@ -2848,7 +2854,7 @@ protected internal void ApplyAuthoritativeState() if (SynchronizePosition) { // Update our current position if it changed or we are interpolating - if (networkState.HasPositionChange || Interpolate) + if (flagStates.HasPositionChange || Interpolate) { if (SyncPositionX && SyncPositionY && SyncPositionZ) { @@ -2857,7 +2863,7 @@ protected internal void ApplyAuthoritativeState() else { // Preserve any non-synchronized changes to the local instance's position - var position = InLocalSpace ? transform.localPosition : transform.position; + var position = InLocalSpace ? CachedTransform.localPosition : CachedTransform.position; m_InternalCurrentPosition.x = SyncPositionX ? adjustedPosition.x : position.x; m_InternalCurrentPosition.y = SyncPositionY ? adjustedPosition.y : position.y; m_InternalCurrentPosition.z = SyncPositionZ ? adjustedPosition.z : position.z; @@ -2878,11 +2884,11 @@ protected internal void ApplyAuthoritativeState() { if (PositionInLocalSpace) { - transform.localPosition = m_InternalCurrentPosition; + CachedTransform.localPosition = m_InternalCurrentPosition; } else { - transform.position = m_InternalCurrentPosition; + CachedTransform.position = m_InternalCurrentPosition; } } } @@ -2900,7 +2906,7 @@ protected internal void ApplyAuthoritativeState() else { // Preserve any non-synchronized changes to the local instance's rotation - var rotation = InLocalSpace ? transform.localRotation.eulerAngles : transform.rotation.eulerAngles; + var rotation = InLocalSpace ? CachedTransform.localRotation.eulerAngles : CachedTransform.rotation.eulerAngles; var currentEuler = m_InternalCurrentRotation.eulerAngles; var updatedEuler = adjustedRotation.eulerAngles; currentEuler.x = SyncRotAngleX ? updatedEuler.x : rotation.x; @@ -2920,11 +2926,11 @@ protected internal void ApplyAuthoritativeState() { if (RotationInLocalSpace) { - transform.localRotation = m_InternalCurrentRotation; + CachedTransform.localRotation = m_InternalCurrentRotation; } else { - transform.rotation = m_InternalCurrentRotation; + CachedTransform.rotation = m_InternalCurrentRotation; } } } @@ -2933,7 +2939,7 @@ protected internal void ApplyAuthoritativeState() if (SynchronizeScale) { // Update our current scale if it changed or we are interpolating - if (networkState.HasScaleChange || Interpolate) + if (flagStates.HasScaleChange || Interpolate) { if (SyncScaleX && SyncScaleY && SyncScaleZ) { @@ -2942,13 +2948,13 @@ protected internal void ApplyAuthoritativeState() else { // Preserve any non-synchronized changes to the local instance's scale - var scale = transform.localScale; + var scale = CachedTransform.localScale; m_InternalCurrentScale.x = SyncScaleX ? adjustedScale.x : scale.x; m_InternalCurrentScale.y = SyncScaleY ? adjustedScale.y : scale.y; m_InternalCurrentScale.z = SyncScaleZ ? adjustedScale.z : scale.z; } } - transform.localScale = m_InternalCurrentScale; + CachedTransform.localScale = m_InternalCurrentScale; } OnTransformUpdated(); } @@ -2970,31 +2976,32 @@ private void ApplyTeleportingState(NetworkTransformState newState) var currentPosition = GetSpaceRelativePosition(); var currentRotation = GetSpaceRelativeRotation(); var currentEulerAngles = currentRotation.eulerAngles; - var currentScale = transform.localScale; + var currentScale = CachedTransform.localScale; var isSynchronization = newState.IsSynchronizing; + var flagStates = newState.FlagStates; // Clear all interpolators m_ScaleInterpolator.Clear(); m_PositionInterpolator.Clear(); m_RotationInterpolator.Clear(); - if (newState.HasPositionChange) + if (flagStates.HasPositionChange) { if (!UseHalfFloatPrecision) { // Adjust based on which axis changed - if (newState.HasPositionX) + if (flagStates.HasPositionX) { currentPosition.x = newState.PositionX; } - if (newState.HasPositionY) + if (flagStates.HasPositionY) { currentPosition.y = newState.PositionY; } - if (newState.HasPositionZ) + if (flagStates.HasPositionZ) { currentPosition.z = newState.PositionZ; } @@ -3034,13 +3041,13 @@ private void ApplyTeleportingState(NetworkTransformState newState) m_LastStateTargetPosition = currentPosition; // Apply the position - if (newState.InLocalSpace) + if (flagStates.InLocalSpace) { - transform.localPosition = currentPosition; + CachedTransform.localPosition = currentPosition; } else { - transform.position = currentPosition; + CachedTransform.position = currentPosition; } #if COM_UNITY_MODULES_PHYSICS || COM_UNITY_MODULES_PHYSICS2D @@ -3056,7 +3063,7 @@ private void ApplyTeleportingState(NetworkTransformState newState) } } - if (newState.HasScaleChange) + if (flagStates.HasScaleChange) { bool shouldUseLossy = false; @@ -3067,17 +3074,17 @@ private void ApplyTeleportingState(NetworkTransformState newState) else { // Adjust based on which axis changed - if (newState.HasScaleX) + if (flagStates.HasScaleX) { currentScale.x = shouldUseLossy ? newState.LossyScale.x : newState.ScaleX; } - if (newState.HasScaleY) + if (flagStates.HasScaleY) { currentScale.y = shouldUseLossy ? newState.LossyScale.y : newState.ScaleY; } - if (newState.HasScaleZ) + if (flagStates.HasScaleZ) { currentScale.z = shouldUseLossy ? newState.LossyScale.z : newState.ScaleZ; } @@ -3087,7 +3094,7 @@ private void ApplyTeleportingState(NetworkTransformState newState) m_TargetScale = currentScale; // Apply the adjusted scale - transform.localScale = currentScale; + CachedTransform.localScale = currentScale; if (Interpolate) { @@ -3095,26 +3102,26 @@ private void ApplyTeleportingState(NetworkTransformState newState) } } - if (newState.HasRotAngleChange) + if (flagStates.HasRotAngleChange) { - if (newState.QuaternionSync) + if (flagStates.QuaternionSync) { currentRotation = newState.Rotation; } else { // Adjust based on which axis changed - if (newState.HasRotAngleX) + if (flagStates.HasRotAngleX) { currentEulerAngles.x = newState.RotAngleX; } - if (newState.HasRotAngleY) + if (flagStates.HasRotAngleY) { currentEulerAngles.y = newState.RotAngleY; } - if (newState.HasRotAngleZ) + if (flagStates.HasRotAngleZ) { currentEulerAngles.z = newState.RotAngleZ; } @@ -3126,17 +3133,17 @@ private void ApplyTeleportingState(NetworkTransformState newState) if (InLocalSpace) { - transform.localRotation = currentRotation; + CachedTransform.localRotation = currentRotation; } else { - transform.rotation = currentRotation; + CachedTransform.rotation = currentRotation; } #if COM_UNITY_MODULES_PHYSICS || COM_UNITY_MODULES_PHYSICS2D if (m_UseRigidbodyForMotion) { - m_NetworkRigidbodyInternal.SetRotation(transform.rotation); + m_NetworkRigidbodyInternal.SetRotation(CachedTransform.rotation); } #endif @@ -3144,7 +3151,7 @@ private void ApplyTeleportingState(NetworkTransformState newState) { m_RotationInterpolator.AutoConvertTransformSpace = SwitchTransformSpaceWhenParented; m_RotationInterpolator.InLocalSpace = newState.InLocalSpace; - m_RotationInterpolator.ResetTo(transform.parent, currentRotation, sentTime); + m_RotationInterpolator.ResetTo(CachedTransform.parent, currentRotation, sentTime); } } @@ -3167,30 +3174,31 @@ private void ApplyTeleportingState(NetworkTransformState newState) /// internal void ApplyUpdatedState(NetworkTransformState newState) { + var flagStates = newState.FlagStates; // Set the transforms's synchronization modes - InLocalSpace = newState.InLocalSpace; - Interpolate = newState.UseInterpolation; - UseQuaternionSynchronization = newState.QuaternionSync; - UseQuaternionCompression = newState.QuaternionCompression; - UseHalfFloatPrecision = newState.UseHalfFloatPrecision; - UseUnreliableDeltas = newState.UseUnreliableDeltas; + InLocalSpace = flagStates.InLocalSpace; + Interpolate = flagStates.UseInterpolation; + UseQuaternionSynchronization = flagStates.QuaternionSync; + UseQuaternionCompression = flagStates.QuaternionCompression; + UseHalfFloatPrecision = flagStates.UseHalfFloatPrecision; + UseUnreliableDeltas = flagStates.UseUnreliableDeltas; - SwitchTransformSpaceWhenParented = newState.SwitchTransformSpaceWhenParented; + SwitchTransformSpaceWhenParented = flagStates.SwitchTransformSpaceWhenParented; - if (SlerpPosition != newState.UsePositionSlerp) + if (SlerpPosition != flagStates.UsePositionSlerp) { - SlerpPosition = newState.UsePositionSlerp; + SlerpPosition = flagStates.UsePositionSlerp; UpdatePositionSlerp(); } m_LocalAuthoritativeNetworkState = newState; - if (m_LocalAuthoritativeNetworkState.IsTeleportingNextFrame) + if (flagStates.IsTeleportingNextFrame) { LastTickSync = m_LocalAuthoritativeNetworkState.GetNetworkTick(); ApplyTeleportingState(m_LocalAuthoritativeNetworkState); return; } - else if (m_LocalAuthoritativeNetworkState.IsSynchronizing) + else if (flagStates.IsSynchronizing) { LastTickSync = m_LocalAuthoritativeNetworkState.GetNetworkTick(); } @@ -3199,10 +3207,10 @@ internal void ApplyUpdatedState(NetworkTransformState newState) var currentRotation = GetSpaceRelativeRotation(); // Only if using half float precision and our position had changed last update then - if (UseHalfFloatPrecision && m_LocalAuthoritativeNetworkState.HasPositionChange) + if (UseHalfFloatPrecision && flagStates.HasPositionChange) { // Do a full precision synchronization to apply the base position and offset. - if (m_LocalAuthoritativeNetworkState.SynchronizeBaseHalfFloat) + if (flagStates.SynchronizeBaseHalfFloat) { m_HalfPositionState = m_LocalAuthoritativeNetworkState.NetworkDeltaPosition; } @@ -3230,25 +3238,25 @@ internal void ApplyUpdatedState(NetworkTransformState newState) // Apply axial changes from the new state // Either apply the delta position target position or the current state's delta position // depending upon whether UsePositionDeltaCompression is enabled - if (m_LocalAuthoritativeNetworkState.HasPositionChange) + if (flagStates.HasPositionChange) { // If interpolating, get the current value as the final next position or current position // depending upon if the interpolator is still processing a state or not. - if (!m_LocalAuthoritativeNetworkState.UseHalfFloatPrecision) + if (!flagStates.UseHalfFloatPrecision) { var newTargetPosition = (Interpolate && SwitchTransformSpaceWhenParented) ? m_PositionInterpolator.GetInterpolatedValue() : m_LastStateTargetPosition; var position = m_LocalAuthoritativeNetworkState.GetPosition(); - if (m_LocalAuthoritativeNetworkState.HasPositionX) + if (flagStates.HasPositionX) { newTargetPosition.x = position.x; } - if (m_LocalAuthoritativeNetworkState.HasPositionY) + if (flagStates.HasPositionY) { newTargetPosition.y = position.y; } - if (m_LocalAuthoritativeNetworkState.HasPositionZ) + if (flagStates.HasPositionZ) { newTargetPosition.z = position.z; } @@ -3258,14 +3266,14 @@ internal void ApplyUpdatedState(NetworkTransformState newState) UpdatePositionInterpolator(m_LastStateTargetPosition, sentTime); } - if (m_LocalAuthoritativeNetworkState.HasScaleChange) + if (flagStates.HasScaleChange) { var currentScale = m_TargetScale; if (UseHalfFloatPrecision) { for (int i = 0; i < 3; i++) { - if (m_LocalAuthoritativeNetworkState.HasScale((Axis)i)) + if (m_LocalAuthoritativeNetworkState.FlagStates.HasScale((Axis)i)) { currentScale[i] = m_LocalAuthoritativeNetworkState.Scale[i]; } @@ -3273,17 +3281,17 @@ internal void ApplyUpdatedState(NetworkTransformState newState) } else { - if (m_LocalAuthoritativeNetworkState.HasScaleX) + if (flagStates.HasScaleX) { currentScale.x = m_LocalAuthoritativeNetworkState.ScaleX; } - if (m_LocalAuthoritativeNetworkState.HasScaleY) + if (flagStates.HasScaleY) { currentScale.y = m_LocalAuthoritativeNetworkState.ScaleY; } - if (m_LocalAuthoritativeNetworkState.HasScaleZ) + if (flagStates.HasScaleZ) { currentScale.z = m_LocalAuthoritativeNetworkState.ScaleZ; } @@ -3295,9 +3303,9 @@ internal void ApplyUpdatedState(NetworkTransformState newState) // With rotation, we check if there are any changes first and // if so then apply the changes to the current Euler rotation // values. - if (m_LocalAuthoritativeNetworkState.HasRotAngleChange) + if (flagStates.HasRotAngleChange) { - if (m_LocalAuthoritativeNetworkState.QuaternionSync) + if (flagStates.QuaternionSync) { currentRotation = m_LocalAuthoritativeNetworkState.Rotation; } @@ -3306,17 +3314,17 @@ internal void ApplyUpdatedState(NetworkTransformState newState) var currentEulerAngles = m_TargetRotation; // Adjust based on which axis changed // (both half precision and full precision apply Eulers to the RotAngle properties when reading the update) - if (m_LocalAuthoritativeNetworkState.HasRotAngleX) + if (flagStates.HasRotAngleX) { currentEulerAngles.x = m_LocalAuthoritativeNetworkState.RotAngleX; } - if (m_LocalAuthoritativeNetworkState.HasRotAngleY) + if (flagStates.HasRotAngleY) { currentEulerAngles.y = m_LocalAuthoritativeNetworkState.RotAngleY; } - if (m_LocalAuthoritativeNetworkState.HasRotAngleZ) + if (flagStates.HasRotAngleZ) { currentEulerAngles.z = m_LocalAuthoritativeNetworkState.RotAngleZ; } @@ -3355,7 +3363,7 @@ private void OnNetworkStateChanged(NetworkTransformState oldState, NetworkTransf // If we are using UseUnreliableDeltas and our old state tick is greater than the new state tick, // then just ignore the newstate. This avoids any scenario where the new state is out of order // from the old state (with unreliable traffic and/or mixed unreliable and reliable) - if (UseUnreliableDeltas && oldState.NetworkTick > newState.NetworkTick && !newState.IsTeleportingNextFrame && !newState.UnreliableFrameSync) + if (UseUnreliableDeltas && oldState.NetworkTick > newState.NetworkTick && !newState.FlagStates.IsTeleportingNextFrame && !newState.FlagStates.UnreliableFrameSync) { return; } @@ -3366,16 +3374,16 @@ private void OnNetworkStateChanged(NetworkTransformState oldState, NetworkTransf if (LogStateUpdate) { var builder = new StringBuilder(); - builder.AppendLine($"[Client-{m_CachedNetworkManager.LocalClientId}][State Update: {newState.GetNetworkTick()}][HasPos: {newState.HasPositionChange}][Has Rot: {newState.HasRotAngleChange}][Has Scale: {newState.HasScaleChange}]"); - if (newState.HasPositionChange) + builder.AppendLine($"[Client-{m_CachedNetworkManager.LocalClientId}][State Update: {newState.GetNetworkTick()}][HasPos: {newState.FlagStates.HasPositionChange}][Has Rot: {newState.FlagStates.HasRotAngleChange}][Has Scale: {newState.FlagStates.HasScaleChange}]"); + if (newState.FlagStates.HasPositionChange) { builder.AppendLine($"Position = {newState.GetPosition()}"); } - if (newState.HasRotAngleChange) + if (newState.FlagStates.HasRotAngleChange) { builder.AppendLine($"Rotation = {newState.GetRotation()}"); } - if (newState.HasScaleChange) + if (newState.FlagStates.HasScaleChange) { builder.AppendLine($"Scale = {newState.GetScale()}"); } @@ -3499,18 +3507,18 @@ private void AxisChangedDeltaPositionCheck() internal void OnUpdateAuthoritativeState(bool settingState = false) { // If our replicated state is not dirty and our local authority state is dirty, clear it. - if (!m_LocalAuthoritativeNetworkState.ExplicitSet && m_LocalAuthoritativeNetworkState.IsDirty && !m_LocalAuthoritativeNetworkState.IsTeleportingNextFrame) + if (!m_LocalAuthoritativeNetworkState.ExplicitSet && m_LocalAuthoritativeNetworkState.FlagStates.IsDirty && !m_LocalAuthoritativeNetworkState.IsTeleportingNextFrame) { // Now clear our bitset and prepare for next network tick state update - m_LocalAuthoritativeNetworkState.ClearBitSetForNextTick(); + m_LocalAuthoritativeNetworkState.FlagStates.ClearBitSetForNextTick(); if (TrackByStateId) { - m_LocalAuthoritativeNetworkState.TrackByStateId = true; + m_LocalAuthoritativeNetworkState.FlagStates.TrackByStateId = true; m_LocalAuthoritativeNetworkState.StateId++; } else { - m_LocalAuthoritativeNetworkState.TrackByStateId = false; + m_LocalAuthoritativeNetworkState.FlagStates.TrackByStateId = false; } } @@ -3660,6 +3668,13 @@ protected virtual void Awake() CachedTransform = transform; } + internal override void InternalOnNetworkPreSpawn(ref NetworkManager networkManager) + { + m_CachedNetworkManager = networkManager; + CachedTransform = transform; + base.InternalOnNetworkPreSpawn(ref networkManager); + } + /// public override void OnNetworkSpawn() { @@ -3816,7 +3831,7 @@ private void InternalInitialization(bool isOwnershipChange = false) #else var forUpdate = true; #endif - m_LocalAuthoritativeNetworkState.SynchronizeBaseHalfFloat = false; + m_LocalAuthoritativeNetworkState.FlagStates.SynchronizeBaseHalfFloat = false; if (CanCommitToTransform) { @@ -3825,7 +3840,7 @@ private void InternalInitialization(bool isOwnershipChange = false) if (UseHalfFloatPrecision) { m_HalfPositionState = new NetworkDeltaPosition(currentPosition, m_CachedNetworkManager.ServerTime.Tick, math.bool3(SyncPositionX, SyncPositionY, SyncPositionZ)); - m_LocalAuthoritativeNetworkState.SynchronizeBaseHalfFloat = isOwnershipChange; + m_LocalAuthoritativeNetworkState.FlagStates.SynchronizeBaseHalfFloat = isOwnershipChange; SetState(teleportDisabled: false); } @@ -4095,7 +4110,7 @@ public void SetState(Vector3? posIn = null, Quaternion? rotIn = null, Vector3? s #endif Vector3 pos = posIn == null ? position : posIn.Value; Quaternion rot = rotIn == null ? rotation : rotIn.Value; - Vector3 scale = scaleIn == null ? transform.localScale : scaleIn.Value; + Vector3 scale = scaleIn == null ? CachedTransform.localScale : scaleIn.Value; if (!CanCommitToTransform) { @@ -4148,7 +4163,7 @@ private void SetStateInternal(Vector3 pos, Quaternion rot, Vector3 scale, bool s // Explicit set states are cumulative during a fractional tick period of time (i.e. each SetState invocation will // update the axial deltas to whatever changes are applied). As such, we need to preserve the dirty and explicit // state flags. - var stateWasDirty = m_LocalAuthoritativeNetworkState.IsDirty; + var stateWasDirty = m_LocalAuthoritativeNetworkState.FlagStates.IsDirty; var explicitState = m_LocalAuthoritativeNetworkState.ExplicitSet; // Apply any delta states to the m_LocalAuthoritativeNetworkState @@ -4161,7 +4176,7 @@ private void SetStateInternal(Vector3 pos, Quaternion rot, Vector3 scale, bool s // If the current explicit set flag is set, then we are dirty. This assures if more than one explicit set state is invoked // in between a fractional tick period and the current explicit set state did not find any deltas that we preserve any // previous dirty state. - m_LocalAuthoritativeNetworkState.IsDirty = m_LocalAuthoritativeNetworkState.ExplicitSet; + m_LocalAuthoritativeNetworkState.FlagStates.IsDirty = m_LocalAuthoritativeNetworkState.ExplicitSet; } /// @@ -4703,8 +4718,8 @@ private void UpdateTransformState() // - If UsUnrealiable is not enabled // - If teleporting or synchronizing // - If sending an UnrealiableFrameSync or synchronizing the base position of the NetworkDeltaPosition - var networkDelivery = !UseUnreliableDeltas | m_LocalAuthoritativeNetworkState.IsTeleportingNextFrame | m_LocalAuthoritativeNetworkState.IsSynchronizing - | m_LocalAuthoritativeNetworkState.UnreliableFrameSync | m_LocalAuthoritativeNetworkState.SynchronizeBaseHalfFloat + var networkDelivery = !UseUnreliableDeltas | m_LocalAuthoritativeNetworkState.FlagStates.IsTeleportingNextFrame | m_LocalAuthoritativeNetworkState.FlagStates.IsSynchronizing + | m_LocalAuthoritativeNetworkState.FlagStates.UnreliableFrameSync | m_LocalAuthoritativeNetworkState.FlagStates.SynchronizeBaseHalfFloat ? MessageDeliveryType.DefaultDelivery : NetworkDelivery.UnreliableSequenced; // Server-host-dahost always sends updates to all clients (but itself) diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/NetworkTransformBase.cs b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/NetworkTransformBase.cs index ba03c78fb4..5ed20dd6cd 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/NetworkTransformBase.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/NetworkTransformBase.cs @@ -824,7 +824,7 @@ public void CommitToTransform() public (bool isDirty, bool isPositionDirty, bool isRotationDirty, bool isScaleDirty) ApplyState() { var transformState = ApplyLocalNetworkState(transform); - return (transformState.IsDirty, transformState.HasPositionChange, transformState.HasRotAngleChange, transformState.HasScaleChange); + return (transformState.FlagStates.IsDirty, transformState.FlagStates.HasPositionChange, transformState.FlagStates.HasRotAngleChange, transformState.FlagStates.HasScaleChange); } } @@ -986,7 +986,7 @@ private void LogState(ref NetworkTransformState state, bool isPush) tick = NetworkManager.ServerTime.Tick; } - m_ChildStateLog.AppendLine($"[{state.NetworkTick}][{tick}] Tele:{state.IsTeleportingNextFrame} Sync: {state.IsSynchronizing} Reliable: {state.IsReliableStateUpdate()} IsParented: {state.IsParented} HasPos: {state.HasPositionChange} Pos: {state.GetPosition()}"); + m_ChildStateLog.AppendLine($"[{state.NetworkTick}][{tick}] Tele:{state.FlagStates.IsTeleportingNextFrame} Sync: {state.FlagStates.IsSynchronizing} Reliable: {state.IsReliableStateUpdate()} IsParented: {state.FlagStates.IsParented} HasPos: {state.FlagStates.HasPositionChange} Pos: {state.GetPosition()}"); m_ChildStateLog.AppendLine($"Lossy:{state.LossyScale} Scale: {state.GetScale()} Rotation: {state.GetRotation()}"); } diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/NetworkTransformGeneral.cs b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/NetworkTransformGeneral.cs index 7306d15b28..941e781d50 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.FlagStates.UseInterpolation = false; localState.CurrentPosition = new Vector3(5.0f, 0.0f, 0.0f); - localState.SetHasPosition(NetworkTransform.Axis.X, true); + localState.FlagStates.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.SetHasPosition(NetworkTransform.Axis.X, false); - localState.SetHasPosition(NetworkTransform.Axis.Z, true); + localState.FlagStates.SetHasPosition(NetworkTransform.Axis.X, false); + localState.FlagStates.SetHasPosition(NetworkTransform.Axis.Z, true); localState.PositionZ = -5.0f; localState.NetworkTick++; m_NonAuthoritativeTransform.ApplyUpdatedState(localState); @@ -355,6 +355,7 @@ public void TestMultipleExplicitSetStates([Values] Interpolation interpolation) [Test] public void NonAuthorityOwnerSettingStateTest([Values] Interpolation interpolation, [Values] SmoothLerpSettings smoothLerp) { + m_EnableVerboseDebug = true; var interpolate = interpolation == Interpolation.EnableInterpolate; var usingSmoothLerp = (smoothLerp == SmoothLerpSettings.SmoothLerp) && interpolate; var waitForDelay = usingSmoothLerp ? 1000 : 500; diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/NetworkTransformStateTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/NetworkTransformStateTests.cs index e48f878978..7f63353e8f 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/NetworkTransformStateTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/NetworkTransformStateTests.cs @@ -4,6 +4,7 @@ using Unity.Netcode.Components; using Unity.Netcode.TestHelpers.Runtime; using UnityEngine; +using static Unity.Netcode.Components.NetworkTransform; using Object = UnityEngine.Object; @@ -39,7 +40,7 @@ public void NetworkTransformStateFlags() private void InlinedBitmathSerialization(ref int numFlags, ref uint[] indexValues, ref bool[] boolSet) { - NetworkTransform.NetworkTransformState transformState; + NetworkTransformState transformState; FastBufferWriter writer; FastBufferReader reader; // Test setting one at a time. @@ -53,9 +54,9 @@ private void InlinedBitmathSerialization(ref int numFlags, ref uint[] indexValue boolSet[j] = true; - transformState = new NetworkTransform.NetworkTransformState() + transformState = new NetworkTransformState() { - FlagStates = new NetworkTransform.PublicFlagStates() + FlagStates = new FlagStates() { InLocalSpace = boolSet[0], HasPositionX = boolSet[1], @@ -74,18 +75,18 @@ private void InlinedBitmathSerialization(ref int numFlags, ref uint[] indexValue UseHalfFloatPrecision = boolSet[14], IsSynchronizing = boolSet[15], UsePositionSlerp = boolSet[16], + IsParented = boolSet[17], + SynchronizeBaseHalfFloat = boolSet[18], + ReliableSequenced = boolSet[19], + UseUnreliableDeltas = boolSet[20], + UnreliableFrameSync = boolSet[21], + SwitchTransformSpaceWhenParented = boolSet[22], + TrackByStateId = boolSet[23], }, - IsParented = boolSet[17], - SynchronizeBaseHalfFloat = boolSet[18], - ReliableSequenced = boolSet[19], - UseUnreliableDeltas = boolSet[20], - UnreliableFrameSync = boolSet[21], - SwitchTransformSpaceWhenParented = boolSet[22], - TrackByStateId = boolSet[23], }; writer = new FastBufferWriter(64, Allocator.Temp); - transformState.SerializeBitset(ref writer); + BytePacker.WriteValueBitPacked(writer, transformState.FlagStates.GetFlags()); // Test the bitset representation of the serialization matches the pre-refactor serialization reader = new FastBufferReader(writer, Allocator.None); @@ -96,17 +97,19 @@ private void InlinedBitmathSerialization(ref int numFlags, ref uint[] indexValue // reset the reader to the beginning of the buffer reader.Seek(0); + ByteUnpacker.ReadValueBitPacked(reader, out uint bitFlags); // Test the deserialized values match the original values - var deserialized = new NetworkTransform.NetworkTransformState(); - deserialized.DeserializeBitset(ref reader); + var deserialized = new NetworkTransformState(); + // Set the flags + deserialized.FlagStates.SetFlags(bitFlags); AssertTransformStateEquals(boolSet, deserialized, "Flag serialization"); } // Test setting all flag values - transformState = new NetworkTransform.NetworkTransformState() + transformState = new NetworkTransformState() { - FlagStates = new NetworkTransform.PublicFlagStates() + FlagStates = new FlagStates() { InLocalSpace = true, HasPositionX = true, @@ -125,18 +128,19 @@ private void InlinedBitmathSerialization(ref int numFlags, ref uint[] indexValue UseHalfFloatPrecision = true, IsSynchronizing = true, UsePositionSlerp = true, + IsParented = true, + SynchronizeBaseHalfFloat = true, + ReliableSequenced = true, + UseUnreliableDeltas = true, + UnreliableFrameSync = true, + SwitchTransformSpaceWhenParented = true, + TrackByStateId = true, }, - IsParented = true, - SynchronizeBaseHalfFloat = true, - ReliableSequenced = true, - UseUnreliableDeltas = true, - UnreliableFrameSync = true, - SwitchTransformSpaceWhenParented = true, - TrackByStateId = true, }; writer = new FastBufferWriter(64, Allocator.Temp); - transformState.SerializeBitset(ref writer); + BytePacker.WriteValueBitPacked(writer, transformState.FlagStates.GetFlags()); + var serializedBuffer = writer.ToArray(); // Use a uint to set all bits to true in a legacy style bitset @@ -152,42 +156,43 @@ private void InlinedBitmathSerialization(ref int numFlags, ref uint[] indexValue // 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); + ByteUnpacker.ReadValueBitPacked(reader, out uint bitFlagsState); + // Test the deserialized values match the original values + var deserializedState = new NetworkTransformState(); + // Set the flags + deserializedState.FlagStates.SetFlags(bitFlagsState); Array.Fill(boolSet, true); AssertTransformStateEquals(boolSet, deserializedState, "Read bitset"); } - private void AssertTransformStateEquals(bool[] expected, NetworkTransform.NetworkTransformState actual, string testName) + private void AssertTransformStateEquals(bool[] expected, 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.SwitchTransformSpaceWhenParented, $"{testName} Flag {nameof(NetworkTransform.NetworkTransformState.SwitchTransformSpaceWhenParented)} is incorrect!"); - Assert.AreEqual(expected[23], actual.TrackByStateId, $"{testName} Flag {nameof(NetworkTransform.NetworkTransformState.TrackByStateId)} is incorrect!"); + Assert.AreEqual(expected[0], actual.FlagStates.InLocalSpace, $"{testName} Flag {nameof(FlagStates.InLocalSpace)} is incorrect!"); + Assert.AreEqual(expected[1], actual.FlagStates.HasPositionX, $"{testName} Flag {nameof(FlagStates.HasPositionX)} is incorrect!"); + Assert.AreEqual(expected[2], actual.FlagStates.HasPositionY, $"{testName} Flag {nameof(FlagStates.HasPositionY)} is incorrect!"); + Assert.AreEqual(expected[3], actual.FlagStates.HasPositionZ, $"{testName} Flag {nameof(FlagStates.HasPositionZ)} is incorrect!"); + Assert.AreEqual(expected[4], actual.FlagStates.HasRotAngleX, $"{testName} Flag {nameof(FlagStates.HasRotAngleX)} is incorrect!"); + Assert.AreEqual(expected[5], actual.FlagStates.HasRotAngleY, $"{testName} Flag {nameof(FlagStates.HasRotAngleY)} is incorrect!"); + Assert.AreEqual(expected[6], actual.FlagStates.HasRotAngleZ, $"{testName} Flag {nameof(FlagStates.HasRotAngleZ)} is incorrect!"); + Assert.AreEqual(expected[7], actual.FlagStates.HasScaleX, $"{testName} Flag {nameof(FlagStates.HasScaleX)} is incorrect!"); + Assert.AreEqual(expected[8], actual.FlagStates.HasScaleY, $"{testName} Flag {nameof(FlagStates.HasScaleY)} is incorrect!"); + Assert.AreEqual(expected[9], actual.FlagStates.HasScaleZ, $"{testName} Flag {nameof(FlagStates.HasScaleZ)} is incorrect!"); + Assert.AreEqual(expected[10], actual.FlagStates.IsTeleportingNextFrame, $"{testName} Flag {nameof(FlagStates.IsTeleportingNextFrame)} is incorrect!"); + Assert.AreEqual(expected[11], actual.FlagStates.UseInterpolation, $"{testName} Flag {nameof(FlagStates.UseInterpolation)} is incorrect!"); + Assert.AreEqual(expected[12], actual.FlagStates.QuaternionSync, $"{testName} Flag {nameof(FlagStates.QuaternionSync)} is incorrect!"); + Assert.AreEqual(expected[13], actual.FlagStates.QuaternionCompression, $"{testName} Flag {nameof(FlagStates.QuaternionCompression)} is incorrect!"); + Assert.AreEqual(expected[14], actual.FlagStates.UseHalfFloatPrecision, $"{testName} Flag {nameof(FlagStates.UseHalfFloatPrecision)} is incorrect!"); + Assert.AreEqual(expected[15], actual.FlagStates.IsSynchronizing, $"{testName} Flag {nameof(FlagStates.IsSynchronizing)} is incorrect!"); + Assert.AreEqual(expected[16], actual.FlagStates.UsePositionSlerp, $"{testName} Flag {nameof(FlagStates.UsePositionSlerp)} is incorrect!"); + Assert.AreEqual(expected[17], actual.FlagStates.IsParented, $"{testName} Flag {nameof(FlagStates.IsParented)} is incorrect!"); + Assert.AreEqual(expected[18], actual.FlagStates.SynchronizeBaseHalfFloat, $"{testName} Flag {nameof(FlagStates.SynchronizeBaseHalfFloat)} is incorrect!"); + Assert.AreEqual(expected[19], actual.FlagStates.ReliableSequenced, $"{testName} Flag {nameof(FlagStates.ReliableSequenced)} is incorrect!"); + Assert.AreEqual(expected[20], actual.FlagStates.UseUnreliableDeltas, $"{testName} Flag {nameof(FlagStates.UseUnreliableDeltas)} is incorrect!"); + Assert.AreEqual(expected[21], actual.FlagStates.UnreliableFrameSync, $"{testName} Flag {nameof(FlagStates.UnreliableFrameSync)} is incorrect!"); + Assert.AreEqual(expected[22], actual.FlagStates.SwitchTransformSpaceWhenParented, $"{testName} Flag {nameof(FlagStates.SwitchTransformSpaceWhenParented)} is incorrect!"); + Assert.AreEqual(expected[23], actual.FlagStates.TrackByStateId, $"{testName} Flag {nameof(FlagStates.TrackByStateId)} is incorrect!"); } } @@ -330,9 +335,9 @@ public void TestSyncAxes([Values] SynchronizationType synchronizationType, [Valu // We want a relatively clean networkTransform state before we try to apply the transform to it // We only preserve InLocalSpace and IsTeleportingNextFrame properties as they are the only things // needed when applying a transform to a NetworkTransformState - var networkTransformState = new NetworkTransform.NetworkTransformState + var networkTransformState = new NetworkTransformState { - FlagStates = new NetworkTransform.PublicFlagStates() + FlagStates = new FlagStates() { InLocalSpace = inLocalSpace, IsTeleportingNextFrame = isTeleporting, @@ -355,9 +360,9 @@ public void TestSyncAxes([Values] SynchronizationType synchronizationType, [Valu // We want to start with a fresh NetworkTransformState since it could have other state // information from the last time we applied the transform - networkTransformState = new NetworkTransform.NetworkTransformState + networkTransformState = new NetworkTransformState { - FlagStates = new NetworkTransform.PublicFlagStates() + FlagStates = new FlagStates() { InLocalSpace = inLocalSpace, IsTeleportingNextFrame = isTeleporting, @@ -372,9 +377,9 @@ public void TestSyncAxes([Values] SynchronizationType synchronizationType, [Valu // axis deltas that happened over a tick as a collection instead of collapsing them // as the changes are detected. { - networkTransformState = new NetworkTransform.NetworkTransformState + networkTransformState = new NetworkTransformState { - FlagStates = new NetworkTransform.PublicFlagStates() + FlagStates = new FlagStates() { InLocalSpace = inLocalSpace, IsTeleportingNextFrame = isTeleporting, @@ -509,9 +514,9 @@ public void TestSyncAxes([Values] SynchronizationType synchronizationType, [Valu { // Reset the NetworkTransformState since teleporting will preserve // any dirty values - networkTransformState = new NetworkTransform.NetworkTransformState + networkTransformState = new NetworkTransformState { - FlagStates = new NetworkTransform.PublicFlagStates() + FlagStates = new FlagStates() { InLocalSpace = inLocalSpace, IsTeleportingNextFrame = isTeleporting, @@ -546,9 +551,9 @@ public void TestSyncAxes([Values] SynchronizationType synchronizationType, [Valu // Reset the NetworkTransformState since teleporting will preserve // any dirty values - networkTransformState = new NetworkTransform.NetworkTransformState + networkTransformState = new NetworkTransformState { - FlagStates = new NetworkTransform.PublicFlagStates() + FlagStates = new FlagStates() { InLocalSpace = inLocalSpace, IsTeleportingNextFrame = isTeleporting, @@ -565,9 +570,9 @@ public void TestSyncAxes([Values] SynchronizationType synchronizationType, [Valu { // We want to start with a fresh NetworkTransformState since it could have other state // information from the last time we applied the transform - networkTransformState = new NetworkTransform.NetworkTransformState + networkTransformState = new NetworkTransformState { - FlagStates = new NetworkTransform.PublicFlagStates() + FlagStates = new FlagStates() { InLocalSpace = inLocalSpace, IsTeleportingNextFrame = isTeleporting, @@ -584,9 +589,9 @@ public void TestSyncAxes([Values] SynchronizationType synchronizationType, [Valu // Reset the NetworkTransformState since teleporting will preserve // any dirty values - networkTransformState = new NetworkTransform.NetworkTransformState + networkTransformState = new NetworkTransformState { - FlagStates = new NetworkTransform.PublicFlagStates() + FlagStates = new FlagStates() { InLocalSpace = inLocalSpace, IsTeleportingNextFrame = isTeleporting, @@ -603,9 +608,9 @@ public void TestSyncAxes([Values] SynchronizationType synchronizationType, [Valu { // We want to start with a fresh NetworkTransformState since it could have other state // information from the last time we applied the transform - networkTransformState = new NetworkTransform.NetworkTransformState + networkTransformState = new NetworkTransformState { - FlagStates = new NetworkTransform.PublicFlagStates() + FlagStates = new FlagStates() { InLocalSpace = inLocalSpace, IsTeleportingNextFrame = isTeleporting, @@ -622,9 +627,9 @@ public void TestSyncAxes([Values] SynchronizationType synchronizationType, [Valu // Reset the NetworkTransformState since teleporting will preserve // any dirty values - networkTransformState = new NetworkTransform.NetworkTransformState + networkTransformState = new NetworkTransformState { - FlagStates = new NetworkTransform.PublicFlagStates() + FlagStates = new FlagStates() { InLocalSpace = inLocalSpace, IsTeleportingNextFrame = isTeleporting, @@ -641,9 +646,9 @@ public void TestSyncAxes([Values] SynchronizationType synchronizationType, [Valu { // We want to start with a fresh NetworkTransformState since it could have other state // information from the last time we applied the transform - networkTransformState = new NetworkTransform.NetworkTransformState + networkTransformState = new NetworkTransformState { - FlagStates = new NetworkTransform.PublicFlagStates() + FlagStates = new FlagStates() { InLocalSpace = inLocalSpace, IsTeleportingNextFrame = isTeleporting, @@ -660,9 +665,9 @@ public void TestSyncAxes([Values] SynchronizationType synchronizationType, [Valu // Reset the NetworkTransformState since teleporting will preserve // any dirty values - networkTransformState = new NetworkTransform.NetworkTransformState + networkTransformState = new NetworkTransformState { - FlagStates = new NetworkTransform.PublicFlagStates() + FlagStates = new FlagStates() { InLocalSpace = inLocalSpace, IsTeleportingNextFrame = isTeleporting, @@ -679,9 +684,9 @@ public void TestSyncAxes([Values] SynchronizationType synchronizationType, [Valu { // We want to start with a fresh NetworkTransformState since it could have other state // information from the last time we applied the transform - networkTransformState = new NetworkTransform.NetworkTransformState + networkTransformState = new NetworkTransformState { - FlagStates = new NetworkTransform.PublicFlagStates() + FlagStates = new FlagStates() { InLocalSpace = inLocalSpace, IsTeleportingNextFrame = isTeleporting, @@ -698,9 +703,9 @@ public void TestSyncAxes([Values] SynchronizationType synchronizationType, [Valu // Reset the NetworkTransformState since teleporting will preserve // any dirty values - networkTransformState = new NetworkTransform.NetworkTransformState + networkTransformState = new NetworkTransformState { - FlagStates = new NetworkTransform.PublicFlagStates() + FlagStates = new FlagStates() { InLocalSpace = inLocalSpace, IsTeleportingNextFrame = isTeleporting, @@ -717,9 +722,9 @@ public void TestSyncAxes([Values] SynchronizationType synchronizationType, [Valu { // We want to start with a fresh NetworkTransformState since it could have other state // information from the last time we applied the transform - networkTransformState = new NetworkTransform.NetworkTransformState + networkTransformState = new NetworkTransformState { - FlagStates = new NetworkTransform.PublicFlagStates() + FlagStates = new FlagStates() { InLocalSpace = inLocalSpace, IsTeleportingNextFrame = isTeleporting, @@ -736,9 +741,9 @@ public void TestSyncAxes([Values] SynchronizationType synchronizationType, [Valu // Reset the NetworkTransformState since teleporting will preserve // any dirty values - networkTransformState = new NetworkTransform.NetworkTransformState + networkTransformState = new NetworkTransformState { - FlagStates = new NetworkTransform.PublicFlagStates() + FlagStates = new FlagStates() { InLocalSpace = inLocalSpace, IsTeleportingNextFrame = isTeleporting, @@ -755,9 +760,9 @@ public void TestSyncAxes([Values] SynchronizationType synchronizationType, [Valu { // We want to start with a fresh NetworkTransformState since it could have other state // information from the last time we applied the transform - networkTransformState = new NetworkTransform.NetworkTransformState + networkTransformState = new NetworkTransformState { - FlagStates = new NetworkTransform.PublicFlagStates() + FlagStates = new FlagStates() { InLocalSpace = inLocalSpace, IsTeleportingNextFrame = isTeleporting, @@ -774,9 +779,9 @@ public void TestSyncAxes([Values] SynchronizationType synchronizationType, [Valu // Reset the NetworkTransformState since teleporting will preserve // any dirty values - networkTransformState = new NetworkTransform.NetworkTransformState + networkTransformState = new NetworkTransformState { - FlagStates = new NetworkTransform.PublicFlagStates() + FlagStates = new FlagStates() { InLocalSpace = inLocalSpace, IsTeleportingNextFrame = isTeleporting, @@ -793,9 +798,9 @@ public void TestSyncAxes([Values] SynchronizationType synchronizationType, [Valu { // We want to start with a fresh NetworkTransformState since it could have other state // information from the last time we applied the transform - networkTransformState = new NetworkTransform.NetworkTransformState + networkTransformState = new NetworkTransformState { - FlagStates = new NetworkTransform.PublicFlagStates() + FlagStates = new FlagStates() { InLocalSpace = inLocalSpace, IsTeleportingNextFrame = isTeleporting, @@ -812,9 +817,9 @@ public void TestSyncAxes([Values] SynchronizationType synchronizationType, [Valu // Reset the NetworkTransformState since teleporting will preserve // any dirty values - networkTransformState = new NetworkTransform.NetworkTransformState + networkTransformState = new NetworkTransformState { - FlagStates = new NetworkTransform.PublicFlagStates() + FlagStates = new FlagStates() { InLocalSpace = inLocalSpace, IsTeleportingNextFrame = isTeleporting, @@ -831,9 +836,9 @@ public void TestSyncAxes([Values] SynchronizationType synchronizationType, [Valu { // We want to start with a fresh NetworkTransformState since it could have other state // information from the last time we applied the transform - networkTransformState = new NetworkTransform.NetworkTransformState + networkTransformState = new NetworkTransformState { - FlagStates = new NetworkTransform.PublicFlagStates() + FlagStates = new FlagStates() { InLocalSpace = inLocalSpace, IsTeleportingNextFrame = isTeleporting, @@ -857,9 +862,9 @@ public void TestSyncAxes([Values] SynchronizationType synchronizationType, [Valu [Test] public void TestThresholds( - [Values(NetworkTransform.PositionThresholdDefault, 1.0f)] float positionThreshold, - [Values(NetworkTransform.RotAngleThresholdDefault, 1.0f)] float rotAngleThreshold, - [Values(NetworkTransform.ScaleThresholdDefault, 0.5f)] float scaleThreshold) + [Values(PositionThresholdDefault, 1.0f)] float positionThreshold, + [Values(RotAngleThresholdDefault, 1.0f)] float rotAngleThreshold, + [Values(ScaleThresholdDefault, 0.5f)] float scaleThreshold) { var inLocalSpace = m_TransformSpace == TransformSpace.Local; var gameObject = new GameObject($"Test-{nameof(NetworkTransformStateTests)}.{nameof(TestThresholds)}"); @@ -887,7 +892,7 @@ public void TestThresholds( networkTransform.RotAngleThreshold = rotAngleThreshold; networkTransform.ScaleThreshold = scaleThreshold; - var networkTransformState = new NetworkTransform.NetworkTransformState + var networkTransformState = new NetworkTransformState { PositionX = initialPosition.x, PositionY = initialPosition.y, @@ -898,7 +903,7 @@ public void TestThresholds( ScaleX = initialScale.x, ScaleY = initialScale.y, ScaleZ = initialScale.z, - FlagStates = new NetworkTransform.PublicFlagStates() + FlagStates = new FlagStates() { InLocalSpace = inLocalSpace, }, From 09f6bb25c6d1b4ee3fcaafbe5823acf9d9332ed5 Mon Sep 17 00:00:00 2001 From: Noel Stephens Date: Fri, 21 Nov 2025 18:56:23 -0600 Subject: [PATCH 14/19] fix This resolves the issue with the EntityId no longer supporting integer conversion. --- .../Runtime/Components/RigidbodyContactEventManager.cs | 7 ++++++- .../Runtime/Transports/UTP/UnityTransport.cs | 7 ++++++- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/com.unity.netcode.gameobjects/Runtime/Components/RigidbodyContactEventManager.cs b/com.unity.netcode.gameobjects/Runtime/Components/RigidbodyContactEventManager.cs index 1421372839..432bdf3362 100644 --- a/com.unity.netcode.gameobjects/Runtime/Components/RigidbodyContactEventManager.cs +++ b/com.unity.netcode.gameobjects/Runtime/Components/RigidbodyContactEventManager.cs @@ -90,10 +90,15 @@ private struct JobResultStruct private NativeArray m_ResultsArray; private int m_Count = 0; private JobHandle m_JobHandle; - +#if UNITY_6000_2_OR_NEWER + private readonly Dictionary m_RigidbodyMapping = new Dictionary(); + private readonly Dictionary m_HandlerMapping = new Dictionary(); + private readonly Dictionary m_HandlerInfo = new Dictionary(); +#else private readonly Dictionary m_RigidbodyMapping = new Dictionary(); private readonly Dictionary m_HandlerMapping = new Dictionary(); private readonly Dictionary m_HandlerInfo = new Dictionary(); +#endif private void OnEnable() { diff --git a/com.unity.netcode.gameobjects/Runtime/Transports/UTP/UnityTransport.cs b/com.unity.netcode.gameobjects/Runtime/Transports/UTP/UnityTransport.cs index 1b14927f84..b7ffdcf65a 100644 --- a/com.unity.netcode.gameobjects/Runtime/Transports/UTP/UnityTransport.cs +++ b/com.unity.netcode.gameobjects/Runtime/Transports/UTP/UnityTransport.cs @@ -355,9 +355,13 @@ private struct PacketLossCache public int PacketsDropped; public float PacketLoss; }; - +#if UNITY_6000_2_OR_NEWER + internal static event Action TransportInitialized; + internal static event Action TransportDisposed; +#else internal static event Action TransportInitialized; internal static event Action TransportDisposed; +#endif /// /// Provides access to the for this instance. @@ -435,6 +439,7 @@ private void InitDriver() out m_UnreliableSequencedFragmentedPipeline, out m_ReliableSequencedPipeline); #if UNITY_6000_2_OR_NEWER + // TODO-FIXME: This is to work around the TransportInitialized?.Invoke(GetEntityId(), m_Driver); #else TransportInitialized?.Invoke(GetInstanceID(), m_Driver); From 45c8886cc58c124d55b2a18369d7b6f3396cc7fd Mon Sep 17 00:00:00 2001 From: Noel Stephens Date: Fri, 21 Nov 2025 19:00:48 -0600 Subject: [PATCH 15/19] style removing partial comment and whitespace. --- .../Runtime/Transports/UTP/UnityTransport.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/com.unity.netcode.gameobjects/Runtime/Transports/UTP/UnityTransport.cs b/com.unity.netcode.gameobjects/Runtime/Transports/UTP/UnityTransport.cs index b7ffdcf65a..d54689d13d 100644 --- a/com.unity.netcode.gameobjects/Runtime/Transports/UTP/UnityTransport.cs +++ b/com.unity.netcode.gameobjects/Runtime/Transports/UTP/UnityTransport.cs @@ -439,7 +439,6 @@ private void InitDriver() out m_UnreliableSequencedFragmentedPipeline, out m_ReliableSequencedPipeline); #if UNITY_6000_2_OR_NEWER - // TODO-FIXME: This is to work around the TransportInitialized?.Invoke(GetEntityId(), m_Driver); #else TransportInitialized?.Invoke(GetInstanceID(), m_Driver); From e9787b255fffd20eec6794fb2e4f06a2cc6e6094 Mon Sep 17 00:00:00 2001 From: Noel Stephens Date: Fri, 21 Nov 2025 19:19:05 -0600 Subject: [PATCH 16/19] fix Another spot where EntityId was needed. --- .../Runtime/Components/RigidbodyContactEventManager.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/com.unity.netcode.gameobjects/Runtime/Components/RigidbodyContactEventManager.cs b/com.unity.netcode.gameobjects/Runtime/Components/RigidbodyContactEventManager.cs index 432bdf3362..0d6e3d494d 100644 --- a/com.unity.netcode.gameobjects/Runtime/Components/RigidbodyContactEventManager.cs +++ b/com.unity.netcode.gameobjects/Runtime/Components/RigidbodyContactEventManager.cs @@ -80,8 +80,13 @@ public class RigidbodyContactEventManager : MonoBehaviour private struct JobResultStruct { public bool HasCollisionStay; +#if UNITY_6000_2_OR_NEWER + public EntityId ThisInstanceID; + public EntityId OtherInstanceID; +#else public int ThisInstanceID; public int OtherInstanceID; +#endif public Vector3 AverageNormal; public Vector3 AverageCollisionStayNormal; public Vector3 ContactPoint; From f7dee369baa96637aefd4489c32f0f6d3384e076 Mon Sep 17 00:00:00 2001 From: Noel Stephens Date: Fri, 21 Nov 2025 19:51:59 -0600 Subject: [PATCH 17/19] update Missed SwitchTransformSpaceWhenParented. --- .../Runtime/Components/NetworkTransform.cs | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/com.unity.netcode.gameobjects/Runtime/Components/NetworkTransform.cs b/com.unity.netcode.gameobjects/Runtime/Components/NetworkTransform.cs index ac47b42947..f1a5a53e92 100644 --- a/com.unity.netcode.gameobjects/Runtime/Components/NetworkTransform.cs +++ b/com.unity.netcode.gameobjects/Runtime/Components/NetworkTransform.cs @@ -104,6 +104,11 @@ internal struct FlagStates internal bool ReliableSequenced; internal bool UseUnreliableDeltas; internal bool UnreliableFrameSync; + /// + /// When set, non-authority instances will smoothly transition between + /// world and local space. + /// + /// internal bool SwitchTransformSpaceWhenParented; internal bool TrackByStateId; // Authoritative and non-authoritative sides use this to determine if a NetworkTransformState is @@ -342,13 +347,6 @@ public struct NetworkTransformState : INetworkSerializable internal FlagStates FlagStates; - /// - /// When set, non-authority instances will smoothly transition between - /// world and local space. - /// - /// - internal bool SwitchTransformSpaceWhenParented; - /// /// When set, the is operates in local space /// @@ -2206,7 +2204,7 @@ private bool CheckForStateChange(ref NetworkTransformState networkState, bool is var isRotationDirty = isTeleportingAndNotSynchronizing ? flagStates.HasRotAngleChange : false; var isScaleDirty = isTeleportingAndNotSynchronizing ? flagStates.HasScaleChange : false; - networkState.SwitchTransformSpaceWhenParented = SwitchTransformSpaceWhenParented; + flagStates.SwitchTransformSpaceWhenParented = SwitchTransformSpaceWhenParented; From ffd986f97806f76dc528e7c733e403e064cc5f43 Mon Sep 17 00:00:00 2001 From: Noel Stephens Date: Fri, 21 Nov 2025 19:53:40 -0600 Subject: [PATCH 18/19] refactor - EntityId Since MP Tools subscribes to the TransportInitialized and TransportDisposed methods and we have to vet against packages with dependencies, I am putting a temporary work-around to get past this issue so we can land the EntityId fix. Once MP Tools is updated for EntityId, we can re-enable the original fix. --- .../Runtime/Transports/UTP/UnityTransport.cs | 28 +++++++++++++++++-- 1 file changed, 26 insertions(+), 2 deletions(-) diff --git a/com.unity.netcode.gameobjects/Runtime/Transports/UTP/UnityTransport.cs b/com.unity.netcode.gameobjects/Runtime/Transports/UTP/UnityTransport.cs index d54689d13d..cf207cc725 100644 --- a/com.unity.netcode.gameobjects/Runtime/Transports/UTP/UnityTransport.cs +++ b/com.unity.netcode.gameobjects/Runtime/Transports/UTP/UnityTransport.cs @@ -355,6 +355,12 @@ private struct PacketLossCache public int PacketsDropped; public float PacketLoss; }; + + /// + /// TODO-FIXME: + /// Multiplayer Tools subscribes to this event and does not have the EntityId udpate. + /// +#if FIXED #if UNITY_6000_2_OR_NEWER internal static event Action TransportInitialized; internal static event Action TransportDisposed; @@ -362,6 +368,9 @@ private struct PacketLossCache internal static event Action TransportInitialized; internal static event Action TransportDisposed; #endif +#endif + internal static event Action TransportInitialized; + internal static event Action TransportDisposed; /// /// Provides access to the for this instance. @@ -439,7 +448,14 @@ private void InitDriver() out m_UnreliableSequencedFragmentedPipeline, out m_ReliableSequencedPipeline); #if UNITY_6000_2_OR_NEWER - TransportInitialized?.Invoke(GetEntityId(), m_Driver); + var entityId = GetEntityId(); +#if UNITY_6000_3_0A6_OR_HIGHER + // TODO-FIXME: Since multiplayer tools subscribes to this and we have to validate against any package that + // might use this action, we have to cast it down temporarily to avoid being blocked from getting these fixes in place. + TransportInitialized?.Invoke((int)entityId.GetRawData(), m_Driver); +#else + TransportInitialized?.Invoke(entityId, m_Driver); +#endif #else TransportInitialized?.Invoke(GetInstanceID(), m_Driver); #endif @@ -460,7 +476,15 @@ private void DisposeInternals() m_SendQueue.Clear(); #if UNITY_6000_2_OR_NEWER - TransportDisposed?.Invoke(GetEntityId()); + var entityId = GetEntityId(); +#if UNITY_6000_3_0A6_OR_HIGHER + // TODO-FIXME: Since multiplayer tools subscribes to this and we have to validate against any package that + // might use this action, we have to cast it down temporarily to avoid being blocked from getting these fixes in place. + TransportDisposed?.Invoke((int)entityId.GetRawData()); +#else + TransportDisposed?.Invoke(entityId, m_Driver); +#endif + #else TransportDisposed?.Invoke(GetInstanceID()); #endif From e9fd51d32c2f0d3edddca846df326325785b4890 Mon Sep 17 00:00:00 2001 From: Noel Stephens Date: Fri, 21 Nov 2025 20:45:56 -0600 Subject: [PATCH 19/19] update Found a few more places that could be micro-optimized. Fixing missed transformToUse that was if-def'd out. --- .../Runtime/Components/NetworkTransform.cs | 31 ++++++++++--------- 1 file changed, 17 insertions(+), 14 deletions(-) diff --git a/com.unity.netcode.gameobjects/Runtime/Components/NetworkTransform.cs b/com.unity.netcode.gameobjects/Runtime/Components/NetworkTransform.cs index f1a5a53e92..c38579c0a6 100644 --- a/com.unity.netcode.gameobjects/Runtime/Components/NetworkTransform.cs +++ b/com.unity.netcode.gameobjects/Runtime/Components/NetworkTransform.cs @@ -2171,6 +2171,8 @@ internal bool ApplyTransformToNetworkState(ref NetworkTransformState networkStat [MethodImpl(MethodImplOptions.AggressiveInlining)] private bool CheckForStateChange(ref NetworkTransformState networkState, bool isSynchronization = false, ulong targetClientId = 0, bool forceState = false) { + var flagStates = networkState.FlagStates; + // As long as we are not doing our first synchronization and we are sending unreliable deltas, each // NetworkTransform will stagger its full transfom synchronization over a 1 second period based on the // assigned tick slot (m_TickSync). @@ -2183,18 +2185,17 @@ private bool CheckForStateChange(ref NetworkTransformState networkState, bool is // We compare against the NetworkTickSystem version since ServerTime is set when updating ticks if (UseUnreliableDeltas && !isSynchronization && m_DeltaSynch && m_NextTickSync <= CurrentTick) { + // TODO-CACHE: m_CachedNetworkManager.NetworkConfig.TickRate value // Increment to the next frame synch tick position for this instance m_NextTickSync += (int)m_CachedNetworkManager.NetworkConfig.TickRate; // If we are teleporting, we do not need to send a frame synch for this tick slot // as a "frame synch" really is effectively just a teleport. - isAxisSync = !networkState.IsTeleportingNextFrame; + isAxisSync = !flagStates.IsTeleportingNextFrame; // Reset our delta synch trigger so we don't send another frame synch until we // send at least 1 unreliable state update after this fame synch or teleport m_DeltaSynch = false; } - var flagStates = networkState.FlagStates; - // This is used to determine if we need to send the state update reliably (if we are doing an axial sync) flagStates.UnreliableFrameSync = isAxisSync; @@ -2213,7 +2214,7 @@ private bool CheckForStateChange(ref NetworkTransformState networkState, bool is #if COM_UNITY_MODULES_PHYSICS || COM_UNITY_MODULES_PHYSICS2D if ((InLocalSpace != flagStates.InLocalSpace || isSynchronization) && !m_UseRigidbodyForMotion) #else - if (InLocalSpace != networkState.InLocalSpace) + if (InLocalSpace != flagStates.InLocalSpace) #endif { // When SwitchTransformSpaceWhenParented is set we automatically set our local space based on whether @@ -2221,7 +2222,7 @@ private bool CheckForStateChange(ref NetworkTransformState networkState, bool is flagStates.InLocalSpace = SwitchTransformSpaceWhenParented ? transform.parent != null : InLocalSpace; if (SwitchTransformSpaceWhenParented) { - InLocalSpace = networkState.InLocalSpace; + InLocalSpace = flagStates.InLocalSpace; } isDirty = true; @@ -2252,8 +2253,8 @@ private bool CheckForStateChange(ref NetworkTransformState networkState, bool is // rotationThreshold = m_NetworkRigidbodyInternal.GetAdjustedRotationThreshold(); //} #else - var position = InLocalSpace ? transformToUse.localPosition : transformToUse.position; - var rotation = InLocalSpace ? transformToUse.localRotation : transformToUse.rotation; + var position = InLocalSpace ? CachedTransform.localPosition : CachedTransform.position; + var rotation = InLocalSpace ? CachedTransform.localRotation : CachedTransform.rotation; var positionThreshold = Vector3.one * PositionThreshold; var rotationThreshold = Vector3.one * RotAngleThreshold; #endif @@ -2521,9 +2522,9 @@ private bool CheckForStateChange(ref NetworkTransformState networkState, bool is { // If we are synchronizing and the associated NetworkObject has a parent then we want to send the // LossyScale if the NetworkObject has a parent since NetworkObject spawn order is not guaranteed - if (networkState.FlagStates.IsParented) + if (flagStates.IsParented) { - networkState.LossyScale = transform.lossyScale; + networkState.LossyScale = CachedTransform.lossyScale; } } @@ -2570,15 +2571,17 @@ private bool CheckForStateChange(ref NetworkTransformState networkState, bool is else // Just apply the full local scale when synchronizing if (SynchronizeScale) { + var localScale = CachedTransform.localScale; if (!UseHalfFloatPrecision) { - networkState.ScaleX = transform.localScale.x; - networkState.ScaleY = transform.localScale.y; - networkState.ScaleZ = transform.localScale.z; + + networkState.ScaleX = localScale.x; + networkState.ScaleY = localScale.y; + networkState.ScaleZ = localScale.z; } else { - networkState.Scale = transform.localScale; + networkState.Scale = localScale; } flagStates.MarkChanged(AxialType.Scale, true); isScaleDirty = true; @@ -2599,7 +2602,7 @@ private bool CheckForStateChange(ref NetworkTransformState networkState, bool is // Mark the state dirty for the next network tick update to clear out the bitset values flagStates.IsDirty |= isDirty; - // Apply any changes to the flag states once + // Apply any flag state changes networkState.FlagStates = flagStates; return isDirty; }