浏览代码

Staging/dev should be pretty in-sync

/dev
当前提交
356c8836
共有 1004 个文件被更改,包括 4320 次插入2104 次删除
  1. 6
      Assets/Prefabs/UI/LobbyButtonUI.prefab
  2. 3
      Assets/Prefabs/UI/LobbyCodeCanvas.prefab
  3. 140
      Assets/Scripts/Auth/NameGenerator.cs
  4. 6
      Assets/Scripts/Entities/GameStateManager.cs
  5. 542
      Assets/Scripts/Entities/LocalLobby.cs
  6. 8
      Assets/Scripts/Entities/LocalLobbyObserver.cs
  7. 200
      Assets/Scripts/Infrastructure/Locator.cs
  8. 126
      Assets/Scripts/Infrastructure/LogHandler.cs
  9. 176
      Assets/Scripts/Infrastructure/Messenger.cs
  10. 276
      Assets/Scripts/Infrastructure/UpdateSlow.cs
  11. 218
      Assets/Scripts/Lobby/LobbyAPIInterface.cs
  12. 439
      Assets/Scripts/Lobby/LobbyAsyncRequests.cs
  13. 218
      Assets/Scripts/Lobby/LobbyContentHeartbeat.cs
  14. 46
      Assets/Scripts/Lobby/LobbyListHeartbeat.cs
  15. 126
      Assets/Scripts/Lobby/ReadyCheck.cs
  16. 178
      Assets/Scripts/Lobby/ToLocalLobby.cs
  17. 4
      Assets/Scripts/LobbyRelaySample.asmdef
  18. 260
      Assets/Scripts/Tests/PlayMode/LobbyReadyCheckTests.cs
  19. 326
      Assets/Scripts/Tests/PlayMode/LobbyRoundtripTests.cs
  20. 2
      Assets/Scripts/Tests/PlayMode/Tests.Play.asmdef
  21. 40
      Assets/Scripts/UI/CountdownUI.cs
  22. 66
      Assets/Scripts/UI/DisplayCodeUI.cs
  23. 30
      Assets/Scripts/UI/EndGameButtonUI.cs
  24. 30
      Assets/Scripts/UI/ExitButtonUI.cs
  25. 120
      Assets/Scripts/UI/InLobbyUserList.cs
  26. 138
      Assets/Scripts/UI/InLobbyUserUI.cs
  27. 42
      Assets/Scripts/UI/JoinCreateLobbyUI.cs
  28. 38
      Assets/Scripts/UI/LobbyNameUI.cs
  29. 46
      Assets/Scripts/UI/ReadyCheckUI.cs
  30. 38
      Assets/Scripts/UI/RelayAddressUI.cs
  31. 4
      Assets/Scripts/UI/ShowWhenLobbyStateUI.cs
  32. 106
      Assets/Scripts/UI/SpinnerUI.cs
  33. 30
      Assets/Scripts/UI/StartLobbyButtonUI.cs
  34. 22
      Packages/manifest.json
  35. 62
      Packages/packages-lock.json
  36. 24
      ProjectSettings/PackageManagerSettings.asset
  37. 6
      ProjectSettings/ProjectSettings.asset
  38. 2
      ProjectSettings/UnityConnectSettings.asset
  39. 42
      README.md
  40. 242
      Packages/com.unity.services.authentication/.README - External.md
  41. 53
      Packages/com.unity.services.authentication/CHANGELOG.md
  42. 7
      Packages/com.unity.services.authentication/CHANGELOG.md.meta
  43. 5
      Packages/com.unity.services.authentication/Documentation~/com.unity.services.authentication.md
  44. 8
      Packages/com.unity.services.authentication/Editor.meta
  45. 5
      Packages/com.unity.services.authentication/Editor/AssemblyInfo.cs
  46. 3
      Packages/com.unity.services.authentication/Editor/AssemblyInfo.cs.meta
  47. 298
      Packages/com.unity.services.authentication/Editor/AuthenticationAdminClient.cs
  48. 3
      Packages/com.unity.services.authentication/Editor/AuthenticationAdminClient.cs.meta
  49. 148
      Packages/com.unity.services.authentication/Editor/AuthenticationAdminNetworkClient.cs
  50. 11
      Packages/com.unity.services.authentication/Editor/AuthenticationAdminNetworkClient.cs.meta
  51. 17
      Packages/com.unity.services.authentication/Editor/AuthenticationIdentifier.cs
  52. 11
      Packages/com.unity.services.authentication/Editor/AuthenticationIdentifier.cs.meta
  53. 47
      Packages/com.unity.services.authentication/Editor/AuthenticationService.cs
  54. 11
      Packages/com.unity.services.authentication/Editor/AuthenticationService.cs.meta
  55. 292
      Packages/com.unity.services.authentication/Editor/AuthenticationSettingsElement.cs
  56. 3
      Packages/com.unity.services.authentication/Editor/AuthenticationSettingsElement.cs.meta
  57. 47
      Packages/com.unity.services.authentication/Editor/AuthenticationSettingsHelper.cs
  58. 3
      Packages/com.unity.services.authentication/Editor/AuthenticationSettingsHelper.cs.meta
  59. 58
      Packages/com.unity.services.authentication/Editor/AuthenticationSettingsProvider.cs
  60. 11
      Packages/com.unity.services.authentication/Editor/AuthenticationSettingsProvider.cs.meta
  61. 20
      Packages/com.unity.services.authentication/Editor/AuthenticationTopMenu.cs
  62. 11
      Packages/com.unity.services.authentication/Editor/AuthenticationTopMenu.cs.meta
  63. 79
      Packages/com.unity.services.authentication/Editor/IAuthenticationAdminClient.cs
  64. 3
      Packages/com.unity.services.authentication/Editor/IAuthenticationAdminClient.cs.meta
  65. 399
      Packages/com.unity.services.authentication/Editor/IdProviderElement.cs
  66. 3
      Packages/com.unity.services.authentication/Editor/IdProviderElement.cs.meta
  67. 8
      Packages/com.unity.services.authentication/Editor/Models.meta
  68. 49
      Packages/com.unity.services.authentication/Editor/Models/CreateIdProviderRequest.cs
  69. 11
      Packages/com.unity.services.authentication/Editor/Models/CreateIdProviderRequest.cs.meta
  70. 20
      Packages/com.unity.services.authentication/Editor/Models/DeleteIdProviderRequest.cs
  71. 11
      Packages/com.unity.services.authentication/Editor/Models/DeleteIdProviderRequest.cs.meta
  72. 19
      Packages/com.unity.services.authentication/Editor/Models/GetIdDomainResponse.cs
  73. 11
      Packages/com.unity.services.authentication/Editor/Models/GetIdDomainResponse.cs.meta
  74. 56
      Packages/com.unity.services.authentication/Editor/Models/IdProviderResponse.cs
  75. 11
      Packages/com.unity.services.authentication/Editor/Models/IdProviderResponse.cs.meta
  76. 19
      Packages/com.unity.services.authentication/Editor/Models/ListIdProviderResponse.cs
  77. 11
      Packages/com.unity.services.authentication/Editor/Models/ListIdProviderResponse.cs.meta
  78. 29
      Packages/com.unity.services.authentication/Editor/Models/TokenExchangeErrorResponse.cs
  79. 11
      Packages/com.unity.services.authentication/Editor/Models/TokenExchangeErrorResponse.cs.meta
  80. 16
      Packages/com.unity.services.authentication/Editor/Models/TokenExchangeRequest.cs
  81. 11
      Packages/com.unity.services.authentication/Editor/Models/TokenExchangeRequest.cs.meta
  82. 16
      Packages/com.unity.services.authentication/Editor/Models/TokenExchangeResponse.cs
  83. 11
      Packages/com.unity.services.authentication/Editor/Models/TokenExchangeResponse.cs.meta
  84. 44
      Packages/com.unity.services.authentication/Editor/Models/UpdateIdProviderRequest.cs
  85. 11
      Packages/com.unity.services.authentication/Editor/Models/UpdateIdProviderRequest.cs.meta
  86. 3
      Packages/com.unity.services.authentication/Editor/USS.meta
  87. 41
      Packages/com.unity.services.authentication/Editor/USS/AuthenticationStyleSheet.uss
  88. 3
      Packages/com.unity.services.authentication/Editor/USS/AuthenticationStyleSheet.uss.meta
  89. 3
      Packages/com.unity.services.authentication/Editor/UXML.meta
  90. 15
      Packages/com.unity.services.authentication/Editor/UXML/AuthenticationProjectSettings.uxml

