From f5b9aee3fae5c77d684f3cd97fdcdab023b7a987 Mon Sep 17 00:00:00 2001 From: Paolo Abela Date: Wed, 22 Feb 2023 17:17:01 +0100 Subject: [PATCH 1/7] [Feature] added "OnServerStopped" event that will trigger only on the server (or host player) to notify that the server is no longer active. [CI] Added tests for OnServerStopped --- .../Runtime/Core/NetworkManager.cs | 13 ++++- .../Runtime/NetworkManagerEventsTests.cs | 48 +++++++++++++++++++ .../Runtime/NetworkManagerEventsTests.cs.meta | 11 +++++ 3 files changed, 71 insertions(+), 1 deletion(-) create mode 100644 com.unity.netcode.gameobjects/Tests/Runtime/NetworkManagerEventsTests.cs create mode 100644 com.unity.netcode.gameobjects/Tests/Runtime/NetworkManagerEventsTests.cs.meta diff --git a/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs b/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs index 8f518d84f7..5f028c8016 100644 --- a/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs +++ b/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs @@ -413,6 +413,11 @@ public IReadOnlyList ConnectedClientsIds /// public event Action OnServerStarted = null; + /// + /// The callback to invoke once the local server stops + /// + public event Action OnServerStopped = null; + /// /// The callback to invoke if the fails. /// @@ -1155,7 +1160,8 @@ internal void ShutdownInternal() NetworkLog.LogInfo(nameof(ShutdownInternal)); } - if (IsServer) + bool wasServer = IsServer; + if (wasServer) { // make sure all messages are flushed before transport disconnect clients if (MessagingSystem != null) @@ -1278,6 +1284,11 @@ internal void ShutdownInternal() m_StopProcessingMessages = false; ClearClients(); + + if (wasServer) + { + OnServerStopped?.Invoke(); + } } /// diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkManagerEventsTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkManagerEventsTests.cs new file mode 100644 index 0000000000..34f79eb159 --- /dev/null +++ b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkManagerEventsTests.cs @@ -0,0 +1,48 @@ +using System; +using System.Collections; +using UnityEngine; +using UnityEngine.TestTools; +using NUnit.Framework; + +namespace Unity.Netcode.RuntimeTests +{ + public class NetworkManagerEventsTests + { + [UnityTest] + public IEnumerator OnServerStoppedCalledWhenServerStops() + { + bool callbackInvoked = false; + var gameObject = new GameObject(nameof(OnServerStoppedCalledWhenServerStops)); + var networkManager = gameObject.AddComponent(); + + // Set dummy transport that does nothing + var transport = gameObject.AddComponent(); + networkManager.NetworkConfig = new NetworkConfig() { NetworkTransport = transport }; + + Action onServerStopped = () => + { + callbackInvoked = true; + if (networkManager.IsServer) + { + Assert.Fail("OnServerStopped called when the server is still active"); + } + }; + + // Start server to cause initialization process + Assert.True(networkManager.StartServer()); + Assert.True(networkManager.IsListening); + + networkManager.OnServerStopped += onServerStopped; + networkManager.Shutdown(); + UnityEngine.Object.DestroyImmediate(gameObject); + + /* Need two updates to actually shut down. First one to see the transport failing, which + marks the NetworkManager as shutting down. Second one where actual shutdown occurs. */ + yield return null; + yield return null; + + Assert.False(networkManager.IsListening); + Assert.True(callbackInvoked, "OnServerStopped wasn't invoked"); + } + } +} diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkManagerEventsTests.cs.meta b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkManagerEventsTests.cs.meta new file mode 100644 index 0000000000..996ba3bf46 --- /dev/null +++ b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkManagerEventsTests.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 238d8724ba5ce3947bc20f5d6c056b6e +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: From e7f9bf23fb60b54b8838c498043e9cac77848de3 Mon Sep 17 00:00:00 2001 From: Paolo Abela Date: Thu, 23 Feb 2023 10:40:30 +0100 Subject: [PATCH 2/7] feat(documentation): added entry to changelog.md --- 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 bad40daedc..d52843f44c 100644 --- a/com.unity.netcode.gameobjects/CHANGELOG.md +++ b/com.unity.netcode.gameobjects/CHANGELOG.md @@ -10,6 +10,8 @@ Additional documentation and release notes are available at [Multiplayer Documen ### Added +- Added public `OnServerStopped` event that will trigger only on the server (or host player) to notify that the server is no longer active. + ### Changed - The UTP component UI has been updated to be more user-friendly for new users by adding a simple toggle to switch between local-only (127.0.0.1) and remote (0.0.0.0) binding modes, using the toggle "Allow Remote Connections" (#2408) From 62df2355b4633a262bde2df71c9061ff39db2b92 Mon Sep 17 00:00:00 2001 From: Paolo Abela Date: Mon, 13 Mar 2023 13:52:07 +0100 Subject: [PATCH 3/7] feat: Added "OnClientStarted" and "OnClientStopped" events that will trigger only on the client (or host player) to notify that the client just started or is no longer active * test Added a more robust/wider range of tests, that covers the Started/Stopped events for client, server and hosts --- .../Runtime/Core/NetworkManager.cs | 25 +- .../Runtime/NetworkManagerEventsTests.cs | 232 +++++++++++++++++- 2 files changed, 243 insertions(+), 14 deletions(-) diff --git a/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs b/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs index 5f028c8016..ccecc75deb 100644 --- a/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs +++ b/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs @@ -409,14 +409,26 @@ public IReadOnlyList ConnectedClientsIds public event Action OnClientDisconnectCallback = null; /// - /// The callback to invoke once the server is ready + /// The callback to invoke once the local server is ready /// public event Action OnServerStarted = null; + /// + /// The callback to invoke once the local client is ready + /// + public event Action OnClientStarted = null; + /// /// The callback to invoke once the local server stops /// - public event Action OnServerStopped = null; + /// The parameter states whether the server was running in host mode + public event Action OnServerStopped = null; + + /// + /// The callback to invoke once the local client stops + /// + /// The parameter states whether the client was running in host mode + public event Action OnClientStopped = null; /// /// The callback to invoke if the fails. @@ -873,6 +885,7 @@ public bool StartClient() IsClient = true; IsListening = true; + OnClientStarted?.Invoke(); return true; } @@ -960,6 +973,7 @@ public bool StartHost() InvokeOnClientConnectedCallback(LocalClientId); OnServerStarted?.Invoke(); + OnClientStarted?.Invoke(); return true; } @@ -1161,6 +1175,7 @@ internal void ShutdownInternal() } bool wasServer = IsServer; + bool wasClient = IsClient; if (wasServer) { // make sure all messages are flushed before transport disconnect clients @@ -1285,9 +1300,13 @@ internal void ShutdownInternal() ClearClients(); + if (wasClient) + { + OnClientStopped?.Invoke(wasServer); + } if (wasServer) { - OnServerStopped?.Invoke(); + OnServerStopped?.Invoke(wasClient); } } diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkManagerEventsTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkManagerEventsTests.cs index 34f79eb159..b509255578 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkManagerEventsTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkManagerEventsTests.cs @@ -3,46 +3,256 @@ using UnityEngine; using UnityEngine.TestTools; using NUnit.Framework; +using Unity.Netcode.TestHelpers.Runtime; namespace Unity.Netcode.RuntimeTests { public class NetworkManagerEventsTests { + private NetworkManager m_ClientManager; + private NetworkManager m_ServerManager; + [UnityTest] public IEnumerator OnServerStoppedCalledWhenServerStops() { bool callbackInvoked = false; var gameObject = new GameObject(nameof(OnServerStoppedCalledWhenServerStops)); - var networkManager = gameObject.AddComponent(); + m_ServerManager = gameObject.AddComponent(); // Set dummy transport that does nothing var transport = gameObject.AddComponent(); - networkManager.NetworkConfig = new NetworkConfig() { NetworkTransport = transport }; + m_ServerManager.NetworkConfig = new NetworkConfig() { NetworkTransport = transport }; - Action onServerStopped = () => + Action onServerStopped = (bool wasAlsoClient) => { callbackInvoked = true; - if (networkManager.IsServer) + Assert.IsFalse(wasAlsoClient); + if (m_ServerManager.IsServer) { Assert.Fail("OnServerStopped called when the server is still active"); } }; // Start server to cause initialization process - Assert.True(networkManager.StartServer()); - Assert.True(networkManager.IsListening); + Assert.True(m_ServerManager.StartServer()); + Assert.True(m_ServerManager.IsListening); - networkManager.OnServerStopped += onServerStopped; - networkManager.Shutdown(); + m_ServerManager.OnServerStopped += onServerStopped; + m_ServerManager.Shutdown(); UnityEngine.Object.DestroyImmediate(gameObject); + yield return WaitUntilManagerShutsdown(); + + Assert.False(m_ServerManager.IsListening); + Assert.True(callbackInvoked, "OnServerStopped wasn't invoked"); + } + + [UnityTest] + public IEnumerator OnClientStoppedCalledWhenClientStops() + { + yield return InitializeServerAndAClient(); + + bool callbackInvoked = false; + Action onClientStopped = (bool wasAlsoServer) => + { + callbackInvoked = true; + Assert.IsFalse(wasAlsoServer); + if (m_ClientManager.IsClient) + { + Assert.Fail("onClientStopped called when the client is still active"); + } + }; + + m_ClientManager.OnClientStopped += onClientStopped; + m_ClientManager.Shutdown(); + yield return WaitUntilManagerShutsdown(); + + Assert.True(callbackInvoked, "OnClientStopped wasn't invoked"); + } + + [UnityTest] + public IEnumerator OnClientAndServerStoppedCalledWhenHostStops() + { + var gameObject = new GameObject(nameof(OnClientAndServerStoppedCalledWhenHostStops)); + m_ServerManager = gameObject.AddComponent(); + + // Set dummy transport that does nothing + var transport = gameObject.AddComponent(); + m_ServerManager.NetworkConfig = new NetworkConfig() { NetworkTransport = transport }; + + int callbacksInvoked = 0; + Action onClientStopped = (bool wasAlsoServer) => + { + callbacksInvoked++; + Assert.IsTrue(wasAlsoServer); + if (m_ServerManager.IsClient) + { + Assert.Fail("onClientStopped called when the client is still active"); + } + }; + + Action onServerStopped = (bool wasAlsoClient) => + { + callbacksInvoked++; + Assert.IsTrue(wasAlsoClient); + if (m_ServerManager.IsServer) + { + Assert.Fail("OnServerStopped called when the server is still active"); + } + }; + + // Start server to cause initialization process + Assert.True(m_ServerManager.StartHost()); + Assert.True(m_ServerManager.IsListening); + + m_ServerManager.OnServerStopped += onServerStopped; + m_ServerManager.OnClientStopped += onClientStopped; + m_ServerManager.Shutdown(); + UnityEngine.Object.DestroyImmediate(gameObject); + + yield return WaitUntilManagerShutsdown(); + + Assert.False(m_ServerManager.IsListening); + Assert.AreEqual(2, callbacksInvoked, "either OnServerStopped or OnClientStopped wasn't invoked"); + } + + [UnityTest] + public IEnumerator OnServerStartedCalledWhenServerStarts() + { + var gameObject = new GameObject(nameof(OnServerStartedCalledWhenServerStarts)); + m_ServerManager = gameObject.AddComponent(); + + // Set dummy transport that does nothing + var transport = gameObject.AddComponent(); + m_ServerManager.NetworkConfig = new NetworkConfig() { NetworkTransport = transport }; + + bool callbackInvoked = false; + Action onServerStarted = () => + { + callbackInvoked = true; + if (!m_ServerManager.IsServer) + { + Assert.Fail("OnServerStarted called when the server is not active yet"); + } + }; + + // Start server to cause initialization process + m_ServerManager.OnServerStarted += onServerStarted; + + Assert.True(m_ServerManager.StartServer()); + Assert.True(m_ServerManager.IsListening); + + yield return WaitUntilServerBufferingIsReady(); + + Assert.True(callbackInvoked, "OnServerStarted wasn't invoked"); + } + + [UnityTest] + public IEnumerator OnClientStartedCalledWhenClientStarts() + { + bool callbackInvoked = false; + Action onClientStarted = () => + { + callbackInvoked = true; + if (!m_ClientManager.IsClient) + { + Assert.Fail("onClientStarted called when the client is not active yet"); + } + }; + + yield return InitializeServerAndAClient(onClientStarted); + + Assert.True(callbackInvoked, "OnClientStarted wasn't invoked"); + } + + [UnityTest] + public IEnumerator OnClientAndServerStartedCalledWhenHostStarts() + { + var gameObject = new GameObject(nameof(OnClientAndServerStartedCalledWhenHostStarts)); + m_ServerManager = gameObject.AddComponent(); + + // Set dummy transport that does nothing + var transport = gameObject.AddComponent(); + m_ServerManager.NetworkConfig = new NetworkConfig() { NetworkTransport = transport }; + + int callbacksInvoked = 0; + Action onClientStarted = () => + { + callbacksInvoked++; + if (!m_ServerManager.IsClient) + { + Assert.Fail("OnClientStarted called when the client is not active yet"); + } + }; + + Action onServerStarted = () => + { + callbacksInvoked++; + if (!m_ServerManager.IsServer) + { + Assert.Fail("OnServerStarted called when the server is not active yet"); + } + }; + + m_ServerManager.OnServerStarted += onServerStarted; + m_ServerManager.OnClientStarted += onClientStarted; + + // Start server to cause initialization process + Assert.True(m_ServerManager.StartHost()); + Assert.True(m_ServerManager.IsListening); + + yield return WaitUntilServerBufferingIsReady(); + Assert.AreEqual(2, callbacksInvoked, "either OnServerStarted or OnClientStarted wasn't invoked"); + } + + private IEnumerator WaitUntilManagerShutsdown() + { /* Need two updates to actually shut down. First one to see the transport failing, which - marks the NetworkManager as shutting down. Second one where actual shutdown occurs. */ + marks the NetworkManager as shutting down. Second one where actual shutdown occurs. */ yield return null; yield return null; + } - Assert.False(networkManager.IsListening); - Assert.True(callbackInvoked, "OnServerStopped wasn't invoked"); + private IEnumerator InitializeServerAndAClient(Action onClientStarted = null) + { + // Create multiple NetworkManager instances + if (!NetcodeIntegrationTestHelpers.Create(1, out m_ServerManager, out NetworkManager[] clients, 30)) + { + Debug.LogError("Failed to create instances"); + Assert.Fail("Failed to create instances"); + } + + // passing no clients on purpose to start them manually later + NetcodeIntegrationTestHelpers.Start(false, m_ServerManager, new NetworkManager[] { }); + + yield return WaitUntilServerBufferingIsReady(); + m_ClientManager = clients[0]; + + if (onClientStarted != null) + { + m_ClientManager.OnClientStarted += onClientStarted; + } + + Assert.True(m_ClientManager.StartClient()); + NetcodeIntegrationTestHelpers.RegisterHandlers(clients[0]); + // Wait for connection on client side + yield return NetcodeIntegrationTestHelpers.WaitForClientsConnected(clients); + } + + private IEnumerator WaitUntilServerBufferingIsReady() + { + /* wait until at least more than 2 server ticks have passed + Note: Waiting for more than 2 ticks on the server is due + to the time system applying buffering to the received time + in NetworkTimeSystem.Sync */ + yield return new WaitUntil(() => m_ServerManager.NetworkTickSystem.ServerTime.Tick > 2); + } + + [UnityTearDown] + public virtual IEnumerator Teardown() + { + NetcodeIntegrationTestHelpers.Destroy(); + yield return null; } } } From 49c2837dd07409069e410c44a957577a94146d56 Mon Sep 17 00:00:00 2001 From: Paolo Abela Date: Mon, 13 Mar 2023 13:55:15 +0100 Subject: [PATCH 4/7] feat(documentation): added entry to changelog.md --- com.unity.netcode.gameobjects/CHANGELOG.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/com.unity.netcode.gameobjects/CHANGELOG.md b/com.unity.netcode.gameobjects/CHANGELOG.md index d52843f44c..335038155e 100644 --- a/com.unity.netcode.gameobjects/CHANGELOG.md +++ b/com.unity.netcode.gameobjects/CHANGELOG.md @@ -10,7 +10,8 @@ Additional documentation and release notes are available at [Multiplayer Documen ### Added -- Added public `OnServerStopped` event that will trigger only on the server (or host player) to notify that the server is no longer active. +- Added `OnServerStarted` and `OnServerStopped` events that will trigger only on the server (or host player) to notify that the server just started or is no longer active +- Added `OnClientStarted` and `OnClientStopped` events that will trigger only on the client (or host player) to notify that the client just started or is no longer active ### Changed From 62b1a181ad147a068e40f46ab5372cd8d8ff35d7 Mon Sep 17 00:00:00 2001 From: Paolo Abela Date: Mon, 13 Mar 2023 19:28:21 +0100 Subject: [PATCH 5/7] chore: fixed line endings --- com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs | 2 +- .../Tests/Runtime/NetworkManagerEventsTests.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs b/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs index a9c86faa6b..51bf0630a9 100644 --- a/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs +++ b/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs @@ -1318,7 +1318,7 @@ internal void ShutdownInternal() { OnServerStopped?.Invoke(wasClient); } - + // This cleans up the internal prefabs list NetworkConfig?.Prefabs.Shutdown(); } diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkManagerEventsTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkManagerEventsTests.cs index b509255578..2544de9bc5 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkManagerEventsTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkManagerEventsTests.cs @@ -164,7 +164,7 @@ public IEnumerator OnClientStartedCalledWhenClientStarts() Assert.True(callbackInvoked, "OnClientStarted wasn't invoked"); } - + [UnityTest] public IEnumerator OnClientAndServerStartedCalledWhenHostStarts() { From 9810c25d1e6a7d2f3b3911713add6e867ff87bfb Mon Sep 17 00:00:00 2001 From: Paolo Abela Date: Mon, 13 Mar 2023 19:47:50 +0100 Subject: [PATCH 6/7] documentation: updated documentation --- com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs b/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs index 51bf0630a9..a9865dc0ba 100644 --- a/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs +++ b/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs @@ -409,7 +409,7 @@ public IReadOnlyList ConnectedClientsIds public event Action OnClientDisconnectCallback = null; /// - /// The callback to invoke once the local server is ready + /// This callback is invoked when the local server is started and listening for incoming connections. /// public event Action OnServerStarted = null; @@ -419,7 +419,7 @@ public IReadOnlyList ConnectedClientsIds public event Action OnClientStarted = null; /// - /// The callback to invoke once the local server stops + /// This callback is invoked once the local server is stopped. /// /// The parameter states whether the server was running in host mode public event Action OnServerStopped = null; From d4f918ca7a593dea7f71f35e70eea8ad13a6c2df Mon Sep 17 00:00:00 2001 From: Paolo Abela Date: Tue, 14 Mar 2023 09:13:58 +0100 Subject: [PATCH 7/7] fix: fixed OnServerStarted and OnClientStarted being called at different times in Host vs Client-only / Server-only mode --- .../Runtime/Core/NetworkManager.cs | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs b/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs index a9865dc0ba..1e50da5a7d 100644 --- a/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs +++ b/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs @@ -421,13 +421,14 @@ public IReadOnlyList ConnectedClientsIds /// /// This callback is invoked once the local server is stopped. /// - /// The parameter states whether the server was running in host mode + /// The first parameter of this event will be set to when stopping a host instance and when stopping a server instance. public event Action OnServerStopped = null; /// /// The callback to invoke once the local client stops /// /// The parameter states whether the client was running in host mode + /// The first parameter of this event will be set to when stopping the host client and when stopping a standard client instance. public event Action OnClientStopped = null; /// @@ -967,14 +968,14 @@ public bool StartHost() SpawnManager.ServerSpawnSceneObjectsOnStartSweep(); + OnServerStarted?.Invoke(); + OnClientStarted?.Invoke(); + // This assures that any in-scene placed NetworkObject is spawned and // any associated NetworkBehaviours' netcode related properties are // set prior to invoking OnClientConnected. InvokeOnClientConnectedCallback(LocalClientId); - OnServerStarted?.Invoke(); - OnClientStarted?.Invoke(); - return true; }