Skip to content

Commit 17a2c04

Browse files
authored
fix: Ensure NetworkConnectionManager correctly handles multiple disconnect messages being sent (#3707)
* fix: Ensure NetworkConnectionManager correctly handles multiple disconnect messages being sent * small fixes * update CHANGELOG * Remove debug logs and fix xml doc * Update CHANGELOG with info for #3647 * Undo update to CHANGELOG
1 parent 462bb88 commit 17a2c04

File tree

11 files changed

+388
-223
lines changed

11 files changed

+388
-223
lines changed

com.unity.netcode.gameobjects/CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,9 @@ Additional documentation and release notes are available at [Multiplayer Documen
1313

1414
### Changed
1515

16+
- The `NetworkManager` functions `GetTransportIdFromClientId` and `GetClientIdFromTransportId` will now return `ulong.MaxValue` when the clientId or transportId do not exist. (#3707)
17+
- Improved performance of the NetworkVariable. (#3683)
18+
- Improved performance around the NetworkBehaviour component. (#3687)
1619

1720
### Deprecated
1821

@@ -22,6 +25,9 @@ Additional documentation and release notes are available at [Multiplayer Documen
2225

2326
### Fixed
2427

28+
- Multiple disconnect events from the same transport will no longer disconnect the host. (#3707)
29+
- Distributed authority clients no longer send themselves in the `ClientIds` list when sending a `ChangeOwnershipMessage`. (#3687)
30+
- Made a variety of small performance improvements. (#3683)
2531

2632
### Security
2733

com.unity.netcode.gameobjects/Runtime/Connection/NetworkConnectionManager.cs

Lines changed: 226 additions & 187 deletions
Large diffs are not rendered by default.

com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs

Lines changed: 20 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1441,18 +1441,13 @@ private void HostServerInitialize()
14411441
}
14421442
}
14431443

1444-
response.Approved = true;
1445-
ConnectionManager.HandleConnectionApproval(ServerClientId, response);
1444+
ConnectionManager.HandleConnectionApproval(ServerClientId, response.CreatePlayerObject, response.PlayerPrefabHash, response.Position, response.Rotation);
14461445
}
14471446
else
14481447
{
1449-
var response = new ConnectionApprovalResponse
1450-
{
1451-
Approved = true,
1452-
// Distributed authority always returns true since the client side handles spawning (whether automatically or manually)
1453-
CreatePlayerObject = DistributedAuthorityMode || NetworkConfig.PlayerPrefab != null,
1454-
};
1455-
ConnectionManager.HandleConnectionApproval(ServerClientId, response);
1448+
// Distributed authority always tries to create the player object since the client side handles spawning (whether automatically or manually)
1449+
var createPlayerObject = DistributedAuthorityMode || NetworkConfig.PlayerPrefab != null;
1450+
ConnectionManager.HandleConnectionApproval(ServerClientId, createPlayerObject);
14561451
}
14571452

14581453
SpawnManager.ServerSpawnSceneObjectsOnStartSweep();
@@ -1473,15 +1468,27 @@ private void HostServerInitialize()
14731468
/// Get the TransportId from the associated ClientId.
14741469
/// </summary>
14751470
/// <param name="clientId">The ClientId to get the TransportId from</param>
1476-
/// <returns>The TransportId associated with the given ClientId</returns>
1477-
public ulong GetTransportIdFromClientId(ulong clientId) => ConnectionManager.ClientIdToTransportId(clientId);
1471+
/// <returns>
1472+
/// The TransportId associated with the given ClientId if the given clientId is valid; otherwise <see cref="ulong.MaxValue"/>
1473+
/// </returns>
1474+
public ulong GetTransportIdFromClientId(ulong clientId)
1475+
{
1476+
var (id, success) = ConnectionManager.ClientIdToTransportId(clientId);
1477+
return success ? id : ulong.MaxValue;
1478+
}
14781479

14791480
/// <summary>
14801481
/// Get the ClientId from the associated TransportId.
14811482
/// </summary>
14821483
/// <param name="transportId">The TransportId to get the ClientId from</param>
1483-
/// <returns>The ClientId from the associated TransportId</returns>
1484-
public ulong GetClientIdFromTransportId(ulong transportId) => ConnectionManager.TransportIdToClientId(transportId);
1484+
/// <returns>
1485+
/// The ClientId from the associated TransportId if the given transportId is valid; otherwise <see cref="ulong.MaxValue"/>
1486+
/// </returns>
1487+
public ulong GetClientIdFromTransportId(ulong transportId)
1488+
{
1489+
var (id, success) = ConnectionManager.TransportIdToClientId(transportId);
1490+
return success ? id : ulong.MaxValue;
1491+
}
14851492

14861493
/// <summary>
14871494
/// Disconnects the remote client.

com.unity.netcode.gameobjects/Runtime/Messaging/DefaultMessageSender.cs

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,19 @@ public DefaultMessageSender(NetworkManager manager)
1919
public void Send(ulong clientId, NetworkDelivery delivery, FastBufferWriter batchData)
2020
{
2121
var sendBuffer = batchData.ToTempByteArray();
22+
var (transportId, clientExists) = m_ConnectionManager.ClientIdToTransportId(clientId);
2223

23-
m_NetworkTransport.Send(m_ConnectionManager.ClientIdToTransportId(clientId), sendBuffer, delivery);
24+
if (!clientExists)
25+
{
26+
if (m_ConnectionManager.NetworkManager.LogLevel <= LogLevel.Error)
27+
{
28+
NetworkLog.LogWarning("Trying to send a message to a client who doesn't have a transport connection");
29+
}
30+
31+
return;
32+
}
33+
34+
m_NetworkTransport.Send(transportId, sendBuffer, delivery);
2435
}
2536
}
2637
}

com.unity.netcode.gameobjects/Runtime/Messaging/Messages/ConnectionRequestMessage.cs

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -210,12 +210,16 @@ public void Handle(ref NetworkContext context)
210210
}
211211
else
212212
{
213-
var response = new NetworkManager.ConnectionApprovalResponse
213+
var createPlayerObject = networkManager.NetworkConfig.PlayerPrefab != null;
214+
215+
// DAHost only:
216+
// Never create the player object on the server if AutoSpawnPlayerPrefabClientSide is set.
217+
if (networkManager.DistributedAuthorityMode && networkManager.AutoSpawnPlayerPrefabClientSide)
214218
{
215-
Approved = true,
216-
CreatePlayerObject = networkManager.DistributedAuthorityMode && networkManager.AutoSpawnPlayerPrefabClientSide ? false : networkManager.NetworkConfig.PlayerPrefab != null
217-
};
218-
networkManager.ConnectionManager.HandleConnectionApproval(senderId, response);
219+
createPlayerObject = false;
220+
}
221+
222+
networkManager.ConnectionManager.HandleConnectionApproval(senderId, createPlayerObject);
219223
}
220224
}
221225
}

