From f7c94c56a5747da13ee398f918af0a341c906ab2 Mon Sep 17 00:00:00 2001 From: NoelStephensUnity Date: Mon, 9 Jun 2025 16:51:41 -0500 Subject: [PATCH 1/3] fix Apply fixes from #3491 --- .../Runtime/Core/NetworkManager.cs | 4 ++-- .../Runtime/Core/NetworkObject.cs | 8 ++++++++ .../Runtime/Spawning/NetworkSpawnManager.cs | 6 ++---- 3 files changed, 12 insertions(+), 6 deletions(-) diff --git a/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs b/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs index 4db8e5c350..b539491533 100644 --- a/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs +++ b/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs @@ -1265,8 +1265,8 @@ internal void ShutdownInternal() // In the event shutdown is invoked within OnClientStopped or OnServerStopped, set it to false again m_ShuttingDown = false; - // Reset the client's roles - ConnectionManager.LocalClient.SetRole(false, false); + // Completely reset the NetworkClient + ConnectionManager.LocalClient = new NetworkClient(); // This cleans up the internal prefabs list NetworkConfig?.Prefabs?.Shutdown(); diff --git a/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs b/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs index d20088feb1..47eca79f97 100644 --- a/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs +++ b/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs @@ -913,6 +913,14 @@ public void Despawn(bool destroy = true) NetworkManager.SpawnManager.DespawnObject(this, destroy); } + internal void ResetOnDespawn() + { + // Always clear out the observers list when despawned + Observers.Clear(); + IsSpawned = false; + m_LatestParent = null; + } + /// /// Removes all ownership of an object from any client. Can only be called from server /// diff --git a/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkSpawnManager.cs b/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkSpawnManager.cs index e6dd8879bd..ea550f0e0f 100644 --- a/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkSpawnManager.cs +++ b/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkSpawnManager.cs @@ -1170,15 +1170,13 @@ internal void OnDespawnObject(NetworkObject networkObject, bool destroyGameObjec } } - networkObject.IsSpawned = false; - if (SpawnedObjects.Remove(networkObject.NetworkObjectId)) { SpawnedObjectsList.Remove(networkObject); } - // Always clear out the observers list when despawned - networkObject.Observers.Clear(); + // Reset the NetworkObject when despawned. + networkObject.ResetOnDespawn(); var gobj = networkObject.gameObject; if (destroyGameObject && gobj != null) From 5ba0abaf4742a65ce22513a19cf2f3e0eb76b7f7 Mon Sep 17 00:00:00 2001 From: NoelStephensUnity Date: Mon, 9 Jun 2025 16:51:58 -0500 Subject: [PATCH 2/3] test Adding tests to validate the fixes. --- .../Tests/Runtime/DisconnectTests.cs | 26 ++++++++-- .../NetworkObjectOnSpawnTests.cs | 48 +++++++++++++++++++ 2 files changed, 70 insertions(+), 4 deletions(-) diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/DisconnectTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/DisconnectTests.cs index bef91313a8..1cc9bc40c9 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/DisconnectTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/DisconnectTests.cs @@ -150,8 +150,8 @@ public IEnumerator ClientPlayerDisconnected([Values] ClientDisconnectType client if (clientDisconnectType == ClientDisconnectType.ServerDisconnectsClient) { - m_ClientNetworkManagers[0].OnClientDisconnectCallback += OnClientDisconnectCallback; - m_ClientNetworkManagers[0].OnConnectionEvent += OnConnectionEvent; + clientManager.OnClientDisconnectCallback += OnClientDisconnectCallback; + clientManager.OnConnectionEvent += OnConnectionEvent; m_ServerNetworkManager.OnConnectionEvent += OnConnectionEvent; m_ServerNetworkManager.DisconnectClient(m_ClientId); } @@ -159,9 +159,18 @@ public IEnumerator ClientPlayerDisconnected([Values] ClientDisconnectType client { m_ServerNetworkManager.OnClientDisconnectCallback += OnClientDisconnectCallback; m_ServerNetworkManager.OnConnectionEvent += OnConnectionEvent; - m_ClientNetworkManagers[0].OnConnectionEvent += OnConnectionEvent; + clientManager.OnConnectionEvent += OnConnectionEvent; - yield return StopOneClient(m_ClientNetworkManagers[0]); + yield return StopOneClient(clientManager); + + if (clientManager.ConnectionManager != null) + { + Assert.False(clientManager.ConnectionManager.LocalClient.IsClient, $"{clientManager.name} still has IsClient setting!"); + Assert.False(clientManager.ConnectionManager.LocalClient.IsConnected, $"{clientManager.name} still has IsConnected setting!"); + Assert.False(clientManager.ConnectionManager.LocalClient.ClientId != 0, $"{clientManager.name} still has ClientId ({clientManager.ConnectionManager.LocalClient.ClientId}) setting!"); + Assert.False(clientManager.ConnectionManager.LocalClient.IsApproved, $"{clientManager.name} still has IsApproved setting!"); + Assert.IsNull(clientManager.ConnectionManager.LocalClient.PlayerObject, $"{clientManager.name} still has Player assigned!"); + } } yield return WaitForConditionOrTimeOut(() => m_ClientDisconnected); @@ -216,6 +225,15 @@ public IEnumerator ClientPlayerDisconnected([Values] ClientDisconnectType client Assert.IsTrue(m_DisconnectedEvent.ContainsKey(m_ServerNetworkManager), $"Could not find the server {nameof(NetworkManager)} disconnect event entry!"); Assert.IsTrue(m_DisconnectedEvent[m_ServerNetworkManager].ClientId == NetworkManager.ServerClientId, $"Expected ClientID {m_ClientId} but found ClientID {m_DisconnectedEvent[m_ServerNetworkManager].ClientId} for the server {nameof(NetworkManager)} disconnect event entry!"); + yield return s_DefaultWaitForTick; + if (m_ServerNetworkManager.ConnectionManager != null) + { + Assert.False(m_ServerNetworkManager.ConnectionManager.LocalClient.IsClient, $"{m_ServerNetworkManager.name} still has IsClient setting!"); + Assert.False(m_ServerNetworkManager.ConnectionManager.LocalClient.IsConnected, $"{m_ServerNetworkManager.name} still has IsConnected setting!"); + Assert.False(m_ServerNetworkManager.ConnectionManager.LocalClient.ClientId != 0, $"{m_ServerNetworkManager.name} still has ClientId ({clientManager.ConnectionManager.LocalClient.ClientId}) setting!"); + Assert.False(m_ServerNetworkManager.ConnectionManager.LocalClient.IsApproved, $"{m_ServerNetworkManager.name} still has IsApproved setting!"); + Assert.IsNull(m_ServerNetworkManager.ConnectionManager.LocalClient.PlayerObject, $"{m_ServerNetworkManager.name} still has Player assigned!"); + } } } } diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkObject/NetworkObjectOnSpawnTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkObject/NetworkObjectOnSpawnTests.cs index d7465cf37e..ff0106355f 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkObject/NetworkObjectOnSpawnTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkObject/NetworkObjectOnSpawnTests.cs @@ -352,5 +352,53 @@ public override void OnNetworkDespawn() OnNetworkDespawnCalledCount++; } } + + private bool AllClientsSpawnedObject() + { + foreach (var networkManager in m_ClientNetworkManagers) + { + if (!networkManager.SpawnManager.SpawnedObjects.ContainsKey(m_SpawnedInstanceId)) + { + return false; + } + } + return true; + } + + private bool AllClientsDespawnedObject() + { + foreach (var networkManager in m_ClientNetworkManagers) + { + if (networkManager.SpawnManager.SpawnedObjects.ContainsKey(m_SpawnedInstanceId)) + { + return false; + } + } + return true; + } + + private ulong m_SpawnedInstanceId; + /// + /// Validates that NetworkObject is reset properly when despawned but not destroyed. + /// + /// IEnumerator + [UnityTest] + public IEnumerator NetworkObjectResetOnDespawn() + { + var authorityNetworkManager = m_ServerNetworkManager; + var instance = SpawnObject(m_ObserverPrefab, authorityNetworkManager).GetComponent(); + m_SpawnedInstanceId = instance.NetworkObjectId; + yield return WaitForConditionOrTimeOut(AllClientsSpawnedObject); + AssertOnTimeout($"Not all clients spawned an instance of {instance.name}!"); + + instance.Despawn(false); + + yield return WaitForConditionOrTimeOut(AllClientsDespawnedObject); + AssertOnTimeout($"Not all clients de-spawned an instance of {instance.name}!"); + + Assert.IsNull(instance.GetNetworkParenting(), "Last parent was not reset!"); + + Object.Destroy(instance.gameObject); + } } } From b78be688d731c6515da199031e8171b7a6f1e005 Mon Sep 17 00:00:00 2001 From: NoelStephensUnity Date: Mon, 9 Jun 2025 16:57:23 -0500 Subject: [PATCH 3/3] update adding change log entries. --- com.unity.netcode.gameobjects/CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/com.unity.netcode.gameobjects/CHANGELOG.md b/com.unity.netcode.gameobjects/CHANGELOG.md index 3d8cc3cc01..2edefebbb3 100644 --- a/com.unity.netcode.gameobjects/CHANGELOG.md +++ b/com.unity.netcode.gameobjects/CHANGELOG.md @@ -15,6 +15,8 @@ Additional documentation and release notes are available at [Multiplayer Documen ### Fixed +- Fixed issue where `NetworkClient` could persist some settings if re-using the same `NetworkManager` instance. (#3494) +- Fixed issue where a pooled `NetworkObject` was not resetting the internal latest parent property when despawned. (#3494) - 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. (#3465) - Fixed issue where when a client changes ownership via RPC the `NetworkBehaviour.OnOwnershipChanged` can result in identical previous and current owner identifiers. (#3434)