浏览代码

Fixing a bug where clients in builds would not receive the initial player state upon relay connection. There are also some nitpicks with the tests in here.

/main/staging
nathaniel.buck@unity3d.com 3 年前
当前提交
838aae18
共有 8 个文件被更改,包括 49 次插入39 次删除
  1. 11
      Assets/Scripts/Relay/RelayUtpClient.cs
  2. 10
      Assets/Scripts/Relay/RelayUtpHost.cs
  3. 2
      Assets/Scripts/Relay/RelayUtpSetup.cs
  4. 3
      Assets/Scripts/Tests/Editor/LoggerTests.cs
  5. 55
      Assets/Scripts/Tests/Editor/MessengerTests.cs
  6. 3
      Assets/Scripts/Tests/Editor/ObserverTests.cs
  7. 2
      Assets/Scripts/Tests/PlayMode/LobbyRoundtripTests.cs
  8. 2
      Assets/Scripts/Tests/PlayMode/RelayRoundTripTests.cs

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)
{

10
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)

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.
}
}
}

2
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)
{

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


using LobbyRelaySample;
namespace LobbyRelaySample.Tests
namespace Test
{
/// <summary>
/// Tests of the LogHandler overriding debug logging.

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


using System.Text.RegularExpressions;
using UnityEngine.TestTools;
public class MessengerTests
namespace Test
/// <summary>Trivial message recipient that will run some action on any message.</summary>
private class Subscriber : IReceiveMessages
public class MessengerTests
private Action m_thingToDo;
public Subscriber(Action thingToDo) { m_thingToDo = thingToDo; }
public void OnReceiveMessage(MessageType type, object msg) { m_thingToDo?.Invoke(); }
}
/// <summary>Trivial message recipient that will run some action on any message.</summary>
private class Subscriber : IReceiveMessages
{
private Action m_thingToDo;
public Subscriber(Action thingToDo) { m_thingToDo = thingToDo; }
public void OnReceiveMessage(MessageType type, object msg) { m_thingToDo?.Invoke(); }
}
/// <summary>
/// If a message recipient takes a long time to process a message, we want to be made aware.
/// </summary>
[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);
/// <summary>
/// If a message recipient takes a long time to process a message, we want to be made aware.
/// </summary>
[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);
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.");
}
}
}

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


using LobbyRelaySample;
namespace LobbyRelaySample.Tests
namespace Test
{
public class ObserverTests
{

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


/// <summary>
/// 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
{

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


/// <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
{

正在加载...
取消
保存