quantum-space-buddies/EpicOnlineTransport/EosTransport.cs
2023-05-07 18:16:46 -07:00

332 lines
12 KiB
C#

using System;
using System.Collections.Generic;
using System.IO;
using UnityEngine;
using Epic.OnlineServices.P2P;
using Epic.OnlineServices;
using Mirror;
using Epic.OnlineServices.Metrics;
using System.Collections;
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;
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
BeginPlayerSessionOptions sessionOptions = new BeginPlayerSessionOptions();
sessionOptions.AccountId = EOSSDKComponent.LocalUserAccountId;
sessionOptions.ControllerType = UserControllerType.Unknown;
sessionOptions.DisplayName = EOSSDKComponent.DisplayName;
sessionOptions.GameSessionId = null;
sessionOptions.ServerIp = null;
Result 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
BeginPlayerSessionOptions sessionOptions = new BeginPlayerSessionOptions();
sessionOptions.AccountId = EOSSDKComponent.LocalUserAccountId;
sessionOptions.ControllerType = UserControllerType.Unknown;
sessionOptions.DisplayName = EOSSDKComponent.DisplayName;
sessionOptions.GameSessionId = null;
sessionOptions.ServerIp = null;
Result result = EOSSDKComponent.GetMetricsInterface().BeginPlayerSession(sessionOptions);
if (result == Result.Success) {
Debug.Log("Started Metric Session");
}
}
} else {
Debug.LogError("Server already started!");
}
}
public override Uri ServerUri() {
UriBuilder 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) {
Packet[] packets = GetPacketArray(channelId, segment);
for(int i = 0; i < packets.Length; i++) {
if (connectionId == int.MinValue) {
if (client == null)
{
OnClientDisconnected.Invoke();
return;
}
client.Send(packets[i].ToBytes(), channelId);
} else {
server.SendAll(connectionId, packets[i].ToBytes(), channelId);
}
}
packetId++;
}
private Packet[] GetPacketArray(int channelId, ArraySegment<byte> segment) {
int packetCount = Mathf.CeilToInt((float) segment.Count / (float)GetMaxSinglePacketSize(channelId));
Packet[] packets = new Packet[packetCount];
for (int i = 0; i < segment.Count; i += GetMaxSinglePacketSize(channelId)) {
int 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
EndPlayerSessionOptions endSessionOptions = new EndPlayerSessionOptions();
endSessionOptions.AccountId = EOSSDKComponent.LocalUserAccountId;
Result 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;
}
SetRelayControlOptions setRelayControlOptions = new SetRelayControlOptions();
setRelayControlOptions.RelayControl = relayControl;
EOSSDKComponent.GetP2PInterface().SetRelayControl(setRelayControlOptions);
}
public void ResetIgnoreMessagesAtStartUpTimer() {
ignoreCachedMessagesTimer = 0;
}
private void OnDestroy() {
if (activeNode != null) {
Shutdown();
}
}
}
}