mirror of
https://github.com/misternebula/quantum-space-buddies.git
synced 2025-01-01 03:32:38 +00:00
399 lines
9.7 KiB
C#
399 lines
9.7 KiB
C#
using Epic.OnlineServices;
|
|
using Epic.OnlineServices.Metrics;
|
|
using Epic.OnlineServices.P2P;
|
|
using Mirror;
|
|
using System;
|
|
using System.Collections;
|
|
using UnityEngine;
|
|
|
|
namespace EpicTransport;
|
|
|
|
/// <summary>
|
|
/// EOS Transport following the Mirror transport standard
|
|
/// </summary>
|
|
public class EosTransport : Transport
|
|
{
|
|
private const string EPIC_SCHEME = "epic";
|
|
|
|
private Client client;
|
|
private Server server;
|
|
|
|
private Common activeNode;
|
|
|
|
[SerializeField]
|
|
public PacketReliability[] Channels = new PacketReliability[2] { PacketReliability.ReliableOrdered, PacketReliability.UnreliableUnordered };
|
|
|
|
[Tooltip("Timeout for connecting in seconds.")]
|
|
public int timeout = 25;
|
|
|
|
[Tooltip("The max fragments used in fragmentation before throwing an error.")]
|
|
public int maxFragments = 55;
|
|
|
|
public float ignoreCachedMessagesAtStartUpInSeconds = 2.0f;
|
|
private float ignoreCachedMessagesTimer = 0.0f;
|
|
|
|
public RelayControl relayControl = RelayControl.AllowRelays;
|
|
|
|
[Header("Info")]
|
|
[Tooltip("This will display your Epic Account ID when you start or connect to a server.")]
|
|
public ProductUserId productUserId;
|
|
|
|
private int packetId = 0;
|
|
|
|
public Action<string> SetTransportError;
|
|
|
|
private void Awake()
|
|
{
|
|
Debug.Assert(Channels != null && Channels.Length > 0, "No channel configured for EOS Transport.");
|
|
Debug.Assert(Channels.Length < byte.MaxValue, "Too many channels configured for EOS Transport");
|
|
|
|
if (Channels[0] != PacketReliability.ReliableOrdered)
|
|
{
|
|
Debug.LogWarning("EOS Transport Channel[0] is not ReliableOrdered, Mirror expects Channel 0 to be ReliableOrdered, only change this if you know what you are doing.");
|
|
}
|
|
|
|
if (Channels[1] != PacketReliability.UnreliableUnordered)
|
|
{
|
|
Debug.LogWarning("EOS Transport Channel[1] is not UnreliableUnordered, Mirror expects Channel 1 to be UnreliableUnordered, only change this if you know what you are doing.");
|
|
}
|
|
|
|
StartCoroutine("FetchEpicAccountId");
|
|
StartCoroutine("ChangeRelayStatus");
|
|
}
|
|
|
|
public override void ClientEarlyUpdate()
|
|
{
|
|
EOSSDKComponent.Tick();
|
|
|
|
if (activeNode != null)
|
|
{
|
|
ignoreCachedMessagesTimer += Time.deltaTime;
|
|
|
|
if (ignoreCachedMessagesTimer <= ignoreCachedMessagesAtStartUpInSeconds)
|
|
{
|
|
activeNode.ignoreAllMessages = true;
|
|
}
|
|
else
|
|
{
|
|
activeNode.ignoreAllMessages = false;
|
|
|
|
if (client != null && !client.isConnecting)
|
|
{
|
|
if (EOSSDKComponent.Initialized)
|
|
{
|
|
client.Connect(client.hostAddress);
|
|
}
|
|
else
|
|
{
|
|
Debug.LogError("EOS not initialized");
|
|
client.EosNotInitialized();
|
|
}
|
|
|
|
client.isConnecting = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (enabled)
|
|
{
|
|
activeNode?.ReceiveData();
|
|
}
|
|
}
|
|
|
|
public override void ClientLateUpdate() { }
|
|
|
|
public override void ServerEarlyUpdate()
|
|
{
|
|
EOSSDKComponent.Tick();
|
|
|
|
if (activeNode != null)
|
|
{
|
|
ignoreCachedMessagesTimer += Time.deltaTime;
|
|
|
|
if (ignoreCachedMessagesTimer <= ignoreCachedMessagesAtStartUpInSeconds)
|
|
{
|
|
activeNode.ignoreAllMessages = true;
|
|
}
|
|
else
|
|
{
|
|
activeNode.ignoreAllMessages = false;
|
|
}
|
|
}
|
|
|
|
if (enabled)
|
|
{
|
|
activeNode?.ReceiveData();
|
|
}
|
|
}
|
|
|
|
public override void ServerLateUpdate() { }
|
|
|
|
public override bool ClientConnected() => ClientActive() && client.Connected;
|
|
|
|
public override void ClientConnect(string address)
|
|
{
|
|
if (!EOSSDKComponent.Initialized)
|
|
{
|
|
Debug.LogError("EOS not initialized. Client could not be started.");
|
|
OnClientDisconnected.Invoke();
|
|
return;
|
|
}
|
|
|
|
StartCoroutine("FetchEpicAccountId");
|
|
|
|
if (ServerActive())
|
|
{
|
|
Debug.LogError("Transport already running as server!");
|
|
return;
|
|
}
|
|
|
|
if (!ClientActive() || client.Error)
|
|
{
|
|
Debug.Log($"Starting client, target address {address}.");
|
|
|
|
client = Client.CreateClient(this, address);
|
|
activeNode = client;
|
|
|
|
if (EOSSDKComponent.CollectPlayerMetrics)
|
|
{
|
|
// Start Metrics colletion session
|
|
var sessionOptions = new BeginPlayerSessionOptions();
|
|
sessionOptions.AccountId = EOSSDKComponent.LocalUserAccountId;
|
|
sessionOptions.ControllerType = UserControllerType.Unknown;
|
|
sessionOptions.DisplayName = EOSSDKComponent.DisplayName;
|
|
sessionOptions.GameSessionId = null;
|
|
sessionOptions.ServerIp = null;
|
|
var result = EOSSDKComponent.GetMetricsInterface().BeginPlayerSession(sessionOptions);
|
|
|
|
if (result == Result.Success)
|
|
{
|
|
Debug.Log("Started Metric Session");
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
Debug.LogError("Client already running!");
|
|
}
|
|
}
|
|
|
|
public override void ClientConnect(Uri uri)
|
|
{
|
|
if (uri.Scheme != EPIC_SCHEME)
|
|
{
|
|
throw new ArgumentException($"Invalid url {uri}, use {EPIC_SCHEME}://EpicAccountId instead", nameof(uri));
|
|
}
|
|
|
|
ClientConnect(uri.Host);
|
|
}
|
|
|
|
public override void ClientSend(ArraySegment<byte> segment, int channelId)
|
|
{
|
|
Send(channelId, segment);
|
|
}
|
|
|
|
public override void ClientDisconnect()
|
|
{
|
|
if (ClientActive())
|
|
{
|
|
Shutdown();
|
|
}
|
|
}
|
|
|
|
public bool ClientActive() => client != null;
|
|
|
|
public override bool ServerActive() => server != null;
|
|
|
|
public override void ServerStart()
|
|
{
|
|
if (!EOSSDKComponent.Initialized)
|
|
{
|
|
Debug.LogError("EOS not initialized. Server could not be started.");
|
|
return;
|
|
}
|
|
|
|
StartCoroutine("FetchEpicAccountId");
|
|
|
|
if (ClientActive())
|
|
{
|
|
Debug.LogError("Transport already running as client!");
|
|
return;
|
|
}
|
|
|
|
if (!ServerActive())
|
|
{
|
|
Debug.Log("Starting server.");
|
|
|
|
server = Server.CreateServer(this, NetworkManager.singleton.maxConnections);
|
|
activeNode = server;
|
|
|
|
if (EOSSDKComponent.CollectPlayerMetrics)
|
|
{
|
|
// Start Metrics colletion session
|
|
var sessionOptions = new BeginPlayerSessionOptions();
|
|
sessionOptions.AccountId = EOSSDKComponent.LocalUserAccountId;
|
|
sessionOptions.ControllerType = UserControllerType.Unknown;
|
|
sessionOptions.DisplayName = EOSSDKComponent.DisplayName;
|
|
sessionOptions.GameSessionId = null;
|
|
sessionOptions.ServerIp = null;
|
|
var result = EOSSDKComponent.GetMetricsInterface().BeginPlayerSession(sessionOptions);
|
|
|
|
if (result == Result.Success)
|
|
{
|
|
Debug.Log("Started Metric Session");
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
Debug.LogError("Server already started!");
|
|
}
|
|
}
|
|
|
|
public override Uri ServerUri()
|
|
{
|
|
var epicBuilder = new UriBuilder
|
|
{
|
|
Scheme = EPIC_SCHEME,
|
|
Host = EOSSDKComponent.LocalUserProductIdString
|
|
};
|
|
|
|
return epicBuilder.Uri;
|
|
}
|
|
|
|
public override void ServerSend(int connectionId, ArraySegment<byte> segment, int channelId)
|
|
{
|
|
if (ServerActive())
|
|
{
|
|
Send(channelId, segment, connectionId);
|
|
}
|
|
}
|
|
|
|
public override void ServerDisconnect(int connectionId) => server.Disconnect(connectionId);
|
|
public override string ServerGetClientAddress(int connectionId) => ServerActive() ? server.ServerGetClientAddress(connectionId) : string.Empty;
|
|
|
|
public override void ServerStop()
|
|
{
|
|
if (ServerActive())
|
|
{
|
|
Shutdown();
|
|
}
|
|
}
|
|
|
|
private void Send(int channelId, ArraySegment<byte> segment, int connectionId = int.MinValue)
|
|
{
|
|
var packets = GetPacketArray(channelId, segment);
|
|
|
|
for (var i = 0; i < packets.Length; i++)
|
|
{
|
|
if (connectionId == int.MinValue)
|
|
{
|
|
client.Send(packets[i].ToBytes(), channelId);
|
|
}
|
|
else
|
|
{
|
|
server.SendAll(connectionId, packets[i].ToBytes(), channelId);
|
|
}
|
|
}
|
|
|
|
packetId++;
|
|
}
|
|
|
|
private Packet[] GetPacketArray(int channelId, ArraySegment<byte> segment)
|
|
{
|
|
var packetCount = Mathf.CeilToInt((float)segment.Count / (float)GetMaxSinglePacketSize(channelId));
|
|
var packets = new Packet[packetCount];
|
|
|
|
for (var i = 0; i < segment.Count; i += GetMaxSinglePacketSize(channelId))
|
|
{
|
|
var fragment = i / GetMaxSinglePacketSize(channelId);
|
|
|
|
packets[fragment] = new Packet();
|
|
packets[fragment].id = packetId;
|
|
packets[fragment].fragment = fragment;
|
|
packets[fragment].moreFragments = segment.Count - i > GetMaxSinglePacketSize(channelId);
|
|
packets[fragment].data = new byte[segment.Count - i > GetMaxSinglePacketSize(channelId) ? GetMaxSinglePacketSize(channelId) : segment.Count - i];
|
|
Array.Copy(segment.Array, i, packets[fragment].data, 0, packets[fragment].data.Length);
|
|
}
|
|
|
|
return packets;
|
|
}
|
|
|
|
public override void Shutdown()
|
|
{
|
|
if (EOSSDKComponent.CollectPlayerMetrics)
|
|
{
|
|
// Stop Metrics collection session
|
|
var endSessionOptions = new EndPlayerSessionOptions();
|
|
endSessionOptions.AccountId = EOSSDKComponent.LocalUserAccountId;
|
|
var result = EOSSDKComponent.GetMetricsInterface().EndPlayerSession(endSessionOptions);
|
|
|
|
if (result == Result.Success)
|
|
{
|
|
Debug.LogError("Stopped Metric Session");
|
|
}
|
|
}
|
|
|
|
server?.Shutdown();
|
|
client?.Disconnect();
|
|
|
|
server = null;
|
|
client = null;
|
|
activeNode = null;
|
|
Debug.Log("Transport shut down.");
|
|
}
|
|
|
|
public int GetMaxSinglePacketSize(int channelId) => P2PInterface.MaxPacketSize - 10; // 1159 bytes, we need to remove 10 bytes for the packet header (id (4 bytes) + fragment (4 bytes) + more fragments (1 byte))
|
|
|
|
public override int GetMaxPacketSize(int channelId) => P2PInterface.MaxPacketSize * maxFragments;
|
|
|
|
public override int GetBatchThreshold(int channelId) => P2PInterface.MaxPacketSize; // Use P2PInterface.MaxPacketSize as everything above will get fragmentated and will be counter effective to batching
|
|
|
|
public override bool Available()
|
|
{
|
|
try
|
|
{
|
|
return EOSSDKComponent.Initialized;
|
|
}
|
|
catch
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
private IEnumerator FetchEpicAccountId()
|
|
{
|
|
while (!EOSSDKComponent.Initialized)
|
|
{
|
|
yield return null;
|
|
}
|
|
|
|
productUserId = EOSSDKComponent.LocalUserProductId;
|
|
}
|
|
|
|
private IEnumerator ChangeRelayStatus()
|
|
{
|
|
while (!EOSSDKComponent.Initialized)
|
|
{
|
|
yield return null;
|
|
}
|
|
|
|
var setRelayControlOptions = new SetRelayControlOptions();
|
|
setRelayControlOptions.RelayControl = relayControl;
|
|
|
|
EOSSDKComponent.GetP2PInterface().SetRelayControl(setRelayControlOptions);
|
|
}
|
|
|
|
public void ResetIgnoreMessagesAtStartUpTimer()
|
|
{
|
|
ignoreCachedMessagesTimer = 0;
|
|
}
|
|
|
|
private void OnDestroy()
|
|
{
|
|
if (activeNode != null)
|
|
{
|
|
Shutdown();
|
|
}
|
|
}
|
|
} |