diff --git a/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs b/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs index 1af4e45b31..89be713d52 100644 --- a/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs +++ b/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs @@ -1495,14 +1495,17 @@ internal void HandleApproval(ulong ownerClientId, bool createPlayerObject, uint? { SceneManager.SynchronizeNetworkObjects(ownerClientId); } + else + { + InvokeOnClientConnectedCallback(ownerClientId); + } } else // Server just adds itself as an observer to all spawned NetworkObjects { SpawnManager.UpdateObservedNetworkObjects(ownerClientId); + InvokeOnClientConnectedCallback(ownerClientId); } - OnClientConnectedCallback?.Invoke(ownerClientId); - if (!createPlayerObject || (playerPrefabHash == null && NetworkConfig.PlayerPrefab == null)) { return; diff --git a/com.unity.netcode.gameobjects/Runtime/Messaging/InternalMessageHandler.cs b/com.unity.netcode.gameobjects/Runtime/Messaging/InternalMessageHandler.cs index c4af496fc7..52cb61f30a 100644 --- a/com.unity.netcode.gameobjects/Runtime/Messaging/InternalMessageHandler.cs +++ b/com.unity.netcode.gameobjects/Runtime/Messaging/InternalMessageHandler.cs @@ -76,6 +76,11 @@ public void HandleConnectionApproved(ulong clientId, Stream stream, float receiv { NetworkObject.DeserializeSceneObject(reader.GetStream() as NetworkBuffer, reader, m_NetworkManager); } + + // Mark the client being connected + m_NetworkManager.IsConnectedClient = true; + // When scene management is disabled we notify after everything is synchronized + m_NetworkManager.InvokeOnClientConnectedCallback(clientId); } } diff --git a/com.unity.netcode.gameobjects/Runtime/SceneManagement/NetworkSceneManager.cs b/com.unity.netcode.gameobjects/Runtime/SceneManagement/NetworkSceneManager.cs index 04a0760bca..a54bba17be 100644 --- a/com.unity.netcode.gameobjects/Runtime/SceneManagement/NetworkSceneManager.cs +++ b/com.unity.netcode.gameobjects/Runtime/SceneManagement/NetworkSceneManager.cs @@ -547,7 +547,7 @@ private SceneEventProgress ValidateSceneEvent(string sceneName, bool isUnloading } var sceneEventProgress = new SceneEventProgress(m_NetworkManager); - sceneEventProgress.SceneIndex = GetBuildIndexFromSceneName(sceneName); + sceneEventProgress.SceneBuildIndex = GetBuildIndexFromSceneName(sceneName); SceneEventProgressTracking.Add(sceneEventProgress.Guid, sceneEventProgress); if (!isUnloading) @@ -582,7 +582,7 @@ private bool OnSceneEventProgressCompleted(SceneEventProgress sceneEventProgress { using var nonNullContext = (InternalCommandContext)context; ClientSynchEventData.SceneEventGuid = sceneEventProgress.Guid; - ClientSynchEventData.SceneIndex = sceneEventProgress.SceneIndex; + ClientSynchEventData.SceneIndex = sceneEventProgress.SceneBuildIndex; ClientSynchEventData.SceneEventType = sceneEventProgress.SceneEventType; ClientSynchEventData.ClientsCompleted = sceneEventProgress.DoneClients; ClientSynchEventData.ClientsTimedOut = m_NetworkManager.ConnectedClients.Keys.Except(sceneEventProgress.DoneClients).ToList(); @@ -596,7 +596,7 @@ private bool OnSceneEventProgressCompleted(SceneEventProgress sceneEventProgress m_NetworkManager.NetworkMetrics.TrackSceneEventSent( m_NetworkManager.ConnectedClientsIds, (uint)sceneEventProgress.SceneEventType, - ScenesInBuild[(int)sceneEventProgress.SceneIndex], + ScenesInBuild[(int)sceneEventProgress.SceneBuildIndex], size); } @@ -604,7 +604,7 @@ private bool OnSceneEventProgressCompleted(SceneEventProgress sceneEventProgress OnSceneEvent?.Invoke(new SceneEvent() { SceneEventType = sceneEventProgress.SceneEventType, - SceneName = ScenesInBuild[(int)sceneEventProgress.SceneIndex], + SceneName = ScenesInBuild[(int)sceneEventProgress.SceneBuildIndex], ClientId = m_NetworkManager.ServerClientId, LoadSceneMode = sceneEventProgress.LoadSceneMode, ClientsThatCompleted = sceneEventProgress.DoneClients, @@ -1377,7 +1377,6 @@ private void HandleClientSceneEvent(Stream stream) // All scenes are synchronized, let the server know we are done synchronizing m_NetworkManager.IsConnectedClient = true; - m_NetworkManager.InvokeOnClientConnectedCallback(m_NetworkManager.LocalClientId); // Notify the client that they have finished synchronizing OnSceneEvent?.Invoke(new SceneEvent() @@ -1385,6 +1384,9 @@ private void HandleClientSceneEvent(Stream stream) SceneEventType = SceneEventData.SceneEventType, ClientId = m_NetworkManager.LocalClientId, // Client sent this to the server }); + + // Client is now synchronized and fully "connected". This also means the client can send "RPCs" at this time + m_NetworkManager.InvokeOnClientConnectedCallback(m_NetworkManager.LocalClientId); } break; } @@ -1477,6 +1479,10 @@ private void HandleServerSceneEvent(ulong clientId, Stream stream) ClientId = clientId }); + // While we did invoke the C2S_SyncComplete event notification, we will also call the traditional client connected callback on the server + // which assures the client is "ready to receive RPCs" as well. + m_NetworkManager.InvokeOnClientConnectedCallback(clientId); + if (SceneEventData.ClientNeedsReSynchronization() && !DisableReSynchronization) { SceneEventData.SceneEventType = SceneEventData.SceneEventTypes.S2C_ReSync; diff --git a/com.unity.netcode.gameobjects/Runtime/SceneManagement/SceneEventProgress.cs b/com.unity.netcode.gameobjects/Runtime/SceneManagement/SceneEventProgress.cs index d38df4ddfb..f9f2010217 100644 --- a/com.unity.netcode.gameobjects/Runtime/SceneManagement/SceneEventProgress.cs +++ b/com.unity.netcode.gameobjects/Runtime/SceneManagement/SceneEventProgress.cs @@ -88,7 +88,7 @@ internal class SceneEventProgress /// internal bool AreAllClientsDoneLoading { get; private set; } - internal uint SceneIndex { get; set; } + internal uint SceneBuildIndex { get; set; } internal Guid Guid { get; } = Guid.NewGuid(); diff --git a/com.unity.netcode.gameobjects/Runtime/Serialization/Pooled/NetworkBufferPool.cs b/com.unity.netcode.gameobjects/Runtime/Serialization/Pooled/NetworkBufferPool.cs index 1e35b991b6..b1cc8186f2 100644 --- a/com.unity.netcode.gameobjects/Runtime/Serialization/Pooled/NetworkBufferPool.cs +++ b/com.unity.netcode.gameobjects/Runtime/Serialization/Pooled/NetworkBufferPool.cs @@ -13,7 +13,7 @@ public static class NetworkBufferPool private static Queue s_Buffers = new Queue(); private const uint k_MaxBitPoolBuffers = 1024; - private const uint k_MaxCreatedDelta = 768; + private const uint k_MaxCreatedDelta = 512; /// diff --git a/testproject/Assets/Tests/Runtime/MultiClientConnectionApproval.cs b/testproject/Assets/Tests/Runtime/MultiClientConnectionApproval.cs index 04b624fed3..c898b189be 100644 --- a/testproject/Assets/Tests/Runtime/MultiClientConnectionApproval.cs +++ b/testproject/Assets/Tests/Runtime/MultiClientConnectionApproval.cs @@ -187,6 +187,60 @@ private void ConnectionApprovalCallback(byte[] connectionData, ulong clientId, N } } + + + private int m_ServerClientConnectedInvocations; + + private int m_ClientConnectedInvocations; + + /// + /// Tests that the OnClientConnectedCallback is invoked when scene management is enabled and disabled + /// + /// + [UnityTest] + public IEnumerator ClientConnectedCallbackTest([Values(true, false)] bool enableSceneManagement) + { + m_ServerClientConnectedInvocations = 0; + m_ClientConnectedInvocations = 0; + + // Create Host and (numClients) clients + Assert.True(MultiInstanceHelpers.Create(3, out NetworkManager server, out NetworkManager[] clients)); + + server.NetworkConfig.EnableSceneManagement = enableSceneManagement; + server.OnClientConnectedCallback += Server_OnClientConnectedCallback; + + foreach (var client in clients) + { + client.NetworkConfig.EnableSceneManagement = enableSceneManagement; + client.OnClientConnectedCallback += Client_OnClientConnectedCallback; + } + + // Start the instances + if (!MultiInstanceHelpers.Start(true, server, clients)) + { + Assert.Fail("Failed to start instances"); + } + + // [Client-Side] Wait for a connection to the server + yield return MultiInstanceHelpers.Run(MultiInstanceHelpers.WaitForClientsConnected(clients, null, 512)); + + // [Host-Side] Check to make sure all clients are connected + yield return MultiInstanceHelpers.Run(MultiInstanceHelpers.WaitForClientsConnectedToServer(server, clients.Length + 1, null, 512)); + + Assert.True(m_ClientConnectedInvocations == 3); + Assert.True(m_ServerClientConnectedInvocations == 4); + } + + private void Client_OnClientConnectedCallback(ulong clientId) + { + m_ClientConnectedInvocations++; + } + + private void Server_OnClientConnectedCallback(ulong clientId) + { + m_ServerClientConnectedInvocations++; + } + [TearDown] public void TearDown() { diff --git a/testproject/Assets/Tests/Runtime/ObjectParenting/NetworkObjectParentingTests.cs b/testproject/Assets/Tests/Runtime/ObjectParenting/NetworkObjectParentingTests.cs index 47120d4e5c..8ce7a0eeb5 100644 --- a/testproject/Assets/Tests/Runtime/ObjectParenting/NetworkObjectParentingTests.cs +++ b/testproject/Assets/Tests/Runtime/ObjectParenting/NetworkObjectParentingTests.cs @@ -51,8 +51,6 @@ public IEnumerator Setup() { SceneManager.sceneLoaded += OnSceneLoaded; - // We need NetworkManager to be instantiated first before you load scenes externally in order to be able to determine if - // we are running a unit test or not. (it is this or manually setting a property) Assert.That(MultiInstanceHelpers.Create(k_ClientInstanceCount, out m_ServerNetworkManager, out m_ClientNetworkManagers)); const string scenePath = "Assets/Tests/Runtime/ObjectParenting/" + nameof(NetworkObjectParentingTests) + ".unity";