2022-02-06 02:06:25 +00:00
using Epic.OnlineServices ;
using Epic.OnlineServices.Metrics ;
2022-02-06 04:17:24 +00:00
using Epic.OnlineServices.P2P ;
using Mirror ;
using System ;
2022-02-06 02:06:25 +00:00
using System.Collections ;
2022-02-06 04:17:24 +00:00
using UnityEngine ;
2022-02-06 02:06:25 +00:00
2022-03-03 03:46:33 +00:00
namespace EpicTransport ;
/// <summary>
/// EOS Transport following the Mirror transport standard
/// </summary>
public class EosTransport : Transport
2022-02-06 04:17:08 +00:00
{
2022-03-03 03:46:33 +00:00
private const string EPIC_SCHEME = "epic" ;
private Client client ;
private Server server ;
2022-02-06 04:17:08 +00:00
2022-03-03 03:46:33 +00:00
private Common activeNode ;
2022-02-06 04:17:08 +00:00
2022-03-03 03:46:33 +00:00
[SerializeField]
public PacketReliability [ ] Channels = new PacketReliability [ 2 ] { PacketReliability . ReliableOrdered , PacketReliability . UnreliableUnordered } ;
2022-02-06 04:17:08 +00:00
2022-03-03 03:46:33 +00:00
[Tooltip("Timeout for connecting in seconds.")]
public int timeout = 25 ;
2022-02-06 04:17:08 +00:00
2022-03-03 03:46:33 +00:00
[Tooltip("The max fragments used in fragmentation before throwing an error.")]
public int maxFragments = 55 ;
2022-02-06 04:17:08 +00:00
2022-03-03 03:46:33 +00:00
public float ignoreCachedMessagesAtStartUpInSeconds = 2.0f ;
private float ignoreCachedMessagesTimer = 0.0f ;
2022-02-06 04:17:08 +00:00
2022-03-03 03:46:33 +00:00
public RelayControl relayControl = RelayControl . AllowRelays ;
2022-02-06 04:17:08 +00:00
2022-03-03 03:46:33 +00:00
[Header("Info")]
[Tooltip("This will display your Epic Account ID when you start or connect to a server.")]
public ProductUserId productUserId ;
2022-02-06 04:17:08 +00:00
2022-03-03 03:46:33 +00:00
private int packetId = 0 ;
2022-02-06 04:17:08 +00:00
2022-03-03 03:46:33 +00:00
public Action < string > SetTransportError ;
2022-02-06 04:17:08 +00:00
2022-03-03 03:46:33 +00:00
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" ) ;
2022-02-06 09:45:18 +00:00
2022-03-03 03:46:33 +00:00
if ( Channels [ 0 ] ! = PacketReliability . ReliableOrdered )
2022-02-06 04:17:08 +00:00
{
2022-03-03 03:46:33 +00:00
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." ) ;
}
2022-02-06 04:17:08 +00:00
2022-03-03 03:46:33 +00:00
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." ) ;
}
2022-02-06 04:17:08 +00:00
2022-03-03 03:46:33 +00:00
StartCoroutine ( "FetchEpicAccountId" ) ;
StartCoroutine ( "ChangeRelayStatus" ) ;
}
2022-02-06 04:17:08 +00:00
2022-03-03 03:46:33 +00:00
public override void ClientEarlyUpdate ( )
{
EOSSDKComponent . Tick ( ) ;
2022-02-06 04:17:08 +00:00
2022-03-03 03:46:33 +00:00
if ( activeNode ! = null )
2022-02-06 04:17:08 +00:00
{
2022-03-03 03:46:33 +00:00
ignoreCachedMessagesTimer + = Time . deltaTime ;
2022-02-06 04:17:08 +00:00
2022-03-03 03:46:33 +00:00
if ( ignoreCachedMessagesTimer < = ignoreCachedMessagesAtStartUpInSeconds )
{
activeNode . ignoreAllMessages = true ;
}
else
2022-02-06 04:17:08 +00:00
{
2022-03-03 03:46:33 +00:00
activeNode . ignoreAllMessages = false ;
2022-02-06 04:17:08 +00:00
2022-03-03 03:46:33 +00:00
if ( client ! = null & & ! client . isConnecting )
2022-02-27 12:40:44 +00:00
{
2022-03-03 03:46:33 +00:00
if ( EOSSDKComponent . Initialized )
2022-02-25 06:04:54 +00:00
{
2022-03-03 03:46:33 +00:00
client . Connect ( client . hostAddress ) ;
}
else
{
Debug . LogError ( "EOS not initialized" ) ;
client . EosNotInitialized ( ) ;
2022-02-06 04:17:08 +00:00
}
2022-02-27 12:40:44 +00:00
2022-03-03 03:46:33 +00:00
client . isConnecting = true ;
}
2022-02-27 12:40:44 +00:00
}
2022-02-06 04:17:08 +00:00
}
2022-03-03 03:46:33 +00:00
if ( enabled )
2022-02-06 04:17:08 +00:00
{
2022-03-03 03:46:33 +00:00
activeNode ? . ReceiveData ( ) ;
2022-02-06 04:17:08 +00:00
}
2022-03-03 03:46:33 +00:00
}
2022-02-06 04:17:08 +00:00
2022-03-03 03:46:33 +00:00
public override void ClientLateUpdate ( ) { }
2022-02-06 04:17:08 +00:00
2022-03-03 03:46:33 +00:00
public override void ServerEarlyUpdate ( )
{
EOSSDKComponent . Tick ( ) ;
2022-02-06 04:17:08 +00:00
2022-03-03 03:46:33 +00:00
if ( activeNode ! = null )
2022-02-06 04:17:08 +00:00
{
2022-03-03 03:46:33 +00:00
ignoreCachedMessagesTimer + = Time . deltaTime ;
2022-02-06 04:17:08 +00:00
2022-03-03 03:46:33 +00:00
if ( ignoreCachedMessagesTimer < = ignoreCachedMessagesAtStartUpInSeconds )
2022-02-06 04:17:08 +00:00
{
2022-03-03 03:46:33 +00:00
activeNode . ignoreAllMessages = true ;
2022-02-06 04:17:08 +00:00
}
else
{
2022-03-03 03:46:33 +00:00
activeNode . ignoreAllMessages = false ;
2022-02-06 04:17:08 +00:00
}
}
2022-03-03 03:46:33 +00:00
if ( enabled )
2022-02-06 04:17:08 +00:00
{
2022-03-03 03:46:33 +00:00
activeNode ? . ReceiveData ( ) ;
2022-02-27 12:40:44 +00:00
}
2022-03-03 03:46:33 +00:00
}
2022-02-06 04:17:08 +00:00
2022-03-03 03:46:33 +00:00
public override void ServerLateUpdate ( ) { }
public override bool ClientConnected ( ) = > ClientActive ( ) & & client . Connected ;
2022-02-06 04:17:08 +00:00
2022-03-03 03:46:33 +00:00
public override void ClientConnect ( string address )
{
if ( ! EOSSDKComponent . Initialized )
2022-02-06 04:17:08 +00:00
{
2022-03-03 03:46:33 +00:00
Debug . LogError ( "EOS not initialized. Client could not be started." ) ;
OnClientDisconnected . Invoke ( ) ;
return ;
2022-02-25 06:04:54 +00:00
}
2022-02-06 04:17:08 +00:00
2022-03-03 03:46:33 +00:00
StartCoroutine ( "FetchEpicAccountId" ) ;
2022-02-27 12:40:44 +00:00
2022-03-03 03:46:33 +00:00
if ( ServerActive ( ) )
2022-02-25 06:04:54 +00:00
{
2022-03-03 03:46:33 +00:00
Debug . LogError ( "Transport already running as server!" ) ;
return ;
}
2022-02-06 04:17:08 +00:00
2022-03-03 03:46:33 +00:00
if ( ! ClientActive ( ) | | client . Error )
{
Debug . Log ( $"Starting client, target address {address}." ) ;
2022-02-06 04:17:08 +00:00
2022-03-03 03:46:33 +00:00
client = Client . CreateClient ( this , address ) ;
activeNode = client ;
2022-02-06 04:17:08 +00:00
2022-03-03 03:46:33 +00:00
if ( EOSSDKComponent . CollectPlayerMetrics )
2022-02-27 12:40:44 +00:00
{
2022-03-03 03:46:33 +00:00
// 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 ) ;
2022-02-27 12:40:44 +00:00
2022-03-03 03:46:33 +00:00
if ( result = = Result . Success )
2022-02-06 04:17:08 +00:00
{
2022-03-03 03:46:33 +00:00
Debug . Log ( "Started Metric Session" ) ;
2022-02-06 04:17:08 +00:00
}
}
}
2022-03-03 03:46:33 +00:00
else
2022-02-06 04:17:08 +00:00
{
2022-03-03 03:46:33 +00:00
Debug . LogError ( "Client already running!" ) ;
2022-02-06 04:17:08 +00:00
}
2022-03-03 03:46:33 +00:00
}
2022-02-06 04:17:08 +00:00
2022-03-03 03:46:33 +00:00
public override void ClientConnect ( Uri uri )
{
if ( uri . Scheme ! = EPIC_SCHEME )
2022-02-06 04:17:08 +00:00
{
2022-03-03 03:46:33 +00:00
throw new ArgumentException ( $"Invalid url {uri}, use {EPIC_SCHEME}://EpicAccountId instead" , nameof ( uri ) ) ;
2022-02-06 04:17:08 +00:00
}
2022-03-03 03:46:33 +00:00
ClientConnect ( uri . Host ) ;
}
public override void ClientSend ( ArraySegment < byte > segment , int channelId )
{
Send ( channelId , segment ) ;
}
2022-02-06 04:17:08 +00:00
2022-03-03 03:46:33 +00:00
public override void ClientDisconnect ( )
{
if ( ClientActive ( ) )
2022-02-06 04:17:08 +00:00
{
2022-03-03 03:46:33 +00:00
Shutdown ( ) ;
2022-02-06 04:17:08 +00:00
}
2022-03-03 03:46:33 +00:00
}
2022-02-06 04:17:08 +00:00
2022-03-03 03:46:33 +00:00
public bool ClientActive ( ) = > client ! = null ;
2022-02-06 04:17:08 +00:00
2022-03-03 03:46:33 +00:00
public override bool ServerActive ( ) = > server ! = null ;
2022-02-06 04:17:08 +00:00
2022-03-03 03:46:33 +00:00
public override void ServerStart ( )
{
if ( ! EOSSDKComponent . Initialized )
{
Debug . LogError ( "EOS not initialized. Server could not be started." ) ;
return ;
2022-02-06 04:17:08 +00:00
}
2022-03-03 03:46:33 +00:00
StartCoroutine ( "FetchEpicAccountId" ) ;
2022-02-25 06:04:54 +00:00
2022-03-03 03:46:33 +00:00
if ( ClientActive ( ) )
{
Debug . LogError ( "Transport already running as client!" ) ;
return ;
2022-02-27 12:40:44 +00:00
}
2022-03-03 03:46:33 +00:00
if ( ! ServerActive ( ) )
2022-02-27 12:40:44 +00:00
{
2022-03-03 03:46:33 +00:00
Debug . Log ( "Starting server." ) ;
server = Server . CreateServer ( this , NetworkManager . singleton . maxConnections ) ;
activeNode = server ;
2022-02-06 04:17:08 +00:00
if ( EOSSDKComponent . CollectPlayerMetrics )
{
2022-03-03 03:46:33 +00:00
// 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 ) ;
2022-02-06 04:17:08 +00:00
if ( result = = Result . Success )
{
2022-03-03 03:46:33 +00:00
Debug . Log ( "Started Metric Session" ) ;
2022-02-06 04:17:08 +00:00
}
}
2022-03-03 03:46:33 +00:00
}
else
{
Debug . LogError ( "Server already started!" ) ;
}
}
public override Uri ServerUri ( )
{
var epicBuilder = new UriBuilder
{
Scheme = EPIC_SCHEME ,
Host = EOSSDKComponent . LocalUserProductIdString
} ;
2022-02-06 04:17:08 +00:00
2022-03-03 03:46:33 +00:00
return epicBuilder . Uri ;
}
2022-02-06 04:17:08 +00:00
2022-03-03 03:46:33 +00:00
public override void ServerSend ( int connectionId , ArraySegment < byte > segment , int channelId )
{
if ( ServerActive ( ) )
{
Send ( channelId , segment , connectionId ) ;
2022-02-06 04:17:08 +00:00
}
2022-03-03 03:46:33 +00:00
}
2022-02-06 04:17:08 +00:00
2022-03-03 03:46:33 +00:00
public override void ServerDisconnect ( int connectionId ) = > server . Disconnect ( connectionId ) ;
public override string ServerGetClientAddress ( int connectionId ) = > ServerActive ( ) ? server . ServerGetClientAddress ( connectionId ) : string . Empty ;
2022-02-06 04:17:08 +00:00
2022-03-03 03:46:33 +00:00
public override void ServerStop ( )
{
if ( ServerActive ( ) )
{
Shutdown ( ) ;
}
}
2022-02-06 04:17:08 +00:00
2022-03-03 03:46:33 +00:00
private void Send ( int channelId , ArraySegment < byte > segment , int connectionId = int . MinValue )
{
var packets = GetPacketArray ( channelId , segment ) ;
2022-02-06 04:17:08 +00:00
2022-03-03 03:46:33 +00:00
for ( var i = 0 ; i < packets . Length ; i + + )
2022-02-06 04:17:08 +00:00
{
2022-03-03 03:46:33 +00:00
if ( connectionId = = int . MinValue )
2022-02-06 04:17:08 +00:00
{
2022-03-03 03:46:33 +00:00
client . Send ( packets [ i ] . ToBytes ( ) , channelId ) ;
2022-02-06 04:17:08 +00:00
}
2022-03-03 03:46:33 +00:00
else
2022-02-06 04:17:08 +00:00
{
2022-03-03 03:46:33 +00:00
server . SendAll ( connectionId , packets [ i ] . ToBytes ( ) , channelId ) ;
2022-02-06 04:17:08 +00:00
}
}
2022-03-03 03:46:33 +00:00
packetId + + ;
}
private Packet [ ] GetPacketArray ( int channelId , ArraySegment < byte > segment )
{
var packetCount = Mathf . CeilToInt ( ( float ) segment . Count / ( float ) GetMaxSinglePacketSize ( channelId ) ) ;
var packets = new Packet [ packetCount ] ;
2022-02-06 04:17:08 +00:00
2022-03-03 03:46:33 +00:00
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 ) ;
2022-02-27 12:40:44 +00:00
}
2022-02-25 06:04:54 +00:00
2022-03-03 03:46:33 +00:00
return packets ;
}
public override void Shutdown ( )
{
if ( EOSSDKComponent . CollectPlayerMetrics )
2022-02-06 04:17:08 +00:00
{
2022-03-03 03:46:33 +00:00
// Stop Metrics collection session
var endSessionOptions = new EndPlayerSessionOptions ( ) ;
endSessionOptions . AccountId = EOSSDKComponent . LocalUserAccountId ;
var result = EOSSDKComponent . GetMetricsInterface ( ) . EndPlayerSession ( endSessionOptions ) ;
if ( result = = Result . Success )
2022-02-06 04:17:08 +00:00
{
2022-03-03 03:46:33 +00:00
Debug . LogError ( "Stopped Metric Session" ) ;
2022-02-06 04:17:08 +00:00
}
2022-03-03 03:46:33 +00:00
}
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
2022-02-06 04:17:08 +00:00
2022-03-03 03:46:33 +00:00
public override bool Available ( )
{
try
{
return EOSSDKComponent . Initialized ;
}
catch
{
return false ;
}
}
2022-02-06 04:17:08 +00:00
2022-03-03 03:46:33 +00:00
private IEnumerator FetchEpicAccountId ( )
{
while ( ! EOSSDKComponent . Initialized )
{
yield return null ;
2022-02-06 04:17:08 +00:00
}
2022-03-03 03:46:33 +00:00
productUserId = EOSSDKComponent . LocalUserProductId ;
}
private IEnumerator ChangeRelayStatus ( )
{
while ( ! EOSSDKComponent . Initialized )
2022-02-06 04:17:08 +00:00
{
2022-03-03 03:46:33 +00:00
yield return null ;
2022-02-06 04:17:08 +00:00
}
2022-03-03 03:46:33 +00:00
var setRelayControlOptions = new SetRelayControlOptions ( ) ;
setRelayControlOptions . RelayControl = relayControl ;
EOSSDKComponent . GetP2PInterface ( ) . SetRelayControl ( setRelayControlOptions ) ;
}
public void ResetIgnoreMessagesAtStartUpTimer ( )
{
ignoreCachedMessagesTimer = 0 ;
}
private void OnDestroy ( )
{
if ( activeNode ! = null )
2022-02-06 04:17:08 +00:00
{
2022-03-03 03:46:33 +00:00
Shutdown ( ) ;
2022-02-06 04:17:08 +00:00
}
}
2022-02-25 06:04:54 +00:00
}