diff --git a/com.unity.netcode.gameobjects/CHANGELOG.md b/com.unity.netcode.gameobjects/CHANGELOG.md
index 2eb3776c3b..5088b959f3 100644
--- a/com.unity.netcode.gameobjects/CHANGELOG.md
+++ b/com.unity.netcode.gameobjects/CHANGELOG.md
@@ -15,6 +15,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/NetworkTransform.cs b/com.unity.netcode.gameobjects/Runtime/Components/NetworkTransform.cs
index 6162ad91e2..311522aad7 100644
--- a/com.unity.netcode.gameobjects/Runtime/Components/NetworkTransform.cs
+++ b/com.unity.netcode.gameobjects/Runtime/Components/NetworkTransform.cs
@@ -26,6 +26,14 @@ public class NetworkTransform : NetworkBehaviour
internal bool NetworkTransformExpanded;
#endif
+ internal enum Axis { X, Y, Z }
+
+ internal enum AxialType
+ {
+ Position,
+ Rotation,
+ Scale
+ }
#region NETWORK TRANSFORM STATE
///
/// Data structure used to synchronize the
@@ -61,18 +69,10 @@ 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
- {
- get { return m_Bitset; }
- set { m_Bitset = value; }
- }
-
// Used to store the tick calculated sent time
internal double SentTime;
@@ -131,74 +131,33 @@ internal uint BitSet
/// world and local space.
///
///
- internal bool SwitchTransformSpaceWhenParented
- {
- get => GetFlag(k_SwitchTransformSpaceWhenParented);
- set
- {
- SetFlag(value, k_SwitchTransformSpaceWhenParented);
- }
- }
+ internal bool SwitchTransformSpaceWhenParented;
///
/// When set, the is operates in local space
///
- public bool InLocalSpace
- {
- get => GetFlag(k_InLocalSpaceBit);
- internal set
- {
- SetFlag(value, k_InLocalSpaceBit);
- }
- }
+ public bool InLocalSpace { get; internal set; }
// Position
///
/// When set, the X-Axis position value has changed
///
- public bool HasPositionX
- {
- get => GetFlag(k_PositionXBit);
- internal set
- {
- SetFlag(value, k_PositionXBit);
- }
- }
+ public bool HasPositionX { get; internal set; }
///
/// When set, the Y-Axis position value has changed
///
- public bool HasPositionY
- {
- get => GetFlag(k_PositionYBit);
- internal set
- {
- SetFlag(value, k_PositionYBit);
- }
- }
+ public bool HasPositionY { get; internal set; }
///
/// When set, the Z-Axis position value has changed
///
- public bool HasPositionZ
- {
- get => GetFlag(k_PositionZBit);
- internal set
- {
- SetFlag(value, k_PositionZBit);
- }
- }
+ public bool HasPositionZ { get; internal set; }
///
/// When set, at least one of the position axis values has changed.
///
- public bool HasPositionChange
- {
- get
- {
- return HasPositionX || HasPositionY || HasPositionZ;
- }
- }
+ public bool HasPositionChange { get; internal set; }
// RotAngles
///
@@ -207,14 +166,7 @@ public bool HasPositionChange
///
/// When quaternion synchronization is enabled all axis are always updated.
///
- public bool HasRotAngleX
- {
- get => GetFlag(k_RotAngleXBit);
- internal set
- {
- SetFlag(value, k_RotAngleXBit);
- }
- }
+ public bool HasRotAngleX { get; internal set; }
///
/// When set, the Euler rotation Y-Axis value has changed.
@@ -222,14 +174,7 @@ internal set
///
/// When quaternion synchronization is enabled all axis are always updated.
///
- public bool HasRotAngleY
- {
- get => GetFlag(k_RotAngleYBit);
- internal set
- {
- SetFlag(value, k_RotAngleYBit);
- }
- }
+ public bool HasRotAngleY { get; internal set; }
///
/// When set, the Euler rotation Z-Axis value has changed.
@@ -237,14 +182,7 @@ internal set
///
/// When quaternion synchronization is enabled all axis are always updated.
///
- public bool HasRotAngleZ
- {
- get => GetFlag(k_RotAngleZBit);
- internal set
- {
- SetFlag(value, k_RotAngleZBit);
- }
- }
+ public bool HasRotAngleZ { get; internal set; }
///
/// When set, at least one of the rotation axis values has changed.
@@ -252,72 +190,105 @@ internal set
///
/// When quaternion synchronization is enabled all axis are always updated.
///
- public bool HasRotAngleChange
- {
- get
- {
- return HasRotAngleX || HasRotAngleY || HasRotAngleZ;
- }
- }
-
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- internal bool HasScale(int axisIndex)
- {
- return GetFlag(k_ScaleXBit << axisIndex);
- }
-
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- internal void SetHasScale(int axisIndex, bool isSet)
- {
- SetFlag(isSet, k_ScaleXBit << axisIndex);
- }
+ public bool HasRotAngleChange { get; internal set; }
// Scale
///
/// When set, the X-Axis scale value has changed.
///
- public bool HasScaleX
- {
- get => GetFlag(k_ScaleXBit);
- internal set
- {
- SetFlag(value, k_ScaleXBit);
- }
- }
+ public bool HasScaleX { get; internal set; }
///
/// When set, the Y-Axis scale value has changed.
///
- public bool HasScaleY
+ public bool HasScaleY { get; internal set; }
+
+ ///
+ /// When set, the Z-Axis scale value has changed.
+ ///
+ public bool HasScaleZ { get; internal set; }
+
+ ///
+ /// When set, at least one of the scale axis values has changed.
+ ///
+ public bool HasScaleChange { get; internal set; }
+
+ internal void MarkChanged(AxialType axialType, bool changed)
{
- get => GetFlag(k_ScaleYBit);
- internal set
+ switch (axialType)
{
- SetFlag(value, k_ScaleYBit);
+ case AxialType.Position:
+ HasPositionX = changed;
+ HasPositionY = changed;
+ HasPositionZ = changed;
+ break;
+ case AxialType.Rotation:
+ HasRotAngleX = changed;
+ HasRotAngleY = changed;
+ HasRotAngleZ = changed;
+ break;
+ case AxialType.Scale:
+ HasScaleX = changed;
+ HasScaleY = changed;
+ HasScaleZ = changed;
+ break;
}
}
+ internal void SetHasPosition(Axis axis, bool changed)
+ {
+ switch (axis)
+ {
+ case Axis.X:
+ HasPositionX = changed;
+ break;
+ case Axis.Y:
+ HasPositionY = changed;
+ break;
+ case Axis.Z:
+ HasPositionZ = changed;
+ break;
+ }
+ HasPositionChange = HasPositionX || HasPositionY || HasPositionZ;
+ }
- ///
- /// When set, the Z-Axis scale value has changed.
- ///
- public bool HasScaleZ
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ internal void SetHasRotation(Axis axis, bool changed)
{
- get => GetFlag(k_ScaleZBit);
- internal set
+ switch (axis)
{
- SetFlag(value, k_ScaleZBit);
+ case Axis.X:
+ HasRotAngleX = changed;
+ break;
+ case Axis.Y:
+ HasRotAngleY = changed;
+ break;
+ case Axis.Z:
+ HasRotAngleZ = changed;
+ break;
}
+ HasRotAngleChange = HasRotAngleX || HasRotAngleY || HasRotAngleZ;
}
- ///
- /// When set, at least one of the scale axis values has changed.
- ///
- public bool HasScaleChange
+ internal void SetHasScale(Axis axis, bool changed)
{
- get
+ switch (axis)
{
- return HasScaleX || HasScaleY || HasScaleZ;
+ case Axis.X:
+ HasScaleX = changed;
+ break;
+ case Axis.Y:
+ HasScaleY = changed;
+ break;
+ case Axis.Z:
+ HasScaleZ = changed;
+ break;
}
+ HasScaleChange = HasScaleX || HasScaleY || HasScaleZ;
+ }
+
+ internal bool HasScale(Axis axis)
+ {
+ return axis == Axis.X ? HasScaleX : axis == Axis.Y ? HasScaleY : HasScaleZ;
}
///
@@ -329,14 +300,7 @@ public bool HasScaleChange
/// - If using half precision, full precision values are used.
/// - All axis marked to be synchronized will be updated.
///
- public bool IsTeleportingNextFrame
- {
- get => GetFlag(k_TeleportingBit);
- internal set
- {
- SetFlag(value, k_TeleportingBit);
- }
- }
+ public bool IsTeleportingNextFrame { get; internal set; }
///
/// When overriding , if the state that was pushed was a teleport then this will be set to true.
@@ -353,14 +317,7 @@ internal set
/// Authority does not apply interpolation via .
/// Authority should handle its own motion/rotation/scale smoothing locally.
///
- public bool UseInterpolation
- {
- get => GetFlag(k_Interpolate);
- internal set
- {
- SetFlag(value, k_Interpolate);
- }
- }
+ public bool UseInterpolation { get; internal set; }
///
/// When enabled, this instance uses synchronization.
@@ -370,14 +327,7 @@ internal set
/// When quaternion synchronization is enabled, the entire quaternion is updated when there are any changes to any axial values.
/// You can use half float precision or quaternion compression to reduce the bandwidth cost.
///
- public bool QuaternionSync
- {
- get => GetFlag(k_QuaternionSync);
- internal set
- {
- SetFlag(value, k_QuaternionSync);
- }
- }
+ public bool QuaternionSync { get; internal set; }
///
/// When set s will be compressed down to 4 bytes using a smallest three implementation.
@@ -388,14 +338,7 @@ internal set
/// - Quaternion Compression: 4 bytes per delta update
/// - Half float precision: 8 bytes per delta update
///
- public bool QuaternionCompression
- {
- get => GetFlag(k_QuaternionCompress);
- internal set
- {
- SetFlag(value, k_QuaternionCompress);
- }
- }
+ public bool QuaternionCompression { get; internal set; }
///
/// When set, the will use half float precision for position, rotation, and scale.
@@ -404,40 +347,19 @@ internal set
/// Postion is synchronized through delta position updates in order to reduce precision loss/drift and to extend to positions beyond the limitation of half float maximum values.
/// Rotation and scale both use half float precision ( and )
///
- public bool UseHalfFloatPrecision
- {
- get => GetFlag(k_UseHalfFloats);
- internal set
- {
- SetFlag(value, k_UseHalfFloats);
- }
- }
+ public bool UseHalfFloatPrecision { get; internal set; }
///
/// When set, this indicates it is the first state being synchronized.
/// Typically when the associate is spawned or a client is being synchronized after connecting to a network session in progress.
///
- public bool IsSynchronizing
- {
- get => GetFlag(k_Synchronization);
- internal set
- {
- SetFlag(value, k_Synchronization);
- }
- }
+ public bool IsSynchronizing { get; internal set; }
///
/// Determines if position interpolation will Slerp towards its target position.
/// This is only really useful if you are moving around a point in a circular pattern.
///
- public bool UsePositionSlerp
- {
- get => GetFlag(k_PositionSlerp);
- internal set
- {
- SetFlag(value, k_PositionSlerp);
- }
- }
+ public bool UsePositionSlerp { get; internal set; }
///
/// Returns whether this state update was a frame synchronization when
@@ -463,77 +385,35 @@ public bool IsReliableStateUpdate()
return ReliableSequenced;
}
- internal bool IsParented
- {
- get => GetFlag(k_IsParented);
- set
- {
- SetFlag(value, k_IsParented);
- }
- }
+ internal bool 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 SynchronizeBaseHalfFloat;
- internal bool UnreliableFrameSync
- {
- get => GetFlag(k_UnreliableFrameSync);
- set
- {
- SetFlag(value, k_UnreliableFrameSync);
- }
- }
+ internal bool ReliableSequenced;
- internal bool TrackByStateId
- {
- get => GetFlag(k_TrackStateId);
- set
- {
- SetFlag(value, k_TrackStateId);
- }
- }
+ internal bool UseUnreliableDeltas;
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- private bool GetFlag(int flag)
- {
- return (m_Bitset & flag) != 0;
- }
+ internal bool UnreliableFrameSync;
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- private void SetFlag(bool set, int flag)
- {
- if (set) { m_Bitset = m_Bitset | (uint)flag; }
- else { m_Bitset = m_Bitset & (uint)~flag; }
- }
+ internal bool TrackByStateId;
+ ///
+ /// Clear everything but flags that should persist between state updates until changed by authority.
+ /// Persistent (non-cleared) flags are , , , ,
+ /// , ,
+ ///
internal void ClearBitSetForNextTick()
{
- // Clear everything but flags that should persist between state updates until changed by authority
- m_Bitset &= k_InLocalSpaceBit | k_Interpolate | k_UseHalfFloats | k_QuaternionSync | k_QuaternionCompress | k_PositionSlerp | k_UseUnreliableDeltas | k_SwitchTransformSpaceWhenParented;
+ MarkChanged(AxialType.Position, false);
+ MarkChanged(AxialType.Rotation, false);
+ MarkChanged(AxialType.Scale, false);
+ IsTeleportingNextFrame = false;
+ IsSynchronizing = false;
+ IsParented = false;
+ SynchronizeBaseHalfFloat = false;
+ ReliableSequenced = false;
+ UnreliableFrameSync = false;
+ TrackByStateId = false;
IsDirty = false;
}
@@ -687,14 +567,14 @@ public void NetworkSerialize(BufferSerializer serializer) where T : IReade
ReliableSequenced = true;
}
- BytePacker.WriteValueBitPacked(m_Writer, m_Bitset);
+ SerializeBitset(ref m_Writer);
// We use network ticks as opposed to absolute time as the authoritative
// side updates on every new tick.
BytePacker.WriteValueBitPacked(m_Writer, NetworkTick);
}
else
{
- ByteUnpacker.ReadValueBitPacked(m_Reader, out m_Bitset);
+ DeserializeBitset(ref m_Reader);
// We use network ticks as opposed to absolute time as the authoritative
// side updates on every new tick.
ByteUnpacker.ReadValueBitPacked(m_Reader, out NetworkTick);
@@ -707,6 +587,11 @@ public void NetworkSerialize(BufferSerializer serializer) where T : IReade
bitSetAndTickSize = m_Writer.Position - positionStart;
lastPosition = m_Writer.Position;
}
+ else
+ {
+ bitSetAndTickSize = m_Reader.Position - positionStart;
+ lastPosition = m_Reader.Position;
+ }
#endif
// If debugging states and track by state identifier is enabled, serialize the current state identifier
@@ -783,6 +668,13 @@ public void NetworkSerialize(BufferSerializer serializer) where T : IReade
{
positionSize = m_Writer.Position - lastPosition;
lastPosition = m_Writer.Position;
+ Debug.Log($"[Write][bitsAndTick={bitSetAndTickSize}][position={positionSize}]");
+ }
+ else
+ {
+ positionSize = m_Reader.Position - lastPosition;
+ lastPosition = m_Reader.Position;
+ Debug.Log($"[Read][bitsAndTick={bitSetAndTickSize}][position={positionSize}]");
}
#endif
@@ -902,6 +794,11 @@ public void NetworkSerialize(BufferSerializer serializer) where T : IReade
rotationSize = m_Writer.Position - lastPosition;
lastPosition = m_Writer.Position;
}
+ else
+ {
+ rotationSize = m_Reader.Position - lastPosition;
+ lastPosition = m_Reader.Position;
+ }
#endif
// Synchronize Scale
@@ -980,6 +877,11 @@ public void NetworkSerialize(BufferSerializer serializer) where T : IReade
scaleSize = m_Writer.Position - lastPosition;
lastPosition = m_Writer.Position;
}
+ else
+ {
+ scaleSize = m_Reader.Position - lastPosition;
+ lastPosition = m_Reader.Position;
+ }
#endif
// Only if we are receiving state
@@ -992,10 +894,96 @@ public void NetworkSerialize(BufferSerializer serializer) where T : IReade
else
{
LastSerializedSize = m_Writer.Position - positionStart;
+ }
+
#if NGO_NETWORKTRANSFORMSTATE_LOGWRITESIZE
- Debug.Log($"[NT-WriteSize][BitsAndTick: {bitSetAndTickSize}][position: {positionSize}][rotation: {rotationSize}][scale: {scaleSize}]");
+ var type = isWriting ? "Write" : "Read";
+ Debug.Log($"[NT-{type}][BitsAndTick: {bitSetAndTickSize}][position: {positionSize}][rotation: {rotationSize}][scale: {scaleSize}]");
#endif
- }
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ internal void SerializeBitset(ref FastBufferWriter writer)
+ {
+ uint bitset = 0;
+
+ Debug.Log($"[NT] Serializing has changes: HasPositionChange: {HasPositionChange}, HasRotAngleChange: {HasRotAngleChange}, HasScaleChange: {HasScaleChange}");
+ Debug.Log($"[NT] Serializing: HasRotAngleX: {HasRotAngleX}, HasRotAngleY: {HasRotAngleY}, HasRotAngleZ: {HasRotAngleZ}");
+
+ if (InLocalSpace) { bitset |= k_InLocalSpaceBit; }
+
+ // if (HasPositionChange)
+ // {
+ if (HasPositionX) { bitset |= k_PositionXBit; }
+ if (HasPositionY) { bitset |= k_PositionYBit; }
+ if (HasPositionZ) { bitset |= k_PositionZBit; }
+ // }
+ //
+ // if (HasRotAngleChange)
+ // {
+ if (HasRotAngleX) { bitset |= k_RotAngleXBit; }
+ if (HasRotAngleY) { bitset |= k_RotAngleYBit; }
+ if (HasRotAngleZ) { bitset |= k_RotAngleZBit; }
+ // }
+ //
+ // if (HasScaleChange)
+ // {
+ if (HasScaleX) { bitset |= k_ScaleXBit; }
+ if (HasScaleY) { bitset |= k_ScaleYBit; }
+ if (HasScaleZ) { bitset |= k_ScaleZBit; }
+
+ // }
+
+ if (IsTeleportingNextFrame) { bitset |= k_TeleportingBit; }
+ if (UseInterpolation) { bitset |= k_Interpolate; }
+ if (QuaternionSync) { bitset |= k_QuaternionSync; }
+ if (QuaternionCompression) { bitset |= k_QuaternionCompress; }
+ if (UseHalfFloatPrecision) { bitset |= k_UseHalfFloats; }
+ if (IsSynchronizing) { bitset |= k_Synchronization; }
+ if (UsePositionSlerp) { bitset |= k_PositionSlerp; }
+ if (IsParented) { bitset |= k_IsParented; }
+ if (SynchronizeBaseHalfFloat) { bitset |= k_SynchBaseHalfFloat; }
+ if (ReliableSequenced) { bitset |= k_ReliableSequenced; }
+ if (UseUnreliableDeltas) { bitset |= k_UseUnreliableDeltas; }
+ if (UnreliableFrameSync) { bitset |= k_UnreliableFrameSync; }
+ if (SwitchTransformSpaceWhenParented) { bitset |= k_SwitchTransformSpaceWhenParented; }
+ if (TrackByStateId) { bitset |= k_TrackStateId; }
+
+ BytePacker.WriteValueBitPacked(writer, bitset);
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ internal void DeserializeBitset(ref FastBufferReader reader)
+ {
+ ByteUnpacker.ReadValueBitPacked(reader, out 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;
+
+ Debug.Log($"Deserialized has changes: HasPositionChange: {HasPositionChange}, HasRotAngleChange: {HasRotAngleChange}, HasScaleChange: {HasScaleChange}");
+
}
}
#endregion
@@ -1837,6 +1825,7 @@ protected override void OnSynchronize(ref BufferSerializer serializer)
var transformToCommit = transform;
// If we are using Half Float Precision, then we want to only synchronize the authority's m_HalfPositionState.FullPosition in order for
// for the non-authority side to be able to properly synchronize delta position updates.
+ Debug.Log($"[Object-{NetworkObjectId}][CheckForStateChange][OnSynchronize] HasRotAngleX: {SynchronizeState.HasRotAngleX}, HasRotAngleChange: {SynchronizeState.HasRotAngleChange}");
CheckForStateChange(ref SynchronizeState, ref transformToCommit, true, targetClientId);
SynchronizeState.NetworkSerialize(serializer);
LastTickSync = SynchronizeState.GetNetworkTick();
@@ -1959,6 +1948,7 @@ private void TryCommitTransform(ref Transform transformToCommit, bool synchroniz
}
#endif
// If the transform has deltas (returns dirty) or if an explicitly set state is pending
+ Debug.Log($"[Object-{NetworkObjectId}][CheckForStateChange][TryCommitTransform][#1] HasRotAngleX: {m_LocalAuthoritativeNetworkState.HasRotAngleX}, HasRotAngleChange: {m_LocalAuthoritativeNetworkState.HasRotAngleChange}");
if (m_LocalAuthoritativeNetworkState.ExplicitSet || CheckForStateChange(ref m_LocalAuthoritativeNetworkState, ref transformToCommit, synchronize, forceState: settingState))
{
// If the state was explicitly set, then update the network tick to match the locally calculate tick
@@ -1971,6 +1961,7 @@ private void TryCommitTransform(ref Transform transformToCommit, bool synchroniz
if (SwitchTransformSpaceWhenParented && m_LocalAuthoritativeNetworkState.ExplicitSet && m_LocalAuthoritativeNetworkState.IsDirty && transform.parent != null && !m_LocalAuthoritativeNetworkState.InLocalSpace)
{
InLocalSpace = true;
+ Debug.Log($"[Object-{NetworkObjectId}][CheckForStateChange][TryCommitTransform][#2] HasRotAngleX: {m_LocalAuthoritativeNetworkState.HasRotAngleX}, HasRotAngleChange: {m_LocalAuthoritativeNetworkState.HasRotAngleChange}");
CheckForStateChange(ref m_LocalAuthoritativeNetworkState, ref transformToCommit, synchronize, forceState: true);
}
}
@@ -2069,6 +2060,7 @@ internal NetworkTransformState ApplyLocalNetworkState(Transform transform)
m_LocalAuthoritativeNetworkState.ClearBitSetForNextTick();
// Now check the transform for any threshold value changes
+ Debug.Log($"[Object-{NetworkObjectId}][CheckForStateChange][ApplyLocalNetworkState] HasRotAngleX: {m_LocalAuthoritativeNetworkState.HasRotAngleX}, HasRotAngleChange: {m_LocalAuthoritativeNetworkState.HasRotAngleChange}");
CheckForStateChange(ref m_LocalAuthoritativeNetworkState, ref transform);
// Return the entire state to be used by the integration test
@@ -2089,6 +2081,7 @@ internal bool ApplyTransformToNetworkState(ref NetworkTransformState networkStat
networkState.UseUnreliableDeltas = UseUnreliableDeltas;
m_HalfPositionState = new NetworkDeltaPosition(Vector3.zero, 0, math.bool3(SyncPositionX, SyncPositionY, SyncPositionZ));
+ Debug.Log($"[Object-{NetworkObjectId}][CheckForStateChange][ApplyTransformToNetworkState] HasRotAngleX: {networkState.HasRotAngleX}, HasRotAngleChange: {networkState.HasRotAngleChange}");
return CheckForStateChange(ref networkState, ref transformToUse);
}
@@ -2124,9 +2117,19 @@ private bool CheckForStateChange(ref NetworkTransformState networkState, ref Tra
var isTeleportingAndNotSynchronizing = networkState.IsTeleportingNextFrame && !isSynchronization;
var isDirty = false;
- var isPositionDirty = isTeleportingAndNotSynchronizing ? networkState.HasPositionChange : false;
- var isRotationDirty = isTeleportingAndNotSynchronizing ? networkState.HasRotAngleChange : false;
- var isScaleDirty = isTeleportingAndNotSynchronizing ? networkState.HasScaleChange : false;
+
+ var isPositionDirty = networkState.HasPositionChange;
+ var isRotationDirty = networkState.HasRotAngleChange;
+ var isScaleDirty = networkState.HasScaleChange;
+ if (!isTeleportingAndNotSynchronizing)
+ {
+ isPositionDirty = false;
+ isRotationDirty = false;
+ isScaleDirty = false;
+ networkState.MarkChanged(AxialType.Position,false);
+ networkState.MarkChanged(AxialType.Rotation,false);
+ networkState.MarkChanged(AxialType.Scale,false);
+ }
networkState.SwitchTransformSpaceWhenParented = SwitchTransformSpaceWhenParented;
// All of the checks below, up to the delta position checking portion, are to determine if the
@@ -2264,21 +2267,21 @@ private bool CheckForStateChange(ref NetworkTransformState networkState, ref Tra
if (SyncPositionX && (Mathf.Abs(networkState.PositionX - position.x) >= positionThreshold.x || networkState.IsTeleportingNextFrame || isAxisSync || forceState))
{
networkState.PositionX = position.x;
- networkState.HasPositionX = true;
+ networkState.SetHasPosition(Axis.X, true);
isPositionDirty = true;
}
if (SyncPositionY && (Mathf.Abs(networkState.PositionY - position.y) >= positionThreshold.y || networkState.IsTeleportingNextFrame || isAxisSync || forceState))
{
networkState.PositionY = position.y;
- networkState.HasPositionY = true;
+ networkState.SetHasPosition(Axis.Y, true);
isPositionDirty = true;
}
if (SyncPositionZ && (Mathf.Abs(networkState.PositionZ - position.z) >= positionThreshold.z || networkState.IsTeleportingNextFrame || isAxisSync || forceState))
{
networkState.PositionZ = position.z;
- networkState.HasPositionZ = true;
+ networkState.SetHasPosition(Axis.Z, true);
isPositionDirty = true;
}
}
@@ -2386,6 +2389,7 @@ private bool CheckForStateChange(ref NetworkTransformState networkState, ref Tra
networkState.HasPositionX = SyncPositionX;
networkState.HasPositionY = SyncPositionY;
networkState.HasPositionZ = SyncPositionZ;
+ networkState.HasPositionChange = SyncPositionX || SyncPositionY || SyncPositionZ;
}
}
@@ -2394,21 +2398,21 @@ private bool CheckForStateChange(ref NetworkTransformState networkState, ref Tra
if (SyncRotAngleX && (Mathf.Abs(Mathf.DeltaAngle(networkState.RotAngleX, rotAngles.x)) >= rotationThreshold.x || networkState.IsTeleportingNextFrame || isAxisSync || forceState))
{
networkState.RotAngleX = rotAngles.x;
- networkState.HasRotAngleX = true;
+ networkState.SetHasRotation(Axis.X, true);
isRotationDirty = true;
}
if (SyncRotAngleY && (Mathf.Abs(Mathf.DeltaAngle(networkState.RotAngleY, rotAngles.y)) >= rotationThreshold.y || networkState.IsTeleportingNextFrame || isAxisSync || forceState))
{
networkState.RotAngleY = rotAngles.y;
- networkState.HasRotAngleY = true;
+ networkState.SetHasRotation(Axis.Y, true);
isRotationDirty = true;
}
if (SyncRotAngleZ && (Mathf.Abs(Mathf.DeltaAngle(networkState.RotAngleZ, rotAngles.z)) >= rotationThreshold.z || networkState.IsTeleportingNextFrame || isAxisSync || forceState))
{
networkState.RotAngleZ = rotAngles.z;
- networkState.HasRotAngleZ = true;
+ networkState.SetHasRotation(Axis.Z, true);
isRotationDirty = true;
}
}
@@ -2432,9 +2436,7 @@ private bool CheckForStateChange(ref NetworkTransformState networkState, ref Tra
if (isRotationDirty)
{
networkState.Rotation = rotation;
- networkState.HasRotAngleX = true;
- networkState.HasRotAngleY = true;
- networkState.HasRotAngleZ = true;
+ networkState.MarkChanged(AxialType.Rotation, true);
}
}
@@ -2457,21 +2459,21 @@ private bool CheckForStateChange(ref NetworkTransformState networkState, ref Tra
if (SyncScaleX && (Mathf.Abs(networkState.ScaleX - scale.x) >= ScaleThreshold || networkState.IsTeleportingNextFrame || isAxisSync || forceState))
{
networkState.ScaleX = scale.x;
- networkState.HasScaleX = true;
+ networkState.SetHasScale(Axis.X, true);
isScaleDirty = true;
}
if (SyncScaleY && (Mathf.Abs(networkState.ScaleY - scale.y) >= ScaleThreshold || networkState.IsTeleportingNextFrame || isAxisSync || forceState))
{
networkState.ScaleY = scale.y;
- networkState.HasScaleY = true;
+ networkState.SetHasScale(Axis.Y, true);
isScaleDirty = true;
}
if (SyncScaleZ && (Mathf.Abs(networkState.ScaleZ - scale.z) >= ScaleThreshold || networkState.IsTeleportingNextFrame || isAxisSync || forceState))
{
networkState.ScaleZ = scale.z;
- networkState.HasScaleZ = true;
+ networkState.SetHasScale(Axis.Z, true);
isScaleDirty = true;
}
}
@@ -2484,7 +2486,7 @@ private bool CheckForStateChange(ref NetworkTransformState networkState, ref Tra
{
isScaleDirty = true;
networkState.Scale[i] = scale[i];
- networkState.SetHasScale(i, i == 0 ? SyncScaleX : i == 1 ? SyncScaleY : SyncScaleZ);
+ networkState.SetHasScale((Axis)i, i == 0 ? SyncScaleX : i == 1 ? SyncScaleY : SyncScaleZ);
}
}
}
@@ -2502,9 +2504,7 @@ private bool CheckForStateChange(ref NetworkTransformState networkState, ref Tra
{
networkState.Scale = transform.localScale;
}
- networkState.HasScaleX = true;
- networkState.HasScaleY = true;
- networkState.HasScaleZ = true;
+ networkState.MarkChanged(AxialType.Scale, true);
isScaleDirty = true;
}
isDirty |= isPositionDirty || isRotationDirty || isScaleDirty;
@@ -2735,7 +2735,7 @@ protected internal void ApplyAuthoritativeState()
{
for (int i = 0; i < 3; i++)
{
- if (m_LocalAuthoritativeNetworkState.HasScale(i))
+ if (m_LocalAuthoritativeNetworkState.HasScale((Axis)i))
{
adjustedScale[i] = m_LocalAuthoritativeNetworkState.Scale[i];
}
@@ -3190,7 +3190,7 @@ internal void ApplyUpdatedState(NetworkTransformState newState)
{
for (int i = 0; i < 3; i++)
{
- if (m_LocalAuthoritativeNetworkState.HasScale(i))
+ if (m_LocalAuthoritativeNetworkState.HasScale((Axis)i))
{
currentScale[i] = m_LocalAuthoritativeNetworkState.Scale[i];
}
@@ -4075,7 +4075,10 @@ private void SetStateInternal(Vector3 pos, Quaternion rot, Vector3 scale, bool s
var explicitState = m_LocalAuthoritativeNetworkState.ExplicitSet;
// Apply any delta states to the m_LocalAuthoritativeNetworkState
+ Debug.Log($"[Object-{NetworkObjectId}][CheckForStateChange][SetStateInternal] HasRotAngleX: {m_LocalAuthoritativeNetworkState.HasRotAngleX}, HasRotAngleChange: {m_LocalAuthoritativeNetworkState.HasRotAngleChange}");
var isDirty = CheckForStateChange(ref m_LocalAuthoritativeNetworkState, ref transformToCommit);
+ Debug.Log($"[After][Object-{NetworkObjectId}][CheckForStateChange][SetStateInternal] HasRotAngleX: {m_LocalAuthoritativeNetworkState.HasRotAngleX}, HasRotAngleChange: {m_LocalAuthoritativeNetworkState.HasRotAngleChange}");
+
// If we were dirty and the explicit state was set (prior to checking for deltas) or the current explicit state is dirty,
// then we set the explicit state flag.
diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/NetworkTransformGeneral.cs b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/NetworkTransformGeneral.cs
index 04afd12cb5..acbfd3d37e 100644
--- a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/NetworkTransformGeneral.cs
+++ b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/NetworkTransformGeneral.cs
@@ -60,7 +60,7 @@ public void TestMultipleStateSynchronization([Values] bool isLocal, [Values] boo
// Simulate a state update
localState.UseInterpolation = false;
localState.CurrentPosition = new Vector3(5.0f, 0.0f, 0.0f);
- localState.HasPositionX = true;
+ localState.SetHasPosition(NetworkTransform.Axis.X, true);
localState.PositionX = 5.0f;
localState.NetworkTick++;
@@ -85,8 +85,8 @@ public void TestMultipleStateSynchronization([Values] bool isLocal, [Values] boo
Assert.IsTrue(localState.NetworkTick == lastStateTick, $"Previous Non-authority state tick was {lastStateTick} but is now {localState.NetworkTick}. Authority pushed a state update.");
// Simualate a 2nd state update on a different position axis
- localState.HasPositionX = false;
- localState.HasPositionZ = true;
+ localState.SetHasPosition(NetworkTransform.Axis.X, false);
+ localState.SetHasPosition(NetworkTransform.Axis.Z, true);
localState.PositionZ = -5.0f;
localState.NetworkTick++;
m_NonAuthoritativeTransform.ApplyUpdatedState(localState);
diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/NetworkTransformStateTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/NetworkTransformStateTests.cs
index 67e66b61b3..79edaece53 100644
--- a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/NetworkTransformStateTests.cs
+++ b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/NetworkTransformStateTests.cs
@@ -1,12 +1,193 @@
+using System;
using NUnit.Framework;
+using Unity.Collections;
using Unity.Netcode.Components;
using Unity.Netcode.TestHelpers.Runtime;
using UnityEngine;
+using Object = UnityEngine.Object;
namespace Unity.Netcode.RuntimeTests
{
+ // These tests do not need to run against the Rust server.
+ [IgnoreIfServiceEnvironmentVariableSet]
+ internal class NetworkTransformStateTests
+ {
+ [Test]
+ public void NetworkTransformStateFlags()
+ {
+ // The current number of flags on the NetworkTransformState
+ var numFlags = 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)
+ {
+ NetworkTransform.NetworkTransformState transformState;
+ FastBufferWriter writer;
+ FastBufferReader reader;
+ // Test setting one at a time.
+ for (int j = 0; j < numFlags; j++)
+ {
+ // reset previous test if needed
+ if (j > 0)
+ {
+ boolSet[j - 1] = false;
+ }
+
+ boolSet[j] = true;
+
+ transformState = new NetworkTransform.NetworkTransformState()
+ {
+ InLocalSpace = boolSet[0],
+ HasPositionX = boolSet[1],
+ HasPositionY = boolSet[2],
+ HasPositionZ = boolSet[3],
+ HasRotAngleX = boolSet[4],
+ HasRotAngleY = boolSet[5],
+ HasRotAngleZ = boolSet[6],
+ HasScaleX = boolSet[7],
+ HasScaleY = boolSet[8],
+ HasScaleZ = boolSet[9],
+ IsTeleportingNextFrame = boolSet[10],
+ UseInterpolation = boolSet[11],
+ QuaternionSync = boolSet[12],
+ QuaternionCompression = boolSet[13],
+ UseHalfFloatPrecision = boolSet[14],
+ IsSynchronizing = boolSet[15],
+ UsePositionSlerp = boolSet[16],
+ IsParented = boolSet[17],
+ SynchronizeBaseHalfFloat = boolSet[18],
+ ReliableSequenced = boolSet[19],
+ UseUnreliableDeltas = boolSet[20],
+ UnreliableFrameSync = boolSet[21],
+ SwitchTransformSpaceWhenParented = boolSet[22],
+ TrackByStateId = boolSet[23],
+ };
+
+ writer = new FastBufferWriter(64, Allocator.Temp);
+ transformState.SerializeBitset(ref writer);
+
+ // Test the bitset representation of the serialization matches the pre-refactor serialization
+ reader = new FastBufferReader(writer, Allocator.None);
+ 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);
+
+ // Test the deserialized values match the original values
+ var deserialized = new NetworkTransform.NetworkTransformState();
+ deserialized.DeserializeBitset(ref reader);
+
+ AssertTransformStateEquals(boolSet, deserialized, "Flag serialization");
+ }
+
+ // Test setting all flag values
+ transformState = new NetworkTransform.NetworkTransformState()
+ {
+ InLocalSpace = true,
+ HasPositionX = true,
+ HasPositionY = true,
+ HasPositionZ = true,
+ HasRotAngleX = true,
+ HasRotAngleY = true,
+ HasRotAngleZ = true,
+ HasScaleX = true,
+ HasScaleY = true,
+ HasScaleZ = true,
+ IsTeleportingNextFrame = true,
+ UseInterpolation = true,
+ QuaternionSync = true,
+ QuaternionCompression = true,
+ UseHalfFloatPrecision = true,
+ IsSynchronizing = true,
+ UsePositionSlerp = true,
+ IsParented = true,
+ SynchronizeBaseHalfFloat = true,
+ ReliableSequenced = true,
+ UseUnreliableDeltas = true,
+ UnreliableFrameSync = true,
+ SwitchTransformSpaceWhenParented = true,
+ TrackByStateId = true,
+ };
+
+ writer = new FastBufferWriter(64, Allocator.Temp);
+ transformState.SerializeBitset(ref writer);
+ var serializedBuffer = writer.ToArray();
+
+ // Use a uint to set all bits to true in a legacy style bitset
+ uint bitset = 0;
+ for (int i = 0; i < numFlags; i++)
+ {
+ bitset |= indexValues[i];
+ }
+
+ var legacyBitsetWriter = new FastBufferWriter(64, Allocator.Temp);
+ 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!");
+
+
+ var deserializedState = new NetworkTransform.NetworkTransformState();
+
+ reader = new FastBufferReader(legacyBitsetWriter, Allocator.None);
+ deserializedState.DeserializeBitset(ref reader);
+
+ Array.Fill(boolSet, true);
+ AssertTransformStateEquals(boolSet, deserializedState, "Read bitset");
+ }
+ private void AssertTransformStateEquals(bool[] expected, NetworkTransform.NetworkTransformState actual, string testName)
+ {
+ Assert.AreEqual(expected[0], actual.InLocalSpace, $"{testName} Flag {nameof(NetworkTransform.NetworkTransformState.InLocalSpace)} is incorrect!");
+ Assert.AreEqual(expected[1], actual.HasPositionX, $"{testName} Flag {nameof(NetworkTransform.NetworkTransformState.HasPositionX)} is incorrect!");
+ Assert.AreEqual(expected[2], actual.HasPositionY, $"{testName} Flag {nameof(NetworkTransform.NetworkTransformState.HasPositionY)} is incorrect!");
+ Assert.AreEqual(expected[3], actual.HasPositionZ, $"{testName} Flag {nameof(NetworkTransform.NetworkTransformState.HasPositionZ)} is incorrect!");
+ Assert.AreEqual(expected[4], actual.HasRotAngleX, $"{testName} Flag {nameof(NetworkTransform.NetworkTransformState.HasRotAngleX)} is incorrect!");
+ Assert.AreEqual(expected[5], actual.HasRotAngleY, $"{testName} Flag {nameof(NetworkTransform.NetworkTransformState.HasRotAngleY)} is incorrect!");
+ Assert.AreEqual(expected[6], actual.HasRotAngleZ, $"{testName} Flag {nameof(NetworkTransform.NetworkTransformState.HasRotAngleZ)} is incorrect!");
+ Assert.AreEqual(expected[7], actual.HasScaleX, $"{testName} Flag {nameof(NetworkTransform.NetworkTransformState.HasScaleX)} is incorrect!");
+ Assert.AreEqual(expected[8], actual.HasScaleY, $"{testName} Flag {nameof(NetworkTransform.NetworkTransformState.HasScaleY)} is incorrect!");
+ Assert.AreEqual(expected[9], actual.HasScaleZ, $"{testName} Flag {nameof(NetworkTransform.NetworkTransformState.HasScaleZ)} is incorrect!");
+ Assert.AreEqual(expected[10], actual.IsTeleportingNextFrame, $"{testName} Flag {nameof(NetworkTransform.NetworkTransformState.IsTeleportingNextFrame)} is incorrect!");
+ Assert.AreEqual(expected[11], actual.UseInterpolation, $"{testName} Flag {nameof(NetworkTransform.NetworkTransformState.UseInterpolation)} is incorrect!");
+ Assert.AreEqual(expected[12], actual.QuaternionSync, $"{testName} Flag {nameof(NetworkTransform.NetworkTransformState.QuaternionSync)} is incorrect!");
+ Assert.AreEqual(expected[13], actual.QuaternionCompression, $"{testName} Flag {nameof(NetworkTransform.NetworkTransformState.QuaternionCompression)} is incorrect!");
+ Assert.AreEqual(expected[14], actual.UseHalfFloatPrecision, $"{testName} Flag {nameof(NetworkTransform.NetworkTransformState.UseHalfFloatPrecision)} is incorrect!");
+ Assert.AreEqual(expected[15], actual.IsSynchronizing, $"{testName} Flag {nameof(NetworkTransform.NetworkTransformState.IsSynchronizing)} is incorrect!");
+ Assert.AreEqual(expected[16], actual.UsePositionSlerp, $"{testName} Flag {nameof(NetworkTransform.NetworkTransformState.UsePositionSlerp)} is incorrect!");
+ Assert.AreEqual(expected[17], actual.IsParented, $"{testName} Flag {nameof(NetworkTransform.NetworkTransformState.IsParented)} is incorrect!");
+ Assert.AreEqual(expected[18], actual.SynchronizeBaseHalfFloat, $"{testName} Flag {nameof(NetworkTransform.NetworkTransformState.SynchronizeBaseHalfFloat)} is incorrect!");
+ Assert.AreEqual(expected[19], actual.ReliableSequenced, $"{testName} Flag {nameof(NetworkTransform.NetworkTransformState.ReliableSequenced)} is incorrect!");
+ Assert.AreEqual(expected[20], actual.UseUnreliableDeltas, $"{testName} Flag {nameof(NetworkTransform.NetworkTransformState.UseUnreliableDeltas)} is incorrect!");
+ Assert.AreEqual(expected[21], actual.UnreliableFrameSync, $"{testName} Flag {nameof(NetworkTransform.NetworkTransformState.UnreliableFrameSync)} is incorrect!");
+ Assert.AreEqual(expected[22], actual.SwitchTransformSpaceWhenParented, $"{testName} Flag {nameof(NetworkTransform.NetworkTransformState.SwitchTransformSpaceWhenParented)} is incorrect!");
+ Assert.AreEqual(expected[23], actual.TrackByStateId, $"{testName} Flag {nameof(NetworkTransform.NetworkTransformState.TrackByStateId)} is incorrect!");
+ }
+
+ }
+
+ // These tests do not need to run against the Rust server.
+ [IgnoreIfServiceEnvironmentVariableSet]
[TestFixture(TransformSpace.World, Precision.Full, Rotation.Euler)]
[TestFixture(TransformSpace.World, Precision.Half, Rotation.Euler)]
[TestFixture(TransformSpace.Local, Precision.Full, Rotation.Euler)]
@@ -15,7 +196,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 +258,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 +272,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)
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);
}
}