Skip to content

Commit 1701474

Browse files
fix: Active scene not being added to server side NetworkSceneManager scenes loaded [MTT-7537] (#2723)
* fix This fixes the issue where the currently active scene, on the server side, is not taken into consideration as being a valid "loaded scene" which can cause issues as outlined in GitHub issue #2722. * fix: TestHelpers This resolves an issue with integration testing where: - the scene handler registration was being registered multiple times - the server registration was not passing in true to NetcodeIntegrationTestHelper.RegisterHandlers - the IntegrationTestSceneHandler.CoroutineRunner could get destroyed if the active scene it was instantiated within was unloaded (now it is migrated to the DDOL) - Registration of the currently active scene during the scene handler registration was adjusted to no longer use NetworkSceneManager.GetAndAddNewlyLoadedSceneByName (but still registers the scene). * test Added an integration test: `NetworkSceneManagerFixValidationTests.InitialActiveSceneUnload` This validates the fix for #2722. * update Adding change log entry for this fix.
1 parent a60288f commit 1701474

File tree

6 files changed

+165
-14
lines changed

6 files changed

+165
-14
lines changed

com.unity.netcode.gameobjects/CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@ Additional documentation and release notes are available at [Multiplayer Documen
1818
- Added `NetworkVariableBase.MarkNetworkBehaviourDirty` so that user-created network variable types can mark their containing `NetworkBehaviour` to be processed by the update loop. (#2694)
1919

2020
### Fixed
21+
22+
- Fixed issue where the server side `NetworkSceneManager` instance was not adding the currently active scene to its list of scenes loaded. (#2723)
2123
- Generic NetworkBehaviour types no longer result in compile errors or runtime errors (#2720)
2224
- Rpcs within Generic NetworkBehaviour types can now serialize parameters of the class's generic types (but may not have generic types of their own) (#2720)
2325
- Errors are no longer thrown when entering play mode with domain reload disabled (#2720)

com.unity.netcode.gameobjects/Runtime/SceneManagement/NetworkSceneManager.cs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -740,6 +740,14 @@ internal NetworkSceneManager(NetworkManager networkManager)
740740
// Since NetworkManager is now always migrated to the DDOL we will use this to get the DDOL scene
741741
DontDestroyOnLoadScene = networkManager.gameObject.scene;
742742

743+
// Since the server tracks loaded scenes, we need to add the currently active scene
744+
// to the list of scenes that can be unloaded.
745+
if (networkManager.IsServer)
746+
{
747+
var activeScene = SceneManager.GetActiveScene();
748+
ScenesLoaded.Add(activeScene.handle, activeScene);
749+
}
750+
743751
// Add to the server to client scene handle table
744752
UpdateServerClientSceneHandle(DontDestroyOnLoadScene.handle, DontDestroyOnLoadScene.handle, DontDestroyOnLoadScene);
745753
}

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

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -913,6 +913,9 @@ public IntegrationTestSceneHandler(NetworkManager networkManager)
913913
if (CoroutineRunner == null)
914914
{
915915
CoroutineRunner = new GameObject("UnitTestSceneHandlerCoroutine").AddComponent<CoroutineRunner>();
916+
// Move the CoroutineRunner into the DDOL in case we unload the scene it was instantiated in.
917+
// (which if that gets destroyed then it basically stops all integration test queue processing)
918+
Object.DontDestroyOnLoad(CoroutineRunner);
916919
}
917920
}
918921

com.unity.netcode.gameobjects/TestHelpers/Runtime/NetcodeIntegrationTest.cs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -886,7 +886,6 @@ protected void RegisterSceneManagerHandler()
886886
{
887887
IntegrationTestSceneHandler.CanClientsLoad += ClientSceneHandler_CanClientsLoad;
888888
IntegrationTestSceneHandler.CanClientsUnload += ClientSceneHandler_CanClientsUnload;
889-
NetcodeIntegrationTestHelpers.RegisterSceneManagerHandler(m_ServerNetworkManager, true);
890889
}
891890

892891
private bool ClientSceneHandler_CanClientsUnload()

com.unity.netcode.gameobjects/TestHelpers/Runtime/NetcodeIntegrationTestHelpers.cs

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -165,7 +165,8 @@ public static void RegisterHandlers(NetworkManager networkManager, bool serverSi
165165

166166
if (!networkManager.IsServer || networkManager.IsServer && serverSideSceneManager)
167167
{
168-
RegisterSceneManagerHandler(networkManager);
168+
// Pass along the serverSideSceneManager property (otherwise the server won't register properly)
169+
RegisterSceneManagerHandler(networkManager, serverSideSceneManager);
169170
}
170171
}
171172

@@ -405,7 +406,10 @@ private static void SceneManagerValidationAndTestRunnerInitialization(NetworkMan
405406
// scene to synchronize NetworkObjects. Next, add the currently active test runner scene to the scenes
406407
// loaded and register the server to client scene handle since host-server shares the test runner scene
407408
// with the clients.
408-
networkManager.SceneManager.GetAndAddNewlyLoadedSceneByName(scene.name);
409+
if (!networkManager.SceneManager.ScenesLoaded.ContainsKey(scene.handle))
410+
{
411+
networkManager.SceneManager.ScenesLoaded.Add(scene.handle, scene);
412+
}
409413
networkManager.SceneManager.ServerSceneHandleToClientSceneHandle.Add(scene.handle, scene.handle);
410414
}
411415
}
@@ -443,8 +447,8 @@ public static bool Start(bool host, NetworkManager server, NetworkManager[] clie
443447
server.ConnectionManager.MessageManager.Hook(hooks);
444448
s_Hooks[server] = hooks;
445449

446-
// if set, then invoke this for the server
447-
RegisterHandlers(server);
450+
// Register the server side handler (always pass true for server)
451+
RegisterHandlers(server, true);
448452

449453
callback?.Invoke();
450454

testproject/Assets/Tests/Runtime/NetworkSceneManager/NetworkSceneManagerFixValidationTests.cs

Lines changed: 144 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,23 @@ namespace TestProject.RuntimeTests
1616
/// </summary>
1717
public class NetworkSceneManagerFixValidationTests : NetcodeIntegrationTest
1818
{
19+
20+
private const string k_SceneToLoad = "UnitTestBaseScene";
21+
private const string k_AdditiveScene1 = "InSceneNetworkObject";
22+
private const string k_AdditiveScene2 = "AdditiveSceneMultiInstance";
23+
1924
protected override int NumberOfClients => 2;
2025

2126
private bool m_CanStart;
27+
private bool m_NoLatency;
28+
29+
private Scene m_OriginalActiveScene;
30+
31+
protected override IEnumerator OnSetup()
32+
{
33+
m_OriginalActiveScene = SceneManager.GetActiveScene();
34+
return base.OnSetup();
35+
}
2236

2337
protected override bool CanStartServerAndClients()
2438
{
@@ -76,15 +90,13 @@ public void DDOLPopulateWithNullNetworkObjectsValidation([Values] bool useHost)
7690
// As long as there are no exceptions this test passes
7791
}
7892

79-
private const string k_SceneToLoad = "UnitTestBaseScene";
80-
81-
protected override void OnCreatePlayerPrefab()
82-
{
83-
base.OnCreatePlayerPrefab();
84-
}
85-
8693
protected override void OnServerAndClientsCreated()
8794
{
95+
if (m_NoLatency)
96+
{
97+
return;
98+
}
99+
88100
// Apply a 500ms latency on packets (primarily for ClientDisconnectsDuringSeneLoadingValidation)
89101
var serverTransport = m_ServerNetworkManager.GetComponent<UnityTransport>();
90102
serverTransport.SetDebugSimulatorParameters(500, 0, 0);
@@ -94,8 +106,6 @@ protected override void OnServerAndClientsCreated()
94106
var clientTransport = m_ServerNetworkManager.GetComponent<UnityTransport>();
95107
clientTransport.SetDebugSimulatorParameters(500, 0, 0);
96108
}
97-
98-
base.OnServerAndClientsCreated();
99109
}
100110

101111
protected override IEnumerator OnServerAndClientsConnected()
@@ -178,9 +188,134 @@ private bool VerifySceneBeforeLoading(int sceneIndex, string sceneName, LoadScen
178188
return true;
179189
}
180190

191+
192+
private Scene m_FirstScene;
193+
private Scene m_SecondScene;
194+
private Scene m_ThirdScene;
195+
196+
private bool m_SceneUnloadedEventCompleted;
197+
198+
199+
[UnityTest]
200+
public IEnumerator InitialActiveSceneUnload()
201+
{
202+
SceneManager.sceneLoaded += SceneManager_sceneLoaded;
203+
SceneManager.LoadScene(k_SceneToLoad, LoadSceneMode.Additive);
204+
205+
yield return WaitForConditionOrTimeOut(FirstSceneIsLoaded);
206+
AssertOnTimeout($"Failed to load scene {k_SceneToLoad}!");
207+
208+
// Now set the "first scene" as the active scene prior to starting the server and clients
209+
SceneManager.SetActiveScene(m_FirstScene);
210+
m_NoLatency = true;
211+
m_CanStart = true;
212+
213+
yield return StartServerAndClients();
214+
215+
var serverSceneManager = m_ServerNetworkManager.SceneManager;
216+
serverSceneManager.OnSceneEvent += ServerSceneManager_OnSceneEvent;
217+
m_SceneLoadEventCompleted = false;
218+
serverSceneManager.LoadScene(k_AdditiveScene1, LoadSceneMode.Additive);
219+
220+
yield return WaitForConditionOrTimeOut(SecondSceneIsLoaded);
221+
AssertOnTimeout($"[Load Event] Failure in loading scene {k_AdditiveScene1} (locally or on client side)!");
222+
223+
// Since we have to keep the test running scene active, we mimic the "auto assignment" of
224+
// the active scene prior to unloading the first scene in order to validate this test scenario.
225+
SceneManager.SetActiveScene(m_SecondScene);
226+
227+
yield return s_DefaultWaitForTick;
228+
229+
// Now unload the "first" scene which, if this was the only scene loaded prior to loading the second scene,
230+
// would automatically make the second scene the currently active scene
231+
m_SceneUnloadedEventCompleted = false;
232+
serverSceneManager.UnloadScene(m_FirstScene);
233+
yield return WaitForConditionOrTimeOut(SceneUnloadEventCompleted);
234+
AssertOnTimeout($"[Unload Event] Failure in unloading scene {m_FirstScene} (locally or on client side)!");
235+
236+
// Now load the third scene, and if no time out occurs then we have validated this test!
237+
m_SceneLoadEventCompleted = false;
238+
serverSceneManager.LoadScene(k_AdditiveScene2, LoadSceneMode.Additive);
239+
yield return WaitForConditionOrTimeOut(ThirdSceneIsLoaded);
240+
AssertOnTimeout($"[Load Event] Failure in loading scene {k_AdditiveScene2} (locally or on client side)!");
241+
serverSceneManager.OnSceneEvent -= ServerSceneManager_OnSceneEvent;
242+
}
243+
244+
private void ServerSceneManager_OnSceneEvent(SceneEvent sceneEvent)
245+
{
246+
if (sceneEvent.ClientId != m_ServerNetworkManager.LocalClientId)
247+
{
248+
return;
249+
}
250+
251+
if (sceneEvent.SceneEventType == SceneEventType.LoadComplete)
252+
{
253+
if (sceneEvent.Scene.name == k_AdditiveScene1)
254+
{
255+
m_SecondScene = sceneEvent.Scene;
256+
}
257+
else if (sceneEvent.Scene.name == k_AdditiveScene2)
258+
{
259+
m_ThirdScene = sceneEvent.Scene;
260+
}
261+
}
262+
263+
if (sceneEvent.SceneEventType == SceneEventType.LoadEventCompleted)
264+
{
265+
if (sceneEvent.SceneName == k_AdditiveScene1 || sceneEvent.SceneName == k_AdditiveScene2)
266+
{
267+
m_SceneLoadEventCompleted = true;
268+
}
269+
}
270+
271+
if (sceneEvent.SceneEventType == SceneEventType.UnloadEventCompleted)
272+
{
273+
if (sceneEvent.SceneName == k_SceneToLoad || sceneEvent.SceneName == k_AdditiveScene1 || sceneEvent.SceneName == k_AdditiveScene2)
274+
{
275+
m_SceneUnloadedEventCompleted = true;
276+
}
277+
}
278+
}
279+
280+
private bool SceneUnloadEventCompleted()
281+
{
282+
return m_SceneUnloadedEventCompleted;
283+
}
284+
285+
private bool FirstSceneIsLoaded()
286+
{
287+
return m_FirstScene.IsValid() && m_FirstScene.isLoaded;
288+
}
289+
290+
private bool SecondSceneIsLoaded()
291+
{
292+
return m_SecondScene.IsValid() && m_SecondScene.isLoaded && m_SceneLoadEventCompleted;
293+
}
294+
295+
private bool ThirdSceneIsLoaded()
296+
{
297+
return m_ThirdScene.IsValid() && m_ThirdScene.isLoaded && m_SceneLoadEventCompleted;
298+
}
299+
300+
private void SceneManager_sceneLoaded(Scene scene, LoadSceneMode mode)
301+
{
302+
if (scene.name == k_SceneToLoad && mode == LoadSceneMode.Additive)
303+
{
304+
m_FirstScene = scene;
305+
SceneManager.sceneLoaded -= SceneManager_sceneLoaded;
306+
}
307+
}
308+
181309
protected override IEnumerator OnTearDown()
182310
{
183311
m_CanStart = false;
312+
m_NoLatency = false;
313+
314+
if (m_OriginalActiveScene != SceneManager.GetActiveScene())
315+
{
316+
SceneManager.SetActiveScene(m_OriginalActiveScene);
317+
}
318+
184319
return base.OnTearDown();
185320
}
186321
}

0 commit comments

Comments
 (0)