move more websocket logic into base classes
This commit is contained in:
parent
57c0d229f1
commit
fb675e59ff
|
@ -5,42 +5,19 @@ using TanksServer.Graphics;
|
||||||
|
|
||||||
namespace TanksServer.Interactivity;
|
namespace TanksServer.Interactivity;
|
||||||
|
|
||||||
internal sealed class ClientScreenServerConnection : IWebsocketServerConnection, IDisposable
|
internal sealed class ClientScreenServerConnection(
|
||||||
{
|
|
||||||
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 DateTime _nextFrameAfter = DateTime.Now;
|
|
||||||
|
|
||||||
public ClientScreenServerConnection(
|
|
||||||
WebSocket webSocket,
|
WebSocket webSocket,
|
||||||
ILogger<ClientScreenServerConnection> logger,
|
ILogger<ClientScreenServerConnection> logger,
|
||||||
TimeSpan minFrameTime,
|
TimeSpan minFrameTime,
|
||||||
Guid? playerGuid = null
|
Guid? playerGuid = null
|
||||||
)
|
) : WebsocketServerConnection(logger, new ByteChannelWebSocket(webSocket, logger, 0)),
|
||||||
|
IDisposable
|
||||||
{
|
{
|
||||||
_logger = logger;
|
private readonly SemaphoreSlim _wantedFrames = new(1);
|
||||||
_minFrameTime = minFrameTime;
|
private readonly PlayerScreenData? _playerScreenData = playerGuid.HasValue ? new PlayerScreenData(logger) : null;
|
||||||
|
private DateTime _nextFrameAfter = DateTime.Now;
|
||||||
|
|
||||||
_playerGuid = playerGuid;
|
public void Dispose() => _wantedFrames.Dispose();
|
||||||
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 async Task SendAsync(PixelGrid pixels, GamePixelGrid gamePixelGrid)
|
public async Task SendAsync(PixelGrid pixels, GamePixelGrid gamePixelGrid)
|
||||||
{
|
{
|
||||||
|
@ -49,26 +26,26 @@ internal sealed class ClientScreenServerConnection : IWebsocketServerConnection,
|
||||||
|
|
||||||
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");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
_nextFrameAfter = DateTime.Today + _minFrameTime;
|
_nextFrameAfter = DateTime.Today + minFrameTime;
|
||||||
|
|
||||||
if (_playerScreenData != null)
|
if (_playerScreenData != null)
|
||||||
RefreshPlayerSpecificData(gamePixelGrid);
|
RefreshPlayerSpecificData(gamePixelGrid);
|
||||||
|
|
||||||
_logger.LogTrace("sending");
|
logger.LogTrace("sending");
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
_logger.LogTrace("sending {} bytes of pixel data", pixels.Data.Length);
|
logger.LogTrace("sending {} bytes of pixel data", pixels.Data.Length);
|
||||||
await _channel.SendAsync(pixels.Data, _playerScreenData == null);
|
await Socket.SendAsync(pixels.Data, _playerScreenData == null);
|
||||||
if (_playerScreenData != null)
|
if (_playerScreenData != null)
|
||||||
await _channel.SendAsync(_playerScreenData.GetPacket());
|
await Socket.SendAsync(_playerScreenData.GetPacket());
|
||||||
}
|
}
|
||||||
catch (WebSocketException ex)
|
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)
|
if (!gamePixel.EntityType.HasValue)
|
||||||
continue;
|
continue;
|
||||||
_playerScreenData.Add(gamePixel.EntityType.Value, gamePixel.BelongsTo?.Id == _playerGuid);
|
_playerScreenData.Add(gamePixel.EntityType.Value, gamePixel.BelongsTo?.Id == playerGuid);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task ReceiveAsync()
|
protected override void HandleMessage(Memory<byte> _) => _wantedFrames.Release();
|
||||||
{
|
|
||||||
await foreach (var _ in _channel.ReadAllAsync())
|
|
||||||
_wantedFrames.Release();
|
|
||||||
_logger.LogTrace("done receiving");
|
|
||||||
}
|
|
||||||
|
|
||||||
public Task CloseAsync()
|
|
||||||
{
|
|
||||||
_logger.LogDebug("closing connection");
|
|
||||||
return _channel.CloseAsync();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,7 +13,7 @@ internal sealed class ControlsServer(
|
||||||
var clientLogger = loggerFactory.CreateLogger<ControlsServerConnection>();
|
var clientLogger = loggerFactory.CreateLogger<ControlsServerConnection>();
|
||||||
var sock = new ControlsServerConnection(ws, clientLogger, player);
|
var sock = new ControlsServerConnection(ws, clientLogger, player);
|
||||||
await AddConnection(sock);
|
await AddConnection(sock);
|
||||||
await sock.Done;
|
await sock.ReceiveAsync();
|
||||||
await RemoveConnection(sock);
|
await RemoveConnection(sock);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,65 +2,12 @@ using System.Net.WebSockets;
|
||||||
|
|
||||||
namespace TanksServer.Interactivity;
|
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
|
private enum MessageType : byte
|
||||||
{
|
{
|
||||||
Enable = 0x01,
|
Enable = 0x01,
|
||||||
|
@ -75,4 +22,42 @@ internal sealed class ControlsServerConnection : IWebsocketServerConnection
|
||||||
Right = 0x04,
|
Right = 0x04,
|
||||||
Shoot = 0x05
|
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");
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,8 +0,0 @@
|
||||||
namespace TanksServer.Interactivity;
|
|
||||||
|
|
||||||
internal interface IWebsocketServerConnection
|
|
||||||
{
|
|
||||||
Task CloseAsync();
|
|
||||||
|
|
||||||
Task Done { get; }
|
|
||||||
}
|
|
|
@ -7,7 +7,7 @@ namespace TanksServer.Interactivity;
|
||||||
internal sealed class PlayerScreenData(ILogger logger)
|
internal sealed class PlayerScreenData(ILogger logger)
|
||||||
{
|
{
|
||||||
private readonly Memory<byte> _data = new byte[MapService.PixelsPerRow * MapService.PixelsPerColumn / 2];
|
private readonly Memory<byte> _data = new byte[MapService.PixelsPerRow * MapService.PixelsPerColumn / 2];
|
||||||
private int _count = 0;
|
private int _count;
|
||||||
|
|
||||||
public void Clear()
|
public void Clear()
|
||||||
{
|
{
|
||||||
|
|
|
@ -2,10 +2,10 @@ using Microsoft.Extensions.Hosting;
|
||||||
|
|
||||||
namespace TanksServer.Interactivity;
|
namespace TanksServer.Interactivity;
|
||||||
|
|
||||||
internal class WebsocketServer<T>(
|
internal abstract class WebsocketServer<T>(
|
||||||
ILogger logger
|
ILogger logger
|
||||||
) : IHostedLifecycleService, IDisposable
|
) : IHostedLifecycleService, IDisposable
|
||||||
where T : IWebsocketServerConnection
|
where T : WebsocketServerConnection
|
||||||
{
|
{
|
||||||
private readonly SemaphoreSlim _mutex = new(1, 1);
|
private readonly SemaphoreSlim _mutex = new(1, 1);
|
||||||
private bool _closing;
|
private bool _closing;
|
||||||
|
@ -56,7 +56,7 @@ internal class WebsocketServer<T>(
|
||||||
protected async Task HandleClientAsync(T connection)
|
protected async Task HandleClientAsync(T connection)
|
||||||
{
|
{
|
||||||
await AddConnection(connection);
|
await AddConnection(connection);
|
||||||
await connection.Done;
|
await connection.ReceiveAsync();
|
||||||
await RemoveConnection(connection);
|
await RemoveConnection(connection);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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);
|
||||||
|
}
|
Loading…
Reference in a new issue