Update .gitignore to exclude TypeScript build info and add dist directory. Expand README with project overview, technology stack, prerequisites, and instructions for running and testing the application.
This commit is contained in:
@@ -0,0 +1,88 @@
|
||||
using System.Net.WebSockets;
|
||||
using HSchool.Server.Game;
|
||||
using HSchool.Server.Net;
|
||||
using HSchool.Simulation;
|
||||
|
||||
var builder = WebApplication.CreateBuilder(args);
|
||||
|
||||
builder.AddServiceDefaults();
|
||||
builder.Services.AddProblemDetails();
|
||||
builder.Services.AddOpenApi();
|
||||
|
||||
builder.Services
|
||||
.AddOptions<SimulationOptions>()
|
||||
.Bind(builder.Configuration.GetSection(SimulationOptions.SectionName))
|
||||
.Validate(options => options.TickRate is > 0 and <= 120, "Simulation:TickRate must be between 1 and 120.")
|
||||
.Validate(options => options.WorldWidth > 0 && options.WorldHeight > 0, "World size must be positive.")
|
||||
.ValidateOnStart();
|
||||
|
||||
builder.Services.AddSingleton<GameCommandQueue>();
|
||||
builder.Services.AddSingleton<ClientRegistry>();
|
||||
builder.Services.AddSingleton<GameMetrics>();
|
||||
builder.Services.AddSingleton<GameSocketHandler>();
|
||||
builder.Services.AddSingleton<GameLoopService>();
|
||||
builder.Services.AddHostedService(sp => sp.GetRequiredService<GameLoopService>());
|
||||
|
||||
builder.Services.AddOpenTelemetry().WithMetrics(metrics => metrics.AddMeter(GameMetrics.MeterName));
|
||||
|
||||
var app = builder.Build();
|
||||
|
||||
app.UseExceptionHandler();
|
||||
|
||||
if (app.Environment.IsDevelopment())
|
||||
{
|
||||
app.MapOpenApi();
|
||||
}
|
||||
|
||||
app.UseWebSockets(new WebSocketOptions
|
||||
{
|
||||
KeepAliveInterval = TimeSpan.FromSeconds(30),
|
||||
});
|
||||
|
||||
var api = app.MapGroup("/api");
|
||||
|
||||
api.MapGet("/status", (GameLoopService loop, ClientRegistry clients) =>
|
||||
{
|
||||
var options = loop.Options;
|
||||
return new GameStatusResponse(
|
||||
loop.CurrentTick,
|
||||
options.TickRate,
|
||||
loop.PlayerCount,
|
||||
clients.Count,
|
||||
options.WorldWidth,
|
||||
options.WorldHeight);
|
||||
})
|
||||
.WithName("GetGameStatus");
|
||||
|
||||
// The realtime channel: one binary frame per protocol message, see docs/protocol.md.
|
||||
app.Map("/ws/game", async (HttpContext context, GameSocketHandler handler) =>
|
||||
{
|
||||
if (!context.WebSockets.IsWebSocketRequest)
|
||||
{
|
||||
context.Response.StatusCode = StatusCodes.Status400BadRequest;
|
||||
await context.Response.WriteAsync("This endpoint expects a WebSocket upgrade.");
|
||||
return;
|
||||
}
|
||||
|
||||
using WebSocket socket = await context.WebSockets.AcceptWebSocketAsync();
|
||||
await handler.HandleAsync(socket, context.RequestAborted);
|
||||
});
|
||||
|
||||
app.MapDefaultEndpoints();
|
||||
|
||||
// In a published container the built client lands in wwwroot next to the server.
|
||||
app.UseFileServer();
|
||||
|
||||
app.Run();
|
||||
|
||||
/// <summary>Snapshot of loop health for dashboards and integration tests.</summary>
|
||||
internal sealed record GameStatusResponse(
|
||||
uint Tick,
|
||||
int TickRate,
|
||||
int Players,
|
||||
int Connections,
|
||||
float WorldWidth,
|
||||
float WorldHeight);
|
||||
|
||||
/// <summary>Exposed so <c>WebApplicationFactory</c>-style tests can reference the entry point.</summary>
|
||||
public partial class Program;
|
||||
Reference in New Issue
Block a user