100 lines
		
	
	
		
			3.8 KiB
		
	
	
	
		
			C#
		
	
	
	
	
	
			
		
		
	
	
			100 lines
		
	
	
		
			3.8 KiB
		
	
	
	
		
			C#
		
	
	
	
	
	
using System.Net.Sockets;
 | 
						|
 | 
						|
using LiteNetLib;
 | 
						|
using LiteNetLib.Utils;
 | 
						|
 | 
						|
using Engine.Core;
 | 
						|
using Engine.Core.Debug;
 | 
						|
 | 
						|
namespace Engine.Systems.Network;
 | 
						|
 | 
						|
public class LiteNetLibClient : LiteNetLibCommunicatorBase, INetworkCommunicatorClient
 | 
						|
{
 | 
						|
    private readonly NetDataWriter netDataWriter = new();
 | 
						|
    private readonly NetDataWriter netDataWriterEncrypted = 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>(T packet, PacketDelivery packetDelivery) where T : class, new()
 | 
						|
    {
 | 
						|
        netDataWriter.Reset();
 | 
						|
        netDataWriterEncrypted.Reset();
 | 
						|
 | 
						|
        if (packet is INetworkPacketEncrypted)
 | 
						|
        {
 | 
						|
            netPacketProcessor.Write(netDataWriterEncrypted, packet);
 | 
						|
            byte[] encryptedData = cryptor.Encrypt(netDataWriterEncrypted.CopyData());
 | 
						|
            netDataWriter.Put(true);
 | 
						|
            netDataWriter.PutBytesWithLength(encryptedData);
 | 
						|
        }
 | 
						|
        else
 | 
						|
        {
 | 
						|
            netDataWriter.Put(false);
 | 
						|
            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;
 | 
						|
    }
 | 
						|
 | 
						|
    public override void EnterUniverse(IUniverse universe)
 | 
						|
    {
 | 
						|
        base.EnterUniverse(universe);
 | 
						|
 | 
						|
        cancellationTokenSource = new CancellationTokenSource();
 | 
						|
        PollEvents(cancellationTokenSource.Token);
 | 
						|
    }
 | 
						|
 | 
						|
    public override void ExitUniverse(IUniverse universe)
 | 
						|
    {
 | 
						|
        base.ExitUniverse(universe);
 | 
						|
        cancellationTokenSource?.Cancel();
 | 
						|
    }
 | 
						|
 | 
						|
    /// <summary>
 | 
						|
    /// Client needs to send everything as soon as possible so 
 | 
						|
    /// the events are polled a separate thread running constantly 
 | 
						|
    /// </summary>
 | 
						|
    private async void PollEvents(CancellationToken cancellationToken) => await Task.Run(() =>
 | 
						|
    {
 | 
						|
        while (true)
 | 
						|
        {
 | 
						|
            Manager.PollEvents();
 | 
						|
            Thread.Sleep(1);
 | 
						|
        }
 | 
						|
    }, cancellationToken);
 | 
						|
}
 |