using System;
using System.Collections;
using System.Collections.Generic;
using Unity.Networking.Transport;
using Unity.Networking.Transport.Relay;
using Unity.Services.Relay.Models;
using UnityEngine;
namespace LobbyRelaySample.relay
{
///
/// Responsible for setting up a connection with Relay using UTP, for the lobby host.
/// Must be a MonoBehaviour since the binding process doesn't have asynchronous callback options.
///
public abstract class RelayUtpSetup : MonoBehaviour
{
protected bool m_isRelayConnected = false;
protected NetworkDriver m_networkDriver;
protected List m_connections;
protected NetworkEndPoint m_endpointForServer;
protected LocalLobby m_localLobby;
protected LobbyUser m_localUser;
protected Action m_onJoinComplete;
public enum MsgType { Ping = 0, NewPlayer, ReadyState, PlayerName, Emote, StartCountdown, CancelCountdown, ConfirmInGame, EndInGame, PlayerDisconnect }
public void BeginRelayJoin(LocalLobby localLobby, LobbyUser localUser, Action onJoinComplete)
{
m_localLobby = localLobby;
m_localUser = localUser;
m_onJoinComplete = onJoinComplete;
JoinRelay();
}
protected abstract void JoinRelay();
///
/// Shared behavior for binding to the Relay allocation, which is required for use.
/// Note that a host will send bytes from the Allocation it creates, whereas a client will send bytes from the JoinAllocation it receives using a relay code.
///
protected void BindToAllocation(string ip, int port, byte[] allocationIdBytes, byte[] connectionDataBytes, byte[] hostConnectionDataBytes, byte[] hmacKeyBytes, int connectionCapacity)
{
NetworkEndPoint serverEndpoint = NetworkEndPoint.Parse(ip, (ushort)port);
RelayAllocationId allocationId = ConvertAllocationIdBytes(allocationIdBytes);
RelayConnectionData connectionData = ConvertConnectionDataBytes(connectionDataBytes);
RelayConnectionData hostConnectionData = ConvertConnectionDataBytes(hostConnectionDataBytes);
RelayHMACKey key = ConvertHMACKeyBytes(hmacKeyBytes);
m_endpointForServer = serverEndpoint;
//TODO Implement DTLS
bool isSecure = false;
var relayServerData = new RelayServerData(ref serverEndpoint, 0, ref allocationId, ref connectionData, ref hostConnectionData, ref key, isSecure);
relayServerData.ComputeNewNonce();
var relayNetworkParameter = new RelayNetworkParameter { ServerData = relayServerData };
m_networkDriver = NetworkDriver.Create(new INetworkParameter[] { relayNetworkParameter });
m_connections = new List(connectionCapacity);
if (m_networkDriver.Bind(NetworkEndPoint.AnyIpv4) != 0)
Debug.LogError("Failed to bind to Relay allocation.");
else
StartCoroutine(WaitForBindComplete());
}
private IEnumerator WaitForBindComplete()
{
while (!m_networkDriver.Bound)
{
m_networkDriver.ScheduleUpdate().Complete();
yield return null;
}
OnBindingComplete();
}
protected abstract void OnBindingComplete();
#region UTP uses pointers instead of managed arrays for performance reasons, so we use these helper functions to convert them.
unsafe private static RelayAllocationId ConvertAllocationIdBytes(byte[] allocationIdBytes)
{
fixed (byte* ptr = allocationIdBytes)
{
return RelayAllocationId.FromBytePointer(ptr, allocationIdBytes.Length);
}
}
unsafe private static RelayConnectionData ConvertConnectionDataBytes(byte[] connectionData)
{
fixed (byte* ptr = connectionData)
{
return RelayConnectionData.FromBytePointer(ptr, RelayConnectionData.k_Length);
}
}
unsafe private static RelayHMACKey ConvertHMACKeyBytes(byte[] hmac)
{
fixed (byte* ptr = hmac)
{
return RelayHMACKey.FromBytePointer(ptr, RelayHMACKey.k_Length);
}
}
#endregion
private void OnDestroy()
{
if (!m_isRelayConnected && m_networkDriver.IsCreated)
m_networkDriver.Dispose();
}
}
///
/// Host logic: Request a new Allocation, and then both bind to it and request a join code. Once those are both complete, supply data back to the lobby.
///
public class RelayUtpSetupHost : RelayUtpSetup
{
[Flags]
private enum JoinState { None = 0, Bound = 1, Joined = 2 }
private JoinState m_joinState = JoinState.None;
private Allocation m_allocation;
protected override void JoinRelay()
{
RelayAPIInterface.AllocateAsync(m_localLobby.MaxPlayerCount, OnAllocation);
}
private void OnAllocation(Allocation allocation)
{
m_allocation = allocation;
RelayAPIInterface.GetJoinCodeAsync(allocation.AllocationId, OnRelayCode);
BindToAllocation(allocation.RelayServer.IpV4, allocation.RelayServer.Port, allocation.AllocationIdBytes, allocation.ConnectionData, allocation.ConnectionData, allocation.Key, 16);
}
private void OnRelayCode(string relayCode)
{
m_localLobby.RelayCode = relayCode;
m_localLobby.RelayServer = new ServerAddress(m_allocation.RelayServer.IpV4, m_allocation.RelayServer.Port);
m_joinState |= JoinState.Joined;
CheckForComplete();
}
protected override void OnBindingComplete()
{
if (m_networkDriver.Listen() != 0)
{
Debug.LogError("Server failed to listen");
m_onJoinComplete(false, null);
}
else
{
Debug.LogWarning("Server is now listening!");
m_joinState |= JoinState.Bound;
CheckForComplete();
}
}
private void CheckForComplete()
{
if (m_joinState == (JoinState.Joined | JoinState.Bound))
{
m_isRelayConnected = true;
RelayUtpHost host = gameObject.AddComponent();
host.Initialize(m_networkDriver, m_connections, m_localUser, m_localLobby);
m_onJoinComplete(true, host);
LobbyAsyncRequests.Instance.UpdatePlayerRelayInfoAsync(m_allocation.AllocationId.ToString(), m_localLobby.RelayCode, null);
}
}
}
///
/// Client logic: Wait until the Relay join code is retrieved from the lobby's shared data. Then, use that code to get the Allocation to bind to, and
/// then create a connection to the host.
///
public class RelayUtpSetupClient : RelayUtpSetup
{
private JoinAllocation m_allocation;
protected override void JoinRelay()
{
m_localLobby.onChanged += OnLobbyChange;
}
private void OnLobbyChange(LocalLobby lobby)
{
if (m_localLobby.RelayCode != null)
{
RelayAPIInterface.JoinAsync(m_localLobby.RelayCode, OnJoin);
m_localLobby.onChanged -= OnLobbyChange;
}
}
private void OnJoin(JoinAllocation joinAllocation)
{
if (joinAllocation == null)
return;
m_allocation = joinAllocation;
BindToAllocation(joinAllocation.RelayServer.IpV4, joinAllocation.RelayServer.Port, joinAllocation.AllocationIdBytes, joinAllocation.ConnectionData, joinAllocation.HostConnectionData, joinAllocation.Key, 1);
m_localLobby.RelayServer = new ServerAddress(joinAllocation.RelayServer.IpV4, joinAllocation.RelayServer.Port);
}
protected override void OnBindingComplete()
{
StartCoroutine(ConnectToServer());
}
private IEnumerator ConnectToServer()
{
// Once the client is bound to the Relay server, send a connection request.
m_connections.Add(m_networkDriver.Connect(m_endpointForServer));
while (m_networkDriver.GetConnectionState(m_connections[0]) == NetworkConnection.State.Connecting)
{
m_networkDriver.ScheduleUpdate().Complete();
yield return null;
}
if (m_networkDriver.GetConnectionState(m_connections[0]) != NetworkConnection.State.Connected)
{
Debug.LogError("Client failed to connect to server");
m_onJoinComplete(false, null);
}
else
{
m_isRelayConnected = true;
RelayUtpClient client = gameObject.AddComponent();
client.Initialize(m_networkDriver, m_connections, m_localUser, m_localLobby);
m_onJoinComplete(true, client);
LobbyAsyncRequests.Instance.UpdatePlayerRelayInfoAsync(m_allocation.AllocationId.ToString(), m_localLobby.RelayCode, null);
}
}
}
}