//
// Licensed under the MIT License. See LICENSE file in the project root for full license information.
//
namespace MUnique.Client.Library;
using System;
using System.Buffers;
using System.Diagnostics;
using System.IO.Pipelines;
using System.Threading.Tasks;
using MUnique.OpenMU.Network;
using Nito.AsyncEx.Synchronous;
///
/// A wrapper for a .
///
public sealed class ConnectionWrapper : IDisposable
{
private readonly int _handle;
private readonly Connection _connection;
///
/// The unmanaged callback to a packet handler. Parameters:
/// - handle
/// - packet size
/// - pointer to packet.
///
private readonly unsafe delegate* unmanaged _onPacketReceived;
///
/// The unmanaged callback to a disconnect handler. Parameter: handle.
///
private readonly unsafe delegate* unmanaged _onDisconnected;
///
/// Initializes a new instance of the class.
///
/// The handle of the connection.
/// The connection.
///
/// The pointer to an unmanaged method which is called when a new packet got received.
/// Parameters: handle, size, pointer to the data.
///
///
/// The pointer to an unmanaged method which is called when the connection got disconnected.
/// Parameter: handle.
///
public unsafe ConnectionWrapper(int handle, Connection connection, delegate* unmanaged onPacketReceived, delegate* unmanaged onDisconnected)
{
this._handle = handle;
this._connection = connection;
this._onPacketReceived = onPacketReceived;
this._onDisconnected = onDisconnected;
connection.PacketReceived += this.OnPacketReceivedAsync;
connection.Disconnected += this.OnDisconnectedAsync;
}
///
/// Gets the output pipe writer.
///
internal PipeWriter Output => this._connection.Output;
///
/// Begins receiving packets from the client.
///
public void BeginReceive()
{
// we never want it on the main thread, so we do a Task.Run.
_ = Task.Run(this._connection.BeginReceiveAsync);
}
///
public void Dispose()
{
this._connection.Dispose();
}
///
/// Disconnects the connection.
///
public void DisconnectAndDispose()
{
_ = Task.Run(async () =>
{
try
{
await this._connection.DisconnectAsync();
this._connection.Dispose();
}
catch (Exception ex)
{
Debug.WriteLine(ex);
}
});
}
///
/// Sends the specified bytes.
///
/// The bytes.
public void Send(Span bytes)
{
using var l = this._connection.OutputLock.Lock();
var targetSpan = this._connection.Output.GetSpan(bytes.Length);
bytes.CopyTo(targetSpan);
this._connection.Output.Advance(bytes.Length);
this._connection.Output.FlushAsync().AsTask().WaitAndUnwrapException();
}
///
/// Sends the specified bytes.
///
/// The factory which creates the packet and returns the length of it.
public void CreateAndSend(Func packetFactory)
{
using var l = this._connection.OutputLock.Lock();
var length = packetFactory(this._connection.Output);
this._connection.Output.Advance(length);
this._connection.Output.FlushAsync().AsTask().WaitAndUnwrapException();
}
private unsafe ValueTask OnPacketReceivedAsync(ReadOnlySequence args)
{
using var memoryOwner = MemoryPool.Shared.Rent((int)args.Length);
var packet = memoryOwner.Memory.Slice(0, (int)args.Length);
args.CopyTo(packet.Span);
fixed (byte* packetPtr = &packet.Span.GetPinnableReference())
{
try
{
this._onPacketReceived(this._handle, packet.Length, packetPtr);
}
catch (Exception ex)
{
Debug.WriteLine(ex);
}
}
return ValueTask.CompletedTask;
}
private unsafe ValueTask OnDisconnectedAsync()
{
try
{
this._onDisconnected(this._handle);
this.Dispose();
}
catch (Exception ex)
{
Debug.WriteLine(ex);
}
return ValueTask.CompletedTask;
}
}