Merge branch 'uniffi'

This commit is contained in:
Vinzenz Schroeter 2024-11-23 23:06:10 +01:00
commit ded0293b13
37 changed files with 270 additions and 234 deletions

2
.envrc
View file

@ -1 +1 @@
use nix use flake

View file

@ -2,11 +2,11 @@
"nodes": { "nodes": {
"nixpkgs": { "nixpkgs": {
"locked": { "locked": {
"lastModified": 1730327045, "lastModified": 1731797254,
"narHash": "sha256-xKel5kd1AbExymxoIfQ7pgcX6hjw9jCgbiBjiUfSVJ8=", "narHash": "sha256-df3dJApLPhd11AlueuoN0Q4fHo/hagP75LlM5K1sz9g=",
"owner": "nixos", "owner": "nixos",
"repo": "nixpkgs", "repo": "nixpkgs",
"rev": "080166c15633801df010977d9d7474b4a6c549d7", "rev": "e8c38b73aeb218e27163376a2d617e61a2ad9b59",
"type": "github" "type": "github"
}, },
"original": { "original": {

View file

@ -1,5 +1,6 @@
 
Microsoft Visual Studio Solution File, Format Version 12.00 Microsoft Visual Studio Solution File, Format Version 12.00
#
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TanksServer", "TanksServer\TanksServer.csproj", "{D88BF376-47A4-4C72-ADD1-983F9285C351}" Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TanksServer", "TanksServer\TanksServer.csproj", "{D88BF376-47A4-4C72-ADD1-983F9285C351}"
EndProject EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "shared", "shared", "{12DB7D48-1BB2-488B-B4D9-4126087D2F8C}" Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "shared", "shared", "{12DB7D48-1BB2-488B-B4D9-4126087D2F8C}"
@ -9,7 +10,19 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "shared", "shared", "{12DB7D
..\.envrc = ..\.envrc ..\.envrc = ..\.envrc
EndProjectSection EndProjectSection
EndProject EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ServicePoint", "servicepoint/crates/servicepoint_binding_cs/ServicePoint/ServicePoint.csproj", "{DFCC69ED-E02B-4631-8A23-5D394BA01E03}" Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "servicepoint", "servicepoint", "{A10FB29A-9078-4E90-9CE1-E6C2B5209E19}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "crates", "crates", "{656A7CBA-9445-41CC-B1AF-A6897AAC9F17}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "servicepoint_binding_uniffi", "servicepoint_binding_uniffi", "{5FD9FAD7-07BA-4DF9-8C84-15A9558373F1}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "libraries", "libraries", "{6082A0DC-5345-48C8-BA2E-667754A2F0E9}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "csharp", "csharp", "{D8A3290B-5DFB-43C6-99EE-56AB5F53F468}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ServicePoint", "servicepoint\crates\servicepoint_binding_uniffi\libraries\csharp\ServicePoint\ServicePoint.csproj", "{D1DDCD0D-6152-45E6-B673-DD78C466BDC3}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ServicePoint.Tests", "servicepoint\crates\servicepoint_binding_uniffi\libraries\csharp\ServicePoint.Tests\ServicePoint.Tests.csproj", "{EF42D6B7-70B1-490B-BB5F-5A44D1309A7C}"
EndProject EndProject
Global Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution GlobalSection(SolutionConfigurationPlatforms) = preSolution
@ -21,9 +34,21 @@ Global
{D88BF376-47A4-4C72-ADD1-983F9285C351}.Debug|Any CPU.Build.0 = Debug|Any CPU {D88BF376-47A4-4C72-ADD1-983F9285C351}.Debug|Any CPU.Build.0 = Debug|Any CPU
{D88BF376-47A4-4C72-ADD1-983F9285C351}.Release|Any CPU.ActiveCfg = Release|Any CPU {D88BF376-47A4-4C72-ADD1-983F9285C351}.Release|Any CPU.ActiveCfg = Release|Any CPU
{D88BF376-47A4-4C72-ADD1-983F9285C351}.Release|Any CPU.Build.0 = Release|Any CPU {D88BF376-47A4-4C72-ADD1-983F9285C351}.Release|Any CPU.Build.0 = Release|Any CPU
{DFCC69ED-E02B-4631-8A23-5D394BA01E03}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {D1DDCD0D-6152-45E6-B673-DD78C466BDC3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{DFCC69ED-E02B-4631-8A23-5D394BA01E03}.Debug|Any CPU.Build.0 = Debug|Any CPU {D1DDCD0D-6152-45E6-B673-DD78C466BDC3}.Debug|Any CPU.Build.0 = Debug|Any CPU
{DFCC69ED-E02B-4631-8A23-5D394BA01E03}.Release|Any CPU.ActiveCfg = Release|Any CPU {D1DDCD0D-6152-45E6-B673-DD78C466BDC3}.Release|Any CPU.ActiveCfg = Release|Any CPU
{DFCC69ED-E02B-4631-8A23-5D394BA01E03}.Release|Any CPU.Build.0 = Release|Any CPU {D1DDCD0D-6152-45E6-B673-DD78C466BDC3}.Release|Any CPU.Build.0 = Release|Any CPU
{EF42D6B7-70B1-490B-BB5F-5A44D1309A7C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{EF42D6B7-70B1-490B-BB5F-5A44D1309A7C}.Debug|Any CPU.Build.0 = Debug|Any CPU
{EF42D6B7-70B1-490B-BB5F-5A44D1309A7C}.Release|Any CPU.ActiveCfg = Release|Any CPU
{EF42D6B7-70B1-490B-BB5F-5A44D1309A7C}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(NestedProjects) = preSolution
{656A7CBA-9445-41CC-B1AF-A6897AAC9F17} = {A10FB29A-9078-4E90-9CE1-E6C2B5209E19}
{5FD9FAD7-07BA-4DF9-8C84-15A9558373F1} = {656A7CBA-9445-41CC-B1AF-A6897AAC9F17}
{6082A0DC-5345-48C8-BA2E-667754A2F0E9} = {5FD9FAD7-07BA-4DF9-8C84-15A9558373F1}
{D8A3290B-5DFB-43C6-99EE-56AB5F53F468} = {6082A0DC-5345-48C8-BA2E-667754A2F0E9}
{D1DDCD0D-6152-45E6-B673-DD78C466BDC3} = {D8A3290B-5DFB-43C6-99EE-56AB5F53F468}
{EF42D6B7-70B1-490B-BB5F-5A44D1309A7C} = {D8A3290B-5DFB-43C6-99EE-56AB5F53F468}
EndGlobalSection EndGlobalSection
EndGlobal EndGlobal

View file

@ -1,3 +1,4 @@
using System.Diagnostics.CodeAnalysis;
using System.IO; using System.IO;
using System.Text; using System.Text;
using System.Text.Json; using System.Text.Json;
@ -7,7 +8,6 @@ using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Http.HttpResults; using Microsoft.AspNetCore.Http.HttpResults;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Diagnostics.HealthChecks; using Microsoft.Extensions.Diagnostics.HealthChecks;
using ServicePoint;
using TanksServer.GameLogic; using TanksServer.GameLogic;
using TanksServer.Interactivity; using TanksServer.Interactivity;
@ -22,6 +22,8 @@ internal sealed class Endpoints(
Connection displayConnection Connection displayConnection
) )
{ {
[RequiresUnreferencedCode("Calls Microsoft.AspNetCore.Builder.EndpointRouteBuilderExtensions.MapPost(String, Delegate)")]
[RequiresDynamicCode("Calls Microsoft.AspNetCore.Builder.EndpointRouteBuilderExtensions.MapPost(String, Delegate)")]
public void Map(WebApplication app) public void Map(WebApplication app)
{ {
app.MapPost("/player", PostPlayer); app.MapPost("/player", PostPlayer);
@ -31,7 +33,7 @@ internal sealed class Endpoints(
app.Map("/controls", ConnectControlsAsync); app.Map("/controls", ConnectControlsAsync);
app.MapGet("/map", () => mapService.MapNames); app.MapGet("/map", () => mapService.MapNames);
app.MapPost("/map", PostMap); app.MapPost("/map", PostMap);
app.MapPost("/resetDisplay", () => displayConnection.Send(Command.HardReset().IntoPacket())); app.MapPost("/resetDisplay", () => displayConnection.Send(Command.HardReset()));
app.MapGet("/map/{name}", GetMapByName); app.MapGet("/map/{name}", GetMapByName);
app.MapHealthChecks("/health", new HealthCheckOptions app.MapHealthChecks("/health", new HealthCheckOptions
@ -117,7 +119,7 @@ internal sealed class Endpoints(
if (!mapService.TryGetPreview(name, out var preview)) if (!mapService.TryGetPreview(name, out var preview))
return TypedResults.NotFound(); return TypedResults.NotFound();
var mapInfo = new MapInfo(prototype.Name, prototype.GetType().Name, preview.Data.ToArray()); var mapInfo = new MapInfo(prototype.Name, prototype.GetType().Name, preview.CopyRaw());
return TypedResults.Ok(mapInfo); return TypedResults.Ok(mapInfo);
} }

View file

@ -7,13 +7,11 @@ internal sealed class GameTickWorker(
IEnumerable<ITickStep> steps, IEnumerable<ITickStep> steps,
IHostApplicationLifetime lifetime, IHostApplicationLifetime lifetime,
ILogger<GameTickWorker> logger ILogger<GameTickWorker> logger
) : IHostedLifecycleService, IDisposable ) : BackgroundService, IDisposable
{ {
private readonly CancellationTokenSource _cancellation = new();
private readonly TaskCompletionSource _shutdownCompletion = new();
private readonly List<ITickStep> _steps = steps.ToList(); private readonly List<ITickStep> _steps = steps.ToList();
public async Task StartedAsync(CancellationToken cancellationToken) protected override async Task ExecuteAsync(CancellationToken cancellationToken)
{ {
await Task.Yield(); await Task.Yield();
@ -23,7 +21,7 @@ internal sealed class GameTickWorker(
try try
{ {
while (!_cancellation.IsCancellationRequested) while (!cancellationToken.IsCancellationRequested)
{ {
var delta = sw.Elapsed; var delta = sw.Elapsed;
sw.Restart(); sw.Restart();
@ -37,19 +35,5 @@ internal sealed class GameTickWorker(
logger.LogError(ex, "game tick service crashed"); logger.LogError(ex, "game tick service crashed");
lifetime.StopApplication(); lifetime.StopApplication();
} }
_shutdownCompletion.SetResult();
} }
public Task StoppingAsync(CancellationToken cancellationToken) => _cancellation.CancelAsync();
public Task StopAsync(CancellationToken cancellationToken) => _shutdownCompletion.Task;
public void Dispose() => _cancellation.Dispose();
public Task StartAsync(CancellationToken cancellationToken) => Task.CompletedTask;
public Task StartingAsync(CancellationToken cancellationToken) => Task.CompletedTask;
public Task StoppedAsync(CancellationToken cancellationToken) => Task.CompletedTask;
} }

View file

@ -4,7 +4,7 @@ internal sealed class Map(string name, bool[,] walls)
{ {
public string Name => name; public string Name => name;
public bool IsWall(int x, int y) => walls[x, y]; public bool IsWall(ulong x, ulong y) => walls[x, y];
public bool IsWall(PixelPosition position) => walls[position.X, position.Y]; public bool IsWall(PixelPosition position) => walls[position.X, position.Y];
@ -12,8 +12,8 @@ internal sealed class Map(string name, bool[,] walls)
{ {
var pixel = position.ToPixelPosition(); var pixel = position.ToPixelPosition();
for (short dx = 0; dx < MapService.TileSize; dx++) for (long dx = 0; dx < (long)MapService.TileSize; dx++)
for (short dy = 0; dy < MapService.TileSize; dy++) for (long dy = 0; dy < (long)MapService.TileSize; dy++)
{ {
if (IsWall(pixel.GetPixelRelative(dx, dy))) if (IsWall(pixel.GetPixelRelative(dx, dy)))
return true; return true;

View file

@ -1,18 +1,17 @@
using System.Diagnostics; using System.Diagnostics;
using System.Diagnostics.CodeAnalysis; using System.Diagnostics.CodeAnalysis;
using System.IO; using System.IO;
using ServicePoint;
using TanksServer.Graphics; using TanksServer.Graphics;
namespace TanksServer.GameLogic; namespace TanksServer.GameLogic;
internal sealed class MapService internal sealed class MapService
{ {
public const ushort TilesPerRow = 44; public static readonly ulong TilesPerRow = 44;
public const ushort TilesPerColumn = 20; public static readonly ulong TilesPerColumn = ServicePointConstants.TileHeight;
public const ushort TileSize = 8; public static readonly ulong TileSize = ServicePointConstants.TileSize;
public const ushort PixelsPerRow = TilesPerRow * TileSize; public static readonly ulong PixelsPerRow = TilesPerRow * TileSize;
public const ushort PixelsPerColumn = TilesPerColumn * TileSize; public static readonly ulong PixelsPerColumn = TilesPerColumn * TileSize;
private readonly ConcurrentDictionary<string, MapPrototype> _mapPrototypes = new(); private readonly ConcurrentDictionary<string, MapPrototype> _mapPrototypes = new();
private readonly ConcurrentDictionary<string, Bitmap> _mapPreviews = new(); private readonly ConcurrentDictionary<string, Bitmap> _mapPreviews = new();

View file

@ -68,10 +68,10 @@ internal sealed class MoveTanks(
{ {
var (topLeft, _) = newPosition.GetBoundsForCenter(MapService.TileSize); var (topLeft, _) = newPosition.GetBoundsForCenter(MapService.TileSize);
for (short y = 0; y < MapService.TileSize; y++) for (ulong y = 0; y < MapService.TileSize; y++)
for (short x = 0; x < MapService.TileSize; x++) for (ulong x = 0; x < MapService.TileSize; x++)
{ {
var pixelToCheck = topLeft.GetPixelRelative(x, y); var pixelToCheck = topLeft.GetPixelRelative((long)x, (long)y);
if (map.Current.IsWall(pixelToCheck)) if (map.Current.IsWall(pixelToCheck))
return true; return true;
} }

View file

@ -11,7 +11,7 @@ internal sealed class SpriteMapPrototype : MapPrototype
public SpriteMapPrototype(string name, Sprite sprite) public SpriteMapPrototype(string name, Sprite sprite)
{ {
if (sprite.Width != MapService.PixelsPerRow || sprite.Height != MapService.PixelsPerColumn) if ((ulong)sprite.Width != MapService.PixelsPerRow || (ulong)sprite.Height != MapService.PixelsPerColumn)
throw new FileLoadException($"invalid image size in file {Name}"); throw new FileLoadException($"invalid image size in file {Name}");
Name = name; Name = name;

View file

@ -8,7 +8,7 @@ internal sealed class TextMapPrototype : MapPrototype
public TextMapPrototype(string name, string text) public TextMapPrototype(string name, string text)
{ {
if (text.Length != MapService.TilesPerColumn * MapService.TilesPerRow) if ((ulong)text.Length != MapService.TilesPerColumn * MapService.TilesPerRow)
throw new ArgumentException($"cannot load map {name}: invalid length"); throw new ArgumentException($"cannot load map {name}: invalid length");
Name = name; Name = name;
Text = text; Text = text;
@ -19,11 +19,11 @@ internal sealed class TextMapPrototype : MapPrototype
{ {
var walls = new bool[MapService.PixelsPerRow, MapService.PixelsPerColumn]; var walls = new bool[MapService.PixelsPerRow, MapService.PixelsPerColumn];
for (ushort tileX = 0; tileX < MapService.TilesPerRow; tileX++) for (ulong tileX = 0; tileX < MapService.TilesPerRow; tileX++)
for (ushort tileY = 0; tileY < MapService.TilesPerColumn; tileY++) for (ulong tileY = 0; tileY < MapService.TilesPerColumn; tileY++)
{ {
var tile = new TilePosition(tileX, tileY); var tile = new TilePosition(tileX, tileY);
if (Text[tileX + tileY * MapService.TilesPerRow] != '#') if (Text[(int)(tileX + tileY * MapService.TilesPerRow)] != '#')
continue; continue;
for (byte pixelInTileX = 0; pixelInTileX < MapService.TileSize; pixelInTileX++) for (byte pixelInTileX = 0; pixelInTileX < MapService.TileSize; pixelInTileX++)

View file

@ -7,3 +7,4 @@ global using System.Threading.Tasks;
global using Microsoft.Extensions.Logging; global using Microsoft.Extensions.Logging;
global using Microsoft.Extensions.Options; global using Microsoft.Extensions.Options;
global using TanksServer.Models; global using TanksServer.Models;
global using ServicePoint;

View file

@ -1,4 +1,3 @@
using ServicePoint;
using TanksServer.GameLogic; using TanksServer.GameLogic;
namespace TanksServer.Graphics; namespace TanksServer.Graphics;
@ -9,8 +8,8 @@ internal sealed class DrawMapStep(MapService map) : IDrawStep
private static void Draw(GamePixelGrid pixels, Map map) private static void Draw(GamePixelGrid pixels, Map map)
{ {
for (ushort y = 0; y < MapService.PixelsPerColumn; y++) for (ulong y = 0; y < MapService.PixelsPerColumn; y++)
for (ushort x = 0; x < MapService.PixelsPerRow; x++) for (ulong x = 0; x < MapService.PixelsPerRow; x++)
{ {
if (!map.IsWall(x, y)) if (!map.IsWall(x, y))
continue; continue;
@ -21,12 +20,12 @@ internal sealed class DrawMapStep(MapService map) : IDrawStep
public static void Draw(Bitmap pixels, Map map) public static void Draw(Bitmap pixels, Map map)
{ {
for (ushort y = 0; y < MapService.PixelsPerColumn; y++) for (ulong y = 0; y < MapService.PixelsPerColumn; y++)
for (ushort x = 0; x < MapService.PixelsPerRow; x++) for (ulong x = 0; x < MapService.PixelsPerRow; x++)
{ {
if (!map.IsWall(x, y)) if (!map.IsWall(x, y))
continue; continue;
pixels[x, y] = true; pixels.Set(x, y, true);
} }
} }
} }

View file

@ -5,7 +5,7 @@ namespace TanksServer.Graphics;
internal sealed class DrawTanksStep(MapEntityManager entityManager) : IDrawStep internal sealed class DrawTanksStep(MapEntityManager entityManager) : IDrawStep
{ {
private readonly SpriteSheet _tankSprites = private readonly SpriteSheet _tankSprites =
SpriteSheet.FromImageFile("assets/tank.png", MapService.TileSize, MapService.TileSize); SpriteSheet.FromImageFile("assets/tank.png", (int)MapService.TileSize, (int)MapService.TileSize);
public void Draw(GamePixelGrid pixels) public void Draw(GamePixelGrid pixels)
{ {

View file

@ -5,27 +5,27 @@ namespace TanksServer.Graphics;
internal sealed class GamePixelGrid : IEnumerable<GamePixel> internal sealed class GamePixelGrid : IEnumerable<GamePixel>
{ {
public int Width { get; } public ulong Width { get; }
public int Height { get; } public ulong Height { get; }
private readonly GamePixel[,] _pixels; private readonly GamePixel[,] _pixels;
public GamePixelGrid(int width, int height) public GamePixelGrid(ulong width, ulong height)
{ {
Width = width; Width = width;
Height = height; Height = height;
_pixels = new GamePixel[width, height]; _pixels = new GamePixel[width, height];
for (var y = 0; y < height; y++) for (ulong y = 0; y < height; y++)
for (var x = 0; x < width; x++) for (ulong x = 0; x < width; x++)
this[x, y] = new GamePixel(); this[x, y] = new GamePixel();
} }
public GamePixel this[int x, int y] public GamePixel this[ulong x, ulong y]
{ {
get get
{ {
Debug.Assert(y * Width + x < _pixels.Length); Debug.Assert(y * Width + x < (ulong)_pixels.Length);
return _pixels[x, y]; return _pixels[x, y];
} }
set => _pixels[x, y] = value; set => _pixels[x, y] = value;
@ -41,8 +41,8 @@ internal sealed class GamePixelGrid : IEnumerable<GamePixel>
public IEnumerator<GamePixel> GetEnumerator() public IEnumerator<GamePixel> GetEnumerator()
{ {
for (var y = 0; y < Height; y++) for (ulong y = 0; y < Height; y++)
for (var x = 0; x < Width; x++) for (ulong x = 0; x < Width; x++)
yield return this[x, y]; yield return this[x, y];
} }
} }

View file

@ -1,4 +1,3 @@
using ServicePoint;
using TanksServer.GameLogic; using TanksServer.GameLogic;
using TanksServer.Interactivity; using TanksServer.Interactivity;
@ -20,7 +19,7 @@ internal sealed class GeneratePixelsTickStep(
public async ValueTask TickAsync(TimeSpan _) public async ValueTask TickAsync(TimeSpan _)
{ {
Draw(_gamePixelGrid, _observerPixelGrid); Draw(_gamePixelGrid, _observerPixelGrid);
if (_observerPixelGrid.Data.SequenceEqual(_lastObserverPixelGrid.Data)) if (_observerPixelGrid.Equals(_lastObserverPixelGrid))
return; return;
await _consumers.Select(c => c.OnFrameDoneAsync(_gamePixelGrid, _observerPixelGrid)) await _consumers.Select(c => c.OnFrameDoneAsync(_gamePixelGrid, _observerPixelGrid))
@ -37,11 +36,11 @@ internal sealed class GeneratePixelsTickStep(
step.Draw(gamePixelGrid); step.Draw(gamePixelGrid);
observerPixelGrid.Fill(false); observerPixelGrid.Fill(false);
for (var y = 0; y < MapService.PixelsPerColumn; y++) for (ulong y = 0; y < MapService.PixelsPerColumn; y++)
for (var x = 0; x < MapService.PixelsPerRow; x++) for (ulong x = 0; x < MapService.PixelsPerRow; x++)
{ {
if (gamePixelGrid[x, y].EntityType.HasValue) if (gamePixelGrid[x, y].EntityType.HasValue)
observerPixelGrid[(ushort)x, (ushort)y] = true; observerPixelGrid.Set(x, y, true);
} }
} }

View file

@ -1,5 +1,3 @@
using ServicePoint;
namespace TanksServer.Graphics; namespace TanksServer.Graphics;
internal interface IFrameConsumer internal interface IFrameConsumer

View file

@ -1,5 +1,4 @@
using System.Net.WebSockets; using System.Net.WebSockets;
using ServicePoint;
using TanksServer.Graphics; using TanksServer.Graphics;
namespace TanksServer.Interactivity; namespace TanksServer.Interactivity;

View file

@ -1,6 +1,5 @@
using System.Buffers; using System.Buffers;
using System.Net.WebSockets; using System.Net.WebSockets;
using ServicePoint;
using TanksServer.Graphics; using TanksServer.Graphics;
namespace TanksServer.Interactivity; namespace TanksServer.Interactivity;
@ -36,7 +35,7 @@ internal sealed class ClientScreenServerConnection
private Package BuildNextPackage(Bitmap pixels, GamePixelGrid gamePixelGrid) private Package BuildNextPackage(Bitmap pixels, GamePixelGrid gamePixelGrid)
{ {
var pixelsData = pixels.Data; var pixelsData = pixels.CopyRaw();
var nextPixels = _bufferPool.Rent(pixelsData.Length); var nextPixels = _bufferPool.Rent(pixelsData.Length);
pixelsData.CopyTo(nextPixels.Memory.Span); pixelsData.CopyTo(nextPixels.Memory.Span);

View file

@ -0,0 +1,42 @@
using Microsoft.Extensions.Hosting;
internal class LoggingLifecycleService(ILogger logger) : IHostedLifecycleService
{
private protected readonly ILogger Logger = logger;
public virtual Task StartAsync(CancellationToken cancellationToken)
{
Logger.LogDebug("StartAsync");
return Task.CompletedTask;
}
public virtual Task StartedAsync(CancellationToken cancellationToken)
{
Logger.LogDebug("StartedAsync");
return Task.CompletedTask;
}
public virtual Task StartingAsync(CancellationToken cancellationToken)
{
Logger.LogDebug("StartingAsync");
return Task.CompletedTask;
}
public virtual Task StopAsync(CancellationToken cancellationToken)
{
Logger.LogDebug("StopAsync");
return Task.CompletedTask;
}
public virtual Task StoppedAsync(CancellationToken cancellationToken)
{
Logger.LogDebug("StoppedAsync");
return Task.CompletedTask;
}
public virtual Task StoppingAsync(CancellationToken cancellationToken)
{
Logger.LogDebug("StoppingAsync");
return Task.CompletedTask;
}
}

View file

@ -1,7 +1,6 @@
using System.Diagnostics; using System.Diagnostics;
using System.Net; using System.Net;
using System.Net.Sockets; using System.Net.Sockets;
using ServicePoint;
using TanksServer.GameLogic; using TanksServer.GameLogic;
using TanksServer.Graphics; using TanksServer.Graphics;
@ -17,7 +16,7 @@ internal sealed class SendToServicePointDisplay : IFrameConsumer, IDisposable
private readonly MapService _mapService; private readonly MapService _mapService;
private readonly ILogger<SendToServicePointDisplay> _logger; private readonly ILogger<SendToServicePointDisplay> _logger;
private readonly PlayerServer _players; private readonly PlayerServer _players;
private readonly Cp437Grid _scoresBuffer; private readonly CharGrid _scoresBuffer;
private readonly TimeSpan _minFrameTime; private readonly TimeSpan _minFrameTime;
private readonly IOptionsMonitor<HostConfiguration> _options; private readonly IOptionsMonitor<HostConfiguration> _options;
@ -42,13 +41,13 @@ internal sealed class SendToServicePointDisplay : IFrameConsumer, IDisposable
var localIp = GetLocalIPv4(displayConfig.Value).Split('.'); var localIp = GetLocalIPv4(displayConfig.Value).Split('.');
Debug.Assert(localIp.Length == 4); Debug.Assert(localIp.Length == 4);
_scoresBuffer = new Cp437Grid(12, 20); _scoresBuffer = new CharGrid(ScoresWidth, ScoresHeight);
_scoresBuffer[00] = "== TANKS! =="; _scoresBuffer.SetRow(00, "== TANKS! ==");
_scoresBuffer[01] = "-- scores --"; _scoresBuffer.SetRow(01, "-- scores --");
_scoresBuffer[17] = "-- join --"; _scoresBuffer.SetRow(17, "-- join --");
_scoresBuffer[18] = string.Join('.', localIp[..2]); _scoresBuffer.SetRow(18, $"{localIp[0]}.{localIp[1]}".PadRight(ScoresWidth));
_scoresBuffer[19] = string.Join('.', localIp[2..]); _scoresBuffer.SetRow(19, $"{localIp[2]}.{localIp[3]}".PadRight(ScoresWidth));
} }
public async Task OnFrameDoneAsync(GamePixelGrid gamePixelGrid, Bitmap observerPixels) public async Task OnFrameDoneAsync(GamePixelGrid gamePixelGrid, Bitmap observerPixels)
@ -66,9 +65,8 @@ internal sealed class SendToServicePointDisplay : IFrameConsumer, IDisposable
try try
{ {
_displayConnection.Send(Command.BitmapLinearWin(0, 0, observerPixels.Clone(), CompressionCode.Lzma) _displayConnection.Send(Command.BitmapLinearWin(0, 0, observerPixels, CompressionCode.Lzma));
.IntoPacket()); _displayConnection.Send(Command.Cp437Data(MapService.TilesPerRow, 0, _scoresBuffer.ToCp437()));
_displayConnection.Send(Command.Cp437Data(MapService.TilesPerRow, 0, _scoresBuffer.Clone()).IntoPacket());
} }
catch (SocketException ex) catch (SocketException ex)
{ {
@ -95,14 +93,14 @@ internal sealed class SendToServicePointDisplay : IFrameConsumer, IDisposable
var name = p.Name[..nameLength]; var name = p.Name[..nameLength];
var spaces = new string(' ', ScoresWidth - score.Length - nameLength); var spaces = new string(' ', ScoresWidth - score.Length - nameLength);
_scoresBuffer[row] = name + spaces + score; _scoresBuffer.SetRow(row, name + spaces + score);
row++; row++;
} }
for (; row < 16; row++) for (; row < 16; row++)
_scoresBuffer[row] = string.Empty; _scoresBuffer.SetRow(row, new string(' ', ScoresWidth));
_scoresBuffer[16] = _mapService.Current.Name[..(Math.Min(ScoresWidth, _mapService.Current.Name.Length) - 1)]; _scoresBuffer.SetRow(16, _mapService.Current.Name[..(Math.Min(ScoresWidth, _mapService.Current.Name.Length) - 1)].PadRight(ScoresWidth));
} }
private static string GetLocalIPv4(DisplayConfiguration configuration) private static string GetLocalIPv4(DisplayConfiguration configuration)

View file

@ -1,23 +1,23 @@
using System.Diagnostics; using System.Diagnostics;
using Microsoft.Extensions.Hosting;
namespace TanksServer.Interactivity; namespace TanksServer.Interactivity;
internal abstract class WebsocketServer<T>( internal abstract class WebsocketServer<T>(
ILogger logger ILogger logger
) : IHostedLifecycleService ) : LoggingLifecycleService(logger)
where T : WebsocketServerConnection where T : WebsocketServerConnection
{ {
private bool _closing; private bool _closing;
private readonly ConcurrentDictionary<T, byte> _connections = []; private readonly ConcurrentDictionary<T, byte> _connections = [];
public async Task StoppingAsync(CancellationToken cancellationToken) public async override Task StoppingAsync(CancellationToken cancellationToken)
{ {
await base.StoppingAsync(cancellationToken);
_closing = true; _closing = true;
logger.LogInformation("closing connections"); Logger.LogInformation("closing connections");
await _connections.Keys.Select(c => c.CloseAsync()) await _connections.Keys.Select(c => c.CloseAsync())
.WhenAll(); .WhenAll();
logger.LogInformation("closed connections"); Logger.LogInformation("closed connections");
} }
protected IEnumerable<T> Connections => _connections.Keys; protected IEnumerable<T> Connections => _connections.Keys;
@ -26,7 +26,7 @@ internal abstract class WebsocketServer<T>(
{ {
if (_closing) if (_closing)
{ {
logger.LogWarning("refusing connection because server is shutting down"); Logger.LogWarning("refusing connection because server is shutting down");
await connection.CloseAsync(); await connection.CloseAsync();
return; return;
} }
@ -39,14 +39,4 @@ internal abstract class WebsocketServer<T>(
_ = _connections.TryRemove(connection, out _); _ = _connections.TryRemove(connection, out _);
connection.Dispose(); connection.Dispose();
} }
public Task StartAsync(CancellationToken cancellationToken) => Task.CompletedTask;
public Task StopAsync(CancellationToken cancellationToken) => Task.CompletedTask;
public Task StartedAsync(CancellationToken cancellationToken) => Task.CompletedTask;
public Task StartingAsync(CancellationToken cancellationToken) => Task.CompletedTask;
public Task StoppedAsync(CancellationToken cancellationToken) => Task.CompletedTask;
} }

View file

@ -4,12 +4,12 @@ using TanksServer.GameLogic;
namespace TanksServer.Models; namespace TanksServer.Models;
[DebuggerDisplay("({X} | {Y})")] [DebuggerDisplay("({X} | {Y})")]
internal readonly struct PixelPosition(int x, int y) internal readonly struct PixelPosition(ulong x, ulong y)
{ {
public int X { get; } = (x + MapService.PixelsPerRow) % MapService.PixelsPerRow; public ulong X { get; } = (x + MapService.PixelsPerRow) % MapService.PixelsPerRow;
public int Y { get; } = (y + MapService.PixelsPerColumn) % MapService.PixelsPerColumn; public ulong Y { get; } = (y + MapService.PixelsPerColumn) % MapService.PixelsPerColumn;
public void Deconstruct(out int x, out int y) public void Deconstruct(out ulong x, out ulong y)
{ {
x = X; x = X;
y = Y; y = Y;

View file

@ -4,20 +4,20 @@ namespace TanksServer.Models;
internal static class PositionHelpers internal static class PositionHelpers
{ {
public static PixelPosition GetPixelRelative(this PixelPosition position, short subX, short subY) public static PixelPosition GetPixelRelative(this PixelPosition position, long subX, long subY)
=> new(position.X + subX, position.Y + subY); => new((ulong)((long)position.X + subX), (ulong)((long)position.Y + subY));
public static PixelPosition ToPixelPosition(this FloatPosition position) public static PixelPosition ToPixelPosition(this FloatPosition position)
=> new((int)Math.Round(position.X), (int)Math.Round(position.Y)); => new((ulong)Math.Round(position.X), (ulong)Math.Round(position.Y));
public static PixelPosition ToPixelPosition(this TilePosition position) => new( public static PixelPosition ToPixelPosition(this TilePosition position) => new(
(ushort)(position.X * MapService.TileSize), (ulong)(position.X * MapService.TileSize),
(ushort)(position.Y * MapService.TileSize) (ulong)(position.Y * MapService.TileSize)
); );
public static TilePosition ToTilePosition(this PixelPosition position) => new( public static TilePosition ToTilePosition(this PixelPosition position) => new(
(ushort)(position.X / MapService.TileSize), (ulong)(position.X / MapService.TileSize),
(ushort)(position.Y / MapService.TileSize) (ulong)(position.Y / MapService.TileSize)
); );
public static FloatPosition ToFloatPosition(this PixelPosition position) => new(position.X, position.Y); public static FloatPosition ToFloatPosition(this PixelPosition position) => new(position.X, position.Y);
@ -28,10 +28,10 @@ internal static class PositionHelpers
Math.Pow(p1.Y - p2.Y, 2) Math.Pow(p1.Y - p2.Y, 2)
); );
public static PixelBounds GetBoundsForCenter(this FloatPosition position, ushort size) public static PixelBounds GetBoundsForCenter(this FloatPosition position, ulong size)
{ {
var sub = (short)(-size / 2d); var sub = (long)(-(long)size / 2d);
var add = (short)(size / 2d - 1); var add = (long)(size / 2d - 1);
var pixelPosition = position.ToPixelPosition(); var pixelPosition = position.ToPixelPosition();
return new PixelBounds( return new PixelBounds(
pixelPosition.GetPixelRelative(sub, sub), pixelPosition.GetPixelRelative(sub, sub),

View file

@ -10,7 +10,8 @@ internal sealed class Tank(Player owner, FloatPosition position) : IMapEntity
[JsonIgnore] public Player Owner { get; } = owner; [JsonIgnore] public Player Owner { get; } = owner;
[JsonIgnore] public double Rotation [JsonIgnore]
public double Rotation
{ {
get => _rotation; get => _rotation;
set set

View file

@ -4,8 +4,8 @@ using TanksServer.GameLogic;
namespace TanksServer.Models; namespace TanksServer.Models;
[DebuggerDisplay("({X} | {Y})")] [DebuggerDisplay("({X} | {Y})")]
internal readonly struct TilePosition(ushort x, ushort y) internal readonly struct TilePosition(ulong x, ulong y)
{ {
public ushort X { get; } = (ushort)((x + MapService.TilesPerRow) % MapService.TilesPerRow); public ulong X { get; } = (ulong)((x + MapService.TilesPerRow) % MapService.TilesPerRow);
public ushort Y { get; } = (ushort)((y + MapService.TilesPerColumn) % MapService.TilesPerColumn); public ulong Y { get; } = (ulong)((y + MapService.TilesPerColumn) % MapService.TilesPerColumn);
} }

View file

@ -1,8 +1,8 @@
using System.Diagnostics.CodeAnalysis;
using System.IO; using System.IO;
using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Builder;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.FileProviders; using Microsoft.Extensions.FileProviders;
using ServicePoint;
using TanksServer.GameLogic; using TanksServer.GameLogic;
using TanksServer.Graphics; using TanksServer.Graphics;
using TanksServer.Interactivity; using TanksServer.Interactivity;
@ -11,6 +11,8 @@ namespace TanksServer;
public static class Program public static class Program
{ {
[RequiresUnreferencedCode("Calls Endpoints.Map")]
[RequiresDynamicCode("Calls Endpoints.Map")]
public static async Task Main(string[] args) public static async Task Main(string[] args)
{ {
var app = Configure(args); var app = Configure(args);
@ -25,6 +27,8 @@ public static class Program
await app.RunAsync(); await app.RunAsync();
} }
[RequiresUnreferencedCode("Calls Microsoft.Extensions.DependencyInjection.OptionsConfigurationServiceCollectionExtensions.Configure<TOptions>(IConfiguration)")]
[RequiresDynamicCode("Calls Microsoft.Extensions.DependencyInjection.OptionsConfigurationServiceCollectionExtensions.Configure<TOptions>(IConfiguration)")]
private static WebApplication Configure(string[] args) private static WebApplication Configure(string[] args)
{ {
var builder = WebApplication.CreateSlimBuilder(args); var builder = WebApplication.CreateSlimBuilder(args);
@ -43,10 +47,7 @@ public static class Program
.AllowAnyOrigin()) .AllowAnyOrigin())
); );
builder.Services.ConfigureHttpJsonOptions(options => builder.Services.ConfigureHttpJsonOptions(options => options.SerializerOptions.TypeInfoResolverChain.Insert(0, new AppSerializerContext()));
{
options.SerializerOptions.TypeInfoResolverChain.Insert(0, new AppSerializerContext());
});
builder.Services.AddHttpLogging(_ => { }); builder.Services.AddHttpLogging(_ => { });
@ -66,8 +67,8 @@ public static class Program
builder.Services.AddSingleton<UpdatesPerSecondCounter>(); builder.Services.AddSingleton<UpdatesPerSecondCounter>();
builder.Services.AddHostedService<GameTickWorker>(); builder.Services.AddHostedService<GameTickWorker>();
builder.Services.AddHostedService(sp => sp.GetRequiredService<ControlsServer>()); builder.Services.AddHostedService(FromServices<ControlsServer>);
builder.Services.AddHostedService(sp => sp.GetRequiredService<ClientScreenServer>()); builder.Services.AddHostedService(FromServices<ClientScreenServer>);
builder.Services.AddSingleton<ITickStep, ChangeToRequestedMap>(sp => builder.Services.AddSingleton<ITickStep, ChangeToRequestedMap>(sp =>
sp.GetRequiredService<ChangeToRequestedMap>()); sp.GetRequiredService<ChangeToRequestedMap>());
@ -100,10 +101,7 @@ public static class Program
builder.Services.AddSingleton<Connection>(sp => builder.Services.AddSingleton<Connection>(sp =>
{ {
var config = sp.GetRequiredService<IOptions<DisplayConfiguration>>().Value; var config = sp.GetRequiredService<IOptions<DisplayConfiguration>>().Value;
var connection = Connection.Open($"{config.Hostname}:{config.Port}"); var connection = new Connection($"{config.Hostname}:{config.Port}");
if (connection == null)
throw new IOException($"Could not open connection to {config.Hostname}:{config.Port}");
return connection; return connection;
}); });
@ -115,4 +113,6 @@ public static class Program
return app; return app;
} }
private static T FromServices<T>(IServiceProvider sp) where T : notnull => sp.GetRequiredService<T>();
} }

View file

@ -29,7 +29,7 @@
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\servicepoint\crates\servicepoint_binding_cs\ServicePoint\ServicePoint.csproj" /> <ProjectReference Include="..\servicepoint\crates\servicepoint_binding_uniffi\libraries\csharp\ServicePoint\ServicePoint.csproj" />
</ItemGroup> </ItemGroup>
</Project> </Project>

@ -1 +1 @@
Subproject commit f968f929173c6646acb7d9a3ed19112e626732b3 Subproject commit 93657c9f85d021c04270e1f5af573a6cda51f18a