浏览代码

Primary rejection behavior - A host will reject an incoming client if the lobby is already in the in-game state. Clients will now no longer send data until they are approved, but once approved, connection completes as usual.

/main/staging/host_handshake
nathaniel.buck@unity3d.com 3 年前
当前提交
1fc7440a
共有 9 个文件被更改,包括 164 次插入20 次删除
  1. 24
      Assets/Scripts/Game/GameManager.cs
  2. 1
      Assets/Scripts/Game/LobbyUser.cs
  3. 4
      Assets/Scripts/Infrastructure/Messenger.cs
  4. 17
      Assets/Scripts/Lobby/LobbyContentHeartbeat.cs
  5. 29
      Assets/Scripts/Relay/RelayUtpClient.cs
  6. 39
      Assets/Scripts/Relay/RelayUtpHost.cs
  7. 2
      Assets/Scripts/Relay/RelayUtpSetup.cs
  8. 57
      Assets/Scripts/Relay/RelayPendingApproval.cs
  9. 11
      Assets/Scripts/Relay/RelayPendingApproval.cs.meta

24
Assets/Scripts/Game/GameManager.cs


else if (type == MessageType.ChangeGameState)
{ SetGameState((GameState)msg);
}
else if (type == MessageType.ClientUserApproved)
{ ConfirmApproval();
}
else if (type == MessageType.UserSetEmote)
{ EmoteType emote = (EmoteType)msg;
m_localUser.Emote = emote;

}
else
{
// TODO: Implement
StartRelayConnection();
}
}

}
m_relayClient = client;
OnReceiveMessage(MessageType.LobbyUserStatus, UserStatus.Lobby);
if (m_localUser.IsHost)
CompleteRelayConnection();
else
Debug.Log("Client is now waiting for approval...");
}
}

if (m_localLobby != null && m_localLobby.LobbyID == lobbyId && !string.IsNullOrEmpty(lobbyId)) // Ensure we didn't leave the lobby during this waiting period.
doConnection?.Invoke();
}
private void ConfirmApproval()
{
if (!m_localUser.IsHost && m_localUser.IsApproved)
{
CompleteRelayConnection();
StartVivoxJoin();
}
}
private void CompleteRelayConnection()
{
OnReceiveMessage(MessageType.LobbyUserStatus, UserStatus.Lobby);
}
private void BeginCountDown()

1
Assets/Scripts/Game/LobbyUser.cs


m_data.IsApproved = value;
m_lastChanged = UserMembers.IsApproved;
OnChanged(this);
Locator.Get.Messenger.OnReceiveMessage(MessageType.ClientUserApproved, null);
}
}
}

4
Assets/Scripts/Infrastructure/Messenger.cs


ConfirmInGameState = 11,
DisplayErrorPopup = 12,
SetPlayerSound = 13,
QuickJoin = 14
QuickJoin = 14,
ClientUserApproved = 15,
ClientUserSeekingDisapproval = 16,
}
/// <summary>

17
Assets/Scripts/Lobby/LobbyContentHeartbeat.cs


using System.Collections.Generic;
using System;
using System.Collections.Generic;
using LobbyRemote = Unity.Services.Lobbies.Models.Lobby;
namespace LobbyRelaySample

/// </summary>
public class LobbyContentHeartbeat
public class LobbyContentHeartbeat : IReceiveMessages
{
private LocalLobby m_localLobby;
private LobbyUser m_localUser;

m_localLobby = lobby;
m_localUser = localUser;
Locator.Get.UpdateSlow.Subscribe(OnUpdate, 1.5f);
Locator.Get.Messenger.Subscribe(this);
m_localLobby.onChanged += OnLocalLobbyChanged;
m_shouldPushData = true; // Ensure the initial presence of a new player is pushed to the lobby; otherwise, when a non-host joins, the LocalLobby never receives their data until they push something new.
m_lifetime = 0;

{
m_shouldPushData = false;
Locator.Get.UpdateSlow.Unsubscribe(OnUpdate);
Locator.Get.Messenger.Unsubscribe(this);
if (m_localLobby != null)
m_localLobby.onChanged -= OnLocalLobbyChanged;
m_localLobby = null;

if (string.IsNullOrEmpty(changed.LobbyID)) // When the player leaves, their LocalLobby is cleared out but maintained.
EndTracking();
m_shouldPushData = true;
}
public void OnReceiveMessage(MessageType type, object msg)
{
if (type == MessageType.ClientUserSeekingDisapproval)
{
bool shouldDisapprove = m_localLobby.State != LobbyState.Lobby; // By not refreshing, it's possible to have a lobby in the lobby list UI after its countdown starts and then try joining.
if (shouldDisapprove)
(msg as Action<relay.Approval>)?.Invoke(relay.Approval.GameAlreadyStarted);
}
}
/// <summary>

29
Assets/Scripts/Relay/RelayUtpClient.cs


using System.Collections.Generic;
using Unity.Networking.Transport;
using UnityEngine;
using MsgType = LobbyRelaySample.relay.RelayUtpSetup.MsgType;
public enum Approval { OK = 0, GameAlreadyStarted }
/// <summary>
/// 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.

protected bool m_hasSentInitialMessage = false;
private const float k_heartbeatPeriod = 5;
protected enum MsgType { Ping = 0, NewPlayer, PlayerApprovalState, ReadyState, PlayerName, Emote, StartCountdown, CancelCountdown, ConfirmInGame, EndInGame, PlayerDisconnect }
public virtual void Initialize(NetworkDriver networkDriver, List<NetworkConnection> connections, LobbyUser localUser, LocalLobby localLobby)
{

}
string id = System.Text.Encoding.UTF8.GetString(msgContents.GetRange(2, idLength).ToArray());
if (id == m_localUser.ID || !m_localLobby.LobbyUsers.ContainsKey(id)) // We don't need to hold onto messages if the ID is absent; users are initialized before they send events.
if (!CanProcessDataEventFor(conn, msgType, id))
if (msgType == MsgType.PlayerName)
if (msgType == MsgType.PlayerApprovalState)
{
Approval approval = (Approval)msgContents[0];
if (approval == Approval.OK && !m_localUser.IsApproved)
OnApproved(m_networkDriver, conn);
else if (approval == Approval.GameAlreadyStarted)
Locator.Get.Messenger.OnReceiveMessage(MessageType.DisplayErrorPopup, "Rejected: Game has already started.");
}
else if (msgType == MsgType.PlayerName)
{
int nameLength = msgContents[0];
string name = System.Text.Encoding.UTF8.GetString(msgContents.GetRange(1, nameLength).ToArray());

ProcessDisconnectEvent(conn, strm);
}
protected virtual bool CanProcessDataEventFor(NetworkConnection conn, MsgType type, string id)
{
// Don't react to our own messages. Also, don't need to hold onto messages if the ID is absent; clients should be initialized and in the lobby before they send events.
// (Note that this enforces lobby membership before processing any events besides an approval request, so a client is unable to fully use Relay unless they're in the lobby.)
return id != m_localUser.ID && m_localLobby.LobbyUsers.ContainsKey(id) && (m_localUser.IsApproved || type == MsgType.PlayerApprovalState);
}
protected virtual void ProcessNetworkEventDataAdditional(NetworkConnection conn, MsgType msgType, string id) { }
protected virtual void ProcessDisconnectEvent(NetworkConnection conn, DataStreamReader strm)
{

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.
}
private void OnApproved(NetworkDriver driver, NetworkConnection connection)
{
m_localUser.IsApproved = true;
ForceFullUserUpdate(driver, connection, m_localUser);
}
/// <summary>

39
Assets/Scripts/Relay/RelayUtpHost.cs


using System.Collections.Generic;
using Unity.Networking.Transport;
using MsgType = LobbyRelaySample.relay.RelayUtpSetup.MsgType;
namespace LobbyRelaySample.relay
{

}
/// <summary>
/// When a new client connects, they need to be updated with the current state of everyone else.
/// When a new client connects, first determine if they are allowed to do so.
/// If so, they need to be updated with the current state of everyone else.
/// If not, they should be informed and rejected.
private void OnNewConnection(NetworkConnection conn)
private void OnNewConnection(NetworkConnection conn, string id)
{
new RelayPendingApproval(conn, NewConnectionApprovalResult, id);
}
private void NewConnectionApprovalResult(NetworkConnection conn, Approval result)
{
WriteByte(m_networkDriver, conn, m_localUser.ID, MsgType.PlayerApprovalState, (byte)result);
if (result == Approval.OK && conn.IsCreated)
{
foreach (var user in m_localLobby.LobbyUsers)
ForceFullUserUpdate(m_networkDriver, conn, user.Value);
m_connections.Add(conn);
}
else
{
conn.Disconnect(m_networkDriver);
}
}
protected override bool CanProcessDataEventFor(NetworkConnection conn, MsgType type, string id)
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);
// Don't send through data from one client to everyone else if they haven't been approved yet. (They should also not be sending data if not approved, so this is a backup.)
return base.CanProcessDataEventFor(conn, type, id) && (m_connections.Contains(conn) || type == MsgType.NewPlayer);
}
protected override void ProcessNetworkEventDataAdditional(NetworkConnection conn, MsgType msgType, string id)

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);
else if (msgType == MsgType.NewPlayer)
OnNewConnection(conn, id);
else if (msgType == MsgType.PlayerDisconnect) // Clients message the host when they intend to disconnect, or else the host ends up keeping the connection open.
{
conn.Disconnect(m_networkDriver);

var conn = m_networkDriver.Accept(); // Note that since we pumped the event queue earlier in Update, m_networkDriver has been updated already this frame.
if (!conn.IsCreated) // "Nothing more to accept" is signalled by returning an invalid connection from Accept.
break;
m_connections.Add(conn);
OnNewConnection(conn); // This ensures that clients in editors are sent player state once they establish a connection. The timing differs slightly from builds.
// Although the connection is created (i.e. Accepted), we still need to approve it, which will trigger when receiving the NewPlayer message from that client.
}
}

