浏览代码
Consolidating and renaming for readability. This still represents the baseline Relay + UTP behavior, without removing existing Lobby behavior yet or handling edge cases.
/main/staging
Consolidating and renaming for readability. This still represents the baseline Relay + UTP behavior, without removing existing Lobby behavior yet or handling edge cases.
/main/staging
nathaniel.buck@unity3d.com
3 年前
当前提交
d1773d39
共有 13 个文件被更改,包括 340 次插入 和 327 次删除
-
7Assets/Prefabs/UI/RenamePopup.prefab
-
68Assets/Scripts/Entities/GameStateManager.cs
-
76Assets/Scripts/Entities/LobbyUser.cs
-
4Assets/Scripts/LobbyRelaySample.asmdef
-
28Assets/Scripts/Relay/RelayUtpSetup.cs
-
9Packages/packages-lock.json
-
188Assets/Scripts/Relay/RelayUtpClient.cs
-
44Assets/Scripts/Relay/RelayUtpHost.cs
-
51Assets/Scripts/Relay/RelayHost.cs
-
192Assets/Scripts/Relay/RelayUserWatcher.cs
-
0/Assets/Scripts/Relay/RelayUtpHost.cs.meta
-
0/Assets/Scripts/Relay/RelayUtpClient.cs.meta
|
|||
using System.Collections.Generic; |
|||
using Unity.Networking.Transport; |
|||
using UnityEngine; |
|||
using MsgType = LobbyRelaySample.Relay.RelayUtpSetup.MsgType; |
|||
|
|||
namespace LobbyRelaySample.Relay |
|||
{ |
|||
/// <summary>
|
|||
/// This will handle observing the local player and updating remote players over Relay when there are local changes.
|
|||
/// Created after the connection to Relay has been confirmed.
|
|||
/// </summary>
|
|||
public class RelayUtpClient : MonoBehaviour // This is a MonoBehaviour merely to have access to Update.
|
|||
{ |
|||
protected LobbyUser m_localUser; |
|||
protected LocalLobby m_localLobby; |
|||
protected NetworkDriver m_networkDriver; |
|||
protected List<NetworkConnection> m_connections; // For clients, this has just one member, but for hosts it will have more.
|
|||
|
|||
private bool m_hasSentInitialMessage = false; |
|||
|
|||
public void Initialize(NetworkDriver networkDriver, List<NetworkConnection> connections, LobbyUser localUser, LocalLobby localLobby) |
|||
{ |
|||
m_localUser = localUser; |
|||
m_localLobby = localLobby; |
|||
m_localUser.onChanged += OnLocalChange; |
|||
m_networkDriver = networkDriver; |
|||
m_connections = connections; |
|||
|
|||
if (this is RelayUtpHost) // The host will be alone in the lobby at first, so they need not send any messages right away.
|
|||
m_hasSentInitialMessage = true; |
|||
} |
|||
public void OnDestroy() |
|||
{ |
|||
m_localUser.onChanged -= OnLocalChange; |
|||
} |
|||
|
|||
private void OnLocalChange(LobbyUser localUser) |
|||
{ |
|||
if (m_connections.Count == 0) // This could be the case for the host alone in the lobby.
|
|||
return; |
|||
m_networkDriver.ScheduleUpdate().Complete(); |
|||
foreach (NetworkConnection conn in m_connections) |
|||
DoUserUpdate(m_networkDriver, conn); // TODO: Hmm...I don't think this ends up working if the host has to manually transmit changes over all connections.
|
|||
} |
|||
|
|||
public void Update() |
|||
{ |
|||
OnUpdate(); |
|||
} |
|||
|
|||
protected virtual void OnUpdate() |
|||
{ |
|||
m_networkDriver.ScheduleUpdate().Complete(); // This pumps all messages, which pings the Relay allocation and keeps it alive.
|
|||
ReceiveNetworkEvents(m_networkDriver, m_connections); |
|||
if (!m_hasSentInitialMessage) |
|||
SendInitialMessage(m_networkDriver, m_connections[0]); |
|||
} |
|||
|
|||
private void ReceiveNetworkEvents(NetworkDriver driver, List<NetworkConnection> connections) // TODO: Just the one connection. Also not NativeArray.
|
|||
{ |
|||
DataStreamReader strm; |
|||
NetworkEvent.Type cmd; |
|||
foreach (NetworkConnection connection in connections) |
|||
{ |
|||
while ((cmd = connection.PopEvent(driver, out strm)) != NetworkEvent.Type.Empty) |
|||
{ |
|||
ProcessNetworkEvent(strm, cmd); |
|||
} |
|||
} |
|||
} |
|||
|
|||
private void ProcessNetworkEvent(DataStreamReader strm, NetworkEvent.Type cmd) |
|||
{ |
|||
if (cmd == NetworkEvent.Type.Data) |
|||
{ |
|||
MsgType msgType = (MsgType)strm.ReadByte(); |
|||
string id = ReadLengthAndString(ref strm); |
|||
if (id == m_localUser.ID || !m_localLobby.LobbyUsers.ContainsKey(id)) |
|||
return; |
|||
|
|||
if (msgType == MsgType.PlayerName) |
|||
{ |
|||
string name = ReadLengthAndString(ref strm); |
|||
m_localLobby.LobbyUsers[id].DisplayName = name; |
|||
Debug.LogError("User id " + id + " is named " + name); |
|||
} |
|||
else if (msgType == MsgType.Emote) |
|||
{ |
|||
EmoteType emote = (EmoteType)strm.ReadByte(); |
|||
m_localLobby.LobbyUsers[id].Emote = emote; |
|||
Debug.LogError("User id " + id + " has emote " + emote.ToString()); |
|||
} |
|||
else if (msgType == MsgType.ReadyState) |
|||
{ |
|||
UserStatus status = (UserStatus)strm.ReadByte(); |
|||
m_localLobby.LobbyUsers[id].UserStatus = status; |
|||
Debug.LogError("User id " + id + " has state " + status.ToString()); |
|||
} |
|||
ProcessNetworkEventDataAdditional(strm, cmd, msgType, id); |
|||
} |
|||
} |
|||
|
|||
protected virtual void ProcessNetworkEventDataAdditional(DataStreamReader strm, NetworkEvent.Type cmd, MsgType msgType, string id) { } |
|||
|
|||
unsafe private string ReadLengthAndString(ref DataStreamReader strm) |
|||
{ |
|||
byte length = strm.ReadByte(); |
|||
byte[] bytes = new byte[length]; |
|||
fixed (byte* ptr = bytes) |
|||
{ |
|||
strm.ReadBytes(ptr, length); |
|||
} |
|||
return System.Text.Encoding.UTF8.GetString(bytes); |
|||
} |
|||
|
|||
private void SendInitialMessage(NetworkDriver driver, NetworkConnection connection) |
|||
{ |
|||
DoUserUpdate(driver, connection); // Assuming this is only created after the Relay connection is successful.
|
|||
m_hasSentInitialMessage = true; |
|||
} |
|||
|
|||
private void DoUserUpdate(NetworkDriver driver, NetworkConnection connection) |
|||
{ |
|||
// Only update with actual changes. (If multiple change at once, we send messages for each separately, but that shouldn't happen often.)
|
|||
if (0 < (m_localUser.LastChanged & LobbyUser.UserMembers.DisplayName)) |
|||
WriteString(driver, connection, MsgType.PlayerName, m_localUser.DisplayName); |
|||
if (0 < (m_localUser.LastChanged & LobbyUser.UserMembers.Emote)) |
|||
WriteByte(driver, connection, MsgType.Emote, (byte)m_localUser.Emote); |
|||
if (0 < (m_localUser.LastChanged & LobbyUser.UserMembers.UserStatus)) |
|||
WriteByte(driver, connection, MsgType.ReadyState, (byte)m_localUser.UserStatus); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Write string data as: [1 byte: msgType][1 byte: id length N][N bytes: id][1 byte: string length M][M bytes: string]
|
|||
/// </summary>
|
|||
private void WriteString(NetworkDriver driver, NetworkConnection connection, MsgType msgType, string str) |
|||
{ |
|||
byte[] idBytes = System.Text.Encoding.UTF8.GetBytes(m_localUser.ID); |
|||
byte[] strBytes = System.Text.Encoding.UTF8.GetBytes(str); |
|||
|
|||
List<byte> message = new List<byte>(idBytes.Length + strBytes.Length + 3); |
|||
message.Add((byte)msgType); |
|||
message.Add((byte)idBytes.Length); |
|||
message.AddRange(idBytes); |
|||
message.Add((byte)strBytes.Length); |
|||
message.AddRange(strBytes); |
|||
|
|||
if (driver.BeginSend(connection, out var dataStream) == 0) // Oh, should check this first?
|
|||
{ |
|||
byte[] bytes = message.ToArray(); |
|||
unsafe |
|||
{ |
|||
fixed (byte* bytesPtr = bytes) |
|||
{ |
|||
dataStream.WriteBytes(bytesPtr, message.Count); |
|||
driver.EndSend(dataStream); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Write byte data as: [1 byte: msgType][1 byte: id length N][N bytes: id][1 byte: data]
|
|||
/// </summary>
|
|||
private void WriteByte(NetworkDriver driver, NetworkConnection connection, MsgType msgType, byte value) |
|||
{ |
|||
byte[] idBytes = System.Text.Encoding.UTF8.GetBytes(m_localUser.ID); |
|||
List<byte> message = new List<byte>(idBytes.Length + 3); |
|||
message.Add((byte)msgType); |
|||
message.Add((byte)idBytes.Length); |
|||
message.AddRange(idBytes); |
|||
message.Add(value); |
|||
|
|||
if (driver.BeginSend(connection, out var dataStream) == 0) // Oh, should check this first?
|
|||
{ |
|||
byte[] bytes = message.ToArray(); |
|||
unsafe |
|||
{ |
|||
fixed (byte* bytesPtr = bytes) |
|||
{ |
|||
dataStream.WriteBytes(bytesPtr, message.Count); |
|||
driver.EndSend(dataStream); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
} |
|||
} |
|
|||
using Unity.Networking.Transport; |
|||
using MsgType = LobbyRelaySample.Relay.RelayUtpSetup.MsgType; |
|||
|
|||
namespace LobbyRelaySample.Relay |
|||
{ |
|||
public class RelayUtpHost : RelayUtpClient |
|||
{ |
|||
protected override void OnUpdate() |
|||
{ |
|||
base.OnUpdate(); |
|||
DoHeartbeat(); |
|||
} |
|||
|
|||
protected override void ProcessNetworkEventDataAdditional(DataStreamReader strm, NetworkEvent.Type cmd, MsgType msgType, string id) |
|||
{ |
|||
// Note that the strm contents might have already been consumed, depending on the msgType.
|
|||
if (msgType == MsgType.ReadyState) |
|||
{ |
|||
// TODO: Check if all players have readied.
|
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Clean out destroyed connections, and accept all new ones.
|
|||
/// </summary>
|
|||
private void DoHeartbeat() |
|||
{ |
|||
m_networkDriver.ScheduleUpdate().Complete(); |
|||
|
|||
for (int c = m_connections.Count - 1; c >= 0; c--) |
|||
{ |
|||
if (!m_connections[c].IsCreated) |
|||
m_connections.RemoveAt(c); |
|||
} |
|||
while (true) |
|||
{ |
|||
var con = m_networkDriver.Accept(); |
|||
if (!con.IsCreated) // "Nothing more to accept" is signalled by returning an invalid connection from Accept.
|
|||
break; |
|||
m_connections.Add(con); |
|||
} |
|||
} |
|||
} |
|||
} |
|
|||
using System; |
|||
using System.Collections; |
|||
using System.Collections.Generic; |
|||
using Unity.Collections; |
|||
using Unity.Jobs; |
|||
using Unity.Networking.Transport; |
|||
using Unity.Networking.Transport.Relay; |
|||
using Unity.Services.Relay.Models; |
|||
using UnityEngine; |
|||
|
|||
using MsgType = LobbyRelaySample.Relay.RelayUTPSetup.MsgType; |
|||
|
|||
namespace LobbyRelaySample.Relay |
|||
{ |
|||
public class RelayHost : RelayUserWatcher |
|||
{ |
|||
protected override void OnUpdate() |
|||
{ |
|||
base.OnUpdate(); |
|||
DoHeartbeat(); |
|||
} |
|||
|
|||
protected override void ProcessNetworkEvent(DataStreamReader strm, NetworkEvent.Type cmd) |
|||
{ |
|||
base.ProcessNetworkEvent(strm, cmd); |
|||
// TODO: The only thing this has to care about is if all players have readied up.
|
|||
} |
|||
|
|||
private void DoHeartbeat() |
|||
{ |
|||
// Update the driver should be the first job in the chain
|
|||
m_networkDriver.ScheduleUpdate().Complete(); |
|||
// Remove connections which have been destroyed from the list of active connections
|
|||
for (int c = m_connections.Count - 1; c >= 0; c--) |
|||
{ |
|||
if (!m_connections[c].IsCreated) |
|||
m_connections.RemoveAtSwapBack(c); |
|||
} |
|||
|
|||
// Accept all new connections
|
|||
while (true) |
|||
{ |
|||
var con = m_networkDriver.Accept(); |
|||
// "Nothing more to accept" is signaled by returning an invalid connection from accept
|
|||
if (!con.IsCreated) |
|||
break; |
|||
m_connections.Add(con); |
|||
} |
|||
} |
|||
} |
|||
} |
|
|||
using System; |
|||
using System.Collections; |
|||
using System.Collections.Generic; |
|||
using Unity.Collections; |
|||
using Unity.Jobs; |
|||
using Unity.Networking.Transport; |
|||
using Unity.Networking.Transport.Relay; |
|||
using Unity.Services.Relay.Models; |
|||
using UnityEngine; |
|||
|
|||
using MsgType = LobbyRelaySample.Relay.RelayUTPSetup.MsgType; |
|||
|
|||
namespace LobbyRelaySample.Relay |
|||
{ |
|||
/// <summary>
|
|||
/// This will handle observing the local user and updating remote users over Relay when there are local changes.
|
|||
/// Created after the connection to Relay has been confirmed.
|
|||
/// </summary>
|
|||
public class RelayUserWatcher : MonoBehaviour, IDisposable |
|||
{ |
|||
protected LobbyUser m_localUser; |
|||
protected LocalLobby m_localLobby; |
|||
private bool m_hasDisposed = false; |
|||
protected NetworkDriver m_networkDriver; |
|||
protected List<NetworkConnection> m_connections; // TODO: Make it clearer that this is just the one member?
|
|||
|
|||
private bool m_hasSentInitialMessage = false; |
|||
|
|||
public void Initialize(NetworkDriver networkDriver, List<NetworkConnection> connections, LobbyUser localUser, LocalLobby localLobby) |
|||
{ |
|||
m_localUser = localUser; |
|||
m_localLobby = localLobby; |
|||
m_localUser.onChanged += OnLocalChange; // TODO: This should break up the state type?
|
|||
m_networkDriver = networkDriver; |
|||
m_connections = connections; |
|||
} |
|||
public void Dispose() |
|||
{ |
|||
if (!m_hasDisposed) |
|||
{ |
|||
m_localUser.onChanged -= OnLocalChange; |
|||
m_hasDisposed = true; |
|||
} |
|||
} |
|||
~RelayUserWatcher() { Dispose(); } // TODO: Disposable or MonoBehaviour?
|
|||
|
|||
private void OnLocalChange(LobbyUser localUser) |
|||
{ |
|||
if (m_connections.Count == 0) // Could be the case for the server, should probably actually break that out after all?
|
|||
return; |
|||
m_networkDriver.ScheduleUpdate().Complete(); |
|||
DoUserUpdate(m_networkDriver, m_connections[0]); // TODO: Hmm...I don't think this ends up working if the host has to manually transmit changes over all connections.
|
|||
} |
|||
|
|||
public void Update() |
|||
{ |
|||
OnUpdate(); |
|||
} |
|||
|
|||
protected virtual void OnUpdate() |
|||
{ |
|||
m_networkDriver.ScheduleUpdate().Complete(); |
|||
ReceiveNetworkEvents(m_networkDriver, m_connections); |
|||
if (!m_hasSentInitialMessage && !(this is RelayHost)) |
|||
SendInitialMessage(m_networkDriver, m_connections[0]); |
|||
} |
|||
|
|||
private void ReceiveNetworkEvents(NetworkDriver driver, List<NetworkConnection> connections) // TODO: Just the one connection. Also not NativeArray.
|
|||
{ |
|||
DataStreamReader strm; |
|||
NetworkEvent.Type cmd; |
|||
foreach (NetworkConnection connection in connections) |
|||
{ |
|||
while ((cmd = connection.PopEvent(driver, out strm)) != NetworkEvent.Type.Empty) |
|||
{ |
|||
ProcessNetworkEvent(strm, cmd); |
|||
} |
|||
} |
|||
} |
|||
|
|||
protected virtual void ProcessNetworkEvent(DataStreamReader strm, NetworkEvent.Type cmd) |
|||
{ |
|||
if (cmd == NetworkEvent.Type.Data) |
|||
{ |
|||
MsgType msgType = (MsgType)strm.ReadByte(); |
|||
string id = ReadLengthAndString(ref strm); |
|||
if (id == m_localUser.ID || !m_localLobby.LobbyUsers.ContainsKey(id)) |
|||
return; |
|||
|
|||
if (msgType == MsgType.PlayerName) |
|||
{ |
|||
string name = ReadLengthAndString(ref strm); |
|||
m_localLobby.LobbyUsers[id].DisplayName = name; |
|||
Debug.LogError("User id " + id + " is named " + name); |
|||
} |
|||
else if (msgType == MsgType.Emote) |
|||
{ |
|||
EmoteType emote = (EmoteType)strm.ReadByte(); |
|||
m_localLobby.LobbyUsers[id].Emote = emote; |
|||
Debug.LogError("User id " + id + " has emote " + emote.ToString()); |
|||
} |
|||
else if (msgType == MsgType.ReadyState) |
|||
{ |
|||
UserStatus status = (UserStatus)strm.ReadByte(); |
|||
m_localLobby.LobbyUsers[id].UserStatus = status; |
|||
Debug.LogError("User id " + id + " has state " + status.ToString()); |
|||
} |
|||
} |
|||
} |
|||
|
|||
unsafe private string ReadLengthAndString(ref DataStreamReader strm) |
|||
{ |
|||
byte length = strm.ReadByte(); |
|||
byte[] bytes = new byte[length]; |
|||
fixed (byte* ptr = bytes) |
|||
{ |
|||
strm.ReadBytes(ptr, length); |
|||
} |
|||
return System.Text.Encoding.UTF8.GetString(bytes); |
|||
} |
|||
|
|||
private void SendInitialMessage(NetworkDriver driver, NetworkConnection connection) |
|||
{ |
|||
// Assuming this is only created after the Relay connection is successful.
|
|||
// TODO: Retry logic for that?
|
|||
DoUserUpdate(driver, connection); |
|||
m_hasSentInitialMessage = true; |
|||
} |
|||
|
|||
private void DoUserUpdate(NetworkDriver driver, NetworkConnection connection) |
|||
{ |
|||
// TODO: Combine these all into one message, if I'm just going to send them all each time anyway.
|
|||
|
|||
WriteString(driver, connection, MsgType.PlayerName, m_localUser.DisplayName); |
|||
WriteByte(driver, connection, MsgType.Emote, (byte)m_localUser.Emote); |
|||
WriteByte(driver, connection, MsgType.ReadyState, (byte)m_localUser.UserStatus); |
|||
} |
|||
|
|||
// TODO: We do have a character limit on the name entry field, right?
|
|||
|
|||
// Msg type, ID length, ID, str length, str
|
|||
// Not doing bit packing.
|
|||
private void WriteString(NetworkDriver driver, NetworkConnection connection, MsgType msgType, string str) |
|||
{ |
|||
byte[] idBytes = System.Text.Encoding.UTF8.GetBytes(m_localUser.ID); |
|||
byte[] strBytes = System.Text.Encoding.UTF8.GetBytes(str); |
|||
|
|||
List<byte> message = new List<byte>(idBytes.Length + strBytes.Length + 3); |
|||
message.Add((byte)msgType); |
|||
message.Add((byte)idBytes.Length); |
|||
message.AddRange(idBytes); |
|||
message.Add((byte)strBytes.Length); |
|||
message.AddRange(strBytes); |
|||
|
|||
if (driver.BeginSend(connection, out var dataStream) == 0) // Oh, should check this first?
|
|||
{ |
|||
byte[] bytes = message.ToArray(); |
|||
unsafe |
|||
{ |
|||
fixed (byte* bytesPtr = bytes) |
|||
{ |
|||
dataStream.WriteBytes(bytesPtr, message.Count); |
|||
driver.EndSend(dataStream); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
|
|||
private void WriteByte(NetworkDriver driver, NetworkConnection connection, MsgType msgType, byte value) |
|||
{ |
|||
byte[] idBytes = System.Text.Encoding.UTF8.GetBytes(m_localUser.ID); |
|||
List<byte> message = new List<byte>(idBytes.Length + 3); |
|||
message.Add((byte)msgType); |
|||
message.Add((byte)idBytes.Length); |
|||
message.AddRange(idBytes); |
|||
message.Add(value); |
|||
|
|||
if (driver.BeginSend(connection, out var dataStream) == 0) // Oh, should check this first?
|
|||
{ |
|||
byte[] bytes = message.ToArray(); |
|||
unsafe |
|||
{ |
|||
fixed (byte* bytesPtr = bytes) |
|||
{ |
|||
dataStream.WriteBytes(bytesPtr, message.Count); |
|||
driver.EndSend(dataStream); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
} |
|||
} |
撰写
预览
正在加载...
取消
保存
Reference in new issue