浏览代码

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 次删除
  1. 8
      Assets/Prefabs/UI/PlayerInteractionPanel.prefab
  2. 8
      Assets/Scripts/Entities/GameStateManager.cs
  3. 6
      Assets/Scripts/Entities/LobbyUser.cs
  4. 9
      Assets/Scripts/Lobby/ToLocalLobby.cs
  5. 6
      Assets/Scripts/Relay/RelayInterface.cs
  6. 194
      Assets/Scripts/Relay/RelayUtpSetup.cs
  7. 4
      Assets/Scripts/UI/EmoteButtonUI.cs
  8. 2
      Assets/Scripts/UI/InLobbyUserUI.cs
  9. 17
      Assets/Scripts/Entities/EmoteType.cs
  10. 11
      Assets/Scripts/Entities/EmoteType.cs.meta
  11. 131
      Assets/Scripts/Relay/RelayHost.cs
  12. 11
      Assets/Scripts/Relay/RelayHost.cs.meta
  13. 204
      Assets/Scripts/Relay/RelayUserWatcher.cs
  14. 11
      Assets/Scripts/Relay/RelayUserWatcher.cs.meta

8
Assets/Prefabs/UI/PlayerInteractionPanel.prefab


m_Script: {fileID: 11500000, guid: 389b6bc508595ea4c920cbaa3d21fc54, type: 3}
m_Name:
m_EditorClassIdentifier:
m_EmoteTextElement: {fileID: 1691307862614502844}
m_emoteType: 3
--- !u!1 &2988716332856923472
GameObject:
m_ObjectHideFlags: 0

m_Script: {fileID: 11500000, guid: 389b6bc508595ea4c920cbaa3d21fc54, type: 3}
m_Name:
m_EditorClassIdentifier:
m_EmoteTextElement: {fileID: 5779364087768960612}
m_emoteType: 1
--- !u!1 &4786508190616014760
GameObject:
m_ObjectHideFlags: 0

m_Script: {fileID: 11500000, guid: 389b6bc508595ea4c920cbaa3d21fc54, type: 3}
m_Name:
m_EditorClassIdentifier:
m_EmoteTextElement: {fileID: 7328874090116149001}
m_emoteType: 4
--- !u!1 &6153877740728873503
GameObject:
m_ObjectHideFlags: 0

m_Script: {fileID: 11500000, guid: 389b6bc508595ea4c920cbaa3d21fc54, type: 3}
m_Name:
m_EditorClassIdentifier:
m_EmoteTextElement: {fileID: 4633941056946047683}
m_emoteType: 2
--- !u!1 &7505011296878837543
GameObject:
m_ObjectHideFlags: 0

8
Assets/Scripts/Entities/GameStateManager.cs


}
else if (type == MessageType.UserSetEmote)
{
var emote = (string)msg;
EmoteType emote = (EmoteType)msg;
m_localUser.Emote = emote;
}
else if (type == MessageType.ChangeLobbyUserState)

if (m_localUser.IsHost)
{
m_RelaySetup = gameObject.AddComponent<RelayUtpSetup_Host>();
(m_RelaySetup as RelayUtpSetup_Host).BeginRelayJoin(m_localLobby);
m_RelaySetup.BeginRelayJoin(m_localLobby, m_localUser);
(m_RelaySetup as RelayUtpSetup_Client).BeginRelayJoin(m_localLobby);
m_RelaySetup.BeginRelayJoin(m_localLobby, m_localUser);
m_localUser.Emote = null;
m_localUser.Emote = EmoteType.None;
LobbyAsyncRequests.Instance.LeaveLobbyAsync(m_localLobby.LobbyID, ResetLocalLobby);
m_lobbyContentHeartbeat.EndTracking();
LobbyAsyncRequests.Instance.EndTracking();

6
Assets/Scripts/Entities/LobbyUser.cs


[Serializable]
public class LobbyUser : Observed<LobbyUser>
{
public LobbyUser(bool isHost = false, string displayName = null, string id = null, string emote = null, string userStatus = null)
public LobbyUser(bool isHost = false, string displayName = null, string id = null, EmoteType emote = EmoteType.None, string userStatus = null)
{
m_isHost = isHost;
m_DisplayName = displayName;

}
}
string m_Emote = "";
EmoteType m_Emote = EmoteType.None;
public string Emote
public EmoteType Emote
{
get => m_Emote;
set

9
Assets/Scripts/Lobby/ToLocalLobby.cs


using System.Collections.Generic;
using System;
using System.Collections.Generic;
using Unity.Services.Lobbies.Models;
namespace LobbyRelaySample.lobby

{
existingLocalUser.IsHost = lobby.HostId.Equals(player.Id);
existingLocalUser.DisplayName = player.Data?.ContainsKey("DisplayName") == true ? player.Data["DisplayName"].Value : existingLocalUser.DisplayName;
existingLocalUser.Emote = player.Data?.ContainsKey("Emote") == true ? player.Data["Emote"].Value : existingLocalUser.Emote;
existingLocalUser.Emote = player.Data?.ContainsKey("Emote") == true ? (EmoteType) int.Parse(player.Data["Emote"].Value) : existingLocalUser.Emote;
lobbyUsers.Add(existingLocalUser.ID, existingLocalUser);
}
else

isHost: lobby.HostId.Equals(player.Id),
id: player.Id,
emote: player.Data?.ContainsKey("Emote") == true ? player.Data["Emote"].Value : null,
emote: player.Data?.ContainsKey("Emote") == true ? (EmoteType)int.Parse(player.Data["Emote"].Value) : EmoteType.None,
userStatus: player.Data?.ContainsKey("UserStatus") == true ? player.Data["UserStatus"].Value : UserStatus.Lobby.ToString()
);
lobbyUsers.Add(user.ID, user);

if (user == null || string.IsNullOrEmpty(user.ID))
return data;
data.Add("DisplayName", user.DisplayName);
data.Add("Emote", user.Emote); // Emote could be null, which is fine.
data.Add("Emote", ((int)user.Emote).ToString());
data.Add("UserStatus", user.UserStatus.ToString());
return data;
}

6
Assets/Scripts/Relay/RelayInterface.cs


{
AllocateAsync(maxConnections, a =>
{
if (a.Status >= 200 && a.Status < 300)
if (a == null)
Debug.LogError("Relay returned a null Allocation. It's possible the Relay service is currently down.");
else if (a.Status >= 200 && a.Status < 300)
{
}
});
}

194
Assets/Scripts/Relay/RelayUtpSetup.cs


protected NetworkEndPoint m_endpointForServer;
protected JobHandle m_currentUpdateHandle;
protected LocalLobby m_localLobby;
protected LobbyUser m_localUser;
protected enum MsgType { NewPlayer = 0, PingPong = 1, ReadyState = 2, PlayerName = 3, Emote = 4 } // We only use 3 bits for this.
public enum MsgType { NewPlayer = 0, ProvidePlayerId = 1, ReadyState = 2, PlayerName = 3, Emote = 4 } // We only use 3 bits for this.
public void BeginRelayJoin(LocalLobby localLobby)//, Action<bool, string> onJoinComplete)
public void BeginRelayJoin(LocalLobby localLobby, LobbyUser localUser)//, Action<bool, string> onJoinComplete)
m_localUser = localUser;
// m_onJoinComplete = onJoinComplete;
JoinRelay();
}

#endregion
private void LateUpdate()
{
if (m_networkDriver.IsCreated && m_isRelayConnected)
m_currentUpdateHandle.Complete(); // This prevents warnings about a job allocation longer than 4 frames if FixedUpdate is very fast.
}
//private void LateUpdate()
//{
// if (m_networkDriver.IsCreated && m_isRelayConnected)
// m_currentUpdateHandle.Complete(); // This prevents warnings about a job allocation longer than 4 frames if FixedUpdate is very fast.
//}
}
public class RelayUtpSetup_Host : RelayUTPSetup

{
Debug.LogWarning("Server is now listening!");
m_isRelayConnected = true;
// TODO: Be able to dispose.
RelayHost host = gameObject.AddComponent<RelayHost>();
host.Initialize(m_networkDriver, m_connections, m_localLobby);
}
}

{
if (cmd == NetworkEvent.Type.Connect)
{
// also, the next step is setting up this behavior in a separate class to happen with manual calls instead of every update
SendPong(driver, connections);
// SendPong(driver, connections);
int contents = header % 32;
header = (byte)(header >> 5);
MsgType msgType = (MsgType)header;
MsgType msgType = (MsgType)(header % 8);
header = (byte)(header >> 3);
int playerId = header;
if (msgType == MsgType.PingPong)
{
SendPong(driver, connections);
}
else if (msgType == MsgType.PlayerName)
if (msgType == MsgType.PlayerName)
byte[] nameBytes = new byte[contents];
byte length = strm.ReadByte();
byte[] nameBytes = new byte[length];
strm.ReadBytes(namePtr, contents);
strm.ReadBytes(namePtr, length);
Debug.LogWarning("Received name for connection " + i + ": " + name);
Debug.LogAssertion("Received name for connection " + i + ": " + name);
SendPong(driver, connections);
// SendPong(driver, connections);
}
}
else if (cmd == NetworkEvent.Type.Disconnect)

}
}
void SendPong(NetworkDriver.Concurrent driver, NativeArray<NetworkConnection> connections)
{
byte reply = (byte)(((int)MsgType.PingPong) << 5);
if (driver.BeginSend(connections[i], out var writeData) == 0)
{
writeData.WriteByte(reply);
driver.EndSend(writeData);
Debug.LogWarning("Sent pong for connection " + i);
}
}
//void SendPong(NetworkDriver.Concurrent driver, NativeArray<NetworkConnection> connections)
//{
// byte reply = (byte)(((int)MsgType.PingPong) << 5);
// if (driver.BeginSend(connections[i], out var writeData) == 0)
// {
// writeData.WriteByte(reply);
// driver.EndSend(writeData);
// Debug.LogWarning("Sent pong for connection " + i);
// }
//}
private void Update()
{
// When connecting to the relay we need to this?
if (m_networkDriver.IsCreated && !m_isRelayConnected)
{
m_networkDriver.ScheduleUpdate().Complete();
var updateJob = new DriverUpdateJob {driver = m_networkDriver, connections = m_connections};
updateJob.Schedule().Complete();
}
}
//private void Update()
//{
// // When connecting to the relay we need to this?
// if (m_networkDriver.IsCreated && !m_isRelayConnected)
// {
// m_networkDriver.ScheduleUpdate().Complete();
void FixedUpdate()
{
if (m_networkDriver.IsCreated && m_isRelayConnected) {
// Wait for the previous frames ping to complete before starting a new one, the Complete in LateUpdate is not
// enough since we can get multiple FixedUpdate per frame on slow clients
m_currentUpdateHandle.Complete();
var updateJob = new DriverUpdateJob {driver = m_networkDriver, connections = m_connections};
var pongJob = new PongJob
{
// PongJob is a ParallelFor job, it must use the concurrent NetworkDriver
driver = m_networkDriver.ToConcurrent(),
// PongJob uses IJobParallelForDeferExtensions, we *must* use AsDeferredJobArray in order to access the
// list from the job
connections = m_connections.AsDeferredJobArray()
};
// Update the driver should be the first job in the chain
m_currentUpdateHandle = m_networkDriver.ScheduleUpdate();
// The DriverUpdateJob which accepts new connections should be the second job in the chain, it needs to depend
// on the driver update job
m_currentUpdateHandle = updateJob.Schedule(m_currentUpdateHandle);
// PongJob uses IJobParallelForDeferExtensions, we *must* schedule with a list as first parameter rather than
// an int since the job needs to pick up new connections from DriverUpdateJob
// The PongJob is the last job in the chain and it must depends on the DriverUpdateJob
m_currentUpdateHandle = pongJob.Schedule(m_connections, 1, m_currentUpdateHandle);
}
}
// var updateJob = new DriverUpdateJob { driver = m_networkDriver, connections = m_connections };
// updateJob.Schedule().Complete();
// }
//}
// void Update()
// {
// if (m_networkDriver.IsCreated && m_isRelayConnected)
// {
// // Wait for the previous frames ping to complete before starting a new one, the Complete in LateUpdate is not
// // enough since we can get multiple FixedUpdate per frame on slow clients
// m_currentUpdateHandle.Complete();
// var updateJob = new DriverUpdateJob { driver = m_networkDriver, connections = m_connections };
// // Update the driver should be the first job in the chain
// m_currentUpdateHandle = m_networkDriver.ScheduleUpdate();
// // The DriverUpdateJob which accepts new connections should be the second job in the chain, it needs to depend
// // on the driver update job
// m_currentUpdateHandle = updateJob.Schedule(m_currentUpdateHandle);
// // PongJob uses IJobParallelForDeferExtensions, we *must* schedule with a list as first parameter rather than
// // an int since the job needs to pick up new connections from DriverUpdateJob
// // The PongJob is the last job in the chain and it must depends on the DriverUpdateJob
//// m_currentUpdateHandle = pongJob.Schedule(m_connections, 1, m_currentUpdateHandle);
// }
// }
}
public class RelayUtpSetup_Client : RelayUTPSetup

}
if (m_networkDriver.GetConnectionState(m_connections[0]) != NetworkConnection.State.Connected)
Debug.LogError("Client failed to connect to server");
else
{
// TODO: Be able to dispose.
RelayUserWatcher watcher = gameObject.AddComponent<RelayUserWatcher>();
watcher.Initialize(m_networkDriver, m_connections, m_localUser);
}
}
private struct PingJob : IJob

private void Update()
{
// When connecting to the relay we need to this?
if (m_networkDriver.IsCreated && !m_isRelayConnected)
{
m_networkDriver.ScheduleUpdate().Complete();
//// When connecting to the relay we need to this?
//if (m_networkDriver.IsCreated && !m_isRelayConnected)
//{
// m_networkDriver.ScheduleUpdate().Complete();
var pingJob = new PingJob
{
driver = m_networkDriver,
connection = m_connections.AsArray(),
fixedTime = Time.fixedTime,
myName = new NativeArray<byte>(System.Text.Encoding.UTF8.GetBytes(myName), Allocator.TempJob)
};
// var pingJob = new PingJob
// {
// driver = m_networkDriver,
// connection = m_connections.AsArray(),
// fixedTime = Time.fixedTime,
// myName = new NativeArray<byte>(System.Text.Encoding.UTF8.GetBytes(myName), Allocator.TempJob)
// };
pingJob.Schedule().Complete();
}
// pingJob.Schedule().Complete();
//}
if (m_networkDriver.IsCreated && m_isRelayConnected)
{
//if (m_networkDriver.IsCreated && m_isRelayConnected)
//{
// Wait for the previous frames ping to complete before starting a new one, the Complete in LateUpdate is not
// enough since we can get multiple FixedUpdate per frame on slow clients
m_currentUpdateHandle.Complete();
// // Wait for the previous frames ping to complete before starting a new one, the Complete in LateUpdate is not
// // enough since we can get multiple FixedUpdate per frame on slow clients
// m_currentUpdateHandle.Complete();
var pingJob = new PingJob
{
driver = m_networkDriver,
connection = m_connections,
fixedTime = Time.fixedTime,
myName = new NativeArray<byte>(System.Text.Encoding.UTF8.GetBytes(myName), Allocator.TempJob)
};
// Schedule a chain with the driver update followed by the ping job
m_currentUpdateHandle = m_networkDriver.ScheduleUpdate();
m_currentUpdateHandle = pingJob.Schedule(m_currentUpdateHandle);
}
// var pingJob = new PingJob
// {
// driver = m_networkDriver,
// connection = m_connections,
// fixedTime = Time.fixedTime,
// myName = new NativeArray<byte>(System.Text.Encoding.UTF8.GetBytes(myName), Allocator.TempJob)
// };
// // Schedule a chain with the driver update followed by the ping job
// m_currentUpdateHandle = m_networkDriver.ScheduleUpdate();
// m_currentUpdateHandle = pingJob.Schedule(m_currentUpdateHandle);
//}
}
}
}

4
Assets/Scripts/UI/EmoteButtonUI.cs


public class EmoteButtonUI : MonoBehaviour
{
[SerializeField]
TMP_Text m_EmoteTextElement;
private EmoteType m_emoteType;
Locator.Get.Messenger.OnReceiveMessage(MessageType.UserSetEmote, m_EmoteTextElement.text);
Locator.Get.Messenger.OnReceiveMessage(MessageType.UserSetEmote, m_emoteType);
}
}
}

2
Assets/Scripts/UI/InLobbyUserUI.cs


{
m_DisplayNameText.SetText(observed.DisplayName);
m_StatusText.SetText(SetStatusFancy(observed.UserStatus));
m_EmoteText.SetText(observed.Emote);
m_EmoteText.SetText(observed.Emote.GetString());
}
string SetStatusFancy(UserStatus status)

17
Assets/Scripts/Entities/EmoteType.cs


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" :
"";
}
}
}

11
Assets/Scripts/Entities/EmoteType.cs.meta


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

131
Assets/Scripts/Relay/RelayHost.cs


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);
}
}
}
}
}

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


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

204
Assets/Scripts/Relay/RelayUserWatcher.cs


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);
}
}
}
}
}
}

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


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