Skip to content

fix: leaving lobby when disconnecting #515

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 21 commits into from
Mar 14, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
06d32a0
Added call to leave lobby when disconnecting
LPLafontaineB Mar 7, 2022
c3179c4
Added comments and todo for better fix
LPLafontaineB Mar 7, 2022
4bd7922
Merge branch 'release/GDC2022' into fix/leaving-lobby-when-disconnecting
LPLafontaineB Mar 11, 2022
59ea34c
Moved removal responsibility of player from lobby to host
LPLafontaineB Mar 11, 2022
fa7a9cc
Adding leaving lobby then trying to re-join in reconnect coroutine
LPLafontaineB Mar 11, 2022
11b6578
Squashed commit of the following:
LPLafontaineB Mar 11, 2022
4c728e1
Adding injection to ServerGameNetPortal
LPLafontaineB Mar 11, 2022
eb6cf85
replaced dumb while with less dumb yield waitwhile
LPLafontaineB Mar 11, 2022
7140994
Removed unnecessary callback registering
LPLafontaineB Mar 11, 2022
9d6dd18
Fixed infinite loop issue
LPLafontaineB Mar 11, 2022
0c3d0c8
Made sure client leaves lobby by themselves when disconnecting as wel…
LPLafontaineB Mar 11, 2022
4b5244d
Adding verification that player is still in lobby before removing them
LPLafontaineB Mar 11, 2022
930eda8
Made sure the client leaves the lobby between each reconnection attem…
LPLafontaineB Mar 11, 2022
0c79ecb
set nbReconnectionAttempts to 2
LPLafontaineB Mar 11, 2022
4acbddb
feat: replace guid with auth playerid for session management (#488)
LPLafontaineB Mar 14, 2022
c960453
Merge branch 'feature/cherrypick/player-id-in-session-manager' into f…
LPLafontaineB Mar 14, 2022
5505add
cleaning up
LPLafontaineB Mar 14, 2022
81608c3
Merge branch 'release/GDC2022' into fix/leaving-lobby-when-disconnecting
LPLafontaineB Mar 14, 2022
2862e59
Added todo comment for better way to wait
LPLafontaineB Mar 14, 2022
9ed88b3
reduced number of reconnect attempts to 1
LPLafontaineB Mar 14, 2022
e4dbecf
Used task objects to do proper WaitUntil(IsCompleted) in reconnect co…
LPLafontaineB Mar 14, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions Assets/BossRoom/Scenes/Startup.unity
Git LFS file not shown
3 changes: 3 additions & 0 deletions Assets/BossRoom/Scripts/Shared/ApplicationController.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
using System.Collections;
using BossRoom.Scripts.Shared.Net.UnityServices.Auth;
using Unity.Multiplayer.Samples.BossRoom.Client;
using Unity.Multiplayer.Samples.BossRoom.Server;
using Unity.Multiplayer.Samples.BossRoom.Shared.Infrastructure;
using Unity.Multiplayer.Samples.BossRoom.Shared.Net.UnityServices.Infrastructure;
using Unity.Multiplayer.Samples.BossRoom.Shared.Net.UnityServices.Lobbies;
Expand All @@ -19,6 +20,7 @@ public class ApplicationController : MonoBehaviour
[SerializeField] UpdateRunner m_UpdateRunner;
[SerializeField] GameNetPortal m_GameNetPortal;
[SerializeField] ClientGameNetPortal m_ClientNetPortal;
[SerializeField] ServerGameNetPortal m_ServerGameNetPortal;

LocalLobby m_LocalLobby;
LobbyServiceFacade m_LobbyServiceFacade;
Expand All @@ -38,6 +40,7 @@ private void Awake()
scope.BindInstanceAsSingle(m_UpdateRunner);
scope.BindInstanceAsSingle(m_GameNetPortal);
scope.BindInstanceAsSingle(m_ClientNetPortal);
scope.BindInstanceAsSingle(m_ServerGameNetPortal);

//the following singletons represent the local representations of the lobby that we're in and the user that we are
//they can persist longer than the lifetime of the UI in MainMenu where we set up the lobby that we create or join
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using System;
using System.Collections;
using Unity.Multiplayer.Samples.BossRoom.Shared;
using Unity.Multiplayer.Samples.BossRoom.Shared.Infrastructure;
using Unity.Multiplayer.Samples.BossRoom.Shared.Net.UnityServices.Lobbies;
using UnityEngine;
Expand Down Expand Up @@ -39,12 +40,14 @@ public class ClientGameNetPortal : MonoBehaviour
/// </summary>
public event Action NetworkTimedOut;

private LobbyServiceFacade m_LobbyServiceFacade;
ApplicationController m_ApplicationController;
LobbyServiceFacade m_LobbyServiceFacade;
IPublisher<ConnectStatus> m_ConnectStatusPub;

[Inject]
private void InjectDependencies(LobbyServiceFacade lobbyServiceFacade, IPublisher<ConnectStatus> connectStatusPub)
private void InjectDependencies(ApplicationController applicationController, LobbyServiceFacade lobbyServiceFacade, IPublisher<ConnectStatus> connectStatusPub)
{
m_ApplicationController = applicationController;
m_LobbyServiceFacade = lobbyServiceFacade;
m_ConnectStatusPub = connectStatusPub;
}
Expand Down Expand Up @@ -153,23 +156,28 @@ private void OnDisconnectOrTimeout(ulong clientID)
// 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.
if (!NetworkManager.Singleton.IsHost || NetworkManager.Singleton.IsHost && NetworkManager.Singleton.LocalClientId == clientID)
{
var lobbyCode = "";
if (m_LobbyServiceFacade.CurrentUnityLobby != null)
{
lobbyCode = m_LobbyServiceFacade.CurrentUnityLobby.LobbyCode;
}

//On a client disconnect we want to take them back to the main menu.
//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;
if (SceneManager.GetActiveScene().name != "MainMenu")
{
if (DisconnectReason.Reason == ConnectStatus.UserRequestedDisconnect || DisconnectReason.Reason == ConnectStatus.HostDisconnected || NetworkManager.Singleton.IsHost)
{
// simply shut down and go back to main menu
NetworkManager.Singleton.Shutdown();
SceneLoaderWrapper.Instance.LoadScene("MainMenu");
m_ApplicationController.LeaveSession();
}
else
{
DisconnectReason.SetDisconnectReason(ConnectStatus.Reconnecting);
// load new scene to workaround MTT-2684
SceneManager.LoadScene("Loading");
// try reconnecting
m_TryToReconnectCoroutine ??= StartCoroutine(TryToReconnect());
m_TryToReconnectCoroutine ??= StartCoroutine(TryToReconnect(lobbyCode));
}
}
else if (DisconnectReason.Reason == ConnectStatus.GenericDisconnect || DisconnectReason.Reason == ConnectStatus.Undefined)
Expand All @@ -183,7 +191,7 @@ private void OnDisconnectOrTimeout(ulong clientID)
}
}

