Skip to content

Commit 93ff764

Browse files
fix: leaving lobby when disconnecting (#515)
* Host now removes clients from lobby when they disconnect * Clients leave the lobby when disconnecting * Host deletes the lobby when disconnecting Co-authored-by: Sam Bellomo <[email protected]>
1 parent 6b323e7 commit 93ff764

File tree

9 files changed

+152
-34
lines changed

9 files changed

+152
-34
lines changed

Assets/BossRoom/Scenes/Startup.unity

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
version https://git-lfs.github.com/spec/v1
2-
oid sha256:dc7f49af8b674af05f84b82125472cba81519e43c16de951dfda618a3884c681
3-
size 44733
2+
oid sha256:225b7ddef7979d1165b3bb8cbbc709fc6bcf97367370b4b1bfe16d74919bbf74
3+
size 44138

Assets/BossRoom/Scripts/Shared/ApplicationController.cs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
using System.Collections;
22
using BossRoom.Scripts.Shared.Net.UnityServices.Auth;
33
using Unity.Multiplayer.Samples.BossRoom.Client;
4+
using Unity.Multiplayer.Samples.BossRoom.Server;
45
using Unity.Multiplayer.Samples.BossRoom.Shared.Infrastructure;
56
using Unity.Multiplayer.Samples.BossRoom.Shared.Net.UnityServices.Infrastructure;
67
using Unity.Multiplayer.Samples.BossRoom.Shared.Net.UnityServices.Lobbies;
@@ -19,6 +20,7 @@ public class ApplicationController : MonoBehaviour
1920
[SerializeField] UpdateRunner m_UpdateRunner;
2021
[SerializeField] GameNetPortal m_GameNetPortal;
2122
[SerializeField] ClientGameNetPortal m_ClientNetPortal;
23+
[SerializeField] ServerGameNetPortal m_ServerGameNetPortal;
2224

2325
LocalLobby m_LocalLobby;
2426
LobbyServiceFacade m_LobbyServiceFacade;
@@ -38,6 +40,7 @@ private void Awake()
3840
scope.BindInstanceAsSingle(m_UpdateRunner);
3941
scope.BindInstanceAsSingle(m_GameNetPortal);
4042
scope.BindInstanceAsSingle(m_ClientNetPortal);
43+
scope.BindInstanceAsSingle(m_ServerGameNetPortal);
4144

4245
//the following singletons represent the local representations of the lobby that we're in and the user that we are
4346
//they can persist longer than the lifetime of the UI in MainMenu where we set up the lobby that we create or join

Assets/BossRoom/Scripts/Shared/Net/ConnectionManagement/ClientGameNetPortal.cs

Lines changed: 31 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
using System;
22
using System.Collections;
3+
using Unity.Multiplayer.Samples.BossRoom.Shared;
34
using Unity.Multiplayer.Samples.BossRoom.Shared.Infrastructure;
45
using Unity.Multiplayer.Samples.BossRoom.Shared.Net.UnityServices.Lobbies;
56
using UnityEngine;
@@ -39,12 +40,14 @@ public class ClientGameNetPortal : MonoBehaviour
3940
/// </summary>
4041
public event Action NetworkTimedOut;
4142

42-
private LobbyServiceFacade m_LobbyServiceFacade;
43+
ApplicationController m_ApplicationController;
44+
LobbyServiceFacade m_LobbyServiceFacade;
4345
IPublisher<ConnectStatus> m_ConnectStatusPub;
4446

4547
[Inject]
46-
private void InjectDependencies(LobbyServiceFacade lobbyServiceFacade, IPublisher<ConnectStatus> connectStatusPub)
48+
private void InjectDependencies(ApplicationController applicationController, LobbyServiceFacade lobbyServiceFacade, IPublisher<ConnectStatus> connectStatusPub)
4749
{
50+
m_ApplicationController = applicationController;
4851
m_LobbyServiceFacade = lobbyServiceFacade;
4952
m_ConnectStatusPub = connectStatusPub;
5053
}
@@ -153,23 +156,28 @@ private void OnDisconnectOrTimeout(ulong clientID)
153156
// not a host (in which case we know this is about us) or that the clientID is the same as ours if we are the host.
154157
if (!NetworkManager.Singleton.IsHost || NetworkManager.Singleton.IsHost && NetworkManager.Singleton.LocalClientId == clientID)
155158
{
159+
var lobbyCode = "";
160+
if (m_LobbyServiceFacade.CurrentUnityLobby != null)
161+
{
162+
lobbyCode = m_LobbyServiceFacade.CurrentUnityLobby.LobbyCode;
163+
}
164+
156165
//On a client disconnect we want to take them back to the main menu.
157166
//We have to check here in SceneManager if our active scene is the main menu, as if it is, it means we timed out rather than a raw disconnect;
158167
if (SceneManager.GetActiveScene().name != "MainMenu")
159168
{
160169
if (DisconnectReason.Reason == ConnectStatus.UserRequestedDisconnect || DisconnectReason.Reason == ConnectStatus.HostDisconnected || NetworkManager.Singleton.IsHost)
161170
{
162171
// simply shut down and go back to main menu
163-
NetworkManager.Singleton.Shutdown();
164-
SceneLoaderWrapper.Instance.LoadScene("MainMenu");
172+
m_ApplicationController.LeaveSession();
165173
}
166174
else
167175
{
168176
DisconnectReason.SetDisconnectReason(ConnectStatus.Reconnecting);
169177
// load new scene to workaround MTT-2684
170178
SceneManager.LoadScene("Loading");
171179
// try reconnecting
172-
m_TryToReconnectCoroutine ??= StartCoroutine(TryToReconnect());
180+
m_TryToReconnectCoroutine ??= StartCoroutine(TryToReconnect(lobbyCode));
173181
}
174182
}
175183
else if (DisconnectReason.Reason == ConnectStatus.GenericDisconnect || DisconnectReason.Reason == ConnectStatus.Undefined)
@@ -183,7 +191,7 @@ private void OnDisconnectOrTimeout(ulong clientID)
183191
}
184192
}
185193

186-
private IEnumerator TryToReconnect()
194+
private IEnumerator TryToReconnect(string lobbyCode)
187195
{
188196
Debug.Log("Lost connection to host, trying to reconnect...");
189197
int nbTries = 0;
@@ -192,13 +200,29 @@ private IEnumerator TryToReconnect()
192200
NetworkManager.Singleton.Shutdown();
193201
yield return new WaitWhile(() => NetworkManager.Singleton.ShutdownInProgress); // wait until NetworkManager completes shutting down
194202
Debug.Log($"Reconnecting attempt {nbTries + 1}/{k_NbReconnectAttempts}...");
195-
ConnectClient(null);
203+
if (!string.IsNullOrEmpty(lobbyCode))
204+
{
205+
var leavingLobby = m_LobbyServiceFacade.EndTracking();
206+
yield return new WaitUntil(() => leavingLobby.IsCompleted);
207+
var joiningLobby = m_LobbyServiceFacade.JoinLobbyAsync("", lobbyCode, onSuccess: lobby =>
208+
{
209+
m_LobbyServiceFacade.BeginTracking(lobby);
210+
ConnectClient(null);
211+
}
212+
, null);
213+
yield return new WaitUntil(() => joiningLobby.IsCompleted);
214+
}
215+
else
216+
{
217+
ConnectClient(null);
218+
}
196219
yield return new WaitForSeconds(1.1f * k_TimeoutDuration); // wait a bit longer than the timeout duration to make sure we have enough time to stop this coroutine if successful
197220
nbTries++;
198221
}
199222

200223
// If the coroutine has not been stopped before this, it means we failed to connect during all attempts
201224
Debug.Log("All tries failed, returning to main menu");
225+
m_LobbyServiceFacade.ForceLeaveLobbyAttempt();
202226
NetworkManager.Singleton.Shutdown();
203227
SceneLoaderWrapper.Instance.LoadScene("MainMenu");
204228
if (!DisconnectReason.HasTransitionReason)

Assets/BossRoom/Scripts/Shared/Net/ConnectionManagement/GameNetPortal.cs

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -108,7 +108,6 @@ private void Awake()
108108

109109
//we synthesize a "OnNetworkSpawn" event for the NetworkManager out of existing events. At some point
110110
//we expect NetworkManager will expose an event like this itself.
111-
NetManager.OnServerStarted += OnNetworkReady;
112111
NetManager.OnClientConnectedCallback += ClientNetworkReadyWrapper;
113112
}
114113

