move more websocket logic into base classes

This commit is contained in:
Vinzenz Schroeter 2024-04-21 23:21:15 +02:00
parent 57c0d229f1
commit fb675e59ff
7 changed files with 89 additions and 123 deletions

View file

@ -5,42 +5,19 @@ using TanksServer.Graphics;
namespace TanksServer.Interactivity;
internal sealed class ClientScreenServerConnection : IWebsocketServerConnection, IDisposable
internal sealed class ClientScreenServerConnection(
WebSocket webSocket,
ILogger<ClientScreenServerConnection> logger,
TimeSpan minFrameTime,
Guid? playerGuid = null
) : WebsocketServerConnection(logger, new ByteChannelWebSocket(webSocket, logger, 0)),
IDisposable
{
private readonly ByteChannelWebSocket _channel;
private readonly ILogger<ClientScreenServerConnection> _logger;
private readonly SemaphoreSlim _wantedFrames = new(1);
private readonly Guid? _playerGuid;
private readonly PlayerScreenData? _playerScreenData;
private readonly TimeSpan _minFrameTime;
private readonly PlayerScreenData? _playerScreenData = playerGuid.HasValue ? new PlayerScreenData(logger) : null;
private DateTime _nextFrameAfter = DateTime.Now;
public ClientScreenServerConnection(
WebSocket webSocket,
ILogger<ClientScreenServerConnection> logger,
TimeSpan minFrameTime,
Guid? playerGuid = null
)
{
_logger = logger;
_minFrameTime = minFrameTime;
_playerGuid = playerGuid;
if (playerGuid.HasValue)
_playerScreenData = new PlayerScreenData(logger);
_channel = new ByteChannelWebSocket(webSocket, logger, 0);
Done = ReceiveAsync();
}
public Task Done { get; }
public void Dispose()
{
_wantedFrames.Dispose();
Done.Dispose();
}
public void Dispose() => _wantedFrames.Dispose();
public async Task SendAsync(PixelGrid pixels, GamePixelGrid gamePixelGrid)
{
@ -49,26 +26,26 @@ internal sealed class ClientScreenServerConnection : IWebsocketServerConnection,
if (!await _wantedFrames.WaitAsync(TimeSpan.Zero))
{
_logger.LogTrace("client does not want a frame yet");
logger.LogTrace("client does not want a frame yet");
return;
}
_nextFrameAfter = DateTime.Today + _minFrameTime;
_nextFrameAfter = DateTime.Today + minFrameTime;
if (_playerScreenData != null)
RefreshPlayerSpecificData(gamePixelGrid);
_logger.LogTrace("sending");
logger.LogTrace("sending");
try
{
_logger.LogTrace("sending {} bytes of pixel data", pixels.Data.Length);
await _channel.SendAsync(pixels.Data, _playerScreenData == null);
logger.LogTrace("sending {} bytes of pixel data", pixels.Data.Length);
await Socket.SendAsync(pixels.Data, _playerScreenData == null);
if (_playerScreenData != null)
await _channel.SendAsync(_playerScreenData.GetPacket());
await Socket.SendAsync(_playerScreenData.GetPacket());
}
catch (WebSocketException ex)
{
_logger.LogWarning(ex, "send failed");
logger.LogWarning(ex, "send failed");
}
}
@ -80,20 +57,9 @@ internal sealed class ClientScreenServerConnection : IWebsocketServerConnection,
{
if (!gamePixel.EntityType.HasValue)
continue;
_playerScreenData.Add(gamePixel.EntityType.Value, gamePixel.BelongsTo?.Id == _playerGuid);
_playerScreenData.Add(gamePixel.EntityType.Value, gamePixel.BelongsTo?.Id == playerGuid);
}
}
private async Task ReceiveAsync()
{
await foreach (var _ in _channel.ReadAllAsync())
_wantedFrames.Release();
_logger.LogTrace("done receiving");
}
public Task CloseAsync()
{
_logger.LogDebug("closing connection");
return _channel.CloseAsync();
}
protected override void HandleMessage(Memory<byte> _) => _wantedFrames.Release();
}

View file

@ -13,7 +13,7 @@ internal sealed class ControlsServer(
var clientLogger = loggerFactory.CreateLogger<ControlsServerConnection>();
var sock = new ControlsServerConnection(ws, clientLogger, player);
await AddConnection(sock);
await sock.Done;
await sock.ReceiveAsync();
await RemoveConnection(sock);
}
}

View file

@ -2,65 +2,12 @@ using System.Net.WebSockets;
namespace TanksServer.Interactivity;
internal sealed class ControlsServerConnection : IWebsocketServerConnection
internal sealed class ControlsServerConnection(
WebSocket socket,
ILogger<ControlsServerConnection> logger,
Player player
) : WebsocketServerConnection(logger, new ByteChannelWebSocket(socket, logger, 2))
{
private readonly ByteChannelWebSocket _binaryWebSocket;
private readonly ILogger<ControlsServerConnection> _logger;
private readonly Player _player;
public ControlsServerConnection(WebSocket socket, ILogger<ControlsServerConnection> logger, Player player)
{
_logger = logger;
_player = player;
_binaryWebSocket = new ByteChannelWebSocket(socket, logger, 2);
Done = ReceiveAsync();
}
public Task Done { get; }
private async Task ReceiveAsync()
{
await foreach (var buffer in _binaryWebSocket.ReadAllAsync())
{
var type = (MessageType)buffer.Span[0];
var control = (InputType)buffer.Span[1];
_logger.LogTrace("player input {} {} {}", _player.Id, type, control);
var isEnable = type switch
{
MessageType.Enable => true,
MessageType.Disable => false,
_ => throw new ArgumentException("invalid message type")
};
_player.LastInput = DateTime.Now;
switch (control)
{
case InputType.Forward:
_player.Controls.Forward = isEnable;
break;
case InputType.Backward:
_player.Controls.Backward = isEnable;
break;
case InputType.Left:
_player.Controls.TurnLeft = isEnable;
break;
case InputType.Right:
_player.Controls.TurnRight = isEnable;
break;
case InputType.Shoot:
_player.Controls.Shoot = isEnable;
break;
default:
throw new ArgumentException("invalid control type");
}
}
}
public Task CloseAsync() => _binaryWebSocket.CloseAsync();
private enum MessageType : byte
{
Enable = 0x01,
@ -75,4 +22,42 @@ internal sealed class ControlsServerConnection : IWebsocketServerConnection
Right = 0x04,
Shoot = 0x05
}
protected override void HandleMessage(Memory<byte> buffer)
{
var type = (MessageType)buffer.Span[0];
var control = (InputType)buffer.Span[1];
logger.LogTrace("player input {} {} {}", player.Id, type, control);
var isEnable = type switch
{
MessageType.Enable => true,
MessageType.Disable => false,
_ => throw new ArgumentException("invalid message type")
};
player.LastInput = DateTime.Now;
switch (control)
{
case InputType.Forward:
player.Controls.Forward = isEnable;
break;
case InputType.Backward:
player.Controls.Backward = isEnable;
break;
case InputType.Left:
player.Controls.TurnLeft = isEnable;
break;
case InputType.Right:
player.Controls.TurnRight = isEnable;
break;
case InputType.Shoot:
player.Controls.Shoot = isEnable;
break;
default:
throw new ArgumentException("invalid control type");
}
}
}

View file

@ -1,8 +0,0 @@
namespace TanksServer.Interactivity;
internal interface IWebsocketServerConnection
{
Task CloseAsync();
Task Done { get; }
}

View file

@ -7,7 +7,7 @@ namespace TanksServer.Interactivity;
internal sealed class PlayerScreenData(ILogger logger)
{
private readonly Memory<byte> _data = new byte[MapService.PixelsPerRow * MapService.PixelsPerColumn / 2];
private int _count = 0;
private int _count;
public void Clear()
{

View file

@ -2,10 +2,10 @@ using Microsoft.Extensions.Hosting;
namespace TanksServer.Interactivity;
internal class WebsocketServer<T>(
internal abstract class WebsocketServer<T>(
ILogger logger
) : IHostedLifecycleService, IDisposable
where T : IWebsocketServerConnection
where T : WebsocketServerConnection
{
private readonly SemaphoreSlim _mutex = new(1, 1);
private bool _closing;
@ -56,7 +56,7 @@ internal class WebsocketServer<T>(
protected async Task HandleClientAsync(T connection)
{
await AddConnection(connection);
await connection.Done;
await connection.ReceiveAsync();
await RemoveConnection(connection);
}

View file

@ -0,0 +1,23 @@
namespace TanksServer.Interactivity;
internal abstract class WebsocketServerConnection(
ILogger logger,
ByteChannelWebSocket socket)
{
protected readonly ByteChannelWebSocket Socket = socket;
public Task CloseAsync()
{
logger.LogDebug("closing connection");
return Socket.CloseAsync();
}
public async Task ReceiveAsync()
{
await foreach (var buffer in Socket.ReadAllAsync())
HandleMessage(buffer);
logger.LogTrace("done receiving");
}
protected abstract void HandleMessage(Memory<byte> buffer);
}