diff --git a/com.unity.netcode.gameobjects/CHANGELOG.md b/com.unity.netcode.gameobjects/CHANGELOG.md index 38656d18cd..b3bd320d69 100644 --- a/com.unity.netcode.gameobjects/CHANGELOG.md +++ b/com.unity.netcode.gameobjects/CHANGELOG.md @@ -28,6 +28,7 @@ Additional documentation and release notes are available at [Multiplayer Documen ### Fixed +- Fixed issue where `NetworkManager` was not cleaning itself up if an exception was thrown while starting. (#3864) - Fixed memory leak in `NetworkAnimator` on clients where `RpcTarget` groups were not being properly disposed due to incorrect type casting of `ProxyRpcTargetGroup` to `RpcTargetGroup`. - Fixed issue when using a client-server topology where a `NetworkList` with owner write permissions was resetting sent time and dirty flags after having been spawned on owning clients that were not the spawn authority. (#3850) - Fixed an integer overflow that occurred when configuring a large disconnect timeout with Unity Transport. (#3810) diff --git a/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs b/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs index 3eeab3649c..c0633da1f4 100644 --- a/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs +++ b/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs @@ -1323,7 +1323,17 @@ public bool StartServer() } ConnectionManager.LocalClient.ClientId = ServerClientId; - Initialize(true); + try + { + Initialize(true); + } + catch (Exception ex) + { + Debug.LogException(ex); + // Always shutdown to assure everything is cleaned up + ShutdownInternal(); + return false; + } try { @@ -1342,11 +1352,12 @@ public bool StartServer() ConnectionManager.TransportFailureEventHandler(true); } - catch (Exception) + catch (Exception ex) { - ConnectionManager.LocalClient.SetRole(false, false); + Debug.LogException(ex); + // Always shutdown to assure everything is cleaned up + ShutdownInternal(); IsListening = false; - throw; } return IsListening; @@ -1373,7 +1384,16 @@ public bool StartClient() return false; } - Initialize(false); + try + { + Initialize(false); + } + catch (Exception ex) + { + Debug.LogException(ex); + ShutdownInternal(); + return false; + } try { @@ -1391,7 +1411,7 @@ public bool StartClient() catch (Exception ex) { Debug.LogException(ex); - ConnectionManager.LocalClient.SetRole(false, false); + ShutdownInternal(); IsListening = false; } @@ -1419,7 +1439,18 @@ public bool StartHost() return false; } - Initialize(true); + try + { + Initialize(true); + } + catch (Exception ex) + { + Debug.LogException(ex); + // Always shutdown to assure everything is cleaned up + ShutdownInternal(); + return false; + } + try { IsListening = NetworkConfig.NetworkTransport.StartServer(); @@ -1437,7 +1468,8 @@ public bool StartHost() catch (Exception ex) { Debug.LogException(ex); - ConnectionManager.LocalClient.SetRole(false, false); + // Always shutdown to assure everything is cleaned up + ShutdownInternal(); IsListening = false; } diff --git a/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkPrefabHandler.cs b/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkPrefabHandler.cs index 7b8632d3c3..c8aab939f4 100644 --- a/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkPrefabHandler.cs +++ b/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkPrefabHandler.cs @@ -287,7 +287,15 @@ internal NetworkObject HandleNetworkPrefabSpawn(uint networkPrefabAssetHash, ulo { if (m_PrefabAssetToPrefabHandlerWithData.TryGetValue(networkPrefabAssetHash, out var prefabInstanceHandler)) { - networkObjectInstance = prefabInstanceHandler.Instantiate(ownerClientId, position, rotation, instantiationData); + try + { + networkObjectInstance = prefabInstanceHandler.Instantiate(ownerClientId, position, rotation, instantiationData); + } + catch (Exception ex) + { + Debug.LogException(ex); + return null; + } } else { @@ -297,7 +305,15 @@ internal NetworkObject HandleNetworkPrefabSpawn(uint networkPrefabAssetHash, ulo } else if (m_PrefabAssetToPrefabHandler.TryGetValue(networkPrefabAssetHash, out var prefabInstanceHandler)) { - networkObjectInstance = prefabInstanceHandler.Instantiate(ownerClientId, position, rotation); + try + { + networkObjectInstance = prefabInstanceHandler.Instantiate(ownerClientId, position, rotation); + } + catch (Exception ex) + { + Debug.LogException(ex); + return null; + } } // Now we must make sure this alternate PrefabAsset spawned in place of the prefab asset with the networkPrefabAssetHash (GlobalObjectIdHash) diff --git a/com.unity.netcode.gameobjects/Tests/Editor/Transports/UnityTransportTests.cs b/com.unity.netcode.gameobjects/Tests/Editor/Transports/UnityTransportTests.cs index 594467efc2..cfab238370 100644 --- a/com.unity.netcode.gameobjects/Tests/Editor/Transports/UnityTransportTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Editor/Transports/UnityTransportTests.cs @@ -154,21 +154,21 @@ public void UnityTransport_StartServerWithoutAddresses() [Test] public void UnityTransport_EmptySecurityStringsShouldThrow([Values("", null)] string cert, [Values("", null)] string secret) { - var supportingGO = new GameObject(); + var supportingGo = new GameObject(); try { - var networkManager = supportingGO.AddComponent(); // NM is required for UTP to work with certificates. + var networkManager = supportingGo.AddComponent(); // NM is required for UTP to work with certificates. networkManager.NetworkConfig = new NetworkConfig(); - UnityTransport transport = supportingGO.AddComponent(); + UnityTransport transport = supportingGo.AddComponent(); networkManager.NetworkConfig.NetworkTransport = transport; - transport.Initialize(); + transport.Initialize(networkManager); transport.SetServerSecrets(serverCertificate: cert, serverPrivateKey: secret); // Use encryption, but don't set certificate and check for exception transport.UseEncryption = true; Assert.Throws(() => { - networkManager.StartServer(); + transport.StartServer(); }); // Make sure StartServer failed Assert.False(transport.GetNetworkDriver().IsCreated); @@ -177,9 +177,9 @@ public void UnityTransport_EmptySecurityStringsShouldThrow([Values("", null)] st } finally { - if (supportingGO != null) + if (supportingGo != null) { - Object.DestroyImmediate(supportingGO); + Object.DestroyImmediate(supportingGo); } } } diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/ClientApprovalDenied.cs b/com.unity.netcode.gameobjects/Tests/Runtime/Connection/ClientApprovalDenied.cs similarity index 100% rename from com.unity.netcode.gameobjects/Tests/Runtime/ClientApprovalDenied.cs rename to com.unity.netcode.gameobjects/Tests/Runtime/Connection/ClientApprovalDenied.cs diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/ClientApprovalDenied.cs.meta b/com.unity.netcode.gameobjects/Tests/Runtime/Connection/ClientApprovalDenied.cs.meta similarity index 100% rename from com.unity.netcode.gameobjects/Tests/Runtime/ClientApprovalDenied.cs.meta rename to com.unity.netcode.gameobjects/Tests/Runtime/Connection/ClientApprovalDenied.cs.meta diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/ClientOnlyConnectionTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/Connection/ClientOnlyConnectionTests.cs similarity index 100% rename from com.unity.netcode.gameobjects/Tests/Runtime/ClientOnlyConnectionTests.cs rename to com.unity.netcode.gameobjects/Tests/Runtime/Connection/ClientOnlyConnectionTests.cs diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/ClientOnlyConnectionTests.cs.meta b/com.unity.netcode.gameobjects/Tests/Runtime/Connection/ClientOnlyConnectionTests.cs.meta similarity index 100% rename from com.unity.netcode.gameobjects/Tests/Runtime/ClientOnlyConnectionTests.cs.meta rename to com.unity.netcode.gameobjects/Tests/Runtime/Connection/ClientOnlyConnectionTests.cs.meta diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/ConnectionApproval.cs b/com.unity.netcode.gameobjects/Tests/Runtime/Connection/ConnectionApproval.cs similarity index 100% rename from com.unity.netcode.gameobjects/Tests/Runtime/ConnectionApproval.cs rename to com.unity.netcode.gameobjects/Tests/Runtime/Connection/ConnectionApproval.cs diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/ConnectionApproval.cs.meta b/com.unity.netcode.gameobjects/Tests/Runtime/Connection/ConnectionApproval.cs.meta similarity index 100% rename from com.unity.netcode.gameobjects/Tests/Runtime/ConnectionApproval.cs.meta rename to com.unity.netcode.gameobjects/Tests/Runtime/Connection/ConnectionApproval.cs.meta diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/ConnectionApprovalTimeoutTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/Connection/ConnectionApprovalTimeoutTests.cs similarity index 100% rename from com.unity.netcode.gameobjects/Tests/Runtime/ConnectionApprovalTimeoutTests.cs rename to com.unity.netcode.gameobjects/Tests/Runtime/Connection/ConnectionApprovalTimeoutTests.cs diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/ConnectionApprovalTimeoutTests.cs.meta b/com.unity.netcode.gameobjects/Tests/Runtime/Connection/ConnectionApprovalTimeoutTests.cs.meta similarity index 100% rename from com.unity.netcode.gameobjects/Tests/Runtime/ConnectionApprovalTimeoutTests.cs.meta rename to com.unity.netcode.gameobjects/Tests/Runtime/Connection/ConnectionApprovalTimeoutTests.cs.meta diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/DisconnectTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/Connection/DisconnectTests.cs similarity index 100% rename from com.unity.netcode.gameobjects/Tests/Runtime/DisconnectTests.cs rename to com.unity.netcode.gameobjects/Tests/Runtime/Connection/DisconnectTests.cs diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/DisconnectTests.cs.meta b/com.unity.netcode.gameobjects/Tests/Runtime/Connection/DisconnectTests.cs.meta similarity index 100% rename from com.unity.netcode.gameobjects/Tests/Runtime/DisconnectTests.cs.meta rename to com.unity.netcode.gameobjects/Tests/Runtime/Connection/DisconnectTests.cs.meta diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/InvalidConnectionEventsTest.cs b/com.unity.netcode.gameobjects/Tests/Runtime/Connection/InvalidConnectionEventsTest.cs similarity index 100% rename from com.unity.netcode.gameobjects/Tests/Runtime/InvalidConnectionEventsTest.cs rename to com.unity.netcode.gameobjects/Tests/Runtime/Connection/InvalidConnectionEventsTest.cs diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/InvalidConnectionEventsTest.cs.meta b/com.unity.netcode.gameobjects/Tests/Runtime/Connection/InvalidConnectionEventsTest.cs.meta similarity index 100% rename from com.unity.netcode.gameobjects/Tests/Runtime/InvalidConnectionEventsTest.cs.meta rename to com.unity.netcode.gameobjects/Tests/Runtime/Connection/InvalidConnectionEventsTest.cs.meta diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkManagerStartExceptionTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkManagerStartExceptionTests.cs new file mode 100644 index 0000000000..e77f3b59b6 --- /dev/null +++ b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkManagerStartExceptionTests.cs @@ -0,0 +1,130 @@ +using System; +using System.Collections; +using NUnit.Framework; +using Unity.Netcode.TestHelpers.Runtime; +using Unity.Netcode.Transports.UTP; +using UnityEngine; +using UnityEngine.TestTools; + +namespace Unity.Netcode.RuntimeTests +{ + [TestFixture(StartType.Server)] + [TestFixture(StartType.Host)] + [TestFixture(StartType.Client)] + internal class NetworkManagerStartExceptionTests : NetcodeIntegrationTest + { + protected override int NumberOfClients => 0; + + public enum StartType + { + Server, + Host, + Client + } + + private StartType m_StartType; + public NetworkManagerStartExceptionTests(StartType startType) : base(startType == StartType.Server ? HostOrServer.Server : HostOrServer.Host) + { + m_StartType = startType; + } + + private const string k_ExceptionText = "This is a test exception"; + + private void ThrowExceptionAction() + { + throw new Exception(k_ExceptionText); + } + + private SessionConfig SessionConfigExceptionThrower() + { + throw new Exception(k_ExceptionText); + } + + [UnityTest] + public IEnumerator VerifyNetworkManagerHandlesExceptionDuringStart() + { + var startType = m_StartType; + NetworkManager toTest; + if (startType == StartType.Client) + { + toTest = CreateNewClient(); + } + else + { + toTest = GetAuthorityNetworkManager(); + yield return StopOneClient(toTest); + } + + var transport = toTest.NetworkConfig.NetworkTransport as UnityTransport; + Assert.That(transport, Is.Not.Null, "Transport should not be null"); + + var isListening = true; + + /* + * Test exception being thrown during NetworkManager.Initialize() + */ + + // It's not possible to throw an exception during server initialization + if (startType != StartType.Server) + { + // OnSessionConfig is called within Initialize only in DAMode + toTest.NetworkConfig.NetworkTopology = NetworkTopologyTypes.DistributedAuthority; + + toTest.OnGetSessionConfig += SessionConfigExceptionThrower; + + LogAssert.Expect(LogType.Exception, $"Exception: {k_ExceptionText}"); + isListening = startType == StartType.Host ? toTest.StartHost() : toTest.StartClient(); + + Assert.That(isListening, Is.False, "Should not have started after exception during Initialize()"); + Assert.That(transport.GetNetworkDriver().IsCreated, Is.False, "NetworkDriver should not be created."); + + toTest.OnGetSessionConfig -= SessionConfigExceptionThrower; + toTest.NetworkConfig.NetworkTopology = NetworkTopologyTypes.ClientServer; + } + + /* + * Test exception being thrown after NetworkTransport.StartClient() + */ + + toTest.OnClientStarted += ThrowExceptionAction; + toTest.OnServerStarted += ThrowExceptionAction; + + LogAssert.Expect(LogType.Exception, $"Exception: {k_ExceptionText}"); + isListening = startType switch + { + StartType.Server => toTest.StartServer(), + StartType.Host => toTest.StartHost(), + StartType.Client => toTest.StartClient(), + _ => true + }; + + Assert.That(isListening, Is.False, "Should not have started after exception during startup"); + Assert.That(transport.GetNetworkDriver().IsCreated, Is.False, "NetworkDriver should not be created."); + Assert.False(toTest.IsServer, "IsServer should be false when NetworkManager failed to start"); + Assert.False(toTest.IsClient, "IsClient should be false when NetworkManager failed to start"); + + toTest.OnClientStarted -= ThrowExceptionAction; + toTest.OnServerStarted -= ThrowExceptionAction; + + if (startType == StartType.Client) + { + // Start the client fully to ensure startup still works with no exceptions + yield return StartClient(toTest); + + Assert.That(toTest.IsListening, Is.True, "Client failed to start"); + Assert.That(transport.GetNetworkDriver().IsCreated, Is.True, "NetworkDriver should be created."); + } + else + { + var isHost = startType == StartType.Host; + NetcodeIntegrationTestHelpers.StartServer(isHost, toTest); + var hostOrServer = isHost ? "Host" : "Server"; + Assert.That(toTest.IsListening, Is.True, $"{hostOrServer} failed to start"); + Assert.That(transport.GetNetworkDriver().IsCreated, Is.True, "NetworkDriver should be created."); + + yield return CreateAndStartNewClient(); + } + } + + } +} diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkManagerStartExceptionTests.cs.meta b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkManagerStartExceptionTests.cs.meta new file mode 100644 index 0000000000..753c875f32 --- /dev/null +++ b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkManagerStartExceptionTests.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 258048582a004eef90cf9e702241ba6b +timeCreated: 1769722148 \ No newline at end of file diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkObject/NetworkObjectDestroyTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkObject/NetworkObjectDestroyTests.cs index 081a141bf6..9f2baa4113 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkObject/NetworkObjectDestroyTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkObject/NetworkObjectDestroyTests.cs @@ -11,7 +11,7 @@ namespace Unity.Netcode.RuntimeTests /// /// Tests calling destroy on spawned / unspawned s. Expected behavior: /// - Server or client destroy on unspawned => Object gets destroyed, no exceptions - /// - Server destroy spawned => Object gets destroyed and despawned/destroyed on all clients. Server does not run . Client runs it. + /// - Server destroy spawned => Object gets destroyed and despawned/destroyed on all clients. Server does not run . Client runs it. /// - Client destroy spawned => throw exception. /// diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/Prefabs/NetworkPrefabHandlerTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/Prefabs/NetworkPrefabHandlerTests.cs index 34b98001ca..4e11adbd3a 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/Prefabs/NetworkPrefabHandlerTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/Prefabs/NetworkPrefabHandlerTests.cs @@ -4,6 +4,7 @@ using NUnit.Framework; using Unity.Netcode.TestHelpers.Runtime; using UnityEngine; +using UnityEngine.TestTools; namespace Unity.Netcode.RuntimeTests { @@ -93,11 +94,11 @@ public void NetworkConfigInvalidNetworkPrefabTest() private const string k_PrefabObjectName = "NetworkPrefabHandlerTestObject"; [Test] - public void NetworkPrefabHandlerClass([Values] bool distributedAuthority) + public void NetworkPrefabHandlerClass([Values] NetworkTopologyTypes topologyType) { var networkConfig = new NetworkConfig() { - NetworkTopology = distributedAuthority ? NetworkTopologyTypes.DistributedAuthority : NetworkTopologyTypes.ClientServer, + NetworkTopology = topologyType, }; Assert.IsTrue(NetworkManagerHelper.StartNetworkManager(out _, networkConfig: networkConfig)); @@ -107,13 +108,13 @@ public void NetworkPrefabHandlerClass([Values] bool distributedAuthority) NetworkObject baseObject = NetworkManagerHelper.InstantiatedNetworkObjects[baseObjectID]; var networkPrefabHandler = new NetworkPrefabHandler(); - var networkPrefaInstanceHandler = new NetworkPrefaInstanceHandler(baseObject); + var networkPrefabInstanceHandler = new NetworkPrefabInstanceHandler(baseObject); var prefabPosition = new Vector3(1.0f, 5.0f, 3.0f); var prefabRotation = new Quaternion(1.0f, 0.5f, 0.4f, 0.1f); //Register via GameObject - var gameObjectRegistered = networkPrefabHandler.AddHandler(baseObject.gameObject, networkPrefaInstanceHandler); + var gameObjectRegistered = networkPrefabHandler.AddHandler(baseObject.gameObject, networkPrefabInstanceHandler); //Test result of registering via GameObject reference Assert.True(gameObjectRegistered); @@ -134,7 +135,7 @@ public void NetworkPrefabHandlerClass([Values] bool distributedAuthority) networkPrefabHandler.RemoveHandler(baseObject); //Remove our handler //Register via NetworkObject - gameObjectRegistered = networkPrefabHandler.AddHandler(baseObject, networkPrefaInstanceHandler); + gameObjectRegistered = networkPrefabHandler.AddHandler(baseObject, networkPrefabInstanceHandler); //Test result of registering via NetworkObject reference Assert.True(gameObjectRegistered); @@ -159,7 +160,7 @@ public void NetworkPrefabHandlerClass([Values] bool distributedAuthority) networkPrefabHandler.RemoveHandler(baseObject); //Remove our handler //Register via GlobalObjectIdHash - gameObjectRegistered = networkPrefabHandler.AddHandler(baseObject.GlobalObjectIdHash, networkPrefaInstanceHandler); + gameObjectRegistered = networkPrefabHandler.AddHandler(baseObject.GlobalObjectIdHash, networkPrefabInstanceHandler); //Test result of registering via GlobalObjectIdHash reference Assert.True(gameObjectRegistered); @@ -183,7 +184,21 @@ public void NetworkPrefabHandlerClass([Values] bool distributedAuthority) networkPrefabHandler.HandleNetworkPrefabDestroy(spawnedObject); //Destroy our prefab instance networkPrefabHandler.RemoveHandler(baseObject); //Remove our handler - Assert.False(networkPrefaInstanceHandler.StillHasInstances()); + // Register a handler that throws an exception + var networkPrefabExceptionThrower = new NetworkPrefabExceptionThrower(); + gameObjectRegistered = networkPrefabHandler.AddHandler(baseObject, networkPrefabExceptionThrower); + //Test result of registering exception handler + Assert.True(gameObjectRegistered); + + LogAssert.Expect(LogType.Exception, "Exception: exception while instantiating"); + spawnedObject = networkPrefabHandler.HandleNetworkPrefabSpawn(baseObject.GlobalObjectIdHash, 0, prefabPosition, prefabRotation); + + // No object should have been spawned, but test should have continued + Assert.Null(spawnedObject); + + networkPrefabHandler.RemoveHandler(baseObject); //Remove our handler + + Assert.False(networkPrefabInstanceHandler.StillHasInstances()); } [SetUp] @@ -216,7 +231,7 @@ public void TearDown() /// /// The Prefab instance handler to use for this test /// - internal class NetworkPrefaInstanceHandler : INetworkPrefabInstanceHandler + internal class NetworkPrefabInstanceHandler : INetworkPrefabInstanceHandler { private NetworkObject m_NetworkObject; @@ -243,10 +258,26 @@ public bool StillHasInstances() return (m_Instances.Count > 0); } - public NetworkPrefaInstanceHandler(NetworkObject networkObject) + public NetworkPrefabInstanceHandler(NetworkObject networkObject) { m_NetworkObject = networkObject; m_Instances = new List(); } } + + /// + /// Causes an exception during client connection + /// + internal class NetworkPrefabExceptionThrower : INetworkPrefabInstanceHandler + { + public NetworkObject Instantiate(ulong ownerClientId, Vector3 position, Quaternion rotation) + { + throw new Exception("exception while instantiating"); + } + + public void Destroy(NetworkObject networkObject) + { + UnityEngine.Object.Destroy(networkObject.gameObject); + } + } } diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/TestHelpers/NetcodeIntegrationTestHelpers.cs b/com.unity.netcode.gameobjects/Tests/Runtime/TestHelpers/NetcodeIntegrationTestHelpers.cs index d09d1da9b6..32a0f734d7 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/TestHelpers/NetcodeIntegrationTestHelpers.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/TestHelpers/NetcodeIntegrationTestHelpers.cs @@ -571,17 +571,26 @@ public static bool Start(bool host, NetworkManager server, NetworkManager[] clie } s_IsStarted = true; + return StartInternal(host, server, clients, callback, startServer); + } + + internal static bool StartServer(bool host, NetworkManager server) + { + return StartInternal(host, server, new NetworkManager[] { }); + } + + + private static bool StartInternal(bool host, NetworkManager server, NetworkManager[] clients, BeforeClientStartCallback callback = null, bool startServer = true) + { s_ClientCount = clients.Length; var hooks = (MultiInstanceHooks)null; if (startServer) { - if (host) - { - server.StartHost(); - } - else + var isListening = host ? server.StartHost() : server.StartServer(); + + if (!isListening) { - server.StartServer(); + return false; } hooks = new MultiInstanceHooks();