@@ -124,7 +123,6 @@ private void OnDestroy()
124123
{
125124
if (NetManager != null)
126125
{
127-
NetManager.OnServerStarted -= OnNetworkReady;
128126
NetManager.OnClientConnectedCallback -= ClientNetworkReadyWrapper;
129127
}
130128

Assets/BossRoom/Scripts/Shared/Net/ConnectionManagement/ServerGameNetPortal.cs

Lines changed: 33 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,11 @@
22
using System.Collections.Generic;
33
using Unity.Collections;
44
using Unity.Multiplayer.Samples.BossRoom.Client;
5+
using Unity.Multiplayer.Samples.BossRoom.Shared.Infrastructure;
6+
using Unity.Multiplayer.Samples.BossRoom.Shared.Net.UnityServices.Lobbies;
57
using Unity.Multiplayer.Samples.Utilities;
68
using UnityEngine;
79
using Unity.Netcode;
8-
using UnityEngine.SceneManagement;
910

1011
namespace Unity.Multiplayer.Samples.BossRoom.Server
1112
{
@@ -17,21 +18,29 @@ public class ServerGameNetPortal : MonoBehaviour
1718
[SerializeField]
1819
NetworkObject m_GameState;
1920

20-
private GameNetPortal m_Portal;
21+
GameNetPortal m_Portal;
2122

2223
// used in ApprovalCheck. This is intended as a bit of light protection against DOS attacks that rely on sending silly big buffers of garbage.
23-
private const int k_MaxConnectPayload = 1024;
24+
const int k_MaxConnectPayload = 1024;
2425

2526
/// <summary>
2627
/// Keeps a list of what clients are in what scenes.
2728
/// </summary>
28-
private Dictionary<ulong, int> m_ClientSceneMap = new Dictionary<ulong, int>();
29+
Dictionary<ulong, int> m_ClientSceneMap = new Dictionary<ulong, int>();
2930

3031
/// <summary>
3132
/// The active server scene index.
3233
/// </summary>
3334
public int ServerScene { get { return UnityEngine.SceneManagement.SceneManager.GetActiveScene().buildIndex; } }
3435

36+
LobbyServiceFacade m_LobbyServiceFacade;
37+
38+
[Inject]
39+
void InjectDependencies(LobbyServiceFacade lobbyServiceFacade)
40+
{
41+
m_LobbyServiceFacade = lobbyServiceFacade;
42+
}
43+
3544
void Start()
3645
{
3746
m_Portal = GetComponent<GameNetPortal>();
@@ -82,7 +91,7 @@ public void OnNetworkReady()
8291
/// Handles the case where NetworkManager has told us a client has disconnected. This includes ourselves, if we're the host,
8392
/// and the server is stopped."
8493
/// </summary>
85-
private void OnClientDisconnect(ulong clientId)
94+
void OnClientDisconnect(ulong clientId)
8695
{
8796
m_ClientSceneMap.Remove(clientId);
8897

@@ -91,6 +100,21 @@ private void OnClientDisconnect(ulong clientId)
91100
//the ServerGameNetPortal may be initialized again, which will cause its OnNetworkSpawn to be called again.
92101
//Consequently we need to unregister anything we registered, when the NetworkManager is shutting down.
93102
m_Portal.NetManager.OnClientDisconnectCallback -= OnClientDisconnect;
103+
if (m_LobbyServiceFacade.CurrentUnityLobby != null)
104+
{
105+
m_LobbyServiceFacade.DeleteLobbyAsync(m_LobbyServiceFacade.CurrentUnityLobby.Id, null, null);
106+
}
107+
}
108+
else
109+
{
110+
var playerId = SessionManager<SessionPlayerData>.Instance.GetPlayerId(clientId);
111+
if (playerId != null)
112+
{
113+
if (m_LobbyServiceFacade.CurrentUnityLobby != null)
114+
{
115+
m_LobbyServiceFacade.RemovePlayerFromLobbyAsync(playerId, m_LobbyServiceFacade.CurrentUnityLobby.Id, null, null);
116+
}
117+
}
94118
}
95119
}
96120

@@ -121,7 +145,7 @@ IEnumerator WaitToShutdown()
121145
m_Portal.NetManager.Shutdown();
122146
}
123147

124-
private void Clear()
148+
void Clear()
125149
{
126150
//resets all our runtime state.
127151
m_ClientSceneMap.Clear();
@@ -151,7 +175,7 @@ public bool AreAllClientsInServerScene()
151175
/// <param name="connectionData">binary data passed into StartClient. In our case this is the client's GUID, which is a unique identifier for their install of the game that persists across app restarts. </param>
152176
/// <param name="clientId">This is the clientId that Netcode assigned us on login. It does not persist across multiple logins from the same client. </param>
153177
/// <param name="connectionApprovedCallback">The delegate we must invoke to signal that the connection was approved or not. </param>
154-
private void ApprovalCheck(byte[] connectionData, ulong clientId, NetworkManager.ConnectionApprovedDelegate connectionApprovedCallback)
178+
void ApprovalCheck(byte[] connectionData, ulong clientId, NetworkManager.ConnectionApprovedDelegate connectionApprovedCallback)
155179
{
156180
if (connectionData.Length > k_MaxConnectPayload)
157181
{
@@ -226,7 +250,7 @@ private void ApprovalCheck(byte[] connectionData, ulong clientId, NetworkManager
226250
}
227251
}
228252

229-
private IEnumerator WaitToDisconnect(ulong clientId)
253+
IEnumerator WaitToDisconnect(ulong clientId)
230254
{
231255
yield return new WaitForSeconds(0.5f);
232256
m_Portal.NetManager.DisconnectClient(clientId);
@@ -269,7 +293,7 @@ public void SendServerToClientConnectResult(ulong clientID, ConnectStatus status
269293
/// <summary>
270294
/// Called after the server is created- This is primarily meant for the host server to clean up or handle/set state as its starting up
271295
/// </summary>
272-
private void ServerStartedHandler()
296+
void ServerStartedHandler()
273297
{
274298
// server spawns game state
275299
var gameState = Instantiate(m_GameState);

Assets/BossRoom/Scripts/Shared/Net/UnityServices/Infrastructure/RateLimitCooldown.cs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,11 @@ public class RateLimitCooldown
1010
readonly float m_CooldownTime;
1111
readonly UpdateRunner m_UpdateRunner;
1212
Queue<Action> m_PendingOperations = new Queue<Action>();
13+
Queue<Action> m_NextPendingOperations = new Queue<Action>();
1314

1415
public void EnqueuePendingOperation(Action action)
1516
{
16-
m_PendingOperations.Enqueue(action);
17+
m_NextPendingOperations.Enqueue(action);
1718
}
1819

1920
public RateLimitCooldown(float cooldownTime, UpdateRunner updateRunner)
@@ -32,6 +33,10 @@ public void PutOnCooldown()
3233

3334
void OnUpdate(float dt)
3435
{
36+
while (m_NextPendingOperations.Count > 0)
37+
{
38+
m_PendingOperations.Enqueue(m_NextPendingOperations.Dequeue());
39+
}
3540
m_TimeSinceLastCall += dt;
3641

3742
if (CanCall)

Assets/BossRoom/Scripts/Shared/Net/UnityServices/Lobbies/LobbyAPIInterface.cs

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -87,18 +87,20 @@ public void DeleteLobbyAsync(string lobbyId, Action onComplete, Action onFailed)
8787
RunTask(task, onComplete, onFailed);
8888
}
8989

90-
public void JoinLobbyAsync_ByCode(string requesterUasId, string lobbyCode, Dictionary<string, PlayerDataObject> localUserData, Action<Lobby> onComplete, Action onFailed)
90+
public Task JoinLobbyAsync_ByCode(string requesterUasId, string lobbyCode, Dictionary<string, PlayerDataObject> localUserData, Action<Lobby> onComplete, Action onFailed)
9191
{
9292
JoinLobbyByCodeOptions joinOptions = new JoinLobbyByCodeOptions { Player = new Player(id: requesterUasId, data: localUserData) };
9393
var task = Lobbies.Instance.JoinLobbyByCodeAsync(lobbyCode, joinOptions);
9494
RunTask(task, onComplete, onFailed);
95+
return task;
9596
}
9697

97-
public void JoinLobbyAsync_ById(string requesterUasId, string lobbyId, Dictionary<string, PlayerDataObject> localUserData, Action<Lobby> onComplete, Action onFailed)
98+
public Task JoinLobbyAsync_ById(string requesterUasId, string lobbyId, Dictionary<string, PlayerDataObject> localUserData, Action<Lobby> onComplete, Action onFailed)
9899
{
99100
JoinLobbyByIdOptions joinOptions = new JoinLobbyByIdOptions { Player = new Player(id: requesterUasId, data: localUserData) };
100101
var task = Lobbies.Instance.JoinLobbyByIdAsync(lobbyId, joinOptions);
101102
RunTask(task, onComplete, onFailed);
103+
return task;
102104
}
103105

104106
public void QuickJoinLobbyAsync(string requesterUasId, Dictionary<string, PlayerDataObject> localUserData, Action<Lobby> onComplete, Action onFailed)
@@ -113,10 +115,11 @@ public void QuickJoinLobbyAsync(string requesterUasId, Dictionary<string, Player
113115
RunTask(task, onComplete, onFailed);
114116
}
115117

116-
public void LeaveLobbyAsync(string requesterUasId, string lobbyId, Action onComplete, Action onFailed)
118+
public Task LeaveLobbyAsync(string requesterUasId, string lobbyId, Action onComplete, Action onFailed)
117119
{
118120
var task = Lobbies.Instance.RemovePlayerAsync(lobbyId, requesterUasId);
119121
RunTask(task, onComplete, onFailed);
122+
return task;
120123
}
121124

122125
public void QueryAllLobbiesAsync(Action<QueryResponse> onComplete, Action onFailed)

0 commit comments

Comments
 (0)