Skip to content

Commit e484dc6

Browse files
feat: upgrade lobby [MTT-3962][MTT-4105][MTT-4052] (#737)
* Upgrading to Lobby version 1.0.3 * replacing usage of deprecated Lobbies class with LobbyService * Using the reconnecting API to reconnect to a lobby instead of leaving it and re-joining * Adding attempts with cooldowns between them for reconnecting to lobby * simplifying lobby reconnect flow and adding comments * Adding changelog entry * fixing relay allocationId not properly sent to lobby
1 parent b139fb5 commit e484dc6

9 files changed

Lines changed: 60 additions & 36 deletions

File tree

Assets/Scripts/ConnectionManagement/ConnectionState/ClientConnectingState.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -78,10 +78,10 @@ async Task<bool> JoinRelayServerAsync()
7878

7979
try
8080
{
81-
var (ipv4Address, port, allocationIdBytes, connectionData, hostConnectionData, key) =
81+
var (ipv4Address, port, allocationIdBytes, allocationId, connectionData, hostConnectionData, key) =
8282
await UnityRelayUtilities.JoinRelayServerFromJoinCode(m_LocalLobby.RelayJoinCode);
8383

84-
await m_LobbyServiceFacade.UpdatePlayerRelayInfoAsync(allocationIdBytes.ToString(), m_LocalLobby.RelayJoinCode);
84+
await m_LobbyServiceFacade.UpdatePlayerRelayInfoAsync(allocationId.ToString(), m_LocalLobby.RelayJoinCode);
8585
var utp = (UnityTransport)m_ConnectionManager.NetworkManager.NetworkConfig.NetworkTransport;
8686
utp.SetClientRelayData(ipv4Address, port, allocationIdBytes, key, connectionData, hostConnectionData, isSecure: true);
8787
}

Assets/Scripts/ConnectionManagement/ConnectionState/ClientReconnectingState.cs

Lines changed: 28 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@ class ClientReconnectingState : ClientConnectingState
2121
string m_LobbyCode = "";
2222
int m_NbAttempts;
2323

24+
const float k_TimeBetweenAttempts = 5;
25+
2426
public override void Enter()
2527
{
2628
m_LobbyCode = m_LobbyServiceFacade.CurrentUnityLobby != null ? m_LobbyServiceFacade.CurrentUnityLobby.LobbyCode : "";
@@ -71,6 +73,15 @@ public override void OnDisconnectReasonReceived(ConnectStatus disconnectReason)
7173

7274
IEnumerator ReconnectCoroutine()
7375
{
76+
// If not on first attempt, wait some time before trying again, so that if the issue causing the disconnect
77+
// is temporary, it has time to fix itself before we try again. Here we are using a simple fixed cooldown
78+
// but we could want to use exponential backoff instead, to wait a longer time between each failed attempt.
79+
// See https://en.wikipedia.org/wiki/Exponential_backoff
80+
if (m_NbAttempts > 0)
81+
{
82+
yield return new WaitForSeconds(k_TimeBetweenAttempts);
83+
}
84+
7485
Debug.Log("Lost connection to host, trying to reconnect...");
7586

7687
m_ConnectionManager.NetworkManager.Shutdown();
@@ -79,26 +90,34 @@ IEnumerator ReconnectCoroutine()
7990
Debug.Log($"Reconnecting attempt {m_NbAttempts + 1}/{m_ConnectionManager.NbReconnectAttempts}...");
8091
m_ReconnectMessagePublisher.Publish(new ReconnectMessage(m_NbAttempts, m_ConnectionManager.NbReconnectAttempts));
8192
m_NbAttempts++;
82-
if (!string.IsNullOrEmpty(m_LobbyCode))
93+
if (!string.IsNullOrEmpty(m_LobbyCode)) // Attempting to reconnect to lobby.
8394
{
84-
var leavingLobby = m_LobbyServiceFacade.EndTracking();
85-
yield return new WaitUntil(() => leavingLobby.IsCompleted);
86-
var joiningLobby = m_LobbyServiceFacade.TryJoinLobbyAsync("", m_LobbyCode);
87-
yield return new WaitUntil(() => joiningLobby.IsCompleted);
88-
if (joiningLobby.Result.Success)
95+
// When using Lobby with Relay, if a user is disconnected from the Relay server, the server will notify
96+
// the Lobby service and mark the user as disconnected, but will not remove them from the lobby. They
97+
// then have some time to attempt to reconnect (defined by the "Disconnect removal time" parameter on
98+
// the dashboard), after which they will be removed from the lobby completely.
99+
// See https://docs.unity.com/lobby/reconnect-to-lobby.html
100+
var reconnectingToLobby = m_LobbyServiceFacade.ReconnectToLobbyAsync(m_LocalLobby?.LobbyID);
101+
yield return new WaitUntil(() => reconnectingToLobby.IsCompleted);
102+
103+
// If succeeded, attempt to connect to Relay
104+
if (!reconnectingToLobby.IsFaulted && reconnectingToLobby.Result != null)
89105
{
90-
m_LobbyServiceFacade.SetRemoteLobby(joiningLobby.Result.Lobby);
106+
// If this fails, the OnClientDisconnect callback will be invoked by Netcode
91107
var connectingToRelay = ConnectClientAsync();
92108
yield return new WaitUntil(() => connectingToRelay.IsCompleted);
93109
}
94110
else
95111
{
96-
Debug.Log("Failed joining lobby.");
112+
Debug.Log("Failed reconnecting to lobby.");
113+
// Calling OnClientDisconnect to mark this attempt as failed and either start a new one or give up
114+
// and return to the Offline state
97115
OnClientDisconnect(0);
98116
}
99117
}
100-
else
118+
else // If not using Lobby, simply try to reconnect to the server directly
101119
{
120+
// If this fails, the OnClientDisconnect callback will be invoked by Netcode
102121
var connectingClient = ConnectClientAsync();
103122
yield return new WaitUntil(() => connectingClient.IsCompleted);
104123
}

Assets/Scripts/ConnectionManagement/ConnectionState/HostingState.cs

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -59,11 +59,6 @@ public override void OnClientDisconnect(ulong clientId)
5959
var playerId = SessionManager<SessionPlayerData>.Instance.GetPlayerId(clientId);
6060
if (playerId != null)
6161
{
62-
if (m_LobbyServiceFacade.CurrentUnityLobby != null)
63-
{
64-
m_LobbyServiceFacade.RemovePlayerFromLobbyAsync(playerId, m_LobbyServiceFacade.CurrentUnityLobby.Id);
65-
}
66-
6762
var sessionData = SessionManager<SessionPlayerData>.Instance.GetPlayerData(playerId);
6863
if (sessionData.HasValue)
6964
{

Assets/Scripts/UnityServices/Lobbies/LobbyAPIInterface.cs

Lines changed: 16 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,6 @@
99

1010
namespace Unity.BossRoom.UnityServices.Lobbies
1111
{
12-
using Lobbies = Unity.Services.Lobbies.Lobbies;
13-
1412
/// <summary>
1513
/// Wrapper for all the interactions with the Lobby API.
1614
/// </summary>
@@ -82,24 +80,24 @@ public async Task<Lobby> CreateLobby(string requesterUasId, string lobbyName, in
8280
Data = lobbyData
8381
};
8482

85-
return await ExceptionHandling(Lobbies.Instance.CreateLobbyAsync(lobbyName, maxPlayers, createOptions));
83+
return await ExceptionHandling(LobbyService.Instance.CreateLobbyAsync(lobbyName, maxPlayers, createOptions));
8684
}
8785

8886
public async Task DeleteLobby(string lobbyId)
8987
{
90-
await ExceptionHandling(Lobbies.Instance.DeleteLobbyAsync(lobbyId));
88+
await ExceptionHandling(LobbyService.Instance.DeleteLobbyAsync(lobbyId));
9189
}
9290

9391
public async Task<Lobby> JoinLobbyByCode(string requesterUasId, string lobbyCode, Dictionary<string, PlayerDataObject> localUserData)
9492
{
9593
JoinLobbyByCodeOptions joinOptions = new JoinLobbyByCodeOptions { Player = new Player(id: requesterUasId, data: localUserData) };
96-
return await ExceptionHandling(Lobbies.Instance.JoinLobbyByCodeAsync(lobbyCode, joinOptions));
94+
return await ExceptionHandling(LobbyService.Instance.JoinLobbyByCodeAsync(lobbyCode, joinOptions));
9795
}
9896

9997
public async Task<Lobby> JoinLobbyById(string requesterUasId, string lobbyId, Dictionary<string, PlayerDataObject> localUserData)
10098
{
10199
JoinLobbyByIdOptions joinOptions = new JoinLobbyByIdOptions { Player = new Player(id: requesterUasId, data: localUserData) };
102-
return await ExceptionHandling(Lobbies.Instance.JoinLobbyByIdAsync(lobbyId, joinOptions));
100+
return await ExceptionHandling(LobbyService.Instance.JoinLobbyByIdAsync(lobbyId, joinOptions));
103101
}
104102

105103
public async Task<Lobby> QuickJoinLobby(string requesterUasId, Dictionary<string, PlayerDataObject> localUserData)
@@ -110,14 +108,19 @@ public async Task<Lobby> QuickJoinLobby(string requesterUasId, Dictionary<string
110108
Player = new Player(id: requesterUasId, data: localUserData)
111109
};
112110

113-
return await ExceptionHandling(Lobbies.Instance.QuickJoinLobbyAsync(joinRequest));
111+
return await ExceptionHandling(LobbyService.Instance.QuickJoinLobbyAsync(joinRequest));
112+
}
113+
114+
public async Task<Lobby> ReconnectToLobby(string lobbyId)
115+
{
116+
return await ExceptionHandling(LobbyService.Instance.ReconnectToLobbyAsync(lobbyId));
114117
}
115118

116119
public async Task RemovePlayerFromLobby(string requesterUasId, string lobbyId)
117120
{
118121
try
119122
{
120-
await ExceptionHandling(Lobbies.Instance.RemovePlayerAsync(lobbyId, requesterUasId));
123+
await ExceptionHandling(LobbyService.Instance.RemovePlayerAsync(lobbyId, requesterUasId));
121124
}
122125
catch (LobbyServiceException e)
123126
when (e is { Reason: LobbyExceptionReason.PlayerNotFound })
@@ -135,18 +138,18 @@ public async Task<QueryResponse> QueryAllLobbies()
135138
Order = m_Order
136139
};
137140

138-
return await ExceptionHandling(Lobbies.Instance.QueryLobbiesAsync(queryOptions));
141+
return await ExceptionHandling(LobbyService.Instance.QueryLobbiesAsync(queryOptions));
139142
}
140143

141144
public async Task<Lobby> GetLobby(string lobbyId)
142145
{
143-
return await ExceptionHandling(Lobbies.Instance.GetLobbyAsync(lobbyId));
146+
return await ExceptionHandling(LobbyService.Instance.GetLobbyAsync(lobbyId));
144147
}
145148

146149
public async Task<Lobby> UpdateLobby(string lobbyId, Dictionary<string, DataObject> data, bool shouldLock)
147150
{
148151
UpdateLobbyOptions updateOptions = new UpdateLobbyOptions { Data = data, IsLocked = shouldLock };
149-
return await ExceptionHandling(Lobbies.Instance.UpdateLobbyAsync(lobbyId, updateOptions));
152+
return await ExceptionHandling(LobbyService.Instance.UpdateLobbyAsync(lobbyId, updateOptions));
150153
}
151154

152155
public async Task<Lobby> UpdatePlayer(string lobbyId, string playerId, Dictionary<string, PlayerDataObject> data, string allocationId, string connectionInfo)
@@ -157,12 +160,12 @@ public async Task<Lobby> UpdatePlayer(string lobbyId, string playerId, Dictionar
157160
AllocationId = allocationId,
158161
ConnectionInfo = connectionInfo
159162
};
160-
return await ExceptionHandling(Lobbies.Instance.UpdatePlayerAsync(lobbyId, playerId, updateOptions));
163+
return await ExceptionHandling(LobbyService.Instance.UpdatePlayerAsync(lobbyId, playerId, updateOptions));
161164
}
162165

163166
public async void SendHeartbeatPing(string lobbyId)
164167
{
165-
await ExceptionHandling(Lobbies.Instance.SendHeartbeatPingAsync(lobbyId));
168+
await ExceptionHandling(LobbyService.Instance.SendHeartbeatPingAsync(lobbyId));
166169
}
167170
}
168171
}

Assets/Scripts/UnityServices/Lobbies/LobbyServiceFacade.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -264,6 +264,11 @@ public async Task RetrieveAndPublishLobbyListAsync()
264264
}
265265
}
266266

267+
public async Task<Lobby> ReconnectToLobbyAsync(string lobbyId)
268+
{
269+
return await m_LobbyApiInterface.ReconnectToLobby(lobbyId);
270+
}
271+
267272
/// <summary>
268273
/// Attempt to leave a lobby
269274
/// </summary>

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,11 +32,13 @@ Additional documentation and release notes are available at [Multiplayer Documen
3232
* Instead of a NetworkBehaviour that carries a WinState netvar we now pass the win state on the server to the PostGame scene and it then stores that state in the netvar, eliminating the need to preserve a NetworkBehaviour-bearing gameObject across scenes. (#724)
3333
* Bump to NGO 1.0.1 (#720)
3434
* Reduced the MaxPacketQueueSize UTP parameter value from 512 to 256 (#728). This reduces the amount of memory used by UTP by around 1 MB. Boss Room does not need a bigger queue size than this because there can only be 7 clients connected to a host and UTP already limits the maximum number of in-flight packets to 32 per connection.
35+
* Updated Lobby package to 1.0.3 and reworked our auto-reconnect flow to use the Reconnect feature from the Lobby API (#737). Now, clients do not leave the lobby when they are disconnected, and the host does not remove them from it. They are marked as disconnected by the Relay server and can attempt to reconnect to the lobby directly, until a certain timeout (specified by the Disconnect removal time parameer, set in the dashboard configuration).
3536
* Cleanup
3637
* Namespaces in the project have been changed to map to their assembly definitions (#732)
3738
* Numerous name changes for fields and variables to match their new type names (#732)
3839
* Removed DynamicNavObstacle - an unused class (#732)
3940
* Merged networked data classes into their Server counterparts. An example of that change is the contents of NetworkCharacterState getting moved into ServerCharacter, contents of NetworkDoorState getting moved into SwitchedDoor etc. (#732)
41+
*
4042
### Removed
4143
*
4244
### Fixed

Packages/com.unity.multiplayer.samples.coop/Utilities/Net/UnityRelayUtilities.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ public static async
4444
}
4545

4646
public static async
47-
Task<(string ipv4address, ushort port, byte[] allocationIdBytes, byte[] connectionData, byte[]
47+
Task<(string ipv4address, ushort port, byte[] allocationIdBytes, Guid allocationId, byte[] connectionData, byte[]
4848
hostConnectionData, byte[] key)> JoinRelayServerFromJoinCode(string joinCode)
4949
{
5050
JoinAllocation allocation;
@@ -62,7 +62,7 @@ public static async
6262
Debug.Log($"client: {allocation.AllocationId}");
6363

6464
var dtlsEndpoint = allocation.ServerEndpoints.First(e => e.ConnectionType == k_KDtlsConnType);
65-
return (dtlsEndpoint.Host, (ushort)dtlsEndpoint.Port, allocation.AllocationIdBytes,
65+
return (dtlsEndpoint.Host, (ushort)dtlsEndpoint.Port, allocation.AllocationIdBytes, allocation.AllocationId,
6666
allocation.ConnectionData, allocation.HostConnectionData, allocation.Key);
6767
}
6868
}

Packages/manifest.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
"com.unity.postprocessing": "3.2.1",
1616
"com.unity.render-pipelines.universal": "12.1.6",
1717
"com.unity.services.authentication": "2.1.1",
18-
"com.unity.services.lobby": "1.0.0-pre.6",
18+
"com.unity.services.lobby": "1.0.3",
1919
"com.unity.services.relay": "1.0.3",
2020
"com.unity.test-framework": "1.1.31",
2121
"com.unity.textmeshpro": "3.0.6",

Packages/packages-lock.json

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -238,18 +238,18 @@
238238
"url": "https://packages.unity.com"
239239
},
240240
"com.unity.services.lobby": {
241-
"version": "1.0.0-pre.6",
241+
"version": "1.0.3",
242242
"depth": 0,
243243
"source": "registry",
244244
"dependencies": {
245-
"com.unity.services.core": "1.1.0-pre.10",
245+
"com.unity.services.core": "1.4.0",
246246
"com.unity.modules.unitywebrequest": "1.0.0",
247247
"com.unity.modules.unitywebrequestassetbundle": "1.0.0",
248248
"com.unity.modules.unitywebrequestaudio": "1.0.0",
249249
"com.unity.modules.unitywebrequesttexture": "1.0.0",
250250
"com.unity.modules.unitywebrequestwww": "1.0.0",
251-
"com.unity.nuget.newtonsoft-json": "2.0.0",
252-
"com.unity.services.authentication": "1.0.0-pre.6"
251+
"com.unity.nuget.newtonsoft-json": "3.0.2",
252+
"com.unity.services.authentication": "2.0.0"
253253
},
254254
"url": "https://packages.unity.com"
255255
},

0 commit comments

Comments
 (0)