6
Assets/Prefabs/UI/LobbyButtonUI.prefab


m_PersistentCalls:
m_Calls:
- m_Target: {fileID: 7018369548608736188}
m_TargetAssemblyTypeName: LobbyRelaySample.UI.LobbyButtonUI, LobbyRooms
m_MethodName: OnLobbyClicked
m_Mode: 1
m_TargetAssemblyTypeName: LobbyRelaySample.UI.LobbyButtonUI, LobbyRelaySample
m_MethodName: OnLobbyUpdated
m_Mode: 0
m_Arguments:
m_ObjectArgument: {fileID: 0}
m_ObjectArgumentAssemblyTypeName: UnityEngine.Object, UnityEngine

3
Assets/Prefabs/UI/LobbyCodeCanvas.prefab


m_PersistentCalls:
m_Calls: []
showing: 0
roomCodeText: {fileID: 5578852939709204548}
m_outputText: {fileID: 5578852939709204548}
m_codeType: 0
--- !u!114 &699060394989383769
MonoBehaviour:
m_ObjectHideFlags: 0

140
Assets/Scripts/Auth/NameGenerator.cs


using System;
using System.Text;
namespace LobbyRelaySample
{
/// <summary>
/// Just for fun, give a cute default player name if no name is provided.
/// </summary>
public static class NameGenerator
{
public static string GetName(string userId)
{
int seed = userId.GetHashCode();
seed *= Math.Sign(seed);
StringBuilder nameOutput = new StringBuilder();
#region Word part
int word = seed % 22;
if (word == 0) // Note that some more data-driven approach would be better.
nameOutput.Append("Ant");
else if (word == 1)
nameOutput.Append("Bear");
else if (word == 2)
nameOutput.Append("Cow");
else if (word == 3)
nameOutput.Append("Dog");
else if (word == 4)
nameOutput.Append("Eel");
else if (word == 5)
nameOutput.Append("Frog");
else if (word == 6)
nameOutput.Append("Gopher");
else if (word == 7)
nameOutput.Append("Heron");
else if (word == 8)
nameOutput.Append("Ibex");
else if (word == 9)
nameOutput.Append("Jerboa");
else if (word == 10)
nameOutput.Append("Koala");
else if (word == 11)
nameOutput.Append("Llama");
else if (word == 12)
nameOutput.Append("Moth");
else if (word == 13)
nameOutput.Append("Newt");
else if (word == 14)
nameOutput.Append("Owl");
else if (word == 15)
nameOutput.Append("Puffin");
else if (word == 16)
nameOutput.Append("Raven");
else if (word == 17)
nameOutput.Append("Snake");
else if (word == 18)
nameOutput.Append("Trout");
else if (word == 19)
nameOutput.Append("Vulture");
else if (word == 20)
nameOutput.Append("Wolf");
else
nameOutput.Append("Zebra");
#endregion
int number = seed % 1000;
nameOutput.Append(number.ToString("000"));
return nameOutput.ToString();
}
}
}
using System;
using System.Text;
namespace LobbyRelaySample
{
/// <summary>
/// Just for fun, give a cute default player name if no name is provided.
/// </summary>
public static class NameGenerator
{
public static string GetName(string userId)
{
int seed = userId.GetHashCode();
seed *= Math.Sign(seed);
StringBuilder nameOutput = new StringBuilder();
#region Word part
int word = seed % 22;
if (word == 0) // Note that some more data-driven approach would be better.
nameOutput.Append("Ant");
else if (word == 1)
nameOutput.Append("Bear");
else if (word == 2)
nameOutput.Append("Cow");
else if (word == 3)
nameOutput.Append("Dog");
else if (word == 4)
nameOutput.Append("Eel");
else if (word == 5)
nameOutput.Append("Frog");
else if (word == 6)
nameOutput.Append("Gopher");
else if (word == 7)
nameOutput.Append("Heron");
else if (word == 8)
nameOutput.Append("Ibex");
else if (word == 9)
nameOutput.Append("Jerboa");
else if (word == 10)
nameOutput.Append("Koala");
else if (word == 11)
nameOutput.Append("Llama");
else if (word == 12)
nameOutput.Append("Moth");
else if (word == 13)
nameOutput.Append("Newt");
else if (word == 14)
nameOutput.Append("Owl");
else if (word == 15)
nameOutput.Append("Puffin");
else if (word == 16)
nameOutput.Append("Raven");
else if (word == 17)
nameOutput.Append("Snake");
else if (word == 18)
nameOutput.Append("Trout");
else if (word == 19)
nameOutput.Append("Vulture");
else if (word == 20)
nameOutput.Append("Wolf");
else
nameOutput.Append("Zebra");
#endregion
int number = seed % 1000;
nameOutput.Append(number.ToString("000"));
return nameOutput.ToString();
}
}
}

