11using System . Collections ;
2- using System . Collections . Generic ;
32using NUnit . Framework ;
43using Unity . Netcode . Components ;
54using Unity . Netcode . TestHelpers . Runtime ;
@@ -12,9 +11,9 @@ namespace Unity.Netcode.RuntimeTests
1211 /// Validates that <see cref="NetworkTransform.UseHalfFloatPrecision"/> does not introduce motion of its own.
1312 /// </summary>
1413 /// <remarks>
15- /// Both tests move the authority in one direction only and require non-authority instances to follow without
16- /// ever moving backwards. Interpolation cannot overshoot, so any movement opposite to the authority's has to
17- /// have come from how the position was encoded rather than from the authority .
14+ /// Both tests move the authority in one direction only and require the non-authority instance to follow
15+ /// without ever moving backwards. Interpolation cannot overshoot, so any movement opposite to the direction
16+ /// the authority moved came from how the position was encoded.
1817 /// <br /><br />
1918 /// These do not use the time travel harness because the behavior only appears over multiple real state update
2019 /// and interpolation cycles.
@@ -25,35 +24,37 @@ internal class NetworkTransformHalfFloatPrecisionTests : IntegrationTestWithAppr
2524 {
2625 protected override int NumberOfClients => 1 ;
2726
28- /// <summary>
29- /// How far the object travels before the position is checked.
30- /// </summary>
31- /// <remarks>
32- /// Half float resolution gets coarser the further the object is from the base position established when it
33- /// spawned, so the object has to travel away from that base for the resolution to be worth testing.
34- /// </remarks>
27+ // Half float resolution gets coarser the further the object is from the base position established when it
28+ // spawned, so it has to travel away from that base for the resolution to be worth testing.
3529 private const float k_TravelDistance = 30.0f ;
3630
3731 private const float k_TravelStep = 1.5f ;
3832
39- // Moves the object off a position that a half float can represent exactly, which is a position that leaves
40- // no rounding loss behind and so cannot show the problem being tested for.
33+ // Moves the object off a position that a half float can represent exactly, which leaves no rounding loss
34+ // behind and so cannot show the problem being tested for.
4135 private const float k_UnrepresentableOffset = 0.0007f ;
4236
4337 // Small enough per update that the encoding cannot represent the change on its own.
4438 private const float k_CreepStep = 0.0005f ;
4539
4640 private const int k_CreepTicks = 60 ;
4741
42+ // Frames sampled after the creep, while the last states sent are still being interpolated.
43+ private const int k_SettleFrames = 30 ;
44+
45+ // Frames sampled with nothing moving at all.
46+ private const int k_StationaryFrames = 120 ;
47+
4848 // Tolerated backwards movement, which is float noise only. Well below the roughly 1mm resolution.
4949 private const float k_MonotonicEpsilon = 1e-5f ;
5050
5151 private GameObject m_TestPrefab ;
5252 private NetworkManager m_AuthorityNetworkManager ;
5353 private NetworkTransform m_AuthorityInstance ;
54+ private NetworkTransform m_NonAuthorityInstance ;
5455
55- private readonly Dictionary < NetworkTransform , float > m_WorstRegression = new Dictionary < NetworkTransform , float > ( ) ;
56- private readonly Dictionary < NetworkTransform , float > m_LastObserved = new Dictionary < NetworkTransform , float > ( ) ;
56+ private float m_WorstRegression ;
57+ private float m_LastObserved ;
5758
5859 private int m_TicksApplied ;
5960 private float m_StepThisPhase ;
@@ -93,22 +94,9 @@ protected override void OnServerAndClientsCreated()
9394 base . OnServerAndClientsCreated ( ) ;
9495 }
9596
96- private bool AllInstancesCaughtUp ( )
97+ private bool NonAuthorityCaughtUp ( )
9798 {
98- var authority = GetAuthorityNetworkManager ( ) ;
99- foreach ( var networkManager in m_NetworkManagers )
100- {
101- if ( networkManager == authority )
102- {
103- continue ;
104- }
105- var nonAuthority = networkManager . SpawnManager . SpawnedObjects [ m_AuthorityInstance . NetworkObjectId ] ;
106- if ( ! Approximately ( nonAuthority . transform . position , m_AuthorityInstance . transform . position ) )
107- {
108- return false ;
109- }
110- }
111- return true ;
99+ return Approximately ( m_NonAuthorityInstance . transform . position , m_AuthorityInstance . transform . position ) ;
112100 }
113101
114102 /// <summary>
@@ -120,53 +108,23 @@ private bool AllInstancesCaughtUp()
120108 /// </remarks>
121109 private void SampleForRegression ( )
122110 {
123- var authority = GetAuthorityNetworkManager ( ) ;
124- foreach ( var networkManager in m_NetworkManagers )
125- {
126- if ( networkManager == authority )
127- {
128- continue ;
129- }
130- var nonAuthority = networkManager . SpawnManager . SpawnedObjects [ m_AuthorityInstance . NetworkObjectId ] . GetComponent < NetworkTransform > ( ) ;
131- var current = nonAuthority . transform . position . x ;
132- if ( m_LastObserved . TryGetValue ( nonAuthority , out var previous ) )
133- {
134- var regression = previous - current ;
135- if ( regression > m_WorstRegression [ nonAuthority ] )
136- {
137- m_WorstRegression [ nonAuthority ] = regression ;
138- }
139- }
140- m_LastObserved [ nonAuthority ] = current ;
141- }
111+ var current = m_NonAuthorityInstance . transform . position . x ;
112+ m_WorstRegression = Mathf . Max ( m_WorstRegression , m_LastObserved - current ) ;
113+ m_LastObserved = current ;
142114 }
143115
144116 private void BeginSampling ( )
145117 {
146- m_WorstRegression . Clear ( ) ;
147- m_LastObserved . Clear ( ) ;
148- var authority = GetAuthorityNetworkManager ( ) ;
149- foreach ( var networkManager in m_NetworkManagers )
150- {
151- if ( networkManager == authority )
152- {
153- continue ;
154- }
155- var nonAuthority = networkManager . SpawnManager . SpawnedObjects [ m_AuthorityInstance . NetworkObjectId ] . GetComponent < NetworkTransform > ( ) ;
156- m_WorstRegression . Add ( nonAuthority , 0.0f ) ;
157- m_LastObserved . Add ( nonAuthority , nonAuthority . transform . position . x ) ;
158- }
118+ m_WorstRegression = 0.0f ;
119+ m_LastObserved = m_NonAuthorityInstance . transform . position . x ;
159120 }
160121
161122 private void AssertNoRegression ( string phase )
162123 {
163- foreach ( var entry in m_WorstRegression )
164- {
165- Assert . LessOrEqual ( entry . Value , k_MonotonicEpsilon ,
166- $ "[{ phase } ] { entry . Key . NetworkManager . name } moved { entry . Value } backwards along X while the " +
167- $ "authority only ever moved forwards. Interpolation cannot overshoot, so this motion was " +
168- $ "introduced by the half float position encoding rather than reproduced from the authority.") ;
169- }
124+ Assert . LessOrEqual ( m_WorstRegression , k_MonotonicEpsilon ,
125+ $ "[{ phase } ] { m_NonAuthorityInstance . NetworkManager . name } moved { m_WorstRegression } backwards along " +
126+ $ "X while the authority only ever moved forwards. Interpolation cannot overshoot, so this motion " +
127+ $ "was introduced by the half float position encoding rather than reproduced from the authority.") ;
170128 }
171129
172130 /// <summary>
@@ -194,24 +152,34 @@ private IEnumerator DriveAuthority(float stepPerTick, int ticks)
194152 }
195153
196154 /// <summary>
197- /// Moves an object away from its base position and then moves it forward in very small steps, requiring
198- /// every non-authority instance to follow without ever moving backwards.
155+ /// Spawns the test object and travels it away from the base position established when it spawned.
199156 /// </summary>
200- /// <returns>An <see cref="IEnumerator"/> for the test coroutine.</returns>
201- [ UnityTest ]
202- public IEnumerator HalfFloatPrecisionDoesNotInvertMotion ( )
157+ private IEnumerator SpawnAndTravel ( )
203158 {
204159 m_AuthorityNetworkManager = GetAuthorityNetworkManager ( ) ;
205160 m_AuthorityInstance = SpawnObject ( m_TestPrefab , m_AuthorityNetworkManager ) . GetComponent < NetworkTransform > ( ) ;
206161
207- yield return WaitForSpawnedOnAllOrTimeOut ( m_AuthorityInstance . gameObject ) ;
162+ yield return WaitForSpawnedOnAllOrTimeOut ( m_AuthorityInstance . NetworkObject ) ;
208163 AssertOnTimeout ( $ "Not all clients spawned { m_AuthorityInstance . name } !") ;
209164
210- var travelTicks = ( int ) ( k_TravelDistance / k_TravelStep ) ;
211- yield return DriveAuthority ( k_TravelStep , travelTicks ) ;
165+ var nonAuthority = GetNonAuthorityNetworkManager ( ) ;
166+ m_NonAuthorityInstance = nonAuthority . SpawnManager . SpawnedObjects [ m_AuthorityInstance . NetworkObjectId ] . GetComponent < NetworkTransform > ( ) ;
167+
168+ yield return DriveAuthority ( k_TravelStep , ( int ) ( k_TravelDistance / k_TravelStep ) ) ;
169+ }
170+
171+ /// <summary>
172+ /// Moves an object away from its base position and then moves it forward in very small steps, requiring
173+ /// the non-authority instance to follow without ever moving backwards.
174+ /// </summary>
175+ /// <returns>An <see cref="IEnumerator"/> for the test coroutine.</returns>
176+ [ UnityTest ]
177+ public IEnumerator HalfFloatPrecisionDoesNotInvertMotion ( )
178+ {
179+ yield return SpawnAndTravel ( ) ;
212180
213- yield return WaitForConditionOrTimeOut ( AllInstancesCaughtUp ) ;
214- AssertOnTimeout ( "Non -authority instances did not catch up to the authority after the travel phase." ) ;
181+ yield return WaitForConditionOrTimeOut ( NonAuthorityCaughtUp ) ;
182+ AssertOnTimeout ( "The non -authority instance did not catch up to the authority after the travel phase." ) ;
215183
216184 BeginSampling ( ) ;
217185 m_TicksApplied = 0 ;
@@ -225,7 +193,7 @@ public IEnumerator HalfFloatPrecisionDoesNotInvertMotion()
225193 m_AuthorityNetworkManager . NetworkTickSystem . Tick -= OnNetworkTick ;
226194
227195 // Keep sampling while the last sent states are still being interpolated.
228- for ( var i = 0 ; i < 30 ; i ++ )
196+ for ( var i = 0 ; i < k_SettleFrames ; i ++ )
229197 {
230198 SampleForRegression ( ) ;
231199 yield return null ;
@@ -234,8 +202,8 @@ public IEnumerator HalfFloatPrecisionDoesNotInvertMotion()
234202 AssertNoRegression ( "creep" ) ;
235203
236204 // Small movements still have to arrive rather than be discarded.
237- yield return WaitForConditionOrTimeOut ( AllInstancesCaughtUp ) ;
238- AssertOnTimeout ( $ "Non -authority instances did not converge on the authority position " +
205+ yield return WaitForConditionOrTimeOut ( NonAuthorityCaughtUp ) ;
206+ AssertOnTimeout ( $ "The non -authority instance did not converge on the authority position " +
239207 $ "{ m_AuthorityInstance . transform . position } after creeping, which means slow motion is being " +
240208 $ "discarded rather than transmitted.") ;
241209 }
@@ -247,28 +215,21 @@ public IEnumerator HalfFloatPrecisionDoesNotInvertMotion()
247215 [ UnityTest ]
248216 public IEnumerator HalfFloatPrecisionHoldsStillWhenStationary ( )
249217 {
250- m_AuthorityNetworkManager = GetAuthorityNetworkManager ( ) ;
251- m_AuthorityInstance = SpawnObject ( m_TestPrefab , m_AuthorityNetworkManager ) . GetComponent < NetworkTransform > ( ) ;
252-
253- yield return WaitForSpawnedOnAllOrTimeOut ( m_AuthorityInstance . gameObject ) ;
254- AssertOnTimeout ( $ "Not all clients spawned { m_AuthorityInstance . name } !") ;
255-
256- var travelTicks = ( int ) ( k_TravelDistance / k_TravelStep ) ;
257- yield return DriveAuthority ( k_TravelStep , travelTicks ) ;
218+ yield return SpawnAndTravel ( ) ;
258219
259220 // A position that a half float happens to represent exactly leaves no rounding loss behind, and with
260221 // no rounding loss there is nothing that could move the object. Offsetting by less than the encoding
261222 // can represent guarantees there is some, which is the state a settling object is normally left in.
262223 yield return DriveAuthority ( k_UnrepresentableOffset , 1 ) ;
263224
264- yield return WaitForConditionOrTimeOut ( AllInstancesCaughtUp ) ;
265- AssertOnTimeout ( "Non -authority instances did not catch up to the authority after the travel phase." ) ;
225+ yield return WaitForConditionOrTimeOut ( NonAuthorityCaughtUp ) ;
226+ AssertOnTimeout ( "The non -authority instance did not catch up to the authority after the travel phase." ) ;
266227
267- // Nothing moves for the rest of the test, so the authority's last direction was forwards. Checking for
268- // backwards movement rather than for drift from a starting point means the instances are still free to
269- // finish interpolating towards the authority without that counting against them .
228+ // Nothing moves for the rest of the test, so the last direction the authority moved was forwards.
229+ // Checking for backwards movement rather than for drift from a starting point means the instance is
230+ // still free to finish interpolating towards the authority without that counting against it .
270231 BeginSampling ( ) ;
271- for ( var i = 0 ; i < 120 ; i ++ )
232+ for ( var i = 0 ; i < k_StationaryFrames ; i ++ )
272233 {
273234 SampleForRegression ( ) ;
274235 yield return null ;
0 commit comments