Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@

# Visual Studio cache directory
.vs/
.idea/
etc/

# Gradle cache directory
.gradle/
Expand Down
8 changes: 3 additions & 5 deletions Assets/EOSTransport/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,11 +1,9 @@
# 1.0.0 (2026-05-07)
# [1.0.0-beta.3](https://github.com/PurrNet/PurrNetEOSTransport/compare/v1.0.0-beta.2...v1.0.0-beta.3) (2026-05-07)


### Bug Fixes
### Features

* Add initial transport + licensing and semantics handling ([723d846](https://github.com/PurrNet/PurrNetEOSTransport/commit/723d846a49259d6695c923387a3f8ce92020d458))
* add release back in semantics ([7fd102d](https://github.com/PurrNet/PurrNetEOSTransport/commit/7fd102d58913e1ad2817c2be1077bcfbed57c455))
* Imported EOS PlayEverware ([132ba5a](https://github.com/PurrNet/PurrNetEOSTransport/commit/132ba5aa35d2a251ebfbe7533fcbedbcb92229fe))
* timeout settings, log level and GC patches ([cfc895d](https://github.com/PurrNet/PurrNetEOSTransport/commit/cfc895dbc9b10c8259906f4f796eb7bd4c03a788))

# 1.0.0 (2026-05-06)

Expand Down
151 changes: 98 additions & 53 deletions Assets/EOSTransport/Runtime/EOSClient.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System;
using System.Buffers;
using PurrNet.Transports;
#if EOS_SDK
using Epic.OnlineServices;
Expand All @@ -19,7 +20,7 @@ public class EOSClient
float _lastCleanupTime;

public event Action<ByteData> onDataReceived;
public event Action<ConnectionState> onConnectionState;
public event Action<ConnectionState, DisconnectReason> onConnectionState;

static readonly byte[] HANDSHAKE = { 0 };

Expand All @@ -35,13 +36,15 @@ public class EOSClient
ConnectionState State
{
get => _state;
set
{
if (_state == value)
return;
_state = value;
onConnectionState?.Invoke(_state);
}
set => SetState(value, DisconnectReason.ClientRequest);
}

void SetState(ConnectionState newState, DisconnectReason reason)
{
if (_state == newState)
return;
_state = newState;
onConnectionState?.Invoke(_state, reason);
}

public ConnectionState connectionState => _state;
Expand All @@ -65,15 +68,15 @@ public void Connect(string remoteProductUserId)

if (_p2p == null || _localUserId == null)
{
UnityEngine.Debug.LogError("[EOSClient] P2P interface or local user not available");
_transport.LogError("[EOSClient] P2P interface or local user not available");
State = ConnectionState.Disconnected;
return;
}

var remoteUserId = ProductUserId.FromString(remoteProductUserId);
if (remoteUserId == null)
{
UnityEngine.Debug.LogError("[EOSClient] Invalid remote ProductUserId");
_transport.LogError("[EOSClient] Invalid remote ProductUserId");
State = ConnectionState.Disconnected;
return;
}
Expand All @@ -90,7 +93,7 @@ public void Connect(string remoteProductUserId)
var acceptResult = _p2p.AcceptConnection(ref acceptOptions);
if (acceptResult != Result.Success)
{
UnityEngine.Debug.LogError($"[EOSClient] AcceptConnection failed: {acceptResult}");
_transport.LogError($"[EOSClient] AcceptConnection failed: {acceptResult}");
State = ConnectionState.Disconnected;
return;
}
Expand All @@ -109,7 +112,7 @@ public void Connect(string remoteProductUserId)
};
_notifyClosedHandle = _p2p.AddNotifyPeerConnectionClosed(ref closedOptions, null, OnConnectionClosed);

_serverPeer = new EOSPeer(_p2p, _localUserId, remoteUserId, _transport.socketName);
_serverPeer = new EOSPeer(_transport, _p2p, _localUserId, remoteUserId, _transport.socketName);

var sendOptions = new SendPacketOptions
{
Expand All @@ -125,17 +128,17 @@ public void Connect(string remoteProductUserId)
var sendResult = _p2p.SendPacket(ref sendOptions);
if (sendResult != Result.Success)
{
UnityEngine.Debug.LogError($"[EOSClient] Failed to send handshake: {sendResult}");
_transport.LogError($"[EOSClient] Failed to send handshake: {sendResult}");
State = ConnectionState.Disconnected;
}
else
{
UnityEngine.Debug.Log("[EOSClient] Handshake sent, waiting for connection...");
_transport.LogInfo("[EOSClient] Handshake sent, waiting for connection...");
}
}
catch (Exception e)
{
UnityEngine.Debug.LogError($"[EOSClient] Failed to connect: {e}");
_transport.LogError($"[EOSClient] Failed to connect: {e}");
State = ConnectionState.Disconnected;
}
#endif
Expand All @@ -149,7 +152,7 @@ public void Send(ByteData data, Channel channel)

if (!_serverPeer.Send(data, channel))
{
UnityEngine.Debug.LogError("[EOSClient] Send failed, disconnecting");
_transport.LogError("[EOSClient] Send failed, disconnecting");
Stop();
}
#endif
Expand Down Expand Up @@ -178,48 +181,67 @@ public void ReceiveMessages()
if (_p2p.GetNextReceivedPacketSize(ref getSizeOptions, out var packetSize) != Result.Success)
break;

var buffer = new byte[packetSize];
var receiveOptions = new ReceivePacketOptions
int size = (int)packetSize;
var buffer = ArrayPool<byte>.Shared.Rent(size);
try
{
LocalUserId = _localUserId,
MaxDataSizeBytes = packetSize
};
var receiveOptions = new ReceivePacketOptions
{
LocalUserId = _localUserId,
MaxDataSizeBytes = packetSize
};

ProductUserId remoteUserId = null;
var socketId = new SocketId();
byte eosChannel = 0;
ProductUserId remoteUserId = null;
var socketId = new SocketId();

var result = _p2p.ReceivePacket(
ref receiveOptions,
ref remoteUserId,
ref socketId,
out eosChannel,
new ArraySegment<byte>(buffer),
out var bytesWritten);
var result = _p2p.ReceivePacket(
ref receiveOptions,
ref remoteUserId,
ref socketId,
out _,
new ArraySegment<byte>(buffer, 0, size),
out var bytesWritten);

if (result != Result.Success)
break;
if (result != Result.Success)
break;

if (socketId.SocketName != _transport.socketName)
continue;
if (socketId.SocketName != _transport.socketName)
continue;

if (remoteUserId.ToString() != _remoteProductUserId)
continue;
if (remoteUserId.ToString() != _remoteProductUserId)
continue;

if (_serverPeer == null)
continue;
if (_serverPeer == null)
continue;

var rawData = new ByteData(buffer, 0, (int)bytesWritten);
var rawData = new ByteData(buffer, 0, (int)bytesWritten);

if (_serverPeer.fragLayer.Receive(rawData, out var assembled))
{
if (_state == ConnectionState.Connecting)
_serverPeer.lastReceivedTime = UnityEngine.Time.unscaledTime;

if (EOSPeer.IsHeartbeat(rawData))
{
UnityEngine.Debug.Log("[EOSClient] Connection established with server (first data)");
State = ConnectionState.Connected;
if (_state == ConnectionState.Connecting)
{
_transport.LogInfo("[EOSClient] Connection established with server (heartbeat)");
State = ConnectionState.Connected;
}
continue;
}

onDataReceived?.Invoke(assembled);
if (_serverPeer.fragLayer.Receive(rawData, out var assembled))
{
if (_state == ConnectionState.Connecting)
{
_transport.LogInfo("[EOSClient] Connection established with server (first data)");
State = ConnectionState.Connected;
}

onDataReceived?.Invoke(assembled);
}
}
finally
{
ArrayPool<byte>.Shared.Return(buffer);
}
}
#endif
Expand All @@ -231,9 +253,27 @@ public void SendMessages()
if (_serverPeer == null)
return;

float now = UnityEngine.Time.unscaledTime;

if (_state == ConnectionState.Connecting || _state == ConnectionState.Connected)
{
if (now - _serverPeer.lastReceivedTime > _transport.connectionTimeout)
{
_transport.LogWarning($"[EOSClient] Connection timed out (no packet for >{_transport.connectionTimeout}s)");
StopWithReason(DisconnectReason.Timeout);
return;
}

if (_state == ConnectionState.Connected &&
now - _serverPeer.lastHeartbeatSentTime >= _transport.heartbeatInterval)
{
_serverPeer.SendHeartbeat();
_serverPeer.lastHeartbeatSentTime = now;
}
}

_serverPeer.FlushQueue();

float now = UnityEngine.Time.unscaledTime;
if (now - _lastCleanupTime > 5f)
{
_lastCleanupTime = now;
Expand All @@ -244,11 +284,16 @@ public void SendMessages()

public void Stop()
{
StopWithReason(DisconnectReason.ClientRequest);
}

public void StopWithReason(DisconnectReason reason)
{
#if EOS_SDK
if (_state == ConnectionState.Disconnected)
return;

State = ConnectionState.Disconnecting;
SetState(ConnectionState.Disconnecting, reason);

var platform = EOSManager.Instance?.GetEOSPlatformInterface();
var p2p = platform?.GetP2PInterface();
Expand All @@ -274,7 +319,7 @@ public void Stop()
_serverPeer?.Dispose();
_serverPeer = null;

State = ConnectionState.Disconnected;
SetState(ConnectionState.Disconnected, reason);
#endif
}

Expand All @@ -286,7 +331,7 @@ void OnConnectionEstablished(ref OnPeerConnectionEstablishedInfo info)

if (_state == ConnectionState.Connecting)
{
UnityEngine.Debug.Log("[EOSClient] Connection established with server (EOS notification)");
_transport.LogInfo("[EOSClient] Connection established with server (EOS notification)");
State = ConnectionState.Connected;
}
}
Expand All @@ -296,7 +341,7 @@ void OnConnectionClosed(ref OnRemoteConnectionClosedInfo info)
if (info.RemoteUserId.ToString() != _remoteProductUserId)
return;

UnityEngine.Debug.Log($"[EOSClient] Connection closed (reason={info.Reason})");
_transport.LogInfo($"[EOSClient] Connection closed (reason={info.Reason})");

_serverPeer?.Dispose();
_serverPeer = null;
Expand All @@ -319,7 +364,7 @@ void SafeRemoveEstablished(P2PInterface p2p)
if (_notifyEstablishedHandle == Common.INVALID_NOTIFICATIONID)
return;
try { p2p.RemoveNotifyPeerConnectionEstablished(_notifyEstablishedHandle); }
catch (Exception e) { UnityEngine.Debug.LogWarning($"[EOSClient] RemoveNotifyPeerConnectionEstablished failed: {e.Message}"); }
catch (Exception e) { _transport.LogWarning($"[EOSClient] RemoveNotifyPeerConnectionEstablished failed: {e.Message}"); }
_notifyEstablishedHandle = Common.INVALID_NOTIFICATIONID;
}

Expand All @@ -328,7 +373,7 @@ void SafeRemoveClosed(P2PInterface p2p)
if (_notifyClosedHandle == Common.INVALID_NOTIFICATIONID)
return;
try { p2p.RemoveNotifyPeerConnectionClosed(_notifyClosedHandle); }
catch (Exception e) { UnityEngine.Debug.LogWarning($"[EOSClient] RemoveNotifyPeerConnectionClosed failed: {e.Message}"); }
catch (Exception e) { _transport.LogWarning($"[EOSClient] RemoveNotifyPeerConnectionClosed failed: {e.Message}"); }
_notifyClosedHandle = Common.INVALID_NOTIFICATIONID;
}
#endif
Expand Down
Loading
Loading