Skip to content

Commit 6db25f0

Browse files
test
The integration test for this fix with some integration test adjustments.
1 parent d7732f2 commit 6db25f0

File tree

3 files changed

+217
-4
lines changed

3 files changed

+217
-4
lines changed

com.unity.netcode.gameobjects/Tests/Runtime/TestHelpers/IntegrationTestSceneHandler.cs

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -729,6 +729,7 @@ public void MoveObjectsFromSceneToDontDestroyOnLoad(ref NetworkManager networkMa
729729
#else
730730
var networkObjects = Object.FindObjectsOfType<NetworkObject>().Where((c) => c.IsSpawned);
731731
#endif
732+
var distributedAuthority = networkManager.DistributedAuthorityMode;
732733
foreach (var networkObject in networkObjects)
733734
{
734735
if (networkObject == null || (networkObject != null && networkObject.gameObject.scene.handle != scene.handle))
@@ -759,6 +760,12 @@ public void MoveObjectsFromSceneToDontDestroyOnLoad(ref NetworkManager networkMa
759760
continue;
760761
}
761762

763+
// Check to determine if we need to allow destroying a non-authority instance
764+
if (distributedAuthority && networkObject.DestroyWithScene && !networkObject.HasAuthority)
765+
{
766+
networkObject.DestroyPendingSceneEvent = true;
767+
}
768+
762769
// Only NetworkObjects marked to not be destroyed with the scene and are not already in the DDOL are preserved
763770
if (!networkObject.DestroyWithScene && networkObject.gameObject.scene != networkManager.SceneManager.DontDestroyOnLoadScene)
764771
{
@@ -769,17 +776,24 @@ public void MoveObjectsFromSceneToDontDestroyOnLoad(ref NetworkManager networkMa
769776
Object.DontDestroyOnLoad(networkObject.gameObject);
770777
}
771778
}
772-
else if (networkManager.IsServer)
779+
else
773780
{
774-
if (networkObject.NetworkManager == networkManager)
781+
if (networkObject.HasAuthority && networkObject.NetworkManager == networkManager)
775782
{
776783
VerboseDebug($"[MoveObjects from {scene.name} | {scene.handle}] Destroying {networkObject.gameObject.name} because it is in scene {networkObject.gameObject.scene.name} with DWS = {networkObject.DestroyWithScene}.");
777784
networkObject.Despawn();
778785
}
779786
else //For integration testing purposes, migrate remaining into DDOL
780787
{
781-
VerboseDebug($"[MoveObjects from {scene.name} | {scene.handle}] Temporarily migrating {networkObject.gameObject.name} into DDOL to await server destroy message.");
782-
Object.DontDestroyOnLoad(networkObject.gameObject);
788+
if (networkObject.DestroyPendingSceneEvent)
789+
{
790+
Object.Destroy(networkObject.gameObject);
791+
}
792+
else
793+
{
794+
VerboseDebug($"[MoveObjects from {scene.name} | {scene.handle}] Temporarily migrating {networkObject.gameObject.name} into DDOL to await server destroy message.");
795+
Object.DontDestroyOnLoad(networkObject.gameObject);
796+
}
783797
}
784798
}
785799
}
Lines changed: 197 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,197 @@
1+
using System.Collections;
2+
using System.Text;
3+
using NUnit.Framework;
4+
using Unity.Netcode;
5+
using Unity.Netcode.TestHelpers.Runtime;
6+
using UnityEngine;
7+
using UnityEngine.SceneManagement;
8+
using UnityEngine.TestTools;
9+
10+
namespace TestProject.RuntimeTests
11+
{
12+
[TestFixture(HostOrServer.DAHost)]
13+
[TestFixture(HostOrServer.Host)]
14+
internal class NetworkObjectDestroyWithSceneTests : NetcodeIntegrationTest
15+
{
16+
private const string k_SceneToLoad = "EmptyScene";
17+
protected override int NumberOfClients => 2;
18+
19+
private bool m_SceneHasLoaded;
20+
private Scene m_SceneLoaded;
21+
private Scene m_NotSessionOwnerScene;
22+
private NetworkManager m_SessionOwner;
23+
private NetworkManager m_NotSessionOwner;
24+
private NetworkObject m_SpawnedInstance;
25+
private StringBuilder m_ErrorLog = new StringBuilder();
26+
private NetworkObject m_TestPrefab;
27+
28+
public NetworkObjectDestroyWithSceneTests(HostOrServer hostOrServer) : base(hostOrServer) { }
29+
30+
protected override void OnServerAndClientsCreated()
31+
{
32+
m_TestPrefab = CreateNetworkObjectPrefab("TestObject").GetComponent<NetworkObject>();
33+
m_TestPrefab.DestroyWithScene = true;
34+
m_TestPrefab.SceneMigrationSynchronization = true;
35+
base.OnServerAndClientsCreated();
36+
}
37+
38+
/// <summary>
39+
/// Conditional that determines if all <see cref="NetworkManager"/> instances have
40+
/// spawned or despawned the instance.
41+
/// </summary>
42+
private bool ObjectSpawnedOnAllNetworkManagers(bool isSpawned)
43+
{
44+
m_ErrorLog.Clear();
45+
var networkObjectId = m_SpawnedInstance.NetworkObjectId;
46+
foreach (var networkManager in m_NetworkManagers)
47+
{
48+
var containsKey = networkManager.SpawnManager.SpawnedObjects.ContainsKey(networkObjectId);
49+
if ((isSpawned && !containsKey) || (!isSpawned && containsKey))
50+
{
51+
m_ErrorLog.AppendLine($"[{networkManager.name}] NetworkObjectId-{networkObjectId} should be spawned ({isSpawned}) but its key status is ({containsKey})!");
52+
continue;
53+
}
54+
}
55+
return m_ErrorLog.Length == 0;
56+
}
57+
58+
/// <summary>
59+
/// Condition that determines if all instances of the spawned object were migrated
60+
/// to the loaded scene.
61+
/// </summary>
62+
private bool AllInstancesMovedToScene()
63+
{
64+
var networkObjectId = m_SpawnedInstance.NetworkObjectId;
65+
foreach (var networkManager in m_NetworkManagers)
66+
{
67+
if (!networkManager.SpawnManager.SpawnedObjects.ContainsKey(networkObjectId))
68+
{
69+
return false;
70+
}
71+
if (networkManager.SpawnManager.SpawnedObjects[networkObjectId].gameObject.scene.name != m_SceneLoaded.name)
72+
{
73+
return false;
74+
}
75+
}
76+
return true;
77+
}
78+
79+
[UnityTest]
80+
public IEnumerator DestroyWithScene()
81+
{
82+
// Get the session owner
83+
m_SessionOwner = GetAuthorityNetworkManager();
84+
// Get a non-session owner client
85+
m_NotSessionOwner = GetNonAuthorityNetworkManager();
86+
87+
// Register for scene events on the session owner side.
88+
m_SessionOwner.SceneManager.OnSceneEvent += OnSessionOwnerSceneEvent;
89+
90+
// Register for scene events on the non-session owner side.
91+
// (this is the scene we want to migrate the spawned object into when running a distributed authority session)
92+
m_NotSessionOwner.SceneManager.OnSceneEvent += OnNonSessionOwnerSceneEvent;
93+
94+
// Have the session owner load the scene
95+
Assert.True(m_SessionOwner.SceneManager.LoadScene(k_SceneToLoad, LoadSceneMode.Additive) == SceneEventProgressStatus.Started, $"[{m_SessionOwner.name}] Failed to begin loading scene {k_SceneToLoad}!");
96+
yield return WaitForConditionOrTimeOut(() => m_SceneHasLoaded && m_NotSessionOwnerScene != null
97+
&& m_NotSessionOwnerScene.IsValid() && m_NotSessionOwnerScene.isLoaded);
98+
AssertOnTimeout($"Scene loading event for {k_SceneToLoad} failed to complete!");
99+
100+
// Depending on network topology, spawn the object with the appropriate owner.
101+
var owner = m_DistributedAuthority ? m_NotSessionOwner : m_SessionOwner;
102+
m_SpawnedInstance = SpawnObject(m_TestPrefab.gameObject, m_NotSessionOwner, true).GetComponent<NetworkObject>();
103+
104+
var instanceName = m_SpawnedInstance.name;
105+
yield return WaitForConditionOrTimeOut(() => ObjectSpawnedOnAllNetworkManagers(true));
106+
AssertOnTimeout($"Not all clients spawned an instance of {instanceName}!");
107+
108+
var sceneToMigrateTo = m_DistributedAuthority ? m_NotSessionOwnerScene : m_SceneLoaded;
109+
// Migrate the newly spawned object into the recently loaded scene so we can unload the scene
110+
// and make sure the spawned object is destroyed when that happens.
111+
SceneManager.MoveGameObjectToScene(m_SpawnedInstance.gameObject, sceneToMigrateTo);
112+
113+
yield return WaitForConditionOrTimeOut(AllInstancesMovedToScene);
114+
AssertOnTimeout($"Not all instances of {instanceName} moved to scene {k_SceneToLoad}!");
115+
116+
// Have the session owner unload the scene containing the spawned object
117+
VerboseDebug($"Unloading scene {k_SceneToLoad}");
118+
Assert.True(m_SessionOwner.SceneManager.UnloadScene(m_SceneLoaded) == SceneEventProgressStatus.Started, $"[{m_SessionOwner.name}] Failed to begin unloading scene {k_SceneToLoad}!");
119+
yield return WaitForConditionOrTimeOut(() => !m_SceneHasLoaded);
120+
AssertOnTimeout($"Scene unloading event for {k_SceneToLoad} failed to complete!");
121+
122+
// Assure the spawned object was destroyed when the scene was unloaded
123+
yield return WaitForConditionOrTimeOut(() => ObjectSpawnedOnAllNetworkManagers(false));
124+
AssertOnTimeout($"Not all despawned an instance of {instanceName}!\n {m_ErrorLog}");
125+
}
126+
127+
/// <summary>
128+
/// Handles scene events for the session owner
129+
/// </summary>
130+
private void OnSessionOwnerSceneEvent(SceneEvent sceneEvent)
131+
{
132+
if (sceneEvent.ClientId != m_SessionOwner.LocalClientId)
133+
{
134+
return;
135+
}
136+
137+
switch (sceneEvent.SceneEventType)
138+
{
139+
case SceneEventType.LoadComplete:
140+
{
141+
VerboseDebug($"Scene loaded: {sceneEvent.Scene.name}");
142+
m_SceneLoaded = sceneEvent.Scene;
143+
break;
144+
}
145+
case SceneEventType.LoadEventCompleted:
146+
{
147+
m_SceneHasLoaded = true;
148+
break;
149+
}
150+
case SceneEventType.UnloadEventCompleted:
151+
{
152+
m_SessionOwner.SceneManager.OnSceneEvent -= OnSessionOwnerSceneEvent;
153+
m_SceneHasLoaded = false;
154+
break;
155+
}
156+
}
157+
}
158+
159+
/// <summary>
160+
/// Handles scene events for the non-session owner
161+
/// </summary>
162+
private void OnNonSessionOwnerSceneEvent(SceneEvent sceneEvent)
163+
{
164+
if (sceneEvent.ClientId != m_NotSessionOwner.LocalClientId)
165+
{
166+
return;
167+
}
168+
169+
switch (sceneEvent.SceneEventType)
170+
{
171+
case SceneEventType.LoadComplete:
172+
{
173+
VerboseDebug($"Scene loaded: {sceneEvent.Scene.name}");
174+
m_NotSessionOwnerScene = sceneEvent.Scene;
175+
m_NotSessionOwner.SceneManager.OnSceneEvent -= OnNonSessionOwnerSceneEvent;
176+
break;
177+
}
178+
}
179+
}
180+
181+
182+
protected override IEnumerator OnTearDown()
183+
{
184+
// In case of an error, assure the scene is unloaded
185+
if (m_SceneLoaded.IsValid() && m_SceneLoaded.isLoaded)
186+
{
187+
SceneManager.UnloadSceneAsync(m_SceneLoaded);
188+
}
189+
190+
if (m_NotSessionOwnerScene.IsValid() && m_NotSessionOwnerScene.isLoaded)
191+
{
192+
SceneManager.UnloadSceneAsync(m_NotSessionOwnerScene);
193+
}
194+
return base.OnTearDown();
195+
}
196+
}
197+
}

testproject/Assets/Tests/Runtime/NetworkObjectDestroyWithSceneTests.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)