diff --git a/.gitignore b/.gitignore index 96ae0de..22981be 100644 --- a/.gitignore +++ b/.gitignore @@ -27,6 +27,8 @@ # Visual Studio cache directory .vs/ +.idea/ +etc/ # Gradle cache directory .gradle/ diff --git a/Assets/EOSTransport/CHANGELOG.md b/Assets/EOSTransport/CHANGELOG.md index 31b5c46..f6223de 100644 --- a/Assets/EOSTransport/CHANGELOG.md +++ b/Assets/EOSTransport/CHANGELOG.md @@ -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) diff --git a/Assets/EOSTransport/Runtime/EOSClient.cs b/Assets/EOSTransport/Runtime/EOSClient.cs index a345a14..65ae8ec 100644 --- a/Assets/EOSTransport/Runtime/EOSClient.cs +++ b/Assets/EOSTransport/Runtime/EOSClient.cs @@ -1,4 +1,5 @@ using System; +using System.Buffers; using PurrNet.Transports; #if EOS_SDK using Epic.OnlineServices; @@ -19,7 +20,7 @@ public class EOSClient float _lastCleanupTime; public event Action onDataReceived; - public event Action onConnectionState; + public event Action onConnectionState; static readonly byte[] HANDSHAKE = { 0 }; @@ -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; @@ -65,7 +68,7 @@ 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; } @@ -73,7 +76,7 @@ public void Connect(string remoteProductUserId) 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; } @@ -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; } @@ -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 { @@ -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 @@ -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 @@ -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.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(buffer), - out var bytesWritten); + var result = _p2p.ReceivePacket( + ref receiveOptions, + ref remoteUserId, + ref socketId, + out _, + new ArraySegment(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.Shared.Return(buffer); } } #endif @@ -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; @@ -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(); @@ -274,7 +319,7 @@ public void Stop() _serverPeer?.Dispose(); _serverPeer = null; - State = ConnectionState.Disconnected; + SetState(ConnectionState.Disconnected, reason); #endif } @@ -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; } } @@ -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; @@ -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; } @@ -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 diff --git a/Assets/EOSTransport/Runtime/EOSPeer.cs b/Assets/EOSTransport/Runtime/EOSPeer.cs index c215f3b..4e5093c 100644 --- a/Assets/EOSTransport/Runtime/EOSPeer.cs +++ b/Assets/EOSTransport/Runtime/EOSPeer.cs @@ -11,9 +11,15 @@ namespace PurrNet.EOSTransport public class EOSPeer : IDisposable { public const int EOS_MAX_PACKET = 1170; + public const byte HEARTBEAT_BYTE = 1; + + static readonly byte[] HEARTBEAT_PAYLOAD = { HEARTBEAT_BYTE }; public readonly FragmentationLayer fragLayer = new(); + public float lastReceivedTime; + public float lastHeartbeatSentTime; + readonly Queue _fragmentQueue = new(); readonly Queue _pendingMessages = new(); @@ -36,19 +42,43 @@ struct QueuedMessage } #if EOS_SDK + readonly EOSTransport _transport; readonly ProductUserId _localUserId; readonly ProductUserId _remoteUserId; readonly SocketId _socketId; readonly P2PInterface _p2p; readonly Action _sendDelegate; - public EOSPeer(P2PInterface p2p, ProductUserId localUserId, ProductUserId remoteUserId, string socketName) + public EOSPeer(EOSTransport transport, P2PInterface p2p, ProductUserId localUserId, ProductUserId remoteUserId, string socketName) { + _transport = transport; _p2p = p2p; _localUserId = localUserId; _remoteUserId = remoteUserId; _socketId = new SocketId { SocketName = socketName }; _sendDelegate = OnSendFragment; + lastReceivedTime = UnityEngine.Time.unscaledTime; + lastHeartbeatSentTime = 0f; + } + + public static bool IsHeartbeat(ByteData data) + { + return data.length == 1 && data.data[data.offset] == HEARTBEAT_BYTE; + } + + public Result SendHeartbeat() + { + var options = new SendPacketOptions + { + LocalUserId = _localUserId, + RemoteUserId = _remoteUserId, + SocketId = _socketId, + Channel = (byte)Channel.Unreliable, + Data = new ArraySegment(HEARTBEAT_PAYLOAD), + AllowDelayedDelivery = true, + Reliability = PacketReliability.UnreliableUnordered + }; + return _p2p.SendPacket(ref options); } public bool Send(ByteData data, Channel channel) @@ -91,7 +121,7 @@ void OnSendFragment(ByteData fragment) else if (result != Result.Success) { _sendFailed = true; - UnityEngine.Debug.LogError($"[EOSPeer] SendPacket failed: {result}"); + _transport.LogError($"[EOSPeer] SendPacket failed: {result}"); } } @@ -152,7 +182,7 @@ public void FlushQueue() _fragmentQueue.Dequeue(); if (result != Result.Success) - UnityEngine.Debug.LogError($"[EOSPeer] Queued fragment send failed: {result}"); + _transport.LogError($"[EOSPeer] Queued fragment send failed: {result}"); } while (_pendingMessages.Count > 0) @@ -187,6 +217,7 @@ static PacketReliability GetReliability(Channel channel) public EOSPeer() { } public bool Send(ByteData data, Channel channel) => false; public void FlushQueue() { } + public static bool IsHeartbeat(ByteData data) => false; #endif public void Dispose() diff --git a/Assets/EOSTransport/Runtime/EOSServer.cs b/Assets/EOSTransport/Runtime/EOSServer.cs index febb965..a776883 100644 --- a/Assets/EOSTransport/Runtime/EOSServer.cs +++ b/Assets/EOSTransport/Runtime/EOSServer.cs @@ -1,5 +1,7 @@ using System; +using System.Buffers; using System.Collections.Generic; +using PurrNet.Pooling; using PurrNet.Transports; #if EOS_SDK using Epic.OnlineServices; @@ -20,7 +22,7 @@ public class EOSServer public event Action onDataReceived; public event Action onRemoteConnected; - public event Action onRemoteDisconnected; + public event Action onRemoteDisconnected; #if EOS_SDK P2PInterface _p2p; @@ -55,7 +57,7 @@ public bool Listen() if (_p2p == null || _localUserId == null) { - UnityEngine.Debug.LogError("[EOSServer] P2P interface or local user not available"); + _transport.LogError("[EOSServer] P2P interface or local user not available"); return false; } @@ -86,7 +88,7 @@ public bool Listen() } catch (Exception e) { - UnityEngine.Debug.LogError($"[EOSServer] Failed to start: {e}"); + _transport.LogError($"[EOSServer] Failed to start: {e}"); return false; } #else @@ -105,7 +107,7 @@ public void SendToConnection(int connectionId, ByteData data, Channel channel) if (!peer.Send(data, channel)) { - UnityEngine.Debug.LogError($"[EOSServer] Send failed for connection {connectionId}, disconnecting"); + _transport.LogError($"[EOSServer] Send failed for connection {connectionId}, disconnecting"); CloseConnection(connectionId); } #endif @@ -113,6 +115,11 @@ public void SendToConnection(int connectionId, ByteData data, Channel channel) public void CloseConnection(int connectionId) { + CloseConnectionInternal(connectionId, DisconnectReason.ServerRequest); + } + + void CloseConnectionInternal(int connectionId, DisconnectReason reason) + { #if EOS_SDK if (!_connectionToUserId.TryGetValue(connectionId, out var userId)) return; @@ -136,7 +143,7 @@ public void CloseConnection(int connectionId) _connectionToUserId.Remove(connectionId); _userIdToConnection.Remove(userId); - onRemoteDisconnected?.Invoke(connectionId); + onRemoteDisconnected?.Invoke(connectionId, reason); #endif } @@ -149,8 +156,9 @@ public void ReceiveMessages() if (EOSManager.Instance == null || !EOSManager.Instance.HasLoggedInWithConnect()) return; - int maxReads = 2048; - for (int i = 0; i < maxReads; i++) + const int MAX_READS = 2048; + + for (int i = 0; i < MAX_READS; i++) { var getSizeOptions = new GetNextReceivedPacketSizeOptions { @@ -160,45 +168,57 @@ 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.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(buffer), - out var bytesWritten); + var result = _p2p.ReceivePacket( + ref receiveOptions, + ref remoteUserId, + ref socketId, + out _, + new ArraySegment(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; - var rawData = new ByteData(buffer, 0, (int)bytesWritten); + var rawData = new ByteData(buffer, 0, (int)bytesWritten); - if (IsHandshake(rawData)) - continue; + var userIdStr = remoteUserId.ToString(); + if (!_peers.TryGetValue(userIdStr, out var peer)) + continue; - var userIdStr = remoteUserId.ToString(); - if (!_peers.TryGetValue(userIdStr, out var peer)) - continue; + if (!_userIdToConnection.TryGetValue(userIdStr, out var connectionId)) + continue; - if (!_userIdToConnection.TryGetValue(userIdStr, out var connectionId)) - continue; + peer.lastReceivedTime = UnityEngine.Time.unscaledTime; - if (peer.fragLayer.Receive(rawData, out var assembled)) - onDataReceived?.Invoke(connectionId, assembled); + if (IsHandshake(rawData)) + continue; + + if (EOSPeer.IsHeartbeat(rawData)) + continue; + + if (peer.fragLayer.Receive(rawData, out var assembled)) + onDataReceived?.Invoke(connectionId, assembled); + } + finally + { + ArrayPool.Shared.Return(buffer); + } } #endif } @@ -211,11 +231,37 @@ public void SendMessages() if (doCleanup) _lastCleanupTime = now; + float timeout = _transport.connectionTimeout; + float interval = _transport.heartbeatInterval; + + using var timedOut = DisposableList.Create(); foreach (var kvp in _peers) { - kvp.Value.FlushQueue(); + var peer = kvp.Value; + + if (now - peer.lastReceivedTime > timeout) + { + if (_userIdToConnection.TryGetValue(kvp.Key, out var connId)) + timedOut.Add(connId); + continue; + } + + if (now - peer.lastHeartbeatSentTime >= interval) + { + peer.SendHeartbeat(); + peer.lastHeartbeatSentTime = now; + } + + peer.FlushQueue(); if (doCleanup) - kvp.Value.fragLayer.CleanupStale(30000); + peer.fragLayer.CleanupStale(30000); + } + + for (int i = 0; i < timedOut.Count; i++) + { + int connId = timedOut[i]; + _transport.LogWarning($"[EOSServer] Connection {connId} timed out (no packet for >{timeout}s)"); + CloseConnectionInternal(connId, DisconnectReason.Timeout); } #endif } @@ -261,7 +307,7 @@ void OnConnectionRequest(ref OnIncomingConnectionRequestInfo info) var result = _p2p.AcceptConnection(ref acceptOptions); if (result != Result.Success) - UnityEngine.Debug.LogError($"[EOSServer] AcceptConnection failed: {result}"); + _transport.LogError($"[EOSServer] AcceptConnection failed: {result}"); } void OnConnectionEstablished(ref OnPeerConnectionEstablishedInfo info) @@ -271,14 +317,14 @@ void OnConnectionEstablished(ref OnPeerConnectionEstablishedInfo info) if (_peers.ContainsKey(userIdStr)) return; - var peer = new EOSPeer(_p2p, _localUserId, info.RemoteUserId, _transport.socketName); + var peer = new EOSPeer(_transport, _p2p, _localUserId, info.RemoteUserId, _transport.socketName); int connectionId = _nextConnectionId++; _peers[userIdStr] = peer; _connectionToUserId[connectionId] = userIdStr; _userIdToConnection[userIdStr] = connectionId; - UnityEngine.Debug.Log($"[EOSServer] Peer connected: {userIdStr} (id={connectionId})"); + _transport.LogInfo($"[EOSServer] Peer connected: {userIdStr} (id={connectionId})"); onRemoteConnected?.Invoke(connectionId); } @@ -296,8 +342,8 @@ void OnConnectionClosed(ref OnRemoteConnectionClosedInfo info) _connectionToUserId.Remove(connectionId); _userIdToConnection.Remove(userIdStr); - UnityEngine.Debug.Log($"[EOSServer] Peer disconnected: {userIdStr} (id={connectionId}, reason={info.Reason})"); - onRemoteDisconnected?.Invoke(connectionId); + _transport.LogInfo($"[EOSServer] Peer disconnected: {userIdStr} (id={connectionId}, reason={info.Reason})"); + onRemoteDisconnected?.Invoke(connectionId, DisconnectReason.ClientRequest); } void SafeRemoveRequest(P2PInterface p2p) @@ -305,7 +351,7 @@ void SafeRemoveRequest(P2PInterface p2p) if (_notifyRequestHandle == Common.INVALID_NOTIFICATIONID) return; try { p2p.RemoveNotifyPeerConnectionRequest(_notifyRequestHandle); } - catch (Exception e) { UnityEngine.Debug.LogWarning($"[EOSServer] RemoveNotifyPeerConnectionRequest failed: {e.Message}"); } + catch (Exception e) { _transport.LogWarning($"[EOSServer] RemoveNotifyPeerConnectionRequest failed: {e.Message}"); } _notifyRequestHandle = Common.INVALID_NOTIFICATIONID; } @@ -314,7 +360,7 @@ void SafeRemoveEstablished(P2PInterface p2p) if (_notifyEstablishedHandle == Common.INVALID_NOTIFICATIONID) return; try { p2p.RemoveNotifyPeerConnectionEstablished(_notifyEstablishedHandle); } - catch (Exception e) { UnityEngine.Debug.LogWarning($"[EOSServer] RemoveNotifyPeerConnectionEstablished failed: {e.Message}"); } + catch (Exception e) { _transport.LogWarning($"[EOSServer] RemoveNotifyPeerConnectionEstablished failed: {e.Message}"); } _notifyEstablishedHandle = Common.INVALID_NOTIFICATIONID; } @@ -323,7 +369,7 @@ void SafeRemoveClosed(P2PInterface p2p) if (_notifyClosedHandle == Common.INVALID_NOTIFICATIONID) return; try { p2p.RemoveNotifyPeerConnectionClosed(_notifyClosedHandle); } - catch (Exception e) { UnityEngine.Debug.LogWarning($"[EOSServer] RemoveNotifyPeerConnectionClosed failed: {e.Message}"); } + catch (Exception e) { _transport.LogWarning($"[EOSServer] RemoveNotifyPeerConnectionClosed failed: {e.Message}"); } _notifyClosedHandle = Common.INVALID_NOTIFICATIONID; } #endif diff --git a/Assets/EOSTransport/Runtime/EOSTransport.cs b/Assets/EOSTransport/Runtime/EOSTransport.cs index 9e15b9e..8f9ec46 100644 --- a/Assets/EOSTransport/Runtime/EOSTransport.cs +++ b/Assets/EOSTransport/Runtime/EOSTransport.cs @@ -17,6 +17,41 @@ public class EOSTransport : GenericTransport, ITransport [SerializeField] string _socketName = "PurrNetEOS"; [SerializeField] string _remoteProductUserId; + [Header("Timeout")] + [SerializeField, Tooltip("Seconds without any received packet before the connection is considered timed out and dropped with DisconnectReason.Timeout.")] + [Min(0.5f)] float _connectionTimeout = 5f; + + [SerializeField, Tooltip("Seconds between keep-alive packets sent to each peer. Should be well under Connection Timeout (e.g. 1/5).")] + [Min(0.1f)] float _heartbeatInterval = 1f; + + [Header("Logging")] + [SerializeField, Tooltip("Minimum log level emitted by this transport. None silences all output; Info is the most verbose.")] + EOSLogLevel _logLevel = EOSLogLevel.Warning; + + public EOSLogLevel logLevel + { + get => _logLevel; + set => _logLevel = value; + } + + internal void LogInfo(string msg) + { + if (_logLevel >= EOSLogLevel.Info) + Debug.Log(msg); + } + + internal void LogWarning(string msg) + { + if (_logLevel >= EOSLogLevel.Warning) + Debug.LogWarning(msg); + } + + internal void LogError(string msg) + { + if (_logLevel >= EOSLogLevel.Error) + Debug.LogError(msg); + } + public string socketName { get => _socketName; @@ -29,6 +64,18 @@ public string remoteProductUserId set => _remoteProductUserId = value; } + public float connectionTimeout + { + get => _connectionTimeout; + set => _connectionTimeout = value; + } + + public float heartbeatInterval + { + get => _heartbeatInterval; + set => _heartbeatInterval = value; + } + public int GetMTU(Connection target, Channel channel, bool asServer) { return channel switch @@ -165,10 +212,10 @@ void OnRemoteConnected(int connectionId) onConnected?.Invoke(new Connection(connectionId), true); } - void OnRemoteDisconnected(int connectionId) + void OnRemoteDisconnected(int connectionId, DisconnectReason reason) { _connections.Remove(new Connection(connectionId)); - onDisconnected?.Invoke(new Connection(connectionId), DisconnectReason.ClientRequest, true); + onDisconnected?.Invoke(new Connection(connectionId), reason, true); } void OnServerData(int connectionId, ByteData data) @@ -209,7 +256,7 @@ void OnClientDataReceived(ByteData data) onDataReceived?.Invoke(new Connection(-1), data, false); } - void OnClientStateChanged(ConnectionState state) + void OnClientStateChanged(ConnectionState state, DisconnectReason reason) { clientState = state; @@ -217,7 +264,7 @@ void OnClientStateChanged(ConnectionState state) onConnected?.Invoke(new Connection(0), false); if (state == ConnectionState.Disconnected) - onDisconnected?.Invoke(new Connection(0), DisconnectReason.ClientRequest, false); + onDisconnected?.Invoke(new Connection(0), reason, false); } public void Disconnect() @@ -329,7 +376,7 @@ public void SendMessages(float delta) } #if EOS_SDK - public static void ConfigurePacketQueue() + void ConfigurePacketQueue() { var p2p = EOSManager.Instance?.GetEOSPlatformInterface()?.GetP2PInterface(); if (p2p == null) return; @@ -342,8 +389,16 @@ public static void ConfigurePacketQueue() var result = p2p.SetPacketQueueSize(ref options); if (result != Result.Success) - Debug.LogWarning($"[EOSTransport] SetPacketQueueSize returned: {result}"); + LogWarning($"[EOSTransport] SetPacketQueueSize returned: {result}"); } #endif } + + public enum EOSLogLevel + { + None = 0, + Error = 1, + Warning = 2, + Info = 3, + } } diff --git a/Assets/EOSTransport/Runtime/EOSTransport.cs.meta b/Assets/EOSTransport/Runtime/EOSTransport.cs.meta index 04f22e8..f003c12 100644 --- a/Assets/EOSTransport/Runtime/EOSTransport.cs.meta +++ b/Assets/EOSTransport/Runtime/EOSTransport.cs.meta @@ -1,2 +1,11 @@ fileFormatVersion: 2 -guid: e220831cf81c0684caa31b14108795ce \ No newline at end of file +guid: e220831cf81c0684caa31b14108795ce +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {fileID: 2800000, guid: 842599860f0474c4a8ee84c73977d23c, type: 3} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/EOSTransport/package.json b/Assets/EOSTransport/package.json index 08e2e18..4c1e485 100644 --- a/Assets/EOSTransport/package.json +++ b/Assets/EOSTransport/package.json @@ -1,6 +1,6 @@ { "name": "dev.purrnet.eostransport", - "version": "1.0.0", + "version": "1.0.0-beta.3", "author": { "name": "PurrNet Team", "url": "https://purrnet.dev/" diff --git a/Assets/StreamingAssets.meta b/Assets/StreamingAssets.meta new file mode 100644 index 0000000..3705e5b --- /dev/null +++ b/Assets/StreamingAssets.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 4ec0d8116d046b74c8c5f4df1639a4bb +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/StreamingAssets/EOS.meta b/Assets/StreamingAssets/EOS.meta new file mode 100644 index 0000000..5b39beb --- /dev/null +++ b/Assets/StreamingAssets/EOS.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 23b069621067fc9478a7cd8098a84885 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/StreamingAssets/EOS/eos_steam_config.json b/Assets/StreamingAssets/EOS/eos_steam_config.json new file mode 100644 index 0000000..ba70ca0 --- /dev/null +++ b/Assets/StreamingAssets/EOS/eos_steam_config.json @@ -0,0 +1,8 @@ +{ + "overrideLibraryPath": null, + "steamSDKMajorVersion": 0, + "steamSDKMinorVersion": 0, + "steamApiInterfaceVersionsArray": null, + "integratedPlatformManagementFlags": 0, + "schemaVersion": "1.0" +} \ No newline at end of file diff --git a/Assets/StreamingAssets/EOS/eos_steam_config.json.meta b/Assets/StreamingAssets/EOS/eos_steam_config.json.meta new file mode 100644 index 0000000..2aedf93 --- /dev/null +++ b/Assets/StreamingAssets/EOS/eos_steam_config.json.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 92ca590fe23f6164ca0eb06e1e00c97a +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: