Skip to content

Commit ed867b5

Browse files
fix: host not generating its own local client disconnect notification (#2822)
* fix Assure the host generates a notification for the local host client no longer being connected when it shuts itself down. * test Include modification to the DisconnectTest to validate this fix. Rewrote the SenderIdTest to be a bit more flexible with regard to waiting for the client disconnect event and not failing if it received anything afterwards. Adding check for ConnectionEvent.ClientDisconnected for both test types. Fixing issue with StopOneClient destroying the NetworkManager when destroy is set to false.
1 parent 7a2f31a commit ed867b5

File tree

5 files changed

+85
-25
lines changed

5 files changed

+85
-25
lines changed

com.unity.netcode.gameobjects/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ Additional documentation and release notes are available at [Multiplayer Documen
1212

1313
### Fixed
1414

15+
- Fixed issue where the host was not invoking `OnClientDisconnectCallback` for its own local client when internally shutting down. (#2822)
1516
- Fixed issue where NetworkTransform could potentially attempt to "unregister" a named message prior to it being registered. (#2807)
1617
- Fixed issue where in-scene placed `NetworkObject`s with complex nested children `NetworkObject`s (more than one child in depth) would not synchronize properly if WorldPositionStays was set to true. (#2796)
1718

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

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1185,6 +1185,12 @@ internal void ShutdownInternal()
11851185
IsListening = false;
11861186
m_ShuttingDown = false;
11871187

1188+
// Generate a local notification that the host client is disconnected
1189+
if (IsHost)
1190+
{
1191+
ConnectionManager.InvokeOnClientDisconnectCallback(LocalClientId);
1192+
}
1193+
11881194
if (ConnectionManager.LocalClient.IsClient)
11891195
{
11901196
// If we were a client, we want to know if we were a host

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

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -534,7 +534,10 @@ protected void CreateAndStartNewClientWithTimeTravel()
534534
protected IEnumerator StopOneClient(NetworkManager networkManager, bool destroy = false)
535535
{
536536
NetcodeIntegrationTestHelpers.StopOneClient(networkManager, destroy);
537-
AddRemoveNetworkManager(networkManager, false);
537+
if (destroy)
538+
{
539+
AddRemoveNetworkManager(networkManager, false);
540+
}
538541
yield return WaitForConditionOrTimeOut(() => !networkManager.IsConnectedClient);
539542
}
540543

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

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
using System.Collections;
2+
using System.Collections.Generic;
23
using System.Linq;
34
using NUnit.Framework;
45
using Unity.Netcode.TestHelpers.Runtime;
@@ -37,7 +38,10 @@ public enum ClientDisconnectType
3738
protected override int NumberOfClients => 1;
3839

3940
private OwnerPersistence m_OwnerPersistence;
41+
private ClientDisconnectType m_ClientDisconnectType;
4042
private bool m_ClientDisconnected;
43+
private Dictionary<NetworkManager, ConnectionEventData> m_DisconnectedEvent = new Dictionary<NetworkManager, ConnectionEventData>();
44+
private ulong m_DisconnectEventClientId;
4145
private ulong m_TransportClientId;
4246
private ulong m_ClientId;
4347

@@ -89,6 +93,16 @@ private void OnClientDisconnectCallback(ulong obj)
8993
m_ClientDisconnected = true;
9094
}
9195

96+
private void OnConnectionEvent(NetworkManager networkManager, ConnectionEventData connectionEventData)
97+
{
98+
if (connectionEventData.EventType != ConnectionEvent.ClientDisconnected)
99+
{
100+
return;
101+
}
102+
103+
m_DisconnectedEvent.Add(networkManager, connectionEventData);
104+
}
105+
92106
/// <summary>
93107
/// Conditional check to assure the transport to client (and vice versa) mappings are cleaned up
94108
/// </summary>
@@ -126,6 +140,7 @@ private bool DoesServerStillHaveSpawnedPlayerObject()
126140
public IEnumerator ClientPlayerDisconnected([Values] ClientDisconnectType clientDisconnectType)
127141
{
128142
m_ClientId = m_ClientNetworkManagers[0].LocalClientId;
143+
m_ClientDisconnectType = clientDisconnectType;
129144

130145
var serverSideClientPlayer = m_ServerNetworkManager.ConnectionManager.ConnectedClients[m_ClientId].PlayerObject;
131146

@@ -134,18 +149,39 @@ public IEnumerator ClientPlayerDisconnected([Values] ClientDisconnectType client
134149
if (clientDisconnectType == ClientDisconnectType.ServerDisconnectsClient)
135150
{
136151
m_ClientNetworkManagers[0].OnClientDisconnectCallback += OnClientDisconnectCallback;
152+
m_ClientNetworkManagers[0].OnConnectionEvent += OnConnectionEvent;
153+
m_ServerNetworkManager.OnConnectionEvent += OnConnectionEvent;
137154
m_ServerNetworkManager.DisconnectClient(m_ClientId);
138155
}
139156
else
140157
{
141158
m_ServerNetworkManager.OnClientDisconnectCallback += OnClientDisconnectCallback;
159+
m_ServerNetworkManager.OnConnectionEvent += OnConnectionEvent;
160+
m_ClientNetworkManagers[0].OnConnectionEvent += OnConnectionEvent;
142161

143162
yield return StopOneClient(m_ClientNetworkManagers[0]);
144163
}
145164

146165
yield return WaitForConditionOrTimeOut(() => m_ClientDisconnected);
147166
AssertOnTimeout("Timed out waiting for client to disconnect!");
148167

168+
if (clientDisconnectType == ClientDisconnectType.ServerDisconnectsClient)
169+
{
170+
Assert.IsTrue(m_DisconnectedEvent.ContainsKey(m_ServerNetworkManager), $"Could not find the server {nameof(NetworkManager)} disconnect event entry!");
171+
Assert.IsTrue(m_DisconnectedEvent[m_ServerNetworkManager].ClientId == m_ClientId, $"Expected ClientID {m_ClientId} but found ClientID {m_DisconnectedEvent[m_ServerNetworkManager].ClientId} for the server {nameof(NetworkManager)} disconnect event entry!");
172+
Assert.IsTrue(m_DisconnectedEvent.ContainsKey(m_ClientNetworkManagers[0]), $"Could not find the client {nameof(NetworkManager)} disconnect event entry!");
173+
Assert.IsTrue(m_DisconnectedEvent[m_ClientNetworkManagers[0]].ClientId == m_ClientId, $"Expected ClientID {m_ClientId} but found ClientID {m_DisconnectedEvent[m_ServerNetworkManager].ClientId} for the client {nameof(NetworkManager)} disconnect event entry!");
174+
// Unregister for this event otherwise it will be invoked during teardown
175+
m_ServerNetworkManager.OnConnectionEvent -= OnConnectionEvent;
176+
}
177+
else
178+
{
179+
Assert.IsTrue(m_DisconnectedEvent.ContainsKey(m_ServerNetworkManager), $"Could not find the server {nameof(NetworkManager)} disconnect event entry!");
180+
Assert.IsTrue(m_DisconnectedEvent[m_ServerNetworkManager].ClientId == m_ClientId, $"Expected ClientID {m_ClientId} but found ClientID {m_DisconnectedEvent[m_ServerNetworkManager].ClientId} for the server {nameof(NetworkManager)} disconnect event entry!");
181+
Assert.IsTrue(m_DisconnectedEvent.ContainsKey(m_ClientNetworkManagers[0]), $"Could not find the client {nameof(NetworkManager)} disconnect event entry!");
182+
Assert.IsTrue(m_DisconnectedEvent[m_ClientNetworkManagers[0]].ClientId == m_ClientId, $"Expected ClientID {m_ClientId} but found ClientID {m_DisconnectedEvent[m_ServerNetworkManager].ClientId} for the client {nameof(NetworkManager)} disconnect event entry!");
183+
}
184+
149185
if (m_OwnerPersistence == OwnerPersistence.DestroyWithOwner)
150186
{
151187
// When we are destroying with the owner, validate the player object is destroyed on the server side
@@ -161,6 +197,21 @@ public IEnumerator ClientPlayerDisconnected([Values] ClientDisconnectType client
161197

162198
yield return WaitForConditionOrTimeOut(TransportIdCleanedUp);
163199
AssertOnTimeout("Timed out waiting for transport and client id mappings to be cleaned up!");
200+
201+
// Validate the host-client generates a OnClientDisconnected event when it shutsdown.
202+
// Only test when the test run is the client disconnecting from the server (otherwise the server will be shutdown already)
203+
if (clientDisconnectType == ClientDisconnectType.ClientDisconnectsFromServer)
204+
{
205+
m_DisconnectedEvent.Clear();
206+
m_ClientDisconnected = false;
207+
m_ServerNetworkManager.Shutdown();
208+
209+
yield return WaitForConditionOrTimeOut(() => m_ClientDisconnected);
210+
AssertOnTimeout("Timed out waiting for host-client to generate disconnect message!");
211+
212+
Assert.IsTrue(m_DisconnectedEvent.ContainsKey(m_ServerNetworkManager), $"Could not find the server {nameof(NetworkManager)} disconnect event entry!");
213+
Assert.IsTrue(m_DisconnectedEvent[m_ServerNetworkManager].ClientId == NetworkManager.ServerClientId, $"Expected ClientID {m_ClientId} but found ClientID {m_DisconnectedEvent[m_ServerNetworkManager].ClientId} for the server {nameof(NetworkManager)} disconnect event entry!");
214+
}
164215
}
165216
}
166217
}

testproject/Assets/Tests/Runtime/SenderIdTests.cs

Lines changed: 23 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
using System;
22
using System.Collections;
3+
using System.Collections.Generic;
34
using NUnit.Framework;
45
using Unity.Collections;
56
using Unity.Netcode;
@@ -89,36 +90,34 @@ public IEnumerator WhenSendingMessageFromClientToServer_SenderIdIsCorrect()
8990
Assert.IsTrue(secondReceived);
9091
}
9192

93+
94+
private List<ulong> m_ClientsDisconnected = new List<ulong>();
95+
private ulong m_ClientToValidateDisconnected;
96+
97+
private bool ValidateClientId()
98+
{
99+
return m_ClientsDisconnected.Contains(m_ClientToValidateDisconnected);
100+
}
101+
102+
private void OnClientDisconnected(ulong clientId)
103+
{
104+
m_ClientsDisconnected.Add(clientId);
105+
}
106+
92107
[UnityTest]
93108
public IEnumerator WhenClientDisconnectsFromServer_ClientIdIsCorrect()
94109
{
95-
var firstClientId = FirstClient.LocalClientId;
96-
bool received = false;
97-
void firstCallback(ulong id)
98-
{
99-
Assert.AreEqual(firstClientId, id);
100-
received = true;
101-
}
102-
m_ServerNetworkManager.OnClientDisconnectCallback += firstCallback;
110+
m_ClientsDisconnected.Clear();
111+
m_ServerNetworkManager.OnClientDisconnectCallback += OnClientDisconnected;
112+
m_ClientToValidateDisconnected = m_ClientNetworkManagers[0].LocalClientId;
103113
FirstClient.Shutdown();
114+
yield return WaitForConditionOrTimeOut(ValidateClientId);
115+
AssertOnTimeout($"Timed out waiting for the server to receive Client-{m_ClientToValidateDisconnected} disconnect event!");
104116

105-
yield return new WaitForSeconds(0.2f);
106-
107-
Assert.IsTrue(received);
108-
var secondClientId = SecondClient.LocalClientId;
109-
received = false;
110-
111-
m_ServerNetworkManager.OnClientDisconnectCallback -= firstCallback;
112-
m_ServerNetworkManager.OnClientDisconnectCallback += id =>
113-
{
114-
Assert.AreEqual(secondClientId, id);
115-
received = true;
116-
};
117+
m_ClientToValidateDisconnected = m_ClientNetworkManagers[1].LocalClientId;
117118
SecondClient.Shutdown();
118-
119-
yield return new WaitForSeconds(0.2f);
120-
121-
Assert.IsTrue(received);
119+
yield return WaitForConditionOrTimeOut(ValidateClientId);
120+
AssertOnTimeout($"Timed out waiting for the server to receive Client-{m_ClientToValidateDisconnected} disconnect event!");
122121
}
123122
}
124123
}

0 commit comments

Comments
 (0)