using System.Buffers; using System.Net.WebSockets; using ServicePoint; using TanksServer.Graphics; namespace TanksServer.Interactivity; internal sealed class ClientScreenServerConnection : DroppablePackageRequestConnection { private readonly BufferPool _bufferPool; private readonly PlayerScreenData? _playerDataBuilder; private readonly Player? _player; public ClientScreenServerConnection( WebSocket webSocket, ILogger logger, Player? player, BufferPool bufferPool ) : base(logger, new ByteChannelWebSocket(webSocket, logger, 0)) { _player = player; _bufferPool = bufferPool; _player?.IncrementConnectionCount(); _playerDataBuilder = player == null ? null : new PlayerScreenData(logger, player); } public async Task OnGameTickAsync(Bitmap pixels, GamePixelGrid gamePixelGrid) { await Task.Yield(); var next = BuildNextPackage(pixels, gamePixelGrid); SetNextPackage(next); } private Package BuildNextPackage(Bitmap pixels, GamePixelGrid gamePixelGrid) { var pixelsData = pixels.CopyRaw(); var nextPixels = _bufferPool.Rent(pixelsData.Length); pixelsData.CopyTo(nextPixels.Memory.Span); if (_playerDataBuilder == null) return new Package(nextPixels, null); var data = _playerDataBuilder.Build(gamePixelGrid); var nextPlayerData = _bufferPool.Rent(data.Length); data.CopyTo(nextPlayerData.Memory); return new Package(nextPixels, nextPlayerData); } protected override async ValueTask SendPackageAsync(Package package) { try { await Socket.SendBinaryAsync(package.Pixels.Memory, package.PlayerData == null); if (package.PlayerData != null) await Socket.SendBinaryAsync(package.PlayerData.Memory); } catch (WebSocketException ex) { Logger.LogWarning(ex, "send failed"); } } public override void Dispose() { base.Dispose(); _player?.DecrementConnectionCount(); } internal sealed record class Package( IMemoryOwner Pixels, IMemoryOwner? PlayerData ) : IDisposable { public void Dispose() { Pixels.Dispose(); PlayerData?.Dispose(); } } }