live player info in client
This commit is contained in:
parent
fb675e59ff
commit
a50a9770c9
14 changed files with 193 additions and 70 deletions
|
@ -7,4 +7,5 @@ namespace TanksServer.Interactivity;
|
|||
[JsonSerializable(typeof(Guid))]
|
||||
[JsonSerializable(typeof(NameId))]
|
||||
[JsonSerializable(typeof(IEnumerable<string>))]
|
||||
[JsonSerializable(typeof(PlayerInfo))]
|
||||
internal sealed partial class AppSerializerContext : JsonSerializerContext;
|
||||
|
|
|
@ -7,9 +7,12 @@ internal sealed class ByteChannelWebSocket(WebSocket socket, ILogger logger, int
|
|||
{
|
||||
private readonly byte[] _buffer = new byte[messageSize];
|
||||
|
||||
public ValueTask SendAsync(ReadOnlyMemory<byte> data, bool endOfMessage = true) =>
|
||||
public ValueTask SendBinaryAsync(ReadOnlyMemory<byte> data, bool endOfMessage = true) =>
|
||||
socket.SendAsync(data, WebSocketMessageType.Binary, endOfMessage, CancellationToken.None);
|
||||
|
||||
public ValueTask SendTextAsync(ReadOnlyMemory<byte> data, bool endOfMessage = true) =>
|
||||
socket.SendAsync(data, WebSocketMessageType.Text, endOfMessage, CancellationToken.None);
|
||||
|
||||
public async IAsyncEnumerable<Memory<byte>> ReadAllAsync()
|
||||
{
|
||||
while (socket.State is WebSocketState.Open or WebSocketState.CloseSent)
|
||||
|
|
|
@ -26,7 +26,7 @@ internal sealed class ClientScreenServerConnection(
|
|||
|
||||
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;
|
||||
}
|
||||
|
||||
|
@ -35,17 +35,17 @@ internal sealed class ClientScreenServerConnection(
|
|||
if (_playerScreenData != null)
|
||||
RefreshPlayerSpecificData(gamePixelGrid);
|
||||
|
||||
logger.LogTrace("sending");
|
||||
Logger.LogTrace("sending");
|
||||
try
|
||||
{
|
||||
logger.LogTrace("sending {} bytes of pixel data", pixels.Data.Length);
|
||||
await Socket.SendAsync(pixels.Data, _playerScreenData == null);
|
||||
Logger.LogTrace("sending {} bytes of pixel data", pixels.Data.Length);
|
||||
await Socket.SendBinaryAsync(pixels.Data, _playerScreenData == null);
|
||||
if (_playerScreenData != null)
|
||||
await Socket.SendAsync(_playerScreenData.GetPacket());
|
||||
await Socket.SendBinaryAsync(_playerScreenData.GetPacket());
|
||||
}
|
||||
catch (WebSocketException ex)
|
||||
{
|
||||
logger.LogWarning(ex, "send failed");
|
||||
Logger.LogWarning(ex, "send failed");
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -61,5 +61,9 @@ internal sealed class ClientScreenServerConnection(
|
|||
}
|
||||
}
|
||||
|
||||
protected override void HandleMessage(Memory<byte> _) => _wantedFrames.Release();
|
||||
protected override ValueTask HandleMessageAsync(Memory<byte> _)
|
||||
{
|
||||
_wantedFrames.Release();
|
||||
return ValueTask.CompletedTask;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,13 +7,11 @@ internal sealed class ControlsServer(
|
|||
ILoggerFactory loggerFactory
|
||||
) : WebsocketServer<ControlsServerConnection>(logger)
|
||||
{
|
||||
public async Task HandleClientAsync(WebSocket ws, Player player)
|
||||
public Task HandleClientAsync(WebSocket ws, Player player)
|
||||
{
|
||||
logger.LogDebug("control client connected {}", player.Id);
|
||||
var clientLogger = loggerFactory.CreateLogger<ControlsServerConnection>();
|
||||
var sock = new ControlsServerConnection(ws, clientLogger, player);
|
||||
await AddConnection(sock);
|
||||
await sock.ReceiveAsync();
|
||||
await RemoveConnection(sock);
|
||||
return HandleClientAsync(sock);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -23,12 +23,12 @@ internal sealed class ControlsServerConnection(
|
|||
Shoot = 0x05
|
||||
}
|
||||
|
||||
protected override void HandleMessage(Memory<byte> buffer)
|
||||
protected override ValueTask HandleMessageAsync(Memory<byte> buffer)
|
||||
{
|
||||
var type = (MessageType)buffer.Span[0];
|
||||
var control = (InputType)buffer.Span[1];
|
||||
|
||||
logger.LogTrace("player input {} {} {}", player.Id, type, control);
|
||||
Logger.LogTrace("player input {} {} {}", player.Id, type, control);
|
||||
|
||||
var isEnable = type switch
|
||||
{
|
||||
|
@ -59,5 +59,7 @@ internal sealed class ControlsServerConnection(
|
|||
default:
|
||||
throw new ArgumentException("invalid control type");
|
||||
}
|
||||
|
||||
return ValueTask.CompletedTask;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,58 @@
|
|||
using System.Net.WebSockets;
|
||||
using System.Text.Json;
|
||||
|
||||
namespace TanksServer.Interactivity;
|
||||
|
||||
internal sealed class PlayerInfoConnection(
|
||||
Player player,
|
||||
ILogger logger,
|
||||
WebSocket rawSocket
|
||||
) : WebsocketServerConnection(logger, new ByteChannelWebSocket(rawSocket, logger, 0)), IDisposable
|
||||
{
|
||||
private readonly SemaphoreSlim _wantedFrames = new(1);
|
||||
private readonly AppSerializerContext _context = new(new JsonSerializerOptions(JsonSerializerDefaults.Web));
|
||||
private byte[] _lastMessage = [];
|
||||
|
||||
protected override ValueTask HandleMessageAsync(Memory<byte> buffer)
|
||||
{
|
||||
var response = GetMessageToSend();
|
||||
if (response == null)
|
||||
{
|
||||
Logger.LogTrace("cannot respond directly, increasing wanted frames");
|
||||
_wantedFrames.Release();
|
||||
return ValueTask.CompletedTask;
|
||||
}
|
||||
|
||||
Logger.LogTrace("responding directly");
|
||||
return Socket.SendTextAsync(response);
|
||||
}
|
||||
|
||||
public async Task OnGameTickAsync()
|
||||
{
|
||||
if (!await _wantedFrames.WaitAsync(TimeSpan.Zero))
|
||||
return;
|
||||
|
||||
var response = GetMessageToSend();
|
||||
if (response == null)
|
||||
{
|
||||
_wantedFrames.Release();
|
||||
return;
|
||||
}
|
||||
|
||||
Logger.LogTrace("responding indirectly");
|
||||
await Socket.SendTextAsync(response);
|
||||
}
|
||||
|
||||
private byte[]? GetMessageToSend()
|
||||
{
|
||||
var info = new PlayerInfo(player.Name, player.Scores, player.Controls);
|
||||
var response = JsonSerializer.SerializeToUtf8Bytes(info, _context.PlayerInfo);
|
||||
|
||||
if (response.SequenceEqual(_lastMessage))
|
||||
return null;
|
||||
|
||||
return _lastMessage = response;
|
||||
}
|
||||
|
||||
public void Dispose() => _wantedFrames.Dispose();
|
||||
}
|
|
@ -1,30 +1,32 @@
|
|||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Net.WebSockets;
|
||||
using TanksServer.GameLogic;
|
||||
|
||||
namespace TanksServer.Interactivity;
|
||||
|
||||
internal sealed class PlayerServer(
|
||||
ILogger<PlayerServer> logger,
|
||||
ILogger<PlayerInfoConnection> connectionLogger,
|
||||
TankSpawnQueue tankSpawnQueue
|
||||
)
|
||||
) : WebsocketServer<PlayerInfoConnection>(logger), ITickStep
|
||||
{
|
||||
private readonly ConcurrentDictionary<string, Player> _players = new();
|
||||
|
||||
public Player? GetOrAdd(string name, Guid id)
|
||||
{
|
||||
Player AddAndSpawn()
|
||||
{
|
||||
var player = new Player(name, id);
|
||||
tankSpawnQueue.EnqueueForImmediateSpawn(player);
|
||||
return player;
|
||||
}
|
||||
|
||||
var player = _players.GetOrAdd(name, _ => AddAndSpawn());
|
||||
if (player.Id != id)
|
||||
var existingOrAddedPlayer = _players.GetOrAdd(name, _ => AddAndSpawn());
|
||||
if (existingOrAddedPlayer.Id != id)
|
||||
return null;
|
||||
|
||||
logger.LogInformation("player {} (re)joined", player.Id);
|
||||
return player;
|
||||
logger.LogInformation("player {} (re)joined", existingOrAddedPlayer.Id);
|
||||
return existingOrAddedPlayer;
|
||||
|
||||
Player AddAndSpawn()
|
||||
{
|
||||
var newPlayer = new Player(name, id);
|
||||
tankSpawnQueue.EnqueueForImmediateSpawn(newPlayer);
|
||||
return newPlayer;
|
||||
}
|
||||
}
|
||||
|
||||
public bool TryGet(Guid? playerId, [MaybeNullWhen(false)] out Player foundPlayer)
|
||||
|
@ -42,4 +44,10 @@ internal sealed class PlayerServer(
|
|||
}
|
||||
|
||||
public IEnumerable<Player> GetAll() => _players.Values;
|
||||
|
||||
public Task HandleClientAsync(WebSocket webSocket, Player player)
|
||||
=> HandleClientAsync(new PlayerInfoConnection(player, connectionLogger, webSocket));
|
||||
|
||||
public Task TickAsync(TimeSpan delta)
|
||||
=> ParallelForEachConnectionAsync(connection => connection.OnGameTickAsync());
|
||||
}
|
||||
|
|
|
@ -35,7 +35,7 @@ internal abstract class WebsocketServer<T>(
|
|||
}
|
||||
}
|
||||
|
||||
protected Task AddConnection(T connection) => Locked(() =>
|
||||
private Task AddConnectionAsync(T connection) => Locked(() =>
|
||||
{
|
||||
if (_closing)
|
||||
{
|
||||
|
@ -47,7 +47,7 @@ internal abstract class WebsocketServer<T>(
|
|||
return Task.CompletedTask;
|
||||
}, CancellationToken.None);
|
||||
|
||||
protected Task RemoveConnection(T connection) => Locked(() =>
|
||||
private Task RemoveConnectionAsync(T connection) => Locked(() =>
|
||||
{
|
||||
_connections.Remove(connection);
|
||||
return Task.CompletedTask;
|
||||
|
@ -55,9 +55,9 @@ internal abstract class WebsocketServer<T>(
|
|||
|
||||
protected async Task HandleClientAsync(T connection)
|
||||
{
|
||||
await AddConnection(connection);
|
||||
await AddConnectionAsync(connection);
|
||||
await connection.ReceiveAsync();
|
||||
await RemoveConnection(connection);
|
||||
await RemoveConnectionAsync(connection);
|
||||
}
|
||||
|
||||
private async Task Locked(Func<Task> action, CancellationToken cancellationToken)
|
||||
|
|
|
@ -2,22 +2,24 @@ namespace TanksServer.Interactivity;
|
|||
|
||||
internal abstract class WebsocketServerConnection(
|
||||
ILogger logger,
|
||||
ByteChannelWebSocket socket)
|
||||
ByteChannelWebSocket socket
|
||||
)
|
||||
{
|
||||
protected readonly ByteChannelWebSocket Socket = socket;
|
||||
protected readonly ILogger Logger = logger;
|
||||
|
||||
public Task CloseAsync()
|
||||
{
|
||||
logger.LogDebug("closing connection");
|
||||
Logger.LogDebug("closing connection");
|
||||
return Socket.CloseAsync();
|
||||
}
|
||||
|
||||
public async Task ReceiveAsync()
|
||||
{
|
||||
await foreach (var buffer in Socket.ReadAllAsync())
|
||||
HandleMessage(buffer);
|
||||
logger.LogTrace("done receiving");
|
||||
await HandleMessageAsync(buffer);
|
||||
Logger.LogTrace("done receiving");
|
||||
}
|
||||
|
||||
protected abstract void HandleMessage(Memory<byte> buffer);
|
||||
protected abstract ValueTask HandleMessageAsync(Memory<byte> buffer);
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue