diff --git a/com.unity.netcode.gameobjects/Runtime/Connection/NetworkConnectionManager.cs b/com.unity.netcode.gameobjects/Runtime/Connection/NetworkConnectionManager.cs index 5c26df4b91..81444f8482 100644 --- a/com.unity.netcode.gameobjects/Runtime/Connection/NetworkConnectionManager.cs +++ b/com.unity.netcode.gameobjects/Runtime/Connection/NetworkConnectionManager.cs @@ -1001,6 +1001,14 @@ internal NetworkClient AddClient(ulong clientId) } var distributedAuthority = NetworkManager.DistributedAuthorityMode; + + // If not using DA return early or if using DA and scene management is disabled then exit early Since we use NetworkShow to spawn + // objects on the newly connected client side. + if (!distributedAuthority || distributedAuthority && !NetworkManager.NetworkConfig.EnableSceneManagement) + { + return networkClient; + } + var sessionOwnerId = NetworkManager.CurrentSessionOwner; var isSessionOwner = NetworkManager.LocalClient.IsSessionOwner; foreach (var networkObject in NetworkManager.SpawnManager.SpawnedObjectsList) diff --git a/com.unity.netcode.gameobjects/Runtime/Core/NetworkBehaviourUpdater.cs b/com.unity.netcode.gameobjects/Runtime/Core/NetworkBehaviourUpdater.cs index 9062ebf113..6a1dd748e6 100644 --- a/com.unity.netcode.gameobjects/Runtime/Core/NetworkBehaviourUpdater.cs +++ b/com.unity.netcode.gameobjects/Runtime/Core/NetworkBehaviourUpdater.cs @@ -141,6 +141,9 @@ private void NetworkBehaviourUpdater_Tick() // Then show any NetworkObjects queued to be made visible/shown m_NetworkManager.SpawnManager.HandleNetworkObjectShow(); + + // Handle object redistribution (DA + disabled scene management only) + m_NetworkManager.HandleRedistributionToClients(); } } } diff --git a/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs b/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs index 56a82bc33f..26be7c9266 100644 --- a/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs +++ b/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs @@ -162,10 +162,30 @@ public bool DAHost } } - // DANGO-TODO-MVP: Remove these properties once the service handles object distribution - internal ulong ClientToRedistribute; - internal bool RedistributeToClient; - internal int TickToRedistribute; + // DANGO-TODO: Determine if this needs to be removed once the service handles object distribution + internal List ClientsToRedistribute = new List(); + internal bool RedistributeToClients; + + /// + /// Handles object redistribution when scene management is disabled. + /// + /// DANGO-TODO: Determine if this needs to be removed once the service handles object distribution + /// + internal void HandleRedistributionToClients() + { + if (!DistributedAuthorityMode || !RedistributeToClients || NetworkConfig.EnableSceneManagement || ShutdownInProgress) + { + return; + } + + foreach (var clientId in ClientsToRedistribute) + { + SpawnManager.DistributeNetworkObjects(clientId); + } + RedistributeToClients = false; + ClientsToRedistribute.Clear(); + } + internal List DeferredDespawnObjects = new List(); @@ -393,16 +413,6 @@ public void NetworkUpdate(NetworkUpdateStage updateStage) // This is "ok" to invoke when not processing messages since it is just cleaning up messages that never got handled within their timeout period. DeferredMessageManager.CleanupStaleTriggers(); - // DANGO-TODO-MVP: Remove this once the service handles object distribution - // NOTE: This needs to be the last thing done and should happen exactly at this point - // in the update - if (RedistributeToClient && ServerTime.Tick <= TickToRedistribute) - { - RedistributeToClient = false; - SpawnManager.DistributeNetworkObjects(ClientToRedistribute); - ClientToRedistribute = 0; - } - if (m_ShuttingDown) { // Host-server will disconnect any connected clients prior to finalizing its shutdown diff --git a/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs b/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs index 74803aa428..661c1ce41b 100644 --- a/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs +++ b/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs @@ -2894,7 +2894,7 @@ internal SceneObject GetMessageSceneObject(ulong targetClientId = NetworkManager SyncObservers = syncObservers, Observers = syncObservers ? Observers.ToArray() : null, NetworkSceneHandle = NetworkSceneHandle, - Hash = HostCheckForGlobalObjectIdHashOverride(), + Hash = CheckForGlobalObjectIdHashOverride(), OwnerObject = this, TargetClientId = targetClientId }; @@ -3246,14 +3246,14 @@ internal void UpdateForSceneChanges() } /// - /// Only applies to Host mode. + /// Only applies to Hosts or session owners (for now) /// Will return the registered source NetworkPrefab's GlobalObjectIdHash if one exists. /// Server and Clients will always return the NetworkObject's GlobalObjectIdHash. /// - /// - internal uint HostCheckForGlobalObjectIdHashOverride() + /// appropriate hash value + internal uint CheckForGlobalObjectIdHashOverride() { - if (NetworkManager.IsServer) + if (NetworkManager.IsServer || (NetworkManager.DistributedAuthorityMode && NetworkManager.LocalClient.IsSessionOwner)) { if (NetworkManager.PrefabHandler.ContainsHandler(this)) { diff --git a/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/ChangeOwnershipMessage.cs b/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/ChangeOwnershipMessage.cs index d418789f4d..cb4f114b91 100644 --- a/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/ChangeOwnershipMessage.cs +++ b/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/ChangeOwnershipMessage.cs @@ -332,8 +332,8 @@ private void HandleOwnershipChange(ref NetworkContext context) // Sanity check that we are not sending duplicated change ownership messages if (networkObject.OwnerClientId == OwnerClientId) { - UnityEngine.Debug.LogError($"Unnecessary ownership changed message for {NetworkObjectId}."); - // Ignore the message + // Log error and then ignore the message + UnityEngine.Debug.LogError($"Client-{context.SenderId} ({RequestClientId}) sent unnecessary ownership changed message for {NetworkObjectId}."); return; } diff --git a/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/ClientConnectedMessage.cs b/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/ClientConnectedMessage.cs index ef7ef5f9a1..0234bc8b67 100644 --- a/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/ClientConnectedMessage.cs +++ b/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/ClientConnectedMessage.cs @@ -55,19 +55,24 @@ public void Handle(ref NetworkContext context) // Don't redistribute for the local instance if (ClientId != networkManager.LocalClientId) { + // Synchronize the client with spawned objects (relative to each client) + networkManager.SpawnManager.SynchronizeObjectsToNewlyJoinedClient(ClientId); + + // Keeping for reference in case the above doesn't resolve for hidden objects (theoretically it should) // Show any NetworkObjects that are: // - Hidden from the session owner // - Owned by this client // - Has NetworkObject.SpawnWithObservers set to true (the default) - if (!networkManager.LocalClient.IsSessionOwner) - { - networkManager.SpawnManager.ShowHiddenObjectsToNewlyJoinedClient(ClientId); - } + //if (!networkManager.LocalClient.IsSessionOwner) + //{ + // networkManager.SpawnManager.ShowHiddenObjectsToNewlyJoinedClient(ClientId); + //} - // We defer redistribution to the end of the NetworkUpdateStage.PostLateUpdate - networkManager.RedistributeToClient = true; - networkManager.ClientToRedistribute = ClientId; - networkManager.TickToRedistribute = networkManager.ServerTime.Tick + 20; + /// We defer redistribution to happen after NetworkShow has been invoked + /// + /// DANGO-TODO: Determine if this needs to be removed once the service handles object distribution + networkManager.RedistributeToClients = true; + networkManager.ClientsToRedistribute.Add(ClientId); } } } diff --git a/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkSpawnManager.cs b/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkSpawnManager.cs index 8bc3517bae..571fe4736e 100644 --- a/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkSpawnManager.cs +++ b/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkSpawnManager.cs @@ -580,7 +580,6 @@ internal void ChangeOwnership(NetworkObject networkObject, ulong clientId, bool { networkObject.ChildNetworkBehaviours[i].UpdateNetworkProperties(); } - size = NetworkManager.ConnectionManager.SendMessage(ref message, NetworkDelivery.ReliableSequenced, NetworkManager.ServerClientId); NetworkManager.NetworkMetrics.TrackOwnershipChangeSent(NetworkManager.LocalClientId, networkObject, size); } @@ -1982,14 +1981,14 @@ internal void NotifyNetworkObjectsSynchronized() /// synchronizing in order to "show" (spawn) anything that might be currently hidden from /// the session owner. /// + /// + /// Replacement is: SynchronizeObjectsToNewlyJoinedClient + /// internal void ShowHiddenObjectsToNewlyJoinedClient(ulong newClientId) { - if (!NetworkManager.DistributedAuthorityMode) + if (NetworkManager == null || NetworkManager.ShutdownInProgress && NetworkManager.LogLevel <= LogLevel.Developer) { - if (NetworkManager == null || !NetworkManager.ShutdownInProgress && NetworkManager.LogLevel <= LogLevel.Developer) - { - Debug.LogWarning($"[Internal Error] {nameof(ShowHiddenObjectsToNewlyJoinedClient)} invoked while !"); - } + Debug.LogWarning($"[Internal Error] {nameof(ShowHiddenObjectsToNewlyJoinedClient)} invoked while shutdown is in progress!"); return; } @@ -2025,5 +2024,46 @@ internal void ShowHiddenObjectsToNewlyJoinedClient(ulong newClientId) } } } + + internal void SynchronizeObjectsToNewlyJoinedClient(ulong newClientId) + { + if (NetworkManager == null || NetworkManager.ShutdownInProgress && NetworkManager.LogLevel <= LogLevel.Developer) + { + Debug.LogWarning($"[Internal Error] {nameof(SynchronizeObjectsToNewlyJoinedClient)} invoked while shutdown is in progress!"); + return; + } + + if (!NetworkManager.DistributedAuthorityMode) + { + Debug.LogError($"[Internal Error] {nameof(SynchronizeObjectsToNewlyJoinedClient)} should only be invoked when using a distributed authority network topology!"); + return; + } + + if (NetworkManager.NetworkConfig.EnableSceneManagement) + { + Debug.LogError($"[Internal Error] {nameof(SynchronizeObjectsToNewlyJoinedClient)} should only be invoked when scene management is disabled!"); + return; + } + + var localClientId = NetworkManager.LocalClient.ClientId; + foreach (var networkObject in SpawnedObjectsList) + { + if (networkObject.SpawnWithObservers && networkObject.OwnerClientId == localClientId) + { + if (networkObject.Observers.Contains(newClientId)) + { + if (NetworkManager.LogLevel <= LogLevel.Developer) + { + // Temporary tracking to make sure we are not showing something already visibile (should never be the case for this) + Debug.LogWarning($"[{nameof(SynchronizeObjectsToNewlyJoinedClient)}][{networkObject.name}] New client as already an observer!"); + } + // For now, remove the client (impossible for the new client to have an instance since the session owner doesn't) to make sure newly added + // code to handle this edge case works. + networkObject.Observers.Remove(newClientId); + } + networkObject.NetworkShow(newClientId); + } + } + } } }