浏览代码

Switched LobbyAsyncRequests and its implemenations over to async Tasks

Started Deprecating/merging the LobbyAPIInterface into LobbyAsyncRequests
Converted DoRequest unit tests.
/main/staging/2021_Upgrade
当前提交
31068570
共有 12 个文件被更改,包括 311 次插入350 次删除
  1. 87
      Assets/Scripts/GameLobby/Game/GameManager.cs
  2. 21
      Assets/Scripts/GameLobby/Lobby/LobbyAPIInterface.cs
  3. 302
      Assets/Scripts/GameLobby/Lobby/LobbyAsyncRequests.cs
  4. 8
      Assets/Scripts/GameLobby/Lobby/LobbyContentUpdater.cs
  5. 19
      Assets/Scripts/GameLobby/Relay/RelayUtpHost.cs
  6. 19
      Assets/Scripts/GameLobby/Relay/RelayUtpSetup.cs
  7. 74
      Assets/Scripts/GameLobby/Tests/PlayMode/LobbyRoundtripTests.cs
  8. 2
      Assets/Scripts/GameLobby/Tests/PlayMode/AsyncTestHelper.cs.meta
  9. 8
      Assets/Scripts/GameLobby/UI/RateLimitVisibility.cs
  10. 27
      Assets/Scripts/GameLobby/Tests/PlayMode/AsyncTestHelper.cs
  11. 94
      Assets/Scripts/GameLobby/Tests/PlayMode/AsyncLobbyTest.cs
  12. 0
      /Assets/Scripts/GameLobby/Tests/PlayMode/AsyncTestHelper.cs.meta

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


using System;
using System.Collections;
using System.Collections.Generic;
using LobbyRelaySample.lobby;
using UnityEngine;
using UnityEngine.Serialization;

/// When looking for the interactions, look up the MessageType and search for it in the code to see where it is used outside this script.
/// EG. Locator.Get.Messenger.OnReceiveMessage(MessageType.RenameRequest, name);
/// </summary>
public void OnReceiveMessage(MessageType type, object msg)
public async void OnReceiveMessage(MessageType type, object msg)
LobbyAsyncRequests.Instance.CreateLobbyAsync(createLobbyData.LobbyName, createLobbyData.MaxPlayerCount, createLobbyData.Private, m_localUser, (r) =>
{
lobby.LobbyConverters.RemoteToLocal(r, m_localLobby);
OnCreatedLobby();
},
OnFailedJoin);
var lobby = await LobbyAsyncRequests.Instance.CreateLobbyAsync(
createLobbyData.LobbyName,
createLobbyData.MaxPlayerCount,
createLobbyData.Private, m_localUser);
if (lobby != null)
{
LobbyConverters.RemoteToLocal(lobby, m_localLobby);
OnCreatedLobby();
}
else
{
OnFailedJoin();
}
LobbyAsyncRequests.Instance.JoinLobbyAsync(lobbyInfo.LobbyID, lobbyInfo.LobbyCode, m_localUser, (r) =>
{
lobby.LobbyConverters.RemoteToLocal(r, m_localLobby);
OnJoinedLobby();
},
OnFailedJoin);
var lobby = await LobbyAsyncRequests.Instance.JoinLobbyAsync(lobbyInfo.LobbyID, lobbyInfo.LobbyCode,
m_localUser);
if (lobby != null)
{
LobbyConverters.RemoteToLocal(lobby, m_localLobby);
OnJoinedLobby();
}
else
{
OnFailedJoin();
}
LobbyAsyncRequests.Instance.RetrieveLobbyListAsync(
qr =>
{
if (qr != null)
OnLobbiesQueried(lobby.LobbyConverters.QueryToLocalList(qr));
},
er =>
{
OnLobbyQueryFailed();
},
m_lobbyColorFilter);
var qr = await LobbyAsyncRequests.Instance.RetrieveLobbyListAsync(m_lobbyColorFilter);
if (qr != null)
OnLobbiesQueried(LobbyConverters.QueryToLocalList(qr));
else
OnLobbyQueryFailed();
LobbyAsyncRequests.Instance.QuickJoinLobbyAsync(m_localUser, m_lobbyColorFilter, (r) =>
{
lobby.LobbyConverters.RemoteToLocal(r, m_localLobby);
OnJoinedLobby();
},
OnFailedJoin);
var lobby = await LobbyAsyncRequests.Instance.QuickJoinLobbyAsync(m_localUser, m_lobbyColorFilter);
if (lobby != null)
{
LobbyConverters.RemoteToLocal(lobby, m_localLobby);
OnJoinedLobby();
}
else
{
OnFailedJoin();
}
}
else if (type == MessageType.RenameRequest)
{

}
}
private void OnLeftLobby()
private async void OnLeftLobby()
LobbyAsyncRequests.Instance.LeaveLobbyAsync(m_localLobby.LobbyID, ResetLocalLobby);
#pragma warning disable 4014
LobbyAsyncRequests.Instance.LeaveLobbyAsync(m_localLobby.LobbyID);
#pragma warning restore 4014
ResetLocalLobby();
m_LobbyContentUpdater.EndTracking();
m_vivoxSetup.LeaveLobbyChannel();

