diff --git a/com.unity.netcode.gameobjects/CHANGELOG.md b/com.unity.netcode.gameobjects/CHANGELOG.md index 3353565e01..7105c92dc8 100644 --- a/com.unity.netcode.gameobjects/CHANGELOG.md +++ b/com.unity.netcode.gameobjects/CHANGELOG.md @@ -17,6 +17,8 @@ Additional documentation and release notes are available at [Multiplayer Documen ### Changed +- Improve performance of `NetworkTransformState`. (#3770) + ### Deprecated 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); } } } diff --git a/com.unity.netcode.gameobjects/Runtime/Components/NetworkTransform.cs b/com.unity.netcode.gameobjects/Runtime/Components/NetworkTransform.cs index 6162ad91e2..c38579c0a6 100644 --- a/com.unity.netcode.gameobjects/Runtime/Components/NetworkTransform.cs +++ b/com.unity.netcode.gameobjects/Runtime/Components/NetworkTransform.cs @@ -26,11 +26,23 @@ 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 + /// 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. /// - public struct NetworkTransformState : INetworkSerializable + internal struct FlagStates { // Persists between state updates (authority dictates if this is set) private const int k_InLocalSpaceBit = 0x00000001; @@ -61,18 +73,229 @@ 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; - // Stores persistent and state relative flags - private uint m_Bitset; - internal uint BitSet + 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; + /// + /// 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 + // 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) { - get { return m_Bitset; } - set { m_Bitset = value; } + 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; @@ -104,10 +327,6 @@ internal uint BitSet // 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. /// @@ -126,30 +345,15 @@ internal uint BitSet private FastBufferReader m_Reader; private FastBufferWriter m_Writer; - /// - /// When set, non-authority instances will smoothly transition between - /// world and local space. - /// - /// - internal bool SwitchTransformSpaceWhenParented - { - get => GetFlag(k_SwitchTransformSpaceWhenParented); - set - { - SetFlag(value, k_SwitchTransformSpaceWhenParented); - } - } + internal FlagStates FlagStates; /// /// When set, the is operates in local space /// public bool InLocalSpace { - get => GetFlag(k_InLocalSpaceBit); - internal set - { - SetFlag(value, k_InLocalSpaceBit); - } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get { return FlagStates.InLocalSpace; } } // Position @@ -158,11 +362,8 @@ internal set /// public bool HasPositionX { - get => GetFlag(k_PositionXBit); - internal set - { - SetFlag(value, k_PositionXBit); - } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get { return FlagStates.HasPositionX; } } /// @@ -170,11 +371,8 @@ internal set /// public bool HasPositionY { - get => GetFlag(k_PositionYBit); - internal set - { - SetFlag(value, k_PositionYBit); - } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get { return FlagStates.HasPositionY; } } /// @@ -182,11 +380,8 @@ internal set /// public bool HasPositionZ { - get => GetFlag(k_PositionZBit); - internal set - { - SetFlag(value, k_PositionZBit); - } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get { return FlagStates.HasPositionZ; } } /// @@ -194,10 +389,8 @@ internal set /// public bool HasPositionChange { - get - { - return HasPositionX || HasPositionY || HasPositionZ; - } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get { return FlagStates.HasPositionChange; } } // RotAngles @@ -209,11 +402,8 @@ public bool HasPositionChange /// public bool HasRotAngleX { - get => GetFlag(k_RotAngleXBit); - internal set - { - SetFlag(value, k_RotAngleXBit); - } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get { return FlagStates.HasRotAngleX; } } /// @@ -224,11 +414,8 @@ internal set /// public bool HasRotAngleY { - get => GetFlag(k_RotAngleYBit); - internal set - { - SetFlag(value, k_RotAngleYBit); - } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get { return FlagStates.HasRotAngleY; } } /// @@ -239,11 +426,8 @@ internal set /// public bool HasRotAngleZ { - get => GetFlag(k_RotAngleZBit); - internal set - { - SetFlag(value, k_RotAngleZBit); - } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get { return FlagStates.HasRotAngleZ; } } /// @@ -254,22 +438,8 @@ internal set /// 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); + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get { return FlagStates.HasRotAngleChange; } } // Scale @@ -278,11 +448,8 @@ internal void SetHasScale(int axisIndex, bool isSet) /// public bool HasScaleX { - get => GetFlag(k_ScaleXBit); - internal set - { - SetFlag(value, k_ScaleXBit); - } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get { return FlagStates.HasScaleX; } } /// @@ -290,11 +457,8 @@ internal set /// public bool HasScaleY { - get => GetFlag(k_ScaleYBit); - internal set - { - SetFlag(value, k_ScaleYBit); - } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get { return FlagStates.HasScaleY; } } /// @@ -302,11 +466,8 @@ internal set /// public bool HasScaleZ { - get => GetFlag(k_ScaleZBit); - internal set - { - SetFlag(value, k_ScaleZBit); - } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get { return FlagStates.HasScaleZ; } } /// @@ -314,10 +475,8 @@ internal set /// public bool HasScaleChange { - get - { - return HasScaleX || HasScaleY || HasScaleZ; - } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get { return FlagStates.HasScaleChange; } } /// @@ -331,11 +490,8 @@ public bool HasScaleChange /// public bool IsTeleportingNextFrame { - get => GetFlag(k_TeleportingBit); - internal set - { - SetFlag(value, k_TeleportingBit); - } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get { return FlagStates.IsTeleportingNextFrame; } } /// @@ -344,7 +500,11 @@ internal set /// /// 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. @@ -355,11 +515,8 @@ internal set /// public bool UseInterpolation { - get => GetFlag(k_Interpolate); - internal set - { - SetFlag(value, k_Interpolate); - } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get { return FlagStates.UseInterpolation; } } /// @@ -372,11 +529,8 @@ internal set /// public bool QuaternionSync { - get => GetFlag(k_QuaternionSync); - internal set - { - SetFlag(value, k_QuaternionSync); - } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get { return FlagStates.QuaternionSync; } } /// @@ -390,11 +544,8 @@ internal set /// public bool QuaternionCompression { - get => GetFlag(k_QuaternionCompress); - internal set - { - SetFlag(value, k_QuaternionCompress); - } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get { return FlagStates.QuaternionCompression; } } /// @@ -406,11 +557,8 @@ internal set /// public bool UseHalfFloatPrecision { - get => GetFlag(k_UseHalfFloats); - internal set - { - SetFlag(value, k_UseHalfFloats); - } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get { return FlagStates.UseHalfFloatPrecision; } } /// @@ -419,11 +567,8 @@ internal set /// public bool IsSynchronizing { - get => GetFlag(k_Synchronization); - internal set - { - SetFlag(value, k_Synchronization); - } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get { return FlagStates.IsSynchronizing; } } /// @@ -432,11 +577,8 @@ internal set /// public bool UsePositionSlerp { - get => GetFlag(k_PositionSlerp); - internal set - { - SetFlag(value, k_PositionSlerp); - } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get { return FlagStates.UsePositionSlerp; } } /// @@ -447,94 +589,17 @@ internal set /// 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 - { - 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 UseUnreliableDeltas - { - get => GetFlag(k_UseUnreliableDeltas); - set - { - SetFlag(value, k_UseUnreliableDeltas); - } - } - - internal bool UnreliableFrameSync - { - get => GetFlag(k_UnreliableFrameSync); - set - { - SetFlag(value, k_UnreliableFrameSync); - } - } - - internal bool TrackByStateId - { - get => GetFlag(k_TrackStateId); - set - { - SetFlag(value, k_TrackStateId); - } - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private bool GetFlag(int flag) - { - return (m_Bitset & flag) != 0; - } - - [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 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; + return FlagStates.ReliableSequenced; } /// @@ -669,32 +734,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; } - BytePacker.WriteValueBitPacked(m_Writer, m_Bitset); + // 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 { - ByteUnpacker.ReadValueBitPacked(m_Reader, out m_Bitset); + // 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); @@ -710,7 +782,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); } @@ -720,7 +792,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; @@ -909,7 +981,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); } @@ -986,7 +1058,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 @@ -997,6 +1069,7 @@ public void NetworkSerialize(BufferSerializer serializer) where T : IReade #endif } } + } #endregion @@ -1600,7 +1673,7 @@ public Vector3 GetSpaceRelativePosition(bool getCurrentState = false) { if (!getCurrentState || CanCommitToTransform) { - return InLocalSpace ? transform.localPosition : transform.position; + return InLocalSpace ? CachedTransform.localPosition : CachedTransform.position; } else { @@ -1649,7 +1722,7 @@ public Quaternion GetSpaceRelativeRotation(bool getCurrentState = false) { if (!getCurrentState || CanCommitToTransform) { - return InLocalSpace ? transform.localRotation : transform.rotation; + return InLocalSpace ? CachedTransform.localRotation : CachedTransform.rotation; } else { @@ -1680,7 +1753,7 @@ public Vector3 GetScale(bool getCurrentState = false) { if (!getCurrentState || CanCommitToTransform) { - return transform.localScale; + return CachedTransform.localScale; } else { @@ -1833,11 +1906,10 @@ protected override void OnSynchronize(ref BufferSerializer serializer) if (serializer.IsWriter) { - SynchronizeState.IsTeleportingNextFrame = true; - var transformToCommit = transform; + SynchronizeState.FlagStates.IsTeleportingNextFrame = true; // 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); @@ -1866,9 +1938,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 @@ -1902,7 +1974,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 { @@ -1942,7 +2014,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) @@ -1959,7 +2031,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) @@ -1968,10 +2040,10 @@ private void TryCommitTransform(ref Transform transformToCommit, bool synchroniz // 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, ref transformToCommit, synchronize, forceState: true); + CheckForStateChange(ref m_LocalAuthoritativeNetworkState, synchronize, forceState: true); } } @@ -1982,11 +2054,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 @@ -2007,7 +2079,7 @@ private void TryCommitTransform(ref Transform transformToCommit, bool synchroniz // 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; } @@ -2066,10 +2138,10 @@ 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, ref transform); + CheckForStateChange(ref m_LocalAuthoritativeNetworkState); // Return the entire state to be used by the integration test return m_LocalAuthoritativeNetworkState; @@ -2080,24 +2152,27 @@ 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.UseInterpolation = Interpolate; - networkState.QuaternionSync = UseQuaternionSynchronization; - networkState.UseHalfFloatPrecision = UseHalfFloatPrecision; - networkState.QuaternionCompression = UseQuaternionCompression; - networkState.UseUnreliableDeltas = UseUnreliableDeltas; + networkState.FlagStates.UseInterpolation = Interpolate; + networkState.FlagStates.QuaternionSync = UseQuaternionSynchronization; + networkState.FlagStates.UseHalfFloatPrecision = UseHalfFloatPrecision; + networkState.FlagStates.QuaternionCompression = UseQuaternionCompression; + networkState.FlagStates.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) { + 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). @@ -2110,46 +2185,51 @@ private bool CheckForStateChange(ref NetworkTransformState networkState, ref Tra // 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; } + // 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 = 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; - networkState.SwitchTransformSpaceWhenParented = SwitchTransformSpaceWhenParented; + var isPositionDirty = isTeleportingAndNotSynchronizing ? flagStates.HasPositionChange : false; + var isRotationDirty = isTeleportingAndNotSynchronizing ? flagStates.HasRotAngleChange : false; + var isScaleDirty = isTeleportingAndNotSynchronizing ? flagStates.HasScaleChange : false; + + flagStates.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) + if (InLocalSpace != flagStates.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; + InLocalSpace = flagStates.InLocalSpace; } isDirty = true; // 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 @@ -2159,8 +2239,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; @@ -2173,17 +2253,17 @@ private bool CheckForStateChange(ref NetworkTransformState networkState, ref Tra // 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 var rotAngles = rotation.eulerAngles; - var scale = transformToUse.localScale; - networkState.IsSynchronizing = isSynchronization; + var scale = CachedTransform.localScale; + 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 @@ -2212,80 +2292,80 @@ private bool CheckForStateChange(ref NetworkTransformState networkState, ref Tra } } - networkState.IsParented = hasParentNetworkObject; + flagStates.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) + if (UseUnreliableDeltas != flagStates.UseUnreliableDeltas) { - networkState.UseUnreliableDeltas = UseUnreliableDeltas; + flagStates.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.HasPositionX = true; + flagStates.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.HasPositionY = true; + flagStates.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.HasPositionZ = true; + flagStates.SetHasPosition(Axis.Z, true); isPositionDirty = true; } } 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; @@ -2318,7 +2398,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; @@ -2332,13 +2412,13 @@ 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; + 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 @@ -2383,39 +2463,40 @@ 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; + 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.HasRotAngleX = true; + flagStates.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.HasRotAngleY = true; + flagStates.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.HasRotAngleZ = true; + flagStates.SetHasRotation(Axis.Z, true); isRotationDirty = true; } } 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) { @@ -2432,20 +2513,18 @@ private bool CheckForStateChange(ref NetworkTransformState networkState, ref Tra if (isRotationDirty) { networkState.Rotation = rotation; - networkState.HasRotAngleX = true; - networkState.HasRotAngleY = true; - networkState.HasRotAngleZ = true; + flagStates.MarkChanged(AxialType.Rotation, true); } } // 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 - if (networkState.IsParented) + if (flagStates.IsParented) { - networkState.LossyScale = transform.lossyScale; + networkState.LossyScale = CachedTransform.lossyScale; } } @@ -2454,24 +2533,24 @@ 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.HasScaleX = true; + flagStates.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.HasScaleY = true; + flagStates.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.HasScaleZ = true; + flagStates.SetHasScale(Axis.Z, true); isScaleDirty = true; } } @@ -2480,11 +2559,11 @@ 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]; - networkState.SetHasScale(i, i == 0 ? SyncScaleX : i == 1 ? SyncScaleY : SyncScaleZ); + flagStates.SetHasScale((Axis)i, i == 0 ? SyncScaleX : i == 1 ? SyncScaleY : SyncScaleZ); } } } @@ -2492,19 +2571,19 @@ private bool CheckForStateChange(ref NetworkTransformState networkState, ref Tra 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; } - networkState.HasScaleX = true; - networkState.HasScaleY = true; - networkState.HasScaleZ = true; + flagStates.MarkChanged(AxialType.Scale, true); isScaleDirty = true; } isDirty |= isPositionDirty || isRotationDirty || isScaleDirty; @@ -2521,7 +2600,10 @@ 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; + flagStates.IsDirty |= isDirty; + + // Apply any flag state changes + networkState.FlagStates = flagStates; return isDirty; } @@ -2560,9 +2642,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(); @@ -2623,6 +2704,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. @@ -2652,19 +2734,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(); } @@ -2726,16 +2808,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(i)) + if (m_LocalAuthoritativeNetworkState.FlagStates.HasScale((Axis)i)) { adjustedScale[i] = m_LocalAuthoritativeNetworkState.Scale[i]; } @@ -2744,26 +2826,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; } } @@ -2773,7 +2855,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) { @@ -2782,7 +2864,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; @@ -2803,11 +2885,11 @@ protected internal void ApplyAuthoritativeState() { if (PositionInLocalSpace) { - transform.localPosition = m_InternalCurrentPosition; + CachedTransform.localPosition = m_InternalCurrentPosition; } else { - transform.position = m_InternalCurrentPosition; + CachedTransform.position = m_InternalCurrentPosition; } } } @@ -2825,7 +2907,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; @@ -2845,11 +2927,11 @@ protected internal void ApplyAuthoritativeState() { if (RotationInLocalSpace) { - transform.localRotation = m_InternalCurrentRotation; + CachedTransform.localRotation = m_InternalCurrentRotation; } else { - transform.rotation = m_InternalCurrentRotation; + CachedTransform.rotation = m_InternalCurrentRotation; } } } @@ -2858,7 +2940,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) { @@ -2867,13 +2949,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(); } @@ -2895,31 +2977,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; } @@ -2959,13 +3042,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 @@ -2981,7 +3064,7 @@ private void ApplyTeleportingState(NetworkTransformState newState) } } - if (newState.HasScaleChange) + if (flagStates.HasScaleChange) { bool shouldUseLossy = false; @@ -2992,17 +3075,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; } @@ -3012,7 +3095,7 @@ private void ApplyTeleportingState(NetworkTransformState newState) m_TargetScale = currentScale; // Apply the adjusted scale - transform.localScale = currentScale; + CachedTransform.localScale = currentScale; if (Interpolate) { @@ -3020,26 +3103,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; } @@ -3051,17 +3134,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 @@ -3069,7 +3152,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); } } @@ -3092,30 +3175,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(); } @@ -3124,10 +3208,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; } @@ -3155,25 +3239,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; } @@ -3183,14 +3267,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(i)) + if (m_LocalAuthoritativeNetworkState.FlagStates.HasScale((Axis)i)) { currentScale[i] = m_LocalAuthoritativeNetworkState.Scale[i]; } @@ -3198,17 +3282,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; } @@ -3220,9 +3304,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; } @@ -3231,17 +3315,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; } @@ -3280,7 +3364,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; } @@ -3291,16 +3375,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()}"); } @@ -3412,7 +3496,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; } } } @@ -3421,27 +3505,27 @@ 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) + 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; } } AxisChangedDeltaPositionCheck(); - TryCommitTransform(ref transformSource, settingState: settingState); + TryCommitTransform(settingState: settingState); } #endregion @@ -3492,12 +3576,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; } /// @@ -3521,7 +3605,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) @@ -3557,6 +3641,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) @@ -3579,6 +3665,15 @@ protected virtual void Awake() { InLocalSpace = false; } + + CachedTransform = transform; + } + + internal override void InternalOnNetworkPreSpawn(ref NetworkManager networkManager) + { + m_CachedNetworkManager = networkManager; + CachedTransform = transform; + base.InternalOnNetworkPreSpawn(ref networkManager); } /// @@ -3737,7 +3832,7 @@ private void InternalInitialization(bool isOwnershipChange = false) #else var forUpdate = true; #endif - m_LocalAuthoritativeNetworkState.SynchronizeBaseHalfFloat = false; + m_LocalAuthoritativeNetworkState.FlagStates.SynchronizeBaseHalfFloat = false; if (CanCommitToTransform) { @@ -3746,7 +3841,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); } @@ -4016,7 +4111,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) { @@ -4064,18 +4159,16 @@ private void SetStateInternal(Vector3 pos, Quaternion rot, Vector3 scale, bool s } transform.localScale = scale; - m_LocalAuthoritativeNetworkState.IsTeleportingNextFrame = shouldTeleport; - - var transformToCommit = transform; + m_LocalAuthoritativeNetworkState.FlagStates.IsTeleportingNextFrame = shouldTeleport; // 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 - 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. @@ -4084,7 +4177,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; } /// @@ -4626,8 +4719,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/Runtime/Components/RigidbodyContactEventManager.cs b/com.unity.netcode.gameobjects/Runtime/Components/RigidbodyContactEventManager.cs index 1421372839..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; @@ -90,10 +95,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..cf207cc725 100644 --- a/com.unity.netcode.gameobjects/Runtime/Transports/UTP/UnityTransport.cs +++ b/com.unity.netcode.gameobjects/Runtime/Transports/UTP/UnityTransport.cs @@ -356,6 +356,19 @@ private struct PacketLossCache 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; +#else + internal static event Action TransportInitialized; + internal static event Action TransportDisposed; +#endif +#endif internal static event Action TransportInitialized; internal static event Action TransportDisposed; @@ -435,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 @@ -456,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 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 04afd12cb5..941e781d50 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/NetworkTransformGeneral.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/NetworkTransformGeneral.cs @@ -55,12 +55,12 @@ 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.HasPositionX = 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.HasPositionX = false; - localState.HasPositionZ = 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 67e66b61b3..7f63353e8f 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/NetworkTransformStateTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/NetworkTransformStateTests.cs @@ -1,12 +1,204 @@ +using System; using NUnit.Framework; +using Unity.Collections; using Unity.Netcode.Components; using Unity.Netcode.TestHelpers.Runtime; using UnityEngine; +using static Unity.Netcode.Components.NetworkTransform; +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 = 24; + + 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) + { + 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 NetworkTransformState() + { + FlagStates = new FlagStates() + { + 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], + SwitchTransformSpaceWhenParented = boolSet[22], + TrackByStateId = boolSet[23], + }, + }; + + writer = new FastBufferWriter(64, Allocator.Temp); + BytePacker.WriteValueBitPacked(writer, transformState.FlagStates.GetFlags()); + + // Test the bitset representation of the serialization matches the pre-refactor serialization + reader = new FastBufferReader(writer, Allocator.None); + 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!"); + + // 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 NetworkTransformState(); + // Set the flags + deserialized.FlagStates.SetFlags(bitFlags); + + AssertTransformStateEquals(boolSet, deserialized, "Flag serialization"); + } + // Test setting all flag values + transformState = new NetworkTransformState() + { + FlagStates = new FlagStates() + { + 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, + SwitchTransformSpaceWhenParented = true, + TrackByStateId = true, + }, + }; + + writer = new FastBufferWriter(64, Allocator.Temp); + BytePacker.WriteValueBitPacked(writer, transformState.FlagStates.GetFlags()); + + 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); + 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!"); + + reader = new FastBufferReader(legacyBitsetWriter, Allocator.None); + 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, NetworkTransformState actual, string testName) + { + 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!"); + } + + } + + // 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)] @@ -15,7 +207,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 { @@ -77,14 +269,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; @@ -98,125 +283,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) @@ -269,10 +335,13 @@ 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 { - InLocalSpace = inLocalSpace, - IsTeleportingNextFrame = isTeleporting, + FlagStates = new FlagStates() + { + InLocalSpace = inLocalSpace, + IsTeleportingNextFrame = isTeleporting, + }, NetworkDeltaPosition = new NetworkDeltaPosition(Vector3.zero, 0) }; @@ -291,10 +360,13 @@ 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 { - InLocalSpace = inLocalSpace, - IsTeleportingNextFrame = isTeleporting, + FlagStates = new FlagStates() + { + InLocalSpace = inLocalSpace, + IsTeleportingNextFrame = isTeleporting, + } }; var position = networkTransform.transform.position; var rotAngles = networkTransform.transform.eulerAngles; @@ -305,10 +377,13 @@ 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 { - InLocalSpace = inLocalSpace, - IsTeleportingNextFrame = isTeleporting, + FlagStates = new FlagStates() + { + InLocalSpace = inLocalSpace, + IsTeleportingNextFrame = isTeleporting, + } }; // SyncPositionX @@ -439,10 +514,13 @@ public void TestSyncAxes([Values] SynchronizationType synchronizationType, [Valu { // Reset the NetworkTransformState since teleporting will preserve // any dirty values - networkTransformState = new NetworkTransform.NetworkTransformState + networkTransformState = new NetworkTransformState { - InLocalSpace = inLocalSpace, - IsTeleportingNextFrame = isTeleporting, + FlagStates = new FlagStates() + { + InLocalSpace = inLocalSpace, + IsTeleportingNextFrame = isTeleporting, + } }; position = networkTransform.transform.position; @@ -473,10 +551,13 @@ public void TestSyncAxes([Values] SynchronizationType synchronizationType, [Valu // Reset the NetworkTransformState since teleporting will preserve // any dirty values - networkTransformState = new NetworkTransform.NetworkTransformState + networkTransformState = new NetworkTransformState { - InLocalSpace = inLocalSpace, - IsTeleportingNextFrame = isTeleporting, + FlagStates = new FlagStates() + { + InLocalSpace = inLocalSpace, + IsTeleportingNextFrame = isTeleporting, + } }; // SyncPositionY if (syncPosY) @@ -489,10 +570,13 @@ 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 { - InLocalSpace = inLocalSpace, - IsTeleportingNextFrame = isTeleporting, + FlagStates = new FlagStates() + { + InLocalSpace = inLocalSpace, + IsTeleportingNextFrame = isTeleporting, + }, }; Assert.IsTrue(networkTransform.ApplyTransformToNetworkState(ref networkTransformState, 0, networkTransform.transform)); Assert.IsFalse(networkTransformState.HasPositionY); @@ -505,10 +589,13 @@ public void TestSyncAxes([Values] SynchronizationType synchronizationType, [Valu // Reset the NetworkTransformState since teleporting will preserve // any dirty values - networkTransformState = new NetworkTransform.NetworkTransformState + networkTransformState = new NetworkTransformState { - InLocalSpace = inLocalSpace, - IsTeleportingNextFrame = isTeleporting, + FlagStates = new FlagStates() + { + InLocalSpace = inLocalSpace, + IsTeleportingNextFrame = isTeleporting, + }, }; // SyncPositionZ if (syncPosZ) @@ -521,10 +608,13 @@ 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 { - InLocalSpace = inLocalSpace, - IsTeleportingNextFrame = isTeleporting, + FlagStates = new FlagStates() + { + InLocalSpace = inLocalSpace, + IsTeleportingNextFrame = isTeleporting, + }, }; Assert.IsTrue(networkTransform.ApplyTransformToNetworkState(ref networkTransformState, 0, networkTransform.transform)); Assert.IsFalse(networkTransformState.HasPositionZ); @@ -537,10 +627,13 @@ public void TestSyncAxes([Values] SynchronizationType synchronizationType, [Valu // Reset the NetworkTransformState since teleporting will preserve // any dirty values - networkTransformState = new NetworkTransform.NetworkTransformState + networkTransformState = new NetworkTransformState { - InLocalSpace = inLocalSpace, - IsTeleportingNextFrame = isTeleporting, + FlagStates = new FlagStates() + { + 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) @@ -553,10 +646,13 @@ 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 { - InLocalSpace = inLocalSpace, - IsTeleportingNextFrame = isTeleporting, + FlagStates = new FlagStates() + { + InLocalSpace = inLocalSpace, + IsTeleportingNextFrame = isTeleporting, + }, }; Assert.IsTrue(networkTransform.ApplyTransformToNetworkState(ref networkTransformState, 0, networkTransform.transform)); Assert.IsFalse(networkTransformState.HasRotAngleX); @@ -569,10 +665,13 @@ public void TestSyncAxes([Values] SynchronizationType synchronizationType, [Valu // Reset the NetworkTransformState since teleporting will preserve // any dirty values - networkTransformState = new NetworkTransform.NetworkTransformState + networkTransformState = new NetworkTransformState { - InLocalSpace = inLocalSpace, - IsTeleportingNextFrame = isTeleporting, + FlagStates = new FlagStates() + { + 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) @@ -585,10 +684,13 @@ 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 { - InLocalSpace = inLocalSpace, - IsTeleportingNextFrame = isTeleporting, + FlagStates = new FlagStates() + { + InLocalSpace = inLocalSpace, + IsTeleportingNextFrame = isTeleporting, + }, }; Assert.IsTrue(networkTransform.ApplyTransformToNetworkState(ref networkTransformState, 0, networkTransform.transform)); Assert.IsFalse(networkTransformState.HasRotAngleY); @@ -601,10 +703,13 @@ public void TestSyncAxes([Values] SynchronizationType synchronizationType, [Valu // Reset the NetworkTransformState since teleporting will preserve // any dirty values - networkTransformState = new NetworkTransform.NetworkTransformState + networkTransformState = new NetworkTransformState { - InLocalSpace = inLocalSpace, - IsTeleportingNextFrame = isTeleporting, + FlagStates = new FlagStates() + { + 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) @@ -617,10 +722,13 @@ 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 { - InLocalSpace = inLocalSpace, - IsTeleportingNextFrame = isTeleporting, + FlagStates = new FlagStates() + { + InLocalSpace = inLocalSpace, + IsTeleportingNextFrame = isTeleporting, + }, }; Assert.IsTrue(networkTransform.ApplyTransformToNetworkState(ref networkTransformState, 0, networkTransform.transform)); Assert.IsFalse(networkTransformState.HasRotAngleZ); @@ -633,10 +741,13 @@ public void TestSyncAxes([Values] SynchronizationType synchronizationType, [Valu // Reset the NetworkTransformState since teleporting will preserve // any dirty values - networkTransformState = new NetworkTransform.NetworkTransformState + networkTransformState = new NetworkTransformState { - InLocalSpace = inLocalSpace, - IsTeleportingNextFrame = isTeleporting, + FlagStates = new FlagStates() + { + InLocalSpace = inLocalSpace, + IsTeleportingNextFrame = isTeleporting, + }, }; // SyncScaleX if (syncScaX) @@ -649,10 +760,13 @@ 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 { - InLocalSpace = inLocalSpace, - IsTeleportingNextFrame = isTeleporting, + FlagStates = new FlagStates() + { + InLocalSpace = inLocalSpace, + IsTeleportingNextFrame = isTeleporting, + }, }; Assert.IsTrue(networkTransform.ApplyTransformToNetworkState(ref networkTransformState, 0, networkTransform.transform)); Assert.IsFalse(networkTransformState.HasScaleX); @@ -665,10 +779,13 @@ public void TestSyncAxes([Values] SynchronizationType synchronizationType, [Valu // Reset the NetworkTransformState since teleporting will preserve // any dirty values - networkTransformState = new NetworkTransform.NetworkTransformState + networkTransformState = new NetworkTransformState { - InLocalSpace = inLocalSpace, - IsTeleportingNextFrame = isTeleporting, + FlagStates = new FlagStates() + { + InLocalSpace = inLocalSpace, + IsTeleportingNextFrame = isTeleporting, + }, }; // SyncScaleY if (syncScaY) @@ -681,10 +798,13 @@ 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 { - InLocalSpace = inLocalSpace, - IsTeleportingNextFrame = isTeleporting, + FlagStates = new FlagStates() + { + InLocalSpace = inLocalSpace, + IsTeleportingNextFrame = isTeleporting, + }, }; Assert.IsTrue(networkTransform.ApplyTransformToNetworkState(ref networkTransformState, 0, networkTransform.transform)); Assert.IsFalse(networkTransformState.HasScaleY); @@ -697,10 +817,13 @@ public void TestSyncAxes([Values] SynchronizationType synchronizationType, [Valu // Reset the NetworkTransformState since teleporting will preserve // any dirty values - networkTransformState = new NetworkTransform.NetworkTransformState + networkTransformState = new NetworkTransformState { - InLocalSpace = inLocalSpace, - IsTeleportingNextFrame = isTeleporting, + FlagStates = new FlagStates() + { + InLocalSpace = inLocalSpace, + IsTeleportingNextFrame = isTeleporting, + }, }; // SyncScaleZ if (syncScaZ) @@ -713,10 +836,13 @@ 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 { - InLocalSpace = inLocalSpace, - IsTeleportingNextFrame = isTeleporting, + FlagStates = new FlagStates() + { + InLocalSpace = inLocalSpace, + IsTeleportingNextFrame = isTeleporting, + } }; Assert.IsTrue(networkTransform.ApplyTransformToNetworkState(ref networkTransformState, 0, networkTransform.transform)); Assert.IsFalse(networkTransformState.HasScaleZ); @@ -736,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)}"); @@ -766,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, @@ -777,7 +903,10 @@ public void TestThresholds( ScaleX = initialScale.x, ScaleY = initialScale.y, ScaleZ = initialScale.z, - InLocalSpace = inLocalSpace + FlagStates = new FlagStates() + { + InLocalSpace = inLocalSpace, + }, }; // Step 1: change properties, expect state to be dirty 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..8f48a20666 --- /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)] + internal 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); } }