Skip to content
Merged
60 changes: 13 additions & 47 deletions Assets/Scripts/UnityServices/Lobbies/LobbyAPIInterface.cs
Original file line number Diff line number Diff line change
@@ -1,11 +1,9 @@
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Unity.BossRoom.Infrastructure;
using Unity.Services.Lobbies;
using Unity.Services.Lobbies.Models;
using UnityEngine;
using VContainer;

namespace Unity.BossRoom.UnityServices.Lobbies
{
Expand All @@ -16,15 +14,11 @@ public class LobbyAPIInterface
{
const int k_MaxLobbiesToShow = 16; // If more are necessary, consider retrieving paginated results or using filters.

readonly IPublisher<UnityServiceErrorMessage> m_UnityServiceErrorMessagePublisher;
readonly List<QueryFilter> m_Filters;
readonly List<QueryOrder> m_Order;

[Inject]
public LobbyAPIInterface(IPublisher<UnityServiceErrorMessage> unityServiceErrorMessagePublisher)
public LobbyAPIInterface()
{
m_UnityServiceErrorMessagePublisher = unityServiceErrorMessagePublisher;

// Filter for open lobbies only
m_Filters = new List<QueryFilter>()
{
Expand All @@ -43,34 +37,6 @@ public LobbyAPIInterface(IPublisher<UnityServiceErrorMessage> unityServiceErrorM
};
}

async Task<T> ExceptionHandling<T>(Task<T> task)
{
try
{
return await task;
}
catch (Exception e)
{
var reason = $"{e.Message} ({e.InnerException?.Message})"; // Lobby error type, then HTTP error type.
m_UnityServiceErrorMessagePublisher.Publish(new UnityServiceErrorMessage("Lobby Error", reason, UnityServiceErrorMessage.Service.Lobby, e));
throw;
}
}

async Task ExceptionHandling(Task task)
{
try
{
await task;
}
catch (Exception e)
{
var reason = $"{e.Message} ({e.InnerException?.Message})"; // Lobby error type, then HTTP error type.
m_UnityServiceErrorMessagePublisher.Publish(new UnityServiceErrorMessage("Lobby Error", reason, UnityServiceErrorMessage.Service.Lobby, e));
throw;
}
}

public async Task<Lobby> CreateLobby(string requesterUasId, string lobbyName, int maxPlayers, bool isPrivate, Dictionary<string, PlayerDataObject> hostUserData, Dictionary<string, DataObject> lobbyData)
{
CreateLobbyOptions createOptions = new CreateLobbyOptions
Expand All @@ -80,24 +46,24 @@ public async Task<Lobby> CreateLobby(string requesterUasId, string lobbyName, in
Data = lobbyData
};

return await ExceptionHandling(LobbyService.Instance.CreateLobbyAsync(lobbyName, maxPlayers, createOptions));
return await LobbyService.Instance.CreateLobbyAsync(lobbyName, maxPlayers, createOptions);
}

public async Task DeleteLobby(string lobbyId)
{
await ExceptionHandling(LobbyService.Instance.DeleteLobbyAsync(lobbyId));
await LobbyService.Instance.DeleteLobbyAsync(lobbyId);
}

public async Task<Lobby> JoinLobbyByCode(string requesterUasId, string lobbyCode, Dictionary<string, PlayerDataObject> localUserData)
{
JoinLobbyByCodeOptions joinOptions = new JoinLobbyByCodeOptions { Player = new Player(id: requesterUasId, data: localUserData) };
return await ExceptionHandling(LobbyService.Instance.JoinLobbyByCodeAsync(lobbyCode, joinOptions));
return await LobbyService.Instance.JoinLobbyByCodeAsync(lobbyCode, joinOptions);
}

public async Task<Lobby> JoinLobbyById(string requesterUasId, string lobbyId, Dictionary<string, PlayerDataObject> localUserData)
{
JoinLobbyByIdOptions joinOptions = new JoinLobbyByIdOptions { Player = new Player(id: requesterUasId, data: localUserData) };
return await ExceptionHandling(LobbyService.Instance.JoinLobbyByIdAsync(lobbyId, joinOptions));
return await LobbyService.Instance.JoinLobbyByIdAsync(lobbyId, joinOptions);
}

public async Task<Lobby> QuickJoinLobby(string requesterUasId, Dictionary<string, PlayerDataObject> localUserData)
Expand All @@ -108,19 +74,19 @@ public async Task<Lobby> QuickJoinLobby(string requesterUasId, Dictionary<string
Player = new Player(id: requesterUasId, data: localUserData)
};

return await ExceptionHandling(LobbyService.Instance.QuickJoinLobbyAsync(joinRequest));
return await LobbyService.Instance.QuickJoinLobbyAsync(joinRequest);
}

public async Task<Lobby> ReconnectToLobby(string lobbyId)
{
return await ExceptionHandling(LobbyService.Instance.ReconnectToLobbyAsync(lobbyId));
return await LobbyService.Instance.ReconnectToLobbyAsync(lobbyId);
}

public async Task RemovePlayerFromLobby(string requesterUasId, string lobbyId)
{
try
{
await ExceptionHandling(LobbyService.Instance.RemovePlayerAsync(lobbyId, requesterUasId));
await LobbyService.Instance.RemovePlayerAsync(lobbyId, requesterUasId);
}
catch (LobbyServiceException e)
when (e is { Reason: LobbyExceptionReason.PlayerNotFound })
Expand All @@ -138,18 +104,18 @@ public async Task<QueryResponse> QueryAllLobbies()
Order = m_Order
};

return await ExceptionHandling(LobbyService.Instance.QueryLobbiesAsync(queryOptions));
return await LobbyService.Instance.QueryLobbiesAsync(queryOptions);
}

public async Task<Lobby> GetLobby(string lobbyId)
{
return await ExceptionHandling(LobbyService.Instance.GetLobbyAsync(lobbyId));
return await LobbyService.Instance.GetLobbyAsync(lobbyId);
}

public async Task<Lobby> UpdateLobby(string lobbyId, Dictionary<string, DataObject> data, bool shouldLock)
{
UpdateLobbyOptions updateOptions = new UpdateLobbyOptions { Data = data, IsLocked = shouldLock };
return await ExceptionHandling(LobbyService.Instance.UpdateLobbyAsync(lobbyId, updateOptions));
return await LobbyService.Instance.UpdateLobbyAsync(lobbyId, updateOptions);
}

public async Task<Lobby> UpdatePlayer(string lobbyId, string playerId, Dictionary<string, PlayerDataObject> data, string allocationId, string connectionInfo)
Expand All @@ -160,12 +126,12 @@ public async Task<Lobby> UpdatePlayer(string lobbyId, string playerId, Dictionar
AllocationId = allocationId,
ConnectionInfo = connectionInfo
};
return await ExceptionHandling(LobbyService.Instance.UpdatePlayerAsync(lobbyId, playerId, updateOptions));
return await LobbyService.Instance.UpdatePlayerAsync(lobbyId, playerId, updateOptions);
}