Locator.Get.Messenger.Unsubscribe(this);
if (!string.IsNullOrEmpty(m_localLobby?.LobbyID))
{
LobbyAsyncRequests.Instance.LeaveLobbyAsync(m_localLobby?.LobbyID, null);
#pragma warning disable 4014
LobbyAsyncRequests.Instance.LeaveLobbyAsync(m_localLobby?.LobbyID);
#pragma warning restore 4014
m_localLobby = null;
}
}

21
Assets/Scripts/GameLobby/Lobby/LobbyAPIInterface.cs


using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Unity.Services.Lobbies;
using Unity.Services.Lobbies.Models;

/// </summary>
public static class LobbyAPIInterface
{
private const int k_maxLobbiesToShow = 16; // If more are necessary, consider retrieving paginated results or using filters.
public static void CreateLobbyAsync(string requesterUASId, string lobbyName, int maxPlayers, bool isPrivate, Dictionary<string, PlayerDataObject> localUserData, Action<Lobby> onComplete)
{

var task = LobbyService.Instance.RemovePlayerAsync(lobbyId, requesterUASId);
AsyncRequestLobby.Instance.DoRequest(task, onComplete);
}
public static void QueryAllLobbiesAsync(List<QueryFilter> filters, Action<QueryResponse> onComplete)
{
QueryLobbiesOptions queryOptions = new QueryLobbiesOptions
{
Count = k_maxLobbiesToShow,
Filters = filters
};
var task = LobbyService.Instance.QueryLobbiesAsync(queryOptions);
AsyncRequestLobby.Instance.DoRequest(task, onComplete);
}
public static void GetLobbyAsync(string lobbyId, Action<Lobby> onComplete)
{
var task = LobbyService.Instance.GetLobbyAsync(lobbyId);
AsyncRequestLobby.Instance.DoRequest(task, onComplete);
}
/// <summary>
/// Uupdates custom data to the lobby, for all to see.
/// </summary>

302
Assets/Scripts/GameLobby/Lobby/LobbyAsyncRequests.cs


using LobbyRelaySample.lobby;
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Threading.Tasks;
using JetBrains.Annotations;
using Unity.Services.Authentication;
using Unity.Services.Lobbies;
using Unity.Services.Lobbies.Models;

/// An abstraction layer between the direct calls into the Lobby API and the outcomes you actually want. E.g. you can request to get a readable list of
/// current lobbies and not need to make the query call directly.
/// </summary>
public class LobbyAsyncRequests
public class LobbyAsyncRequests : IDisposable
private const int k_maxLobbiesToShow = 16; // If more are necessary, consider retrieving paginated results or using filters.
public static LobbyAsyncRequests Instance
{

//Once connected to a lobby, cache the local lobby object so we don't query for it for every lobby operation.
// (This assumes that the player will be actively in just one lobby at a time, though they could passively be in more.)
Lobby m_RemoteLobby;
/// <summary>
/// Store the LobbySubscription so we can unsubscribe later.
/// </summary>

return data;
}
//TODO Back to Polling i Guess
void BeginListening(string lobbyID)
{
m_lobbyEvents = new LobbyEventCallbacks();

/// <summary>
/// Attempt to create a new lobby and then join it.
/// </summary>
public void CreateLobbyAsync(string lobbyName, int maxPlayers, bool isPrivate, LobbyUser localUser, Action<Lobby> onSuccess, Action onFailure)
public async Task<Lobby> CreateLobbyAsync(string lobbyName, int maxPlayers, bool isPrivate, LobbyUser localUser)
if (!m_rateLimitHost.CanCall())
if (m_rateLimitHost.IsInCooldown)
onFailure?.Invoke();
return;
return null;
LobbyAPIInterface.CreateLobbyAsync(uasId, lobbyName, maxPlayers, isPrivate, CreateInitialPlayerData(localUser), OnLobbyCreated);
void OnLobbyCreated(Lobby response)
CreateLobbyOptions createOptions = new CreateLobbyOptions
if (response == null)
onFailure?.Invoke();
else
{
JoinLobby(response);
onSuccess?.Invoke(response); // The Create request automatically joins the lobby, so we need not take further action.
}
}
IsPrivate = isPrivate,
Player = new Player(id: uasId, data: CreateInitialPlayerData(localUser))
};
var lobby = await LobbyService.Instance.CreateLobbyAsync(lobbyName, maxPlayers, createOptions);
JoinLobby(lobby);
return lobby;
public void JoinLobbyAsync(string lobbyId, string lobbyCode, LobbyUser localUser, Action<Lobby> onSuccess, Action onFailure)
public async Task<Lobby> JoinLobbyAsync(string lobbyId, string lobbyCode, LobbyUser localUser)
if (!m_rateLimitJoin.CanCall() ||
if (m_rateLimitJoin.IsInCooldown ||
onFailure?.Invoke();
UnityEngine.Debug.LogWarning("Join Lobby hit the rate limit.");
return;
return null;
Lobby joinedLobby = null;
var playerData = CreateInitialPlayerData(localUser);
LobbyAPIInterface.JoinLobbyAsync_ById(uasId, lobbyId, CreateInitialPlayerData(localUser), OnLobbyJoined);
{
JoinLobbyByIdOptions joinOptions = new JoinLobbyByIdOptions { Player = new Player(id: uasId, data: playerData) };
joinedLobby = await LobbyService.Instance.JoinLobbyByIdAsync(lobbyId, joinOptions);
}
LobbyAPIInterface.JoinLobbyAsync_ByCode(uasId, lobbyCode, CreateInitialPlayerData(localUser), OnLobbyJoined);
void OnLobbyJoined(Lobby response)
if (response == null)
onFailure?.Invoke();
else
{
JoinLobby(response);
onSuccess?.Invoke(response);
}
JoinLobbyByCodeOptions joinOptions = new JoinLobbyByCodeOptions { Player = new Player(id: uasId, data: playerData) };
joinedLobby = await LobbyService.Instance.JoinLobbyByCodeAsync(lobbyCode, joinOptions);
JoinLobby(joinedLobby);
return joinedLobby;
public void QuickJoinLobbyAsync(LobbyUser localUser, LobbyColor limitToColor = LobbyColor.None, Action<Lobby> onSuccess = null, Action onFailure = null)
public async Task<Lobby> QuickJoinLobbyAsync(LobbyUser localUser, LobbyColor limitToColor = LobbyColor.None)
if (!m_rateLimitQuickJoin.CanCall())
if (m_rateLimitQuickJoin.IsInCooldown)
onFailure?.Invoke();
return;
return null;
LobbyAPIInterface.QuickJoinLobbyAsync(uasId, filters, CreateInitialPlayerData(localUser), OnLobbyJoined);
void OnLobbyJoined(Lobby response)
var joinRequest = new QuickJoinLobbyOptions
if (response == null)
onFailure?.Invoke();
else
{
JoinLobby(response);
onSuccess?.Invoke(response);
}
}
Filter = filters,
Player = new Player(id: uasId, data: CreateInitialPlayerData(localUser))
};
var lobby = await LobbyService.Instance.QuickJoinLobbyAsync(joinRequest);
JoinLobby(lobby);
return lobby;
}
void JoinLobby(Lobby response)