connection.Disconnect(m_networkDriver); // Note that Lobby won't receive the disconnect immediately, so its auto-disconnect takes 30-40s, if needed.
m_connections.Clear();
m_localLobby.RelayServer = null;
}
}

2
Assets/Scripts/Relay/RelayUtpSetup.cs


protected LobbyUser m_localUser;
protected Action<bool, RelayUtpClient> m_onJoinComplete;
public enum MsgType { Ping = 0, NewPlayer, ReadyState, PlayerName, Emote, StartCountdown, CancelCountdown, ConfirmInGame, EndInGame, PlayerDisconnect }
public void BeginRelayJoin(LocalLobby localLobby, LobbyUser localUser, Action<bool, RelayUtpClient> onJoinComplete)
{
m_localLobby = localLobby;

57
Assets/Scripts/Relay/RelayPendingApproval.cs


using System;
using Unity.Networking.Transport;
namespace LobbyRelaySample.relay
{
/// <summary>
/// The Relay host doesn't need to know what might approve or disapprove of a pending connection, so this will
/// broadcast a message that approval is being sought, and if nothing disapproves, the connection will be permitted.
/// </summary>
public class RelayPendingApproval : IDisposable
{
private NetworkConnection m_pendingConnection;
private bool m_hasDisposed = false;
private const float k_waitTime = 0.1f;
private Action<NetworkConnection, Approval> m_onResult;
public string ID { get; private set; }
public RelayPendingApproval(NetworkConnection conn, Action<NetworkConnection, Approval> onResult, string id)
{
m_pendingConnection = conn;
m_onResult = onResult;
ID = id;
Locator.Get.UpdateSlow.Subscribe(Approve, k_waitTime);
Locator.Get.Messenger.OnReceiveMessage(MessageType.ClientUserSeekingDisapproval, (Action<Approval>)Disapprove);
}
~RelayPendingApproval() { Dispose(); }
private void Approve(float unused)
{
try
{ m_onResult?.Invoke(m_pendingConnection, Approval.OK);
}
finally
{ Dispose();
}
}
public void Disapprove(Approval reason)
{
try
{ m_onResult?.Invoke(m_pendingConnection, reason);
}
finally
{ Dispose();
}
}
public void Dispose()
{
if (!m_hasDisposed)
{
Locator.Get.UpdateSlow.Unsubscribe(Approve);
m_hasDisposed = true;
}
}
}
}

11
Assets/Scripts/Relay/RelayPendingApproval.cs.meta


fileFormatVersion: 2
guid: dbd38eacd3a3f464d8a9a1b69efe37db
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
正在加载...
取消
保存