Merge branch 'uniffi'
This commit is contained in:
		
						commit
						ded0293b13
					
				
					 37 changed files with 270 additions and 234 deletions
				
			
		
							
								
								
									
										2
									
								
								.envrc
									
										
									
									
									
								
							
							
						
						
									
										2
									
								
								.envrc
									
										
									
									
									
								
							|  | @ -1 +1 @@ | ||||||
| use nix | use flake | ||||||
|  |  | ||||||
							
								
								
									
										6
									
								
								flake.lock
									
										
									
										generated
									
									
									
								
							
							
						
						
									
										6
									
								
								flake.lock
									
										
									
										generated
									
									
									
								
							|  | @ -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…
	
	Add table
		Add a link
		
	
		Reference in a new issue
	
	 Vinzenz Schroeter
						Vinzenz Schroeter