Skip to content

Commit a6d5041

Browse files
test: Refer to the single non-authority instance directly
There is one connected client, so the per instance dictionaries and the loops over m_NetworkManagers only ever held one entry. Resolve the non-authority instance once through GetNonAuthorityNetworkManager and name the two sampling frame counts that were inline literals.
1 parent ed35319 commit a6d5041

1 file changed

Lines changed: 57 additions & 96 deletions

File tree

com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/NetworkTransformHalfFloatPrecisionTests.cs

Lines changed: 57 additions & 96 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
using System.Collections;
2-
using System.Collections.Generic;
32
using NUnit.Framework;
43
using Unity.Netcode.Components;
54
using 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

Comments
 (0)