Skip to content

cherrypick fix: leaving lobby when disconnecting (#515) #553

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 1 commit into from
Mar 15, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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 @@ -18,6 +19,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 @@ -37,6 +39,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,4 +1,5 @@
using System;
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 @@ -33,12 +34,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 @@ -123,19 +126,24 @@ 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")
{
// we're not at the main menu, so we obviously had a connection before... thus, we aren't in a timeout scenario.
// Just shut down networking and switch back to main menu.
NetworkManager.Singleton.Shutdown();
if (!DisconnectReason.HasTransitionReason)
{
//disconnect that happened for some other reason than user UI interaction--should display a message.
DisconnectReason.SetDisconnectReason(ConnectStatus.GenericDisconnect);
}
SceneManager.LoadScene("MainMenu");
m_ApplicationController.LeaveSession();
}
else if (DisconnectReason.Reason == ConnectStatus.GenericDisconnect || DisconnectReason.Reason == ConnectStatus.Undefined)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,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 @@ -122,7 +121,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,6 +2,8 @@
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 UnityEngine;
using Unity.Netcode;
using UnityEngine.SceneManagement;
Expand All @@ -16,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 @@ -79,7 +89,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 @@ -88,6 +98,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 All @@ -105,7 +130,7 @@ public void OnUserDisconnectRequest()
Clear();
}

private void Clear()
void Clear()
{
//resets all our runtime state.
m_ClientSceneMap.Clear();
Expand Down Expand Up @@ -135,7 +160,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 @@ -210,7 +235,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 @@ -243,7 +268,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
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Unity.Multiplayer.Samples.BossRoom.Shared.Infrastructure;
using Unity.Multiplayer.Samples.BossRoom.Shared.Net.UnityServices.Infrastructure;
using Unity.Services.Authentication;
using Unity.Services.Lobbies.Models;
using UnityEngine;

namespace Unity.Multiplayer.Samples.BossRoom.Shared.Net.UnityServices.Lobbies
{
Expand Down Expand Up @@ -86,8 +88,9 @@ public void BeginTracking(Lobby lobby)
}
}

public void EndTracking()
public Task EndTracking()
{
var task = Task.CompletedTask;
if (m_IsTracking)
{
m_UpdateRunner.Unsubscribe(UpdateLobby);
Expand All @@ -98,12 +101,14 @@ public void EndTracking()

if (!string.IsNullOrEmpty(m_LocalLobby?.LobbyID))
{
LeaveLobbyAsync(m_LocalLobby?.LobbyID, null, null);
task = LeaveLobbyAsync(m_LocalLobby?.LobbyID, null, null);
}

m_LocalUser.ResetState();
m_LocalLobby?.Reset(m_LocalUser);
}

return task;
}

void UpdateLobby(float unused)
Expand Down Expand Up @@ -157,24 +162,24 @@ public void CreateLobbyAsync(string lobbyName, int maxPlayers, bool isPrivate, O
/// <summary>
/// Attempt to join an existing lobby. Will try to join via code, if code is null - will try to join via ID.
/// </summary>
public void JoinLobbyAsync(string lobbyId, string lobbyCode, Action<Lobby> onSuccess, Action onFailure)
public Task JoinLobbyAsync(string lobbyId, string lobbyCode, Action<Lobby> onSuccess, Action onFailure)
{
if (!m_RateLimitJoin.CanCall ||
(lobbyId == null && lobbyCode == null))
{
onFailure?.Invoke();
UnityEngine.Debug.LogWarning("Join Lobby hit the rate limit.");
return;
return Task.CompletedTask;
}
m_RateLimitJoin.PutOnCooldown();

if (!string.IsNullOrEmpty(lobbyCode))
{
m_LobbyApiInterface.JoinLobbyAsync_ByCode(AuthenticationService.Instance.PlayerId, lobbyCode, m_LocalUser.GetDataForUnityServices(), onSuccess, onFailure);
return m_LobbyApiInterface.JoinLobbyAsync_ByCode(AuthenticationService.Instance.PlayerId, lobbyCode, m_LocalUser.GetDataForUnityServices(), onSuccess, onFailure);
}
else
{
m_LobbyApiInterface.JoinLobbyAsync_ById(AuthenticationService.Instance.PlayerId, lobbyId, m_LocalUser.GetDataForUnityServices(), onSuccess, onFailure);
return m_LobbyApiInterface.JoinLobbyAsync_ById(AuthenticationService.Instance.PlayerId, lobbyId, m_LocalUser.GetDataForUnityServices(), onSuccess, onFailure);
}
}

Expand Down Expand Up @@ -231,10 +236,55 @@ void RetrieveLobbyAsync(string lobbyId, Action<Lobby> onSuccess, Action onFailur
/// <summary>
/// Attempt to leave a lobby, and then delete it if no players remain.
/// </summary>
public void LeaveLobbyAsync(string lobbyId, Action onSuccess, Action onFailure)
public Task LeaveLobbyAsync(string lobbyId, Action onSuccess, Action onFailure)
{
string uasId = AuthenticationService.Instance.PlayerId;
m_LobbyApiInterface.LeaveLobbyAsync(uasId, lobbyId, onSuccess, onFailure);
return m_LobbyApiInterface.LeaveLobbyAsync(uasId, lobbyId, onSuccess, onFailure);
}

public void RemovePlayerFromLobbyAsync(string uasId, string lobbyId, Action onSuccess, Action onFailure)
{
if (m_LocalUser.IsHost)
{
RetrieveLobbyAsync(lobbyId, OnRetrieveSuccess, onFailure);


void OnRetrieveSuccess(Lobby lobby)
{
bool playerFound = false;
foreach (var player in lobby.Players)
{
if (player.Id == uasId)
{
m_LobbyApiInterface.LeaveLobbyAsync(uasId, lobbyId, onSuccess, onFailure);
playerFound = true;
break;
}
}

if (!playerFound)
{
Debug.Log($"Player {uasId} has already left the lobby.");
}
}

}
else
{
Debug.LogError("Only the host can remove other players from the lobby.");
}
}

public void DeleteLobbyAsync(string lobbyId, Action onSuccess, Action onFailure)
{
if (m_LocalUser.IsHost)
{
m_LobbyApiInterface.DeleteLobbyAsync(lobbyId, onSuccess, onFailure);
}
else
{
Debug.LogError("Only the host can delete a lobby.");
}
}

/// <summary>
Expand Down
Loading