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
{
///
/// 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.
///
public class RelayUserWatcher : MonoBehaviour, IDisposable
{
protected LobbyUser m_localUser;
protected LocalLobby m_localLobby;
private bool m_hasDisposed = false;
protected NetworkDriver m_networkDriver;
protected List m_connections; // TODO: Make it clearer that this is just the one member?
private bool m_hasSentInitialMessage = false;
public void Initialize(NetworkDriver networkDriver, List 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 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 message = new List(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 message = new List(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);
}
}
}
}
}
}