/// Used for getting the list of all active lobbies, without needing full info for each.
/// </summary>
/// <param name="onListRetrieved">If called with null, retrieval was unsuccessful. Else, this will be given a list of contents to display, as pairs of a lobby code and a display string for that lobby.</param>
public void RetrieveLobbyListAsync(Action<QueryResponse> onListRetrieved, Action<QueryResponse> onError = null, LobbyColor limitToColor = LobbyColor.None)
public async Task<QueryResponse> RetrieveLobbyListAsync(LobbyColor limitToColor = LobbyColor.None)
Debug.Log("Retrieving Lobby List");
if (!m_rateLimitQuery.CanCall())
{
onListRetrieved?.Invoke(null);
m_rateLimitQuery.EnqueuePendingOperation(() => { RetrieveLobbyListAsync(onListRetrieved, onError, limitToColor); });
UnityEngine.Debug.LogWarning("Retrieve Lobby list hit the rate limit. Will try again soon...");
return;
}
if (!await m_rateLimitQuery.WaitInQueueUntilTaskIsFirst(
RetrieveLobbyListAsync(limitToColor)))
return null;
Debug.Log("Retrieving Lobby List");
LobbyAPIInterface.QueryAllLobbiesAsync(filters, OnLobbyListRetrieved);
void OnLobbyListRetrieved(QueryResponse response)
QueryLobbiesOptions queryOptions = new QueryLobbiesOptions
if (response != null)
onListRetrieved?.Invoke(response);
else
onError?.Invoke(response);
}
Count = k_maxLobbiesToShow,
Filters = filters
};
return await LobbyService.Instance.QueryLobbiesAsync(queryOptions);
}
private List<QueryFilter> LobbyColorToFilters(LobbyColor limitToColor)