public async void SendHeartbeatPing(string lobbyId)
{
await ExceptionHandling(LobbyService.Instance.SendHeartbeatPingAsync(lobbyId));
await LobbyService.Instance.SendHeartbeatPingAsync(lobbyId);
}
}
}
106 changes: 97 additions & 9 deletions Assets/Scripts/UnityServices/Lobbies/LobbyServiceFacade.cs
Original file line number Diff line number Diff line change
Expand Up @@ -91,9 +91,18 @@ public Task EndTracking()
{
CurrentUnityLobby = null;

if (!string.IsNullOrEmpty(m_LocalLobby?.LobbyID))
var lobbyId = m_LocalLobby?.LobbyID;

if (!string.IsNullOrEmpty(lobbyId))
{
task = LeaveLobbyAsync(m_LocalLobby?.LobbyID);
if (m_LocalUser.IsHost)
{
task = DeleteLobbyAsync(lobbyId);
}
else
{
task = LeaveLobbyAsync(lobbyId);
}
}

m_LocalUser.ResetState();
Expand Down Expand Up @@ -146,6 +155,10 @@ async void UpdateLobby(float unused)
{
m_RateLimitQuery.PutOnCooldown();
}
else if (e.Reason != LobbyExceptionReason.LobbyNotFound && !m_LocalUser.IsHost) // If Lobby is not found and if we are not the host, it has already been deleted. No need to publish the error here.
{
PublishError(e);
}
}
}

Expand All @@ -171,6 +184,10 @@ async void UpdateLobby(float unused)
{
m_RateLimitHost.PutOnCooldown();
}
else
{
PublishError(e);
}
}

return (false, null);
Expand Down Expand Up @@ -207,6 +224,10 @@ async void UpdateLobby(float unused)
{
m_RateLimitJoin.PutOnCooldown();
}
else
{
PublishError(e);
}
}

return (false, null);
Expand Down Expand Up @@ -234,6 +255,10 @@ async void UpdateLobby(float unused)
{
m_RateLimitQuickJoin.PutOnCooldown();
}
else
{
PublishError(e);
}
}

return (false, null);
Expand Down Expand Up @@ -261,12 +286,29 @@ public async Task RetrieveAndPublishLobbyListAsync()
{
m_RateLimitQuery.PutOnCooldown();
}
else
{
PublishError(e);
}
}
}

public async Task<Lobby> ReconnectToLobbyAsync(string lobbyId)
{
return await m_LobbyApiInterface.ReconnectToLobby(lobbyId);
try
{
return await m_LobbyApiInterface.ReconnectToLobby(lobbyId);
}
catch (LobbyServiceException e)
{
// If Lobby is not found and if we are not the host, it has already been deleted. No need to publish the error here.
if (e.Reason != LobbyExceptionReason.LobbyNotFound && !m_LocalUser.IsHost)
{
PublishError(e);
}
}

return null;
}

/// <summary>
Expand All @@ -280,9 +322,12 @@ public async Task LeaveLobbyAsync(string lobbyId)
await m_LobbyApiInterface.RemovePlayerFromLobby(uasId, lobbyId);
}
catch (LobbyServiceException e)
when (e is { Reason: LobbyExceptionReason.LobbyNotFound })
{
// If Lobby is not found, it has already been deleted. No need to throw here.
// If Lobby is not found and if we are not the host, it has already been deleted. No need to publish the error here.
if (e.Reason != LobbyExceptionReason.LobbyNotFound && !m_LocalUser.IsHost)
{
PublishError(e);
}
}

}
Expand All @@ -291,19 +336,33 @@ public async void RemovePlayerFromLobbyAsync(string uasId, string lobbyId)
{
if (m_LocalUser.IsHost)
{
await m_LobbyApiInterface.RemovePlayerFromLobby(uasId, lobbyId);
try
{
await m_LobbyApiInterface.RemovePlayerFromLobby(uasId, lobbyId);
}
catch (LobbyServiceException e)
{
PublishError(e);
}
}
else
{
Debug.LogError("Only the host can remove other players from the lobby.");
}
}

public async void DeleteLobbyAsync(string lobbyId)
public async Task DeleteLobbyAsync(string lobbyId)
{
if (m_LocalUser.IsHost)
{
await m_LobbyApiInterface.DeleteLobby(lobbyId);
try
{
await m_LobbyApiInterface.DeleteLobby(lobbyId);
}
catch (LobbyServiceException e)
{
PublishError(e);
}
}
else
{
Expand Down Expand Up @@ -336,6 +395,10 @@ public async Task UpdatePlayerDataAsync(Dictionary<string, PlayerDataObject> dat
{
m_RateLimitQuery.PutOnCooldown();
}
else if (e.Reason != LobbyExceptionReason.LobbyNotFound && !m_LocalUser.IsHost) // If Lobby is not found and if we are not the host, it has already been deleted. No need to publish the error here.
{
PublishError(e);
}
}
}

Expand All @@ -359,6 +422,10 @@ public async Task UpdatePlayerRelayInfoAsync(string allocationId, string connect
{
m_RateLimitQuery.PutOnCooldown();
}
else
{
PublishError(e);
}

//todo - retry logic? SDK is supposed to handle this eventually
}
Expand Down Expand Up @@ -406,6 +473,10 @@ public async Task UpdateLobbyDataAsync(Dictionary<string, DataObject> data)
{
m_RateLimitQuery.PutOnCooldown();
}
else
{
PublishError(e);
}
}
}

Expand All @@ -418,8 +489,25 @@ public void DoLobbyHeartbeat(float dt)
if (m_HeartbeatTime > k_HeartbeatPeriod)
{
m_HeartbeatTime -= k_HeartbeatPeriod;
m_LobbyApiInterface.SendHeartbeatPing(CurrentUnityLobby.Id);
try
{
m_LobbyApiInterface.SendHeartbeatPing(CurrentUnityLobby.Id);
}
catch (LobbyServiceException e)
{
// If Lobby is not found and if we are not the host, it has already been deleted. No need to publish the error here.
if (e.Reason != LobbyExceptionReason.LobbyNotFound && !m_LocalUser.IsHost)
{
PublishError(e);
}
}
}
}

void PublishError(LobbyServiceException e)
{
var reason = $"{e.Message} ({e.InnerException?.Message})"; // Lobby error type, then HTTP error type.
m_UnityServiceErrorMessagePub.Publish(new UnityServiceErrorMessage("Lobby Error", reason, UnityServiceErrorMessage.Service.Lobby, e));
}
}
}
12 changes: 7 additions & 5 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,17 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/)

Additional documentation and release notes are available at [Multiplayer Documentation](https://docs-multiplayer.unity3d.com).

## [unreleased] - yyy-mm-dd
## [unreleased] - aaaa-mm-dd

### Added
*
*
### Changed
*
* Hosts now delete their lobby when shutting down instead of only leaving it (#772) Since Boss Room doesn't support host migration, there is no need to keep the lobby alive after the host shuts down. This also changes how LobbyServiceExceptions are handled to prevent popup messages on clients trying to leave a lobby that is already deleted, following the best practices outlined in this doc : https://docs.unity.com/lobby/delete-a-lobby.html
*
### Cleanup
*
*
### Fixed
*
*

## [2.0.2] - 2022-11-01
### Fixed
Expand Down