using System; using System.Linq; using System.Net.Sockets; using System.Threading; using System.Threading.Tasks; using LiteNetLib; using LiteNetLib.Utils; using Syntriax.Engine.Core; using Syntriax.Engine.Core.Debug; namespace Syntriax.Engine.Network; public class LiteNetLibClient : LiteNetLibCommunicatorBase, INetworkCommunicatorClient { private readonly NetDataWriter netDataWriter = new(); private CancellationTokenSource? cancellationTokenSource = null; protected override IConnection GetConnection(NetPeer peer) => new LiteNetLibServerConnection(peer); public INetworkCommunicatorClient Connect(string address, int port, string? password = null) { if (!UniverseObject.IsInUniverse) throw new($"{nameof(LiteNetLibClient)} must be in an universe to connect"); password ??= string.Empty; // Okay, for some reason sometimes LiteNetLib goes dumb when the server hostname has IPv6 address as well as IPv4 // but the client doesn't support IPv6, it still tries to use the v6 unless we explicitly tell the ip to connect to, // which fails to connect... So for the time being I am preferring IPv4 below over IPv6 for clients. // TODO: I think this is something that happens on Linux only? I need to check on Windows as well just to be sure. System.Net.IPAddress[] addresses = System.Net.Dns.GetHostAddresses(address); string connectionAddress = addresses.FirstOrDefault(a => a.AddressFamily == AddressFamily.InterNetwork, addresses[0]).ToString(); logger?.Log(this, $"Connecting to server at '{address}:{port}' with password '{password}'"); logger?.Log(this, $"Resolved address for {address}: {connectionAddress}"); Manager.Start(); Manager.Connect(connectionAddress, port, password); return this; } public INetworkCommunicatorClient SendToServer(T packet, PacketDelivery packetDelivery) where T : class, new() { netDataWriter.Reset(); netPacketProcessor.Write(netDataWriter, packet); switch (packetDelivery) { case PacketDelivery.ReliableInOrder: Manager.FirstPeer.Send(netDataWriter, DeliveryMethod.ReliableOrdered); break; case PacketDelivery.UnreliableInOrder: Manager.FirstPeer.Send(netDataWriter, DeliveryMethod.Sequenced); break; case PacketDelivery.ReliableOutOfOrder: Manager.FirstPeer.Send(netDataWriter, DeliveryMethod.ReliableUnordered); break; case PacketDelivery.UnreliableOutOfOrder: Manager.FirstPeer.Send(netDataWriter, DeliveryMethod.Unreliable); break; default: Manager.FirstPeer.Send(netDataWriter, DeliveryMethod.ReliableOrdered); break; } return this; } protected override void OnEnteredUniverse(IUniverse universe) { base.OnEnteredUniverse(universe); cancellationTokenSource = new CancellationTokenSource(); PollEvents(cancellationTokenSource.Token); } protected override void OnExitedUniverse(IUniverse universe) { base.OnExitedUniverse(universe); cancellationTokenSource?.Cancel(); } /// /// Client needs to send everything as soon as possible so /// the events are polled a separate thread running constantly /// private async void PollEvents(CancellationToken cancellationToken) => await Task.Run(() => { while (true) { Manager.PollEvents(); Thread.Sleep(1); } }, cancellationToken); }