private IEnumerator TryToReconnect()
private IEnumerator TryToReconnect(string lobbyCode)
{
Debug.Log("Lost connection to host, trying to reconnect...");
int nbTries = 0;
Expand All @@ -192,13 +200,29 @@ private IEnumerator TryToReconnect()
NetworkManager.Singleton.Shutdown();
yield return new WaitWhile(() => NetworkManager.Singleton.ShutdownInProgress); // wait until NetworkManager completes shutting down
Debug.Log($"Reconnecting attempt {nbTries + 1}/{k_NbReconnectAttempts}...");
ConnectClient(null);
if (!string.IsNullOrEmpty(lobbyCode))
{
var leavingLobby = m_LobbyServiceFacade.EndTracking();
yield return new WaitUntil(() => leavingLobby.IsCompleted);
var joiningLobby = m_LobbyServiceFacade.JoinLobbyAsync("", lobbyCode, onSuccess: lobby =>
{
m_LobbyServiceFacade.BeginTracking(lobby);
ConnectClient(null);
}
, null);
yield return new WaitUntil(() => joiningLobby.IsCompleted);
}
else
{
ConnectClient(null);
}
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
nbTries++;
}

// If the coroutine has not been stopped before this, it means we failed to connect during all attempts
Debug.Log("All tries failed, returning to main menu");
m_LobbyServiceFacade.ForceLeaveLobbyAttempt();
NetworkManager.Singleton.Shutdown();
SceneLoaderWrapper.Instance.LoadScene("MainMenu");
if (!DisconnectReason.HasTransitionReason)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,6 @@ private void Awake()

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

