当前提交
356c8836
共有 1004 个文件被更改,包括 4320 次插入 和 2104 次删除
-
6Assets/Prefabs/UI/LobbyButtonUI.prefab
-
3Assets/Prefabs/UI/LobbyCodeCanvas.prefab
-
140Assets/Scripts/Auth/NameGenerator.cs
-
6Assets/Scripts/Entities/GameStateManager.cs
-
542Assets/Scripts/Entities/LocalLobby.cs
-
8Assets/Scripts/Entities/LocalLobbyObserver.cs
-
200Assets/Scripts/Infrastructure/Locator.cs
-
126Assets/Scripts/Infrastructure/LogHandler.cs
-
176Assets/Scripts/Infrastructure/Messenger.cs
-
276Assets/Scripts/Infrastructure/UpdateSlow.cs
-
218Assets/Scripts/Lobby/LobbyAPIInterface.cs
-
439Assets/Scripts/Lobby/LobbyAsyncRequests.cs
-
218Assets/Scripts/Lobby/LobbyContentHeartbeat.cs
-
46Assets/Scripts/Lobby/LobbyListHeartbeat.cs
-
126Assets/Scripts/Lobby/ReadyCheck.cs
-
178Assets/Scripts/Lobby/ToLocalLobby.cs
-
4Assets/Scripts/LobbyRelaySample.asmdef
-
260Assets/Scripts/Tests/PlayMode/LobbyReadyCheckTests.cs
-
326Assets/Scripts/Tests/PlayMode/LobbyRoundtripTests.cs
-
2Assets/Scripts/Tests/PlayMode/Tests.Play.asmdef
-
40Assets/Scripts/UI/CountdownUI.cs
-
66Assets/Scripts/UI/DisplayCodeUI.cs
-
30Assets/Scripts/UI/EndGameButtonUI.cs
-
30Assets/Scripts/UI/ExitButtonUI.cs
-
120Assets/Scripts/UI/InLobbyUserList.cs
-
138Assets/Scripts/UI/InLobbyUserUI.cs
-
42Assets/Scripts/UI/JoinCreateLobbyUI.cs
-
38Assets/Scripts/UI/LobbyNameUI.cs
-
46Assets/Scripts/UI/ReadyCheckUI.cs
-
38Assets/Scripts/UI/RelayAddressUI.cs
-
4Assets/Scripts/UI/ShowWhenLobbyStateUI.cs
-
106Assets/Scripts/UI/SpinnerUI.cs
-
30Assets/Scripts/UI/StartLobbyButtonUI.cs
-
22Packages/manifest.json
-
62Packages/packages-lock.json
-
24ProjectSettings/PackageManagerSettings.asset
-
6ProjectSettings/ProjectSettings.asset
-
2ProjectSettings/UnityConnectSettings.asset
-
42README.md
-
242Packages/com.unity.services.authentication/.README - External.md
-
53Packages/com.unity.services.authentication/CHANGELOG.md
-
7Packages/com.unity.services.authentication/CHANGELOG.md.meta
-
5Packages/com.unity.services.authentication/Documentation~/com.unity.services.authentication.md
-
8Packages/com.unity.services.authentication/Editor.meta
-
5Packages/com.unity.services.authentication/Editor/AssemblyInfo.cs
-
3Packages/com.unity.services.authentication/Editor/AssemblyInfo.cs.meta
-
298Packages/com.unity.services.authentication/Editor/AuthenticationAdminClient.cs
-
3Packages/com.unity.services.authentication/Editor/AuthenticationAdminClient.cs.meta
-
148Packages/com.unity.services.authentication/Editor/AuthenticationAdminNetworkClient.cs
-
11Packages/com.unity.services.authentication/Editor/AuthenticationAdminNetworkClient.cs.meta
-
17Packages/com.unity.services.authentication/Editor/AuthenticationIdentifier.cs
-
11Packages/com.unity.services.authentication/Editor/AuthenticationIdentifier.cs.meta
-
47Packages/com.unity.services.authentication/Editor/AuthenticationService.cs
-
11Packages/com.unity.services.authentication/Editor/AuthenticationService.cs.meta
-
292Packages/com.unity.services.authentication/Editor/AuthenticationSettingsElement.cs
-
3Packages/com.unity.services.authentication/Editor/AuthenticationSettingsElement.cs.meta
-
47Packages/com.unity.services.authentication/Editor/AuthenticationSettingsHelper.cs
-
3Packages/com.unity.services.authentication/Editor/AuthenticationSettingsHelper.cs.meta
-
58Packages/com.unity.services.authentication/Editor/AuthenticationSettingsProvider.cs
-
11Packages/com.unity.services.authentication/Editor/AuthenticationSettingsProvider.cs.meta
-
20Packages/com.unity.services.authentication/Editor/AuthenticationTopMenu.cs
-
11Packages/com.unity.services.authentication/Editor/AuthenticationTopMenu.cs.meta
-
79Packages/com.unity.services.authentication/Editor/IAuthenticationAdminClient.cs
-
3Packages/com.unity.services.authentication/Editor/IAuthenticationAdminClient.cs.meta
-
399Packages/com.unity.services.authentication/Editor/IdProviderElement.cs
-
3Packages/com.unity.services.authentication/Editor/IdProviderElement.cs.meta
-
8Packages/com.unity.services.authentication/Editor/Models.meta
-
49Packages/com.unity.services.authentication/Editor/Models/CreateIdProviderRequest.cs
-
11Packages/com.unity.services.authentication/Editor/Models/CreateIdProviderRequest.cs.meta
-
20Packages/com.unity.services.authentication/Editor/Models/DeleteIdProviderRequest.cs
-
11Packages/com.unity.services.authentication/Editor/Models/DeleteIdProviderRequest.cs.meta
-
19Packages/com.unity.services.authentication/Editor/Models/GetIdDomainResponse.cs
-
11Packages/com.unity.services.authentication/Editor/Models/GetIdDomainResponse.cs.meta
-
56Packages/com.unity.services.authentication/Editor/Models/IdProviderResponse.cs
-
11Packages/com.unity.services.authentication/Editor/Models/IdProviderResponse.cs.meta
-
19Packages/com.unity.services.authentication/Editor/Models/ListIdProviderResponse.cs
-
11Packages/com.unity.services.authentication/Editor/Models/ListIdProviderResponse.cs.meta
-
29Packages/com.unity.services.authentication/Editor/Models/TokenExchangeErrorResponse.cs
-
11Packages/com.unity.services.authentication/Editor/Models/TokenExchangeErrorResponse.cs.meta
-
16Packages/com.unity.services.authentication/Editor/Models/TokenExchangeRequest.cs
-
11Packages/com.unity.services.authentication/Editor/Models/TokenExchangeRequest.cs.meta
-
16Packages/com.unity.services.authentication/Editor/Models/TokenExchangeResponse.cs
-
11Packages/com.unity.services.authentication/Editor/Models/TokenExchangeResponse.cs.meta
-
44Packages/com.unity.services.authentication/Editor/Models/UpdateIdProviderRequest.cs
-
11Packages/com.unity.services.authentication/Editor/Models/UpdateIdProviderRequest.cs.meta
-
3Packages/com.unity.services.authentication/Editor/USS.meta
-
41Packages/com.unity.services.authentication/Editor/USS/AuthenticationStyleSheet.uss
-
3Packages/com.unity.services.authentication/Editor/USS/AuthenticationStyleSheet.uss.meta
-
3Packages/com.unity.services.authentication/Editor/UXML.meta
-
15Packages/com.unity.services.authentication/Editor/UXML/AuthenticationProjectSettings.uxml
|
|||
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(); |
|||
} |
|||
} |
|||
} |
|
|||
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); |
|||
} |
|||
} |
|||
} |
|
|||
namespace LobbyRelaySample |
|||
{ |
|||
public class LocalLobbyObserver : ObserverBehaviour<LocalLobby> { } |
|||
} |
|||
namespace LobbyRelaySample |
|||
{ |
|||
public class LocalLobbyObserver : ObserverBehaviour<LocalLobby> { } |
|||
} |
|
|||
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; |
|||
} |
|||
} |
|||
} |
|
|||
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); |
|||
} |
|||
} |
|||
} |
|
|||
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); |
|||
} |
|||
} |
|||
} |
|
|||
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); |
|||
} |
|||
} |
|||
} |
|
|||
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; |
|||