Skip to content

Commit 73ac141

Browse files
fix: NetworkManager ApprovalTimeout should not depend upon client synchronization (#2261)
* fix This fix separates the IsConnectedClient from the approval process by adding an IsApproved flag. This also has a fix to prevent the domain backup error during serialization. * test Added the ability to bypass the entire NetcodeIntegrationTest connection approval process after the server and clients have been started. This allows us to now use NetcodeIntegrationTest for unique connection oriented tests without being bound to the asserts if not all clients connected properly. Refactored for ConnectionApprovalTimeoutTests to use the added m_BypassConnectionTimeout to bypass the waiting for clients to connect. It still uses the message hook catch technique to simulate the timeout scenarios where either a server detects a transport connection but never receives a connection request or a client sends the connection request but never receives approval for the connection.
1 parent 01d7ed7 commit 73ac141

File tree

6 files changed

+176
-116
lines changed

6 files changed

+176
-116
lines changed

com.unity.netcode.gameobjects/CHANGELOG.md

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,19 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/)
77

88
Additional documentation and release notes are available at [Multiplayer Documentation](https://docs-multiplayer.unity3d.com).
99

10+
## [Unreleased]
11+
12+
### Added
13+
- Added `NetworkManager.IsApproved` flag that is set to `true` a client has been approved.(#2261)
14+
15+
### Changed
16+
17+
18+
### Fixed
19+
20+
- Fixed `NetworkManager.ApprovalTimeout` will not timeout due to slower client synchronization times as it now uses the added `NetworkManager.IsApproved` flag to determined if the client has been approved or not.(#2261)
21+
22+
1023
## [1.1.0] - 2022-10-19
1124

1225
### Added

com.unity.netcode.gameobjects/Components/NetworkAnimator.cs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -232,6 +232,10 @@ private void BuildDestinationToTransitionInfoTable()
232232
private void BuildTransitionStateInfoList()
233233
{
234234
#if UNITY_EDITOR
235+
if (UnityEditor.EditorApplication.isUpdating)
236+
{
237+
return;
238+
}
235239
TransitionStateInfoList = new List<TransitionStateinfo>();
236240
var animatorController = m_Animator.runtimeAnimatorController as AnimatorController;
237241
if (animatorController == null)

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

Lines changed: 67 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -366,10 +366,22 @@ public IReadOnlyList<ulong> ConnectedClientsIds
366366
public bool IsListening { get; internal set; }
367367

368368
/// <summary>
369-
/// Gets if we are connected as a client
369+
/// When true, the client is connected, approved, and synchronized with
370+
/// the server.
370371
/// </summary>
371372
public bool IsConnectedClient { get; internal set; }
372373

374+
/// <summary>
375+
/// Is true when the client has been approved.
376+
/// </summary>
377+
/// <remarks>
378+
/// This only reflects the client's approved status and does not mean the client
379+
/// has finished the connection and synchronization process. The server-host will
380+
/// always be approved upon being starting the <see cref="NetworkManager"/>
381+
/// <see cref="IsConnectedClient"/>
382+
/// </remarks>
383+
public bool IsApproved { get; internal set; }
384+
373385
/// <summary>
374386
/// Can be used to determine if the <see cref="NetworkManager"/> is currently shutting itself down
375387
/// </summary>
@@ -877,6 +889,8 @@ private void Initialize(bool server)
877889
return;
878890
}
879891

892+
IsApproved = false;
893+
880894
ComponentFactory.SetDefaults();
881895

882896
if (NetworkLog.CurrentLogLevel <= LogLevel.Developer)
@@ -1018,7 +1032,6 @@ public bool StartServer()
10181032
}
10191033

10201034
Initialize(true);
1021-
10221035
IsServer = true;
10231036
IsClient = false;
10241037
IsListening = true;
@@ -1031,6 +1044,7 @@ public bool StartServer()
10311044
SpawnManager.ServerSpawnSceneObjectsOnStartSweep();
10321045

10331046
OnServerStarted?.Invoke();
1047+
IsApproved = true;
10341048
return true;
10351049
}
10361050
else
@@ -1152,6 +1166,7 @@ public bool StartHost()
11521166
}
11531167

11541168
response.Approved = true;
1169+
IsApproved = true;
11551170
HandleConnectionApproval(ServerClientId, response);
11561171
}
11571172
else
@@ -1413,6 +1428,7 @@ internal void ShutdownInternal()
14131428
}
14141429

14151430
IsConnectedClient = false;
1431+
IsApproved = false;
14161432

14171433
// We need to clean up NetworkObjects before we reset the IsServer
14181434
// and IsClient properties. This provides consistency of these two
@@ -1649,59 +1665,71 @@ private void SendConnectionRequest()
16491665

