浏览代码
Still partial progress. I have a little handoff going where the client identifies itself to the server and the server responds with an ID to use in further communication. However, I don't think that will actually work since all clients would also need those IDs? I can just send the user ID, which takes up more bandwidth but not dramatically so. Also, this pinpoints the heartbeat for keeping the Relay allocation up. I'm trying to pull out all the job system things, since that both seems like an extra layer of complexity that devs would have to learn and it also isn't, like, *actually* used correctly in the sample?
/main/staging
Still partial progress. I have a little handoff going where the client identifies itself to the server and the server responds with an ID to use in further communication. However, I don't think that will actually work since all clients would also need those IDs? I can just send the user ID, which takes up more bandwidth but not dramatically so. Also, this pinpoints the heartbeat for keeping the Relay allocation up. I'm trying to pull out all the job system things, since that both seems like an extra layer of complexity that devs would have to learn and it also isn't, like, *actually* used correctly in the sample?
/main/staging
nathaniel.buck@unity3d.com
3 年前
当前提交
678fd232
共有 14 个文件被更改,包括 505 次插入 和 117 次删除
-
8Assets/Prefabs/UI/PlayerInteractionPanel.prefab
-
8Assets/Scripts/Entities/GameStateManager.cs
-
6Assets/Scripts/Entities/LobbyUser.cs
-
9Assets/Scripts/Lobby/ToLocalLobby.cs
-
6Assets/Scripts/Relay/RelayInterface.cs
-
194Assets/Scripts/Relay/RelayUtpSetup.cs
-
4Assets/Scripts/UI/EmoteButtonUI.cs
-
2Assets/Scripts/UI/InLobbyUserUI.cs
-
17Assets/Scripts/Entities/EmoteType.cs
-
11Assets/Scripts/Entities/EmoteType.cs.meta
-
131Assets/Scripts/Relay/RelayHost.cs
-
11Assets/Scripts/Relay/RelayHost.cs.meta
-
204Assets/Scripts/Relay/RelayUserWatcher.cs
-
11Assets/Scripts/Relay/RelayUserWatcher.cs.meta
|
|||
namespace LobbyRelaySample |
|||
{ |
|||
public enum EmoteType { None = 0, Smile, Frown, Shock, Laugh } |
|||
|
|||
public static class EmoteTypeExtensions |
|||
{ |
|||
public static string GetString(this EmoteType emote) |
|||
{ |
|||
return |
|||
emote == EmoteType.Smile ? ":D" : |
|||
emote == EmoteType.Frown ? ":(" : |
|||
emote == EmoteType.Shock ? ":O" : |
|||
emote == EmoteType.Laugh ? "XD" : |
|||
""; |
|||
} |
|||
} |
|||
} |
|
|||
fileFormatVersion: 2 |
|||
guid: 7aacd044390df3c4ca5cd037fec35483 |
|||
MonoImporter: |
|||
externalObjects: {} |
|||
serializedVersion: 2 |
|||
defaultReferences: [] |
|||
executionOrder: 0 |
|||
icon: {instanceID: 0} |
|||
userData: |
|||
assetBundleName: |
|||
assetBundleVariant: |
|
|||
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 : MonoBehaviour // TODO: Should it be a child of the client version?
|
|||
{ |
|||
private int m_userIdNext = 0; |
|||
private LocalLobby m_localLobby; |
|||
private Dictionary<int, LobbyUser> m_usersById = new Dictionary<int, LobbyUser>(); |
|||
protected NetworkDriver m_networkDriver; |
|||
protected NativeList<NetworkConnection> m_connections; |
|||
|
|||
public void Initialize(NetworkDriver networkDriver, NativeList<NetworkConnection> connections, LocalLobby localLobby) |
|||
{ |
|||
m_networkDriver = networkDriver; |
|||
m_connections = connections; |
|||
m_localLobby = localLobby; |
|||
} |
|||
|
|||
public void Update() |
|||
{ |
|||
DoHeartbeat(); |
|||
ReceiveNetworkEvents(m_networkDriver.ToConcurrent(), m_connections); |
|||
} |
|||
|
|||
private void ReceiveNetworkEvents(NetworkDriver.Concurrent driver, NativeArray<NetworkConnection> connections) // TODO: Need the concurrent?
|
|||
{ |
|||
DataStreamReader strm; |
|||
NetworkEvent.Type cmd; |
|||
foreach (NetworkConnection connection in connections) |
|||
{ |
|||
while ((cmd = driver.PopEventForConnection(connection, out strm)) != NetworkEvent.Type.Empty) |
|||
{ |
|||
if (cmd == NetworkEvent.Type.Connect) |
|||
{ |
|||
|
|||
} |
|||
else if (cmd == NetworkEvent.Type.Data) |
|||
{ |
|||
// TODO: Update other users' data, with a shared mechanism with servers.
|
|||
|
|||
byte header = strm.ReadByte(); |
|||
MsgType msgType = (MsgType)(header % 8); |
|||
header = (byte)(header >> 3); |
|||
int playerId = header; |
|||
|
|||
if (msgType == MsgType.NewPlayer) |
|||
{ |
|||
byte length = strm.ReadByte(); |
|||
byte[] idBytes = new byte[length]; |
|||
unsafe |
|||
{ |
|||
fixed(byte* idPtr = idBytes) |
|||
{ |
|||
strm.ReadBytes(idPtr, length); |
|||
} |
|||
} |
|||
string id = System.Text.Encoding.UTF8.GetString(idBytes); |
|||
Debug.LogWarning("Received ID: " + id); |
|||
|
|||
if (playerId == 0) |
|||
{ |
|||
// New player, which we could detect in the Connect event but only insofar as the connection is associated with it, but we need to find the LobbyUser.
|
|||
foreach (var user in m_localLobby.LobbyUsers) |
|||
{ |
|||
if (user.Value.ID != id) |
|||
continue; |
|||
int idShort = ++m_userIdNext; |
|||
m_usersById.Add(idShort, user.Value); |
|||
playerId = idShort; |
|||
break; |
|||
} |
|||
} |
|||
Debug.LogWarning("Providing a player ID of " + ((byte)playerId)); |
|||
SendMessageBytes(m_networkDriver.ToConcurrent(), connection, GetHeaderByte(MsgType.ProvidePlayerId), (byte)playerId); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
} |
|||
|
|||
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.Length - 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); |
|||
} |
|||
} |
|||
|
|||
private byte GetHeaderByte(MsgType msgType) |
|||
{ |
|||
return (byte)msgType; // The host doesn't have a player ID.
|
|||
} |
|||
|
|||
unsafe private void SendMessageBytes(NetworkDriver.Concurrent driver, NetworkConnection connection, params byte[] msg) |
|||
{ |
|||
if (driver.BeginSend(connection, out var writeData) == 0) |
|||
{ |
|||
fixed (byte* msgPtr = msg) |
|||
{ |
|||
writeData.WriteBytes(msgPtr, msg.Length); |
|||
driver.EndSend(writeData); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
} |
|
|||
fileFormatVersion: 2 |
|||
guid: 567f8fffb5a28a446b1e98cbd2510b0f |
|||
MonoImporter: |
|||
externalObjects: {} |
|||
serializedVersion: 2 |
|||
defaultReferences: [] |
|||
executionOrder: 0 |
|||
icon: {instanceID: 0} |
|||
userData: |
|||
assetBundleName: |
|||
assetBundleVariant: |
|
|||
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 |
|||
{ |
|||
private LobbyUser m_localUser; |
|||
private bool m_hasDisposed = false; |
|||
private NetworkDriver m_networkDriver; |
|||
private NativeList<NetworkConnection> m_connections; // TODO: Make it clearer that this is just the one member?
|
|||
private JobHandle m_mostRecentJob; |
|||
|
|||
private int? m_playerId = null; // Provided by the host.
|
|||
private bool m_hasSentInitialMessage = false; |
|||
|
|||
public void Initialize(NetworkDriver networkDriver, NativeList<NetworkConnection> connections, LobbyUser localUser) |
|||
{ |
|||
m_localUser = localUser; |
|||
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_mostRecentJob.IsCompleted)
|
|||
// m_mostRecentJob.Complete();
|
|||
|
|||
m_networkDriver.ScheduleUpdate().Complete(); |
|||
|
|||
DoUserUpdate(m_networkDriver, m_connections); |
|||
|
|||
//UserUpdateJob userUpdateJob = new UserUpdateJob
|
|||
//{
|
|||
// driver = m_networkDriver,
|
|||
// connection = m_connections.AsArray(),
|
|||
// myName = new NativeArray<byte>(System.Text.Encoding.UTF8.GetBytes(m_localUser.DisplayName), Allocator.TempJob)
|
|||
//};
|
|||
//m_mostRecentJob = userUpdateJob.Schedule();
|
|||
|
|||
// TODO: Force complete on disconnect
|
|||
} |
|||
|
|||
public void Update() |
|||
{ |
|||
m_networkDriver.ScheduleUpdate().Complete(); |
|||
ReceiveNetworkEvents(m_networkDriver, m_connections); |
|||
if (!m_hasSentInitialMessage) |
|||
SendInitialMessage(m_networkDriver, m_connections); |
|||
} |
|||
|
|||
private void ReceiveNetworkEvents(NetworkDriver driver, NativeArray<NetworkConnection> connection) // TODO: Just the one connection.
|
|||
{ |
|||
DataStreamReader strm; |
|||
NetworkEvent.Type cmd; |
|||
while (connection.Length > 0 && (cmd = connection[0].PopEvent(driver, out strm)) != NetworkEvent.Type.Empty) |
|||
{ |
|||
if (cmd == NetworkEvent.Type.Data) |
|||
{ |
|||
// TODO: Update other users' data, with a shared mechanism with servers.
|
|||
|
|||
byte header = strm.ReadByte(); |
|||
MsgType msgType = (MsgType)(header % 8); |
|||
header = (byte)(header >> 3); |
|||
int playerId = header; |
|||
|
|||
if (msgType == MsgType.ProvidePlayerId) |
|||
{ |
|||
byte id = strm.ReadByte(); |
|||
m_playerId = id; |
|||
Debug.LogError("Received an ID! " + id); |
|||
// Now, we can send all our info.
|
|||
WriteString(driver, connection, MsgType.PlayerName, m_localUser.DisplayName); |
|||
// TODO: Send all of it.
|
|||
|
|||
} |
|||
} |
|||
} |
|||
} |
|||
|
|||
private void SendInitialMessage(NetworkDriver driver, NativeArray<NetworkConnection> connection) |
|||
{ |
|||
// Assuming this is only created after the Relay connection is successful.
|
|||
// TODO: Retry logic for that?
|
|||
WriteString(driver, connection, MsgType.NewPlayer, m_localUser.ID); |
|||
m_hasSentInitialMessage = true; |
|||
} |
|||
|
|||
private void DoUserUpdate(NetworkDriver driver, NativeArray<NetworkConnection> connection) |
|||
{ |
|||
// Process all events on the connection. If the connection is invalid it will return Empty immediately
|
|||
//while (connection.Length > 0 && (cmd = connection[0].PopEvent(driver, out strm)) != NetworkEvent.Type.Empty)
|
|||
{ |
|||
//if (cmd == NetworkEvent.Type.Connect)
|
|||
//{
|
|||
// WriteName(driver, connection);
|
|||
//}
|
|||
// TODO: Update other clients' local data
|
|||
|
|||
//else if (cmd == NetworkEvent.Type.Disconnect)
|
|||
//{
|
|||
// // If the server disconnected us we clear out connection
|
|||
// connection[0] = default(NetworkConnection);
|
|||
//}
|
|||
} |
|||
} |
|||
|
|||
// 3-bit message type + 5-bit ID length, 1-byte str length, id, msg
|
|||
private void WriteString(NetworkDriver driver, NativeArray<NetworkConnection> connection, MsgType msgType, string str) |
|||
{ |
|||
byte[] strBytes = System.Text.Encoding.UTF8.GetBytes(str); |
|||
if (strBytes == null || strBytes.Length == 0) |
|||
return; |
|||
byte header = (byte)(((m_playerId ?? 0) << 5) + msgType); |
|||
byte msgLength = (byte)strBytes.Length; // TODO: We do have a character limit on the name entry field, right?
|
|||
List<byte> message = new List<byte>(strBytes.Length + 2); |
|||
message.Add(header); |
|||
message.Add(msgLength); |
|||
message.AddRange(strBytes); |
|||
|
|||
if (driver.BeginSend(connection[0], 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 struct UserUpdateJob : IJob |
|||
{ |
|||
public NetworkDriver driver; |
|||
public NativeArray<NetworkConnection> connection; // TODO: I think we were using NativeArray to merely contain one entry, since we'd be unable to pass just that via jobs?
|
|||
public NativeArray<byte> myName; |
|||
|
|||
|
|||
public void Execute() |
|||
{ |
|||
DataStreamReader strm; |
|||
NetworkEvent.Type cmd; |
|||
// Process all events on the connection. If the connection is invalid it will return Empty immediately
|
|||
while (connection.Length > 0 && (cmd = connection[0].PopEvent(driver, out strm)) != NetworkEvent.Type.Empty) |
|||
{ |
|||
if (cmd == NetworkEvent.Type.Connect) |
|||
{ |
|||
// Same as name sending.
|
|||
if (myName == null || myName.Length == 0) |
|||
return; |
|||
List<byte> message = new List<byte>(myName.Length + 1); |
|||
message.AddRange(myName); |
|||
byte header = (byte) ((((int)RelayUTPSetup.MsgType.PlayerName) << 5) + myName.Length); // TODO: Truncate length.
|
|||
message.Insert(0, header); |
|||
|
|||
if (driver.BeginSend(connection[0], out var connectData) == 0) // Oh, should check this first?
|
|||
{ |
|||
byte[] bytes = message.ToArray(); |
|||
unsafe |
|||
{ |
|||
fixed (byte* bytesPtr = bytes) |
|||
{ |
|||
connectData.WriteBytes(bytesPtr, message.Count); |
|||
driver.EndSend(connectData); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
else if (cmd == NetworkEvent.Type.Disconnect) |
|||
{ |
|||
// If the server disconnected us we clear out connection
|
|||
connection[0] = default(NetworkConnection); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
} |
|||
} |
|
|||
fileFormatVersion: 2 |
|||
guid: 57add7ba7b318f04a8781c247344cab8 |
|||
MonoImporter: |
|||
externalObjects: {} |
|||
serializedVersion: 2 |
|||
defaultReferences: [] |
|||
executionOrder: 0 |
|||
icon: {instanceID: 0} |
|||
userData: |
|||
assetBundleName: |
|||
assetBundleVariant: |
撰写
预览
正在加载...
取消
保存
Reference in new issue