From 1445fc014f851a78636415e8b7a95faeb6b0bf0b Mon Sep 17 00:00:00 2001 From: Adrian Mazur Date: Tue, 25 Aug 2026 02:10:54 +0200 Subject: [PATCH] feat: implement multiplayer session state management and add invalid port exception --- .../MultiplayerAlreadyConnectedException.cs | 5 ++- .../MultiplayerInvalidPortException.cs | 4 ++ .../MultiplayerInvalidPortException.cs.uid | 1 + .../Multiplayer/Domain/MultiplayerSession.cs | 22 +++++++++-- .../Domain/MultiplayerSessionState.cs | 9 +++++ .../Domain/MultiplayerSessionState.cs.uid | 1 + .../Godot/MultiplayerController.cs | 38 ++++++++++++++----- 7 files changed, 64 insertions(+), 16 deletions(-) create mode 100644 src/Features/Multiplayer/Contracts/Exceptions/MultiplayerInvalidPortException.cs create mode 100644 src/Features/Multiplayer/Contracts/Exceptions/MultiplayerInvalidPortException.cs.uid create mode 100644 src/Features/Multiplayer/Domain/MultiplayerSessionState.cs create mode 100644 src/Features/Multiplayer/Domain/MultiplayerSessionState.cs.uid diff --git a/src/Features/Multiplayer/Contracts/Exceptions/MultiplayerAlreadyConnectedException.cs b/src/Features/Multiplayer/Contracts/Exceptions/MultiplayerAlreadyConnectedException.cs index 88fb049..570bde6 100644 --- a/src/Features/Multiplayer/Contracts/Exceptions/MultiplayerAlreadyConnectedException.cs +++ b/src/Features/Multiplayer/Contracts/Exceptions/MultiplayerAlreadyConnectedException.cs @@ -1,6 +1,7 @@ namespace Game.Features.Multiplayer.Contracts.Exceptions; /// -/// Thrown when trying to host or join while already connected to a session. +/// Thrown when trying to host or join while connecting or already connected to a session. /// -public sealed class MultiplayerAlreadyConnectedException() : MultiplayerException("Already connected to a session."); +public sealed class MultiplayerAlreadyConnectedException() + : MultiplayerException("Already connected to or connecting with a session."); diff --git a/src/Features/Multiplayer/Contracts/Exceptions/MultiplayerInvalidPortException.cs b/src/Features/Multiplayer/Contracts/Exceptions/MultiplayerInvalidPortException.cs new file mode 100644 index 0000000..0e76439 --- /dev/null +++ b/src/Features/Multiplayer/Contracts/Exceptions/MultiplayerInvalidPortException.cs @@ -0,0 +1,4 @@ +namespace Game.Features.Multiplayer.Contracts.Exceptions; + +public sealed class MultiplayerInvalidPortException(int port) + : MultiplayerException($"Invalid port {port}. Port must be between 1 and 65535."); diff --git a/src/Features/Multiplayer/Contracts/Exceptions/MultiplayerInvalidPortException.cs.uid b/src/Features/Multiplayer/Contracts/Exceptions/MultiplayerInvalidPortException.cs.uid new file mode 100644 index 0000000..35fc532 --- /dev/null +++ b/src/Features/Multiplayer/Contracts/Exceptions/MultiplayerInvalidPortException.cs.uid @@ -0,0 +1 @@ +uid://d3n8p5w2y7c4h diff --git a/src/Features/Multiplayer/Domain/MultiplayerSession.cs b/src/Features/Multiplayer/Domain/MultiplayerSession.cs index 8c03f08..9f3c51d 100644 --- a/src/Features/Multiplayer/Domain/MultiplayerSession.cs +++ b/src/Features/Multiplayer/Domain/MultiplayerSession.cs @@ -8,8 +8,7 @@ public sealed class MultiplayerSession public const int MaxPlayers = 4; public IReadOnlyCollection PeerIds => _peerIds; public bool IsFull => _peerIds.Count >= MaxPlayers; - public bool IsHosting { get; set; } - public bool IsConnected { get; set; } + public MultiplayerSessionState State { get; private set; } = MultiplayerSessionState.Disconnected; private readonly HashSet _peerIds = []; @@ -17,10 +16,25 @@ public sealed class MultiplayerSession public void RemovePeer(PeerId peerId) => _peerIds.Remove(peerId); + public void StartHosting(PeerId hostPeerId) + { + _peerIds.Add(hostPeerId); + State = MultiplayerSessionState.Hosting; + } + + public void StartConnecting() + { + State = MultiplayerSessionState.Connecting; + } + + public void MarkConnected() + { + State = MultiplayerSessionState.Connected; + } + public void Reset() { _peerIds.Clear(); - IsHosting = false; - IsConnected = false; + State = MultiplayerSessionState.Disconnected; } } diff --git a/src/Features/Multiplayer/Domain/MultiplayerSessionState.cs b/src/Features/Multiplayer/Domain/MultiplayerSessionState.cs new file mode 100644 index 0000000..f1ceada --- /dev/null +++ b/src/Features/Multiplayer/Domain/MultiplayerSessionState.cs @@ -0,0 +1,9 @@ +namespace Game.Features.Multiplayer.Domain; + +public enum MultiplayerSessionState +{ + Disconnected, + Hosting, + Connecting, + Connected +} diff --git a/src/Features/Multiplayer/Domain/MultiplayerSessionState.cs.uid b/src/Features/Multiplayer/Domain/MultiplayerSessionState.cs.uid new file mode 100644 index 0000000..7b3015b --- /dev/null +++ b/src/Features/Multiplayer/Domain/MultiplayerSessionState.cs.uid @@ -0,0 +1 @@ +uid://b7m2q9r4v6x1k diff --git a/src/Features/Multiplayer/Godot/MultiplayerController.cs b/src/Features/Multiplayer/Godot/MultiplayerController.cs index f92042c..2c81630 100644 --- a/src/Features/Multiplayer/Godot/MultiplayerController.cs +++ b/src/Features/Multiplayer/Godot/MultiplayerController.cs @@ -8,6 +8,9 @@ namespace Game.Features.Multiplayer.Godot; public partial class MultiplayerController : Node, IMultiplayerPeer { + private const int MinPort = 1; + private const int MaxPort = 65535; + private readonly MultiplayerSession _session = new(); private IMultiplayerPeerFactory _multiplayerPeerFactory; @@ -33,29 +36,45 @@ public partial class MultiplayerController : Node, IMultiplayerPeer private void OnConnectedToServer() { + _session.MarkConnected(); Connected?.Invoke(); } private void OnConnectionFailed() { - _session.Reset(); + ResetConnection(); ConnectionFailed?.Invoke(); } private void OnServerDisconnected() { - _session.Reset(); + ResetConnection(); Disconnected?.Invoke(); } + + private static void ValidatePort(int port) + { + if (port is < MinPort or > MaxPort) + { + throw new MultiplayerInvalidPortException(port); + } + } + + private void ResetConnection() + { + Multiplayer.MultiplayerPeer?.Close(); + Multiplayer.MultiplayerPeer = null; + _session.Reset(); + } private void AssertNoActiveSession() { - if (_session.IsHosting) + if (_session.State == MultiplayerSessionState.Hosting) { throw new MultiplayerAlreadyHostingException(); } - if (_session.IsConnected) + if (_session.State is MultiplayerSessionState.Connecting or MultiplayerSessionState.Connected) { throw new MultiplayerAlreadyConnectedException(); } @@ -68,26 +87,25 @@ public partial class MultiplayerController : Node, IMultiplayerPeer public void Host(int port) { + ValidatePort(port); AssertNoActiveSession(); Multiplayer.MultiplayerPeer = _multiplayerPeerFactory.CreateHost(port, MultiplayerSession.MaxPlayers - 1); - _session.AddPeer(new PeerId(Multiplayer.GetUniqueId())); - _session.IsHosting = true; + _session.StartHosting(new PeerId(Multiplayer.GetUniqueId())); } public void Join(string address, int port) { + ValidatePort(port); AssertNoActiveSession(); Multiplayer.MultiplayerPeer = _multiplayerPeerFactory.CreateClient(address, port); - _session.IsConnected = true; + _session.StartConnecting(); } public void Disconnect() { - Multiplayer.MultiplayerPeer?.Close(); - Multiplayer.MultiplayerPeer = null; - _session.Reset(); + ResetConnection(); } public override void _Ready()