Skip to content

Commit d08d496

Browse files
test
Adding new test for attachables.
1 parent 1832d21 commit d08d496

File tree

2 files changed

+383
-0
lines changed

2 files changed

+383
-0
lines changed
Lines changed: 381 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,381 @@
1+
using System.Collections;
2+
using System.Collections.Generic;
3+
using System.Text;
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)]
13+
[TestFixture(HostOrServer.Server)]
14+
[TestFixture(HostOrServer.DAHost)]
15+
internal class AttachableBehaviourTests : NetcodeIntegrationTest
16+
{
17+
protected override int NumberOfClients => 2;
18+
19+
public AttachableBehaviourTests(HostOrServer hostOrServer) : base(hostOrServer) { }
20+
21+
private GameObject m_SourcePrefab;
22+
private GameObject m_TargetPrefabA;
23+
private GameObject m_TargetPrefabB;
24+
25+
/// <summary>
26+
/// All of the below instances belong to the authority
27+
/// </summary>
28+
private NetworkObject m_SourceInstance;
29+
private NetworkObject m_TargetInstance;
30+
private NetworkObject m_TargetInstanceB;
31+
private TestAttachable m_AttachableBehaviourInstance;
32+
private TestNode m_AttachableNodeInstance;
33+
private TestNode m_AttachableNodeInstanceB;
34+
35+
private bool m_UseTargetB;
36+
37+
private StringBuilder m_ErrorLog = new StringBuilder();
38+
39+
protected override IEnumerator OnSetup()
40+
{
41+
m_ErrorLog.Clear();
42+
return base.OnSetup();
43+
}
44+
45+
protected override void OnServerAndClientsCreated()
46+
{
47+
// The source prefab contains the nested NetworkBehaviour that
48+
// will be parented under the target prefab.
49+
m_SourcePrefab = CreateNetworkObjectPrefab("Source");
50+
// The target prefab that the source prefab will attach
51+
// will be parented under the target prefab.
52+
m_TargetPrefabA = CreateNetworkObjectPrefab("TargetA");
53+
m_TargetPrefabB = CreateNetworkObjectPrefab("TargetB");
54+
var sourceChild = new GameObject("SourceChild");
55+
var targetChildA = new GameObject("TargetChildA");
56+
var targetChildB = new GameObject("TargetChildB");
57+
sourceChild.transform.parent = m_SourcePrefab.transform;
58+
targetChildA.transform.parent = m_TargetPrefabA.transform;
59+
targetChildB.transform.parent = m_TargetPrefabB.transform;
60+
61+
sourceChild.AddComponent<TestAttachable>();
62+
targetChildA.AddComponent<TestNode>();
63+
targetChildB.AddComponent<TestNode>();
64+
base.OnServerAndClientsCreated();
65+
}
66+
67+
private NetworkObject GetTargetInstance()
68+
{
69+
return m_UseTargetB ? m_TargetInstanceB : m_TargetInstance;
70+
}
71+
72+
private bool AllClientsSpawnedInstances()
73+
{
74+
m_ErrorLog.Clear();
75+
foreach (var networkManager in m_NetworkManagers)
76+
{
77+
if (!networkManager.SpawnManager.SpawnedObjects.ContainsKey(m_SourceInstance.NetworkObjectId))
78+
{
79+
m_ErrorLog.AppendLine($"[Client-{networkManager.LocalClientId}] Has not spawned {m_SourceInstance.name} yet!");
80+
}
81+
if (!networkManager.SpawnManager.SpawnedObjects.ContainsKey(m_TargetInstance.NetworkObjectId))
82+
{
83+
m_ErrorLog.AppendLine($"[Client-{networkManager.LocalClientId}] Has not spawned {m_TargetInstance.name} yet!");
84+
}
85+
if (!networkManager.SpawnManager.SpawnedObjects.ContainsKey(m_TargetInstanceB.NetworkObjectId))
86+
{
87+
m_ErrorLog.AppendLine($"[Client-{networkManager.LocalClientId}] Has not spawned {m_TargetInstanceB.name} yet!");
88+
}
89+
}
90+
return m_ErrorLog.Length == 0;
91+
}
92+
93+
private bool ResetAllStates()
94+
{
95+
m_ErrorLog.Clear();
96+
var target = GetTargetInstance();
97+
// The attachable can move between the two spawned instances.
98+
var currentAttachableRoot = m_AttachableBehaviourInstance.State == AttachableBehaviour.AttachState.Attached ? target : m_SourceInstance;
99+
foreach (var networkManager in m_NetworkManagers)
100+
{
101+
// Source
102+
if (!networkManager.SpawnManager.SpawnedObjects.ContainsKey(m_SourceInstance.NetworkObjectId))
103+
{
104+
m_ErrorLog.AppendLine($"[Client-{networkManager.LocalClientId}] Has no spawned instance of {currentAttachableRoot.name}!");
105+
}
106+
else
107+
{
108+
var attachable = networkManager.SpawnManager.SpawnedObjects[currentAttachableRoot.NetworkObjectId].GetComponentInChildren<TestAttachable>();
109+
attachable.ResetStates();
110+
}
111+
112+
// Target
113+
if (!networkManager.SpawnManager.SpawnedObjects.ContainsKey(m_TargetInstance.NetworkObjectId))
114+
{
115+
m_ErrorLog.AppendLine($"[Client-{networkManager.LocalClientId}] Has no spawned instance of {m_TargetInstance.name}!");
116+
}
117+
else
118+
{
119+
var node = networkManager.SpawnManager.SpawnedObjects[m_TargetInstance.NetworkObjectId].GetComponentInChildren<TestNode>();
120+
node.ResetStates();
121+
}
122+
123+
// Target B
124+
if (!networkManager.SpawnManager.SpawnedObjects.ContainsKey(m_TargetInstanceB.NetworkObjectId))
125+
{
126+
m_ErrorLog.AppendLine($"[Client-{networkManager.LocalClientId}] Has no spawned instance of {m_TargetInstanceB.name}!");
127+
}
128+
else
129+
{
130+
var node = networkManager.SpawnManager.SpawnedObjects[m_TargetInstanceB.NetworkObjectId].GetComponentInChildren<TestNode>();
131+
node.ResetStates();
132+
}
133+
}
134+
return m_ErrorLog.Length == 0;
135+
}
136+
137+
private bool AllInstancesAttachedStateChanged(bool checkAttached)
138+
{
139+
m_ErrorLog.Clear();
140+
var target = GetTargetInstance();
141+
// The attachable can move between the two spawned instances so we have to use the appropriate one depending upon the authority's current state.
142+
var currentAttachableRoot = m_AttachableBehaviourInstance.State == AttachableBehaviour.AttachState.Attached ? target : m_SourceInstance;
143+
var attachable = (TestAttachable)null;
144+
var node = (TestNode)null;
145+
foreach (var networkManager in m_NetworkManagers)
146+
{
147+
if (!networkManager.SpawnManager.SpawnedObjects.ContainsKey(currentAttachableRoot.NetworkObjectId))
148+
{
149+
m_ErrorLog.AppendLine($"[Client-{networkManager.LocalClientId}] Has no spawned instance of {currentAttachableRoot.name}!");
150+
continue;
151+
}
152+
else
153+
{
154+
attachable = networkManager.SpawnManager.SpawnedObjects[currentAttachableRoot.NetworkObjectId].GetComponentInChildren<TestAttachable>();
155+
}
156+
157+
if (!attachable)
158+
{
159+
attachable = networkManager.SpawnManager.SpawnedObjects[m_TargetInstance.NetworkObjectId].GetComponentInChildren<TestAttachable>();
160+
if (!attachable)
161+
{
162+
attachable = networkManager.SpawnManager.SpawnedObjects[m_TargetInstanceB.NetworkObjectId].GetComponentInChildren<TestAttachable>();
163+
if (!attachable)
164+
{
165+
m_ErrorLog.AppendLine($"[Client-{networkManager.LocalClientId}][Attachable] Attachable was not found!");
166+
}
167+
}
168+
continue;
169+
}
170+
171+
if (!networkManager.SpawnManager.SpawnedObjects.ContainsKey(target.NetworkObjectId))
172+
{
173+
m_ErrorLog.AppendLine($"[Client-{networkManager.LocalClientId}] Has no spawned instance of {target.name}!");
174+
continue;
175+
}
176+
else
177+
{
178+
node = networkManager.SpawnManager.SpawnedObjects[target.NetworkObjectId].GetComponentInChildren<TestNode>();
179+
}
180+
181+
if (!attachable.CheckStateChangedOverride(checkAttached, false, node))
182+
{
183+
m_ErrorLog.AppendLine($"[Client-{networkManager.LocalClientId}][{attachable.name}] Did not have its override invoked!");
184+
}
185+
if (!attachable.CheckStateChangedOverride(checkAttached, true, node))
186+
{
187+
m_ErrorLog.AppendLine($"[Client-{networkManager.LocalClientId}][{attachable.name}] Did not have its event invoked!");
188+
}
189+
if ((checkAttached && !node.OnAttachedInvoked) || (!checkAttached && !node.OnDetachedInvoked))
190+
{
191+
m_ErrorLog.AppendLine($"[Client-{networkManager.LocalClientId}][{node.name}] Did not have its override invoked!");
192+
}
193+
if (checkAttached && attachable.transform.parent != node.transform)
194+
{
195+
m_ErrorLog.AppendLine($"[Client-{networkManager.LocalClientId}][{attachable.name}] {node.name} is not the parent of {attachable.name}!");
196+
}
197+
else if (!checkAttached && attachable.transform.parent != attachable.DefaultParent.transform)
198+
{
199+
m_ErrorLog.AppendLine($"[Client-{networkManager.LocalClientId}][{attachable.name}] {attachable.DefaultParent.name} is not the parent of {attachable.name}!");
200+
}
201+
}
202+
return m_ErrorLog.Length == 0;
203+
}
204+
205+
[UnityTest]
206+
public IEnumerator AttachAndDetachTests()
207+
{
208+
var authority = GetAuthorityNetworkManager();
209+
m_SourceInstance = SpawnObject(m_SourcePrefab, authority).GetComponent<NetworkObject>();
210+
m_TargetInstance = SpawnObject(m_TargetPrefabA, authority).GetComponent<NetworkObject>();
211+
m_TargetInstanceB = SpawnObject(m_TargetPrefabB, authority).GetComponent<NetworkObject>();
212+
yield return WaitForConditionOrTimeOut(AllClientsSpawnedInstances);
213+
AssertOnTimeout($"Timed out waiting for all clients to spawn {m_SourceInstance.name} and {m_TargetInstance.name}!\n {m_ErrorLog}");
214+
215+
m_AttachableBehaviourInstance = m_SourceInstance.GetComponentInChildren<TestAttachable>();
216+
Assert.NotNull(m_AttachableBehaviourInstance, $"{m_SourceInstance.name} does not have a nested child {nameof(AttachableBehaviour)}!");
217+
218+
m_AttachableNodeInstance = m_TargetInstance.GetComponentInChildren<TestNode>();
219+
Assert.NotNull(m_AttachableNodeInstance, $"{m_TargetInstance.name} does not have a nested child {nameof(AttachableNode)}!");
220+
221+
m_AttachableNodeInstanceB = m_TargetInstanceB.GetComponentInChildren<TestNode>();
222+
Assert.NotNull(m_AttachableNodeInstanceB, $"{m_TargetInstanceB.name} does not have a nested child {nameof(AttachableNode)}!");
223+
224+
Assert.True(ResetAllStates(), $"Failed to reset all states!\n {m_ErrorLog}");
225+
m_AttachableBehaviourInstance.Attach(m_AttachableNodeInstance);
226+
227+
yield return WaitForConditionOrTimeOut(() => AllInstancesAttachedStateChanged(true));
228+
AssertOnTimeout($"Timed out waiting for all clients to attach {m_AttachableBehaviourInstance.name} to {m_AttachableNodeInstance.name}!\n {m_ErrorLog}");
229+
230+
// Wait a brief period of time
231+
yield return s_DefaultWaitForTick;
232+
233+
// Now late join a client to make sure it synchronizes properly
234+
yield return CreateAndStartNewClient();
235+
yield return WaitForConditionOrTimeOut(() => AllInstancesAttachedStateChanged(true));
236+
AssertOnTimeout($"Timed out waiting for all clients to attach {m_AttachableBehaviourInstance.name} to {m_AttachableNodeInstance.name}!\n {m_ErrorLog}");
237+
238+
// Wait a brief period of time
239+
yield return s_DefaultWaitForTick;
240+
241+
// Reset all states and prepare for 2nd attach test
242+
Assert.True(ResetAllStates(), $"Failed to reset all states!\n {m_ErrorLog}");
243+
244+
// Now, while attached, attach to another attachable node which should detach from the current and attach to the new.
245+
m_AttachableBehaviourInstance.Attach(m_AttachableNodeInstanceB);
246+
247+
// The attachable should detach from the current AttachableNode first
248+
yield return WaitForConditionOrTimeOut(() => AllInstancesAttachedStateChanged(false));
249+
AssertOnTimeout($"Timed out waiting for all clients to detach {m_AttachableBehaviourInstance.name} from {m_AttachableNodeInstance.name}!\n {m_ErrorLog}");
250+
251+
// Switch the conditional to check the target B attachable node
252+
m_UseTargetB = true;
253+
254+
// Then the attachable should attach to the target B attachable node
255+
yield return WaitForConditionOrTimeOut(() => AllInstancesAttachedStateChanged(true));
256+
AssertOnTimeout($"Timed out waiting for all clients to attach {m_AttachableBehaviourInstance.name} to {m_AttachableNodeInstanceB.name}!\n {m_ErrorLog}");
257+
258+
// Reset all states and prepare for final detach test
259+
Assert.True(ResetAllStates(), $"Failed to reset all states!\n {m_ErrorLog}");
260+
261+
// Now verify complete detaching works
262+
m_AttachableBehaviourInstance.Detach();
263+
yield return WaitForConditionOrTimeOut(() => AllInstancesAttachedStateChanged(false));
264+
AssertOnTimeout($"Timed out waiting for all clients to detach {m_AttachableBehaviourInstance.name} from {m_AttachableNodeInstance.name}!\n {m_ErrorLog}");
265+
}
266+
267+
/// <summary>
268+
/// Helps to validate that the overrides and events are invoked when an attachable attaches or detaches from the instance.
269+
/// This also helps to validate that the appropriate <see cref="AttachableNode"/> instance is passed in as a parameter.
270+
/// </summary>
271+
public class TestAttachable : AttachableBehaviour
272+
{
273+
private Dictionary<AttachState, AttachableNode> m_StateUpdates = new Dictionary<AttachState, AttachableNode>();
274+
275+
private Dictionary<AttachState, AttachableNode> m_StateUpdateEvents = new Dictionary<AttachState, AttachableNode>();
276+
277+
public GameObject DefaultParent => m_DefaultParent;
278+
public AttachState State => m_AttachState;
279+
280+
public override void OnNetworkSpawn()
281+
{
282+
AttachStateChange += OnAttachStateChangeEvent;
283+
name = $"{name}-{NetworkManager.LocalClientId}";
284+
base.OnNetworkSpawn();
285+
}
286+
287+
public override void OnNetworkDespawn()
288+
{
289+
AttachStateChange -= OnAttachStateChangeEvent;
290+
base.OnNetworkDespawn();
291+
}
292+
293+
private void OnAttachStateChangeEvent(AttachState attachState, AttachableNode attachableNode)
294+
{
295+
m_StateUpdateEvents.Add(attachState, attachableNode);
296+
}
297+
298+
protected override void OnAttachStateChanged(AttachState attachState, AttachableNode attachableNode)
299+
{
300+
m_StateUpdates.Add(attachState, attachableNode);
301+
base.OnAttachStateChanged(attachState, attachableNode);
302+
}
303+
304+
public void ResetStates()
305+
{
306+
m_StateUpdates.Clear();
307+
m_StateUpdateEvents.Clear();
308+
}
309+
310+
private void Log(string message)
311+
{
312+
Debug.Log($"[{name}] {message}");
313+
}
314+
315+
public bool CheckStateChangedOverride(bool checkAttached, bool checkEvent, AttachableNode attachableNode)
316+
{
317+
var tableToCheck = checkEvent ? m_StateUpdateEvents : m_StateUpdates;
318+
var checkStatus = checkAttached ? (tableToCheck.ContainsKey(AttachState.Attaching) && tableToCheck.ContainsKey(AttachState.Attached)) :
319+
(tableToCheck.ContainsKey(AttachState.Detaching) && tableToCheck.ContainsKey(AttachState.Detached));
320+
321+
if (checkStatus)
322+
{
323+
foreach (var entry in tableToCheck)
324+
{
325+
// Ignore any states that don't match what is being checked
326+
if ((checkStatus && (entry.Key == AttachState.Detaching || entry.Key == AttachState.Detached)) ||
327+
(!checkStatus && (entry.Key == AttachState.Attaching || entry.Key == AttachState.Attached)))
328+
{
329+
continue;
330+
}
331+
332+
// Special case for completely detached
333+
if (entry.Key == AttachState.Detached)
334+
{
335+
if (entry.Value != null)
336+
{
337+
Log($"[Value] The value {entry.Value.name} is not null!");
338+
checkStatus = false;
339+
break;
340+
}
341+
}
342+
else if (entry.Value != attachableNode)
343+
{
344+
Log($"[{entry.Key}][Value] The value {entry.Value.name} is not the same as {attachableNode.name}!");
345+
checkStatus = false;
346+
break;
347+
}
348+
}
349+
}
350+
return checkStatus;
351+
}
352+
}
353+
354+
/// <summary>
355+
/// Helps to validate that the overrides are invoked when an attachable attaches or detaches from the instance.
356+
/// </summary>
357+
public class TestNode : AttachableNode
358+
{
359+
public bool OnAttachedInvoked { get; private set; }
360+
public bool OnDetachedInvoked { get; private set; }
361+
362+
public void ResetStates()
363+
{
364+
OnAttachedInvoked = false;
365+
OnDetachedInvoked = false;
366+
}
367+
368+
protected override void OnAttached(AttachableBehaviour attachableBehaviour)
369+
{
370+
OnAttachedInvoked = true;
371+
base.OnAttached(attachableBehaviour);
372+
}
373+
374+
protected override void OnDetached(AttachableBehaviour attachableBehaviour)
375+
{
376+
OnDetachedInvoked = true;
377+
base.OnDetached(attachableBehaviour);
378+
}
379+
}
380+
}
381+
}

com.unity.netcode.gameobjects/Tests/Runtime/AttachableBehaviourTests.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)