122 lines
4.7 KiB
C#
122 lines
4.7 KiB
C#
using Microsoft.AspNetCore.Http.Extensions;
|
|
using Shogi.Contracts.Socket;
|
|
using Shogi.Contracts.Types;
|
|
using System.Buffers;
|
|
using System.Net.WebSockets;
|
|
using System.Text.Json;
|
|
using static Shogi.UI.Shared.Events;
|
|
|
|
namespace Shogi.UI.Shared;
|
|
|
|
public class ShogiSocket : IDisposable
|
|
{
|
|
public event AsyncEventHandler? OnSessionCreated;
|
|
public event AsyncEventHandler<SessionJoinedByPlayerSocketMessage>? OnSessionJoined;
|
|
public event AsyncEventHandler<PlayerHasMovedMessage>? OnPlayerMoved;
|
|
|
|
private readonly ClientWebSocket socket;
|
|
private readonly JsonSerializerOptions serializerOptions;
|
|
private readonly UriBuilder uriBuilder;
|
|
private readonly CancellationTokenSource cancelToken;
|
|
private readonly IMemoryOwner<byte> memoryOwner;
|
|
private bool disposedValue;
|
|
|
|
public ShogiSocket(IConfiguration configuration, ClientWebSocket socket, JsonSerializerOptions serializerOptions)
|
|
{
|
|
this.socket = socket;
|
|
this.serializerOptions = serializerOptions;
|
|
this.uriBuilder = new UriBuilder(configuration["SocketUrl"] ?? throw new InvalidOperationException("SocketUrl configuration is missing."));
|
|
this.cancelToken = new CancellationTokenSource();
|
|
this.memoryOwner = MemoryPool<byte>.Shared.Rent(1024 * 2);
|
|
}
|
|
|
|
public async Task OpenAsync(string token)
|
|
{
|
|
uriBuilder.Query = new QueryBuilder
|
|
{
|
|
{ "token", token }
|
|
}.ToQueryString().Value;
|
|
|
|
await socket.ConnectAsync(this.uriBuilder.Uri, cancelToken.Token);
|
|
Console.WriteLine("Socket Connected");
|
|
// Fire and forget! I'm way too lazy to write my own javascript interop to a web worker. Nooo thanks.
|
|
_ = Listen().ContinueWith(async antecedent =>
|
|
{
|
|
Console.WriteLine($"Socket fault. {antecedent.Exception}");
|
|
this.cancelToken.Cancel();
|
|
await this.socket.CloseAsync(WebSocketCloseStatus.NormalClosure, "Page was probably closed or refresh.", CancellationToken.None);
|
|
if (antecedent.Exception != null)
|
|
{
|
|
throw antecedent.Exception;
|
|
}
|
|
}, TaskContinuationOptions.OnlyOnFaulted);
|
|
}
|
|
|
|
private async Task Listen()
|
|
{
|
|
while (socket.State == WebSocketState.Open && !cancelToken.IsCancellationRequested)
|
|
{
|
|
var result = await socket.ReceiveAsync(this.memoryOwner.Memory, cancelToken.Token);
|
|
var memory = this.memoryOwner.Memory[..result.Count].ToArray();
|
|
var action = JsonDocument
|
|
.Parse(memory[..result.Count])
|
|
.RootElement
|
|
.GetProperty(nameof(ISocketResponse.Action))
|
|
.Deserialize<SocketAction>();
|
|
|
|
Console.WriteLine($"Socket action: {action}");
|
|
switch (action)
|
|
{
|
|
case SocketAction.SessionCreated:
|
|
if (this.OnSessionCreated is not null)
|
|
{
|
|
await this.OnSessionCreated();
|
|
}
|
|
break;
|
|
case SocketAction.SessionJoined:
|
|
if (this.OnSessionJoined is not null)
|
|
{
|
|
var args = JsonSerializer.Deserialize<SessionJoinedByPlayerSocketMessage>(memory[..result.Count], serializerOptions);
|
|
await this.OnSessionJoined(args!);
|
|
}
|
|
break;
|
|
case SocketAction.PieceMoved:
|
|
if (this.OnPlayerMoved is not null)
|
|
{
|
|
var args = JsonSerializer.Deserialize<PlayerHasMovedMessage>(memory[..result.Count], serializerOptions);
|
|
await this.OnPlayerMoved(args!);
|
|
}
|
|
break;
|
|
default:
|
|
throw new NotImplementedException($"Socket message for action:{action} is not implemented.");
|
|
}
|
|
}
|
|
await socket.CloseAsync(WebSocketCloseStatus.NormalClosure, "Socket closed because cancellation token was cancelled.", CancellationToken.None);
|
|
if (!cancelToken.IsCancellationRequested)
|
|
{
|
|
throw new InvalidOperationException("Stopped socket listening without cancelling.");
|
|
}
|
|
}
|
|
|
|
protected virtual void Dispose(bool disposing)
|
|
{
|
|
if (!disposedValue)
|
|
{
|
|
if (disposing)
|
|
{
|
|
cancelToken.Cancel();
|
|
socket.Dispose();
|
|
memoryOwner.Dispose();
|
|
}
|
|
disposedValue = true;
|
|
}
|
|
}
|
|
|
|
public void Dispose()
|
|
{
|
|
// Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
|
|
Dispose(disposing: true);
|
|
GC.SuppressFinalize(this);
|
|
}
|
|
}
|