浏览代码

Merged in latest staging for doc image capture

/main/staging
当前提交
eea9277d
共有 24 个文件被更改,包括 183 次插入398 次删除
  1. 18
      Assets/Scripts/Infrastructure/Messenger.cs
  2. 8
      Assets/Scripts/Lobby/LobbyAPIInterface.cs
  3. 29
      Assets/Scripts/Lobby/LobbyAsyncRequests.cs
  4. 11
      Assets/Scripts/Lobby/LobbyListHeartbeat.cs
  5. 2
      Assets/Scripts/Lobby/ToLocalLobby.cs
  6. 23
      Assets/Scripts/Relay/RelayInterface.cs
  7. 11
      Assets/Scripts/Relay/RelayUtpClient.cs
  8. 12
      Assets/Scripts/Relay/RelayUtpHost.cs
  9. 16
      Assets/Scripts/Relay/RelayUtpSetup.cs
  10. 3
      Assets/Scripts/Tests/Editor/AuthTests.cs
  11. 38
      Assets/Scripts/Tests/Editor/LoggerTests.cs
  12. 53
      Assets/Scripts/Tests/Editor/MessengerTests.cs
  13. 22
      Assets/Scripts/Tests/Editor/ObserverTests.cs
  14. 14
      Assets/Scripts/Tests/PlayMode/LobbyRoundtripTests.cs
  15. 94
      Assets/Scripts/Tests/PlayMode/RelayRoundTripTests.cs
  16. 6
      Assets/Scripts/Tests/PlayMode/UpdateSlowTests.cs
  17. 15
      Assets/Scripts/Game/LocalLobby.cs
  18. 5
      Assets/Scripts/Game/GameStateManager.cs
  19. 11
      Assets/Scripts/Tests/Editor/LobbyTests.cs.meta
  20. 44
      Assets/Scripts/Tests/Editor/LobbyTests.cs
  21. 11
      Assets/Scripts/Tests/PlayMode/LobbyReadyCheckTests.cs.meta
  22. 135
      Assets/Scripts/Tests/PlayMode/LobbyReadyCheckTests.cs
  23. 0
      /Assets/Scripts/Game
  24. 0
      /Assets/Scripts/Game.meta

18
Assets/Scripts/Infrastructure/Messenger.cs


JoinLobbyRequest = 2,
CreateLobbyRequest = 3,
QueryLobbies = 4,
PlayerJoinedLobby = 5,
PlayerLeftLobby = 6,
ChangeGameState = 7,
LobbyUserStatus = 8,
HostInitReadyCheck = 9,
LocalUserReadyCheckResponse = 10,
UserSetEmote = 11,
EndGame = 12,
StartCountdown = 13,
CancelCountdown = 14,
ConfirmInGameState = 15,
ChangeGameState = 5,
LobbyUserStatus = 6,
UserSetEmote = 7,
EndGame = 8,
StartCountdown = 9,
CancelCountdown = 10,
ConfirmInGameState = 11,
}
/// <summary>

8
Assets/Scripts/Lobby/LobbyAPIInterface.cs


}
}
private const int k_maxLobbiesToShow = 64;
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<Response<Lobby>> onComplete)
{

new InProgressRequest<Response<Lobby>>(task, onComplete);
}
public static void UpdatePlayerAsync(string lobbyId, string playerId, Dictionary<string, PlayerDataObject> data, Action<Response<Lobby>> onComplete)
public static void UpdatePlayerAsync(string lobbyId, string playerId, Dictionary<string, PlayerDataObject> data, Action<Response<Lobby>> onComplete, string allocationId, string connectionInfo)
data: data
data: data,
allocationId: allocationId,
connectionInfo: connectionInfo
));
var task = LobbyService.LobbyApiClient.UpdatePlayerAsync(updateRequest);
new InProgressRequest<Response<Lobby>>(task, onComplete);

29
Assets/Scripts/Lobby/LobbyAsyncRequests.cs


public void EndTracking()
{
m_currentLobbyId = null;
m_lastKnownLobby = null;
}
private void UpdateLobby(float unused)

/// <param name="data">Key-value pairs, which will overwrite any existing data for these keys. Presumed to be available to all lobby members but not publicly.</param>
public void UpdatePlayerDataAsync(Dictionary<string, string> data, Action onComplete)
{
if (!ShouldUpdateData(() => { UpdatePlayerDataAsync(data, onComplete); }, onComplete))
if (!ShouldUpdateData(() => { UpdatePlayerDataAsync(data, onComplete); }, onComplete, false))
Lobby lobby = m_lastKnownLobby;
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(lobby.Id, Locator.Get.Identity.GetSubIdentity(Auth.IIdentityType.Auth).GetContent("id"), dataCurr, (r) => { onComplete?.Invoke(); });
LobbyAPIInterface.UpdatePlayerAsync(m_lastKnownLobby.Id, playerId, dataCurr, (r) => { onComplete?.Invoke(); }, null, null);
}
/// <summary>Lobby can be provided info about Relay (or any other remote allocation) so it can add automatic disconnect handling.</summary>
public void UpdatePlayerRelayInfoAsync(string allocationId, string connectionInfo, Action onComplete)
{
if (!ShouldUpdateData(() => { UpdatePlayerRelayInfoAsync(allocationId, connectionInfo, onComplete); }, onComplete, true)) // Do retry here since the RelayUtpSetup that called this might be destroyed right after this.
return;
string playerId = Locator.Get.Identity.GetSubIdentity(Auth.IIdentityType.Auth).GetContent("id");
LobbyAPIInterface.UpdatePlayerAsync(m_lastKnownLobby.Id, playerId, new Dictionary<string, PlayerDataObject>(), (r) => { onComplete?.Invoke(); }, allocationId, connectionInfo);
if (!ShouldUpdateData(() => { UpdateLobbyDataAsync(data, onComplete); }, onComplete))
if (!ShouldUpdateData(() => { UpdateLobbyDataAsync(data, onComplete); }, onComplete, false))
return;
Lobby lobby = m_lastKnownLobby;

LobbyAPIInterface.UpdateLobbyAsync(lobby.Id, dataCurr, (r) => { onComplete?.Invoke(); });
}
private bool ShouldUpdateData(Action caller, Action onComplete)
/// <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_isMidRetrieve)
{ m_pendingOperations.Enqueue(caller);

if (lobby == null)
{ onComplete?.Invoke();
{
if (shouldRetryIfLobbyNull)
m_pendingOperations.Enqueue(caller);
onComplete?.Invoke();
return false;
}
return true;

11
Assets/Scripts/Lobby/LobbyListHeartbeat.cs


/// </summary>
public class LobbyListHeartbeat : MonoBehaviour
{
private const float k_refreshRate = 5;
private float m_refreshTimer = 0;
// This is called in-editor via events.
public void SetActive(bool isActive)
{

Locator.Get.UpdateSlow.Unsubscribe(OnUpdate);
m_refreshTimer = 0;
Locator.Get.Messenger.OnReceiveMessage(MessageType.QueryLobbies, null);
m_refreshTimer += dt;
if (m_refreshTimer > k_refreshRate)
{
Locator.Get.Messenger.OnReceiveMessage(MessageType.QueryLobbies, null);
m_refreshTimer = 0;
}
}
}
}

2
Assets/Scripts/Lobby/ToLocalLobby.cs


Private = lobby.IsPrivate,
LobbyName = lobby.Name,
MaxPlayerCount = lobby.MaxPlayers,
RelayCode = lobby.Data?.ContainsKey("RelayCode") == true ? lobby.Data["RelayCode"].Value : null, // TODO: Remove?
RelayCode = lobby.Data?.ContainsKey("RelayCode") == true ? lobby.Data["RelayCode"].Value : null, // 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.
State = lobby.Data?.ContainsKey("State") == true ? (LobbyState) int.Parse(lobby.Data["State"].Value) : LobbyState.Lobby, // TODO: Consider TryParse, just in case (and below). Although, we don't have fail logic anyway...
Color = lobby.Data?.ContainsKey("Color") == true ? (LobbyColor) int.Parse(lobby.Data["Color"].Value) : LobbyColor.None
};

23
Assets/Scripts/Relay/RelayInterface.cs


} finally {
onComplete?.Invoke(result);
}
// TODO: Ensure that passing null as result is handled.
}
}

/// <param name="maxConnections"></param>
public static void AllocateAsync(int maxConnections, Action<Response<AllocateResponseBody>> onComplete)
public static void AllocateAsync(int maxConnections, Action<Allocation> onComplete)
new InProgressRequest<Response<AllocateResponseBody>>(task, onComplete);
}
new InProgressRequest<Response<AllocateResponseBody>>(task, OnResponse);
public static void AllocateAsync(int maxConnections, Action<Allocation> onComplete)
{
AllocateAsync(maxConnections, a =>
void OnResponse(Response<AllocateResponseBody> response)
if (a == null)
if (response == null)
else if (a.Status >= 200 && a.Status < 300)
onComplete?.Invoke(a.Result.Data.Allocation);
else if (response.Status >= 200 && response.Status < 300)
onComplete?.Invoke(response.Result.Data.Allocation);
Debug.LogError($"Allocation returned a non Success code: {a.Status}");
});
Debug.LogError($"Allocation returned a non Success code: {response.Status}");
};
}
/// <summary>

11
Assets/Scripts/Relay/RelayUtpClient.cs


namespace LobbyRelaySample.Relay
{
/// <summary>
/// This will handle observing the local player and updating remote players over Relay when there are local changes.
/// This observes the local player and updates remote players over Relay when there are local changes, demonstrating basic data transfer over the Unity Transport (UTP).
/// Created after the connection to Relay has been confirmed.
/// </summary>
public class RelayUtpClient : MonoBehaviour // This is a MonoBehaviour merely to have access to Update.

{
MsgType msgType = (MsgType)strm.ReadByte();
string id = ReadLengthAndString(ref strm);
if (id == m_localUser.ID || !m_localLobby.LobbyUsers.ContainsKey(id)) // TODO: Do we want to hold onto the message if the user isn't present *now* in case they're pending?
if (id == m_localUser.ID || !m_localLobby.LobbyUsers.ContainsKey(id)) // We don't hold onto messages, since an incoming user will be fully initialized before they send events.
return;
if (msgType == MsgType.PlayerName)

private void SendInitialMessage(NetworkDriver driver, NetworkConnection connection)
{
WriteByte(driver, connection, m_localUser.ID, MsgType.NewPlayer, 0);
ForceFullUserUpdate(driver, connection, m_localUser); // Assuming this is only created after the Relay connection is successful.
m_hasSentInitialMessage = true;
}

}
protected void ForceFullUserUpdate(NetworkDriver driver, NetworkConnection connection, LobbyUser user)
{
// TODO: Write full state in one message?
// Note that it would be better to send a single message with the full state, but for the sake of shorter code we'll leave that out here.
WriteString(driver, connection, user.ID, MsgType.PlayerName, user.DisplayName);
WriteByte(driver, connection, user.ID, MsgType.Emote, (byte)user.Emote);
WriteByte(driver, connection, user.ID, MsgType.ReadyState, (byte)user.UserStatus);

/// Write string data as: [1 byte: msgType][1 byte: id length N][N bytes: id][1 byte: string length M][M bytes: string]
/// Write string data as: [1 byte: msgType] [1 byte: id length N] [N bytes: id] [1 byte: string length M] [M bytes: string]
/// </summary>
protected void WriteString(NetworkDriver driver, NetworkConnection connection, string id, MsgType msgType, string str)
{

}
/// <summary>
/// Write byte data as: [1 byte: msgType][1 byte: id length N][N bytes: id][1 byte: data]
/// Write byte data as: [1 byte: msgType] [1 byte: id length N] [N bytes: id] [1 byte: data]
/// </summary>
protected void WriteByte(NetworkDriver driver, NetworkConnection connection, string id, MsgType msgType, byte value)
{

12
Assets/Scripts/Relay/RelayUtpHost.cs


}
/// <summary>
/// When a new client connects, they need to be given all up-to-date info.
/// When a new client connects, they need to be updated with the current state of everyone else.
// When a new client connects, they need to be updated with the current state of everyone else.
// (We can't exclude this client from the events we send to it, since we don't have its ID in strm, but it will ignore messages about itself on arrival.)
foreach (var user in m_localLobby.LobbyUsers)
foreach (var user in m_localLobby.LobbyUsers) // The host includes itself here since we don't necessarily have an ID available, but it will ignore its own messages on arrival.
ForceFullUserUpdate(m_networkDriver, conn, user.Value);
}

WriteByte(m_networkDriver, otherConn, id, msgType, value);
}
}
else if (msgType == MsgType.NewPlayer) // This ensures clients in builds are sent player state once they establish that they can send (and receive) events.
OnNewConnection(conn);
// If a client has changed state, check if this changes whether all players have readied.
if (msgType == MsgType.ReadyState)

protected override void ProcessDisconnectEvent(NetworkConnection conn, DataStreamReader strm)
{
// TODO: If a client disconnects, see if remaining players are all already ready.
// TEMP logging
UnityEngine.Debug.LogError("Client disconnected!");
}
public void OnReceiveMessage(MessageType type, object msg)

if (!conn.IsCreated) // "Nothing more to accept" is signalled by returning an invalid connection from Accept.
break;
m_connections.Add(conn);
OnNewConnection(conn);
OnNewConnection(conn); // This ensures that clients in editors are sent player state once they establish a connection. The timing differs slightly from builds.
}
}
}

16
Assets/Scripts/Relay/RelayUtpSetup.cs


protected LobbyUser m_localUser;
protected Action<bool, RelayUtpClient> m_onJoinComplete;
public enum MsgType { NewPlayer = 0, Ping = 1, ReadyState = 2, PlayerName = 3, Emote = 4, StartCountdown = 5, CancelCountdown = 6, ConfirmInGame = 7, EndInGame = 8 }
public enum MsgType { Ping = 0, NewPlayer, ReadyState, PlayerName, Emote, StartCountdown, CancelCountdown, ConfirmInGame, EndInGame }
public void BeginRelayJoin(LocalLobby localLobby, LobbyUser localUser, Action<bool, RelayUtpClient> onJoinComplete)
{

[Flags]
private enum JoinState { None = 0, Bound = 1, Joined = 2 }
private JoinState m_joinState = JoinState.None;
private Allocation m_allocation;
protected override void JoinRelay()
{

private void OnAllocation(Allocation allocation)
{
m_allocation = allocation;
RelayInterface.GetJoinCodeAsync(allocation.AllocationId, OnRelayCode);
BindToAllocation(allocation.RelayServer.IpV4, allocation.RelayServer.Port, allocation.AllocationIdBytes, allocation.ConnectionData, allocation.ConnectionData, allocation.Key, 16);
}

m_localLobby.RelayCode = relayCode;
RelayInterface.JoinAsync(m_localLobby.RelayCode, OnJoin);
}
private void OnJoin(JoinAllocation joinAllocation)
{
m_localLobby.RelayServer = new ServerAddress(joinAllocation.RelayServer.IpV4, joinAllocation.RelayServer.Port);
m_localLobby.RelayServer = new ServerAddress(m_allocation.RelayServer.IpV4, m_allocation.RelayServer.Port);
m_joinState |= JoinState.Joined;
CheckForComplete();
}

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

private JoinAllocation m_allocation;
protected override void JoinRelay()
{
m_localLobby.onChanged += OnLobbyChange;

{
if (allocation == null)
return;
m_allocation = allocation;
BindToAllocation(allocation.RelayServer.IpV4, allocation.RelayServer.Port, allocation.AllocationIdBytes, allocation.ConnectionData, allocation.HostConnectionData, allocation.Key, 1);
m_localLobby.RelayServer = new ServerAddress(allocation.RelayServer.IpV4, allocation.RelayServer.Port);
}

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

3
Assets/Scripts/Tests/Editor/AuthTests.cs


{
public class AuthTests
{
/// <summary>
/// Ensure that any changes to a flavor of SubIdentity are automatically updated.
/// </summary>
[Test]
public void IdentityBasicSubidentity()
{

38
Assets/Scripts/Tests/Editor/LoggerTests.cs


using LobbyRelaySample;
namespace LobbyRelaySample.Tests
namespace Test
/// <summary>
/// Tests of the LogHandler overriding debug logging.
/// </summary>
/// <summary>Reset the console between tests.</summary>
Debug.ClearDeveloperConsole(); // Reset Console between tests
Debug.ClearDeveloperConsole();
/// <summary>
/// Only display Log messages when set to Verbose.
/// </summary>
public void TestLog() // Should not show when not Verbose
public void TestLog()
LogAssert.NoUnexpectedReceived(); //Checks to see if there is anything here, there should not be
LogAssert.NoUnexpectedReceived(); // Ensure that we haven't received any unexpected logs.
LogHandler.Get().mode = LogMode.Warnings;
Debug.Log("WarningLog");

LogAssert.Expect(LogType.Log, "VerbLog");
}
/// <summary>
/// Only display Warning messages when set to Verbose or Warnings.
/// </summary>
public void TestWarning() // Should not show when Critical
public void TestWarning()
LogAssert.NoUnexpectedReceived(); //Checks to see if there is anything here, there should not be
LogAssert.NoUnexpectedReceived();
LogHandler.Get().mode = LogMode.Warnings;
Debug.LogWarning("WarningWarning");

LogAssert.Expect(LogType.Warning, "VerbWarning");
}
/// <summary>
/// Always display Error messages.
/// </summary>
public void TestError() // Should show regardless.
public void TestError()
{
LogHandler.Get().mode = LogMode.Critical;
Debug.LogError("CritError");

LogAssert.Expect(LogType.Error, "VerbError");
}
/// <summary>
/// Always display Assert messages.
/// </summary>
public void TestAssert() //Should Show regardless
public void TestAssert()
{
LogHandler.Get().mode = LogMode.Critical;
Debug.LogAssertion(true);

LogAssert.Expect(LogType.Assert, "True");
}
/// <summary>
/// Always display Exception messages.
/// </summary>
public void TestException() //Should Show regardless
public void TestException()
{
LogHandler.Get().mode = LogMode.Critical;
LogAssert.Expect(LogType.Exception, "Exception: CriticalException");

53
Assets/Scripts/Tests/Editor/MessengerTests.cs


using System.Text.RegularExpressions;
using UnityEngine.TestTools;
public class MessengerTests
namespace Test
private class Subscriber : IReceiveMessages
public class MessengerTests
private Action m_thingToDo;
public Subscriber(Action thingToDo)
/// <summary>Trivial message recipient that will run some action on any message.</summary>
private class Subscriber : IReceiveMessages
m_thingToDo = thingToDo;
private Action m_thingToDo;
public Subscriber(Action thingToDo) { m_thingToDo = thingToDo; }
public void OnReceiveMessage(MessageType type, object msg) { m_thingToDo?.Invoke(); }
public void OnReceiveMessage(MessageType type, object msg)
/// <summary>
/// If a message recipient takes a long time to process a message, we want to be made aware.
/// </summary>
[Test]
public void WhatIfAMessageIsVerySlow()
m_thingToDo?.Invoke();
}
}
[Test]
public void WhatIfAMessageIsVerySlow()
{
Messenger messenger = new Messenger();
int msgCount = 0;
string inefficientString = "";
Subscriber sub = new Subscriber(() =>
{ for (int n = 0; n < 12345; n++)
inefficientString += n.ToString();
msgCount++;
});
messenger.Subscribe(sub);
Messenger messenger = new Messenger();
int msgCount = 0;
string inefficientString = "";
Subscriber sub = new Subscriber(() =>
{ for (int n = 0; n < 12345; n++)
inefficientString += n.ToString();
msgCount++;
});
messenger.Subscribe(sub);
LogAssert.Expect(UnityEngine.LogType.Warning, new Regex(".*took too long.*"));
messenger.OnReceiveMessage(MessageType.None, "");
LogAssert.Expect(UnityEngine.LogType.Warning, new Regex(".*took too long.*"));
messenger.OnReceiveMessage(MessageType.None, "");
Assert.AreEqual(1, msgCount, "Should have acted on the message.");
Assert.AreEqual(1, msgCount, "Should have acted on the message.");
}
}
}

