From 48d3cee1e384bf3f749da9532f792f129e5954be Mon Sep 17 00:00:00 2001 From: Noel Stephens Date: Tue, 9 Dec 2025 22:36:41 -0600 Subject: [PATCH 1/3] fix Fixing issue where spawning a player in distributed authority mode via a client, typically session owner, other than the newly connected client and scene management is disabled then the already spawned players will not properly get synchronized by each owning client due to the newly connected client's identifier already being added prior to synchronization. --- .../Runtime/Core/NetworkObject.cs | 21 ++++++++++---- .../Messages/ConnectionApprovedMessage.cs | 2 +- .../Messaging/Messages/CreateObjectMessage.cs | 2 +- .../Runtime/Spawning/NetworkSpawnManager.cs | 29 ++++++++++++------- 4 files changed, 35 insertions(+), 19 deletions(-) diff --git a/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs b/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs index 608d5360d3..cbcd40ed4d 100644 --- a/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs +++ b/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs @@ -1320,6 +1320,15 @@ public void SetSceneObjectStatus(bool isSceneObject = false) internal readonly HashSet Observers = new HashSet(); + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal void AddObserver(ulong clientId) + { +#if NETCODE_DEBUG_OBSERVERS + Debug.Log($"[{nameof(NetworkObject)}][{name}-{NetworkObjectId}] Adding Client-{clientId} as an observer."); +#endif + Observers.Add(clientId); + } + #if MULTIPLAYER_TOOLS private string m_CachedNameForMetrics; #endif @@ -1469,7 +1478,7 @@ public void NetworkShow(ulong clientId) return; } NetworkManager.SpawnManager.MarkObjectForShowingTo(this, clientId); - Observers.Add(clientId); + AddObserver(clientId); } @@ -3346,7 +3355,7 @@ internal static NetworkObject Deserialize(in SerializedObject serializedObject, { foreach (var observer in serializedObject.Observers) { - networkObject.Observers.Add(observer); + networkObject.AddObserver(observer); } } @@ -3366,11 +3375,11 @@ internal static NetworkObject Deserialize(in SerializedObject serializedObject, if (networkObject.IsPlayerObject) { // If it is another player, then make sure the local player is aware of the player - playerObject.Observers.Add(networkObject.OwnerClientId); + playerObject.AddObserver(networkObject.OwnerClientId); } // Assure the local player has observability - networkObject.Observers.Add(playerObject.OwnerClientId); + networkObject.AddObserver(playerObject.OwnerClientId); // If it is a player object, then add it to all known spawned NetworkObjects that spawn with observers if (networkObject.IsPlayerObject) @@ -3379,7 +3388,7 @@ internal static NetworkObject Deserialize(in SerializedObject serializedObject, { if (netObject.Value.SpawnWithObservers) { - netObject.Value.Observers.Add(networkObject.OwnerClientId); + netObject.Value.AddObserver(networkObject.OwnerClientId); } } } @@ -3391,7 +3400,7 @@ internal static NetworkObject Deserialize(in SerializedObject serializedObject, // Add all known players to the observers list if they don't already exist foreach (var player in networkManager.SpawnManager.PlayerObjects) { - networkObject.Observers.Add(player.OwnerClientId); + networkObject.AddObserver(player.OwnerClientId); } } } diff --git a/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/ConnectionApprovedMessage.cs b/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/ConnectionApprovedMessage.cs index 5a1c5899cc..ab6a0ea042 100644 --- a/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/ConnectionApprovedMessage.cs +++ b/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/ConnectionApprovedMessage.cs @@ -158,7 +158,7 @@ public void Serialize(FastBufferWriter writer, int targetVersion) { if (sobj.SpawnWithObservers && (sobj.CheckObjectVisibility == null || sobj.CheckObjectVisibility(OwnerClientId))) { - sobj.Observers.Add(OwnerClientId); + sobj.AddObserver(OwnerClientId); // In distributed authority mode, we send the currently known observers of each NetworkObject to the client being synchronized. var serializedObject = sobj.Serialize(OwnerClientId, IsDistributedAuthority); serializedObject.Serialize(writer); diff --git a/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/CreateObjectMessage.cs b/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/CreateObjectMessage.cs index 475ba0b171..cf518a6216 100644 --- a/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/CreateObjectMessage.cs +++ b/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/CreateObjectMessage.cs @@ -208,7 +208,7 @@ internal static void CreateObject(ref NetworkManager networkManager, ulong sende // Update the observers for this instance for (int i = 0; i < clientList.Count; i++) { - networkObject.Observers.Add(clientList[i]); + networkObject.AddObserver(clientList[i]); } // Mock CMB Service and forward to all clients diff --git a/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkSpawnManager.cs b/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkSpawnManager.cs index 75dcc83ec3..c157c68cea 100644 --- a/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkSpawnManager.cs +++ b/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkSpawnManager.cs @@ -81,18 +81,25 @@ private void AddPlayerObject(NetworkObject playerObject) } } + var cmbService = NetworkManager.CMBServiceConnection; + var sceneManagement = NetworkManager.NetworkConfig.EnableSceneManagement; + foreach (var player in m_PlayerObjects) { + // When connected to the CMB service, scene management is disabled, and it is the local player, we do + // not want to add the newly connected player to the observers list of the local player as that will be + // done when the local client shows the NetworkObject to the newly connected client. + var shouldAddObserver = cmbService && !sceneManagement ? !player.IsLocalPlayer : true; // If the player's SpawnWithObservers is not set then do not add the new player object's owner as an observer. - if (player.SpawnWithObservers) + if (player.SpawnWithObservers && shouldAddObserver) { - player.Observers.Add(playerObject.OwnerClientId); + player.AddObserver(playerObject.OwnerClientId); } // If the new player object's SpawnWithObservers is not set then do not add this player as an observer to the new player object. if (playerObject.SpawnWithObservers) { - playerObject.Observers.Add(player.OwnerClientId); + playerObject.AddObserver(player.OwnerClientId); } } @@ -100,7 +107,7 @@ private void AddPlayerObject(NetworkObject playerObject) // the owner as an observer. if (playerObject.SpawnWithObservers || (NetworkManager.DistributedAuthorityMode && NetworkManager.LocalClientId == playerObject.OwnerClientId)) { - playerObject.Observers.Add(playerObject.OwnerClientId); + playerObject.AddObserver(playerObject.OwnerClientId); } m_PlayerObjects.Add(playerObject); @@ -1066,7 +1073,7 @@ internal void AuthorityLocalSpawn([NotNull] NetworkObject networkObject, ulong n // (authority should not take into consideration networkObject.CheckObjectVisibility when SpawnWithObservers is false) if (!networkObject.SpawnWithObservers) { - networkObject.Observers.Add(ownerClientId); + networkObject.AddObserver(ownerClientId); } else { @@ -1077,7 +1084,7 @@ internal void AuthorityLocalSpawn([NotNull] NetworkObject networkObject, ulong n { continue; } - networkObject.Observers.Add(clientId); + networkObject.AddObserver(clientId); } // Sanity check to make sure the owner is always included @@ -1085,7 +1092,7 @@ internal void AuthorityLocalSpawn([NotNull] NetworkObject networkObject, ulong n if (!networkObject.Observers.Contains(ownerClientId)) { Debug.LogError($"Client-{ownerClientId} is the owner of {networkObject.name} but is not an observer! Adding owner, but there is a bug in observer synchronization!"); - networkObject.Observers.Add(ownerClientId); + networkObject.AddObserver(ownerClientId); } } } @@ -1174,7 +1181,7 @@ internal void SpawnNetworkObjectLocallyCommon(NetworkObject networkObject, ulong // If running as a server only, then make sure to always add the server's client identifier if (!NetworkManager.IsHost) { - networkObject.Observers.Add(NetworkManager.LocalClientId); + networkObject.AddObserver(NetworkManager.LocalClientId); } // Add client observers @@ -1185,7 +1192,7 @@ internal void SpawnNetworkObjectLocallyCommon(NetworkObject networkObject, ulong { continue; } - networkObject.Observers.Add(NetworkManager.ConnectedClientsIds[i]); + networkObject.AddObserver(NetworkManager.ConnectedClientsIds[i]); } } @@ -1738,7 +1745,7 @@ internal void UpdateObservedNetworkObjects(ulong clientId) // If the client is not part of the observers and spawn with observers is enabled on this instance or the clientId is the server/host/DAHost if (!sobj.Observers.Contains(clientId) && (sobj.SpawnWithObservers || clientId == NetworkManager.ServerClientId)) { - sobj.Observers.Add(clientId); + sobj.AddObserver(clientId); } } else @@ -1746,7 +1753,7 @@ internal void UpdateObservedNetworkObjects(ulong clientId) // CheckObject visibility overrides SpawnWithObservers under this condition if (sobj.CheckObjectVisibility(clientId)) { - sobj.Observers.Add(clientId); + sobj.AddObserver(clientId); } else // Otherwise, if the observers contains the clientId (shouldn't happen) then remove it since CheckObjectVisibility returned false { From 4747484dd9a24b7318413ca850fa2e78604f6383 Mon Sep 17 00:00:00 2001 From: Noel Stephens Date: Thu, 11 Dec 2025 08:58:27 -0600 Subject: [PATCH 2/3] update Adding change log entry. From 93fe70496550d64cc92ba060adb8e712626fca3e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Chrobot?= <124174716+michalChrobot@users.noreply.github.com> Date: Wed, 17 Dec 2025 09:47:58 +0100 Subject: [PATCH 3/3] Fix/early disconnect due to topology mismatch (#3823) * Updated changelog and package version for Netcode in anticipation of v2.8.0 release * fix Transport topology type mismatch on OSX. * Revert "Updated changelog and package version for Netcode in anticipation of v2.8.0 release" This reverts commit 037fe454149008756133c3ba45336d53d5fb1b31. --------- Co-authored-by: netcode-automation Co-authored-by: Noel Stephens --- .../Runtime/Core/NetworkManager.cs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs b/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs index faa97b87ff..66f1a67e3b 100644 --- a/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs +++ b/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs @@ -313,11 +313,11 @@ internal void NetworkTransformRegistration(NetworkObject networkObject, bool onU private void UpdateTopology() { - var transportTopology = IsListening ? NetworkConfig.NetworkTransport.CurrentTopology() : NetworkConfig.NetworkTopology; + var transportTopology = IsListening && IsConnectedClient ? NetworkConfig.NetworkTransport.CurrentTopology() : NetworkConfig.NetworkTopology; if (transportTopology != NetworkConfig.NetworkTopology) { - NetworkLog.LogErrorServer($"[Topology Mismatch] Transport detected an issue with the topology ({transportTopology} | {NetworkConfig.NetworkTopology}) usage or setting! Disconnecting from session."); - Shutdown(); + NetworkLog.LogErrorServer($"[Topology Mismatch][{transportTopology}:{transportTopology.GetType().Name}][NetworkManager.NetworkConfig:{NetworkConfig.NetworkTopology}] Transport detected an issue with the topology usage or setting! Disconnecting from session."); + Shutdown(true); } else { @@ -504,6 +504,7 @@ public void NetworkUpdate(NetworkUpdateStage updateStage) ShutdownInternal(); } } + } break; }