Merge branch 'uniffi'
This commit is contained in:
commit
ded0293b13
|
@ -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": {
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -88,13 +88,13 @@ internal sealed class CollideBullets : ITickStep
|
||||||
|
|
||||||
pixel = pixel.GetPixelRelative(-4, -4);
|
pixel = pixel.GetPixelRelative(-4, -4);
|
||||||
for (short dx = 0; dx < _explosiveSprite.Width; dx++)
|
for (short dx = 0; dx < _explosiveSprite.Width; dx++)
|
||||||
for (short dy = 0; dy < _explosiveSprite.Height; dy++)
|
for (short dy = 0; dy < _explosiveSprite.Height; dy++)
|
||||||
{
|
{
|
||||||
if (!_explosiveSprite[dx, dy].HasValue)
|
if (!_explosiveSprite[dx, dy].HasValue)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
Core(pixel.GetPixelRelative(dx, dy));
|
Core(pixel.GetPixelRelative(dx, dy));
|
||||||
}
|
}
|
||||||
|
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
|
|
@ -10,22 +10,22 @@ internal sealed class EmptyTileFinder(
|
||||||
var maxMinDistance = 0d;
|
var maxMinDistance = 0d;
|
||||||
TilePosition spawnTile = default;
|
TilePosition spawnTile = default;
|
||||||
for (ushort x = 1; x < MapService.TilesPerRow - 1; x++)
|
for (ushort x = 1; x < MapService.TilesPerRow - 1; x++)
|
||||||
for (ushort y = 1; y < MapService.TilesPerColumn - 1; y++)
|
for (ushort y = 1; y < MapService.TilesPerColumn - 1; y++)
|
||||||
{
|
{
|
||||||
var tile = new TilePosition(x, y);
|
var tile = new TilePosition(x, y);
|
||||||
if (mapService.Current.IsWall(tile))
|
if (mapService.Current.IsWall(tile))
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
var tilePixelCenter = tile.GetCenter().ToFloatPosition();
|
var tilePixelCenter = tile.GetCenter().ToFloatPosition();
|
||||||
var minDistance = entityManager.AllEntities
|
var minDistance = entityManager.AllEntities
|
||||||
.Select(entity => entity.Position.Distance(tilePixelCenter))
|
.Select(entity => entity.Position.Distance(tilePixelCenter))
|
||||||
.Aggregate(double.MaxValue, Math.Min);
|
.Aggregate(double.MaxValue, Math.Min);
|
||||||
if (minDistance <= maxMinDistance)
|
if (minDistance <= maxMinDistance)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
maxMinDistance = minDistance;
|
maxMinDistance = minDistance;
|
||||||
spawnTile = tile;
|
spawnTile = tile;
|
||||||
}
|
}
|
||||||
|
|
||||||
return spawnTile;
|
return spawnTile;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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,12 +12,12 @@ 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;
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
|
@ -37,7 +37,7 @@ internal sealed class MapEntityManager(
|
||||||
{
|
{
|
||||||
Rotation = Random.Shared.NextDouble(),
|
Rotation = Random.Shared.NextDouble(),
|
||||||
MaxBullets = _rules.MagazineSize,
|
MaxBullets = _rules.MagazineSize,
|
||||||
BulletStats =new BulletStats(_rules.BulletSpeed, 0, false, false)
|
BulletStats = new BulletStats(_rules.BulletSpeed, 0, false, false)
|
||||||
};
|
};
|
||||||
_playerTanks[player] = tank;
|
_playerTanks[player] = tank;
|
||||||
logger.LogInformation("Tank added for player {}", player.Name);
|
logger.LogInformation("Tank added for player {}", player.Name);
|
||||||
|
|
|
@ -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();
|
||||||
|
|
|
@ -68,13 +68,13 @@ 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;
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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,20 +19,20 @@ 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);
|
|
||||||
if (Text[tileX + tileY * MapService.TilesPerRow] != '#')
|
|
||||||
continue;
|
|
||||||
|
|
||||||
for (byte pixelInTileX = 0; pixelInTileX < MapService.TileSize; pixelInTileX++)
|
|
||||||
for (byte pixelInTileY = 0; pixelInTileY < MapService.TileSize; pixelInTileY++)
|
|
||||||
{
|
{
|
||||||
var (x, y) = tile.ToPixelPosition().GetPixelRelative(pixelInTileX, pixelInTileY);
|
var tile = new TilePosition(tileX, tileY);
|
||||||
walls[x, y] = true;
|
if (Text[(int)(tileX + tileY * MapService.TilesPerRow)] != '#')
|
||||||
|
continue;
|
||||||
|
|
||||||
|
for (byte pixelInTileX = 0; pixelInTileX < MapService.TileSize; pixelInTileX++)
|
||||||
|
for (byte pixelInTileY = 0; pixelInTileY < MapService.TileSize; pixelInTileY++)
|
||||||
|
{
|
||||||
|
var (x, y) = tile.ToPixelPosition().GetPixelRelative(pixelInTileX, pixelInTileY);
|
||||||
|
walls[x, y] = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
return new Map(Name, walls);
|
return new Map(Name, walls);
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
using ServicePoint;
|
|
||||||
using TanksServer.GameLogic;
|
using TanksServer.GameLogic;
|
||||||
|
|
||||||
namespace TanksServer.Graphics;
|
namespace TanksServer.Graphics;
|
||||||
|
@ -9,24 +8,24 @@ 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;
|
||||||
|
|
||||||
pixels[x, y].EntityType = GamePixelEntityType.Wall;
|
pixels[x, y].EntityType = GamePixelEntityType.Wall;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -30,16 +30,16 @@ internal sealed class DrawPowerUpsStep(MapEntityManager entityManager) : IDrawSt
|
||||||
private static void DrawPowerUp(GamePixelGrid pixels, Sprite sprite, PixelPosition position)
|
private static void DrawPowerUp(GamePixelGrid pixels, Sprite sprite, PixelPosition position)
|
||||||
{
|
{
|
||||||
for (byte dy = 0; dy < MapService.TileSize; dy++)
|
for (byte dy = 0; dy < MapService.TileSize; dy++)
|
||||||
for (byte dx = 0; dx < MapService.TileSize; dx++)
|
for (byte dx = 0; dx < MapService.TileSize; dx++)
|
||||||
{
|
{
|
||||||
var pixelState = sprite[dx, dy];
|
var pixelState = sprite[dx, dy];
|
||||||
if (!pixelState.HasValue)
|
if (!pixelState.HasValue)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
var (x, y) = position.GetPixelRelative(dx, dy);
|
var (x, y) = position.GetPixelRelative(dx, dy);
|
||||||
pixels[x, y].EntityType = pixelState.Value
|
pixels[x, y].EntityType = pixelState.Value
|
||||||
? GamePixelEntityType.PowerUp
|
? GamePixelEntityType.PowerUp
|
||||||
: null;
|
: null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)
|
||||||
{
|
{
|
||||||
|
@ -14,16 +14,16 @@ internal sealed class DrawTanksStep(MapEntityManager entityManager) : IDrawStep
|
||||||
var tankPosition = tank.Bounds.TopLeft;
|
var tankPosition = tank.Bounds.TopLeft;
|
||||||
|
|
||||||
for (byte dy = 0; dy < MapService.TileSize; dy++)
|
for (byte dy = 0; dy < MapService.TileSize; dy++)
|
||||||
for (byte dx = 0; dx < MapService.TileSize; dx++)
|
for (byte dx = 0; dx < MapService.TileSize; dx++)
|
||||||
{
|
{
|
||||||
var pixel = _tankSprites[tank.Orientation][dx, dy];
|
var pixel = _tankSprites[tank.Orientation][dx, dy];
|
||||||
if (!pixel.HasValue || !pixel.Value)
|
if (!pixel.HasValue || !pixel.Value)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
var (x, y) = tankPosition.GetPixelRelative(dx, dy);
|
var (x, y) = tankPosition.GetPixelRelative(dx, dy);
|
||||||
pixels[x, y].EntityType = GamePixelEntityType.Tank;
|
pixels[x, y].EntityType = GamePixelEntityType.Tank;
|
||||||
pixels[x, y].BelongsTo = tank.Owner;
|
pixels[x, y].BelongsTo = tank.Owner;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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,12 +36,12 @@ 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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
|
|
|
@ -1,5 +1,3 @@
|
||||||
using ServicePoint;
|
|
||||||
|
|
||||||
namespace TanksServer.Graphics;
|
namespace TanksServer.Graphics;
|
||||||
|
|
||||||
internal interface IFrameConsumer
|
internal interface IFrameConsumer
|
||||||
|
|
|
@ -12,13 +12,13 @@ internal sealed class Sprite(bool?[,] data)
|
||||||
|
|
||||||
var whitePixel = new Rgba32(byte.MaxValue, byte.MaxValue, byte.MaxValue, byte.MaxValue);
|
var whitePixel = new Rgba32(byte.MaxValue, byte.MaxValue, byte.MaxValue, byte.MaxValue);
|
||||||
for (var y = 0; y < image.Height; y++)
|
for (var y = 0; y < image.Height; y++)
|
||||||
for (var x = 0; x < image.Width; x++)
|
for (var x = 0; x < image.Width; x++)
|
||||||
{
|
{
|
||||||
var pixelValue = image[x, y];
|
var pixelValue = image[x, y];
|
||||||
data[x, y] = pixelValue.A == 0
|
data[x, y] = pixelValue.A == 0
|
||||||
? null
|
? null
|
||||||
: pixelValue == whitePixel;
|
: pixelValue == whitePixel;
|
||||||
}
|
}
|
||||||
|
|
||||||
return new Sprite(data);
|
return new Sprite(data);
|
||||||
}
|
}
|
||||||
|
@ -34,8 +34,8 @@ internal sealed class Sprite(bool?[,] data)
|
||||||
var result = new bool[Width, Height];
|
var result = new bool[Width, Height];
|
||||||
|
|
||||||
for (var y = 0; y < Height; y++)
|
for (var y = 0; y < Height; y++)
|
||||||
for (var x = 0; x < Width; x++)
|
for (var x = 0; x < Width; x++)
|
||||||
result[x, y] = this[x, y] ?? false;
|
result[x, y] = this[x, y] ?? false;
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,24 +21,24 @@ internal sealed class SpriteSheet(Sprite[,] sprites)
|
||||||
|
|
||||||
var whitePixel = new Rgba32(byte.MaxValue, byte.MaxValue, byte.MaxValue, byte.MaxValue);
|
var whitePixel = new Rgba32(byte.MaxValue, byte.MaxValue, byte.MaxValue, byte.MaxValue);
|
||||||
for (var spriteY = 0; spriteY < spritesPerColumn; spriteY++)
|
for (var spriteY = 0; spriteY < spritesPerColumn; spriteY++)
|
||||||
for (var spriteX = 0; spriteX < spritesPerRow; spriteX++)
|
for (var spriteX = 0; spriteX < spritesPerRow; spriteX++)
|
||||||
{
|
|
||||||
var data = new bool?[spriteWidth, spriteHeight];
|
|
||||||
for (var dy = 0; dy < spriteHeight; dy++)
|
|
||||||
for (var dx = 0; dx < spriteWidth; dx++)
|
|
||||||
{
|
{
|
||||||
var x = spriteX * spriteWidth + dx;
|
var data = new bool?[spriteWidth, spriteHeight];
|
||||||
var y = spriteY * spriteHeight + dy;
|
for (var dy = 0; dy < spriteHeight; dy++)
|
||||||
|
for (var dx = 0; dx < spriteWidth; dx++)
|
||||||
|
{
|
||||||
|
var x = spriteX * spriteWidth + dx;
|
||||||
|
var y = spriteY * spriteHeight + dy;
|
||||||
|
|
||||||
var pixelValue = image[x, y];
|
var pixelValue = image[x, y];
|
||||||
data[dx, dy] = pixelValue.A == 0
|
data[dx, dy] = pixelValue.A == 0
|
||||||
? null
|
? null
|
||||||
: pixelValue == whitePixel;
|
: pixelValue == whitePixel;
|
||||||
|
}
|
||||||
|
|
||||||
|
sprites[spriteX, spriteY] = new Sprite(data);
|
||||||
}
|
}
|
||||||
|
|
||||||
sprites[spriteX, spriteY] = new Sprite(data);
|
|
||||||
}
|
|
||||||
|
|
||||||
return new SpriteSheet(sprites);
|
return new SpriteSheet(sprites);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -2,13 +2,13 @@ using System.Buffers;
|
||||||
|
|
||||||
namespace TanksServer.Interactivity;
|
namespace TanksServer.Interactivity;
|
||||||
|
|
||||||
internal sealed class BufferPool: MemoryPool<byte>
|
internal sealed class BufferPool : MemoryPool<byte>
|
||||||
{
|
{
|
||||||
private readonly MemoryPool<byte> _actualPool = Shared;
|
private readonly MemoryPool<byte> _actualPool = Shared;
|
||||||
|
|
||||||
public override int MaxBufferSize => int.MaxValue;
|
public override int MaxBufferSize => int.MaxValue;
|
||||||
|
|
||||||
protected override void Dispose(bool disposing) {}
|
protected override void Dispose(bool disposing) { }
|
||||||
|
|
||||||
public override IMemoryOwner<byte> Rent(int minBufferSize = -1)
|
public override IMemoryOwner<byte> Rent(int minBufferSize = -1)
|
||||||
{
|
{
|
||||||
|
@ -16,7 +16,7 @@ internal sealed class BufferPool: MemoryPool<byte>
|
||||||
return new BufferPoolMemoryOwner(_actualPool.Rent(minBufferSize), minBufferSize);
|
return new BufferPoolMemoryOwner(_actualPool.Rent(minBufferSize), minBufferSize);
|
||||||
}
|
}
|
||||||
|
|
||||||
private sealed class BufferPoolMemoryOwner(IMemoryOwner<byte> actualOwner, int wantedSize): IMemoryOwner<byte>
|
private sealed class BufferPoolMemoryOwner(IMemoryOwner<byte> actualOwner, int wantedSize) : IMemoryOwner<byte>
|
||||||
{
|
{
|
||||||
public Memory<byte> Memory { get; } = actualOwner.Memory[..wantedSize];
|
public Memory<byte> Memory { get; } = actualOwner.Memory[..wantedSize];
|
||||||
|
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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);
|
||||||
|
|
||||||
|
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -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)
|
||||||
|
|
|
@ -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;
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,7 +3,7 @@ namespace TanksServer.Interactivity;
|
||||||
internal abstract class WebsocketServerConnection(
|
internal abstract class WebsocketServerConnection(
|
||||||
ILogger logger,
|
ILogger logger,
|
||||||
ByteChannelWebSocket socket
|
ByteChannelWebSocket socket
|
||||||
): IDisposable
|
) : IDisposable
|
||||||
{
|
{
|
||||||
protected readonly ByteChannelWebSocket Socket = socket;
|
protected readonly ByteChannelWebSocket Socket = socket;
|
||||||
protected readonly ILogger Logger = logger;
|
protected readonly ILogger Logger = logger;
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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),
|
||||||
|
|
|
@ -11,7 +11,7 @@ internal enum PowerUpType
|
||||||
SmartBullets,
|
SmartBullets,
|
||||||
}
|
}
|
||||||
|
|
||||||
internal sealed class PowerUp: IMapEntity
|
internal sealed class PowerUp : IMapEntity
|
||||||
{
|
{
|
||||||
public required FloatPosition Position { get; init; }
|
public required FloatPosition Position { get; init; }
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
|
@ -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>();
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
Loading…
Reference in a new issue