make big display toggleable

This commit is contained in:
Vinzenz Schroeter 2024-04-07 20:16:22 +02:00
parent a9aaf899a2
commit dc9ad21a3d
29 changed files with 119 additions and 46 deletions

View file

@ -1,4 +1,5 @@
using TanksServer.Helpers; using TanksServer.Helpers;
using TanksServer.Services;
namespace TanksServer.DrawSteps; namespace TanksServer.DrawSteps;

View file

@ -1,4 +1,6 @@
using TanksServer.Helpers; using TanksServer.Helpers;
using TanksServer.Models;
using TanksServer.Services;
namespace TanksServer.DrawSteps; namespace TanksServer.DrawSteps;

View file

@ -1,6 +1,7 @@
using SixLabors.ImageSharp; using SixLabors.ImageSharp;
using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.PixelFormats;
using TanksServer.Helpers; using TanksServer.Helpers;
using TanksServer.Services;
namespace TanksServer.DrawSteps; namespace TanksServer.DrawSteps;

View file

@ -1,8 +1,7 @@
global using System; global using System;
global using System.Collections.Concurrent;
global using System.Collections.Generic; global using System.Collections.Generic;
global using System.Linq; global using System.Linq;
global using System.Threading; global using System.Threading;
global using System.Threading.Tasks; global using System.Threading.Tasks;
global using Microsoft.Extensions.Options; global using Microsoft.Extensions.Options;
global using TanksServer.Models;
global using TanksServer.Services;

View file

@ -1,4 +1,5 @@
using System.Text.Json.Serialization; using System.Text.Json.Serialization;
using TanksServer.Models;
namespace TanksServer.Helpers; namespace TanksServer.Helpers;

View file

@ -1,4 +1,6 @@
using System.Diagnostics; using System.Diagnostics;
using TanksServer.Models;
using TanksServer.Services;
namespace TanksServer.Helpers; namespace TanksServer.Helpers;

View file

@ -1,7 +1,8 @@
namespace TanksServer.Services; namespace TanksServer.Models;
internal sealed class ServicePointDisplayConfiguration internal sealed class ServicePointDisplayConfiguration
{ {
public bool Enable { get; set; } = true;
public string Hostname { get; set; } = string.Empty; public string Hostname { get; set; } = string.Empty;
public int Port { get; set; } public int Port { get; set; }
} }

View file

@ -6,7 +6,9 @@ using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.FileProviders; using Microsoft.Extensions.FileProviders;
using TanksServer.DrawSteps; using TanksServer.DrawSteps;
using TanksServer.Helpers; using TanksServer.Helpers;
using TanksServer.Models;
using TanksServer.Servers; using TanksServer.Servers;
using TanksServer.Services;
using TanksServer.TickSteps; using TanksServer.TickSteps;
namespace TanksServer; namespace TanksServer;
@ -74,14 +76,17 @@ internal static class Program
options.SerializerOptions.TypeInfoResolverChain.Insert(0, new AppSerializerContext()); options.SerializerOptions.TypeInfoResolverChain.Insert(0, new AppSerializerContext());
}); });
builder.Services.AddOptions();
builder.Services.AddSingleton<MapService>(); builder.Services.AddSingleton<MapService>();
builder.Services.AddSingleton<BulletManager>(); builder.Services.AddSingleton<BulletManager>();
builder.Services.AddSingleton<TankManager>(); builder.Services.AddSingleton<TankManager>();
builder.Services.AddSingleton<SpawnNewTanks>(); builder.Services.AddSingleton<SpawnNewTanks>();
builder.Services.AddSingleton<PixelDrawer>();
builder.Services.AddSingleton<ControlsServer>(); builder.Services.AddSingleton<ControlsServer>();
builder.Services.AddSingleton<PlayerServer>(); builder.Services.AddSingleton<PlayerServer>();
builder.Services.AddSingleton<ClientScreenServer>(); builder.Services.AddSingleton<ClientScreenServer>();
builder.Services.AddSingleton<LastFinishedFrameProvider>();
builder.Services.AddSingleton<SpawnQueueProvider>();
builder.Services.AddHostedService<GameTickService>(); builder.Services.AddHostedService<GameTickService>();
builder.Services.AddHostedService(sp => sp.GetRequiredService<ControlsServer>()); builder.Services.AddHostedService(sp => sp.GetRequiredService<ControlsServer>());
@ -94,7 +99,7 @@ internal static class Program
builder.Services.AddSingleton<ITickStep, MoveTanks>(); builder.Services.AddSingleton<ITickStep, MoveTanks>();
builder.Services.AddSingleton<ITickStep, ShootFromTanks>(); builder.Services.AddSingleton<ITickStep, ShootFromTanks>();
builder.Services.AddSingleton<ITickStep>(sp => sp.GetRequiredService<SpawnNewTanks>()); builder.Services.AddSingleton<ITickStep>(sp => sp.GetRequiredService<SpawnNewTanks>());
builder.Services.AddSingleton<ITickStep>(sp => sp.GetRequiredService<PixelDrawer>()); builder.Services.AddSingleton<ITickStep, DrawStateToFrame>();
builder.Services.AddSingleton<ITickStep, SendToServicePointDisplay>(); builder.Services.AddSingleton<ITickStep, SendToServicePointDisplay>();
builder.Services.AddSingleton<ITickStep, SendToClientScreen>(); builder.Services.AddSingleton<ITickStep, SendToClientScreen>();
@ -102,6 +107,9 @@ internal static class Program
builder.Services.AddSingleton<IDrawStep, TankDrawer>(); builder.Services.AddSingleton<IDrawStep, TankDrawer>();
builder.Services.AddSingleton<IDrawStep, BulletDrawer>(); builder.Services.AddSingleton<IDrawStep, BulletDrawer>();
builder.Services.Configure<ServicePointDisplayConfiguration>(
builder.Configuration.GetSection("ServicePointDisplay"));
return builder.Build(); return builder.Build();
} }
} }

