Skip to content

Commit bb6ca19

Browse files
test: Address review feedback on the render time and tick latency tests
- Drop the redundant HostOrServer fixture argument and the UseCMBService override; Host is the default and a client-server fixture never runs under the CMB service. - Use WaitForSpawnedOnAllOrTimeOut, GetNonAuthorityNetworkManager and WaitForTicks instead of hand rolled equivalents. - There is only ever one connected client, so drop the collections and refer to the single non-authority instance directly. - Fold the two tick latency tests into one and drop the assertion that recomputed the implementation's own formula. What is left is what can actually regress: the value does not drift with session time, and it grows by exactly the ticks added to the interpolation buffer.
1 parent cbd18c6 commit bb6ca19

2 files changed

Lines changed: 45 additions & 140 deletions

File tree

Lines changed: 33 additions & 110 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;
@@ -18,8 +17,8 @@ namespace Unity.Netcode.RuntimeTests
1817
/// can never be less than the tick latency. Deriving the render time from LocalTime eats into that margin by
1918
/// however far the two clocks are apart, and can push the target past ServerTime entirely.
2019
/// </remarks>
21-
[TestFixture(HostOrServer.Host, NetworkTransform.InterpolationTypes.Lerp)]
22-
[TestFixture(HostOrServer.Host, NetworkTransform.InterpolationTypes.SmoothDampening)]
20+
[TestFixture(NetworkTransform.InterpolationTypes.Lerp)]
21+
[TestFixture(NetworkTransform.InterpolationTypes.SmoothDampening)]
2322
internal class NetworkTransformInterpolationRenderTimeTests : IntegrationTestWithApproximation
2423
{
2524
protected override int NumberOfClients => 1;
@@ -47,19 +46,12 @@ internal class NetworkTransformInterpolationRenderTimeTests : IntegrationTestWit
4746
private NetworkManager m_AuthorityNetworkManager;
4847
private NetworkTransform m_AuthorityInstance;
4948
private Vector3 m_Direction;
50-
private int m_TickCount;
5149

52-
public NetworkTransformInterpolationRenderTimeTests(HostOrServer hostOrServer, NetworkTransform.InterpolationTypes interpolationType) : base(hostOrServer)
50+
public NetworkTransformInterpolationRenderTimeTests(NetworkTransform.InterpolationTypes interpolationType)
5351
{
5452
m_InterpolationType = interpolationType;
5553
}
5654

57-
// TODO: [CmbServiceTests] ServerTime's meaning under a CMB service session has not been verified.
58-
protected override bool UseCMBService()
59-
{
60-
return false;
61-
}
62-
6355
protected override void OnServerAndClientsCreated()
6456
{
6557
m_TestPrefab = CreateNetworkObjectPrefab("RenderTimeTestObj");
@@ -86,139 +78,70 @@ private static double GetClockLeadInTicks(NetworkManager networkManager)
8678
/// </summary>
8779
private void OnNetworkTick()
8880
{
89-
m_TickCount++;
9081
m_AuthorityInstance.transform.position += m_Direction * k_DistancePerTick;
9182
}
9283

93-
private bool AllClientsSpawnedInstance()
94-
{
95-
foreach (var networkManager in m_NetworkManagers)
96-
{
97-
if (networkManager == m_AuthorityNetworkManager)
98-
{
99-
continue;
100-
}
101-
102-
if (!networkManager.SpawnManager.SpawnedObjects.ContainsKey(m_AuthorityInstance.NetworkObject.NetworkObjectId))
103-
{
104-
return false;
105-
}
106-
}
107-
return true;
108-
}
109-
110-
private List<NetworkTransform> GetNonAuthorityInstances()
111-
{
112-
var instances = new List<NetworkTransform>();
113-
foreach (var networkManager in m_NetworkManagers)
114-
{
115-
if (networkManager == m_AuthorityNetworkManager)
116-
{
117-
continue;
118-
}
119-
120-
var spawnedObject = networkManager.SpawnManager.SpawnedObjects[m_AuthorityInstance.NetworkObject.NetworkObjectId];
121-
instances.Add(spawnedObject.GetComponent<NetworkTransform>());
122-
}
123-
return instances;
124-
}
125-
12684
[UnityTest]
12785
public IEnumerator RenderTimeTrailsTheServerClock()
12886
{
12987
m_AuthorityNetworkManager = GetAuthorityNetworkManager();
13088
m_AuthorityInstance = SpawnObject(m_TestPrefab, m_AuthorityNetworkManager).GetComponent<NetworkTransform>();
13189

132-
yield return WaitForConditionOrTimeOut(AllClientsSpawnedInstance);
90+
yield return WaitForSpawnedOnAllOrTimeOut(m_AuthorityInstance.NetworkObject);
13391
AssertOnTimeout($"Not all clients spawned {m_AuthorityInstance.name}!");
13492

135-
var nonAuthorityInstances = GetNonAuthorityInstances();
136-
Assert.IsNotEmpty(nonAuthorityInstances, "There were no non-authority instances to measure!");
93+
var nonAuthority = GetNonAuthorityNetworkManager();
94+
var nonAuthorityInstance = nonAuthority.SpawnManager.SpawnedObjects[m_AuthorityInstance.NetworkObject.NetworkObjectId].GetComponent<NetworkTransform>();
13795

13896
// Separate the two clocks by a known amount so that which one the render time is derived from is
13997
// actually distinguishable.
140-
foreach (var instance in nonAuthorityInstances)
141-
{
142-
var networkManager = instance.NetworkManager;
143-
networkManager.NetworkTimeSystem.LocalBufferSec = k_LocalBufferTicks * GetTickInterval(networkManager);
144-
}
98+
nonAuthority.NetworkTimeSystem.LocalBufferSec = k_LocalBufferTicks * GetTickInterval(nonAuthority);
14599

146100
// Start continuous motion on the authority.
147101
m_Direction = GetRandomVector3(-10, 10).normalized;
148-
m_TickCount = 0;
149102
m_AuthorityNetworkManager.NetworkTickSystem.Tick += OnNetworkTick;
150103

151104
// The offset only moves when the client next receives a time sync, so wait for the separation to
152105
// actually take hold rather than assuming it has.
153-
yield return WaitForConditionOrTimeOut(() =>
154-
{
155-
foreach (var instance in nonAuthorityInstances)
156-
{
157-
if (GetClockLeadInTicks(instance.NetworkManager) < k_RequiredLeadTicks)
158-
{
159-
return false;
160-
}
161-
}
162-
return true;
163-
});
164-
AssertOnTimeout($"The client clocks never separated by {k_RequiredLeadTicks} ticks, so this test " +
165-
$"cannot tell the two clocks apart and would pass regardless of which one is used.");
106+
yield return WaitForConditionOrTimeOut(() => GetClockLeadInTicks(nonAuthority) >= k_RequiredLeadTicks);
107+
AssertOnTimeout($"The nonAuthority clock never fell {k_RequiredLeadTicks} ticks behind, so this test " +
108+
"cannot tell the two clocks apart and would pass regardless of which one is used.");
166109

167110
// Let the interpolator settle at the new separation before measuring.
168-
var warmUpTarget = m_TickCount + k_WarmUpTicks;
169-
yield return WaitForConditionOrTimeOut(() => m_TickCount >= warmUpTarget);
170-
AssertOnTimeout("Timed out waiting for the authority to keep moving!");
111+
yield return WaitForTicks(m_AuthorityNetworkManager, k_WarmUpTicks);
171112

172113
// Sample how far behind ServerTime the state being interpolated towards was sent.
173-
var totalTargetLagTicks = new Dictionary<NetworkTransform, double>();
174-
var totalBuffered = new Dictionary<NetworkTransform, int>();
175-
var samples = new Dictionary<NetworkTransform, int>();
176-
foreach (var instance in nonAuthorityInstances)
177-
{
178-
totalTargetLagTicks.Add(instance, 0.0d);
179-
totalBuffered.Add(instance, 0);
180-
samples.Add(instance, 0);
181-
}
182-
114+
var interpolator = nonAuthorityInstance.GetPositionInterpolator();
115+
var totalTargetLagTicks = 0.0d;
116+
var totalBuffered = 0;
117+
var samples = 0;
183118
for (int frame = 0; frame < k_SampledFrames; frame++)
184119
{
185-
foreach (var instance in nonAuthorityInstances)
120+
if (interpolator.InterpolateState.Target.HasValue)
186121
{
187-
var interpolator = instance.GetPositionInterpolator();
188-
if (!interpolator.InterpolateState.Target.HasValue)
189-
{
190-
continue;
191-
}
192-
193-
var networkManager = instance.NetworkManager;
194-
var targetLag = networkManager.ServerTime.Time - interpolator.InterpolateState.Target.Value.TimeSent;
195-
totalTargetLagTicks[instance] += targetLag / GetTickInterval(networkManager);
196-
totalBuffered[instance] += interpolator.m_BufferQueue.Count;
197-
samples[instance]++;
122+
var targetLag = nonAuthority.ServerTime.Time - interpolator.InterpolateState.Target.Value.TimeSent;
123+
totalTargetLagTicks += targetLag / GetTickInterval(nonAuthority);
124+
totalBuffered += interpolator.m_BufferQueue.Count;
125+
samples++;
198126
}
199127
yield return null;
200128
}
201129

202130
m_AuthorityNetworkManager.NetworkTickSystem.Tick -= OnNetworkTick;
203131

204-
foreach (var instance in nonAuthorityInstances)
205-
{
206-
Assert.Greater(samples[instance], 0, $"{instance.name} never had a state to interpolate towards!");
207-
208-
var networkManager = instance.NetworkManager;
209-
var meanTargetLagTicks = totalTargetLagTicks[instance] / samples[instance];
210-
var meanBuffered = totalBuffered[instance] / (float)samples[instance];
211-
var tickLatency = networkManager.NetworkTimeSystem.TickLatency;
212-
213-
// Anything less than the tick latency means the render time came from a clock that leads the one
214-
// the states are stamped on.
215-
Assert.GreaterOrEqual(meanTargetLagTicks, tickLatency,
216-
$"[{m_InterpolationType}] {instance.name} was interpolating towards a state sent " +
217-
$"{meanTargetLagTicks:F3} ticks behind the server clock, but the render time is the server " +
218-
$"clock minus a tick latency of {tickLatency}, so it should never be less than that. " +
219-
$"(clock lead {GetClockLeadInTicks(networkManager):F3} ticks, mean buffered {meanBuffered:F3}). " +
220-
$"The render time is being derived from a clock that leads the one state updates are stamped on.");
221-
}
132+
Assert.Greater(samples, 0, $"{nonAuthorityInstance.name} never had a state to interpolate towards!");
133+
134+
var meanTargetLagTicks = totalTargetLagTicks / samples;
135+
var meanBuffered = totalBuffered / (float)samples;
136+
var tickLatency = nonAuthority.NetworkTimeSystem.TickLatency;
137+
138+
// Anything less than the tick latency means the render time came from a clock that leads the one
139+
// the states are stamped on.
140+
Assert.GreaterOrEqual(meanTargetLagTicks, tickLatency,
141+
$"[{m_InterpolationType}] {nonAuthorityInstance.name} was interpolating towards a state sent " +
142+
$"{meanTargetLagTicks:F3} ticks behind the server clock, but the render time is the server " +
143+
$"clock minus a tick latency of {tickLatency}, so it should never be less than that. " +
144+
$"(clock lead {GetClockLeadInTicks(nonAuthority):F3} ticks, mean buffered {meanBuffered:F3})");
222145
}
223146
}
224147
}

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

