// // 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; } }