improve ByteChannelWebSocket
This commit is contained in:
parent
de3d298475
commit
40eba7a7c7
|
@ -6,6 +6,7 @@ namespace DisplayCommands;
|
||||||
public sealed class Cp437Grid(ushort width, ushort height) : IEquatable<Cp437Grid>
|
public sealed class Cp437Grid(ushort width, ushort height) : IEquatable<Cp437Grid>
|
||||||
{
|
{
|
||||||
private readonly ByteGrid _byteGrid = new(width, height);
|
private readonly ByteGrid _byteGrid = new(width, height);
|
||||||
|
private readonly Encoding _encoding = Encoding.GetEncoding(437);
|
||||||
|
|
||||||
public ushort Height { get; } = height;
|
public ushort Height { get; } = height;
|
||||||
|
|
||||||
|
@ -13,8 +14,6 @@ public sealed class Cp437Grid(ushort width, ushort height) : IEquatable<Cp437Gri
|
||||||
|
|
||||||
internal Memory<byte> Data => _byteGrid.Data;
|
internal Memory<byte> Data => _byteGrid.Data;
|
||||||
|
|
||||||
private readonly Encoding _encoding = Encoding.GetEncoding(437);
|
|
||||||
|
|
||||||
public char this[ushort x, ushort y]
|
public char this[ushort x, ushort y]
|
||||||
{
|
{
|
||||||
get => ByteToChar(_byteGrid[x, y]);
|
get => ByteToChar(_byteGrid[x, y]);
|
||||||
|
|
|
@ -14,8 +14,8 @@ internal sealed class MoveBullets(BulletManager bullets, IOptions<TanksConfigura
|
||||||
{
|
{
|
||||||
var angle = bullet.Rotation * 2 * Math.PI;
|
var angle = bullet.Rotation * 2 * Math.PI;
|
||||||
bullet.Position = new FloatPosition(
|
bullet.Position = new FloatPosition(
|
||||||
x: bullet.Position.X + Math.Sin(angle) * config.Value.BulletSpeed,
|
bullet.Position.X + Math.Sin(angle) * config.Value.BulletSpeed,
|
||||||
y: bullet.Position.Y - Math.Cos(angle) * config.Value.BulletSpeed
|
bullet.Position.Y - Math.Cos(angle) * config.Value.BulletSpeed
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -1,89 +1,50 @@
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using System.Net.WebSockets;
|
using System.Net.WebSockets;
|
||||||
using System.Threading.Channels;
|
|
||||||
|
|
||||||
namespace TanksServer.Interactivity;
|
namespace TanksServer.Interactivity;
|
||||||
|
|
||||||
/// <summary>
|
internal sealed class ByteChannelWebSocket(WebSocket socket, ILogger logger, int messageSize)
|
||||||
/// Hacky class for easier semantics
|
|
||||||
/// </summary>
|
|
||||||
internal sealed class ByteChannelWebSocket : Channel<Memory<byte>>
|
|
||||||
{
|
{
|
||||||
private readonly ILogger _logger;
|
private readonly byte[] _buffer = new byte[messageSize];
|
||||||
private readonly WebSocket _socket;
|
|
||||||
private readonly Task _backgroundDone;
|
|
||||||
private readonly byte[] _buffer;
|
|
||||||
|
|
||||||
private readonly Channel<Memory<byte>> _outgoing = Channel.CreateUnbounded<Memory<byte>>();
|
public ValueTask SendAsync(ReadOnlyMemory<byte> data) =>
|
||||||
private readonly Channel<Memory<byte>> _incoming = Channel.CreateUnbounded<Memory<byte>>();
|
socket.SendAsync(data, WebSocketMessageType.Binary, true, CancellationToken.None);
|
||||||
|
|
||||||
public ByteChannelWebSocket(WebSocket socket, ILogger logger, int messageSize)
|
public async IAsyncEnumerable<Memory<byte>> ReadAllAsync()
|
||||||
{
|
|
||||||
_socket = socket;
|
|
||||||
_logger = logger;
|
|
||||||
_buffer = new byte[messageSize];
|
|
||||||
_backgroundDone = Task.WhenAll(ReadLoopAsync(), WriteLoopAsync());
|
|
||||||
|
|
||||||
Reader = _incoming.Reader;
|
|
||||||
Writer = _outgoing.Writer;
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task ReadLoopAsync()
|
|
||||||
{
|
{
|
||||||
while (true)
|
while (true)
|
||||||
{
|
{
|
||||||
if (_socket.State is not (WebSocketState.Open or WebSocketState.CloseSent))
|
if (socket.State is not (WebSocketState.Open or WebSocketState.CloseSent))
|
||||||
break;
|
break;
|
||||||
|
|
||||||
var response = await _socket.ReceiveAsync(_buffer, CancellationToken.None);
|
var response = await socket.ReceiveAsync(_buffer, CancellationToken.None);
|
||||||
if (response.MessageType == WebSocketMessageType.Close)
|
if (response.MessageType == WebSocketMessageType.Close)
|
||||||
{
|
{
|
||||||
if (_socket.State == WebSocketState.CloseReceived)
|
if (socket.State == WebSocketState.CloseReceived)
|
||||||
await _socket.CloseOutputAsync(WebSocketCloseStatus.NormalClosure, string.Empty,
|
await socket.CloseOutputAsync(WebSocketCloseStatus.NormalClosure, string.Empty,
|
||||||
CancellationToken.None);
|
CancellationToken.None);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (response.Count != _buffer.Length)
|
if (response.Count != _buffer.Length)
|
||||||
{
|
{
|
||||||
await _socket.CloseAsync(
|
await socket.CloseOutputAsync(
|
||||||
WebSocketCloseStatus.InvalidPayloadData,
|
WebSocketCloseStatus.InvalidPayloadData,
|
||||||
"response has unexpected size",
|
"response has unexpected size",
|
||||||
CancellationToken.None);
|
CancellationToken.None);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
await _incoming.Writer.WriteAsync(_buffer.ToArray());
|
yield return _buffer.ToArray();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_socket.State != WebSocketState.Closed)
|
if (socket.State != WebSocketState.Closed)
|
||||||
Debugger.Break();
|
Debugger.Break();
|
||||||
|
|
||||||
_incoming.Writer.Complete();
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task WriteLoopAsync()
|
|
||||||
{
|
|
||||||
await foreach (var data in _outgoing.Reader.ReadAllAsync())
|
|
||||||
{
|
|
||||||
_logger.LogTrace("sending {} bytes of data", data.Length);
|
|
||||||
try
|
|
||||||
{
|
|
||||||
await _socket.SendAsync(data, WebSocketMessageType.Binary, true, CancellationToken.None);
|
|
||||||
}
|
|
||||||
catch (WebSocketException wsEx)
|
|
||||||
{
|
|
||||||
_logger.LogDebug(wsEx, "send failed");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
await _socket.CloseOutputAsync(WebSocketCloseStatus.NormalClosure, string.Empty, CancellationToken.None);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task CloseAsync()
|
public async Task CloseAsync()
|
||||||
{
|
{
|
||||||
_logger.LogDebug("closing socket");
|
logger.LogDebug("closing socket");
|
||||||
_outgoing.Writer.Complete();
|
await socket.CloseOutputAsync(WebSocketCloseStatus.NormalClosure, string.Empty, CancellationToken.None);
|
||||||
await _backgroundDone;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -14,6 +14,13 @@ internal sealed class ClientScreenServer(
|
||||||
private readonly ConcurrentDictionary<ClientScreenServerConnection, byte> _connections = new();
|
private readonly ConcurrentDictionary<ClientScreenServerConnection, byte> _connections = new();
|
||||||
private bool _closing;
|
private bool _closing;
|
||||||
|
|
||||||
|
public Task StoppingAsync(CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
logger.LogInformation("closing connections");
|
||||||
|
_closing = true;
|
||||||
|
return Task.WhenAll(_connections.Keys.Select(c => c.CloseAsync()));
|
||||||
|
}
|
||||||
|
|
||||||
public Task HandleClient(WebSocket socket)
|
public Task HandleClient(WebSocket socket)
|
||||||
{
|
{
|
||||||
if (_closing)
|
if (_closing)
|
||||||
|
@ -30,29 +37,26 @@ internal sealed class ClientScreenServer(
|
||||||
return connection.Done;
|
return connection.Done;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Task StoppingAsync(CancellationToken cancellationToken)
|
private void Remove(ClientScreenServerConnection connection)
|
||||||
{
|
{
|
||||||
logger.LogInformation("closing connections");
|
_connections.TryRemove(connection, out _);
|
||||||
_closing = true;
|
|
||||||
return Task.WhenAll(_connections.Keys.Select(c => c.CloseAsync()));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public IEnumerable<ClientScreenServerConnection> GetConnections() => _connections.Keys;
|
||||||
|
|
||||||
public Task StartAsync(CancellationToken cancellationToken) => Task.CompletedTask;
|
public Task StartAsync(CancellationToken cancellationToken) => Task.CompletedTask;
|
||||||
public Task StopAsync(CancellationToken cancellationToken) => Task.CompletedTask;
|
public Task StopAsync(CancellationToken cancellationToken) => Task.CompletedTask;
|
||||||
public Task StartedAsync(CancellationToken cancellationToken) => Task.CompletedTask;
|
public Task StartedAsync(CancellationToken cancellationToken) => Task.CompletedTask;
|
||||||
public Task StartingAsync(CancellationToken cancellationToken) => Task.CompletedTask;
|
public Task StartingAsync(CancellationToken cancellationToken) => Task.CompletedTask;
|
||||||
public Task StoppedAsync(CancellationToken cancellationToken) => Task.CompletedTask;
|
public Task StoppedAsync(CancellationToken cancellationToken) => Task.CompletedTask;
|
||||||
|
|
||||||
private void Remove(ClientScreenServerConnection connection) => _connections.TryRemove(connection, out _);
|
|
||||||
|
|
||||||
public IEnumerable<ClientScreenServerConnection> GetConnections() => _connections.Keys;
|
|
||||||
|
|
||||||
internal sealed class ClientScreenServerConnection : IDisposable
|
internal sealed class ClientScreenServerConnection : IDisposable
|
||||||
{
|
{
|
||||||
private readonly ByteChannelWebSocket _channel;
|
private readonly ByteChannelWebSocket _channel;
|
||||||
private readonly SemaphoreSlim _wantedFrames = new(1);
|
|
||||||
private readonly ClientScreenServer _server;
|
|
||||||
private readonly ILogger<ClientScreenServerConnection> _logger;
|
private readonly ILogger<ClientScreenServerConnection> _logger;
|
||||||
|
private readonly ClientScreenServer _server;
|
||||||
|
private readonly SemaphoreSlim _wantedFrames = new(1);
|
||||||
|
private PixelGrid? _lastSentPixels;
|
||||||
|
|
||||||
public ClientScreenServerConnection(WebSocket webSocket,
|
public ClientScreenServerConnection(WebSocket webSocket,
|
||||||
ILogger<ClientScreenServerConnection> logger,
|
ILogger<ClientScreenServerConnection> logger,
|
||||||
|
@ -60,12 +64,23 @@ internal sealed class ClientScreenServer(
|
||||||
{
|
{
|
||||||
_server = server;
|
_server = server;
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
_channel = new(webSocket, logger, 0);
|
_channel = new ByteChannelWebSocket(webSocket, logger, 0);
|
||||||
Done = ReceiveAsync();
|
Done = ReceiveAsync();
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task SendAsync(PixelGrid buf)
|
public Task Done { get; }
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
{
|
{
|
||||||
|
_wantedFrames.Dispose();
|
||||||
|
Done.Dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task SendAsync(PixelGrid pixels)
|
||||||
|
{
|
||||||
|
if (_lastSentPixels == pixels)
|
||||||
|
return;
|
||||||
|
|
||||||
if (!await _wantedFrames.WaitAsync(TimeSpan.Zero))
|
if (!await _wantedFrames.WaitAsync(TimeSpan.Zero))
|
||||||
{
|
{
|
||||||
_logger.LogTrace("client does not want a frame yet");
|
_logger.LogTrace("client does not want a frame yet");
|
||||||
|
@ -75,7 +90,8 @@ internal sealed class ClientScreenServer(
|
||||||
_logger.LogTrace("sending");
|
_logger.LogTrace("sending");
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
await _channel.Writer.WriteAsync(buf.Data);
|
await _channel.SendAsync(pixels.Data);
|
||||||
|
_lastSentPixels = pixels;
|
||||||
}
|
}
|
||||||
catch (ChannelClosedException)
|
catch (ChannelClosedException)
|
||||||
{
|
{
|
||||||
|
@ -85,7 +101,7 @@ internal sealed class ClientScreenServer(
|
||||||
|
|
||||||
private async Task ReceiveAsync()
|
private async Task ReceiveAsync()
|
||||||
{
|
{
|
||||||
await foreach (var _ in _channel.Reader.ReadAllAsync())
|
await foreach (var _ in _channel.ReadAllAsync())
|
||||||
_wantedFrames.Release();
|
_wantedFrames.Release();
|
||||||
|
|
||||||
_logger.LogTrace("done receiving");
|
_logger.LogTrace("done receiving");
|
||||||
|
@ -97,13 +113,5 @@ internal sealed class ClientScreenServer(
|
||||||
_logger.LogDebug("closing connection");
|
_logger.LogDebug("closing connection");
|
||||||
return _channel.CloseAsync();
|
return _channel.CloseAsync();
|
||||||
}
|
}
|
||||||
|
|
||||||
public Task Done { get; }
|
|
||||||
|
|
||||||
public void Dispose()
|
|
||||||
{
|
|
||||||
_wantedFrames.Dispose();
|
|
||||||
Done.Dispose();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -8,6 +8,11 @@ internal sealed class ControlsServer(ILogger<ControlsServer> logger, ILoggerFact
|
||||||
{
|
{
|
||||||
private readonly List<ControlsServerConnection> _connections = [];
|
private readonly List<ControlsServerConnection> _connections = [];
|
||||||
|
|
||||||
|
public Task StoppingAsync(CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
return Task.WhenAll(_connections.Select(c => c.CloseAsync()));
|
||||||
|
}
|
||||||
|
|
||||||
public Task HandleClient(WebSocket ws, Player player)
|
public Task HandleClient(WebSocket ws, Player player)
|
||||||
{
|
{
|
||||||
logger.LogDebug("control client connected {}", player.Id);
|
logger.LogDebug("control client connected {}", player.Id);
|
||||||
|
@ -17,10 +22,7 @@ internal sealed class ControlsServer(ILogger<ControlsServer> logger, ILoggerFact
|
||||||
return sock.Done;
|
return sock.Done;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Task StoppingAsync(CancellationToken cancellationToken)
|
private void Remove(ControlsServerConnection connection) => _connections.Remove(connection);
|
||||||
{
|
|
||||||
return Task.WhenAll(_connections.Select(c => c.CloseAsync()));
|
|
||||||
}
|
|
||||||
|
|
||||||
public Task StartAsync(CancellationToken cancellationToken) => Task.CompletedTask;
|
public Task StartAsync(CancellationToken cancellationToken) => Task.CompletedTask;
|
||||||
public Task StopAsync(CancellationToken cancellationToken) => Task.CompletedTask;
|
public Task StopAsync(CancellationToken cancellationToken) => Task.CompletedTask;
|
||||||
|
@ -28,17 +30,12 @@ internal sealed class ControlsServer(ILogger<ControlsServer> logger, ILoggerFact
|
||||||
public Task StartingAsync(CancellationToken cancellationToken) => Task.CompletedTask;
|
public Task StartingAsync(CancellationToken cancellationToken) => Task.CompletedTask;
|
||||||
public Task StoppedAsync(CancellationToken cancellationToken) => Task.CompletedTask;
|
public Task StoppedAsync(CancellationToken cancellationToken) => Task.CompletedTask;
|
||||||
|
|
||||||
private void Remove(ControlsServerConnection connection)
|
|
||||||
{
|
|
||||||
_connections.Remove(connection);
|
|
||||||
}
|
|
||||||
|
|
||||||
private sealed class ControlsServerConnection
|
private sealed class ControlsServerConnection
|
||||||
{
|
{
|
||||||
private readonly ByteChannelWebSocket _binaryWebSocket;
|
private readonly ByteChannelWebSocket _binaryWebSocket;
|
||||||
private readonly ILogger<ControlsServerConnection> _logger;
|
private readonly ILogger<ControlsServerConnection> _logger;
|
||||||
private readonly ControlsServer _server;
|
|
||||||
private readonly Player _player;
|
private readonly Player _player;
|
||||||
|
private readonly ControlsServer _server;
|
||||||
|
|
||||||
public ControlsServerConnection(WebSocket socket, ILogger<ControlsServerConnection> logger,
|
public ControlsServerConnection(WebSocket socket, ILogger<ControlsServerConnection> logger,
|
||||||
ControlsServer server, Player player)
|
ControlsServer server, Player player)
|
||||||
|
@ -46,30 +43,15 @@ internal sealed class ControlsServer(ILogger<ControlsServer> logger, ILoggerFact
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
_server = server;
|
_server = server;
|
||||||
_player = player;
|
_player = player;
|
||||||
_binaryWebSocket = new(socket, logger, 2);
|
_binaryWebSocket = new ByteChannelWebSocket(socket, logger, 2);
|
||||||
Done = ReceiveAsync();
|
Done = ReceiveAsync();
|
||||||
}
|
}
|
||||||
|
|
||||||
public Task Done { get; }
|
public Task Done { get; }
|
||||||
|
|
||||||
private enum MessageType : byte
|
|
||||||
{
|
|
||||||
Enable = 0x01,
|
|
||||||
Disable = 0x02,
|
|
||||||
}
|
|
||||||
|
|
||||||
private enum InputType : byte
|
|
||||||
{
|
|
||||||
Forward = 0x01,
|
|
||||||
Backward = 0x02,
|
|
||||||
Left = 0x03,
|
|
||||||
Right = 0x04,
|
|
||||||
Shoot = 0x05
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task ReceiveAsync()
|
private async Task ReceiveAsync()
|
||||||
{
|
{
|
||||||
await foreach (var buffer in _binaryWebSocket.Reader.ReadAllAsync())
|
await foreach (var buffer in _binaryWebSocket.ReadAllAsync())
|
||||||
{
|
{
|
||||||
var type = (MessageType)buffer.Span[0];
|
var type = (MessageType)buffer.Span[0];
|
||||||
var control = (InputType)buffer.Span[1];
|
var control = (InputType)buffer.Span[1];
|
||||||
|
@ -110,6 +92,24 @@ internal sealed class ControlsServer(ILogger<ControlsServer> logger, ILoggerFact
|
||||||
_server.Remove(this);
|
_server.Remove(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Task CloseAsync() => _binaryWebSocket.CloseAsync();
|
public Task CloseAsync()
|
||||||
|
{
|
||||||
|
return _binaryWebSocket.CloseAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
private enum MessageType : byte
|
||||||
|
{
|
||||||
|
Enable = 0x01,
|
||||||
|
Disable = 0x02
|
||||||
|
}
|
||||||
|
|
||||||
|
private enum InputType : byte
|
||||||
|
{
|
||||||
|
Forward = 0x01,
|
||||||
|
Backward = 0x02,
|
||||||
|
Left = 0x03,
|
||||||
|
Right = 0x04,
|
||||||
|
Shoot = 0x05
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue