浏览代码

Almost actual progress: This now has the two clients connecting to Relay on join, with the host keeping the allocation alive, and they will send each other changes to their name, emote, and ready immediately.

The code will get some cleanup and renaming, and then there are still plenty more features necessary, including UI work and changes to how Lobby data get used.
/main/staging
nathaniel.buck@unity3d.com 3 年前
当前提交
42fe907a
共有 4 个文件被更改,包括 105 次插入433 次删除
  1. 1
      Assets/Scripts/Entities/GameStateManager.cs
  2. 94
      Assets/Scripts/Relay/RelayHost.cs
  3. 196
      Assets/Scripts/Relay/RelayUserWatcher.cs
  4. 247
      Assets/Scripts/Relay/RelayUtpSetup.cs

1
Assets/Scripts/Entities/GameStateManager.cs


else
{
m_RelaySetup = gameObject.AddComponent<RelayUtpSetup_Client>();
(m_RelaySetup as RelayUtpSetup_Client).myName = m_localUser.DisplayName; // TODO: Also for the server player.
m_RelaySetup.BeginRelayJoin(m_localLobby, m_localUser);
}
}

94
Assets/Scripts/Relay/RelayHost.cs


namespace LobbyRelaySample.Relay
{
public class RelayHost : MonoBehaviour // TODO: Should it be a child of the client version?
public class RelayHost : RelayUserWatcher
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()
protected override void OnUpdate()
base.OnUpdate();
ReceiveNetworkEvents(m_networkDriver.ToConcurrent(), m_connections);
private void ReceiveNetworkEvents(NetworkDriver.Concurrent driver, NativeArray<NetworkConnection> connections) // TODO: Need the concurrent?
protected override void ProcessNetworkEvent(DataStreamReader strm, NetworkEvent.Type cmd)
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);
}
}
}
}
base.ProcessNetworkEvent(strm, cmd);
// TODO: The only thing this has to care about is if all players have readied up.
}
private void DoHeartbeat()

// Remove connections which have been destroyed from the list of active connections
for (int c = m_connections.Length - 1; c >= 0; c--)
for (int c = m_connections.Count - 1; c >= 0; c--)
{
if (!m_connections[c].IsCreated)
m_connections.RemoveAtSwapBack(c);

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

196
Assets/Scripts/Relay/RelayUserWatcher.cs


/// </summary>
public class RelayUserWatcher : MonoBehaviour, IDisposable
{
private LobbyUser m_localUser;
protected LobbyUser m_localUser;
protected LocalLobby m_localLobby;
private NetworkDriver m_networkDriver;
private NativeList<NetworkConnection> m_connections; // TODO: Make it clearer that this is just the one member?
private JobHandle m_mostRecentJob;
protected NetworkDriver m_networkDriver;
protected List<NetworkConnection> m_connections; // TODO: Make it clearer that this is just the one member?
private int? m_playerId = null; // Provided by the host.
public void Initialize(NetworkDriver networkDriver, NativeList<NetworkConnection> connections, LobbyUser localUser)
public void Initialize(NetworkDriver networkDriver, List<NetworkConnection> connections, LobbyUser localUser, LocalLobby localLobby)
m_localLobby = localLobby;
m_localUser.onChanged += OnLocalChange; // TODO: This should break up the state type?
m_networkDriver = networkDriver;
m_connections = connections;

private void OnLocalChange(LobbyUser localUser)
{
//if (!m_mostRecentJob.IsCompleted)
// m_mostRecentJob.Complete();
if (m_connections.Count == 0) // Could be the case for the server, should probably actually break that out after all?
return;
DoUserUpdate(m_networkDriver, m_connections);
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.
}
//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()
{
OnUpdate();
public void Update()
protected virtual void OnUpdate()
if (!m_hasSentInitialMessage)
SendInitialMessage(m_networkDriver, m_connections);
if (!m_hasSentInitialMessage && !(this is RelayHost))
SendInitialMessage(m_networkDriver, m_connections[0]);
private void ReceiveNetworkEvents(NetworkDriver driver, NativeArray<NetworkConnection> connection) // TODO: Just the one connection.
private void ReceiveNetworkEvents(NetworkDriver driver, List<NetworkConnection> connections) // TODO: Just the one connection. Also not NativeArray.
while (connection.Length > 0 && (cmd = connection[0].PopEvent(driver, out strm)) != NetworkEvent.Type.Empty)
foreach (NetworkConnection connection in connections)
if (cmd == NetworkEvent.Type.Data)
while ((cmd = connection.PopEvent(driver, out strm)) != NetworkEvent.Type.Empty)
// 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;
ProcessNetworkEvent(strm, cmd);
}
}
}
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.
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());
private void SendInitialMessage(NetworkDriver driver, NativeArray<NetworkConnection> connection)
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)
WriteString(driver, connection, MsgType.NewPlayer, m_localUser.ID);
DoUserUpdate(driver, connection);
private void DoUserUpdate(NetworkDriver driver, NativeArray<NetworkConnection> connection)
private void DoUserUpdate(NetworkDriver driver, 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
// TODO: Combine these all into one message, if I'm just going to send them all each time anyway.
//else if (cmd == NetworkEvent.Type.Disconnect)
//{
// // If the server disconnected us we clear out connection
// connection[0] = default(NetworkConnection);
//}
}
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);
// 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)
// 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);
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);
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);
if (driver.BeginSend(connection[0], out var dataStream) == 0) // Oh, should check this first?
if (driver.BeginSend(connection, out var dataStream) == 0) // Oh, should check this first?
{
byte[] bytes = message.ToArray();
unsafe

}
}
private struct UserUpdateJob : IJob
private void WriteByte(NetworkDriver driver, NetworkConnection connection, MsgType msgType, byte value)
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;
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);
public void Execute()
if (driver.BeginSend(connection, out var dataStream) == 0) // Oh, should check this first?
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)
byte[] bytes = message.ToArray();
unsafe
if (cmd == NetworkEvent.Type.Connect)
fixed (byte* bytesPtr = bytes)
// 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);
dataStream.WriteBytes(bytesPtr, message.Count);
driver.EndSend(dataStream);
}
}
}

247
Assets/Scripts/Relay/RelayUtpSetup.cs


{
protected bool m_isRelayConnected = false;
protected NetworkDriver m_networkDriver;
protected NativeList<NetworkConnection> m_connections;
protected List<NetworkConnection> m_connections;
protected NetworkEndPoint m_endpointForServer;
protected JobHandle m_currentUpdateHandle;
protected LocalLobby m_localLobby;

public enum MsgType { NewPlayer = 0, ProvidePlayerId = 1, ReadyState = 2, PlayerName = 3, Emote = 4 } // We only use 3 bits for this.
public enum MsgType { NewPlayer = 0, ReadyState = 2, PlayerName = 3, Emote = 4 }
public void BeginRelayJoin(LocalLobby localLobby, LobbyUser localUser)//, Action<bool, string> onJoinComplete)
{

var relayNetworkParameter = new RelayNetworkParameter { ServerData = relayServerData };
m_networkDriver = NetworkDriver.Create(new INetworkParameter[] { relayNetworkParameter });
m_connections = new NativeList<NetworkConnection>(connectionCapacity, Allocator.Persistent);
m_connections = new List<NetworkConnection>(connectionCapacity);
if (m_networkDriver.Bind(NetworkEndPoint.AnyIpv4) != 0)
Debug.LogError("Failed to bind to Relay allocation.");

}
}
#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.
//}
}
public class RelayUtpSetup_Host : RelayUTPSetup