/// Attempt to leave a lobby, and then delete it if no players remain.
/// </summary>
/// <param name="onComplete">Called once the request completes, regardless of success or failure.</param>
public void LeaveLobbyAsync(string lobbyId, Action onComplete)
public async Task LeaveLobbyAsync(string lobbyId)
LobbyAPIInterface.LeaveLobbyAsync(uasId, lobbyId, OnLeftLobby);
void OnLeftLobby()
{
onComplete?.Invoke();
m_RemoteLobby = null;
EndListening();
// Lobbies will automatically delete the lobby if unoccupied, so we don't need to take further action.
}
await LobbyService.Instance.RemovePlayerAsync(lobbyId, uasId);
m_RemoteLobby = null;
EndListening();
// Lobbies will automatically delete the lobby if unoccupied, so we don't need to take further action.
public void UpdatePlayerDataAsync(Dictionary<string, string> data, Action onComplete)
public async Task UpdatePlayerDataAsync(Dictionary<string, string> data)
if (!ShouldUpdateData(() => { UpdatePlayerDataAsync(data, onComplete); }, onComplete, false))
return;
if (!await m_rateLimitQuery.WaitInQueueUntilTaskIsFirst(
UpdatePlayerDataAsync(data)))
return;
string playerId = Locator.Get.Identity.GetSubIdentity(Auth.IIdentityType.Auth).GetContent("id");
Dictionary<string, PlayerDataObject> dataCurr = new Dictionary<string, PlayerDataObject>();
foreach (var dataNew in data)

dataCurr.Add(dataNew.Key, dataObj);
}
LobbyAPIInterface.UpdatePlayerAsync(m_RemoteLobby.Id, playerId, dataCurr, (result) =>
UpdatePlayerOptions updateOptions = new UpdatePlayerOptions
onComplete?.Invoke();
}, null, null);
Data = dataCurr,
AllocationId = null,
ConnectionInfo = null
};
await LobbyService.Instance.UpdatePlayerAsync(m_RemoteLobby.Id, playerId, updateOptions);
public void UpdatePlayerRelayInfoAsync(string allocationId, string connectionInfo, Action onComplete)
public async Task UpdatePlayerRelayInfoAsync(string allocationId, string connectionInfo)
if (!ShouldUpdateData(() => { UpdatePlayerRelayInfoAsync(allocationId, connectionInfo, onComplete); }, onComplete, true)) // Do retry here since the RelayUtpSetup that called this might be destroyed right after this.
if (!await m_rateLimitQuery.WaitInQueueUntilTaskIsFirst(
UpdatePlayerRelayInfoAsync(allocationId, connectionInfo)))
await AwaitRemoteLobby();
LobbyAPIInterface.UpdatePlayerAsync(m_RemoteLobby.Id, playerId, new Dictionary<string, PlayerDataObject>(), (r) => { onComplete?.Invoke(); }, allocationId, connectionInfo);
UpdatePlayerOptions updateOptions = new UpdatePlayerOptions
{
Data = new Dictionary<string, PlayerDataObject>(),
AllocationId = allocationId,
ConnectionInfo = connectionInfo
};
await LobbyService.Instance.UpdatePlayerAsync(m_RemoteLobby.Id, playerId, updateOptions);
public void UpdateLobbyDataAsync(Dictionary<string, string> data, Action onComplete)
public async Task UpdateLobbyDataAsync(Dictionary<string, string> data)
if (!ShouldUpdateData(() => { UpdateLobbyDataAsync(data, onComplete); }, onComplete, false))
if (await m_rateLimitQuery.WaitInQueueUntilTaskIsFirst(UpdateLobbyDataAsync(data)))
return;
Dictionary<string, DataObject> dataCurr = m_RemoteLobby.Data ?? new Dictionary<string, DataObject>();

shouldLock = lobbyState != LobbyState.Lobby;
}
}
LobbyAPIInterface.UpdateLobbyAsync(m_RemoteLobby.Id, dataCurr, shouldLock, (result) =>
{
if (result != null)
m_RemoteLobby = result;
onComplete?.Invoke();
});
UpdateLobbyOptions updateOptions = new UpdateLobbyOptions { Data = dataCurr, IsLocked = shouldLock };
var result = await LobbyService.Instance.UpdateLobbyAsync(m_RemoteLobby.Id, updateOptions);
if (result != null)
m_RemoteLobby = result;
/// <summary>
/// If we are in the middle of another operation, hold onto any pending ones until after that.
/// If we aren't in a lobby yet, leave it to the caller to decide what to do, since some callers might need to retry and others might not.
/// </summary>
private bool ShouldUpdateData(Action caller, Action onComplete, bool shouldRetryIfLobbyNull)
{
if (m_rateLimitQuery.IsInCooldown)
{
m_rateLimitQuery.EnqueuePendingOperation(caller);
return false;
}
if (m_RemoteLobby == null)
{
if (shouldRetryIfLobbyNull)
m_rateLimitQuery.EnqueuePendingOperation(caller);
onComplete?.Invoke();
return false;
}
return true;
}
private float m_heartbeatTime = 0;
private const float k_heartbeatPeriod = 8; // The heartbeat must be rate-limited to 5 calls per 30 seconds. We'll aim for longer in case periods don't align.

}
}
public class RateLimitCooldown : Observed<RateLimitCooldown>
async Task AwaitRemoteLobby()
private float m_timeSinceLastCall = float.MaxValue;
private readonly float m_cooldownTime;
private Queue<Action> m_pendingOperations = new Queue<Action>();
while (m_RemoteLobby == null)
await Task.Delay(100);
}
public void EnqueuePendingOperation(Action action)
{
//We probably dont want many of the same actions added to fire off multiple times.
if (!m_pendingOperations.Contains(action))
return;
public void Dispose()
{
m_pendingOperations.Enqueue(action);
}
}
public class RateLimitCooldown
{
public Action<bool> onCooldownChange;
public readonly float m_CooldownSeconds;
public readonly int m_CoolDownMS;
Queue<Task> m_TaskQueue = new Queue<Task>();
Task m_DequeuedTask;
private bool m_isInCooldown = false;
private bool m_IsInCooldown = false;
get => m_isInCooldown;
get => m_IsInCooldown;
if (m_isInCooldown != value)
if (m_IsInCooldown != value)
m_isInCooldown = value;
OnChanged(this);
m_IsInCooldown = value;
onCooldownChange?.Invoke(m_IsInCooldown);
public RateLimitCooldown(float cooldownTime)
public RateLimitCooldown(float cooldownSeconds)
m_cooldownTime = cooldownTime;
m_CooldownSeconds = cooldownSeconds;
m_CoolDownMS = Mathf.FloorToInt(m_CooldownSeconds * 1000);
public bool CanCall()
public async Task<bool> WaitInQueueUntilTaskIsFirst(Task newTask)
if (m_timeSinceLastCall < m_cooldownTime)
//Task is already in the queue
if (m_TaskQueue.Contains(newTask))
else
Debug.Log($"Enqueing Task: {newTask.Id} - {newTask.Status}");
//No Queue!
if (CanCall())
return true;
Debug.LogWarning("Hit the rate limit. Queueing...");
m_TaskQueue.Enqueue(newTask);
while (m_IsInCooldown)
Locator.Get.UpdateSlow.Subscribe(OnUpdate, m_cooldownTime);
m_timeSinceLastCall = 0;
IsInCooldown = true;
return true;
if (m_DequeuedTask == newTask)
break;
await Task.Delay(100);
//Got to the part of the queue that was the Task'
return true;
private void OnUpdate(float dt)
bool CanCall()
m_timeSinceLastCall += dt;
if (m_timeSinceLastCall >= m_cooldownTime)
if (!IsInCooldown)
IsInCooldown = false;
if (!m_isInCooldown) // It's possible that by setting IsInCooldown, something called CanCall immediately, in which case we want to stay on UpdateSlow.
{
Locator.Get.UpdateSlow.Unsubscribe(OnUpdate); // Note that this is after IsInCooldown is set, to prevent an Observer from kicking off CanCall again immediately.
int numPending = m_pendingOperations.Count; // It's possible a pending operation will re-enqueue itself or new operations, which should wait until the next loop.
for (; numPending > 0; numPending--)
m_pendingOperations.Dequeue()?.Invoke(); // Note: If this ends up enqueuing many operations, we might need to batch them and/or ensure they don't all execute at once.
}
#pragma warning disable 4014
CoolDownAsync();
#pragma warning restore 4014
return true;
else return false;
public override void CopyObserved(RateLimitCooldown oldObserved)
async Task CoolDownAsync()
/* This behavior isn't needed; we're just here for the OnChanged event management. */
if (m_IsInCooldown)
return;
IsInCooldown = true;
await Task.Delay(m_CoolDownMS);
if (m_TaskQueue.Count > 0)
{
m_DequeuedTask = m_TaskQueue.Dequeue();
await CoolDownAsync();
}
IsInCooldown = false;
}
}

8
Assets/Scripts/GameLobby/Lobby/LobbyContentUpdater.cs


void DoLobbyDataPush()
{
LobbyAsyncRequests.Instance.UpdateLobbyDataAsync(LobbyConverters.LocalToRemoteData(m_LocalLobby), null);
#pragma warning disable 4014
LobbyAsyncRequests.Instance.UpdateLobbyDataAsync(LobbyConverters.LocalToRemoteData(m_LocalLobby));
#pragma warning restore 4014
LobbyAsyncRequests.Instance.UpdatePlayerDataAsync(LobbyConverters.LocalToRemoteUserData(m_LocalUser), null);
#pragma warning disable 4014
LobbyAsyncRequests.Instance.UpdatePlayerDataAsync(LobbyConverters.LocalToRemoteUserData(m_LocalUser));
#pragma warning restore 4014
}
}

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


using System.Collections.Generic;
using System.Threading.Tasks;
using Unity.Networking.Transport;
namespace LobbyRelaySample.relay

UnityEngine.Debug.LogWarning("Disconnecting a client due to a disconnect message.");
conn.Disconnect(m_networkDriver);
m_connections.Remove(conn);
LobbyAsyncRequests.Instance.GetRateLimit(LobbyAsyncRequests.RequestType.Query).EnqueuePendingOperation(WaitToCheckForUsers);
return;
#pragma warning disable 4014
var queryCooldownMilliseconds = LobbyAsyncRequests.Instance.GetRateLimit(LobbyAsyncRequests.RequestType.Query)
.m_CoolDownMS;
void WaitToCheckForUsers()
{ LobbyAsyncRequests.Instance.GetRateLimit(LobbyAsyncRequests.RequestType.Query).EnqueuePendingOperation(CheckIfAllUsersReady);
}
WaitAndCheckUsers(queryCooldownMilliseconds*2);
#pragma warning restore 4014
return;
}
async Task WaitAndCheckUsers(int milliSeconds)
{
await Task.Delay(milliSeconds);
CheckIfAllUsersReady();
}
protected override void ProcessDisconnectEvent(NetworkConnection conn, DataStreamReader strm)

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


using System;
using System.Collections;
using System.Collections.Generic;
using System.Threading.Tasks;
using Unity.Networking.Transport;
using Unity.Networking.Transport.Relay;
using Unity.Services.Relay.Models;

m_localLobby.RelayCode = relayCode;
m_localLobby.RelayServer = new ServerAddress(AddressFromEndpoint(m_endpointForServer), m_endpointForServer.Port);
m_joinState |= JoinState.Joined;
#pragma warning disable 4014
#pragma warning restore 4014
}
protected override void OnBindingComplete()

{
Debug.Log("Relay host is bound.");
m_joinState |= JoinState.Bound;
#pragma warning disable 4014
#pragma warning restore 4014
private void 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_onJoinComplete(true, host);
LobbyAsyncRequests.Instance.UpdatePlayerRelayInfoAsync(m_allocation.AllocationId.ToString(), m_localLobby.RelayCode, null);
await LobbyAsyncRequests.Instance.UpdatePlayerRelayInfoAsync(m_allocation.AllocationId.ToString(), m_localLobby.RelayCode);
}
}
}

protected override void OnBindingComplete()
{
StartCoroutine(ConnectToServer());
#pragma warning disable 4014
ConnectToServer();
#pragma warning restore 4014
private IEnumerator 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));

yield return null;
await Task.Delay(100);
}
if (m_networkDriver.GetConnectionState(m_connections[0]) != NetworkConnection.State.Connected)
{

RelayUtpClient client = gameObject.AddComponent<RelayUtpClient>();
client.Initialize(m_networkDriver, m_connections, m_localUser, m_localLobby);
m_onJoinComplete(true, client);
LobbyAsyncRequests.Instance.UpdatePlayerRelayInfoAsync(m_allocation.AllocationId.ToString(), m_localLobby.RelayCode, null);
await LobbyAsyncRequests.Instance.UpdatePlayerRelayInfoAsync(m_allocation.AllocationId.ToString(), m_localLobby.RelayCode);
}
}
}

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


