Skip to content

Commit d56c661

Browse files
test
Adding the test to validate the fix for an object in motion coming to a rest where the interpolator has detected no additional state updates, reset itself, and then the object starts moving again (for longer than 3-10x the tick frequency) then the time between the first new state update and the second state update should not exceed the given tick frequency if both state updates happened sequentially (tick relative).
1 parent e7c859c commit d56c661

File tree

2 files changed

+237
-0
lines changed

2 files changed

+237
-0
lines changed
Lines changed: 235 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,235 @@
1+
using System.Collections;
2+
using System.Collections.Generic;
3+
using System.Linq;
4+
using NUnit.Framework;
5+
using Unity.Netcode.Components;
6+
using Unity.Netcode.TestHelpers.Runtime;
7+
using UnityEngine;
8+
using UnityEngine.TestTools;
9+
10+
namespace Unity.Netcode.RuntimeTests
11+
{
12+
[TestFixture(HostOrServer.Host, NetworkTransform.InterpolationTypes.Lerp)]
13+
[TestFixture(HostOrServer.Host, NetworkTransform.InterpolationTypes.SmoothDampening)]
14+
[TestFixture(HostOrServer.DAHost, NetworkTransform.InterpolationTypes.Lerp)]
15+
[TestFixture(HostOrServer.DAHost, NetworkTransform.InterpolationTypes.SmoothDampening)]
16+
internal class InterpolationStopAndStartMotionTest : IntegrationTestWithApproximation
17+
{
18+
protected override int NumberOfClients => 2;
19+
20+
private GameObject m_TestPrefab;
21+
22+
private TestStartStopTransform m_AuthorityInstance;
23+
private List<TestStartStopTransform> m_NonAuthorityInstances = new List<TestStartStopTransform>();
24+
25+
private NetworkTransform.InterpolationTypes m_InterpolationType;
26+
private List<NetworkManager> m_NetworkManagers = new List<NetworkManager>();
27+
private NetworkManager m_AuthorityNetworkManager;
28+
29+
private int m_NumberOfUpdates;
30+
private Vector3 m_Direction;
31+
32+
public InterpolationStopAndStartMotionTest(HostOrServer hostOrServer, NetworkTransform.InterpolationTypes interpolationType) : base(hostOrServer)
33+
{
34+
m_InterpolationType = interpolationType;
35+
}
36+
37+
protected override void OnServerAndClientsCreated()
38+
{
39+
m_TestPrefab = CreateNetworkObjectPrefab("TestObj");
40+
var testStartStopTransform = m_TestPrefab.AddComponent<TestStartStopTransform>();
41+
testStartStopTransform.PositionInterpolationType = m_InterpolationType;
42+
base.OnServerAndClientsCreated();
43+
}
44+
45+
private bool WaitForInstancesToSpawn()
46+
{
47+
foreach (var networkManager in m_NetworkManagers)
48+
{
49+
if (networkManager == m_AuthorityNetworkManager)
50+
{
51+
continue;
52+
}
53+
54+
if (!networkManager.SpawnManager.SpawnedObjects.ContainsKey(m_AuthorityInstance.NetworkObjectId))
55+
{
56+
return false;
57+
}
58+
}
59+
return true;
60+
}
61+
62+
private bool WaitForInstancesToFinishInterpolation()
63+
{
64+
m_NonAuthorityInstances.Clear();
65+
foreach (var networkManager in m_NetworkManagers)
66+
{
67+
if (networkManager == m_AuthorityNetworkManager)
68+
{
69+
continue;
70+
}
71+
72+
if (!networkManager.SpawnManager.SpawnedObjects.ContainsKey(m_AuthorityInstance.NetworkObjectId))
73+
{
74+
return false;
75+
}
76+
77+
var nonAuthority = networkManager.SpawnManager.SpawnedObjects[m_AuthorityInstance.NetworkObjectId].GetComponent<TestStartStopTransform>();
78+
79+
// Each non-authority instance needs to have reached their final target and reset waiting for the
80+
// object to start moving again.
81+
var positionInterpolator = nonAuthority.GetPositionInterpolator();
82+
if (positionInterpolator.InterpolateState.Target.HasValue)
83+
{
84+
return false;
85+
}
86+
87+
if (!Approximately(nonAuthority.transform.position, m_AuthorityInstance.transform.position))
88+
{
89+
return false;
90+
}
91+
92+
m_NonAuthorityInstances.Add(nonAuthority);
93+
}
94+
return true;
95+
}
96+
97+
[UnityTest]
98+
public IEnumerator StopAndStartMotion()
99+
{
100+
m_NetworkManagers.AddRange(m_ClientNetworkManagers);
101+
if (!UseCMBService())
102+
{
103+
m_NetworkManagers.Insert(0, m_ServerNetworkManager);
104+
}
105+
m_AuthorityNetworkManager = m_NetworkManagers[0];
106+
107+
m_AuthorityInstance = SpawnObject(m_TestPrefab, m_AuthorityNetworkManager).GetComponent<TestStartStopTransform>();
108+
// Wait for all clients to spawn the instance
109+
yield return WaitForConditionOrTimeOut(WaitForInstancesToSpawn);
110+
AssertOnTimeout($"Not all clients spawned {m_AuthorityInstance.name}!");
111+
112+
////// Start Motion
113+
// Have authority move in a direction for a short period of time
114+
m_Direction = GetRandomVector3(-10, 10).normalized;
115+
m_NumberOfUpdates = 0;
116+
m_AuthorityNetworkManager.NetworkTickSystem.Tick += NetworkTickSystem_Tick;
117+
118+
yield return WaitForConditionOrTimeOut(() => m_NumberOfUpdates >= 10);
119+
AssertOnTimeout($"Timed out waiting for all updates to be applied to the authority instance!");
120+
121+
////// Finish interpolating and wait for each interpolator to detect a stop in the motion
122+
// Wait for all non-authority instances to finish interpolating to the final destination point.
123+
yield return WaitForConditionOrTimeOut(WaitForInstancesToFinishInterpolation);
124+
AssertOnTimeout($"Not all clients finished interpolating {m_AuthorityInstance.name}!");
125+
126+
// Start recording the state updates on the non-authority instances
127+
foreach (var testTransform in m_NonAuthorityInstances)
128+
{
129+
testTransform.CheckStateUpdates = true;
130+
}
131+
132+
////// Stop to Start motion begins here
133+
m_Direction = GetRandomVector3(-10, 10).normalized;
134+
m_NumberOfUpdates = 0;
135+
m_AuthorityNetworkManager.NetworkTickSystem.Tick += NetworkTickSystem_Tick;
136+
137+
yield return WaitForConditionOrTimeOut(() => m_NumberOfUpdates >= 10);
138+
AssertOnTimeout($"Timed out waiting for all updates to be applied to the authority instance!");
139+
140+
// Wait for all non-authority instances to finish interpolating to the final destination point.
141+
yield return WaitForConditionOrTimeOut(WaitForInstancesToFinishInterpolation);
142+
AssertOnTimeout($"Not all clients finished interpolating {m_AuthorityInstance.name}!");
143+
144+
// Checks that the time between the first and second state update is approximately the tick frequency
145+
foreach (var testTransform in m_NonAuthorityInstances)
146+
{
147+
var deltaVariance = testTransform.GetTimeDeltaVarience();
148+
Assert.True(Approximately(deltaVariance, s_DefaultWaitForTick.waitTime), $"{testTransform.name}'s delta variance was {deltaVariance} when it should have been approximately {s_DefaultWaitForTick.waitTime}!");
149+
}
150+
}
151+
152+
/// <summary>
153+
/// Moves the authority instance once per tick to simulate a change in transform state that occurs
154+
/// every tick.
155+
/// </summary>
156+
private void NetworkTickSystem_Tick()
157+
{
158+
m_NumberOfUpdates++;
159+
m_AuthorityInstance.transform.position = m_AuthorityInstance.transform.position + m_Direction * 2;
160+
if (m_NumberOfUpdates >= 10)
161+
{
162+
m_AuthorityNetworkManager.NetworkTickSystem.Tick -= NetworkTickSystem_Tick;
163+
}
164+
}
165+
166+
internal class TestStartStopTransform : NetworkTransform
167+
{
168+
169+
public bool CheckStateUpdates;
170+
171+
private BufferedLinearInterpolatorVector3 m_PosInterpolator;
172+
173+
private Dictionary<int, StateEntry> m_StatesProcessed = new Dictionary<int, StateEntry>();
174+
175+
public struct StateEntry
176+
{
177+
public float TimeAdded;
178+
public BufferedLinearInterpolator<Vector3>.CurrentState State;
179+
}
180+
181+
protected override void Awake()
182+
{
183+
base.Awake();
184+
m_PosInterpolator = GetPositionInterpolator();
185+
}
186+
187+
/// <summary>
188+
/// Checks the time that passed between the first and second state updates.
189+
/// </summary>
190+
/// <returns>time passed as a float</returns>
191+
public float GetTimeDeltaVarience()
192+
{
193+
var stateKeys = m_StatesProcessed.Keys.ToList();
194+
var firstState = m_StatesProcessed[stateKeys[0]];
195+
var secondState = m_StatesProcessed[stateKeys[1]];
196+
197+
var firstAndSecondTimeDelta = secondState.TimeAdded - firstState.TimeAdded;
198+
199+
// Get the delta time between the two times of both the first and second state.
200+
// Then add the time it should have taken to get to the second state, and this should be the total time to interpolate
201+
// from the current position to the target position of the second state update.
202+
var stateDelta = (float)(secondState.State.Target.Value.TimeSent - firstState.State.Target.Value.TimeSent + secondState.State.TimeToTargetValue);
203+
// Return the time detla between the time that passed and the time that should have passed processing the states.
204+
return Mathf.Abs(stateDelta - firstAndSecondTimeDelta);
205+
}
206+
207+
public override void OnUpdate()
208+
{
209+
base.OnUpdate();
210+
211+
// If we are checking the state updates, then we want to track each unique state update
212+
if (CheckStateUpdates)
213+
{
214+
// Make sure we have a valid target
215+
if (m_PosInterpolator.InterpolateState.Target.HasValue)
216+
{
217+
// If the state update's identifier is different
218+
var itemId = m_PosInterpolator.InterpolateState.Target.Value.ItemId;
219+
if (!m_StatesProcessed.ContainsKey(itemId))
220+
{
221+
// Add it to the table of state updates
222+
var stateEntry = new StateEntry()
223+
{
224+
TimeAdded = Time.realtimeSinceStartup,
225+
State = m_PosInterpolator.InterpolateState,
226+
};
227+
228+
m_StatesProcessed.Add(itemId, stateEntry);
229+
}
230+
}
231+
}
232+
}
233+
}
234+
}
235+
}

com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/InterpolationStopAndStartMotionTest.cs.meta

Lines changed: 2 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)