com.unity.netcode.gameobjects/Runtime/Transports/UTP/UnityTransport.cs

Lines changed: 19 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -909,7 +909,11 @@ private void SendBatchedMessages(SendTarget sendTarget, BatchedSendQueue queue)
909909
var mtu = 0;
910910
if (m_NetworkManager)
911911
{
912-
var ngoClientId = m_NetworkManager.ConnectionManager.TransportIdToClientId(sendTarget.ClientId);
912+
var (ngoClientId, isConnectedClient) = m_NetworkManager.ConnectionManager.TransportIdToClientId(sendTarget.ClientId);
913+
if (!isConnectedClient)
914+
{
915+
return;
916+
}
913917
mtu = m_NetworkManager.GetPeerMTU(ngoClientId);
914918
}
915919

@@ -1321,7 +1325,7 @@ public override ulong GetCurrentRtt(ulong clientId)
13211325

13221326
if (m_NetworkManager != null)
13231327
{
1324-
var transportId = m_NetworkManager.ConnectionManager.ClientIdToTransportId(clientId);
1328+
var (transportId, _) = m_NetworkManager.ConnectionManager.ClientIdToTransportId(clientId);
13251329

13261330
var rtt = ExtractRtt(ParseClientId(transportId));
13271331
if (rtt > 0)
@@ -1347,13 +1351,14 @@ public NetworkEndpoint GetEndpoint(ulong clientId)
13471351
{
13481352
if (m_Driver.IsCreated && m_NetworkManager != null && m_NetworkManager.IsListening)
13491353
{
1350-
var transportId = m_NetworkManager.ConnectionManager.ClientIdToTransportId(clientId);
1354+
var (transportId, connectionExists) = m_NetworkManager.ConnectionManager.ClientIdToTransportId(clientId);
13511355
var networkConnection = ParseClientId(transportId);
1352-
if (m_Driver.GetConnectionState(networkConnection) == NetworkConnection.State.Connected)
1356+
if (connectionExists && m_Driver.GetConnectionState(networkConnection) == NetworkConnection.State.Connected)
13531357
{
13541358
return m_Driver.GetRemoteEndpoint(networkConnection);
13551359
}
13561360
}
1361+
13571362
return new NetworkEndpoint();
13581363
}
13591364

@@ -1447,10 +1452,18 @@ public override void Send(ulong clientId, ArraySegment<byte> payload, NetworkDel
14471452
// If the message is sent reliably, then we're over capacity and we can't
14481453
// provide any reliability guarantees anymore. Disconnect the client since at
14491454
// this point they're bound to become desynchronized.
1455+
if (m_NetworkManager != null)
1456+
{
1457+
var (ngoClientId, isConnectedClient) = m_NetworkManager.ConnectionManager.TransportIdToClientId(clientId);
1458+
if (isConnectedClient)
1459+
{
1460+
clientId = ngoClientId;
1461+
}
1462+
1463+
}
14501464

1451-
var ngoClientId = m_NetworkManager?.ConnectionManager.TransportIdToClientId(clientId) ?? clientId;
14521465
Debug.LogError($"Couldn't add payload of size {payload.Count} to reliable send queue. " +
1453-
$"Closing connection {ngoClientId} as reliability guarantees can't be maintained.");
1466+
$"Closing connection {clientId} as reliability guarantees can't be maintained.");
14541467

14551468
if (clientId == m_ServerClientId)
14561469
{

com.unity.netcode.gameobjects/Tests/Runtime/DisconnectTests.cs

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -108,16 +108,24 @@ private void OnConnectionEvent(NetworkManager networkManager, ConnectionEventDat
108108
/// </summary>
109109
private bool TransportIdCleanedUp()
110110
{
111-
if (m_ServerNetworkManager.ConnectionManager.TransportIdToClientId(m_TransportClientId) == m_ClientId)
111+
var (clientId, isConnected) = m_ServerNetworkManager.ConnectionManager.TransportIdToClientId(m_TransportClientId);
112+
if (isConnected)
112113
{
113114
return false;
114115
}
115116

116-
if (m_ServerNetworkManager.ConnectionManager.ClientIdToTransportId(m_ClientId) == m_TransportClientId)
117+
if (clientId == m_ClientId)
117118
{
118119
return false;
119120
}
120-
return true;
121+
122+
var (transportId, connectionExists) = m_ServerNetworkManager.ConnectionManager.ClientIdToTransportId(m_ClientId);
123+
if (connectionExists)
124+
{
125+
return false;
126+
}
127+
128+
return transportId != m_TransportClientId;
121129
}
122130

123131
/// <summary>
@@ -145,7 +153,9 @@ public IEnumerator ClientPlayerDisconnected([Values] ClientDisconnectType client
145153

146154
var serverSideClientPlayer = m_ServerNetworkManager.ConnectionManager.ConnectedClients[m_ClientId].PlayerObject;
147155

148-
m_TransportClientId = m_ServerNetworkManager.ConnectionManager.ClientIdToTransportId(m_ClientId);
156+
bool connectionExists;
157+
(m_TransportClientId, connectionExists) = m_ServerNetworkManager.ConnectionManager.ClientIdToTransportId(m_ClientId);
158+
Assert.IsTrue(connectionExists);
149159

150160
if (clientDisconnectType == ClientDisconnectType.ServerDisconnectsClient)
151161
{

com.unity.netcode.gameobjects/Tests/Runtime/TestHelpers/NetcodeIntegrationTest.cs

Lines changed: 19 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -484,6 +484,17 @@ protected void SetDistributedAuthorityProperties(NetworkManager networkManager)
484484
/// </summary>
485485
protected virtual bool m_EnableTimeTravel => false;
486486

487+
/// <summary>
488+
/// When true, <see cref="CreateServerAndClients()"/> and <see cref="CreateNewClient"/> will use a <see cref="MockTransport"/>
489+
/// as the <see cref="NetworkConfig.NetworkTransport"/> on the created server and/or clients.
490+
/// When false, a <see cref="UnityTransport"/> is used.
491+
/// </summary>
492+
/// <remarks>
493+
/// This defaults to, and is required to be true when <see cref="m_EnableTimeTravel"/> is true.
494+
/// <see cref="m_EnableTimeTravel"/> will not work with the <see cref="UnityTransport"/> component.
495+
/// </remarks>
496+
protected virtual bool m_UseMockTransport => m_EnableTimeTravel;
497+
487498
/// <summary>
488499
/// If this is false, SetUp will call OnInlineSetUp instead of OnSetUp.
489500
/// This is a performance advantage when not using the coroutine functionality, as a coroutine that
@@ -638,7 +649,7 @@ public IEnumerator SetUp()
638649
VerboseDebugLog.Clear();
639650
VerboseDebug($"Entering {nameof(SetUp)}");
640651
NetcodeLogAssert = new NetcodeLogAssert();
641-
if (m_EnableTimeTravel)
652+
if (m_UseMockTransport)
642653
{
643654
if (m_NetworkManagerInstatiationMode == NetworkManagerInstatiationMode.AllTests)
644655
{
@@ -648,8 +659,11 @@ public IEnumerator SetUp()
648659
{
649660
MockTransport.Reset();
650661
}
662+
}
651663

652-
// Setup the frames per tick for time travel advance to next tick
664+
// Setup the frames per tick for time travel advance to next tick
665+
if (m_EnableTimeTravel)
666+
{
653667
ConfigureFramesPerTick();
654668
}
655669

@@ -781,7 +795,7 @@ protected void CreateServerAndClients(int numberOfClients)
781795
}
782796

783797
// Create multiple NetworkManager instances
784-
if (!NetcodeIntegrationTestHelpers.Create(numberOfClients, out NetworkManager server, out NetworkManager[] clients, m_TargetFrameRate, m_CreateServerFirst, m_EnableTimeTravel, m_UseCmbService))
798+
if (!NetcodeIntegrationTestHelpers.Create(numberOfClients, out NetworkManager server, out NetworkManager[] clients, m_TargetFrameRate, m_CreateServerFirst, m_UseMockTransport, m_UseCmbService))
785799
{
786800
Debug.LogError("Failed to create instances");
787801
Assert.Fail("Failed to create instances");
@@ -872,7 +886,7 @@ protected virtual bool ShouldWaitForNewClientToConnect(NetworkManager networkMan
872886
/// <returns>The newly created <see cref="NetworkManager"/>.</returns>
873887
protected NetworkManager CreateNewClient()
874888
{
875-
var networkManager = NetcodeIntegrationTestHelpers.CreateNewClient(m_ClientNetworkManagers.Length, m_EnableTimeTravel, m_UseCmbService);
889+
var networkManager = NetcodeIntegrationTestHelpers.CreateNewClient(m_ClientNetworkManagers.Length, m_UseMockTransport, m_UseCmbService);
876890
networkManager.NetworkConfig.PlayerPrefab = m_PlayerPrefab;
877891
SetDistributedAuthorityProperties(networkManager);
878892

@@ -981,7 +995,7 @@ private bool AllPlayerObjectClonesSpawned(NetworkManager joinedClient)
981995
/// </summary>
982996
protected void CreateAndStartNewClientWithTimeTravel()
983997
{
984-
var networkManager = NetcodeIntegrationTestHelpers.CreateNewClient(m_ClientNetworkManagers.Length, m_EnableTimeTravel);
998+
var networkManager = NetcodeIntegrationTestHelpers.CreateNewClient(m_ClientNetworkManagers.Length, m_UseMockTransport);
985999
networkManager.NetworkConfig.PlayerPrefab = m_PlayerPrefab;
9861000
SetDistributedAuthorityProperties(networkManager);
9871001

com.unity.netcode.gameobjects/Tests/Runtime/TestHelpers/NetcodeIntegrationTestHelpers.cs

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -327,7 +327,18 @@ public static bool Create(int clientCount, out NetworkManager server, out Networ
327327
return true;
328328
}
329329

330-
internal static NetworkManager CreateNewClient(int identifier, bool mockTransport = false, bool useCmbService = false)
330+
/// <summary>
331+
/// Creates a new <see cref="NetworkManager"/> and configures it for use in a multi instance setting.
332+
/// </summary>
333+
/// <param name="identifier">The ClientId representation that is used in the name of the NetworkManager</param>
334+
/// <param name="mockTransport">
335+
/// When true, the client is created with a <see cref="MockTransport"/>; otherwise a <see cref="UnityTransport"/> is added
336+
/// </param>
337+
/// <param name="useCmbService">
338+
/// Whether to configure the client to run against a hosted build of the CMB Service. Only applies if mockTransport is set to false.
339+
/// </param>
340+
/// <returns>The newly created <see cref="NetworkManager"/> component.</returns>
341+
public static NetworkManager CreateNewClient(int identifier, bool mockTransport = false, bool useCmbService = false)
331342
{
332343
// Create gameObject
333344
var go = new GameObject("NetworkManager - Client - " + identifier);
@@ -351,7 +362,7 @@ internal static NetworkManager CreateNewClient(int identifier, bool mockTranspor
351362
/// <param name="clients">Output array containing the created NetworkManager instances</param>
352363
/// <param name="useMockTransport">When true, uses mock transport for testing, otherwise uses real transport. Default value is false</param>
353364
/// <param name="useCmbService">If true, each client will be created with transport configured to connect to a locally hosted da service</param>
354-
/// <returns> Returns <see cref="true"/> if the clients were successfully created and configured, otherwise <see cref="false"/>.</returns>
365+
/// <returns> Returns true if the clients were successfully created and configured, otherwise false.</returns>
355366
public static bool CreateNewClients(int clientCount, out NetworkManager[] clients, bool useMockTransport = false, bool useCmbService = false)
356367
{
357368
clients = new NetworkManager[clientCount];
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
using System.Collections;
2+
using NUnit.Framework;
3+
using Unity.Netcode.TestHelpers.Runtime;
4+
using UnityEngine.TestTools;
5+
6+
namespace Unity.Netcode.RuntimeTests
7+
{
8+
[TestFixture(HostOrServer.Server)]
9+
[TestFixture(HostOrServer.Host)]
10+
internal class TransportTests : NetcodeIntegrationTest
11+
{
12+
protected override int NumberOfClients => 2;
13+
14+
protected override bool m_UseMockTransport => true;
15+
16+
public TransportTests(HostOrServer hostOrServer) : base(hostOrServer) { }
17+
18+
[UnityTest]
19+
public IEnumerator MultipleDisconnectEventsNoop()
20+
{
21+
var clientToDisconnect = GetNonAuthorityNetworkManager(0);
22+
var clientTransport = clientToDisconnect.NetworkConfig.NetworkTransport;
23+
24+
var otherClient = GetNonAuthorityNetworkManager(1);
25+
26+
// Send multiple disconnect events
27+
clientTransport.DisconnectLocalClient();
28+
clientTransport.DisconnectLocalClient();
29+
30+
// completely stop and clean up the client
31+
yield return StopOneClient(clientToDisconnect);
32+
33+
var expectedConnectedClients = m_UseHost ? NumberOfClients : NumberOfClients - 1;
34+
yield return WaitForConditionOrTimeOut(() => otherClient.ConnectedClientsIds.Count == expectedConnectedClients);
35+
AssertOnTimeout($"Incorrect number of connected clients. Expected: {expectedConnectedClients}, have: {otherClient.ConnectedClientsIds.Count}");
36+
37+
// Start a new client to ensure everything is still working
38+
yield return CreateAndStartNewClient();
39+
40+
var newExpectedClients = m_UseHost ? NumberOfClients + 1 : NumberOfClients;
41+
yield return WaitForConditionOrTimeOut(() => otherClient.ConnectedClientsIds.Count == newExpectedClients);
42+
AssertOnTimeout($"Incorrect number of connected clients. Expected: {newExpectedClients}, have: {otherClient.ConnectedClientsIds.Count}");
43+
44+
45+
}
46+
}
47+
}

0 commit comments

Comments
 (0)