From dab835fe1ad467d6bf7c2da53262094a365e59a0 Mon Sep 17 00:00:00 2001 From: Noel Stephens Date: Wed, 26 Aug 2026 12:28:11 -0500 Subject: [PATCH 1/3] fix: server not tracking scene handles for pre-loaded scenes Scenes that were already loaded when a session starts were added to ScenesLoaded but never to the server-to-client scene handle tables, so UnloadScene passed its ScenesLoaded check and then failed inside RemoveServerClientSceneHandle. The scene unloaded on every peer, but an error was logged and the ScenesLoaded entry was never removed. The client-server branch of the NetworkSceneManager constructor now registers those scenes the same way InitializeScenesLoaded does for distributed authority. NetcodeIntegrationTestHelpers registered the test runner scene in ServerSceneHandleToClientSceneHandle unconditionally, which now collides with the constructor already having added it, so that add is guarded. --- com.unity.netcode.gameobjects/CHANGELOG.md | 1 + .../SceneManagement/NetworkSceneManager.cs | 2 +- .../NetcodeIntegrationTestHelpers.cs | 7 ++++- .../NetworkSceneManagerStartupTests.cs | 30 +++++++++++++++++++ 4 files changed, 38 insertions(+), 2 deletions(-) diff --git a/com.unity.netcode.gameobjects/CHANGELOG.md b/com.unity.netcode.gameobjects/CHANGELOG.md index 85157fe510..65db23ecf2 100644 --- a/com.unity.netcode.gameobjects/CHANGELOG.md +++ b/com.unity.netcode.gameobjects/CHANGELOG.md @@ -29,6 +29,7 @@ Additional documentation and release notes are available at [Multiplayer Documen ### Fixed +- Fixed issue where scenes additively loaded before a session started were tracked as loaded on the server but had no scene handle entries, which caused `NetworkSceneManager.UnloadScene` to log an error and leave the scene registered as loaded even though it unloaded on all peers. (#4146) - Issue with not being able to spawn initially disabled in-scene placed objects. (#4093) - Issue with pre-instantiated network prefab instances being marked as in-scene placed. Now pre-instantiated network prefabs are dynamically spawned. (#4093) - Issue where a user could spawn runtime created `NetworkObject` that has a GlobalObjectIdHash of zero. These are not valid instances and will no longer be allowed to spawn. (#4093) diff --git a/com.unity.netcode.gameobjects/Runtime/SceneManagement/NetworkSceneManager.cs b/com.unity.netcode.gameobjects/Runtime/SceneManagement/NetworkSceneManager.cs index b0db598848..15f8b5afe4 100644 --- a/com.unity.netcode.gameobjects/Runtime/SceneManagement/NetworkSceneManager.cs +++ b/com.unity.netcode.gameobjects/Runtime/SceneManagement/NetworkSceneManager.cs @@ -847,7 +847,7 @@ internal NetworkSceneManager(NetworkManager networkManager) for (int i = 0; i < SceneManager.sceneCount; i++) { var loadedScene = SceneManager.GetSceneAt(i); - ScenesLoaded.Add(loadedScene.handle, loadedScene); + UpdateServerClientSceneHandle(loadedScene.handle, loadedScene.handle, loadedScene); } SceneManagerHandler.PopulateLoadedScenes(ref ScenesLoaded, NetworkManager); } diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/TestHelpers/NetcodeIntegrationTestHelpers.cs b/com.unity.netcode.gameobjects/Tests/Runtime/TestHelpers/NetcodeIntegrationTestHelpers.cs index 8f32fc1916..69d96df763 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/TestHelpers/NetcodeIntegrationTestHelpers.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/TestHelpers/NetcodeIntegrationTestHelpers.cs @@ -540,7 +540,12 @@ private static void SceneManagerValidationAndTestRunnerInitialization(NetworkMan } return; } - networkManager.SceneManager.ServerSceneHandleToClientSceneHandle.Add(scene.handle, scene.handle); + + // The server already registers every scene loaded prior to startup, so only add the test runner scene if it is not already there. + if (!networkManager.SceneManager.ServerSceneHandleToClientSceneHandle.ContainsKey(scene.handle)) + { + networkManager.SceneManager.ServerSceneHandleToClientSceneHandle.Add(scene.handle, scene.handle); + } } } diff --git a/testproject/Assets/Tests/Runtime/NetworkSceneManager/NetworkSceneManagerStartupTests.cs b/testproject/Assets/Tests/Runtime/NetworkSceneManager/NetworkSceneManagerStartupTests.cs index 28cd0b49d2..968f63e9dc 100644 --- a/testproject/Assets/Tests/Runtime/NetworkSceneManager/NetworkSceneManagerStartupTests.cs +++ b/testproject/Assets/Tests/Runtime/NetworkSceneManager/NetworkSceneManagerStartupTests.cs @@ -21,6 +21,7 @@ internal class NetworkSceneManagerStartupTests : NetcodeIntegrationTest { private const string k_ActiveScene = "SessionSynchronize"; private const string k_AdditionalScene = "InSceneNetworkObjectMovesToDDOL"; + private const string k_PreLoadedScene = "EmptyScene1"; private readonly List m_ObjectsInScenes = new List(); private Scene m_OriginalActiveScene; @@ -141,6 +142,35 @@ public IEnumerator AllExistingObjectsAreSpawnedAtStartup([Values] LoadSceneMode AssertOnTimeout("Timed out waiting for objects to spawn on all clients!"); } + /// + /// Validates that a scene additively loaded before the session started is tracked well enough + /// to be unloaded through without error. + /// + [UnityTest] + public IEnumerator UnloadPreLoadedScene() + { + yield return PreLoadScene(k_PreLoadedScene); + var preLoadedScene = m_SceneLoaded; + + m_CanStart = true; + yield return StartServerAndClients(); + + // Scenes loaded before the session started are registered in both the loaded scenes and the + // scene handle tables, otherwise unloading them fails part way through and leaks the entry. + var sceneManager = GetAuthorityNetworkManager().SceneManager; + Assert.IsTrue(sceneManager.ScenesLoaded.ContainsKey(preLoadedScene.handle), $"{k_PreLoadedScene} is not in {nameof(NetworkSceneManager.ScenesLoaded)}!"); + Assert.IsTrue(sceneManager.ServerSceneHandleToClientSceneHandle.ContainsKey(preLoadedScene.handle), $"{k_PreLoadedScene} is not in {nameof(NetworkSceneManager.ServerSceneHandleToClientSceneHandle)}!"); + + var status = sceneManager.UnloadScene(preLoadedScene); + Assert.AreEqual(SceneEventProgressStatus.Started, status, $"{nameof(NetworkSceneManager.UnloadScene)} returned {status}!"); + + yield return WaitForConditionOrTimeOut(() => !preLoadedScene.isLoaded); + AssertOnTimeout($"Timed out waiting for {k_PreLoadedScene} to unload!"); + + Assert.IsFalse(sceneManager.ScenesLoaded.ContainsKey(preLoadedScene.handle), $"{k_PreLoadedScene} was unloaded but is still in {nameof(NetworkSceneManager.ScenesLoaded)}!"); + Assert.IsFalse(sceneManager.ServerSceneHandleToClientSceneHandle.ContainsKey(preLoadedScene.handle), $"{k_PreLoadedScene} was unloaded but is still in {nameof(NetworkSceneManager.ServerSceneHandleToClientSceneHandle)}!"); + } + #region Scene loading and related methods /// From b4710a1b82912d49e808f7e77c940c39e574a742 Mon Sep 17 00:00:00 2001 From: Noel Stephens Date: Thu, 27 Aug 2026 14:05:03 -0500 Subject: [PATCH 2/3] Update com.unity.netcode.gameobjects/Tests/Runtime/TestHelpers/NetcodeIntegrationTestHelpers.cs Co-authored-by: Emma --- .../Runtime/TestHelpers/NetcodeIntegrationTestHelpers.cs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/TestHelpers/NetcodeIntegrationTestHelpers.cs b/com.unity.netcode.gameobjects/Tests/Runtime/TestHelpers/NetcodeIntegrationTestHelpers.cs index 69d96df763..c4f00148ff 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/TestHelpers/NetcodeIntegrationTestHelpers.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/TestHelpers/NetcodeIntegrationTestHelpers.cs @@ -542,10 +542,7 @@ private static void SceneManagerValidationAndTestRunnerInitialization(NetworkMan } // The server already registers every scene loaded prior to startup, so only add the test runner scene if it is not already there. - if (!networkManager.SceneManager.ServerSceneHandleToClientSceneHandle.ContainsKey(scene.handle)) - { - networkManager.SceneManager.ServerSceneHandleToClientSceneHandle.Add(scene.handle, scene.handle); - } + networkManager.SceneManager.ServerSceneHandleToClientSceneHandle.TryAdd(scene.handle, scene.handle); } } From bfbbf65407076de0801584f31d0bfba3fb51118d Mon Sep 17 00:00:00 2001 From: Noel Stephens Date: Fri, 28 Aug 2026 12:04:20 -0500 Subject: [PATCH 3/3] style +Indent. --- .../Tests/Runtime/TestHelpers/NetcodeIntegrationTestHelpers.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/TestHelpers/NetcodeIntegrationTestHelpers.cs b/com.unity.netcode.gameobjects/Tests/Runtime/TestHelpers/NetcodeIntegrationTestHelpers.cs index c4f00148ff..02b2503fbd 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/TestHelpers/NetcodeIntegrationTestHelpers.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/TestHelpers/NetcodeIntegrationTestHelpers.cs @@ -542,7 +542,7 @@ private static void SceneManagerValidationAndTestRunnerInitialization(NetworkMan } // The server already registers every scene loaded prior to startup, so only add the test runner scene if it is not already there. - networkManager.SceneManager.ServerSceneHandleToClientSceneHandle.TryAdd(scene.handle, scene.handle); + networkManager.SceneManager.ServerSceneHandleToClientSceneHandle.TryAdd(scene.handle, scene.handle); } }