浏览代码

Renamed LobbyUpdater to Lobby Synchronizer.

Removed Identity Service, Replaced with AuthenticationManager
Removed “private” in front of all private fields.
/main/staging/2021_Upgrade/Async_Refactor
当前提交
8a5b5a31
共有 44 个文件被更改,包括 498 次插入493 次删除
  1. 6
      Assets/Scripts/GameLobby/Game/Countdown.cs
  2. 10
      Assets/Scripts/GameLobby/Game/GameManager.cs
  3. 4
      Assets/Scripts/GameLobby/Game/LobbyUser.cs
  4. 31
      Assets/Scripts/GameLobby/Game/LocalLobby.cs
  5. 7
      Assets/Scripts/GameLobby/Infrastructure/Locator.cs
  6. 2
      Assets/Scripts/GameLobby/Infrastructure/Messenger.cs
  7. 14
      Assets/Scripts/GameLobby/Infrastructure/UpdateSlow.cs
  8. 49
      Assets/Scripts/GameLobby/Lobby/LobbyConverters.cs
  9. 4
      Assets/Scripts/GameLobby/Lobby/LobbySynchronizer.cs
  10. 2
      Assets/Scripts/GameLobby/NGO/InGameRunner.cs
  11. 4
      Assets/Scripts/GameLobby/NGO/IntroOutroRunner.cs
  12. 14
      Assets/Scripts/GameLobby/NGO/NetworkedDataStore.cs
  13. 4
      Assets/Scripts/GameLobby/NGO/PlayerCursor.cs
  14. 8
      Assets/Scripts/GameLobby/NGO/ResultsUserUI.cs
  15. 10
      Assets/Scripts/GameLobby/NGO/Scorer.cs
  16. 2
      Assets/Scripts/GameLobby/NGO/SetupInGame.cs
  17. 10
      Assets/Scripts/GameLobby/NGO/SymbolContainer.cs
  18. 4
      Assets/Scripts/GameLobby/NGO/SymbolKillVolume.cs
  19. 4
      Assets/Scripts/GameLobby/NGO/SymbolObject.cs
  20. 2
      Assets/Scripts/GameLobby/Relay/AsyncRequestRelay.cs
  21. 2
      Assets/Scripts/GameLobby/Relay/RelayPendingApproval.cs
  22. 4
      Assets/Scripts/GameLobby/Relay/RelayUtpClient.cs
  23. 12
      Assets/Scripts/GameLobby/Relay/RelayUtpHost.cs
  24. 468
      Assets/Scripts/GameLobby/Relay/RelayUtpSetup.cs
  25. 8
      Assets/Scripts/GameLobby/Tests/Editor/MessengerTests.cs
  26. 4
      Assets/Scripts/GameLobby/Tests/Editor/ObserverTests.cs
  27. 45
      Assets/Scripts/GameLobby/Tests/PlayMode/LobbyRoundtripTests.cs
  28. 122
      Assets/Scripts/GameLobby/Tests/PlayMode/RelayRoundTripTests.cs
  29. 3
      Assets/Scripts/GameLobby/Tests/PlayMode/Tests.Play.asmdef
  30. 7
      Assets/Scripts/GameLobby/Tests/PlayMode/UtpTests.cs
  31. 2
      Assets/Scripts/GameLobby/UI/EmoteButtonUI.cs
  32. 10
      Assets/Scripts/GameLobby/UI/InLobbyUserUI.cs
  33. 8
      Assets/Scripts/GameLobby/UI/JoinMenuUI.cs
  34. 12
      Assets/Scripts/GameLobby/UI/LobbyUserVolumeUI.cs
  35. 54
      Assets/Scripts/GameLobby/UI/RateLimitVisibility.cs
  36. 2
      Assets/Scripts/GameLobby/UI/ReadyCheckUI.cs
  37. 6
      Assets/Scripts/GameLobby/UI/ShowWhenLobbyStateUI.cs
  38. 10
      Assets/Scripts/GameLobby/UI/SpinnerUI.cs
  39. 3
      Assets/Scripts/GameLobby/Vivox/VivoxSetup.cs
  40. 4
      Packages/ParrelSync/Editor/ClonesManagerWindow.cs
  41. 8
      Packages/ParrelSync/Editor/NonCore/OtherMenuItem.cs
  42. 6
      Packages/packages-lock.json
  43. 0
      /Assets/Scripts/GameLobby/Lobby/LobbySynchronizer.cs.meta
  44. 0
      /Assets/Scripts/GameLobby/Lobby/LobbySynchronizer.cs

6
Assets/Scripts/GameLobby/Game/Countdown.cs


{
public class Data : Observed<Countdown.Data>
{
private float m_timeLeft;
public float TimeLeft
float m_timeLeft;
public float TimeLeft
{
get => m_timeLeft;
set

public override void CopyObserved(Data oldObserved) { /*No-op, since this is unnecessary.*/ }
}
private Data m_data = new Data();
Data m_data = new Data();
private UI.CountdownUI m_ui;
private const int k_countdownTime = 4;

10
Assets/Scripts/GameLobby/Game/GameManager.cs


LobbyUser m_LocalUser;
LocalLobby m_LocalLobby;
LobbyServiceData m_LobbyServiceData = new LobbyServiceData();
LobbyUpdater m_LobbyUpdater;
LobbySynchronizer m_LobbySynchronizer;
RelayUtpSetup m_RelaySetup;
RelayUtpClient m_RelayClient;

m_LocalLobby
.AddPlayer(m_LocalUser); // The local LobbyUser object will be hooked into UI before the LocalLobby is populated during lobby join, so the LocalLobby must know about it already when that happens.
LobbyManager = new LobbyManager();
m_LobbyUpdater = new LobbyUpdater(LobbyManager);
m_LobbySynchronizer = new LobbySynchronizer(LobbyManager);
}
/// <summary>

void OnJoinedLobby()
{
m_LobbyUpdater.BeginTracking(m_LocalLobby, m_LocalUser);
m_LobbySynchronizer.BeginTracking(m_LocalLobby, m_LocalUser);
SetUserLobbyState();
// The host has the opportunity to reject incoming players, but to do so the player needs to connect to Relay without having game logic available.

LobbyManager.LeaveLobbyAsync(m_LocalLobby.LobbyID);
#pragma warning restore 4014
ResetLocalLobby();
m_LobbyUpdater.EndTracking();
m_LobbySynchronizer.EndTracking();
m_VivoxSetup.LeaveLobbyChannel();
if (m_RelaySetup != null)

void OnDestroy()
{
ForceLeaveAttempt();
m_LobbyUpdater.Dispose();
m_LobbySynchronizer.Dispose();
LobbyManager.Dispose();
}

4
Assets/Scripts/GameLobby/Game/LobbyUser.cs


}
}
private UserData m_data;
UserData m_data;
public void ResetState()
{

IsApproved = 32
}
private UserMembers m_lastChanged;
UserMembers m_lastChanged;
public UserMembers LastChanged => m_lastChanged;
public bool IsHost

31
Assets/Scripts/GameLobby/Game/LocalLobby.cs


using System.Collections.Generic;
using System.Text;
using UnityEngine;
using UnityEngine.Serialization;
namespace LobbyRelaySample
{

return;
}
DoAddPlayer(user);
AddUser(user);
private void DoAddPlayer(LobbyUser user)
void AddUser(LobbyUser user)
{
m_LobbyUsers.Add(user.ID, user);
user.onChanged += OnChangedUser;

}
}
public void CopyObserved(LobbyData data, Dictionary<string, LobbyUser> currUsers)
public void CopyObserved(LobbyData lobbyData, Dictionary<string, LobbyUser> lobbyUsers)
var pendingState = data.State;
var pendingColor = data.Color;
var pendingNgoCode = data.RelayNGOCode;
if (m_Data.State_LastEdit > data.State_LastEdit)
var pendingState = lobbyData.State;
var pendingColor = lobbyData.Color;
var pendingNgoCode = lobbyData.RelayNGOCode;
if (m_Data.State_LastEdit > lobbyData.State_LastEdit)
if (m_Data.Color_LastEdit > data.Color_LastEdit)
if (m_Data.Color_LastEdit > lobbyData.Color_LastEdit)
if (m_Data.RelayNGOCode_LastEdit > data.RelayNGOCode_LastEdit)
if (m_Data.RelayNGOCode_LastEdit > lobbyData.RelayNGOCode_LastEdit)
m_Data = data;
m_Data = lobbyData;
if (currUsers == null)
if (lobbyUsers == null)
m_LobbyUsers = new Dictionary<string, LobbyUser>();
else
{

if (currUsers.ContainsKey(oldUser.Key))
oldUser.Value.CopyObserved(currUsers[oldUser.Key]);
if (lobbyUsers.ContainsKey(oldUser.Key))
oldUser.Value.CopyObserved(lobbyUsers[oldUser.Key]);
else
toRemove.Add(oldUser.Value);
}

DoRemoveUser(remove);
}
foreach (var currUser in currUsers)
foreach (var currUser in lobbyUsers)
DoAddPlayer(currUser.Value);
AddUser(currUser.Value);
}
}

7
Assets/Scripts/GameLobby/Infrastructure/Locator.cs


using LobbyRelaySample.Auth;
using System;
using System.Collections.Generic;

{
Provide(new Messenger());
Provide(new UpdateSlowNoop());
Provide(new IdentityNoop());
Provide(new ngo.InGameInputHandlerNoop());
FinishConstruction();

public IUpdateSlow UpdateSlow => Locate<IUpdateSlow>();
public void Provide(IUpdateSlow updateSlow) { ProvideAny(updateSlow); }
public IIdentity Identity => Locate<IIdentity>();
public void Provide(IIdentity identity) { ProvideAny(identity); }
}
}

2
Assets/Scripts/GameLobby/Infrastructure/Messenger.cs


/// </summary>
public class Messenger : IMessenger
{
private List<IReceiveMessages> m_receivers = new List<IReceiveMessages>();
List<IReceiveMessages> m_receivers = new List<IReceiveMessages>();
private const float k_durationToleranceMs = 15;
// We need to handle subscribers who modify the receiver list, e.g. a subscriber who unsubscribes in their OnReceiveMessage.

14
Assets/Scripts/GameLobby/Infrastructure/UpdateSlow.cs


/// </summary>
public class UpdateSlow : MonoBehaviour, IUpdateSlow
{
private class Subscriber
class Subscriber
public Subscriber(UpdateMethod updateMethod, float period)
{
public Subscriber(UpdateMethod updateMethod, float period)
{
this.updateMethod = updateMethod;
this.period = period;
this.periodCurrent = 0;

[SerializeField]
[Tooltip("If a subscriber to slow update takes longer than this to execute, it can be automatically unsubscribed.")]
private float m_durationToleranceMs = 10;
float m_durationToleranceMs = 10;
private bool m_doNotRemoveIfTooLong = false;
private List<Subscriber> m_subscribers = new List<Subscriber>();
bool m_doNotRemoveIfTooLong = false;
List<Subscriber> m_subscribers = new List<Subscriber>();
public void Awake()
{

}
private void Update()
void Update()
{
OnUpdate(Time.deltaTime);
}

49
Assets/Scripts/GameLobby/Lobby/LobbyConverters.cs


using System.Collections.Generic;
using Unity.Services.Lobbies.Models;
using UnityEngine;
namespace LobbyRelaySample.lobby
{

return data;
data.Add("DisplayName", user.DisplayName); // The lobby doesn't need to know any data beyond the name and state; Relay will handle the rest.
data.Add("UserStatus", ((int)user.UserStatus).ToString());
data.Add("Emote", user.Emote.ToString());
return data;
}

public static void RemoteToLocal(Lobby lobby, LocalLobby lobbyToUpdate)
public static void RemoteToLocal(Lobby remoteLobby, LocalLobby localLobbyToUpdate)
LocalLobby.LobbyData info = new LocalLobby.LobbyData(lobbyToUpdate.Data)
LocalLobby.LobbyData lobbyData = new LocalLobby.LobbyData(localLobbyToUpdate.Data)
LobbyID = lobby.Id,
LobbyCode = lobby.LobbyCode,
Private = lobby.IsPrivate,
LobbyName = lobby.Name,
MaxPlayerCount = lobby.MaxPlayers,
RelayCode = lobby.Data?.ContainsKey("RelayCode") == true ? lobby.Data["RelayCode"].Value : lobbyToUpdate.RelayCode, // By providing RelayCode through the lobby data with Member visibility, we ensure a client is connected to the lobby before they could attempt a relay connection, preventing timing issues between them.
RelayNGOCode = lobby.Data?.ContainsKey("RelayNGOCode") == true ? lobby.Data["RelayNGOCode"].Value : lobbyToUpdate.RelayNGOCode,
State = lobby.Data?.ContainsKey("State") == true ? (LobbyState)int.Parse(lobby.Data["State"].Value) : LobbyState.Lobby,
Color = lobby.Data?.ContainsKey("Color") == true ? (LobbyColor)int.Parse(lobby.Data["Color"].Value) : LobbyColor.None,
State_LastEdit = lobby.Data?.ContainsKey("State_LastEdit") == true ? long.Parse(lobby.Data["State_LastEdit"].Value) : lobbyToUpdate.Data.State_LastEdit,
Color_LastEdit = lobby.Data?.ContainsKey("Color_LastEdit") == true ? long.Parse(lobby.Data["Color_LastEdit"].Value) : lobbyToUpdate.Data.Color_LastEdit,
RelayNGOCode_LastEdit = lobby.Data?.ContainsKey("RelayNGOCode_LastEdit") == true ? long.Parse(lobby.Data["RelayNGOCode_LastEdit"].Value) : lobbyToUpdate.Data.RelayNGOCode_LastEdit
LobbyID = remoteLobby.Id,
LobbyCode = remoteLobby.LobbyCode,
Private = remoteLobby.IsPrivate,
LobbyName = remoteLobby.Name,
MaxPlayerCount = remoteLobby.MaxPlayers,
RelayCode = remoteLobby.Data?.ContainsKey("RelayCode") == true ? remoteLobby.Data["RelayCode"].Value : localLobbyToUpdate.RelayCode, // By providing RelayCode through the lobby data with Member visibility, we ensure a client is connected to the lobby before they could attempt a relay connection, preventing timing issues between them.
RelayNGOCode = remoteLobby.Data?.ContainsKey("RelayNGOCode") == true ? remoteLobby.Data["RelayNGOCode"].Value : localLobbyToUpdate.RelayNGOCode,
State = remoteLobby.Data?.ContainsKey("State") == true ? (LobbyState)int.Parse(remoteLobby.Data["State"].Value) : LobbyState.Lobby,
Color = remoteLobby.Data?.ContainsKey("Color") == true ? (LobbyColor)int.Parse(remoteLobby.Data["Color"].Value) : LobbyColor.None,
State_LastEdit = remoteLobby.Data?.ContainsKey("State_LastEdit") == true ? long.Parse(remoteLobby.Data["State_LastEdit"].Value) : localLobbyToUpdate.Data.State_LastEdit,
Color_LastEdit = remoteLobby.Data?.ContainsKey("Color_LastEdit") == true ? long.Parse(remoteLobby.Data["Color_LastEdit"].Value) : localLobbyToUpdate.Data.Color_LastEdit,
RelayNGOCode_LastEdit = remoteLobby.Data?.ContainsKey("RelayNGOCode_LastEdit") == true ? long.Parse(remoteLobby.Data["RelayNGOCode_LastEdit"].Value) : localLobbyToUpdate.Data.RelayNGOCode_LastEdit
foreach (var player in lobby.Players)
foreach (var player in remoteLobby.Players)
// If we already know about this player and this player is already connected to Relay, don't overwrite things that Relay might be changing.
if (player.Data?.ContainsKey("UserStatus") == true && int.TryParse(player.Data["UserStatus"].Value, out int status))
{
if (status > (int)UserStatus.Connecting && lobbyToUpdate.LobbyUsers.ContainsKey(player.Id))
{
lobbyUsers.Add(player.Id, lobbyToUpdate.LobbyUsers[player.Id]);
continue;
}
}
// If the player isn't connected to Relay, get the most recent data that the lobby knows.
IsHost = lobby.HostId.Equals(player.Id),
IsHost = remoteLobby.HostId.Equals(player.Id),
DisplayName = player.Data?.ContainsKey("DisplayName") == true ? player.Data["DisplayName"].Value : default,
Emote = player.Data?.ContainsKey("Emote") == true ? (EmoteType)int.Parse(player.Data["Emote"].Value) : default,
UserStatus = player.Data?.ContainsKey("UserStatus") == true ? (UserStatus)int.Parse(player.Data["UserStatus"].Value) : UserStatus.Connecting,

}
//Push all the data at once so we don't call OnChanged for each variable
lobbyToUpdate.CopyObserved(info, lobbyUsers);
localLobbyToUpdate.CopyObserved(lobbyData, lobbyUsers);
}
/// <summary>

return retLst;
}
private static LocalLobby RemoteToNewLocal(Lobby lobby)
static LocalLobby RemoteToNewLocal(Lobby lobby)
{
LocalLobby data = new LocalLobby();
RemoteToLocal(lobby, data);

4
Assets/Scripts/GameLobby/Lobby/LobbySynchronizer.cs


/// <summary>
/// Keep updated on changes to a joined lobby, at a speed compliant with Lobby's rate limiting.
/// </summary>
public class LobbyUpdater : IReceiveMessages, IDisposable
public class LobbySynchronizer : IReceiveMessages, IDisposable
{
LocalLobby m_LocalLobby;
LobbyUser m_LocalUser;

int m_lifetime = 0;
const int k_UpdateIntervalMS = 100;
public LobbyUpdater(LobbyManager lobbyManager)
public LobbySynchronizer(LobbyManager lobbyManager)
{
m_LobbyManager = lobbyManager;
}

2
Assets/Scripts/GameLobby/NGO/InGameRunner.cs


/// </summary>
public class InGameRunner : NetworkBehaviour, IInGameInputHandler
{
private Action m_onConnectionVerified, m_onGameEnd;
Action m_onConnectionVerified, m_onGameEnd;
private int m_expectedPlayerCount; // Used by the host, but we can't call the RPC until the network connection completes.
private bool? m_canSpawnInGameObjects;
private Queue<Vector2> m_pendingSymbolPositions = new Queue<Vector2>();

4
Assets/Scripts/GameLobby/NGO/IntroOutroRunner.cs


/// </summary>
public class IntroOutroRunner : MonoBehaviour
{
[SerializeField] private Animator m_animator;
private Action m_onOutroComplete;
[SerializeField] Animator m_animator;
Action m_onOutroComplete;
public void DoIntro()
{

14
Assets/Scripts/GameLobby/NGO/NetworkedDataStore.cs


// Using a singleton here since we need spawned PlayerCursors to be able to find it, but we don't need the flexibility offered by the Locator.
public static NetworkedDataStore Instance;
private Dictionary<ulong, PlayerData> m_playerData = new Dictionary<ulong, PlayerData>();
private ulong m_localId;
Dictionary<ulong, PlayerData> m_playerData = new Dictionary<ulong, PlayerData>();
ulong m_localId;
private Action<PlayerData> m_onGetCurrentCallback;
private UnityEvent<PlayerData> m_onEachPlayerCallback;
Action<PlayerData> m_onGetCurrentCallback;
UnityEvent<PlayerData> m_onEachPlayerCallback;
public void Awake()
{

}
[ServerRpc(RequireOwnership = false)]
private void GetAllPlayerData_ServerRpc(ulong callerId)
void GetAllPlayerData_ServerRpc(ulong callerId)
{
var sortedData = m_playerData.Select(kvp => kvp.Value).OrderByDescending(data => data.score);
GetAllPlayerData_ClientRpc(callerId, sortedData.ToArray());

private void GetAllPlayerData_ClientRpc(ulong callerId, PlayerData[] sortedData)
void GetAllPlayerData_ClientRpc(ulong callerId, PlayerData[] sortedData)
{
if (callerId != m_localId)
return;

}
[ServerRpc(RequireOwnership = false)]
private void GetPlayerData_ServerRpc(ulong id, ulong callerId)
void GetPlayerData_ServerRpc(ulong id, ulong callerId)
{
if (m_playerData.ContainsKey(id))
GetPlayerData_ClientRpc(callerId, m_playerData[id]);

4
Assets/Scripts/GameLobby/NGO/PlayerCursor.cs


[RequireComponent(typeof(Collider))]
public class PlayerCursor : NetworkBehaviour, IReceiveMessages
{
[SerializeField] private SpriteRenderer m_renderer = default;
[SerializeField] private ParticleSystem m_onClickParticles = default;
[SerializeField] SpriteRenderer m_renderer = default;
[SerializeField] ParticleSystem m_onClickParticles = default;
[SerializeField] private TMPro.TMP_Text m_nameOutput = default;
private Camera m_mainCamera;
private NetworkVariable<Vector3> m_position = new NetworkVariable<Vector3>( Vector3.zero); // (Using a NetworkTransform to sync position would also work.)

8
Assets/Scripts/GameLobby/NGO/ResultsUserUI.cs


public class ResultsUserUI : NetworkBehaviour
{
[Tooltip("The containers for the player data outputs, in order, to be hidden until the game ends.")]
[SerializeField] private CanvasGroup[] m_containers;
[SerializeField] CanvasGroup[] m_containers;
[SerializeField] private TMPro.TMP_Text[] m_playerNameOutputs;
[SerializeField] TMPro.TMP_Text[] m_playerNameOutputs;
[SerializeField] private TMPro.TMP_Text[] m_playerScoreOutputs;
private int m_index = 0;
[SerializeField] TMPro.TMP_Text[] m_playerScoreOutputs;
int m_index = 0;
public void Start()
{

10
Assets/Scripts/GameLobby/NGO/Scorer.cs


/// </summary>
public class Scorer : NetworkBehaviour
{
[SerializeField] private NetworkedDataStore m_dataStore = default;
private ulong m_localId;
[SerializeField] private TMP_Text m_scoreOutputText = default;
[SerializeField] NetworkedDataStore m_dataStore = default;
ulong m_localId;
[SerializeField] TMP_Text m_scoreOutputText = default;
[SerializeField] private UnityEvent<PlayerData> m_onGameEnd = default;
[SerializeField] UnityEvent<PlayerData> m_onGameEnd = default;
public override void OnNetworkSpawn()
{

}
[ClientRpc]
private void UpdateScoreOutput_ClientRpc(ulong id, int score)
void UpdateScoreOutput_ClientRpc(ulong id, int score)
{
if (m_localId == id)
m_scoreOutputText.text = score.ToString("00");

2
Assets/Scripts/GameLobby/NGO/SetupInGame.cs


/// </summary>
public class SetupInGame : MonoBehaviour, IReceiveMessages
{
[SerializeField] private GameObject m_IngameRunnerPrefab = default;
[SerializeField] GameObject m_IngameRunnerPrefab = default;
[SerializeField] private GameObject[] m_disableWhileInGame = default;

10
Assets/Scripts/GameLobby/NGO/SymbolContainer.cs


public class SymbolContainer : NetworkBehaviour, IReceiveMessages
{
[SerializeField]
private float m_speed = 1;
private bool m_isConnected = false;
private bool m_hasGameStarted = false;
float m_speed = 1;
bool m_isConnected = false;
bool m_hasGameStarted = false;
private void OnGameStarted()
void OnGameStarted()
{
m_hasGameStarted = true;
if (m_isConnected)

BeginMotion();
}
private void BeginMotion()
void BeginMotion()
{
transform.position += Time.deltaTime * m_speed*Vector3.down;
}

4
Assets/Scripts/GameLobby/NGO/SymbolKillVolume.cs


[RequireComponent(typeof(Collider))]
public class SymbolKillVolume : MonoBehaviour
{
private bool m_isInitialized = false;
private Action m_onSymbolCollided;
bool m_isInitialized = false;
Action m_onSymbolCollided;
public void Initialize(Action onSymbolCollided)
{

4
Assets/Scripts/GameLobby/NGO/SymbolObject.cs


/// </summary>
public class SymbolObject : NetworkBehaviour
{
[SerializeField] private SymbolData m_symbolData;
[SerializeField] private SpriteRenderer m_renderer;
[SerializeField] SymbolData m_symbolData;
[SerializeField] SpriteRenderer m_renderer;
[SerializeField] private Animator m_animator;
public bool Clicked { get; private set; }

2
Assets/Scripts/GameLobby/Relay/AsyncRequestRelay.cs


{
public class AsyncRequestRelay : AsyncRequest
{
private static AsyncRequestRelay s_instance;
static AsyncRequestRelay s_instance;
public static AsyncRequestRelay Instance
{
get

2
Assets/Scripts/GameLobby/Relay/RelayPendingApproval.cs


/// </summary>
public class RelayPendingApproval : IDisposable
{
private NetworkConnection m_pendingConnection;
NetworkConnection m_pendingConnection;
private bool m_hasDisposed = false;
private const float k_waitTime = 0.1f;
private Action<NetworkConnection, Approval> m_onResult;

4
Assets/Scripts/GameLobby/Relay/RelayUtpClient.cs


protected bool m_IsRelayConnected { get { return m_localLobby.RelayServer != null; } }
protected bool m_hasSentInitialMessage = false;
private const float k_heartbeatPeriod = 5;
private bool m_hasDisposed = false;
const float k_heartbeatPeriod = 5;
bool m_hasDisposed = false;
protected enum MsgType { Ping = 0, NewPlayer, PlayerApprovalState, ReadyState, PlayerName, Emote, StartCountdown, CancelCountdown, ConfirmInGame, EndInGame, PlayerDisconnect }

12
Assets/Scripts/GameLobby/Relay/RelayUtpHost.cs


/// If so, they need to be updated with the current state of everyone else.
/// If not, they should be informed and rejected.
/// </summary>
private void OnNewConnection(NetworkConnection conn, string id)
void OnNewConnection(NetworkConnection conn, string id)
private void NewConnectionApprovalResult(NetworkConnection conn, Approval result)
void NewConnectionApprovalResult(NetworkConnection conn, Approval result)
{
WriteByte(m_networkDriver, conn, m_localUser.ID, MsgType.PlayerApprovalState, (byte)result);
if (result == Approval.OK && conn.IsCreated)

#pragma warning disable 4014
var queryCooldownMilliseconds = LobbyAsyncRequests.Instance.GetRateLimit(LobbyAsyncRequests.RequestType.Query)
/*var queryCooldownMilliseconds = LobbyManager.Instance.GetRateLimit(LobbyManager.RequestType.Query)
WaitAndCheckUsers(queryCooldownMilliseconds*2);
WaitAndCheckUsers(queryCooldownMilliseconds*2);*/
#pragma warning restore 4014
return;
}

}
}
private void CheckIfAllUsersReady()
void CheckIfAllUsersReady()
{
bool haveAllReadied = true;
foreach (var user in m_localLobby.LobbyUsers)

/// <summary>
/// Clean out destroyed connections, and accept all new ones.
/// </summary>
private void UpdateConnections()
void UpdateConnections()
{
for (int c = m_connections.Count - 1; c >= 0; c--)
{

468
Assets/Scripts/GameLobby/Relay/RelayUtpSetup.cs


namespace LobbyRelaySample.relay
{
/// <summary>
/// Responsible for setting up a connection with Relay using Unity Transport (UTP). A Relay Allocation is created by the host, and then all players
/// bind UTP to that Allocation in order to send data to each other.
/// Must be a MonoBehaviour since the binding process doesn't have asynchronous callback options.
/// </summary>
public abstract class RelayUtpSetup : MonoBehaviour
{
protected bool m_isRelayConnected = false;
protected NetworkDriver m_networkDriver;
protected List<NetworkConnection> m_connections;
protected NetworkEndPoint m_endpointForServer;
protected LocalLobby m_localLobby;
protected LobbyUser m_localUser;
protected Action<bool, RelayUtpClient> m_onJoinComplete;
/// <summary>
/// Responsible for setting up a connection with Relay using Unity Transport (UTP). A Relay Allocation is created by the host, and then all players
/// bind UTP to that Allocation in order to send data to each other.
/// Must be a MonoBehaviour since the binding process doesn't have asynchronous callback options.
/// </summary>
public abstract class RelayUtpSetup : MonoBehaviour
{
protected bool m_isRelayConnected = false;
protected NetworkDriver m_networkDriver;
protected List<NetworkConnection> m_connections;
protected NetworkEndPoint m_endpointForServer;
protected LocalLobby m_localLobby;
protected LobbyUser m_localUser;
protected Action<bool, RelayUtpClient> m_onJoinComplete;
public static string AddressFromEndpoint(NetworkEndPoint endpoint)
{
return endpoint.Address.Split(':')[0];
}
public static string AddressFromEndpoint(NetworkEndPoint endpoint)
{
return endpoint.Address.Split(':')[0];
}
public void BeginRelayJoin(LocalLobby localLobby, LobbyUser localUser, Action<bool, RelayUtpClient> onJoinComplete)
{
m_localLobby = localLobby;
m_localUser = localUser;
m_onJoinComplete = onJoinComplete;
JoinRelay();
}
protected abstract void JoinRelay();
public void BeginRelayJoin(
LocalLobby localLobby,
LobbyUser localUser,
Action<bool, RelayUtpClient> onJoinComplete)
{
m_localLobby = localLobby;
m_localUser = localUser;
m_onJoinComplete = onJoinComplete;
JoinRelay();
}
protected abstract void JoinRelay();
/// <summary>
/// Determine the server endpoint for connecting to the Relay server, for either an Allocation or a JoinAllocation.
/// If DTLS encryption is available, and there's a secure server endpoint available, use that as a secure connection. Otherwise, just connect to the Relay IP unsecured.
/// </summary>
public static NetworkEndPoint GetEndpointForAllocation(List<RelayServerEndpoint> endpoints, string ip, int port, out bool isSecure)
{
#if ENABLE_MANAGED_UNITYTLS
foreach (RelayServerEndpoint endpoint in endpoints)
{
if (endpoint.Secure && endpoint.Network == RelayServerEndpoint.NetworkOptions.Udp)
{
isSecure = true;
return NetworkEndPoint.Parse(endpoint.Host, (ushort)endpoint.Port);
}
}
#endif
isSecure = false;
return NetworkEndPoint.Parse(ip, (ushort)port);
}
/// <summary>
/// Shared behavior for binding to the Relay allocation, which is required for use.
/// Note that a host will send bytes from the Allocation it creates, whereas a client will send bytes from the JoinAllocation it receives using a relay code.
/// </summary>
protected void BindToAllocation(NetworkEndPoint serverEndpoint, byte[] allocationIdBytes, byte[] connectionDataBytes, byte[] hostConnectionDataBytes, byte[] hmacKeyBytes, int connectionCapacity, bool isSecure)
{
RelayAllocationId allocationId = ConvertAllocationIdBytes(allocationIdBytes);
RelayConnectionData connectionData = ConvertConnectionDataBytes(connectionDataBytes);
RelayConnectionData hostConnectionData = ConvertConnectionDataBytes(hostConnectionDataBytes);
RelayHMACKey key = ConvertHMACKeyBytes(hmacKeyBytes);
/// <summary>
/// Determine the server endpoint for connecting to the Relay server, for either an Allocation or a JoinAllocation.
/// If DTLS encryption is available, and there's a secure server endpoint available, use that as a secure connection. Otherwise, just connect to the Relay IP unsecured.
/// </summary>
public static NetworkEndPoint GetEndpointForAllocation(
List<RelayServerEndpoint> endpoints,
string ip,
int port,
out bool isSecure)
{
#if ENABLE_MANAGED_UNITYTLS
foreach (RelayServerEndpoint endpoint in endpoints)
{
if (endpoint.Secure && endpoint.Network == RelayServerEndpoint.NetworkOptions.Udp)
{
isSecure = true;
return NetworkEndPoint.Parse(endpoint.Host, (ushort) endpoint.Port);
}
}
#endif
isSecure = false;
return NetworkEndPoint.Parse(ip, (ushort) port);
}
var relayServerData = new RelayServerData(ref serverEndpoint, 0, ref allocationId, ref connectionData, ref hostConnectionData, ref key, isSecure);
relayServerData.ComputeNewNonce(); // For security, the nonce value sent when authenticating the allocation must be increased.
var networkSettings = new NetworkSettings();
/// <summary>
/// Shared behavior for binding to the Relay allocation, which is required for use.
/// Note that a host will send bytes from the Allocation it creates, whereas a client will send bytes from the JoinAllocation it receives using a relay code.
/// </summary>
protected void BindToAllocation(
NetworkEndPoint serverEndpoint,
byte[] allocationIdBytes,
byte[] connectionDataBytes,
byte[] hostConnectionDataBytes,
byte[] hmacKeyBytes,
int connectionCapacity,
bool isSecure)
{
RelayAllocationId allocationId = ConvertAllocationIdBytes(allocationIdBytes);
RelayConnectionData connectionData = ConvertConnectionDataBytes(connectionDataBytes);
RelayConnectionData hostConnectionData = ConvertConnectionDataBytes(hostConnectionDataBytes);
RelayHMACKey key = ConvertHMACKeyBytes(hmacKeyBytes);
m_networkDriver = NetworkDriver.Create(networkSettings.WithRelayParameters(ref relayServerData));
m_connections = new List<NetworkConnection>(connectionCapacity);
var relayServerData = new RelayServerData(ref serverEndpoint, 0, ref allocationId, ref connectionData,
ref hostConnectionData, ref key, isSecure);
relayServerData
.ComputeNewNonce(); // For security, the nonce value sent when authenticating the allocation must be increased.
var networkSettings = new NetworkSettings();
if (m_networkDriver.Bind(NetworkEndPoint.AnyIpv4) != 0)
Debug.LogError("Failed to bind to Relay allocation.");
else
StartCoroutine(WaitForBindComplete());
}
m_networkDriver = NetworkDriver.Create(networkSettings.WithRelayParameters(ref relayServerData));
m_connections = new List<NetworkConnection>(connectionCapacity);
private IEnumerator WaitForBindComplete()
{
while (!m_networkDriver.Bound)
{
m_networkDriver.ScheduleUpdate().Complete();
yield return null;
}
OnBindingComplete();
}
if (m_networkDriver.Bind(NetworkEndPoint.AnyIpv4) != 0)
Debug.LogError("Failed to bind to Relay allocation.");
else
StartCoroutine(WaitForBindComplete());
}
protected abstract void OnBindingComplete();
private IEnumerator WaitForBindComplete()
{
while (!m_networkDriver.Bound)
{
m_networkDriver.ScheduleUpdate().Complete();
yield return null;
}
#region UTP uses pointers instead of managed arrays for performance reasons, so we use these helper functions to convert them.
unsafe private static RelayAllocationId ConvertAllocationIdBytes(byte[] allocationIdBytes)
{
fixed (byte* ptr = allocationIdBytes)
{
return RelayAllocationId.FromBytePointer(ptr, allocationIdBytes.Length);
}
}
OnBindingComplete();
}
unsafe private static RelayConnectionData ConvertConnectionDataBytes(byte[] connectionData)
{
fixed (byte* ptr = connectionData)
{
return RelayConnectionData.FromBytePointer(ptr, RelayConnectionData.k_Length);
}
}
protected abstract void OnBindingComplete();
unsafe private static RelayHMACKey ConvertHMACKeyBytes(byte[] hmac)
{
fixed (byte* ptr = hmac)
{
return RelayHMACKey.FromBytePointer(ptr, RelayHMACKey.k_Length);
}
}
#endregion
#region UTP uses pointers instead of managed arrays for performance reasons, so we use these helper functions to convert them.
private void OnDestroy()
{
if (!m_isRelayConnected && m_networkDriver.IsCreated)
m_networkDriver.Dispose();
}
}
unsafe private static RelayAllocationId ConvertAllocationIdBytes(byte[] allocationIdBytes)
{
fixed (byte* ptr = allocationIdBytes)
{
return RelayAllocationId.FromBytePointer(ptr, allocationIdBytes.Length);
}
}
/// <summary>
/// Host logic: Request a new Allocation, and then both bind to it and request a join code. Once those are both complete, supply data back to the lobby.
/// </summary>
public class RelayUtpSetupHost : RelayUtpSetup
{
[Flags]
private enum JoinState { None = 0, Bound = 1, Joined = 2 }
private JoinState m_joinState = JoinState.None;
private Allocation m_allocation;
unsafe private static RelayConnectionData ConvertConnectionDataBytes(byte[] connectionData)
{
fixed (byte* ptr = connectionData)
{
return RelayConnectionData.FromBytePointer(ptr, RelayConnectionData.k_Length);
}
}
protected override void JoinRelay()
{
RelayAPIInterface.AllocateAsync(m_localLobby.MaxPlayerCount, OnAllocation);
}
unsafe private static RelayHMACKey ConvertHMACKeyBytes(byte[] hmac)
{
fixed (byte* ptr = hmac)
{
return RelayHMACKey.FromBytePointer(ptr, RelayHMACKey.k_Length);
}
}
private void OnAllocation(Allocation allocation)
{
m_allocation = allocation;
RelayAPIInterface.GetJoinCodeAsync(allocation.AllocationId, OnRelayCode);
bool isSecure = false;
m_endpointForServer = GetEndpointForAllocation(allocation.ServerEndpoints, allocation.RelayServer.IpV4, allocation.RelayServer.Port, out isSecure);
BindToAllocation(m_endpointForServer, allocation.AllocationIdBytes, allocation.ConnectionData, allocation.ConnectionData, allocation.Key, 16, isSecure);
}
#endregion
private void OnDestroy()
{
if (!m_isRelayConnected && m_networkDriver.IsCreated)
m_networkDriver.Dispose();
}
}
/// <summary>
/// Host logic: Request a new Allocation, and then both bind to it and request a join code. Once those are both complete, supply data back to the lobby.
/// </summary>
public class RelayUtpSetupHost : RelayUtpSetup
{
[Flags]
private enum JoinState
{
None = 0,
Bound = 1,
Joined = 2
}
private JoinState m_joinState = JoinState.None;
private Allocation m_allocation;
protected override void JoinRelay()
{
RelayAPIInterface.AllocateAsync(m_localLobby.MaxPlayerCount, OnAllocation);
}
private void OnAllocation(Allocation allocation)
{
m_allocation = allocation;
RelayAPIInterface.GetJoinCodeAsync(allocation.AllocationId, OnRelayCode);
bool isSecure = false;
m_endpointForServer = GetEndpointForAllocation(allocation.ServerEndpoints, allocation.RelayServer.IpV4,
allocation.RelayServer.Port, out isSecure);
BindToAllocation(m_endpointForServer, allocation.AllocationIdBytes, allocation.ConnectionData,
allocation.ConnectionData, allocation.Key, 16, isSecure);
}
private void OnRelayCode(string relayCode)
{
m_localLobby.RelayCode = relayCode;
m_localLobby.RelayServer = new ServerAddress(AddressFromEndpoint(m_endpointForServer), m_endpointForServer.Port);
m_joinState |= JoinState.Joined;
private void OnRelayCode(string relayCode)
{
m_localLobby.RelayCode = relayCode;
m_localLobby.RelayServer =
new ServerAddress(AddressFromEndpoint(m_endpointForServer), m_endpointForServer.Port);
m_joinState |= JoinState.Joined;
CheckForComplete();
CheckForComplete();
}
}
protected override void OnBindingComplete()
{
if (m_networkDriver.Listen() != 0)
{
Debug.LogError("RelayUtpSetupHost failed to bind to the Relay Allocation.");
m_onJoinComplete(false, null);
}
else
{
Debug.Log("Relay host is bound.");
m_joinState |= JoinState.Bound;
protected override void OnBindingComplete()
{
if (m_networkDriver.Listen() != 0)
{
Debug.LogError("RelayUtpSetupHost failed to bind to the Relay Allocation.");
m_onJoinComplete(false, null);
}
else
{
Debug.Log("Relay host is bound.");
m_joinState |= JoinState.Bound;
CheckForComplete();
CheckForComplete();
}
}
}
}
private async Task CheckForComplete()
{
if (m_joinState == (JoinState.Joined | JoinState.Bound) && this != null) // this will equal null (i.e. this component has been destroyed) if the host left the lobby during the Relay connection sequence.
{
m_isRelayConnected = true;
RelayUtpHost host = gameObject.AddComponent<RelayUtpHost>();
host.Initialize(m_networkDriver, m_connections, m_localUser, m_localLobby);
m_onJoinComplete(true, host);
await LobbyAsyncRequests.Instance.UpdatePlayerRelayInfoAsync(m_allocation.AllocationId.ToString(), m_localLobby.RelayCode);
}
}
}
private async Task CheckForComplete()
{
if (m_joinState == (JoinState.Joined | JoinState.Bound) && this != null
) // this will equal null (i.e. this component has been destroyed) if the host left the lobby during the Relay connection sequence.
{
m_isRelayConnected = true;
RelayUtpHost host = gameObject.AddComponent<RelayUtpHost>();
host.Initialize(m_networkDriver, m_connections, m_localUser, m_localLobby);
m_onJoinComplete(true, host);
var connectionInfo = $"{m_allocation.RelayServer.IpV4}:{m_allocation.RelayServer.Port}";
// await LobbyManager.Instance.UpdatePlayerRelayInfoAsync(m_allocation.AllocationId.ToString(), m_localLobby.RelayCode, connectionInfo);
}
}
}
/// <summary>
/// Client logic: Wait until the Relay join code is retrieved from the lobby's shared data. Then, use that code to get the Allocation to bind to, and
/// then create a connection to the host.
/// </summary>
public class RelayUtpSetupClient : RelayUtpSetup
{
private JoinAllocation m_allocation;
/// <summary>
/// Client logic: Wait until the Relay join code is retrieved from the lobby's shared data. Then, use that code to get the Allocation to bind to, and
/// then create a connection to the host.
/// </summary>
public class RelayUtpSetupClient : RelayUtpSetup
{
private JoinAllocation m_allocation;
protected override void JoinRelay()
{
m_localLobby.onChanged += OnLobbyChange;
}
protected override void JoinRelay()
{
m_localLobby.onChanged += OnLobbyChange;
}
private void OnLobbyChange(LocalLobby lobby)
{
if (m_localLobby.RelayCode != null)
{
RelayAPIInterface.JoinAsync(m_localLobby.RelayCode, OnJoin);
m_localLobby.onChanged -= OnLobbyChange;
}
}
private void OnLobbyChange(LocalLobby lobby)
{
if (m_localLobby.RelayCode != null)
{
RelayAPIInterface.JoinAsync(m_localLobby.RelayCode, OnJoin);
m_localLobby.onChanged -= OnLobbyChange;
}
}
private void OnJoin(JoinAllocation joinAllocation)
{
if (joinAllocation == null || this == null) // The returned JoinAllocation is null if allocation failed. this would be destroyed already if you quit the lobby while Relay is connecting.
return;
m_allocation = joinAllocation;
bool isSecure = false;
m_endpointForServer = GetEndpointForAllocation(joinAllocation.ServerEndpoints, joinAllocation.RelayServer.IpV4, joinAllocation.RelayServer.Port, out isSecure);
BindToAllocation(m_endpointForServer, joinAllocation.AllocationIdBytes, joinAllocation.ConnectionData, joinAllocation.HostConnectionData, joinAllocation.Key, 1, isSecure);
m_localLobby.RelayServer = new ServerAddress(AddressFromEndpoint(m_endpointForServer), m_endpointForServer.Port);
}
private void OnJoin(JoinAllocation joinAllocation)
{
if (joinAllocation == null || this == null
) // The returned JoinAllocation is null if allocation failed. this would be destroyed already if you quit the lobby while Relay is connecting.
return;
m_allocation = joinAllocation;
bool isSecure = false;
m_endpointForServer = GetEndpointForAllocation(joinAllocation.ServerEndpoints,
joinAllocation.RelayServer.IpV4, joinAllocation.RelayServer.Port, out isSecure);
BindToAllocation(m_endpointForServer, joinAllocation.AllocationIdBytes, joinAllocation.ConnectionData,
joinAllocation.HostConnectionData, joinAllocation.Key, 1, isSecure);
m_localLobby.RelayServer =
new ServerAddress(AddressFromEndpoint(m_endpointForServer), m_endpointForServer.Port);
}
protected override void OnBindingComplete()
{
protected override void OnBindingComplete()
{
ConnectToServer();
ConnectToServer();
}
}
private async Task ConnectToServer()
{
// Once the client is bound to the Relay server, send a connection request.
m_connections.Add(m_networkDriver.Connect(m_endpointForServer));
while (m_networkDriver.GetConnectionState(m_connections[0]) == NetworkConnection.State.Connecting)
{
m_networkDriver.ScheduleUpdate().Complete();
await Task.Delay(100);
}
if (m_networkDriver.GetConnectionState(m_connections[0]) != NetworkConnection.State.Connected)
{
Debug.LogError("RelayUtpSetupClient could not connect to the host.");
m_onJoinComplete(false, null);
}
else if (this != null)
{
m_isRelayConnected = true;
RelayUtpClient client = gameObject.AddComponent<RelayUtpClient>();
client.Initialize(m_networkDriver, m_connections, m_localUser, m_localLobby);
m_onJoinComplete(true, client);
await LobbyAsyncRequests.Instance.UpdatePlayerRelayInfoAsync(m_allocation.AllocationId.ToString(), m_localLobby.RelayCode);
}
}
}
private async Task ConnectToServer()
{
// Once the client is bound to the Relay server, send a connection request.
m_connections.Add(m_networkDriver.Connect(m_endpointForServer));
while (m_networkDriver.GetConnectionState(m_connections[0]) == NetworkConnection.State.Connecting)
{
m_networkDriver.ScheduleUpdate().Complete();
await Task.Delay(100);
}
if (m_networkDriver.GetConnectionState(m_connections[0]) != NetworkConnection.State.Connected)
{
Debug.LogError("RelayUtpSetupClient could not connect to the host.");
m_onJoinComplete(false, null);
}
else if (this != null)
{
m_isRelayConnected = true;
RelayUtpClient client = gameObject.AddComponent<RelayUtpClient>();
client.Initialize(m_networkDriver, m_connections, m_localUser, m_localLobby);
m_onJoinComplete(true, client);
var connectionInfo = $"{m_allocation.RelayServer.IpV4}:{m_allocation.RelayServer.Port}";
// await LobbyManager.Instance.UpdatePlayerRelayInfoAsync(m_allocation.AllocationId.ToString(), m_localLobby.RelayCode,connectionInfo);
}
}
}
}

8
Assets/Scripts/GameLobby/Tests/Editor/MessengerTests.cs


{
#region Test classes
/// <summary>Trivial message recipient that will run some action on any message.</summary>
private class Subscriber : IReceiveMessages
class Subscriber : IReceiveMessages
private Action m_thingToDo;
Action m_thingToDo;
public Subscriber(Action thingToDo) { m_thingToDo = thingToDo; }
public void OnReceiveMessage(MessageType type, object msg) { m_thingToDo?.Invoke(); }
}

{
Messenger messenger = new Messenger();
int msgCount = 0;
SubscriberArgs sub = new SubscriberArgs((type, msg) => {
SubscriberArgs sub = new SubscriberArgs((type, msg) => {
msgCount++; // These are just for simple detection of the intended behavior.
if (type == MessageType.RenameRequest) msgCount += 9;
if (msg is string) msgCount += int.Parse(msg as string);

Assert.AreEqual(1, msgCount, "Should have acted on the message.");
}
}
}
}

4
Assets/Scripts/GameLobby/Tests/Editor/ObserverTests.cs


}
// We just have a couple Observers that update some arbitrary member, in this case a string.
private class TestObserved : Observed<TestObserved>
class TestObserved : Observed<TestObserved>
{
string m_stringField;

}
}
private class TestObserverBehaviour : ObserverBehaviour<TestObserved>
class TestObserverBehaviour : ObserverBehaviour<TestObserved>
{
public string displayStringField;

45
Assets/Scripts/GameLobby/Tests/PlayMode/LobbyRoundtripTests.cs


using System.Linq;
using Test.Tools;
using LobbyRelaySample;
using Unity.Services.Core;
using Unity.Services.Authentication;
using UnityEngine;
using UnityEngine.TestTools;

/// </summary>
public class LobbyRoundtripTests
{
private string m_workingLobbyId;
private LobbyRelaySample.Auth.SubIdentity_Authentication m_auth;
private bool m_didSigninComplete = false;
private Dictionary<string, PlayerDataObject> m_mockUserData; // This is handled in the LobbyAsyncRequest calls normally, but we need to supply this for the direct Lobby API calls.
string m_workingLobbyId;
bool m_didSigninComplete = false;
string playerID;
Dictionary<string, PlayerDataObject> m_mockUserData; // This is handled in the LobbyAsyncRequest calls normally, but we need to supply this for the direct Lobby API calls.
LobbyManager m_LobbyManager;
public void Setup()
public IEnumerator Setup()
m_auth = new LobbyRelaySample.Auth.SubIdentity_Authentication("testProfile", () => { m_didSigninComplete = true; });
return TestAuthSetup();
}
IEnumerator TestAuthSetup()
{
yield return AsyncTestHelper.Await(async ()=> await UnityServices.InitializeAsync());
yield return AsyncTestHelper.Await(async () => await AuthenticationService.Instance.SignInAnonymouslyAsync());
m_didSigninComplete = true;
}
[UnityTearDown]

{ yield return AsyncTestHelper.Await(async ()=> await LobbyAsyncRequests.Instance.LeaveLobbyAsync(m_workingLobbyId));
{ yield return AsyncTestHelper.Await(async ()=> await m_LobbyManager.LeaveLobbyAsync(m_workingLobbyId));
[OneTimeTearDown]
public void Teardown()
{
m_auth?.Dispose();
}
/// <summary>
/// Make sure the entire roundtrip for Lobby works: Once signed in, create a lobby, query to make sure it exists, then delete it.

QueryResponse queryResponse = null;
Debug.Log("Getting Lobby List 1");
yield return AsyncTestHelper.Await(async () => queryResponse = await LobbyAsyncRequests.Instance.RetrieveLobbyListAsync());
yield return AsyncTestHelper.Await(async () => queryResponse = await m_LobbyManager.RetrieveLobbyListAsync());
Assert.IsNotNull(queryResponse, "QueryAllLobbiesAsync should return a non-null result. (#0)");

string lobbyName = "TestLobby-JustATest-123";
yield return AsyncTestHelper.Await(async () =>
createResponse = await LobbyAsyncRequests.Instance.CreateLobbyAsync(
createResponse = await m_LobbyManager.CreateLobbyAsync(
lobbyName,
100,
false,

yield return new WaitForSeconds(1); // To prevent a possible 429 with the upcoming Query request.
Debug.Log("Getting Lobby List 2");
yield return AsyncTestHelper.Await(async () => queryResponse = await LobbyAsyncRequests.Instance.RetrieveLobbyListAsync());
yield return AsyncTestHelper.Await(async () => queryResponse = await m_LobbyManager.RetrieveLobbyListAsync());
Assert.IsNotNull(queryResponse, "QueryAllLobbiesAsync should return a non-null result. (#1)");
Assert.AreEqual(1 + numLobbiesIni, queryResponse.Results.Count, "Queried lobbies list should contain the test lobby.");

Debug.Log("Getting current Lobby");
Lobby currentLobby = LobbyAsyncRequests.Instance.CurrentLobby;
Lobby currentLobby = null;
Assert.IsNotNull(currentLobby, "GetLobbyAsync should return a non-null result.");
Assert.AreEqual(lobbyName, currentLobby.Name, "Checking the lobby we got for name.");
Assert.AreEqual(m_workingLobbyId, currentLobby.Id, "Checking the lobby we got for ID.");

yield return AsyncTestHelper.Await(async ()=> await LobbyAsyncRequests.Instance.LeaveLobbyAsync(m_workingLobbyId));
yield return AsyncTestHelper.Await(async ()=> await m_LobbyManager.LeaveLobbyAsync(m_workingLobbyId));
yield return AsyncTestHelper.Await(async () => queryResponse = await LobbyAsyncRequests.Instance.RetrieveLobbyListAsync());
yield return AsyncTestHelper.Await(async () => queryResponse = await m_LobbyManager.RetrieveLobbyListAsync());
Assert.IsNotNull(queryResponse, "QueryAllLobbiesAsync should return a non-null result. (#2)");

LogAssert.ignoreFailingMessages = true; // Multiple errors will appears for the exception.
Lobby createLobby = null;
yield return AsyncTestHelper.Await(async () =>
createLobby = await LobbyAsyncRequests.Instance.CreateLobbyAsync(
createLobby = await m_LobbyManager.CreateLobbyAsync(
"lobby name",
123,
false,

122
Assets/Scripts/GameLobby/Tests/PlayMode/RelayRoundTripTests.cs


using System;
using System.Collections;
using LobbyRelaySample;
using NUnit.Framework;
using Test.Tools;
using Unity.Services.Relay;

namespace Test
{
/// <summary>
/// Accesses the Authentication and Relay services in order to ensure we can connect to Relay and retrieve a join code.
/// RelayUtp* wraps the Relay API, so go through that in practice. This simply ensures the connection to the Lobby service is functional.
///
/// If the tests pass, you can assume you are connecting to the Relay service itself properly.
/// </summary>
public class RelayRoundTripTests
{
private LobbyRelaySample.Auth.SubIdentity_Authentication m_auth;
private bool m_didSigninComplete = false;
/// <summary>
/// Accesses the Authentication and Relay services in order to ensure we can connect to Relay and retrieve a join code.
/// RelayUtp* wraps the Relay API, so go through that in practice. This simply ensures the connection to the Lobby service is functional.
///
/// If the tests pass, you can assume you are connecting to the Relay service itself properly.
/// </summary>
public class RelayRoundTripTests
{
bool m_DidSigninComplete = false;
[OneTimeSetUp]
public void Setup()
{
m_auth = new LobbyRelaySample.Auth.SubIdentity_Authentication("testProfile", () => { m_didSigninComplete = true; });
}
[OneTimeSetUp]
public void Setup()
{
Auth.Authenticate("testProfile");
}
[OneTimeTearDown]
public void Teardown()
{
m_auth?.Dispose();
}
/// <summary>
/// Create a Relay allocation, request a join code, and then join. Note that this is purely to ensure the service is functioning;
/// in practice, the RelayUtpSetup does more work to bind to the allocation and has slightly different logic for hosts vs. clients.
/// </summary>
[UnityTest]
public IEnumerator DoBaseRoundTrip()
{
if (!m_didSigninComplete)
yield return new WaitForSeconds(3);
if (!m_didSigninComplete)
Assert.Fail("Did not sign in.");
yield return new WaitForSeconds(1); // To prevent a possible 429 after a previous test.
/// <summary>
/// Create a Relay allocation, request a join code, and then join. Note that this is purely to ensure the service is functioning;
/// in practice, the RelayUtpSetup does more work to bind to the allocation and has slightly different logic for hosts vs. clients.
/// </summary>
[UnityTest]
public IEnumerator DoBaseRoundTrip()
{
yield return new WaitUntil(Auth.DoneAuthenticating);
// Allocation
float timeout = 5;
Allocation allocation = null;
yield return AsyncTestHelper.Await(async () => allocation = await Relay.Instance.CreateAllocationAsync(4));
// Allocation
Allocation allocation = null;
yield return AsyncTestHelper.Await(async () => allocation = await Relay.Instance.CreateAllocationAsync(4));
while (allocation == null && timeout > 0)
{
yield return new WaitForSeconds(0.25f);
timeout -= 0.25f;
}
Assert.Greater(timeout, 0, "Timeout Check (Allocate)");
Guid allocationId = allocation.AllocationId;
var allocationIP = allocation.RelayServer.IpV4;
var allocationPort = allocation.RelayServer.Port;
Assert.NotNull(allocationId);
Assert.NotNull(allocationIP);
Assert.NotNull(allocationPort);
Guid allocationId = allocation.AllocationId;
var allocationIP = allocation.RelayServer.IpV4;
var allocationPort = allocation.RelayServer.Port;
Assert.NotNull(allocationId);
Assert.NotNull(allocationIP);
Assert.NotNull(allocationPort);
// Join code retrieval
timeout = 5;
string joinCode = null;
yield return AsyncTestHelper.Await(async () => joinCode = await Relay.Instance.GetJoinCodeAsync(allocationId));
// Join code retrieval
string joinCode = null;
yield return AsyncTestHelper.Await(async () =>
joinCode = await Relay.Instance.GetJoinCodeAsync(allocationId));
while (joinCode == null && timeout > 0)
{
yield return new WaitForSeconds(0.25f);
timeout -= 0.25f;
}
Assert.Greater(timeout, 0, "Timeout Check (JoinCode)");
Assert.False(string.IsNullOrEmpty(joinCode));
Assert.False(string.IsNullOrEmpty(joinCode));
// Joining with the join code
timeout = 5;
JoinAllocation joinResponse = null;
yield return AsyncTestHelper.Await(async () => joinResponse = await Relay.Instance.JoinAllocationAsync(joinCode));
// Joining with the join code
JoinAllocation joinResponse = null;
yield return AsyncTestHelper.Await(async () =>
joinResponse = await Relay.Instance.JoinAllocationAsync(joinCode));
while (joinResponse == null && timeout > 0)
{
yield return new WaitForSeconds(0.25f);
timeout -= 0.25f;
}
Assert.Greater(timeout, 0, "Timeout Check (Join)");
var codeIp = joinResponse.RelayServer.IpV4;
var codePort = joinResponse.RelayServer.Port;
Assert.AreEqual(codeIp, allocationIP);
Assert.AreEqual(codePort, allocationPort);
}
}
var codeIp = joinResponse.RelayServer.IpV4;
var codePort = joinResponse.RelayServer.Port;
Assert.AreEqual(codeIp, allocationIP);
Assert.AreEqual(codePort, allocationPort);
}
}
}

3
Assets/Scripts/GameLobby/Tests/PlayMode/Tests.Play.asmdef


"Unity.Services.Lobbies",
"Unity.Services.Relay",
"Unity.Networking.Transport",
"Unity.Services.Core"
"Unity.Services.Core",
"Unity.Services.Authentication"
],
"includePlatforms": [],
"excludePlatforms": [],

7
Assets/Scripts/GameLobby/Tests/PlayMode/UtpTests.cs


using System;
using System.Collections;
using System.Threading.Tasks;
using LobbyRelaySample;
using LobbyRelaySample.relay;
using NUnit.Framework;
using Unity.Networking.Transport;

{
public class UtpTests
{
private class RelayUtpTest : RelayUtpSetupHost
class RelayUtpTest : RelayUtpSetupHost
{
public Action<NetworkEndPoint, bool> OnGetEndpoint { private get; set; }

}
}
private LobbyRelaySample.Auth.SubIdentity_Authentication m_auth;
GameObject m_dummy;
//Only used when testing DTLS
#pragma warning disable CS0414 // This is the "assigned but its value is never used" warning, which will otherwise appear when DTLS is unavailable.

public void Setup()
{
m_dummy = new GameObject();
m_auth = new LobbyRelaySample.Auth.SubIdentity_Authentication("TestProfile",() => { m_didSigninComplete = true; });
Auth.Authenticate("testProfile");
}
async Task InitServices()

[OneTimeTearDown]
public void Teardown()
{
m_auth?.Dispose();
GameObject.Destroy(m_dummy);
}

2
Assets/Scripts/GameLobby/UI/EmoteButtonUI.cs


public class EmoteButtonUI : MonoBehaviour
{
[SerializeField]
private EmoteType m_emoteType;
EmoteType m_emoteType;
public void SetPlayerEmote()
{

10
Assets/Scripts/GameLobby/UI/InLobbyUserUI.cs


[SerializeField]
TMP_Text m_StatusText;
[SerializeField]
Image m_EmoteImage;

public bool IsAssigned => UserId != null;
public string UserId { get; private set; }
private LobbyUserObserver m_observer;
public string UserId { get; set; }
LobbyUserObserver m_observer;
public void SetUser(LobbyUser myLobbyUser)
{

m_EmoteImage.sprite = EmoteIcon(observed.Emote);
m_HostIcon.enabled = observed.IsHost;
}
/// <summary>
/// EmoteType to Icon Sprite
/// m_EmoteIcon[0] = Smile

8
Assets/Scripts/GameLobby/UI/JoinMenuUI.cs


Locator.Get.Messenger.OnReceiveMessage(MessageType.QuickJoin, null);
}
private bool CanDisplay(LocalLobby lobby)
bool CanDisplay(LocalLobby lobby)
{
return lobby.Data.State == LobbyState.Lobby && !lobby.Private;
}

/// </summary>
private void AddNewLobbyButton(string lobbyCode, LocalLobby lobby)
void AddNewLobbyButton(string lobbyCode, LocalLobby lobby)
{
var lobbyButtonInstance = Instantiate(m_LobbyButtonPrefab, m_LobbyButtonParent);
lobbyButtonInstance.GetComponent<LocalLobbyObserver>().BeginObserving(lobby);

m_LocalLobby.Add(lobbyCode, lobby);
}
private void UpdateLobbyButton(string lobbyCode, LocalLobby lobby)
void UpdateLobbyButton(string lobbyCode, LocalLobby lobby)
private void RemoveLobbyButton(LocalLobby lobby)
void RemoveLobbyButton(LocalLobby lobby)
{
var lobbyID = lobby.LobbyID;
var lobbyButton = m_LobbyButtons[lobbyID];

12
Assets/Scripts/GameLobby/UI/LobbyUserVolumeUI.cs


public class LobbyUserVolumeUI : MonoBehaviour
{
[SerializeField]
private UIPanelBase m_volumeSliderContainer;
UIPanelBase m_volumeSliderContainer;
private UIPanelBase m_muteToggleContainer;
UIPanelBase m_muteToggleContainer;
private GameObject m_muteIcon;
GameObject m_muteIcon;
private GameObject m_micMuteIcon;
GameObject m_micMuteIcon;
private Slider m_volumeSlider;
Slider m_volumeSlider;
private Toggle m_muteToggle;
Toggle m_muteToggle;
/// <param name="shouldResetUi">
/// When the user is being added, we want the UI to reset to the default values.

54
Assets/Scripts/GameLobby/UI/RateLimitVisibility.cs


namespace LobbyRelaySample.UI
{
/// <summary>
/// Observes the Lobby request rate limits and changes the visibility of a UIPanelBase to suit.
/// E.g. the refresh button on the Join menu should be inactive after a refresh for long enough to avoid the lobby query rate limit.
/// </summary>
public class RateLimitVisibility : MonoBehaviour
{
[SerializeField]
UIPanelBase m_target;
[SerializeField]
float m_alphaWhenHidden = 0.5f;
[SerializeField]
LobbyAsyncRequests.RequestType m_requestType;
/// <summary>
/// Observes the Lobby request rate limits and changes the visibility of a UIPanelBase to suit.
/// E.g. the refresh button on the Join menu should be inactive after a refresh for long enough to avoid the lobby query rate limit.
/// </summary>
public class RateLimitVisibility : MonoBehaviour
{
[SerializeField] UIPanelBase m_target;
[SerializeField] float m_alphaWhenHidden = 0.5f;
[SerializeField] LobbyManager.RequestType m_requestType;
private void Start()
{
LobbyAsyncRequests.Instance.GetRateLimit(m_requestType).onCooldownChange += UpdateVisibility;
}
private void OnDestroy()
{
LobbyAsyncRequests.Instance.GetRateLimit(m_requestType).onCooldownChange -= UpdateVisibility;
}
void Start()
{
GameManager.Instance.LobbyManager.GetRateLimit(m_requestType).onCooldownChange += UpdateVisibility;
}
private void UpdateVisibility(bool isCoolingDown)
{
if (isCoolingDown)
m_target.Hide(m_alphaWhenHidden);
else
m_target.Show();
}
}
void OnDestroy()
{
GameManager.Instance.LobbyManager.GetRateLimit(m_requestType).onCooldownChange -= UpdateVisibility;
}
void UpdateVisibility(bool isCoolingDown)
{
if (isCoolingDown)
m_target.Hide(m_alphaWhenHidden);
else
m_target.Show();
}
}
}

2
Assets/Scripts/GameLobby/UI/ReadyCheckUI.cs


{
ChangeState(UserStatus.Lobby);
}
private void ChangeState(UserStatus status)
void ChangeState(UserStatus status)
{
Locator.Get.Messenger.OnReceiveMessage(MessageType.LobbyUserStatus, status);
}

6
Assets/Scripts/GameLobby/UI/ShowWhenLobbyStateUI.cs


namespace LobbyRelaySample.UI
{
/// <summary>
/// UI element that is displayed when the lobby is in a particular state (e.g. counting down, in-game).
/// <summary>
/// UI element that is displayed when the lobby is in a particular state (e.g. counting down, in-game).
private LobbyState m_ShowThisWhen;
LobbyState m_ShowThisWhen;
public override void ObservedUpdated(LocalLobby observed)
{

10
Assets/Scripts/GameLobby/UI/SpinnerUI.cs


/// </summary>
public class SpinnerUI : ObserverPanel<LobbyServiceData>
{
[SerializeField] private TMP_Text m_errorText;
[SerializeField] private UIPanelBase m_spinnerImage;
[SerializeField] private UIPanelBase m_noServerText;
[SerializeField] private UIPanelBase m_errorTextVisibility;
[SerializeField] TMP_Text m_errorText;
[SerializeField] UIPanelBase m_spinnerImage;
[SerializeField] UIPanelBase m_noServerText;
[SerializeField] UIPanelBase m_errorTextVisibility;
[SerializeField] private UIPanelBase m_raycastBlocker;
[SerializeField] UIPanelBase m_raycastBlocker;
public override void ObservedUpdated(LobbyServiceData observed)
{

3
Assets/Scripts/GameLobby/Vivox/VivoxSetup.cs


using System;
using System.Collections.Generic;
using Unity.Services.Authentication;
using Unity.Services.Vivox;
using VivoxUnity;

m_userHandlers = userHandlers;
VivoxService.Instance.Initialize();
Account account = new Account(Locator.Get.Identity.GetSubIdentity(Auth.IIdentityType.Auth).GetContent("id"));
Account account = new Account(AuthenticationService.Instance.PlayerId);
m_loginSession = VivoxService.Instance.Client.GetLoginSession(account);
string token = m_loginSession.GetLoginToken();

4
Packages/ParrelSync/Editor/ClonesManagerWindow.cs


}
[MenuItem("ParrelSync/Clones Manager", priority = 0)]
private static void InitWindow()
static void InitWindow()
{
ClonesManagerWindow window = (ClonesManagerWindow)EditorWindow.GetWindow(typeof(ClonesManagerWindow));
window.titleContent = new GUIContent("Clones Manager");

string argumentFilePath = Path.Combine(cloneProjectPath, ClonesManager.ArgumentFileName);
//Need to be careful with file reading/writing since it will effect the deletion of
//the clone project(The directory won't be fully deleted if there's still file inside being read or write).
//The argument file will be deleted first at the beginning of the project deletion process
//The argument file will be deleted first at the beginning of the project deletion process
//to prevent any further being read and write.
//Will need to take some extra cautious if want to change the design of how file editing is handled.
if (File.Exists(argumentFilePath))

8
Packages/ParrelSync/Editor/NonCore/OtherMenuItem.cs


public class OtherMenuItem
{
[MenuItem("ParrelSync/GitHub/View this project on GitHub", priority = 10)]
private static void OpenGitHub()
static void OpenGitHub()
private static void OpenFAQ()
static void OpenFAQ()
private static void OpenGitHubIssues()
static void OpenGitHubIssues()
}
}

6
Packages/packages-lock.json


"com.unity.modules.imgui": "1.0.0"
}
},
"com.veriorpies.parrelsync": {
"version": "file:ParrelSync",
"depth": 0,
"source": "embedded",
"dependencies": {}
},
"com.unity.modules.ai": {
"version": "1.0.0",
"depth": 0,

/Assets/Scripts/GameLobby/Lobby/LobbyUpdater.cs.meta → /Assets/Scripts/GameLobby/Lobby/LobbySynchronizer.cs.meta

/Assets/Scripts/GameLobby/Lobby/LobbyUpdater.cs → /Assets/Scripts/GameLobby/Lobby/LobbySynchronizer.cs

正在加载...
取消
保存