6
Assets/Scripts/Entities/GameStateManager.cs


var createLobbyData = (LocalLobby)msg;
LobbyAsyncRequests.Instance.CreateLobbyAsync(createLobbyData.LobbyName, createLobbyData.MaxPlayerCount, createLobbyData.Private, (r) =>
{
Lobby.ToLocalLobby.Convert(r, m_localLobby, m_localUser);
lobby.ToLocalLobby.Convert(r, m_localLobby, m_localUser);
OnCreatedLobby();
}, OnFailedJoin);
}

LobbyAsyncRequests.Instance.JoinLobbyAsync(lobbyInfo.LobbyID, lobbyInfo.LobbyCode, (r) =>
{
Lobby.ToLocalLobby.Convert(r, m_localLobby, m_localUser);
lobby.ToLocalLobby.Convert(r, m_localLobby, m_localUser);
OnJoinedLobby();
}, OnFailedJoin);
}

qr =>
{
if (qr != null)
OnRefreshed(Lobby.ToLocalLobby.Convert(qr));
OnRefreshed(lobby.ToLocalLobby.Convert(qr));
}, er =>
{
long errorLong = 0;

542
Assets/Scripts/Entities/LocalLobby.cs


using System;
using System.Collections.Generic;
using System.Linq;
using UnityEngine;
namespace LobbyRelaySample
{
[Flags]
public enum LobbyState
{
Lobby = 1,
CountDown = 2,
InGame = 4
}
public struct LobbyInfo
{
public string LobbyID { get; set; }
public string LobbyCode { get; set; }
public string RelayCode { get; set; }
public string LobbyName { get; set; }
public bool Private { get; set; }
public int MaxPlayerCount { get; set; }
public LobbyState State { get; set; }
public long? AllPlayersReadyTime { get; set; }
public LobbyInfo(LobbyInfo existing)
{
LobbyID = existing.LobbyID;
LobbyCode = existing.LobbyCode;
RelayCode = existing.RelayCode;
LobbyName = existing.LobbyName;
Private = existing.Private;
MaxPlayerCount = existing.MaxPlayerCount;
State = existing.State;
AllPlayersReadyTime = existing.AllPlayersReadyTime;
}
public LobbyInfo(string lobbyCode)
{
LobbyID = null;
LobbyCode = lobbyCode;
RelayCode = null;
LobbyName = null;
Private = false;
MaxPlayerCount = -1;
State = LobbyState.Lobby;
AllPlayersReadyTime = null;
}
}
/// <summary>
/// A local wrapper around a lobby's remote data, with additional functionality for providing that data to UI elements and tracking local player objects.
/// </summary>
[System.Serializable]
public class LocalLobby : Observed<LocalLobby>
{
Dictionary<string, LobbyUser> m_LobbyUsers = new Dictionary<string, LobbyUser>();
public Dictionary<string, LobbyUser> LobbyUsers => m_LobbyUsers;
#region LocalLobbyData
private LobbyInfo m_data;
public LobbyInfo Data
{
get { return new LobbyInfo(m_data); }
}
float m_CountDownTime;
public float CountDownTime
{
get { return m_CountDownTime; }
set
{
m_CountDownTime = value;
OnChanged(this);
}
}
DateTime m_TargetEndTime;
public DateTime TargetEndTime
{
get => m_TargetEndTime;
set
{
m_TargetEndTime = value;
OnChanged(this);
}
}
ServerAddress m_relayServer;
public ServerAddress RelayServer
{
get => m_relayServer;
set
{
m_relayServer = value;
OnChanged(this);
}
}
#endregion
public void AddPlayer(LobbyUser user)
{
if (m_LobbyUsers.ContainsKey(user.ID))
{
Debug.LogError($"Cant add player {user.DisplayName}({user.ID}) to lobby: {LobbyID} twice");
return;
}
DoAddPlayer(user);
OnChanged(this);
}
private void DoAddPlayer(LobbyUser user)
{
m_LobbyUsers.Add(user.ID, user);
user.onChanged += OnChangedUser;
}
public void RemovePlayer(LobbyUser user)
{
DoRemoveUser(user);
OnChanged(this);
}
private void DoRemoveUser(LobbyUser user)
{
if (!m_LobbyUsers.ContainsKey(user.ID))
{
Debug.LogWarning($"Player {user.DisplayName}({user.ID}) does not exist in lobby: {LobbyID}");
return;
}
m_LobbyUsers.Remove(user.ID);
user.onChanged -= OnChangedUser;
}
private void OnChangedUser(LobbyUser user)
{
OnChanged(this);
}
public string LobbyID
{
get => m_data.LobbyID;
set
{
m_data.LobbyID = value;
OnChanged(this);
}
}
public string LobbyCode
{
get => m_data.LobbyCode;
set
{
m_data.LobbyCode = value;
OnChanged(this);
}
}
public string RelayCode
{
get => m_data.RelayCode;
set
{
m_data.RelayCode = value;
OnChanged(this);
}
}
public string LobbyName
{
get => m_data.LobbyName;
set
{
m_data.LobbyName = value;
OnChanged(this);
}
}
public LobbyState State
{
get => m_data.State;
set
{
m_data.State = value;
OnChanged(this);
}
}
public bool Private
{
get => m_data.Private;
set
{
m_data.Private = value;
OnChanged(this);
}
}
public int PlayerCount => m_LobbyUsers.Count;
public int MaxPlayerCount
{
get => m_data.MaxPlayerCount;
set
{
m_data.MaxPlayerCount = value;
OnChanged(this);
}
}
public long? AllPlayersReadyTime => m_data.AllPlayersReadyTime;
/// <summary>
/// Checks if we have n players that have the Status.
/// -1 Count means you need all Lobbyusers
/// </summary>
/// <returns>True if enough players are of the input status.</returns>
public bool PlayersOfState(UserStatus status, int playersCount = -1)
{
var statePlayers = m_LobbyUsers.Values.Count(user => user.UserStatus == status);
if (playersCount < 0)
return statePlayers == m_LobbyUsers.Count;
return statePlayers == playersCount;
}
public void CopyObserved(LobbyInfo info, Dictionary<string, LobbyUser> oldUsers)
{
m_data = info;
if (oldUsers == null)
m_LobbyUsers = new Dictionary<string, LobbyUser>();
else
{
List<LobbyUser> toRemove = new List<LobbyUser>();
foreach (var user in m_LobbyUsers)
{
if (oldUsers.ContainsKey(user.Key))
user.Value.CopyObserved(oldUsers[user.Key]);
else
toRemove.Add(user.Value);
}
foreach (var remove in toRemove)
{
DoRemoveUser(remove);
}
foreach (var oldUser in oldUsers)
{
if (!m_LobbyUsers.ContainsKey(oldUser.Key))
DoAddPlayer(oldUser.Value);
}
}
OnChanged(this);
}
public override void CopyObserved(LocalLobby oldObserved)
{
CopyObserved(oldObserved.Data, oldObserved.m_LobbyUsers);
}
}
}
using System;
using System.Collections.Generic;
using System.Linq;
using UnityEngine;
namespace LobbyRelaySample
{
[Flags]
public enum LobbyState
{
Lobby = 1,
CountDown = 2,
InGame = 4
}
public struct LobbyInfo
{
public string LobbyID { get; set; }
public string LobbyCode { get; set; }
public string RelayCode { get; set; }
public string LobbyName { get; set; }
public bool Private { get; set; }
public int MaxPlayerCount { get; set; }
public LobbyState State { get; set; }
public long? AllPlayersReadyTime { get; set; }
public LobbyInfo(LobbyInfo existing)
{
LobbyID = existing.LobbyID;
LobbyCode = existing.LobbyCode;
RelayCode = existing.RelayCode;
LobbyName = existing.LobbyName;
Private = existing.Private;
MaxPlayerCount = existing.MaxPlayerCount;
State = existing.State;
AllPlayersReadyTime = existing.AllPlayersReadyTime;
}
public LobbyInfo(string lobbyCode)
{
LobbyID = null;
LobbyCode = lobbyCode;
RelayCode = null;
LobbyName = null;
Private = false;
MaxPlayerCount = -1;
State = LobbyState.Lobby;
AllPlayersReadyTime = null;
}
}
/// <summary>
/// A local wrapper around a lobby's remote data, with additional functionality for providing that data to UI elements and tracking local player objects.
/// </summary>
[System.Serializable]
public class LocalLobby : Observed<LocalLobby>
{
Dictionary<string, LobbyUser> m_LobbyUsers = new Dictionary<string, LobbyUser>();
public Dictionary<string, LobbyUser> LobbyUsers => m_LobbyUsers;
#region LocalLobbyData
private LobbyInfo m_data;
public LobbyInfo Data
{
get { return new LobbyInfo(m_data); }
}
float m_CountDownTime;
public float CountDownTime
{
get { return m_CountDownTime; }
set
{
m_CountDownTime = value;
OnChanged(this);
}
}
DateTime m_TargetEndTime;
public DateTime TargetEndTime
{
get => m_TargetEndTime;
set
{
m_TargetEndTime = value;
OnChanged(this);
}
}
ServerAddress m_relayServer;
public ServerAddress RelayServer
{
get => m_relayServer;
set
{
m_relayServer = value;
OnChanged(this);
}
}
#endregion
public void AddPlayer(LobbyUser user)
{
if (m_LobbyUsers.ContainsKey(user.ID))
{
Debug.LogError($"Cant add player {user.DisplayName}({user.ID}) to lobby: {LobbyID} twice");
return;
}
DoAddPlayer(user);
OnChanged(this);
}
private void DoAddPlayer(LobbyUser user)
{
m_LobbyUsers.Add(user.ID, user);
user.onChanged += OnChangedUser;
}
public void RemovePlayer(LobbyUser user)
{
DoRemoveUser(user);
OnChanged(this);
}
private void DoRemoveUser(LobbyUser user)
{
if (!m_LobbyUsers.ContainsKey(user.ID))
{
Debug.LogWarning($"Player {user.DisplayName}({user.ID}) does not exist in lobby: {LobbyID}");
return;
}
m_LobbyUsers.Remove(user.ID);
user.onChanged -= OnChangedUser;
}
private void OnChangedUser(LobbyUser user)
{
OnChanged(this);
}
public string LobbyID
{
get => m_data.LobbyID;
set
{
m_data.LobbyID = value;
OnChanged(this);
}
}
public string LobbyCode
{
get => m_data.LobbyCode;
set
{
m_data.LobbyCode = value;
OnChanged(this);
}
}
public string RelayCode
{
get => m_data.RelayCode;
set
{
m_data.RelayCode = value;
OnChanged(this);
}
}
public string LobbyName
{
get => m_data.LobbyName;
set
{
m_data.LobbyName = value;
OnChanged(this);
}
}
public LobbyState State
{
get => m_data.State;
set
{
m_data.State = value;
OnChanged(this);
}
}
public bool Private
{
get => m_data.Private;
set
{
m_data.Private = value;
OnChanged(this);
}
}
public int PlayerCount => m_LobbyUsers.Count;
public int MaxPlayerCount
{
get => m_data.MaxPlayerCount;
set
{
m_data.MaxPlayerCount = value;
OnChanged(this);
}
}
public long? AllPlayersReadyTime => m_data.AllPlayersReadyTime;
/// <summary>
/// Checks if we have n players that have the Status.
/// -1 Count means you need all Lobbyusers
/// </summary>
/// <returns>True if enough players are of the input status.</returns>
public bool PlayersOfState(UserStatus status, int playersCount = -1)
{
var statePlayers = m_LobbyUsers.Values.Count(user => user.UserStatus == status);
if (playersCount < 0)
return statePlayers == m_LobbyUsers.Count;
return statePlayers == playersCount;
}
public void CopyObserved(LobbyInfo info, Dictionary<string, LobbyUser> oldUsers)
{
m_data = info;
if (oldUsers == null)
m_LobbyUsers = new Dictionary<string, LobbyUser>();
else
{
List<LobbyUser> toRemove = new List<LobbyUser>();
foreach (var user in m_LobbyUsers)
{
if (oldUsers.ContainsKey(user.Key))
user.Value.CopyObserved(oldUsers[user.Key]);
else
toRemove.Add(user.Value);
}
foreach (var remove in toRemove)
{
DoRemoveUser(remove);
}
foreach (var oldUser in oldUsers)
{
if (!m_LobbyUsers.ContainsKey(oldUser.Key))
DoAddPlayer(oldUser.Value);
}
}
OnChanged(this);
}
public override void CopyObserved(LocalLobby oldObserved)
{
CopyObserved(oldObserved.Data, oldObserved.m_LobbyUsers);
}
}
}

8
Assets/Scripts/Entities/LocalLobbyObserver.cs


namespace LobbyRelaySample
{
public class LocalLobbyObserver : ObserverBehaviour<LocalLobby> { }
}
namespace LobbyRelaySample
{
public class LocalLobbyObserver : ObserverBehaviour<LocalLobby> { }
}

200
Assets/Scripts/Infrastructure/Locator.cs


using LobbyRelaySample.Auth;
using System;
using System.Collections.Generic;
namespace LobbyRelaySample
{
/// <summary>
/// Allows Located services to transfer data to their replacements if needed.
/// </summary>
/// <typeparam name="T">The base interface type you want to Provide.</typeparam>
public interface IProvidable<T>
{
void OnReProvided(T previousProvider);
}
/// <summary>
/// Base Locator behavior, without static access.
/// </summary>
public class LocatorBase
{
private Dictionary<Type, object> m_provided = new Dictionary<Type, object>();
/// <summary>
/// On construction, we can prepare default implementations of any services we expect to be required. This way, if for some reason the actual implementations
/// are never Provided (e.g. for tests), nothing will break.
/// </summary>
public LocatorBase()
{
Provide(new Messenger());
Provide(new UpdateSlowNoop());
Provide(new IdentityNoop());
FinishConstruction();
}
protected virtual void FinishConstruction() { }
/// <summary>
/// Call this to indicate that something is available for global access.
/// </summary>
private void ProvideAny<T>(T instance) where T : IProvidable<T>
{
Type type = typeof(T);
if (m_provided.ContainsKey(type))
{
var previousProvision = (T)m_provided[type];
instance.OnReProvided(previousProvision);
m_provided.Remove(type);
}
m_provided.Add(type, instance);
}
/// <summary>
/// If a T has previously been Provided, this will retrieve it. Else, null is returned.
/// </summary>
private T Locate<T>() where T : class
{
Type type = typeof(T);
if (!m_provided.ContainsKey(type))
return null;
return m_provided[type] as T;
}
// To limit global access to only components that should have it, and to reduce programmer error, we'll declare explicit flavors of Provide and getters for them.
public IMessenger Messenger => Locate<IMessenger>();
public void Provide(IMessenger messenger) { ProvideAny(messenger); }
public IUpdateSlow UpdateSlow => Locate<IUpdateSlow>();
public void Provide(IUpdateSlow updateSlow) { ProvideAny(updateSlow); }
public IIdentity Identity => Locate<IIdentity>();
public void Provide(IIdentity identity) { ProvideAny(identity); }
// As you add more Provided types, be sure their default implementations are included in the constructor.
}
/// <summary>
/// Anything which provides itself to a Locator can then be globally accessed. This should be a single access point for things that *want* to be singleton (that is,
/// when they want to be available for use by arbitrary, unknown clients) but might not always be available or might need alternate flavors for tests, logging, etc.
/// </summary>
public class Locator : LocatorBase
{
private static Locator s_instance;
public static Locator Get
{
get
{
if (s_instance == null)
s_instance = new Locator();
return s_instance;
}
}
protected override void FinishConstruction()
{
s_instance = this;
}
}
using LobbyRelaySample.Auth;
using System;
using System.Collections.Generic;
namespace LobbyRelaySample
{
/// <summary>
/// Allows Located services to transfer data to their replacements if needed.
/// </summary>
/// <typeparam name="T">The base interface type you want to Provide.</typeparam>
public interface IProvidable<T>
{
void OnReProvided(T previousProvider);
}
/// <summary>
/// Base Locator behavior, without static access.
/// </summary>
public class LocatorBase
{
private Dictionary<Type, object> m_provided = new Dictionary<Type, object>();
/// <summary>
/// On construction, we can prepare default implementations of any services we expect to be required. This way, if for some reason the actual implementations
/// are never Provided (e.g. for tests), nothing will break.
/// </summary>
public LocatorBase()
{
Provide(new Messenger());
Provide(new UpdateSlowNoop());
Provide(new IdentityNoop());
FinishConstruction();
}
protected virtual void FinishConstruction() { }
/// <summary>
/// Call this to indicate that something is available for global access.
/// </summary>
private void ProvideAny<T>(T instance) where T : IProvidable<T>
{
Type type = typeof(T);
if (m_provided.ContainsKey(type))
{
var previousProvision = (T)m_provided[type];
instance.OnReProvided(previousProvision);
m_provided.Remove(type);
}
m_provided.Add(type, instance);
}
/// <summary>
/// If a T has previously been Provided, this will retrieve it. Else, null is returned.
/// </summary>
private T Locate<T>() where T : class
{
Type type = typeof(T);
if (!m_provided.ContainsKey(type))
return null;
return m_provided[type] as T;
}
// To limit global access to only components that should have it, and to reduce programmer error, we'll declare explicit flavors of Provide and getters for them.
public IMessenger Messenger => Locate<IMessenger>();
public void Provide(IMessenger messenger) { ProvideAny(messenger); }
public IUpdateSlow UpdateSlow => Locate<IUpdateSlow>();
public void Provide(IUpdateSlow updateSlow) { ProvideAny(updateSlow); }
public IIdentity Identity => Locate<IIdentity>();
public void Provide(IIdentity identity) { ProvideAny(identity); }
// As you add more Provided types, be sure their default implementations are included in the constructor.
}
/// <summary>
/// Anything which provides itself to a Locator can then be globally accessed. This should be a single access point for things that *want* to be singleton (that is,
/// when they want to be available for use by arbitrary, unknown clients) but might not always be available or might need alternate flavors for tests, logging, etc.
/// </summary>
public class Locator : LocatorBase
{
private static Locator s_instance;
public static Locator Get
{
get
{
if (s_instance == null)
s_instance = new Locator();
return s_instance;
}
}
protected override void FinishConstruction()
{
s_instance = this;
}
}
}

126
Assets/Scripts/Infrastructure/LogHandler.cs


using System;
using UnityEngine;
using Object = UnityEngine.Object;
namespace LobbyRelaySample
{
public enum LogMode
{
Critical, // Errors only.
Warnings, // Errors and Warnings
Verbose // Everything
}
/// <summary>
/// Overrides the Default Unity Logging with our own
/// </summary>
public class LogHandler : ILogHandler
{
public LogMode mode = LogMode.Critical;
static LogHandler s_instance;
ILogHandler m_DefaultLogHandler = Debug.unityLogger.logHandler; //Store the unity default logger to print to console.
public static LogHandler Get()
{
if (s_instance != null) return s_instance;
s_instance = new LogHandler();
Debug.unityLogger.logHandler = s_instance;
return s_instance;
}
public void LogFormat(LogType logType, Object context, string format, params object[] args)
{
if (logType == LogType.Exception) // Exceptions are captured by LogException?
return;
if (logType == LogType.Error || logType == LogType.Assert)
{
m_DefaultLogHandler.LogFormat(logType, context, format, args);
return;
}
if (mode == LogMode.Critical)
return;
if (logType == LogType.Warning)
{
m_DefaultLogHandler.LogFormat(logType, context, format, args);
return;
}
if (mode != LogMode.Verbose)
return;
m_DefaultLogHandler.LogFormat(logType, context, format, args);
}
public void LogException(Exception exception, Object context)
{
m_DefaultLogHandler.LogException(exception, context);
}
}
}
using System;
using UnityEngine;
using Object = UnityEngine.Object;
namespace LobbyRelaySample
{
public enum LogMode
{
Critical, // Errors only.
Warnings, // Errors and Warnings
Verbose // Everything
}
/// <summary>
/// Overrides the default Unity logging with our own, so that verbose logs (both from the services and from any of our Debug.Log* calls) don't clutter the Console.
/// </summary>
public class LogHandler : ILogHandler
{
public LogMode mode = LogMode.Critical;
static LogHandler s_instance;
ILogHandler m_DefaultLogHandler = Debug.unityLogger.logHandler; //Store the unity default logger to print to console.
public static LogHandler Get()
{
if (s_instance != null) return s_instance;
s_instance = new LogHandler();
Debug.unityLogger.logHandler = s_instance;
return s_instance;
}
public void LogFormat(LogType logType, Object context, string format, params object[] args)
{
if (logType == LogType.Exception) // Exceptions are captured by LogException?
return;
if (logType == LogType.Error || logType == LogType.Assert)
{
m_DefaultLogHandler.LogFormat(logType, context, format, args);
return;
}
if (mode == LogMode.Critical)
return;
if (logType == LogType.Warning)
{
m_DefaultLogHandler.LogFormat(logType, context, format, args);
return;
}
if (mode != LogMode.Verbose)
return;
m_DefaultLogHandler.LogFormat(logType, context, format, args);
}
public void LogException(Exception exception, Object context)
{
m_DefaultLogHandler.LogException(exception, context);
}
}
}

176
Assets/Scripts/Infrastructure/Messenger.cs


using System.Collections.Generic;
using UnityEngine;
using Stopwatch = System.Diagnostics.Stopwatch;
namespace LobbyRelaySample
{
/// <summary>
/// Ensure that message contents are obvious but not dependent on spelling strings correctly.
/// </summary>
public enum MessageType
{
// These are assigned arbitrary explicit values so that if a MessageType is serialized and more enum values are later inserted/removed, the serialized values need not be reassigned.
// (If you want to remove a message, make sure it isn't serialized somewhere first.)
None = 0,
RenameRequest = 1,
JoinLobbyRequest = 2,
CreateLobbyRequest = 3,
QueryLobbies = 4,
PlayerJoinedLobby = 5,
PlayerLeftLobby = 6,
ChangeGameState = 7,
ChangeLobbyUserState = 8,
HostInitReadyCheck = 9,
LocalUserReadyCheckResponse = 10,
UserSetEmote = 11,
ToLobby = 12,
Client_EndReadyCountdownAt = 13,
}
/// <summary>
/// Something that wants to subscribe to messages from arbitrary, unknown senders.
/// </summary>
public interface IReceiveMessages
{
void OnReceiveMessage(MessageType type, object msg);
}
/// <summary>
/// Something to which IReceiveMessages can send/subscribe for arbitrary messages.
/// </summary>
public interface IMessenger : IReceiveMessages, IProvidable<IMessenger>
{
void Subscribe(IReceiveMessages receiver);
void Unsubscribe(IReceiveMessages receiver);
}
/// <summary>
/// Core mechanism for routing messages to arbitrary listeners.
/// </summary>
public class Messenger : IMessenger
{
private List<IReceiveMessages> m_receivers = new List<IReceiveMessages>();
private const float k_durationToleranceMs = 10;
/// <summary>
/// Assume that you won't receive messages in a specific order.
/// </summary>
public virtual void Subscribe(IReceiveMessages receiver)
{
if (!m_receivers.Contains(receiver))
m_receivers.Add(receiver);
}
public virtual void Unsubscribe(IReceiveMessages receiver)
{
m_receivers.Remove(receiver);
}
public virtual void OnReceiveMessage(MessageType type, object msg)
{
Stopwatch stopwatch = new Stopwatch();
for (int r = 0; r < m_receivers.Count; r++)
{
stopwatch.Restart();
m_receivers[r].OnReceiveMessage(type, msg);
stopwatch.Stop();
if (stopwatch.ElapsedMilliseconds > k_durationToleranceMs)
Debug.LogWarning($"Message recipient \"{m_receivers[r]}\" took too long to process message \"{msg}\" of type {type}");
}
}
public void OnReProvided(IMessenger previousProvider)
{
if (previousProvider is Messenger)
m_receivers.AddRange((previousProvider as Messenger).m_receivers);
}
}
}
using System.Collections.Generic;
using UnityEngine;
using Stopwatch = System.Diagnostics.Stopwatch;
namespace LobbyRelaySample
{
/// <summary>
/// Ensure that message contents are obvious but not dependent on spelling strings correctly.
/// </summary>
public enum MessageType
{
// These are assigned arbitrary explicit values so that if a MessageType is serialized and more enum values are later inserted/removed, the serialized values need not be reassigned.
// (If you want to remove a message, make sure it isn't serialized somewhere first.)
None = 0,
RenameRequest = 1,
JoinLobbyRequest = 2,
CreateLobbyRequest = 3,
QueryLobbies = 4,
PlayerJoinedLobby = 5,
PlayerLeftLobby = 6,
ChangeGameState = 7,
ChangeLobbyUserState = 8,
HostInitReadyCheck = 9,
LocalUserReadyCheckResponse = 10,
UserSetEmote = 11,
ToLobby = 12,
Client_EndReadyCountdownAt = 13,
}
/// <summary>
/// Something that wants to subscribe to messages from arbitrary, unknown senders.
/// </summary>
public interface IReceiveMessages
{
void OnReceiveMessage(MessageType type, object msg);
}
/// <summary>
/// Something to which IReceiveMessages can send/subscribe for arbitrary messages.
/// </summary>
public interface IMessenger : IReceiveMessages, IProvidable<IMessenger>
{
void Subscribe(IReceiveMessages receiver);
void Unsubscribe(IReceiveMessages receiver);
}
/// <summary>
/// Core mechanism for routing messages to arbitrary listeners.
/// </summary>
public class Messenger : IMessenger
{
private List<IReceiveMessages> m_receivers = new List<IReceiveMessages>();
private const float k_durationToleranceMs = 10;
/// <summary>
/// Assume that you won't receive messages in a specific order.
/// </summary>
public virtual void Subscribe(IReceiveMessages receiver)
{
if (!m_receivers.Contains(receiver))
m_receivers.Add(receiver);
}
public virtual void Unsubscribe(IReceiveMessages receiver)
{
m_receivers.Remove(receiver);
}
public virtual void OnReceiveMessage(MessageType type, object msg)
{
Stopwatch stopwatch = new Stopwatch();
for (int r = 0; r < m_receivers.Count; r++)
{
stopwatch.Restart();
m_receivers[r].OnReceiveMessage(type, msg);
stopwatch.Stop();
if (stopwatch.ElapsedMilliseconds > k_durationToleranceMs)
Debug.LogWarning($"Message recipient \"{m_receivers[r]}\" took too long to process message \"{msg}\" of type {type}");
}
}
public void OnReProvided(IMessenger previousProvider)
{
if (previousProvider is Messenger)
m_receivers.AddRange((previousProvider as Messenger).m_receivers);
}
}
}

276
Assets/Scripts/Infrastructure/UpdateSlow.cs


using System.Collections.Generic;
using UnityEngine;
using Stopwatch = System.Diagnostics.Stopwatch;
namespace LobbyRelaySample
{
public delegate void UpdateMethod(float dt);
public interface IUpdateSlow : IProvidable<IUpdateSlow>
{
void OnUpdate(float dt);
void Subscribe(UpdateMethod onUpdate);
void Unsubscribe(UpdateMethod onUpdate);
}
/// <summary>
/// A default implementation.
/// </summary>
public class UpdateSlowNoop : IUpdateSlow
{
public void OnUpdate(float dt) { }
public void Subscribe(UpdateMethod onUpdate) { }
public void Unsubscribe(UpdateMethod onUpdate) { }
public void OnReProvided(IUpdateSlow prev) { }
}
/// <summary>
/// Some objects might need to be on a slower update loop than the usual MonoBehaviour Update, e.g. to refresh data from services.
/// Some might also not want to be coupled to a Unity object at all but still need an update loop.
/// </summary>
public class UpdateSlow : MonoBehaviour, IUpdateSlow
{
[SerializeField]
[Tooltip("Update interval. Note that lobby Get requests must occur at least 1 second apart, so this period should likely be greater than that.")]
private float m_updatePeriod = 1.5f;
[SerializeField]
[Tooltip("If a subscriber to slow update takes longer than this to execute, it can be automatically unsubscribed.")]
private float m_durationToleranceMs = 10;
[SerializeField]
[Tooltip("We ordinarily automatically remove a subscriber that takes too long. Otherwise, we'll simply log.")]
private bool m_doNotRemoveIfTooLong = false;
private List<UpdateMethod> m_subscribers = new List<UpdateMethod>();
private float m_updateTimer = 0;
private int m_nextActiveSubIndex = 0; // For staggering subscribers, to prevent spikes of lots of things triggering at once.
public void Awake()
{
Locator.Get.Provide(this);
}
public void OnDestroy()
{
// We should clean up references in case they would prevent garbage collection.
m_subscribers.Clear();
}
/// <summary>Don't assume that onUpdate will be called in any particular order compared to other subscribers.</summary>
public void Subscribe(UpdateMethod onUpdate)
{
if (!m_subscribers.Contains(onUpdate))
m_subscribers.Add(onUpdate);
}
/// <summary>Safe to call even if onUpdate was not previously Subscribed.</summary>
public void Unsubscribe(UpdateMethod onUpdate)
{
int index = m_subscribers.IndexOf(onUpdate);
if (index >= 0)
{
m_subscribers.Remove(onUpdate);
if (index < m_nextActiveSubIndex)
m_nextActiveSubIndex--;
}
}
private void Update()
{
if (m_subscribers.Count == 0)
return;
m_updateTimer += Time.deltaTime;
float effectivePeriod = m_updatePeriod / m_subscribers.Count;
while (m_updateTimer > effectivePeriod)
{
m_updateTimer -= effectivePeriod;
OnUpdate(effectivePeriod);
}
}
public void OnUpdate(float dt)
{
Stopwatch stopwatch = new Stopwatch();
m_nextActiveSubIndex = System.Math.Max(0, System.Math.Min(m_subscribers.Count - 1, m_nextActiveSubIndex)); // Just a backup.
UpdateMethod onUpdate = m_subscribers[m_nextActiveSubIndex];
if (onUpdate == null || onUpdate.Target == null) // In case something forgets to Unsubscribe when it dies.
{ Remove(m_nextActiveSubIndex, $"Did not Unsubscribe from UpdateSlow: {onUpdate.Target} : {onUpdate.Method}");
return;
}
if (onUpdate.Method.ToString().Contains("<")) // Detect an anonymous or lambda or local method that cannot be Unsubscribed, by checking for a character that can't exist in a declared method name.
{ Remove(m_nextActiveSubIndex, $"Removed anonymous from UpdateSlow: {onUpdate.Target} : {onUpdate.Method}");
return;
}
stopwatch.Restart();
onUpdate?.Invoke(dt);
stopwatch.Stop();
if (stopwatch.ElapsedMilliseconds > m_durationToleranceMs)
{
if (!m_doNotRemoveIfTooLong)
Remove(m_nextActiveSubIndex, $"UpdateSlow subscriber took too long, removing: {onUpdate.Target} : {onUpdate.Method}");
else
{
Debug.LogWarning($"UpdateSlow subscriber took too long: {onUpdate.Target} : {onUpdate.Method}");
Increment();
}
}
else
Increment();
void Remove(int index, string msg)
{
m_subscribers.RemoveAt(index);
m_nextActiveSubIndex--;
Debug.LogError(msg);
Increment();
}
void Increment()
{
m_nextActiveSubIndex++;
if (m_nextActiveSubIndex >= m_subscribers.Count)
m_nextActiveSubIndex = 0;
}
}
public void OnReProvided(IUpdateSlow prevUpdateSlow)
{
if (prevUpdateSlow is UpdateSlow)
m_subscribers.AddRange((prevUpdateSlow as UpdateSlow).m_subscribers);
}
}
}
using System.Collections.Generic;
using UnityEngine;
using Stopwatch = System.Diagnostics.Stopwatch;
namespace LobbyRelaySample
{
public delegate void UpdateMethod(float dt);
public interface IUpdateSlow : IProvidable<IUpdateSlow>
{
void OnUpdate(float dt);
void Subscribe(UpdateMethod onUpdate);
void Unsubscribe(UpdateMethod onUpdate);
}
/// <summary>
/// A default implementation.
/// </summary>
public class UpdateSlowNoop : IUpdateSlow
{
public void OnUpdate(float dt) { }
public void Subscribe(UpdateMethod onUpdate) { }
public void Unsubscribe(UpdateMethod onUpdate) { }
public void OnReProvided(IUpdateSlow prev) { }
}
/// <summary>
/// Some objects might need to be on a slower update loop than the usual MonoBehaviour Update, e.g. to refresh data from services.
/// Some might also not want to be coupled to a Unity object at all but still need an update loop.
/// </summary>
public class UpdateSlow : MonoBehaviour, IUpdateSlow
{
[SerializeField]
[Tooltip("Update interval. Note that lobby Get requests must occur at least 1 second apart, so this period should likely be greater than that.")]
private float m_updatePeriod = 1.5f;
[SerializeField]
[Tooltip("If a subscriber to slow update takes longer than this to execute, it can be automatically unsubscribed.")]
private float m_durationToleranceMs = 10;
[SerializeField]
[Tooltip("We ordinarily automatically remove a subscriber that takes too long. Otherwise, we'll simply log.")]
private bool m_doNotRemoveIfTooLong = false;
private List<UpdateMethod> m_subscribers = new List<UpdateMethod>();
private float m_updateTimer = 0;
private int m_nextActiveSubIndex = 0; // For staggering subscribers, to prevent spikes of lots of things triggering at once.
public void Awake()
{
Locator.Get.Provide(this);
}
public void OnDestroy()
{
// We should clean up references in case they would prevent garbage collection.
m_subscribers.Clear();
}
/// <summary>Don't assume that onUpdate will be called in any particular order compared to other subscribers.</summary>
public void Subscribe(UpdateMethod onUpdate)
{
if (!m_subscribers.Contains(onUpdate))
m_subscribers.Add(onUpdate);
}
/// <summary>Safe to call even if onUpdate was not previously Subscribed.</summary>
public void Unsubscribe(UpdateMethod onUpdate)
{
int index = m_subscribers.IndexOf(onUpdate);
if (index >= 0)
{
m_subscribers.Remove(onUpdate);
if (index < m_nextActiveSubIndex)
m_nextActiveSubIndex--;
}
}
private void Update()
{
if (m_subscribers.Count == 0)
return;
m_updateTimer += Time.deltaTime;
float effectivePeriod = m_updatePeriod / m_subscribers.Count;
while (m_updateTimer > effectivePeriod)
{
m_updateTimer -= effectivePeriod;
OnUpdate(effectivePeriod);
}
}
public void OnUpdate(float dt)
{
Stopwatch stopwatch = new Stopwatch();
m_nextActiveSubIndex = System.Math.Max(0, System.Math.Min(m_subscribers.Count - 1, m_nextActiveSubIndex)); // Just a backup.
UpdateMethod onUpdate = m_subscribers[m_nextActiveSubIndex];
if (onUpdate == null || onUpdate.Target == null) // In case something forgets to Unsubscribe when it dies.
{ Remove(m_nextActiveSubIndex, $"Did not Unsubscribe from UpdateSlow: {onUpdate.Target} : {onUpdate.Method}");
return;
}
if (onUpdate.Method.ToString().Contains("<")) // Detect an anonymous or lambda or local method that cannot be Unsubscribed, by checking for a character that can't exist in a declared method name.
{ Remove(m_nextActiveSubIndex, $"Removed anonymous from UpdateSlow: {onUpdate.Target} : {onUpdate.Method}");
return;
}
stopwatch.Restart();
onUpdate?.Invoke(dt);
stopwatch.Stop();
if (stopwatch.ElapsedMilliseconds > m_durationToleranceMs)
{
if (!m_doNotRemoveIfTooLong)
Remove(m_nextActiveSubIndex, $"UpdateSlow subscriber took too long, removing: {onUpdate.Target} : {onUpdate.Method}");
else
{
Debug.LogWarning($"UpdateSlow subscriber took too long: {onUpdate.Target} : {onUpdate.Method}");
Increment();
}
}
else
Increment();
void Remove(int index, string msg)
{
m_subscribers.RemoveAt(index);
m_nextActiveSubIndex--;
Debug.LogError(msg);
Increment();
}
void Increment()
{
m_nextActiveSubIndex++;
if (m_nextActiveSubIndex >= m_subscribers.Count)
m_nextActiveSubIndex = 0;
}
}
public void OnReProvided(IUpdateSlow prevUpdateSlow)
{
if (prevUpdateSlow is UpdateSlow)
m_subscribers.AddRange((prevUpdateSlow as UpdateSlow).m_subscribers);
}
}
}

218
Assets/Scripts/Lobby/LobbyAPIInterface.cs


using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Unity.Services.Rooms;
using Unity.Services.Rooms.Models;
using Unity.Services.Rooms.Rooms;
namespace LobbyRelaySample.Lobby
{
/// <summary>
/// Does all the interactions with the Lobby API.
/// </summary>
public static class LobbyAPIInterface
{
private class InProgressRequest<T>
{
public InProgressRequest(Task<T> task, Action<T> onComplete)
{
DoRequest(task, onComplete);
}
private async void DoRequest(Task<T> task, Action<T> onComplete)
{
T result = default;
string currentTrace = System.Environment.StackTrace;
try {
result = await task;
} catch (Exception e) {
Exception eFull = new Exception($"Call stack before async call:\n{currentTrace}\n", e);
throw eFull;
} finally {
onComplete?.Invoke(result);
}
}
}
private const int k_maxLobbiesToShow = 64;