m_isRelayConnected = true;
// TODO: Be able to dispose.
RelayHost host = gameObject.AddComponent<RelayHost>();
host.Initialize(m_networkDriver, m_connections, m_localLobby);
}
}
struct DriverUpdateJob : IJob
{
public NetworkDriver driver;
public NativeList<NetworkConnection> connections;
public void Execute()
{
// Remove connections which have been destroyed from the list of active connections
for (int i = 0; i < connections.Length; ++i)
{
if (!connections[i].IsCreated)
{
connections.RemoveAtSwapBack(i);
// Index i is a new connection since we did a swap back, check it again
--i;
}
}
// Accept all new connections
while (true)
{
var con = driver.Accept();
// "Nothing more to accept" is signaled by returning an invalid connection from accept
if (!con.IsCreated)
break;
connections.Add(con);
}
}
}
private struct PongJob : IJobParallelForDefer
{
public NetworkDriver.Concurrent driver;
public NativeArray<NetworkConnection> connections;
public void Execute(int i)
{
DataStreamReader strm;
NetworkEvent.Type cmd;
// Pop all events for the connection
while ((cmd = driver.PopEventForConnection(connections[i], out strm)) != NetworkEvent.Type.Empty)
{
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
// TODO: Assuming that i is the index in connections, which will be the order in which they are received and also will not shift downward if disconnects happen? Need to test with multiple clients.
// SendPong(driver, connections);
}
else if (cmd == NetworkEvent.Type.Data)
{
byte header = strm.ReadByte();
MsgType msgType = (MsgType)(header % 8);
header = (byte)(header >> 3);
int playerId = header;
if (msgType == MsgType.PlayerName)
{
byte length = strm.ReadByte();
byte[] nameBytes = new byte[length];
unsafe
{
fixed(byte* namePtr = nameBytes)
{
strm.ReadBytes(namePtr, length);
}
}
string name = System.Text.Encoding.UTF8.GetString(nameBytes);
Debug.LogAssertion("Received name for connection " + i + ": " + name);
// SendPong(driver, connections);
}
}
else if (cmd == NetworkEvent.Type.Disconnect)
{
// When disconnected we make sure the connection return false to IsCreated so the next frames
// DriverUpdateJob will remove it
connections[i] = default(NetworkConnection);
}
}
//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);
// }
//}
host.Initialize(m_networkDriver, m_connections, m_localUser, m_localLobby);
//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();
// }
//}
// 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);
// }
// }
// TEMP
public string myName { private get; set; }
protected override void JoinRelay()
{
m_localLobby.onChanged += OnLobbyChange;

if (allocation == null)
return; // TODO: Error messaging.
BindToAllocation(allocation.RelayServer.IpV4, allocation.RelayServer.Port, allocation.AllocationIdBytes, allocation.ConnectionData, allocation.HostConnectionData, allocation.Key, 1);
m_localLobby.RelayServer = new ServerAddress(allocation.RelayServer.IpV4, allocation.RelayServer.Port);
}
protected override void OnBindingComplete()

{
// TODO: Be able to dispose.
RelayUserWatcher watcher = gameObject.AddComponent<RelayUserWatcher>();
watcher.Initialize(m_networkDriver, m_connections, m_localUser);
watcher.Initialize(m_networkDriver, m_connections, m_localUser, m_localLobby);
}
private struct PingJob : 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 float fixedTime;
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)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.Data)
{
//// When the pong message is received we calculate the ping time and disconnect
//pingStats[1] = (int)((fixedTime - pendingPings[0].time) * 1000);
//connection[0].Disconnect(driver);
//connection[0] = default(NetworkConnection);
if (driver.BeginSend(connection[0], out var pingData) == 0)
{
pingData.WriteInt(1234);
driver.EndSend(pingData);
}
}
else if (cmd == NetworkEvent.Type.Disconnect)
{
// If the server disconnected us we clear out connection
connection[0] = default(NetworkConnection);
}
}
}
}
private void Update()
{
//// 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)
// };
// pingJob.Schedule().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 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);
//}
}
}
}
正在加载...
取消
保存