Skip to content

Commit 9930593

Browse files
fix: leaving lobby when disconnecting (#515) (#553)
* Host now removes clients from lobby when they disconnect * Clients leave the lobby when disconnecting * Host deletes the lobby when disconnecting (cherry picked from commit 93ff764) Co-Authored-By: Sam Bellomo <[email protected]>
1 parent eda9e9b commit 9930593

File tree

9 files changed

+133
-30
lines changed

9 files changed

+133
-30
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:22939a0b19a2e47d1c43cf76d229f2a99bcc8511f03020367645a163b6ab2fba
3-
size 35402
2+
oid sha256:b72ef30fe5939bc280416c3707afd0c6945d0ba5126d43f82a59f284dffe19d3
3+
size 35886

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;
@@ -18,6 +19,7 @@ public class ApplicationController : MonoBehaviour
1819
[SerializeField] UpdateRunner m_UpdateRunner;
1920
[SerializeField] GameNetPortal m_GameNetPortal;
2021
[SerializeField] ClientGameNetPortal m_ClientNetPortal;
22+
[SerializeField] ServerGameNetPortal m_ServerGameNetPortal;
2123

2224
LocalLobby m_LocalLobby;
2325
LobbyServiceFacade m_LobbyServiceFacade;
@@ -37,6 +39,7 @@ private void Awake()
3739
scope.BindInstanceAsSingle(m_UpdateRunner);
3840
scope.BindInstanceAsSingle(m_GameNetPortal);
3941
scope.BindInstanceAsSingle(m_ClientNetPortal);
42+
scope.BindInstanceAsSingle(m_ServerGameNetPortal);
4043

4144
//the following singletons represent the local representations of the lobby that we're in and the user that we are
4245
//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: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
using System;
2+
using Unity.Multiplayer.Samples.BossRoom.Shared;
23
using Unity.Multiplayer.Samples.BossRoom.Shared.Infrastructure;
34
using Unity.Multiplayer.Samples.BossRoom.Shared.Net.UnityServices.Lobbies;
45
using UnityEngine;
@@ -33,12 +34,14 @@ public class ClientGameNetPortal : MonoBehaviour
3334
/// </summary>
3435
public event Action NetworkTimedOut;
3536

36-
private LobbyServiceFacade m_LobbyServiceFacade;
37+
ApplicationController m_ApplicationController;
38+
LobbyServiceFacade m_LobbyServiceFacade;
3739
IPublisher<ConnectStatus> m_ConnectStatusPub;
3840

3941
[Inject]
40-
private void InjectDependencies(LobbyServiceFacade lobbyServiceFacade, IPublisher<ConnectStatus> connectStatusPub)
42+
private void InjectDependencies(ApplicationController applicationController, LobbyServiceFacade lobbyServiceFacade, IPublisher<ConnectStatus> connectStatusPub)
4143
{
44+
m_ApplicationController = applicationController;
4245
m_LobbyServiceFacade = lobbyServiceFacade;
4346
m_ConnectStatusPub = connectStatusPub;
4447
}
@@ -123,19 +126,24 @@ private void OnDisconnectOrTimeout(ulong clientID)
123126
// 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.
124127
if (!NetworkManager.Singleton.IsHost || NetworkManager.Singleton.IsHost && NetworkManager.Singleton.LocalClientId == clientID)
125128
{
129+
var lobbyCode = "";
130+
if (m_LobbyServiceFacade.CurrentUnityLobby != null)
131+
{
132+
lobbyCode = m_LobbyServiceFacade.CurrentUnityLobby.LobbyCode;
133+
}
134+
126135
//On a client disconnect we want to take them back to the main menu.
127136
//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;
128137
if (SceneManager.GetActiveScene().name != "MainMenu")
129138
{
130139
// we're not at the main menu, so we obviously had a connection before... thus, we aren't in a timeout scenario.
131140
// Just shut down networking and switch back to main menu.
132-
NetworkManager.Singleton.Shutdown();
133141
if (!DisconnectReason.HasTransitionReason)
134142
{
135143
//disconnect that happened for some other reason than user UI interaction--should display a message.
136144
DisconnectReason.SetDisconnectReason(ConnectStatus.GenericDisconnect);
137145
}
138-
SceneManager.LoadScene("MainMenu");
146+
m_ApplicationController.LeaveSession();
139147
}
140148
else if (DisconnectReason.Reason == ConnectStatus.GenericDisconnect || DisconnectReason.Reason == ConnectStatus.Undefined)
141149
{

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

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

107107
//we synthesize a "OnNetworkSpawn" event for the NetworkManager out of existing events. At some point
108108
//we expect NetworkManager will expose an event like this itself.
109-
NetManager.OnServerStarted += OnNetworkReady;
110109
NetManager.OnClientConnectedCallback += ClientNetworkReadyWrapper;
111110
}
112111

@@ -122,7 +121,6 @@ private void OnDestroy()
122121
{
123122
if (NetManager != null)
124123
{
125-
NetManager.OnServerStarted -= OnNetworkReady;
126124
NetManager.OnClientConnectedCallback -= ClientNetworkReadyWrapper;
127125
}
128126

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

Lines changed: 33 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
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 UnityEngine;
68
using Unity.Netcode;
79
using UnityEngine.SceneManagement;
@@ -16,21 +18,29 @@ public class ServerGameNetPortal : MonoBehaviour
1618
[SerializeField]
1719
NetworkObject m_GameState;
1820

19-
private GameNetPortal m_Portal;
21+
GameNetPortal m_Portal;
2022

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

2426
/// <summary>
2527
/// Keeps a list of what clients are in what scenes.
2628
/// </summary>
27-
private Dictionary<ulong, int> m_ClientSceneMap = new Dictionary<ulong, int>();
29+
Dictionary<ulong, int> m_ClientSceneMap = new Dictionary<ulong, int>();
2830

2931
/// <summary>
3032
/// The active server scene index.
3133
/// </summary>
3234
public int ServerScene { get { return UnityEngine.SceneManagement.SceneManager.GetActiveScene().buildIndex; } }
3335

36+
LobbyServiceFacade m_LobbyServiceFacade;
37+
38+
[Inject]
39+
void InjectDependencies(LobbyServiceFacade lobbyServiceFacade)
40+
{
41+
m_LobbyServiceFacade = lobbyServiceFacade;
42+
}
43+
3444
void Start()
3545
{
3646
m_Portal = GetComponent<GameNetPortal>();
@@ -79,7 +89,7 @@ public void OnNetworkReady()
7989
/// Handles the case where NetworkManager has told us a client has disconnected. This includes ourselves, if we're the host,
8090
/// and the server is stopped."
8191
/// </summary>
82-
private void OnClientDisconnect(ulong clientId)
92+
void OnClientDisconnect(ulong clientId)
8393
{
8494
m_ClientSceneMap.Remove(clientId);
8595

@@ -88,6 +98,21 @@ private void OnClientDisconnect(ulong clientId)
8898
//the ServerGameNetPortal may be initialized again, which will cause its OnNetworkSpawn to be called again.
8999
//Consequently we need to unregister anything we registered, when the NetworkManager is shutting down.
90100
m_Portal.NetManager.OnClientDisconnectCallback -= OnClientDisconnect;
101+
if (m_LobbyServiceFacade.CurrentUnityLobby != null)
102+
{
103+
m_LobbyServiceFacade.DeleteLobbyAsync(m_LobbyServiceFacade.CurrentUnityLobby.Id, null, null);
104+
}
105+
}
106+
else
107+
{
108+
var playerId = SessionManager<SessionPlayerData>.Instance.GetPlayerId(clientId);
109+
if (playerId != null)
110+
{
111+
if (m_LobbyServiceFacade.CurrentUnityLobby != null)
112+
{
113+
m_LobbyServiceFacade.RemovePlayerFromLobbyAsync(playerId, m_LobbyServiceFacade.CurrentUnityLobby.Id, null, null);
114+
}
115+
}
91116
}
92117
}
93118

@@ -105,7 +130,7 @@ public void OnUserDisconnectRequest()
105130
Clear();
106131
}
107132

108-
private void Clear()
133+
void Clear()
109134
{
110135
//resets all our runtime state.
111136
m_ClientSceneMap.Clear();
@@ -135,7 +160,7 @@ public bool AreAllClientsInServerScene()
135160
/// <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>
136161
/// <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>
137162
/// <param name="connectionApprovedCallback">The delegate we must invoke to signal that the connection was approved or not. </param>
138-
private void ApprovalCheck(byte[] connectionData, ulong clientId, NetworkManager.ConnectionApprovedDelegate connectionApprovedCallback)
163+
void ApprovalCheck(byte[] connectionData, ulong clientId, NetworkManager.ConnectionApprovedDelegate connectionApprovedCallback)
139164
{
140165
if (connectionData.Length > k_MaxConnectPayload)
141166
{
@@ -210,7 +235,7 @@ private void ApprovalCheck(byte[] connectionData, ulong clientId, NetworkManager
210235
}
211236
}
212237

213-
private IEnumerator WaitToDisconnect(ulong clientId)
238+
IEnumerator WaitToDisconnect(ulong clientId)
214239
{
215240
yield return new WaitForSeconds(0.5f);
216241
m_Portal.NetManager.DisconnectClient(clientId);
@@ -243,7 +268,7 @@ public void SendServerToClientConnectResult(ulong clientID, ConnectStatus status
243268
/// <summary>
244269
/// 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
245270
/// </summary>
246-
private void ServerStartedHandler()
271+
void ServerStartedHandler()
247272
{
248273
// server spawns game state
249274
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)

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

Lines changed: 58 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
using System;
22
using System.Collections.Generic;
3+
using System.Threading.Tasks;
34
using Unity.Multiplayer.Samples.BossRoom.Shared.Infrastructure;
45
using Unity.Multiplayer.Samples.BossRoom.Shared.Net.UnityServices.Infrastructure;
56
using Unity.Services.Authentication;
67
using Unity.Services.Lobbies.Models;
8+
using UnityEngine;
79

810
namespace Unity.Multiplayer.Samples.BossRoom.Shared.Net.UnityServices.Lobbies
911
{
@@ -86,8 +88,9 @@ public void BeginTracking(Lobby lobby)
8688
}
8789
}
8890

89-
public void EndTracking()
91+
public Task EndTracking()
9092
{
93+
var task = Task.CompletedTask;
9194
if (m_IsTracking)
9295
{
9396
m_UpdateRunner.Unsubscribe(UpdateLobby);
@@ -98,12 +101,14 @@ public void EndTracking()
98101

99102
if (!string.IsNullOrEmpty(m_LocalLobby?.LobbyID))
100103
{
101-
LeaveLobbyAsync(m_LocalLobby?.LobbyID, null, null);
104+
task = LeaveLobbyAsync(m_LocalLobby?.LobbyID, null, null);
102105
}
103106

104107
m_LocalUser.ResetState();
105108
m_LocalLobby?.Reset(m_LocalUser);
106109
}
110+
111+
return task;
107112
}
108113

109114
void UpdateLobby(float unused)
@@ -157,24 +162,24 @@ public void CreateLobbyAsync(string lobbyName, int maxPlayers, bool isPrivate, O
157162
/// <summary>
158163
/// Attempt to join an existing lobby. Will try to join via code, if code is null - will try to join via ID.
159164
/// </summary>
160-
public void JoinLobbyAsync(string lobbyId, string lobbyCode, Action<Lobby> onSuccess, Action onFailure)
165+
public Task JoinLobbyAsync(string lobbyId, string lobbyCode, Action<Lobby> onSuccess, Action onFailure)
161166
{
162167
if (!m_RateLimitJoin.CanCall ||
163168
(lobbyId == null && lobbyCode == null))
164169
{
165170
onFailure?.Invoke();
166171
UnityEngine.Debug.LogWarning("Join Lobby hit the rate limit.");
167-
return;
172+
return Task.CompletedTask;
168173
}
169174
m_RateLimitJoin.PutOnCooldown();
170175

171176
if (!string.IsNullOrEmpty(lobbyCode))
172177
{
173-
m_LobbyApiInterface.JoinLobbyAsync_ByCode(AuthenticationService.Instance.PlayerId, lobbyCode, m_LocalUser.GetDataForUnityServices(), onSuccess, onFailure);
178+
return m_LobbyApiInterface.JoinLobbyAsync_ByCode(AuthenticationService.Instance.PlayerId, lobbyCode, m_LocalUser.GetDataForUnityServices(), onSuccess, onFailure);
174179
}
175180
else
176181
{
177-
m_LobbyApiInterface.JoinLobbyAsync_ById(AuthenticationService.Instance.PlayerId, lobbyId, m_LocalUser.GetDataForUnityServices(), onSuccess, onFailure);
182+
return m_LobbyApiInterface.JoinLobbyAsync_ById(AuthenticationService.Instance.PlayerId, lobbyId, m_LocalUser.GetDataForUnityServices(), onSuccess, onFailure);
178183
}
179184
}
180185

@@ -231,10 +236,55 @@ void RetrieveLobbyAsync(string lobbyId, Action<Lobby> onSuccess, Action onFailur
231236
/// <summary>
232237
/// Attempt to leave a lobby, and then delete it if no players remain.
233238
/// </summary>
234-
public void LeaveLobbyAsync(string lobbyId, Action onSuccess, Action onFailure)
239+
public Task LeaveLobbyAsync(string lobbyId, Action onSuccess, Action onFailure)
235240
{
236241
string uasId = AuthenticationService.Instance.PlayerId;
237-
m_LobbyApiInterface.LeaveLobbyAsync(uasId, lobbyId, onSuccess, onFailure);
242+
return m_LobbyApiInterface.LeaveLobbyAsync(uasId, lobbyId, onSuccess, onFailure);
243+
}
244+
245+
public void RemovePlayerFromLobbyAsync(string uasId, string lobbyId, Action onSuccess, Action onFailure)
246+
{
247+
if (m_LocalUser.IsHost)
248+
{
249+
RetrieveLobbyAsync(lobbyId, OnRetrieveSuccess, onFailure);
250+
251+
252+
void OnRetrieveSuccess(Lobby lobby)
253+
{
254+
bool playerFound = false;
255+
foreach (var player in lobby.Players)
256+
{
257+
if (player.Id == uasId)
258+
{
259+
m_LobbyApiInterface.LeaveLobbyAsync(uasId, lobbyId, onSuccess, onFailure);
260+
playerFound = true;
261+
break;
262+
}
263+
}
264+
265+
if (!playerFound)
266+
{
267+
Debug.Log($"Player {uasId} has already left the lobby.");
268+
}
269+
}
270+
271+
}
272+
else
273+
{
274+
Debug.LogError("Only the host can remove other players from the lobby.");
275+
}
276+
}
277+
278+
public void DeleteLobbyAsync(string lobbyId, Action onSuccess, Action onFailure)
279+
{
280+
if (m_LocalUser.IsHost)
281+
{
282+
m_LobbyApiInterface.DeleteLobbyAsync(lobbyId, onSuccess, onFailure);
283+
}
284+
else
285+
{
286+
Debug.LogError("Only the host can delete a lobby.");
287+
}
238288
}
239289

240290
/// <summary>

0 commit comments

Comments
 (0)