From 23a0c8e8939a6e6e9df33915fedbc952720b7259 Mon Sep 17 00:00:00 2001 From: Syntriax Date: Sat, 10 May 2025 22:38:01 +0300 Subject: [PATCH] feat: added revised version of the old networking system --- Engine | 2 +- Platforms/Desktop/Desktop.csproj | 2 +- Shared/Network/Abstract/INetworkBehaviour.cs | 8 ++ .../Network/Abstract/INetworkCommunicator.cs | 28 ++++++ Shared/Network/Abstract/INetworkEntity.cs | 8 ++ Shared/Network/Abstract/INetworkManager.cs | 12 +++ Shared/Network/Abstract/INetworkPacket.cs | 3 + Shared/Network/Abstract/PacketEntityData.cs | 7 ++ Shared/Network/NetworkBase.cs | 98 +++++++++++++++++++ Shared/Network/NetworkClient.cs | 27 +++++ Shared/Network/NetworkManager.cs | 36 +++++++ Shared/Network/NetworkServer.cs | 60 ++++++++++++ Shared/Shared.csproj | 3 +- 13 files changed, 291 insertions(+), 3 deletions(-) create mode 100644 Shared/Network/Abstract/INetworkBehaviour.cs create mode 100644 Shared/Network/Abstract/INetworkCommunicator.cs create mode 100644 Shared/Network/Abstract/INetworkEntity.cs create mode 100644 Shared/Network/Abstract/INetworkManager.cs create mode 100644 Shared/Network/Abstract/INetworkPacket.cs create mode 100644 Shared/Network/Abstract/PacketEntityData.cs create mode 100644 Shared/Network/NetworkBase.cs create mode 100644 Shared/Network/NetworkClient.cs create mode 100644 Shared/Network/NetworkManager.cs create mode 100644 Shared/Network/NetworkServer.cs diff --git a/Engine b/Engine index 6e5b805..0bf3823 160000 --- a/Engine +++ b/Engine @@ -1 +1 @@ -Subproject commit 6e5b805803ce60f9ad904180406e57de9e9f9c4e +Subproject commit 0bf38234c6dbef8824045c81c4bd79c440a70abc diff --git a/Platforms/Desktop/Desktop.csproj b/Platforms/Desktop/Desktop.csproj index 89d8332..8981cd7 100644 --- a/Platforms/Desktop/Desktop.csproj +++ b/Platforms/Desktop/Desktop.csproj @@ -1,7 +1,7 @@ WinExe - net8.0 + net9.0 Major false false diff --git a/Shared/Network/Abstract/INetworkBehaviour.cs b/Shared/Network/Abstract/INetworkBehaviour.cs new file mode 100644 index 0000000..c9ca2f7 --- /dev/null +++ b/Shared/Network/Abstract/INetworkBehaviour.cs @@ -0,0 +1,8 @@ +using Syntriax.Engine.Core; + +namespace Syntriax.Engine.Network; + +public interface INetworkBehaviour : IBehaviour, INetworkEntity +{ + INetworkCommunicator NetworkCommunicator { get; } +} diff --git a/Shared/Network/Abstract/INetworkCommunicator.cs b/Shared/Network/Abstract/INetworkCommunicator.cs new file mode 100644 index 0000000..4152c8d --- /dev/null +++ b/Shared/Network/Abstract/INetworkCommunicator.cs @@ -0,0 +1,28 @@ +namespace Syntriax.Engine.Network; + +public interface INetworkCommunicator +{ + event OnPacketReceivedDelegate? OnPacketReceived; + + void Stop(); + + delegate void OnPacketReceivedDelegate(INetworkCommunicator sender, object packet, string from); +} + +public interface INetworkCommunicatorClient : INetworkCommunicator +{ + void Connect(string address, int port, string? password = null); + + void SendToServer(INetworkPacket packet); +} + +public interface INetworkCommunicatorServer : INetworkCommunicator +{ + string Password { get; } + int MaxConnectionCount { get; } + int Port { get; } + + void Start(int port, int maxConnectionCount, string? password = null); + + void SendToClient(string to, INetworkPacket packet); +} diff --git a/Shared/Network/Abstract/INetworkEntity.cs b/Shared/Network/Abstract/INetworkEntity.cs new file mode 100644 index 0000000..bc68596 --- /dev/null +++ b/Shared/Network/Abstract/INetworkEntity.cs @@ -0,0 +1,8 @@ +using Syntriax.Engine.Core; + +namespace Syntriax.Engine.Network; + +public interface INetworkEntity : IEntity +{ + void ReceiveData(object data); +} diff --git a/Shared/Network/Abstract/INetworkManager.cs b/Shared/Network/Abstract/INetworkManager.cs new file mode 100644 index 0000000..93ad5cf --- /dev/null +++ b/Shared/Network/Abstract/INetworkManager.cs @@ -0,0 +1,12 @@ +using System.Collections.Generic; +using Syntriax.Engine.Core; + +namespace Syntriax.Engine.Network; + +public interface INetworkManager +{ + INetworkCommunicator NetworkCommunicator { get; } + + IReadOnlyDictionary NetworkEntities { get; } + IBehaviourCollector NetworkEntityCollector { get; } +} diff --git a/Shared/Network/Abstract/INetworkPacket.cs b/Shared/Network/Abstract/INetworkPacket.cs new file mode 100644 index 0000000..2e56821 --- /dev/null +++ b/Shared/Network/Abstract/INetworkPacket.cs @@ -0,0 +1,3 @@ +namespace Syntriax.Engine.Network; + +public interface INetworkPacket; diff --git a/Shared/Network/Abstract/PacketEntityData.cs b/Shared/Network/Abstract/PacketEntityData.cs new file mode 100644 index 0000000..c94c4dc --- /dev/null +++ b/Shared/Network/Abstract/PacketEntityData.cs @@ -0,0 +1,7 @@ +namespace Syntriax.Engine.Network; + +public interface INetworkPacketEntityData : INetworkPacket +{ + string Entity { get; } + T Data { get; } +} diff --git a/Shared/Network/NetworkBase.cs b/Shared/Network/NetworkBase.cs new file mode 100644 index 0000000..42d4160 --- /dev/null +++ b/Shared/Network/NetworkBase.cs @@ -0,0 +1,98 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; + +using LiteNetLib; +using LiteNetLib.Utils; + +using Syntriax.Engine.Core; + +namespace Syntriax.Engine.Network; + +public abstract class NetworkBase : UniverseObject, INetworkCommunicator +{ + public event INetworkCommunicator.OnPacketReceivedDelegate? OnPacketReceived = null; + + protected readonly NetPacketProcessor netPacketProcessor = new(); + + public EventBasedNetListener Listener { get; private set; } = null!; + public NetManager Manager { get; private set; } = null!; + + public void Stop() => Manager.Stop(); + + protected override void OnEnteringUniverse(IUniverse universe) + { + universe.OnPreUpdate += PollEvents; + } + + protected override void OnExitingUniverse(IUniverse universe) + { + universe.OnPreUpdate -= PollEvents; + + Stop(); + } + + private void PollEvents(IUniverse sender, UniverseTime engineTime) => Manager.PollEvents(); + + private void OnPacketArrived(INetworkPacket packet, NetPeer peer) + { + OnPacketReceived?.Invoke(this, packet, peer.Id.ToString()); + } + + private void NetworkReceiveEvent(NetPeer peer, NetPacketReader reader, byte channel, DeliveryMethod deliveryMethod) + { + try { netPacketProcessor.ReadAllPackets(reader, peer); } + catch { } + } + + private void SetupPackets() + { + #region Packet Types Registration + IEnumerable packetTypes = Assembly.GetExecutingAssembly().GetTypes() + .Where(t => typeof(INetworkPacket).IsAssignableFrom(t) && !t.IsInterface); + + MethodInfo registerPacketTypeMethodInfo = typeof(NetPacketProcessor).GetMethod(nameof(NetPacketProcessor.RegisterNestedType), BindingFlags.Instance)!; + foreach (Type packetType in packetTypes) + { + MethodInfo genericRegisterPacketTypeMethodInfo = registerPacketTypeMethodInfo?.MakeGenericMethod(packetType)!; + genericRegisterPacketTypeMethodInfo.Invoke(netPacketProcessor, null); + } + #endregion + + #region Deserialization Callback via netPacketProcessor.SubscribeReusable to OnPacketArrived + MethodInfo subscribeMethod = netPacketProcessor.GetType() + .GetMethods() + .Where(m => m.Name == nameof(NetPacketProcessor.SubscribeReusable)) + .FirstOrDefault(m => + m.GetParameters().Length == 1 && + m.GetParameters()[0].ParameterType.GetGenericTypeDefinition() == typeof(Action<,>) + )!; + + MethodInfo packetArrivedMethod = typeof(NetworkBase) + .GetMethod(nameof(OnPacketArrived), BindingFlags.NonPublic | BindingFlags.Instance)!; + + foreach (var packetType in packetTypes) + { + MethodInfo genericSubscribeMethod = subscribeMethod.MakeGenericMethod(packetType, typeof(NetPeer)); + + Action handler = (packet, peer) => + { + MethodInfo handlerMethod = packetArrivedMethod.MakeGenericMethod(packetType); + handlerMethod.Invoke(this, [packet, peer]); + }; + genericSubscribeMethod.Invoke(netPacketProcessor, [handler]); + } + #endregion + } + + public NetworkBase() + { + Listener = new EventBasedNetListener(); + Manager = new NetManager(Listener); + + Listener.NetworkReceiveEvent += NetworkReceiveEvent; + + SetupPackets(); + } +} diff --git a/Shared/Network/NetworkClient.cs b/Shared/Network/NetworkClient.cs new file mode 100644 index 0000000..d9ebd4a --- /dev/null +++ b/Shared/Network/NetworkClient.cs @@ -0,0 +1,27 @@ +using LiteNetLib.Utils; + +namespace Syntriax.Engine.Network; + +public class NetworkClient : NetworkBase, INetworkCommunicatorClient +{ + private readonly NetDataWriter netDataWriter = new(); + + public void Connect(string address, int port, string? password = null) + { + if (!IsInUniverse) + throw new($"{nameof(NetworkClient)} must be in an universe to connect"); + + Manager.Start(); + Manager.Connect(address, port, password ?? string.Empty); + } + + public void SendToServer(INetworkPacket packet) + { + if (packet is not INetSerializable netSerializable) + throw new($"Packet trying to be sent to the server is not compatible with LiteNetLib"); + + netDataWriter.Reset(); + netPacketProcessor.WriteNetSerializable(netDataWriter, ref netSerializable); + Manager.FirstPeer.Send(netDataWriter, LiteNetLib.DeliveryMethod.ReliableOrdered); + } +} diff --git a/Shared/Network/NetworkManager.cs b/Shared/Network/NetworkManager.cs new file mode 100644 index 0000000..f1cdf0d --- /dev/null +++ b/Shared/Network/NetworkManager.cs @@ -0,0 +1,36 @@ +using System.Collections.Generic; + +using Syntriax.Engine.Core; + +namespace Syntriax.Engine.Network; + +public class NetworkManager : UniverseObject, INetworkManager +{ + public INetworkCommunicator NetworkCommunicator { get; set; } = null!; + + private readonly Dictionary _networkEntities = []; + public IReadOnlyDictionary NetworkEntities => _networkEntities; + + private readonly BehaviourCollector _networkEntityCollector = new(); + public IBehaviourCollector NetworkEntityCollector => _networkEntityCollector; + + public NetworkManager() + { + _networkEntityCollector.OnCollected += OnCollected; + _networkEntityCollector.OnRemoved += OnRemoved; + } + + private void OnCollected(IBehaviourCollector sender, INetworkEntity behaviourCollected) + { + if (!_networkEntities.TryAdd(behaviourCollected.Id, behaviourCollected)) + throw new($"Unable to add {behaviourCollected.Id} to {nameof(NetworkManager)}"); + } + + private void OnRemoved(IBehaviourCollector sender, INetworkEntity behaviourRemoved) + { + _networkEntities.Remove(behaviourRemoved.Id); + } + + protected override void OnEnteringUniverse(IUniverse universe) => _networkEntityCollector.Assign(universe); + protected override void OnExitingUniverse(IUniverse universe) => _networkEntityCollector.Unassign(); +} diff --git a/Shared/Network/NetworkServer.cs b/Shared/Network/NetworkServer.cs new file mode 100644 index 0000000..d44474b --- /dev/null +++ b/Shared/Network/NetworkServer.cs @@ -0,0 +1,60 @@ +using System.Linq; + +using LiteNetLib; +using LiteNetLib.Utils; + +namespace Syntriax.Engine.Network; + +public class NetworkServer : NetworkBase, INetworkCommunicatorServer +{ + public string Password { get; private set; } = string.Empty; + public int MaxConnectionCount { get; private set; } = 2; + public int Port { get; private set; } = 8888; + + private readonly NetDataWriter netDataWriter = new(); + + public NetworkServer() : this(8888, 2) { } + public NetworkServer(int port, int maxConnectionCount) : base() + { + MaxConnectionCount = maxConnectionCount; + Port = port; + + Listener.ConnectionRequestEvent += request => + { + if (Manager.ConnectedPeersCount < maxConnectionCount) + request.AcceptIfKey(Password); + else + request.Reject(); + }; + } + + public void Start(int port, int maxConnectionCount, string? password = null) + { + if (!IsInUniverse) + throw new($"{nameof(NetworkServer)} must be in an universe to start"); + + Password = password ?? string.Empty; + MaxConnectionCount = maxConnectionCount; + Port = port; + + Manager.Start(port); + } + + + public void SendToClient(string to, INetworkPacket packet) + { + if (packet is not INetSerializable netSerializable) + throw new($"Packet trying to be sent to the server is not compatible with LiteNetLib"); + + bool isBroadcastToAll = to.CompareTo("*") == 0; + + netDataWriter.Reset(); + netPacketProcessor.WriteNetSerializable(netDataWriter, ref netSerializable); + if (isBroadcastToAll) + Manager.SendToAll(netDataWriter, DeliveryMethod.ReliableOrdered); + else if (Manager.ConnectedPeerList.FirstOrDefault(p => p.Id.CompareTo(to) == 0) is NetPeer netPeer) + netPeer.Send(netDataWriter, DeliveryMethod.ReliableOrdered); + else + throw new($"Peer {to} couldn't be found."); + } +} diff --git a/Shared/Shared.csproj b/Shared/Shared.csproj index 6c58620..b8bf6ce 100644 --- a/Shared/Shared.csproj +++ b/Shared/Shared.csproj @@ -1,10 +1,11 @@ - net8.0 + net9.0 enable + All