using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Text.RegularExpressions;
using Test.Tools;
using System.Threading.Tasks;
using LobbyRelaySample;
using Unity.Services.Lobbies;
using Unity.Services.Lobbies.Models;
using UnityEngine;

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.
[OneTimeSetUp]
public void Setup()
{

public IEnumerator DoRoundtrip()
{
#region Setup
// Wait a reasonable amount of time for sign-in to complete.
if (!m_didSigninComplete)
yield return new WaitForSeconds(3);

// Since we're signed in through the same pathway as the actual game, the list of lobbies will include any that have been made in the game itself, so we should account for those.
// If you want to get around this, consider having a secondary project using the same assets with its own credentials.
yield return new WaitForSeconds(1); // To prevent a possible 429 with the upcoming Query request, in case a previous test had one; Query requests can only occur at a rate of 1 per second.
yield return
new WaitForSeconds(
1); // To prevent a possible 429 with the upcoming Query request, in case a previous test had one; Query requests can only occur at a rate of 1 per second.
float timeout = 5;
Debug.Log("Getting Lobby List 1");
float timeout = 5;
LobbyAPIInterface.QueryAllLobbiesAsync(new List<QueryFilter>(), (qr) => { queryResponse = qr; });
while (queryResponse == null && timeout > 0)
{ yield return new WaitForSeconds(0.25f);
timeout -= 0.25f;
}
yield return AsyncTestHelper.Await(async () => queryResponse = await LobbyAsyncRequests.Instance.RetrieveLobbyListAsync());
Debug.Log("Got Lobby List 1");
#endregion
// Create a test lobby.

LobbyAPIInterface.CreateLobbyAsync(m_auth.GetContent("id"), lobbyName, 100, false, m_mockUserData, (r) => { createResponse = r; });
LobbyAPIInterface.CreateLobbyAsync(m_auth.GetContent("id"), lobbyName, 100, false, m_mockUserData,
(r) => { createResponse = r; });
{ yield return new WaitForSeconds(0.25f);
{
yield return new WaitForSeconds(0.25f);
Assert.Greater(timeout, 0, "Timeout check (create)");
Assert.IsNotNull(createResponse, "CreateLobbyAsync should return a non-null result.");
m_workingLobbyId = createResponse.Id;

yield return new WaitForSeconds(1); // To prevent a possible 429 with the upcoming Query request.
queryResponse = null;
timeout = 5;
LobbyAPIInterface.QueryAllLobbiesAsync(new List<QueryFilter>(), (qr) => { queryResponse = qr; });
while (queryResponse == null && timeout > 0)
{ yield return new WaitForSeconds(0.25f);
timeout -= 0.25f;
}
Debug.Log("Getting Lobby List 2");
yield return AsyncTestHelper.Await(async () => queryResponse = await LobbyAsyncRequests.Instance.RetrieveLobbyListAsync());
Debug.Log("Got Lobby List 2");
Assert.Greater(timeout, 0, "Timeout check (query #1)");
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.");

// Query for solely the test lobby via GetLobby.
Lobby getResponse = null;
LobbyAPIInterface.GetLobbyAsync(createResponse.Id, (r) => { getResponse = r; });
while (getResponse == null && timeout > 0)
{ yield return new WaitForSeconds(0.25f);
timeout -= 0.25f;
}
Debug.Log("Getting Lobby");
Lobby lobby = null;
yield return AsyncTestHelper.Await(async ()=> lobby = await LobbyService.Instance.GetLobbyAsync(createResponse.Id));
Debug.Log("Got Lobby");
Assert.IsNotNull(getResponse, "GetLobbyAsync should return a non-null result.");
Assert.AreEqual(lobbyName, getResponse.Name, "Checking the lobby we got for name.");
Assert.AreEqual(m_workingLobbyId, getResponse.Id, "Checking the lobby we got for ID.");
Assert.IsNotNull(lobby, "GetLobbyAsync should return a non-null result.");
Assert.AreEqual(lobbyName, lobby.Name, "Checking the lobby we got for name.");
Assert.AreEqual(m_workingLobbyId, lobby.Id, "Checking the lobby we got for ID.");
// Delete the test lobby.
bool didDeleteFinish = false;

// Query to ensure the lobby is gone.
yield return new WaitForSeconds(1); // To prevent a possible 429 with the upcoming Query request.
QueryResponse queryResponseTwo = null;
LobbyAPIInterface.QueryAllLobbiesAsync(new List<QueryFilter>(), (qr) => { queryResponseTwo = qr; });
while (queryResponseTwo == null && timeout > 0)
{ yield return new WaitForSeconds(0.25f);
timeout -= 0.25f;
}
Debug.Log("Getting Lobby List 3");
yield return AsyncTestHelper.Await(async () => queryResponse = await LobbyAsyncRequests.Instance.RetrieveLobbyListAsync());
Debug.Log("Got Lobby List 3");
Assert.AreEqual(numLobbiesIni, queryResponseTwo.Results.Count, "Queried lobbies list should be empty.");
Assert.AreEqual(numLobbiesIni, queryResponse.Results.Count, "Queried lobbies list should be empty.");
// Some error messages might be asynchronous, so to reduce spillover into other tests, just wait here for a bit before proceeding.
yield return new WaitForSeconds(3);

2
Assets/Scripts/GameLobby/Tests/PlayMode/AsyncTestHelper.cs.meta


fileFormatVersion: 2
guid: adc002dd7627a40afac107b97fcc05a0
guid: cfbe0210f43014b7989e0ca76171c90d
MonoImporter:
externalObjects: {}
serializedVersion: 2

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


private void Start()
{
LobbyAsyncRequests.Instance.GetRateLimit(m_requestType).onChanged += UpdateVisibility;
LobbyAsyncRequests.Instance.GetRateLimit(m_requestType).onCooldownChange += UpdateVisibility;
LobbyAsyncRequests.Instance.GetRateLimit(m_requestType).onChanged -= UpdateVisibility;
LobbyAsyncRequests.Instance.GetRateLimit(m_requestType).onCooldownChange -= UpdateVisibility;
private void UpdateVisibility(LobbyAsyncRequests.RateLimitCooldown rateLimit)
private void UpdateVisibility(bool isCoolingDown)
if (rateLimit.IsInCooldown)
if (isCoolingDown)
m_target.Hide(m_alphaWhenHidden);
else
m_target.Show();

27
Assets/Scripts/GameLobby/Tests/PlayMode/AsyncTestHelper.cs


using System;
using System.Collections;
using System.Threading.Tasks;
namespace Test.Tools
{
public class AsyncTestHelper
{
public static IEnumerator Await(Task task)
{
while (!task.IsCompleted)
{
yield return null;
}
if (task.IsFaulted)
{
throw task.Exception;
}
}
public static IEnumerator Await(Func<Task> taskDelegate)
{
return Await(taskDelegate.Invoke());
}
}
}

94
Assets/Scripts/GameLobby/Tests/PlayMode/AsyncLobbyTest.cs


using System;
using System.Collections;
using System.Collections.Generic;
using System.Threading.Tasks;
using Unity.Services.Lobbies;
using Unity.Services.Lobbies.Models;
using Unity.Services.Relay;
using Unity.Services.Relay.Models;
using UnityEngine;
namespace LobbyAsync
{
public class LobbyRelayTest
{
public async Task Join(string lobbyId, string password = "")
{
try
{/*
// Authenticate
await AuthenticationService.Instance.SignInAnonymouslyAsync();
// Join Lobby
LobbyPlayer player = new LobbyPlayer(AuthenticationService.Instance.PlayerId);
JoinLobbyByIdOptions options = new JoinLobbyByIdOptions()
{
Player = player
};
Lobby lobby = = await LobbyHelper.Instance.JoinLobbyByIdAsync(lobbyId, options);
// Join Relay
string joinCode = lobby.Data["joinCode"].Value;
JoinAllocation join = await Relay.Instance.JoinAllocationAsync(joinCode);
await Lobbies.Instance.UpdatePlayerAsync(lobby.Id, player.Id, new UpdatePlayerOptions()
{
AllocationId = join.AllocationId.ToString(),
ConnectionInfo = joinCode
});
relayTransport.SetClientRelayData(join.RelayServer.IpV4, (ushort) join.RelayServer.Port,
join.AllocationIdBytes, join.Key, join.ConnectionData, join.HostConnectionData, true);
// Start Client
NetworkManager.Singleton.StartClient();*/
}
catch (Exception e)
{
Debug.LogError(e);
}
}
public async Task Create()
{
try
{
/*
// Authenticate
await Authenticate();
// Allocate Relay
Allocation allocation = await Relay.Instance.CreateAllocationAsync(maxPlayers);
relayTransport.SetHostRelayData(allocation.RelayServer.IpV4, (ushort)allocation.RelayServer.Port, allocation.AllocationIdBytes, allocation.Key, allocation.ConnectionData, true);
// Generate Join Code
string joinCode = await Relay.Instance.GetJoinCodeAsync(allocation.AllocationId);
// Create Lobby
CreateLobbyOptions options = new CreateLobbyOptions()
{
IsPrivate = isPrivate,
Data = new Dictionary<string, DataObject>()
{
{ "joinCode", new DataObject(DataObject.VisibilityOptions.Public, joinCode) },
},
Player = new LobbyPlayer(AuthenticationService.Instance.PlayerId, joinCode, null, allocation.AllocationId.ToString())
};
Lobby lobby = await LobbyHelper.Instance.CreateLobbyAsync(worldNameInputField.text, maxPlayers, options);
// Start Host
NetworkManager.Singleton.StartHost();*/
}
catch (Exception e)
{
Debug.LogError(e);
}
}
}
}

/Assets/Scripts/GameLobby/Tests/PlayMode/AsyncLobbyTest.cs.meta → /Assets/Scripts/GameLobby/Tests/PlayMode/AsyncTestHelper.cs.meta

正在加载...
取消
保存