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.
ci / server (push) Failing after 4m10s
ci / client (push) Successful in 17s

This commit is contained in:
Leonid Pershin
2026-08-18 11:11:48 +03:00
parent 84aafb0b69
commit e6739e7912
84 changed files with 5698 additions and 2 deletions
+58
View File
@@ -0,0 +1,58 @@
using System.Net.WebSockets;
using System.Threading.Channels;
namespace HSchool.Server.Net;
/// <summary>
/// One connected browser. Frames are queued instead of written inline so a slow client
/// can never stall the game loop; when the outbox overflows the oldest snapshot is dropped,
/// which is exactly what you want for state that is resent 20 times a second.
/// </summary>
internal sealed class GameClient(uint playerId, WebSocket socket)
{
private const int OutboxCapacity = 32;
private readonly Channel<ReadOnlyMemory<byte>> _outbox =
Channel.CreateBounded<ReadOnlyMemory<byte>>(new BoundedChannelOptions(OutboxCapacity)
{
FullMode = BoundedChannelFullMode.DropOldest,
SingleReader = true,
SingleWriter = false,
});
private bool _ready;
public uint PlayerId { get; } = playerId;
public WebSocket Socket { get; } = socket;
public string Name { get; set; } = $"player-{playerId}";
/// <summary>
/// Set once the welcome frame is out. Snapshots are only queued for ready clients, so a
/// connection never sees world state before it knows its own entity id.
/// </summary>
public bool IsReady => Volatile.Read(ref _ready);
public void MarkReady() => Volatile.Write(ref _ready, true);
/// <summary>Queues a frame. Returns false once the connection is shutting down.</summary>
public bool TrySend(ReadOnlyMemory<byte> frame) => _outbox.Writer.TryWrite(frame);
/// <summary>Pumps queued frames to the socket until cancelled or the outbox completes.</summary>
public async Task RunSendLoopAsync(CancellationToken cancellationToken)
{
await foreach (var frame in _outbox.Reader.ReadAllAsync(cancellationToken).ConfigureAwait(false))
{
if (Socket.State != WebSocketState.Open)
{
break;
}
await Socket.SendAsync(frame, WebSocketMessageType.Binary, endOfMessage: true, cancellationToken)
.ConfigureAwait(false);
}
}
public void CompleteOutbox() => _outbox.Writer.TryComplete();
}