View file

@ -1,4 +1,3 @@
using System.Collections.Concurrent;
using System.Diagnostics; using System.Diagnostics;
using System.Net.WebSockets; using System.Net.WebSockets;
using System.Threading.Channels; using System.Threading.Channels;

View file

@ -2,6 +2,7 @@ using System.Net.WebSockets;
using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using TanksServer.Helpers; using TanksServer.Helpers;
using TanksServer.Models;
namespace TanksServer.Servers; namespace TanksServer.Servers;

View file

@ -1,11 +1,11 @@
using System.Collections.Concurrent;
using System.Diagnostics.CodeAnalysis; using System.Diagnostics.CodeAnalysis;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using TanksServer.TickSteps; using TanksServer.Models;
using TanksServer.Services;
namespace TanksServer.Servers; namespace TanksServer.Servers;
internal sealed class PlayerServer(ILogger<PlayerServer> logger, SpawnNewTanks spawnNewTanks) internal sealed class PlayerServer(ILogger<PlayerServer> logger, SpawnQueueProvider spawnQueueProvider)
{ {
private readonly ConcurrentDictionary<string, Player> _players = new(); private readonly ConcurrentDictionary<string, Player> _players = new();
@ -33,7 +33,7 @@ internal sealed class PlayerServer(ILogger<PlayerServer> logger, SpawnNewTanks s
private Player AddAndSpawn(string name) private Player AddAndSpawn(string name)
{ {
var player = new Player(name); var player = new Player(name);
spawnNewTanks.SpawnTankForPlayer(player); spawnQueueProvider.Queue.Enqueue(player);
return player; return player;
} }
} }

View file

@ -1,3 +1,5 @@
using TanksServer.Models;
namespace TanksServer.Services; namespace TanksServer.Services;
internal sealed class BulletManager internal sealed class BulletManager

View file

@ -1,9 +1,12 @@
using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using TanksServer.TickSteps; using TanksServer.TickSteps;
namespace TanksServer.Services; namespace TanksServer.Services;
internal sealed class GameTickService(IEnumerable<ITickStep> steps) : IHostedService, IDisposable internal sealed class GameTickService(
IEnumerable<ITickStep> steps, IHostApplicationLifetime lifetime, ILogger<GameTickService> logger
) : IHostedService, IDisposable
{ {
private readonly CancellationTokenSource _cancellation = new(); private readonly CancellationTokenSource _cancellation = new();
private readonly List<ITickStep> _steps = steps.ToList(); private readonly List<ITickStep> _steps = steps.ToList();
@ -16,12 +19,20 @@ internal sealed class GameTickService(IEnumerable<ITickStep> steps) : IHostedSer
} }
private async Task RunAsync() private async Task RunAsync()
{
try
{ {
while (!_cancellation.IsCancellationRequested) while (!_cancellation.IsCancellationRequested)
{ {
foreach (var step in _steps) foreach (var step in _steps)
await step.TickAsync(); await step.TickAsync();
await Task.Delay(1000/25); await Task.Delay(1000 / 25);
}
}
catch (Exception ex)
{
logger.LogError(ex, "game tick service crashed");
lifetime.StopApplication();
} }
} }

View file

@ -0,0 +1,14 @@
using TanksServer.Helpers;
namespace TanksServer.Services;
internal sealed class LastFinishedFrameProvider
{
private DisplayPixelBuffer? _lastFrame;
public DisplayPixelBuffer LastFrame
{
get => _lastFrame ?? throw new InvalidOperationException("first frame not yet drawn");
set => _lastFrame = value;
}
}

View file

@ -1,3 +1,5 @@
using TanksServer.Models;
namespace TanksServer.Services; namespace TanksServer.Services;
internal sealed class MapService internal sealed class MapService

View file

@ -0,0 +1,8 @@
using TanksServer.Models;
namespace TanksServer.Services;
internal sealed class SpawnQueueProvider
{
public ConcurrentQueue<Player> Queue { get; } = new();
}

View file

@ -1,6 +1,6 @@
using System.Collections; using System.Collections;
using System.Collections.Concurrent;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using TanksServer.Models;
namespace TanksServer.Services; namespace TanksServer.Services;

View file

@ -16,6 +16,7 @@
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Microsoft.Extensions.Options.ConfigurationExtensions" Version="8.0.0" />
<PackageReference Include="SixLabors.ImageSharp" Version="3.1.3" /> <PackageReference Include="SixLabors.ImageSharp" Version="3.1.3" />
</ItemGroup> </ItemGroup>

View file

@ -1,4 +1,6 @@
using TanksServer.Helpers; using TanksServer.Helpers;
using TanksServer.Models;
using TanksServer.Services;
namespace TanksServer.TickSteps; namespace TanksServer.TickSteps;

View file

@ -1,3 +1,6 @@
using TanksServer.Models;
using TanksServer.Services;
namespace TanksServer.TickSteps; namespace TanksServer.TickSteps;
internal sealed class CollideBulletsWithTanks(BulletManager bullets) : ITickStep internal sealed class CollideBulletsWithTanks(BulletManager bullets) : ITickStep

View file

@ -1,27 +1,22 @@
using TanksServer.DrawSteps; using TanksServer.DrawSteps;
using TanksServer.Helpers; using TanksServer.Helpers;
using TanksServer.TickSteps; using TanksServer.Services;
namespace TanksServer.Services; namespace TanksServer.TickSteps;
internal sealed class PixelDrawer(IEnumerable<IDrawStep> drawSteps) : ITickStep internal sealed class DrawStateToFrame(
IEnumerable<IDrawStep> drawSteps, LastFinishedFrameProvider lastFrameProvider
) : ITickStep
{ {
private const uint GameFieldPixelCount = MapService.PixelsPerRow * MapService.PixelsPerColumn; private const uint GameFieldPixelCount = MapService.PixelsPerRow * MapService.PixelsPerColumn;
private DisplayPixelBuffer? _lastFrame;
private readonly List<IDrawStep> _drawSteps = drawSteps.ToList(); private readonly List<IDrawStep> _drawSteps = drawSteps.ToList();
public DisplayPixelBuffer LastFrame
{
get => _lastFrame ?? throw new InvalidOperationException("first frame not yet drawn");
private set => _lastFrame = value;
}
public Task TickAsync() public Task TickAsync()
{ {
var buffer = CreateGameFieldPixelBuffer(); var buffer = CreateGameFieldPixelBuffer();
foreach (var step in _drawSteps) foreach (var step in _drawSteps)
step.Draw(buffer); step.Draw(buffer);
LastFrame = buffer; lastFrameProvider.LastFrame = buffer;
return Task.CompletedTask; return Task.CompletedTask;
} }

View file

@ -1,3 +1,6 @@
using TanksServer.Models;
using TanksServer.Services;
namespace TanksServer.TickSteps; namespace TanksServer.TickSteps;
internal sealed class MoveBullets(BulletManager bullets) : ITickStep internal sealed class MoveBullets(BulletManager bullets) : ITickStep

View file

@ -1,3 +1,6 @@
using TanksServer.Models;
using TanksServer.Services;
namespace TanksServer.TickSteps; namespace TanksServer.TickSteps;
internal sealed class MoveTanks( internal sealed class MoveTanks(

View file

@ -1,3 +1,6 @@
using TanksServer.Models;
using TanksServer.Services;
namespace TanksServer.TickSteps; namespace TanksServer.TickSteps;
internal sealed class RotateTanks(TankManager tanks, IOptions<TanksConfiguration> options) : ITickStep internal sealed class RotateTanks(TankManager tanks, IOptions<TanksConfiguration> options) : ITickStep

View file

@ -1,13 +1,18 @@
using TanksServer.Servers; using TanksServer.Servers;
using TanksServer.Services;
namespace TanksServer.TickSteps; namespace TanksServer.TickSteps;
internal sealed class SendToClientScreen( internal sealed class SendToClientScreen(
ClientScreenServer clientScreenServer, PixelDrawer drawer ClientScreenServer clientScreenServer,
LastFinishedFrameProvider lastFinishedFrameProvider
) : ITickStep ) : ITickStep
{ {
public Task TickAsync() public Task TickAsync()
{ {
return Task.WhenAll(clientScreenServer.GetConnections().Select(c => c.SendAsync(drawer.LastFrame))); var tasks = clientScreenServer
.GetConnections()
.Select(c => c.SendAsync(lastFinishedFrameProvider.LastFrame));
return Task.WhenAll(tasks);
} }
} }

View file

@ -1,21 +1,25 @@
using System.Net.Sockets; using System.Net.Sockets;
using TanksServer.Models;
using TanksServer.Services;
namespace TanksServer.TickSteps; namespace TanksServer.TickSteps;
internal sealed class SendToServicePointDisplay( internal sealed class SendToServicePointDisplay(
IOptions<ServicePointDisplayConfiguration> options, IOptions<ServicePointDisplayConfiguration> options,
PixelDrawer drawer LastFinishedFrameProvider lastFinishedFrameProvider
) : ITickStep, IDisposable ) : ITickStep, IDisposable
{ {
private readonly UdpClient _udpClient = new(options.Value.Hostname, options.Value.Port); private readonly UdpClient? _udpClient = options.Value.Enable
? new(options.Value.Hostname, options.Value.Port)
: null;
public Task TickAsync() public Task TickAsync()
{ {
return _udpClient.SendAsync(drawer.LastFrame.Data).AsTask(); return _udpClient?.SendAsync(lastFinishedFrameProvider.LastFrame.Data).AsTask() ?? Task.CompletedTask;
} }
public void Dispose() public void Dispose()
{ {
_udpClient.Dispose(); _udpClient?.Dispose();
} }
} }

View file

@ -1,3 +1,6 @@
using TanksServer.Models;
using TanksServer.Services;
namespace TanksServer.TickSteps; namespace TanksServer.TickSteps;
internal sealed class ShootFromTanks( internal sealed class ShootFromTanks(

View file

@ -1,14 +1,13 @@
using System.Collections.Concurrent; using TanksServer.Models;
using TanksServer.Services;
namespace TanksServer.TickSteps; namespace TanksServer.TickSteps;
internal sealed class SpawnNewTanks(TankManager tanks, MapService map) : ITickStep internal sealed class SpawnNewTanks(TankManager tanks, MapService map, SpawnQueueProvider queueProvider) : ITickStep
{ {
private readonly ConcurrentQueue<Player> _playersToSpawn = new();
public Task TickAsync() public Task TickAsync()
{ {
while (_playersToSpawn.TryDequeue(out var player)) while (queueProvider.Queue.TryDequeue(out var player))
{ {
var tank = new Tank(player, ChooseSpawnPosition()) var tank = new Tank(player, ChooseSpawnPosition())
{ {
@ -42,9 +41,4 @@ internal sealed class SpawnNewTanks(TankManager tanks, MapService map) : ITickSt
chosenTile.Y * MapService.TileSize chosenTile.Y * MapService.TileSize
); );
} }
public void SpawnTankForPlayer(Player player)
{
_playersToSpawn.Enqueue(player);
}
} }

View file

@ -13,5 +13,10 @@
"Url": "http://localhost:3000" "Url": "http://localhost:3000"
} }
} }
},
"ServicePointDisplay": {
"Enable": false,
"Hostname": "172.23.42.29",
"Port": 2342
} }
} }