22
Assets/Scripts/Tests/Editor/ObserverTests.cs


using LobbyRelaySample;
namespace LobbyRelaySample.Tests
namespace Test
/// <summary>
/// When an observed value changes, the Observer should automatically update.
/// </summary>
public IEnumerator ObserverChangeWhenObservedChanged() // Test if Observer changes when StringField gets set
public IEnumerator ObserverChangeWhenObservedChanged()
{
var observed = new TestObserved();
var observer = new GameObject("PlayerObserver").AddComponent<TestObserverBehaviour>();

Assert.AreEqual(observed.StringField, observer.displayStringField);
}
/// <summary>
/// When an Observer is registered, it should receive the observed field's initial value.
/// </summary>
/// <returns></returns>
public IEnumerator ObserverRegistersInitialChanges() // Test if Observer changes on Initialization
public IEnumerator ObserverRegistersInitialChanges()
observed.StringField = "NewName"; // Set the field before we begin observing
observed.StringField = "NewName";
var observer = new GameObject("PlayerObserver").AddComponent<TestObserverBehaviour>();
Assert.AreNotEqual(observed.StringField, observer.displayStringField);

Assert.AreEqual(observed.StringField, observer.displayStringField);
}
class TestObserved : Observed<TestObserved>
// We just have a couple Observers that update some arbitrary member, in this case a string.
private class TestObserved : Observed<TestObserved>
{
string m_stringField;

}
}
//Mock UI Observer
class TestObserverBehaviour : ObserverBehaviour<TestObserved>
private class TestObserverBehaviour : ObserverBehaviour<TestObserved>
{
public string displayStringField;

14
Assets/Scripts/Tests/PlayMode/LobbyRoundtripTests.cs


namespace Test
{
/// <summary>
/// Hits the Authentication and Lobbies services in order to ensure lobbies can be created and deleted.
/// The actual code accessing lobbies should go through LobbyAsyncRequests. This serves to ensure the connection to the Lobby service is functional.
/// Accesses the Authentication and Lobby services in order to ensure lobbies can be created and deleted.
/// LobbyAsyncRequests wraps the Lobby 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 Lobby service itself properly.
/// </summary>
public class LobbyRoundtripTests
{

LogAssert.ignoreFailingMessages = false;
}
/// <summary>
/// Make sure the entire roundtrip for Lobby works: Once signed in, create a lobby, query to make sure it exists, then delete it.
/// </summary>
#region Setup
LogAssert.ignoreFailingMessages = true; // Not sure why, but when auth logs in, it sometimes generates an error: "A Native Collection has not been disposed[...]." We don't want this to cause test failures, since in practice it *seems* to not negatively impact behavior.
// Wait a reasonable amount of time for sign-in to complete.

Assert.Greater(timeout, 0, "Timeout check (query #0)");
Assert.IsTrue(queryResponse.Status >= 200 && queryResponse.Status < 300, "QueryAllLobbiesAsync should return a success code. (#0)");
int numLobbiesIni = queryResponse.Result.Results?.Count ?? 0;
#endregion
// Create a test lobby.
Response<Lobby> createResponse = null;

LogAssert.ignoreFailingMessages = false;
}
/// <summary>
/// If the Lobby create call fails, we want to ensure we call onComplete so we can act on the failure.
/// </summary>
[UnityTest]
public IEnumerator OnCompletesOnFailure()
{

94
Assets/Scripts/Tests/PlayMode/RelayRoundTripTests.cs


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;

LogAssert.ignoreFailingMessages = false;
}
/// <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)
Assert.Fail("Did not sign in.");
//Allocation
float timeout = 5;
Response<AllocateResponseBody> allocationResponse = null;
RelayInterface.AllocateAsync(4, (a) => { allocationResponse = a; });
while (allocationResponse == null && timeout > 0)
{
yield return new WaitForSeconds(0.25f);
timeout -= 0.25f;
}
Assert.Greater(timeout, 0, "Timeout Check (Allocate)");
Assert.IsTrue(allocationResponse.Status >= 200 && allocationResponse.Status < 300, "AllocationResponse should return a success code.");
Guid allocationId = allocationResponse.Result.Data.Allocation.AllocationId;
var allocationIP = allocationResponse.Result.Data.Allocation.RelayServer.IpV4;
var allocationPort = allocationResponse.Result.Data.Allocation.RelayServer.Port;
Assert.NotNull(allocationId);
Assert.NotNull(allocationIP);
Assert.NotNull(allocationPort);
//Join Code Fetch
timeout = 5;
Response<JoinCodeResponseBody> joinCodeResponse = null;
RelayInterface.GetJoinCodeAsync(allocationId, (j) => { joinCodeResponse = j; });
while (joinCodeResponse == null && timeout > 0)
{
yield return new WaitForSeconds(0.25f);
timeout -= 0.25f;
}
Assert.Greater(timeout, 0, "Timeout Check (JoinCode)");
Assert.IsTrue(allocationResponse.Status >= 200 && allocationResponse.Status < 300, "JoinCodeResponse should return a success code.");
string joinCode = joinCodeResponse.Result.Data.JoinCode;
Assert.False(string.IsNullOrEmpty(joinCode));
//Join Via Join Code
timeout = 5;
Response<JoinResponseBody> joinResponse = null;
RelayInterface.JoinAsync(joinCode, (j) => { joinResponse = j; });
while (joinResponse == null && timeout > 0)
{
yield return new WaitForSeconds(0.25f);
timeout -= 0.25f;
}
Assert.Greater(timeout, 0, "Timeout Check (Join)");
Assert.IsTrue(allocationResponse.Status >= 200 && allocationResponse.Status < 300, "JoinResponse should return a success code.");
var codeIp = joinResponse.Result.Data.Allocation.RelayServer.IpV4;
var codePort = joinResponse.Result.Data.Allocation.RelayServer.Port;
Assert.AreEqual(codeIp, allocationIP);
Assert.AreEqual(codePort, allocationPort);
}
[UnityTest]
public IEnumerator DoShortcutRoundtrip()
{
LogAssert.ignoreFailingMessages = true;
if (!m_didSigninComplete)
yield return new WaitForSeconds(3);
if (!m_didSigninComplete)
Assert.Fail("Did not sign in.");
//Allocation
// Allocation
{
yield return new WaitForSeconds(0.25f);
{ yield return new WaitForSeconds(0.25f);
timeout -= 0.25f;
}

Assert.NotNull(allocationIP);
Assert.NotNull(allocationPort);
//Join Code Fetch
// Join code retrieval
{
yield return new WaitForSeconds(0.25f);
{ yield return new WaitForSeconds(0.25f);
//Join Via Join Code
// Joining with the join code
{
yield return new WaitForSeconds(0.25f);
{ yield return new WaitForSeconds(0.25f);
Assert.Greater(timeout, 0, "Timeout Check (Join)");
var codeIp = joinResponse.Result.Data.Allocation.RelayServer.IpV4;
var codePort = joinResponse.Result.Data.Allocation.RelayServer.Port;

6
Assets/Scripts/Tests/PlayMode/UpdateSlowTests.cs


using UnityEngine.TestTools;
namespace Test
{
{
/// <summary>
/// Testing some edge cases with the UpdateSlow.
/// </summary>
public class UpdateSlowTests
{
private GameObject m_updateSlowObj;

/// <summary>Trivial Subscriber to do some action every UpdateSlow.</summary>
private class Subscriber : IDisposable
{
private Action m_thingToDo;

15
Assets/Scripts/Game/LocalLobby.cs


using System;
using System.Collections.Generic;
using System.Linq;
using UnityEngine;
namespace LobbyRelaySample

m_data.Color = value;
OnChanged(this);
}
}
/// <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) // TODO: Remove test-only API.
{
var statePlayers = m_LobbyUsers.Values.Count(user => user.UserStatus == status);
if (playersCount < 0)
return statePlayers == m_LobbyUsers.Count;
return statePlayers == playersCount;
}
public void CopyObserved(LobbyData data, Dictionary<string, LobbyUser> currUsers)

5
Assets/Scripts/Game/GameStateManager.cs


IEnumerator LeaveBeforeQuit()
{
ForceLeaveAttempt();
// TEMP: Since we're temporarily (as of 6/31/21) deleting empty lobbies when we leave them manually, we'll delay longer to ensure that happens.
//yield return null;
yield return new WaitForSeconds(0.5f);
yield return null;
Application.Quit();
}
}

11
Assets/Scripts/Tests/Editor/LobbyTests.cs.meta


fileFormatVersion: 2
guid: 9d49868c0d767434f95879cf758a4fb2
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

44
Assets/Scripts/Tests/Editor/LobbyTests.cs


using LobbyRelaySample;
using NUnit.Framework;
namespace Test
{
public class LobbyTests
{
LocalLobby m_LocalLobby;
const int k_TestUserCount = 3;
[SetUp]
public void Setup()
{
m_LocalLobby = new LocalLobby();
for (int i = 0; i < k_TestUserCount; i++)
{
m_LocalLobby.AddPlayer(new LobbyUser
{
ID = i.ToString()
});
}
}
[Test]
public void LobbyPlayerStateTest()
{
Assert.False(m_LocalLobby.PlayersOfState(UserStatus.Ready));
m_LocalLobby.LobbyUsers["0"].UserStatus = UserStatus.Ready;
Assert.False(m_LocalLobby.PlayersOfState(UserStatus.Ready));
Assert.True(m_LocalLobby.PlayersOfState(UserStatus.Ready, 1));
m_LocalLobby.LobbyUsers["1"].UserStatus = UserStatus.Ready;
Assert.False(m_LocalLobby.PlayersOfState(UserStatus.Ready));
Assert.True(m_LocalLobby.PlayersOfState(UserStatus.Ready, 2));
m_LocalLobby.LobbyUsers["2"].UserStatus = UserStatus.Ready;
Assert.True(m_LocalLobby.PlayersOfState(UserStatus.Ready));
}
}
}

11
Assets/Scripts/Tests/PlayMode/LobbyReadyCheckTests.cs.meta


fileFormatVersion: 2
guid: 851dd0cdcc3b6c84398f6db2aaf4c35a
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

135
Assets/Scripts/Tests/PlayMode/LobbyReadyCheckTests.cs


// TODO: Obsolete. Replace with something accurate to Relay with transport.
//using LobbyRelaySample;
//using NUnit.Framework;
//using System.Collections;
//using Unity.Services.Lobbies;
//using Unity.Services.Lobbies.Models;
//using UnityEngine;
//using UnityEngine.TestTools;
//using LobbyAPIInterface = LobbyRelaySample.lobby.LobbyAPIInterface;
//namespace Test
//{
// public class LobbyReadyCheckTests
// {
// private string m_workingLobbyId;
// private LobbyRelaySample.Auth.Identity m_auth;
// private bool m_didSigninComplete = false;
// private GameObject m_updateSlowObj;
// [OneTimeSetUp]
// public void Setup()
// {
// m_auth = new LobbyRelaySample.Auth.Identity(() => { m_didSigninComplete = true; });
// Locator.Get.Provide(m_auth);
// m_updateSlowObj = new GameObject("UpdateSlowTest");
// m_updateSlowObj.AddComponent<UpdateSlow>();
// }
// [UnityTearDown]
// public IEnumerator PerTestTeardown()
// {
// if (m_workingLobbyId != null)
// { LobbyAPIInterface.DeleteLobbyAsync(m_workingLobbyId, null);
// m_workingLobbyId = null;
// }
// yield return new WaitForSeconds(0.5f); // We need a yield anyway, so wait long enough to probably delete the lobby. There currently (6/22/2021) aren't other tests that would have issues if this took longer.
// }
// [OneTimeTearDown]
// public void Teardown()
// {
// Locator.Get.Provide(new LobbyRelaySample.Auth.IdentityNoop());
// m_auth.Dispose();
// LogAssert.ignoreFailingMessages = false;
// LobbyAsyncRequests.Instance.EndTracking();
// GameObject.Destroy(m_updateSlowObj);
// }
// private IEnumerator WaitForSignin()
// {
// // Wait a reasonable amount of time for sign-in to complete.
// if (!m_didSigninComplete)
// yield return new WaitForSeconds(3);
// if (!m_didSigninComplete)
// Assert.Fail("Did not sign in.");
// }
// private IEnumerator CreateLobby(string lobbyName, string userId)
// {
// Response<Lobby> createResponse = null;
// float timeout = 5;
// LobbyAPIInterface.CreateLobbyAsync(userId, lobbyName, 4, false, (r) => { createResponse = r; });
// while (createResponse == null && timeout > 0)
// { yield return new WaitForSeconds(0.25f);
// timeout -= 0.25f;
// }
// Assert.Greater(timeout, 0, "Timeout check (lobby creation).");
// m_workingLobbyId = createResponse.Result.Id;
// }
// private IEnumerator PushPlayerData(LobbyUser player)
// {
// bool hasPushedPlayerData = false;
// float timeout = 5;
// LobbyAsyncRequests.Instance.UpdatePlayerDataAsync(LobbyRelaySample.lobby.ToLocalLobby.RetrieveUserData(player), () => { hasPushedPlayerData = true; }); // LobbyContentHeartbeat normally does this.
// while (!hasPushedPlayerData && timeout > 0)
// { yield return new WaitForSeconds(0.25f);
// timeout -= 0.25f;
// }
// Assert.Greater(timeout, 0, "Timeout check (push player data).");
// }
// /// <summary>
// /// After creating a lobby and a player, signal that the player is Ready. This should lead to a countdown time being set for all players.
// /// </summary>
// [UnityTest]
// public IEnumerator SetCountdownTimeSinglePlayer()
// {
// LogAssert.ignoreFailingMessages = true; // Not sure why, but when auth logs in, it sometimes generates an error: "A Native Collection has not been disposed[...]." We don't want this to cause test failures, since in practice it *seems* to not negatively impact behavior.
// ReadyCheck readyCheck = new ReadyCheck(5); // This ready time is used for the countdown target end, not for any of the timing of actually detecting readies.
// yield return WaitForSignin();
// string userId = m_auth.GetSubIdentity(LobbyRelaySample.Auth.IIdentityType.Auth).GetContent("id");
// yield return CreateLobby("TestReadyLobby1", userId);
// LobbyAsyncRequests.Instance.BeginTracking(m_workingLobbyId);
// yield return new WaitForSeconds(2); // Allow the initial lobby retrieval.
// LobbyUser user = new LobbyUser();
// user.ID = userId;
// user.UserStatus = UserStatus.Ready;
// yield return PushPlayerData(user);
// readyCheck.BeginCheckingForReady();
// float timeout = 5; // Long enough for two slow updates
// yield return new WaitForSeconds(timeout);
// readyCheck.Dispose();
// LobbyAsyncRequests.Instance.EndTracking();
// yield return new WaitForSeconds(2); // Buffer to prevent a 429 on the upcoming Get, since there's a Get request on the slow upate loop when that's active.
// Response<Lobby> getResponse = null;
// timeout = 5;
// LobbyAPIInterface.GetLobbyAsync(m_workingLobbyId, (r) => { getResponse = r; });
// while (getResponse == null && timeout > 0)
// { yield return new WaitForSeconds(0.25f);
// timeout -= 0.25f;
// }
// Assert.Greater(timeout, 0, "Timeout check (get lobby).");
// Assert.NotNull(getResponse.Result, "Retrieved lobby successfully.");
// Assert.NotNull(getResponse.Result.Data, "Lobby should have data.");
// Assert.True(getResponse.Result.Data.ContainsKey("AllPlayersReady"), "Check for AllPlayersReady key.");
// string readyString = getResponse.Result.Data["AllPlayersReady"]?.Value;
// Assert.NotNull(readyString, "Check for non-null AllPlayersReady.");
// Assert.True(long.TryParse(readyString, out long ticks), "Check for ticks value in AllPlayersReady."); // This will be based on the current time, so we won't check for a specific value.
// }
// // Can't test with multiple players on one machine, since anonymous UAS credentials can't be manually supplied.
// }
//}

/Assets/Scripts/Entities → /Assets/Scripts/Game

/Assets/Scripts/Entities.meta → /Assets/Scripts/Game.meta

正在加载...
取消
保存