Expand All @@ -124,7 +123,6 @@ private void OnDestroy()
{
if (NetManager != null)
{
NetManager.OnServerStarted -= OnNetworkReady;
NetManager.OnClientConnectedCallback -= ClientNetworkReadyWrapper;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,11 @@
using System.Collections.Generic;
using Unity.Collections;
using Unity.Multiplayer.Samples.BossRoom.Client;
using Unity.Multiplayer.Samples.BossRoom.Shared.Infrastructure;
using Unity.Multiplayer.Samples.BossRoom.Shared.Net.UnityServices.Lobbies;
using Unity.Multiplayer.Samples.Utilities;
using UnityEngine;
using Unity.Netcode;
using UnityEngine.SceneManagement;

namespace Unity.Multiplayer.Samples.BossRoom.Server
{
Expand All @@ -17,21 +18,29 @@ public class ServerGameNetPortal : MonoBehaviour
[SerializeField]
NetworkObject m_GameState;

private GameNetPortal m_Portal;
GameNetPortal m_Portal;

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

/// <summary>
/// Keeps a list of what clients are in what scenes.
/// </summary>
private Dictionary<ulong, int> m_ClientSceneMap = new Dictionary<ulong, int>();
Dictionary<ulong, int> m_ClientSceneMap = new Dictionary<ulong, int>();

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

LobbyServiceFacade m_LobbyServiceFacade;

[Inject]
void InjectDependencies(LobbyServiceFacade lobbyServiceFacade)
{
m_LobbyServiceFacade = lobbyServiceFacade;
}

void Start()
{
m_Portal = GetComponent<GameNetPortal>();
Expand Down Expand Up @@ -82,7 +91,7 @@ public void OnNetworkReady()
/// Handles the case where NetworkManager has told us a client has disconnected. This includes ourselves, if we're the host,
/// and the server is stopped."
/// </summary>
private void OnClientDisconnect(ulong clientId)
void OnClientDisconnect(ulong clientId)
{
m_ClientSceneMap.Remove(clientId);

Expand All @@ -91,6 +100,21 @@ private void OnClientDisconnect(ulong clientId)
//the ServerGameNetPortal may be initialized again, which will cause its OnNetworkSpawn to be called again.
//Consequently we need to unregister anything we registered, when the NetworkManager is shutting down.
m_Portal.NetManager.OnClientDisconnectCallback -= OnClientDisconnect;
if (m_LobbyServiceFacade.CurrentUnityLobby != null)
{
m_LobbyServiceFacade.DeleteLobbyAsync(m_LobbyServiceFacade.CurrentUnityLobby.Id, null, null);
}
}
else
{
var playerId = SessionManager<SessionPlayerData>.Instance.GetPlayerId(clientId);
if (playerId != null)
{
if (m_LobbyServiceFacade.CurrentUnityLobby != null)
{
m_LobbyServiceFacade.RemovePlayerFromLobbyAsync(playerId, m_LobbyServiceFacade.CurrentUnityLobby.Id, null, null);
}
}
}
}

Expand Down Expand Up @@ -121,7 +145,7 @@ IEnumerator WaitToShutdown()
m_Portal.NetManager.Shutdown();
}

private void Clear()
void Clear()
{
//resets all our runtime state.
m_ClientSceneMap.Clear();
Expand Down Expand Up @@ -151,7 +175,7 @@ public bool AreAllClientsInServerScene()
/// <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>
/// <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>
/// <param name="connectionApprovedCallback">The delegate we must invoke to signal that the connection was approved or not. </param>
private void ApprovalCheck(byte[] connectionData, ulong clientId, NetworkManager.ConnectionApprovedDelegate connectionApprovedCallback)
void ApprovalCheck(byte[] connectionData, ulong clientId, NetworkManager.ConnectionApprovedDelegate connectionApprovedCallback)
{
if (connectionData.Length > k_MaxConnectPayload)
{
Expand Down Expand Up @@ -226,7 +250,7 @@ private void ApprovalCheck(byte[] connectionData, ulong clientId, NetworkManager
}
}

private IEnumerator WaitToDisconnect(ulong clientId)
IEnumerator WaitToDisconnect(ulong clientId)
{
yield return new WaitForSeconds(0.5f);
m_Portal.NetManager.DisconnectClient(clientId);
Expand Down Expand Up @@ -269,7 +293,7 @@ public void SendServerToClientConnectResult(ulong clientID, ConnectStatus status
/// <summary>
/// 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
/// </summary>
private void ServerStartedHandler()
void ServerStartedHandler()
{
// server spawns game state
var gameState = Instantiate(m_GameState);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,11 @@ public class RateLimitCooldown
readonly float m_CooldownTime;
readonly UpdateRunner m_UpdateRunner;
Queue<Action> m_PendingOperations = new Queue<Action>();
Queue<Action> m_NextPendingOperations = new Queue<Action>();

public void EnqueuePendingOperation(Action action)
{
m_PendingOperations.Enqueue(action);
m_NextPendingOperations.Enqueue(action);
}

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

void OnUpdate(float dt)
{
while (m_NextPendingOperations.Count > 0)
{
m_PendingOperations.Enqueue(m_NextPendingOperations.Dequeue());
}
m_TimeSinceLastCall += dt;

if (CanCall)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -87,18 +87,20 @@ public void DeleteLobbyAsync(string lobbyId, Action onComplete, Action onFailed)
RunTask(task, onComplete, onFailed);
}

public void JoinLobbyAsync_ByCode(string requesterUasId, string lobbyCode, Dictionary<string, PlayerDataObject> localUserData, Action<Lobby> onComplete, Action onFailed)
public Task JoinLobbyAsync_ByCode(string requesterUasId, string lobbyCode, Dictionary<string, PlayerDataObject> localUserData, Action<Lobby> onComplete, Action onFailed)
{
JoinLobbyByCodeOptions joinOptions = new JoinLobbyByCodeOptions { Player = new Player(id: requesterUasId, data: localUserData) };
var task = Lobbies.Instance.JoinLobbyByCodeAsync(lobbyCode, joinOptions);
RunTask(task, onComplete, onFailed);
return task;
}

public void JoinLobbyAsync_ById(string requesterUasId, string lobbyId, Dictionary<string, PlayerDataObject> localUserData, Action<Lobby> onComplete, Action onFailed)
public Task JoinLobbyAsync_ById(string requesterUasId, string lobbyId, Dictionary<string, PlayerDataObject> localUserData, Action<Lobby> onComplete, Action onFailed)
{
JoinLobbyByIdOptions joinOptions = new JoinLobbyByIdOptions { Player = new Player(id: requesterUasId, data: localUserData) };
var task = Lobbies.Instance.JoinLobbyByIdAsync(lobbyId, joinOptions);
RunTask(task, onComplete, onFailed);
return task;
}

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

public void LeaveLobbyAsync(string requesterUasId, string lobbyId, Action onComplete, Action onFailed)
public Task LeaveLobbyAsync(string requesterUasId, string lobbyId, Action onComplete, Action onFailed)
{
var task = Lobbies.Instance.RemovePlayerAsync(lobbyId, requesterUasId);
RunTask(task, onComplete, onFailed);
return task;
}

public void QueryAllLobbiesAsync(Action<QueryResponse> onComplete, Action onFailed)
Expand Down
Loading