Lines changed: 12 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -43,56 +43,38 @@ protected override IEnumerator OnTearDown()
4343
return base.OnTearDown();
4444
}
4545

46-
private static float GetExpectedLatencyInSeconds(NetworkManager networkManager)
47-
{
48-
var ticksBehind = networkManager.NetworkTimeSystem.TickLatency + NetworkTransform.InterpolationBufferTickOffset;
49-
return (float)(ticksBehind * networkManager.ServerTime.FixedDeltaTimeAsDouble);
50-
}
51-
5246
[UnityTest]
53-
public IEnumerator GetTickLatencyInSecondsReturnsADuration()
47+
public IEnumerator GetTickLatencyInSecondsReturnsADurationNotATimestamp()
5448
{
55-
var client = m_ClientNetworkManagers[0];
49+
var client = GetNonAuthorityNetworkManager();
50+
var tickInterval = (float)client.ServerTime.FixedDeltaTimeAsDouble;
5651

57-
// Sample repeatedly while the session clock advances. A duration tracks the tick latency, where an
58-
// absolute timestamp would climb by roughly one second per second.
59-
//
60-
// NetworkTimeSystem.TickLatency is adaptive and can legitimately change mid-run, so the value is
61-
// only held to being unchanged across samples where the tick latency itself did not change.
52+
// A timestamp climbs by roughly a second per second, so sample while the session clock advances
53+
// and hold the value to only moving when the tick latency it is derived from moves. That latency
54+
// is adaptive and can legitimately change mid-run.
6255
var previousTicksBehind = -1;
6356
var previousValue = 0f;
6457
for (int i = 0; i < k_Samples; i++)
6558
{
6659
var ticksBehind = client.NetworkTimeSystem.TickLatency + NetworkTransform.InterpolationBufferTickOffset;
67-
var expected = GetExpectedLatencyInSeconds(client);
68-
var actual = NetworkTransform.GetTickLatencyInSeconds(client);
69-
70-
Assert.AreEqual(expected, actual, k_Tolerance,
71-
$"Expected the tick latency to be {expected}s but it was {actual}s.");
60+
var latency = NetworkTransform.GetTickLatencyInSeconds(client);
7261

62+
Assert.Greater(latency, 0f, "A latency of zero or less is not a duration this can be measured against.");
7363
if (ticksBehind == previousTicksBehind)
7464
{
75-
Assert.AreEqual(previousValue, actual, k_Tolerance,
76-
$"The reported latency moved from {previousValue}s to {actual}s while the tick latency " +
65+
Assert.AreEqual(previousValue, latency, k_Tolerance,
66+
$"The reported latency moved from {previousValue}s to {latency}s while the tick latency " +
7767
$"stayed at {ticksBehind} ticks, so it is tracking elapsed time rather than latency.");
7868
}
7969

8070
previousTicksBehind = ticksBehind;
81-
previousValue = actual;
71+
previousValue = latency;
8272
yield return null;
8373
}
84-
}
85-
86-
[UnityTest]
87-
public IEnumerator GetTickLatencyInSecondsTracksTheBufferTickOffset()
88-
{
89-
var client = m_ClientNetworkManagers[0];
90-
var tickInterval = (float)client.ServerTime.FixedDeltaTimeAsDouble;
9174

75+
// Buffering more ticks has to lengthen the reported duration by exactly those ticks.
9276
var latencyBefore = client.NetworkTimeSystem.TickLatency;
9377
var before = NetworkTransform.GetTickLatencyInSeconds(client);
94-
95-
// Buffering more ticks has to lengthen the reported duration by exactly those ticks.
9678
NetworkTransform.InterpolationBufferTickOffset = m_OriginalBufferTickOffset + k_AddedBufferTicks;
9779
yield return null;
9880

0 commit comments

Comments
 (0)