16501666
private IEnumerator ApprovalTimeout(ulong clientId)
16511667
{
1652-
if (IsServer)
1668+
var timeStarted = IsServer ? LocalTime.TimeAsFloat : Time.realtimeSinceStartup;
1669+
var timedOut = false;
1670+
var connectionApproved = false;
1671+
var connectionNotApproved = false;
1672+
var timeoutMarker = timeStarted + NetworkConfig.ClientConnectionBufferTimeout;
1673+
1674+
while (IsListening && !ShutdownInProgress && !timedOut && !connectionApproved)
16531675
{
1654-
NetworkTime timeStarted = LocalTime;
1676+
yield return null;
1677+
// Check if we timed out
1678+
timedOut = timeoutMarker < (IsServer ? LocalTime.TimeAsFloat : Time.realtimeSinceStartup);
16551679

1656-
//We yield every frame incase a pending client disconnects and someone else gets its connection id
1657-
while (IsListening && (LocalTime - timeStarted).Time < NetworkConfig.ClientConnectionBufferTimeout && PendingClients.ContainsKey(clientId))
1680+
if (IsServer)
16581681
{
1659-
yield return null;
1660-
}
1682+
// When the client is no longer in the pending clients list and is in the connected clients list
1683+
// it has been approved
1684+
connectionApproved = !PendingClients.ContainsKey(clientId) && ConnectedClients.ContainsKey(clientId);
16611685

1662-
if (!IsListening)
1663-
{
1664-
yield break;
1686+
// For the server side, if the client is in neither list then it was declined or the client disconnected
1687+
connectionNotApproved = !PendingClients.ContainsKey(clientId) && !ConnectedClients.ContainsKey(clientId);
16651688
}
1666-
1667-
if (PendingClients.ContainsKey(clientId) && !ConnectedClients.ContainsKey(clientId))
1689+
else
16681690
{
1669-
// Timeout
1670-
if (NetworkLog.CurrentLogLevel <= LogLevel.Developer)
1671-
{
1672-
NetworkLog.LogInfo($"Client {clientId} Handshake Timed Out");
1673-
}
1674-
1675-
DisconnectClient(clientId);
1691+
connectionApproved = IsApproved;
16761692
}
16771693
}
1678-
else
1694+
1695+
// Exit coroutine if we are no longer listening or a shutdown is in progress (client or server)
1696+
if (!IsListening || ShutdownInProgress)
16791697
{
1680-
float timeStarted = Time.realtimeSinceStartup;
1681-
//We yield every frame in case a pending client disconnects and someone else gets its connection id
1682-
while (IsListening && (Time.realtimeSinceStartup - timeStarted) < NetworkConfig.ClientConnectionBufferTimeout && !IsConnectedClient)
1683-
{
1684-
yield return null;
1685-
}
1698+
yield break;
1699+
}
16861700

1687-
if (!IsConnectedClient && NetworkLog.CurrentLogLevel <= LogLevel.Normal)
1701+
// If the client timed out or was not approved
1702+
if (timedOut || connectionNotApproved)
1703+
{
1704+
// Timeout
1705+
if (NetworkLog.CurrentLogLevel <= LogLevel.Developer)
16881706
{
1689-
// TODO: Possibly add a callback for users to be notified of this condition here?
1690-
NetworkLog.LogWarning($"[ApprovalTimeout] Client timed out! You might need to increase the {nameof(NetworkConfig.ClientConnectionBufferTimeout)} duration. Approval Check Start: {timeStarted} | Approval Check Stopped: {Time.realtimeSinceStartup}");
1707+
if (timedOut)
1708+
{
1709+
if (IsServer)
1710+
{
1711+
// Log a warning that the transport detected a connection but then did not receive a follow up connection request message.
1712+
// (hacking or something happened to the server's network connection)
1713+
NetworkLog.LogWarning($"Server detected a transport connection from Client-{clientId}, but timed out waiting for the connection request message.");
1714+
}
1715+
else
1716+
{
1717+
// We only provide informational logging for the client side
1718+
NetworkLog.LogInfo("Timed out waiting for the server to approve the connection request.");
1719+
}
1720+
}
1721+
else if (connectionNotApproved)
1722+
{
1723+
NetworkLog.LogInfo($"Client-{clientId} was either denied approval or disconnected while being approved.");
1724+
}
16911725
}
16921726

1693-
if (!IsListening)
1727+
if (IsServer)
16941728
{
1695-
yield break;
1729+
DisconnectClient(clientId);
16961730
}
1697-
1698-
if (!IsConnectedClient)
1731+
else
16991732
{
1700-
// Timeout
1701-
if (NetworkLog.CurrentLogLevel <= LogLevel.Developer)
1702-
{
1703-
NetworkLog.LogInfo("Server Handshake Timed Out");
1704-
}
17051733
Shutdown(true);
17061734
}
17071735
}

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,7 @@ public void Handle(ref NetworkContext context)
7979
networkManager.NetworkTickSystem.Reset(networkManager.NetworkTimeSystem.LocalTime, networkManager.NetworkTimeSystem.ServerTime);
8080

8181
networkManager.LocalClient = new NetworkClient() { ClientId = networkManager.LocalClientId };
82+
networkManager.IsApproved = true;
8283

8384
// Only if scene management is disabled do we handle NetworkObject synchronization at this point
8485
if (!networkManager.NetworkConfig.EnableSceneManagement)

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

Lines changed: 34 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,18 @@ public enum HostOrServer
131131

132132
protected bool m_EnableVerboseDebug { get; set; }
133133

134+
/// <summary>
135+
/// When set to true, this will bypass the entire
136+
/// wait for clients to connect process.
137+
/// </summary>
138+
/// <remarks>
139+
/// CAUTION:
140+
/// Setting this to true will bypass other helper
141+
/// identification related code, so this should only
142+
/// be used for connection failure oriented testing
143+
/// </remarks>
144+
protected bool m_BypassConnectionTimeout { get; set; }
145+
134146
/// <summary>
135147
/// Used to display the various integration test
136148
/// stages and can be used to log verbose information
@@ -455,31 +467,36 @@ protected IEnumerator StartServerAndClients()
455467
// Notification that the server and clients have been started
456468
yield return OnStartedServerAndClients();
457469

458-
// Wait for all clients to connect
459-
yield return WaitForClientsConnectedOrTimeOut();
460-
AssertOnTimeout($"{nameof(StartServerAndClients)} timed out waiting for all clients to be connected!");
461-
462-
if (m_UseHost || m_ServerNetworkManager.IsHost)
470+
// When true, we skip everything else (most likely a connection oriented test)
471+
if (!m_BypassConnectionTimeout)
463472
{
464-
// Add the server player instance to all m_ClientSidePlayerNetworkObjects entries
465-
var serverPlayerClones = Object.FindObjectsOfType<NetworkObject>().Where((c) => c.IsPlayerObject && c.OwnerClientId == m_ServerNetworkManager.LocalClientId);
466-
foreach (var playerNetworkObject in serverPlayerClones)
473+
// Wait for all clients to connect
474+
yield return WaitForClientsConnectedOrTimeOut();
475+
476+
AssertOnTimeout($"{nameof(StartServerAndClients)} timed out waiting for all clients to be connected!");
477+
478+
if (m_UseHost || m_ServerNetworkManager.IsHost)
467479
{
468-
if (!m_PlayerNetworkObjects.ContainsKey(playerNetworkObject.NetworkManager.LocalClientId))
480+
// Add the server player instance to all m_ClientSidePlayerNetworkObjects entries
481+
var serverPlayerClones = Object.FindObjectsOfType<NetworkObject>().Where((c) => c.IsPlayerObject && c.OwnerClientId == m_ServerNetworkManager.LocalClientId);
482+
foreach (var playerNetworkObject in serverPlayerClones)
469483
{
470-
m_PlayerNetworkObjects.Add(playerNetworkObject.NetworkManager.LocalClientId, new Dictionary<ulong, NetworkObject>());
484+
if (!m_PlayerNetworkObjects.ContainsKey(playerNetworkObject.NetworkManager.LocalClientId))
485+
{
486+
m_PlayerNetworkObjects.Add(playerNetworkObject.NetworkManager.LocalClientId, new Dictionary<ulong, NetworkObject>());
487+
}
488+
m_PlayerNetworkObjects[playerNetworkObject.NetworkManager.LocalClientId].Add(m_ServerNetworkManager.LocalClientId, playerNetworkObject);
471489
}
472-
m_PlayerNetworkObjects[playerNetworkObject.NetworkManager.LocalClientId].Add(m_ServerNetworkManager.LocalClientId, playerNetworkObject);
473490
}
474-
}
475491

476-
ClientNetworkManagerPostStartInit();
492+
ClientNetworkManagerPostStartInit();
477493

478-
// Notification that at this time the server and client(s) are instantiated,
479-
// started, and connected on both sides.
480-
yield return OnServerAndClientsConnected();
494+
// Notification that at this time the server and client(s) are instantiated,
495+
// started, and connected on both sides.
496+
yield return OnServerAndClientsConnected();
481497

482-
VerboseDebug($"Exiting {nameof(StartServerAndClients)}");
498+
VerboseDebug($"Exiting {nameof(StartServerAndClients)}");
499+
}
483500
}
484501
}
485502

0 commit comments

Comments
 (0)