more configuration, limit rate of sent frames

This commit is contained in:
Vinzenz Schroeter 2024-04-16 21:34:54 +02:00
parent 786c974a23
commit 3f4a301993
10 changed files with 104 additions and 45 deletions

View file

@ -1,6 +1,10 @@
namespace TanksServer.GameLogic; namespace TanksServer.GameLogic;
internal sealed class CollideBulletsWithMap(BulletManager bullets, MapService map) : ITickStep internal sealed class CollideBulletsWithMap(
BulletManager bullets,
MapService map,
IOptions<GameRulesConfiguration> options
) : ITickStep
{ {
public Task TickAsync(TimeSpan _) public Task TickAsync(TimeSpan _)
{ {
@ -14,6 +18,7 @@ internal sealed class CollideBulletsWithMap(BulletManager bullets, MapService ma
if (!map.Current.IsWall(pixel)) if (!map.Current.IsWall(pixel))
return false; return false;
if (options.Value.DestructibleWalls)
map.Current.DestroyWallAt(pixel); map.Current.DestroyWallAt(pixel);
return true; return true;
} }

View file

@ -0,0 +1,6 @@
namespace TanksServer.GameLogic;
public class GameRulesConfiguration
{
public bool DestructibleWalls { get; set; } = true;
}

View file

@ -1,4 +1,3 @@
using DisplayCommands;
using SixLabors.ImageSharp; using SixLabors.ImageSharp;
using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.PixelFormats;
using TanksServer.GameLogic; using TanksServer.GameLogic;

View file

@ -1,5 +1,3 @@
using DisplayCommands;
namespace TanksServer.Graphics; namespace TanksServer.Graphics;
internal interface IDrawStep internal interface IDrawStep

View file

@ -8,10 +8,12 @@ namespace TanksServer.Interactivity;
internal sealed class ClientScreenServer( internal sealed class ClientScreenServer(
ILogger<ClientScreenServer> logger, ILogger<ClientScreenServer> logger,
ILoggerFactory loggerFactory ILoggerFactory loggerFactory,
IOptions<HostConfiguration> hostConfig
) : IHostedLifecycleService, IFrameConsumer ) : IHostedLifecycleService, IFrameConsumer
{ {
private readonly ConcurrentDictionary<ClientScreenServerConnection, byte> _connections = new(); private readonly ConcurrentDictionary<ClientScreenServerConnection, byte> _connections = new();
private readonly TimeSpan _minFrameTime = TimeSpan.FromMilliseconds(hostConfig.Value.ClientDisplayMinFrameTimeMs);
private bool _closing; private bool _closing;
public Task StoppingAsync(CancellationToken cancellationToken) public Task StoppingAsync(CancellationToken cancellationToken)
@ -34,6 +36,7 @@ internal sealed class ClientScreenServer(
socket, socket,
loggerFactory.CreateLogger<ClientScreenServerConnection>(), loggerFactory.CreateLogger<ClientScreenServerConnection>(),
this, this,
_minFrameTime,
playerGuid); playerGuid);
var added = _connections.TryAdd(connection, 0); var added = _connections.TryAdd(connection, 0);
Debug.Assert(added); Debug.Assert(added);

View file

@ -11,18 +11,23 @@ internal sealed class ClientScreenServerConnection : IDisposable
private readonly ILogger<ClientScreenServerConnection> _logger; private readonly ILogger<ClientScreenServerConnection> _logger;
private readonly ClientScreenServer _server; private readonly ClientScreenServer _server;
private readonly SemaphoreSlim _wantedFrames = new(1); private readonly SemaphoreSlim _wantedFrames = new(1);
private readonly Guid? _playerGuid = null; private readonly Guid? _playerGuid;
private readonly PlayerScreenData? _playerScreenData = null; private readonly PlayerScreenData? _playerScreenData;
private readonly TimeSpan _minFrameTime;
private DateTime _nextFrameAfter = DateTime.Now;
public ClientScreenServerConnection( public ClientScreenServerConnection(
WebSocket webSocket, WebSocket webSocket,
ILogger<ClientScreenServerConnection> logger, ILogger<ClientScreenServerConnection> logger,
ClientScreenServer server, ClientScreenServer server,
TimeSpan minFrameTime,
Guid? playerGuid = null Guid? playerGuid = null
) )
{ {
_server = server; _server = server;
_logger = logger; _logger = logger;
_minFrameTime = minFrameTime;
_playerGuid = playerGuid; _playerGuid = playerGuid;
if (playerGuid.HasValue) if (playerGuid.HasValue)
@ -42,12 +47,17 @@ internal sealed class ClientScreenServerConnection : IDisposable
public async Task SendAsync(PixelGrid pixels, GamePixelGrid gamePixelGrid) public async Task SendAsync(PixelGrid pixels, GamePixelGrid gamePixelGrid)
{ {
if (_nextFrameAfter > DateTime.Now)
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");
return; return;
} }
_nextFrameAfter = DateTime.Today + _minFrameTime;
if (_playerScreenData != null) if (_playerScreenData != null)
RefreshPlayerSpecificData(gamePixelGrid); RefreshPlayerSpecificData(gamePixelGrid);

View file

@ -16,17 +16,22 @@ internal sealed class SendToServicePointDisplay : IFrameConsumer
private readonly ILogger<SendToServicePointDisplay> _logger; private readonly ILogger<SendToServicePointDisplay> _logger;
private readonly PlayerServer _players; private readonly PlayerServer _players;
private readonly Cp437Grid _scoresBuffer; private readonly Cp437Grid _scoresBuffer;
private DateTime _nextFailLog = DateTime.Now; private readonly TimeSpan _minFrameTime;
private DateTime _nextFailLogAfter = DateTime.Now;
private DateTime _nextFrameAfter = DateTime.Now;
public SendToServicePointDisplay( public SendToServicePointDisplay(
PlayerServer players, PlayerServer players,
ILogger<SendToServicePointDisplay> logger, ILogger<SendToServicePointDisplay> logger,
IDisplayConnection displayConnection IDisplayConnection displayConnection,
IOptions<HostConfiguration> hostOptions
) )
{ {
_players = players; _players = players;
_logger = logger; _logger = logger;
_displayConnection = displayConnection; _displayConnection = displayConnection;
_minFrameTime = TimeSpan.FromMilliseconds(hostOptions.Value.ServicePointDisplayMinFrameTimeMs);
var localIp = _displayConnection.GetLocalIPv4().Split('.'); var localIp = _displayConnection.GetLocalIPv4().Split('.');
Debug.Assert(localIp.Length == 4); Debug.Assert(localIp.Length == 4);
@ -42,7 +47,12 @@ internal sealed class SendToServicePointDisplay : IFrameConsumer
public async Task OnFrameDoneAsync(GamePixelGrid gamePixelGrid, PixelGrid observerPixels) public async Task OnFrameDoneAsync(GamePixelGrid gamePixelGrid, PixelGrid observerPixels)
{ {
if (DateTime.Now < _nextFrameAfter)
return;
_nextFrameAfter = DateTime.Now + _minFrameTime;
RefreshScores(); RefreshScores();
try try
{ {
await _displayConnection.SendBitmapLinearWindowAsync(0, 0, observerPixels); await _displayConnection.SendBitmapLinearWindowAsync(0, 0, observerPixels);
@ -50,10 +60,10 @@ internal sealed class SendToServicePointDisplay : IFrameConsumer
} }
catch (SocketException ex) catch (SocketException ex)
{ {
if (DateTime.Now > _nextFailLog) if (DateTime.Now > _nextFailLogAfter)
{ {
_logger.LogWarning("could not send data to service point display: {}", ex.Message); _logger.LogWarning("could not send data to service point display: {}", ex.Message);
_nextFailLog = DateTime.Now + TimeSpan.FromSeconds(5); _nextFailLogAfter = DateTime.Now + TimeSpan.FromSeconds(5);
} }
} }
} }

View file

@ -0,0 +1,10 @@
namespace TanksServer.Models;
public class HostConfiguration
{
public bool EnableServicePointDisplay { get; set; } = true;
public int ServicePointDisplayMinFrameTimeMs { get; set; } = 25;
public int ClientDisplayMinFrameTimeMs { get; set; } = 25;
}

View file

@ -3,6 +3,7 @@ using DisplayCommands;
using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.FileProviders; using Microsoft.Extensions.FileProviders;
using TanksServer.GameLogic; using TanksServer.GameLogic;
@ -73,7 +74,7 @@ public static class Program
return Results.Empty; return Results.Empty;
}); });
app.MapGet("/map", () =>mapService.MapNames); app.MapGet("/map", () => mapService.MapNames);
app.MapPost("/map", ([FromQuery] string name) => app.MapPost("/map", ([FromQuery] string name) =>
{ {
@ -112,6 +113,11 @@ public static class Program
builder.Services.AddHttpLogging(_ => { }); builder.Services.AddHttpLogging(_ => { });
builder.Services.Configure<HostConfiguration>(builder.Configuration.GetSection("Host"));
var hostConfiguration = builder.Configuration.GetSection("Host").Get<HostConfiguration>();
if (hostConfiguration == null)
throw new InvalidOperationException("'Host' configuration missing");
builder.Services.AddSingleton<MapService>(); builder.Services.AddSingleton<MapService>();
builder.Services.AddSingleton<BulletManager>(); builder.Services.AddSingleton<BulletManager>();
builder.Services.AddSingleton<TankManager>(); builder.Services.AddSingleton<TankManager>();
@ -137,7 +143,6 @@ public static class Program
builder.Services.AddSingleton<IDrawStep, DrawTanksStep>(); builder.Services.AddSingleton<IDrawStep, DrawTanksStep>();
builder.Services.AddSingleton<IDrawStep, DrawBulletsStep>(); builder.Services.AddSingleton<IDrawStep, DrawBulletsStep>();
builder.Services.AddSingleton<IFrameConsumer, SendToServicePointDisplay>();
builder.Services.AddSingleton<IFrameConsumer, ClientScreenServer>(sp => builder.Services.AddSingleton<IFrameConsumer, ClientScreenServer>(sp =>
sp.GetRequiredService<ClientScreenServer>()); sp.GetRequiredService<ClientScreenServer>());
@ -145,7 +150,13 @@ public static class Program
builder.Configuration.GetSection("Tanks")); builder.Configuration.GetSection("Tanks"));
builder.Services.Configure<PlayersConfiguration>( builder.Services.Configure<PlayersConfiguration>(
builder.Configuration.GetSection("Players")); builder.Configuration.GetSection("Players"));
builder.Services.Configure<GameRulesConfiguration>(builder.Configuration.GetSection("GameRules"));
if (hostConfiguration.EnableServicePointDisplay)
{
builder.Services.AddSingleton<IFrameConsumer, SendToServicePointDisplay>();
builder.Services.AddDisplay(builder.Configuration.GetSection("ServicePointDisplay")); builder.Services.AddDisplay(builder.Configuration.GetSection("ServicePointDisplay"));
}
var app = builder.Build(); var app = builder.Build();

View file

@ -16,7 +16,6 @@
} }
}, },
"ServicePointDisplay": { "ServicePointDisplay": {
"Enable": true,
"Hostname": "172.23.42.29", "Hostname": "172.23.42.29",
"Port": 2342 "Port": 2342
}, },
@ -26,8 +25,16 @@
"ShootDelayMs": 450, "ShootDelayMs": 450,
"BulletSpeed": 75 "BulletSpeed": 75
}, },
"GameRules": {
"DestructibleWalls": true
},
"Players": { "Players": {
"SpawnDelayMs": 3000, "SpawnDelayMs": 3000,
"IdleTimeoutMs": 30000 "IdleTimeoutMs": 30000
},
"Host": {
"EnableServicePointDisplay": true,
"ServicePointDisplayMinFrameTimeMs": 25,
"ClientScreenMinFrameTime": 5
} }
} }