From 08e2075142a3914e3dadd94ba2c61da6f76d2c6b Mon Sep 17 00:00:00 2001 From: NoelStephensUnity Date: Mon, 2 Jun 2025 15:56:44 -0500 Subject: [PATCH 1/4] fix Fixing issue where dont destroy with owner was not being honored. --- .../Runtime/Connection/NetworkConnectionManager.cs | 4 ++-- .../Runtime/Spawning/NetworkSpawnManager.cs | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/com.unity.netcode.gameobjects/Runtime/Connection/NetworkConnectionManager.cs b/com.unity.netcode.gameobjects/Runtime/Connection/NetworkConnectionManager.cs index 59a187a5fc..9590e219a9 100644 --- a/com.unity.netcode.gameobjects/Runtime/Connection/NetworkConnectionManager.cs +++ b/com.unity.netcode.gameobjects/Runtime/Connection/NetworkConnectionManager.cs @@ -1177,7 +1177,7 @@ internal void OnClientDisconnectFromServer(ulong clientId) { // If destroying with owner, then always despawn and destroy (or defer destroying to prefab handler) // Handle an object with no observers other than the current disconnecting client as destroying with owner - if (!ownedObject.DontDestroyWithOwner || ownedObject.Observers.Count == 0 || (ownedObject.Observers.Count == 1 && ownedObject.Observers.Contains(clientId))) + if (!ownedObject.DontDestroyWithOwner && (ownedObject.Observers.Count == 0 || (ownedObject.Observers.Count == 1 && ownedObject.Observers.Contains(clientId)))) { if (NetworkManager.PrefabHandler.ContainsHandler(ownedObject.GlobalObjectIdHash)) { @@ -1243,7 +1243,7 @@ internal void OnClientDisconnectFromServer(ulong clientId) } // Skip destroy with owner objects as they will be processed by the outer loop - if (!childObject.DontDestroyWithOwner || childObject.Observers.Count == 0 || (childObject.Observers.Count == 1 && childObject.Observers.Contains(clientId))) + if (!childObject.DontDestroyWithOwner && (childObject.Observers.Count == 0 || (childObject.Observers.Count == 1 && childObject.Observers.Contains(clientId)))) { continue; } diff --git a/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkSpawnManager.cs b/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkSpawnManager.cs index 3b5bb2e6e0..2bca1fb8fd 100644 --- a/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkSpawnManager.cs +++ b/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkSpawnManager.cs @@ -1449,7 +1449,7 @@ internal void DespawnAndDestroyNetworkObjects() { // If it is an in-scene placed NetworkObject then just despawn and let it be destroyed when the scene // is unloaded. Otherwise, despawn and destroy it. - var shouldDestroy = !(networkObjects[i].IsSceneObject == null || (networkObjects[i].IsSceneObject != null && networkObjects[i].IsSceneObject.Value)); + var shouldDestroy = !(networkObjects[i].DontDestroyWithOwner || networkObjects[i].IsSceneObject == null || (networkObjects[i].IsSceneObject != null && networkObjects[i].IsSceneObject.Value)); // If we are going to destroy this NetworkObject, check for any in-scene placed children that need to be removed if (shouldDestroy) From a8b7e9be727e7e73b024c239df18921f961d7023 Mon Sep 17 00:00:00 2001 From: NoelStephensUnity Date: Mon, 2 Jun 2025 15:57:10 -0500 Subject: [PATCH 2/4] test Adding another integration test to validate this fix. --- .../NetworkObjectDontDestroyWithOwnerTests.cs | 31 +++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkObject/NetworkObjectDontDestroyWithOwnerTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkObject/NetworkObjectDontDestroyWithOwnerTests.cs index 2c59647aa9..9466148d38 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkObject/NetworkObjectDontDestroyWithOwnerTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkObject/NetworkObjectDontDestroyWithOwnerTests.cs @@ -23,6 +23,7 @@ protected override bool UseCMBService() } protected GameObject m_PrefabToSpawn; + protected GameObject m_PrefabNoObserversSpawn; public NetworkObjectDontDestroyWithOwnerTests(HostOrServer hostOrServer) : base(hostOrServer) { } @@ -30,6 +31,11 @@ protected override void OnServerAndClientsCreated() { m_PrefabToSpawn = CreateNetworkObjectPrefab("ClientOwnedObject"); m_PrefabToSpawn.GetComponent().DontDestroyWithOwner = true; + + m_PrefabNoObserversSpawn = CreateNetworkObjectPrefab("NoObserversObject"); + var prefabNoObserversNetworkObject = m_PrefabNoObserversSpawn.GetComponent(); + prefabNoObserversNetworkObject.SpawnWithObservers = false; + prefabNoObserversNetworkObject.DontDestroyWithOwner = true; } [UnityTest] @@ -74,5 +80,30 @@ public IEnumerator DontDestroyWithOwnerTest() Assert.That(networkObject.OwnerClientId == m_ServerNetworkManager.LocalClientId); } } + + [UnityTest] + public IEnumerator NetworkShowThenClientDisconnects() + { + var authorityManager = GetAuthorityNetworkManager(); + var networkObject = SpawnObject(m_PrefabNoObserversSpawn, authorityManager).GetComponent(); + var longWait = new WaitForSeconds(0.25f); + yield return longWait; + var nonAuthorityManager = GetNonAuthorityNetworkManager(); + Assert.False(nonAuthorityManager.SpawnManager.SpawnedObjects.ContainsKey(networkObject.NetworkObjectId), $"[Client-{nonAuthorityManager.LocalClientId}] " + + $"Already has an instance of {networkObject.name} when it should not!"); + networkObject.NetworkShow(nonAuthorityManager.LocalClientId); + networkObject.ChangeOwnership(nonAuthorityManager.LocalClientId); + + yield return WaitForConditionOrTimeOut(() => nonAuthorityManager.SpawnManager.SpawnedObjects.ContainsKey(networkObject.NetworkObjectId) + && nonAuthorityManager.SpawnManager.SpawnedObjects[networkObject.NetworkObjectId].OwnerClientId == nonAuthorityManager.LocalClientId); + AssertOnTimeout($"[Client-{nonAuthorityManager.LocalClientId}] Failed to spawn {networkObject.name} when it was shown!"); + + yield return s_DefaultWaitForTick; + + nonAuthorityManager.Shutdown(); + + yield return longWait; + Assert.True(networkObject.IsSpawned, $"The spawned test prefab was despawned on the authority side when it shouldn't have been!"); + } } } From 805a0d5a19e7c3065b93e6a7f5207d64dbdd1f9c Mon Sep 17 00:00:00 2001 From: NoelStephensUnity Date: Mon, 2 Jun 2025 16:14:55 -0500 Subject: [PATCH 3/4] update Adding changelog. --- com.unity.netcode.gameobjects/CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/com.unity.netcode.gameobjects/CHANGELOG.md b/com.unity.netcode.gameobjects/CHANGELOG.md index 79d8e69c83..6652ff94f6 100644 --- a/com.unity.netcode.gameobjects/CHANGELOG.md +++ b/com.unity.netcode.gameobjects/CHANGELOG.md @@ -14,6 +14,7 @@ Additional documentation and release notes are available at [Multiplayer Documen ### Fixed +- Fixed issue where the `NetworkObject.DontDestroyWithOwner` was not being honored. (#3477) - Fixed issue where invoking `NetworkObject.NetworkShow` and `NetworkObject.ChangeOwnership` consecutively within the same call stack location could result in an unnecessary change in ownership error message generated on the target client side. (#3468) - Fixed issue where `NetworkVariable`s on a `NetworkBehaviour` could fail to synchronize changes if one has `NetworkVariableUpdateTraits` set and is dirty but is not ready to send. (#3466) - Fixed inconsistencies in the `OnSceneEvent` callback. (#3458) From 29a0784396ed1360a4aaa0fce55847e0a44496c9 Mon Sep 17 00:00:00 2001 From: NoelStephensUnity Date: Mon, 2 Jun 2025 16:26:16 -0500 Subject: [PATCH 4/4] revert Reverting one change that was not needed for this fix. --- .../Runtime/Spawning/NetworkSpawnManager.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkSpawnManager.cs b/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkSpawnManager.cs index 2bca1fb8fd..3b5bb2e6e0 100644 --- a/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkSpawnManager.cs +++ b/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkSpawnManager.cs @@ -1449,7 +1449,7 @@ internal void DespawnAndDestroyNetworkObjects() { // If it is an in-scene placed NetworkObject then just despawn and let it be destroyed when the scene // is unloaded. Otherwise, despawn and destroy it. - var shouldDestroy = !(networkObjects[i].DontDestroyWithOwner || networkObjects[i].IsSceneObject == null || (networkObjects[i].IsSceneObject != null && networkObjects[i].IsSceneObject.Value)); + var shouldDestroy = !(networkObjects[i].IsSceneObject == null || (networkObjects[i].IsSceneObject != null && networkObjects[i].IsSceneObject.Value)); // If we are going to destroy this NetworkObject, check for any in-scene placed children that need to be removed if (shouldDestroy)