-
Notifications
You must be signed in to change notification settings - Fork 459
Expand file tree
/
Copy pathInterpolationStopAndStartMotionTest.cs
More file actions
285 lines (244 loc) · 12.5 KB
/
InterpolationStopAndStartMotionTest.cs
File metadata and controls
285 lines (244 loc) · 12.5 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using NUnit.Framework;
using Unity.Netcode.Components;
using Unity.Netcode.TestHelpers.Runtime;
using UnityEngine;
using UnityEngine.TestTools;
namespace Unity.Netcode.RuntimeTests
{
[TestFixture(HostOrServer.Host, NetworkTransform.InterpolationTypes.Lerp)]
[TestFixture(HostOrServer.Host, NetworkTransform.InterpolationTypes.SmoothDampening)]
[TestFixture(HostOrServer.DAHost, NetworkTransform.InterpolationTypes.Lerp)]
[TestFixture(HostOrServer.DAHost, NetworkTransform.InterpolationTypes.SmoothDampening)]
internal class InterpolationStopAndStartMotionTest : IntegrationTestWithApproximation
{
protected override int NumberOfClients => 2;
// TODO: [CmbServiceTests] Update the Lerp test to work with the service
protected override bool UseCMBService()
{
return false;
}
private GameObject m_TestPrefab;
private TestStartStopTransform m_AuthorityInstance;
private List<TestStartStopTransform> m_NonAuthorityInstances = new List<TestStartStopTransform>();
private NetworkTransform.InterpolationTypes m_InterpolationType;
private NetworkManager m_AuthorityNetworkManager;
private bool m_IsSecondPass;
private bool m_SecondPassClientsCheckingState;
private int m_NumberOfUpdates;
private Vector3 m_Direction;
public InterpolationStopAndStartMotionTest(HostOrServer hostOrServer, NetworkTransform.InterpolationTypes interpolationType) : base(hostOrServer)
{
m_InterpolationType = interpolationType;
}
protected override void OnServerAndClientsCreated()
{
m_TestPrefab = CreateNetworkObjectPrefab("TestObj");
var testStartStopTransform = m_TestPrefab.AddComponent<TestStartStopTransform>();
testStartStopTransform.PositionInterpolationType = m_InterpolationType;
base.OnServerAndClientsCreated();
}
private bool WaitForInstancesToSpawn()
{
foreach (var networkManager in m_NetworkManagers)
{
if (networkManager == m_AuthorityNetworkManager)
{
continue;
}
if (!networkManager.SpawnManager.SpawnedObjects.ContainsKey(m_AuthorityInstance.NetworkObjectId))
{
return false;
}
}
return true;
}
private bool WaitForInstancesToFinishInterpolation()
{
m_NonAuthorityInstances.Clear();
foreach (var networkManager in m_NetworkManagers)
{
if (networkManager == m_AuthorityNetworkManager)
{
continue;
}
if (!networkManager.SpawnManager.SpawnedObjects.ContainsKey(m_AuthorityInstance.NetworkObjectId))
{
return false;
}
var nonAuthority = networkManager.SpawnManager.SpawnedObjects[m_AuthorityInstance.NetworkObjectId].GetComponent<TestStartStopTransform>();
// Each non-authority instance needs to have reached their final target and reset waiting for the
// object to start moving again.
var positionInterpolator = nonAuthority.GetPositionInterpolator();
if (positionInterpolator.InterpolateState.Target.HasValue)
{
return false;
}
if (!Approximately(nonAuthority.transform.position, m_AuthorityInstance.transform.position))
{
return false;
}
m_NonAuthorityInstances.Add(nonAuthority);
}
return true;
}
[UnityTest]
public IEnumerator StopAndStartMotion()
{
m_IsSecondPass = false;
m_SecondPassClientsCheckingState = false;
m_AuthorityNetworkManager = GetAuthorityNetworkManager();
m_AuthorityInstance = SpawnObject(m_TestPrefab, m_AuthorityNetworkManager).GetComponent<TestStartStopTransform>();
// Wait for all clients to spawn the instance
yield return WaitForConditionOrTimeOut(WaitForInstancesToSpawn);
AssertOnTimeout($"Not all clients spawned {m_AuthorityInstance.name}!");
////// Start Motion
// Have authority move in a direction for a short period of time
m_Direction = GetRandomVector3(-10, 10).normalized;
m_NumberOfUpdates = 0;
m_AuthorityNetworkManager.NetworkTickSystem.Tick += NetworkTickSystem_Tick;
yield return WaitForConditionOrTimeOut(() => m_NumberOfUpdates >= 10);
AssertOnTimeout($"Timed out waiting for all updates to be applied to the authority instance!");
////// Finish interpolating and wait for each interpolator to detect a stop in the motion
// Wait for all non-authority instances to finish interpolating to the final destination point.
yield return WaitForConditionOrTimeOut(WaitForInstancesToFinishInterpolation);
AssertOnTimeout($"Not all clients finished interpolating {m_AuthorityInstance.name}!");
////// Stop to Start motion begins here
m_Direction = GetRandomVector3(-10, 10).normalized;
m_NumberOfUpdates = 0;
m_AuthorityNetworkManager.NetworkTickSystem.Tick += NetworkTickSystem_Tick;
m_IsSecondPass = true;
yield return WaitForConditionOrTimeOut(() => m_NumberOfUpdates >= 10);
AssertOnTimeout($"Timed out waiting for all updates to be applied to the authority instance!");
// Wait for all non-authority instances to finish interpolating to the final destination point.
yield return WaitForConditionOrTimeOut(WaitForInstancesToFinishInterpolation);
AssertOnTimeout($"Not all clients finished interpolating {m_AuthorityInstance.name}!");
// Checks that the time between the first and second state update is approximately the tick frequency
foreach (var testTransform in m_NonAuthorityInstances)
{
var deltaVariance = testTransform.GetTimeDeltaVarience();
Assert.True(Approximately(deltaVariance, s_DefaultWaitForTick.waitTime), $"{testTransform.name}'s delta variance was {deltaVariance} when it should have been approximately {s_DefaultWaitForTick.waitTime}!");
}
}
/// <summary>
/// Moves the authority instance once per tick to simulate a change in transform state that occurs
/// every tick.
/// </summary>
private void NetworkTickSystem_Tick()
{
// In the event a VM is running slower than expected, this counter is used to check
// that we are not invoking the network tick event more than once in a single frame.
// If we are, then exit early since we are just using this to assure that we only
// update the position enough per tick to generate a state update ==and== to prevent
// from incrementing the m_NumberOfUpdates more than once per tick for the frame that
// the tick event was invoked.
if (TestStartStopTransform.TickInvocationCounter >= 1)
{
return;
}
// Start tracking state on a network tick
if (m_IsSecondPass && !m_SecondPassClientsCheckingState)
{
// Start recording the state updates on the non-authority instances
foreach (var testTransform in m_NonAuthorityInstances)
{
testTransform.CheckStateUpdates = true;
}
m_SecondPassClientsCheckingState = true;
}
m_NumberOfUpdates++;
m_AuthorityInstance.transform.position = m_AuthorityInstance.transform.position + m_Direction * 2;
if (m_NumberOfUpdates >= 10)
{
m_AuthorityNetworkManager.NetworkTickSystem.Tick -= NetworkTickSystem_Tick;
}
TestStartStopTransform.TickInvocationCounter++;
}
internal class TestStartStopTransform : NetworkTransform, INetworkUpdateSystem
{
// In the event a VM is running slower than expected, this counter is used to check
// that we are not invoking the network tick event more than once in a single frame.
// If we are, then within the above integration test it will not continue to update
// and increment the counter prematurely.
public static int TickInvocationCounter = 0;
public bool CheckStateUpdates;
private BufferedLinearInterpolatorVector3 m_PosInterpolator;
private Dictionary<int, StateEntry> m_StatesProcessed = new Dictionary<int, StateEntry>();
public struct StateEntry
{
public float TimeAdded;
public BufferedLinearInterpolator<Vector3>.CurrentState State;
}
protected override void Awake()
{
base.Awake();
m_PosInterpolator = GetPositionInterpolator();
}
protected override void OnNetworkPostSpawn()
{
if (CanCommitToTransform)
{
NetworkUpdateLoop.RegisterNetworkUpdate(this, NetworkUpdateStage.EarlyUpdate);
}
base.OnNetworkPostSpawn();
}
public void NetworkUpdate(NetworkUpdateStage updateStage)
{
if (updateStage == NetworkUpdateStage.EarlyUpdate)
{
// Each new frame, we reset this counter.
TickInvocationCounter = 0;
}
}
public override void OnNetworkDespawn()
{
NetworkUpdateLoop.UnregisterAllNetworkUpdates(this);
base.OnNetworkDespawn();
}
/// <summary>
/// Checks the time that passed between the first and second state updates.
/// </summary>
/// <returns>time passed as a float</returns>
public float GetTimeDeltaVarience()
{
var stateKeys = m_StatesProcessed.Keys.ToList();
var firstState = m_StatesProcessed[stateKeys[0]];
var secondState = m_StatesProcessed[stateKeys[1]];
var firstAndSecondTimeDelta = secondState.TimeAdded - firstState.TimeAdded;
// Get the delta time between the two times of both the first and second state.
// Then add the time it should have taken to get to the second state, and this should be the total time to interpolate
// from the current position to the target position of the second state update.
var stateDelta = (float)(secondState.State.Target.Value.TimeSent - firstState.State.Target.Value.TimeSent + secondState.State.TimeToTargetValue);
// Return the time detla between the time that passed and the time that should have passed processing the states.
return Mathf.Abs(stateDelta - firstAndSecondTimeDelta);
}
public override void OnUpdate()
{
base.OnUpdate();
// If we are checking the state updates, then we want to track each unique state update
if (CheckStateUpdates)
{
// Make sure we have a valid target
if (m_PosInterpolator.InterpolateState.Target.HasValue)
{
// If the state update's identifier is different
var itemId = m_PosInterpolator.InterpolateState.Target.Value.ItemId;
if (!m_StatesProcessed.ContainsKey(itemId))
{
// Add it to the table of state updates
var stateEntry = new StateEntry()
{
// Use the server time to get the valid "relative" time since the session started.
TimeAdded = NetworkManager.ServerTime.TimeAsFloat,
State = m_PosInterpolator.InterpolateState,
};
m_StatesProcessed.Add(itemId, stateEntry);
}
}
}
}
}
}
}