Enhance school management and protocol handling by introducing a new worker failure mechanism, ensuring proper cleanup of schools when a worker thread encounters an error. Update the map snapshot protocol to allow larger school data sizes, improving the handling of extensive map layouts. Revise documentation to reflect changes in save intervals and introduce a minimum save interval for better performance. Update tests to validate the new functionalities and ensure robustness in handling large data scenarios.
This commit is contained in:
@@ -80,6 +80,13 @@ internal sealed class GameClient(uint playerId, WebSocket socket)
|
||||
var reliable = _reliable.Reader;
|
||||
var outbox = _outbox.Reader;
|
||||
|
||||
// The two waits live across iterations on purpose. Creating a fresh pair every idle pass
|
||||
// left the loser of Task.WhenAny queued on its channel forever — and because clock frames
|
||||
// arrive twenty times a second, the reliable channel collected one dead waiter per tick,
|
||||
// each holding a cancellation registration, for as long as the player stayed in a school.
|
||||
Task<bool>? waitReliable = null;
|
||||
Task<bool>? waitOutbox = null;
|
||||
|
||||
while (Socket.State == WebSocketState.Open && !cancellationToken.IsCancellationRequested)
|
||||
{
|
||||
if (reliable.TryRead(out var reliableFrame))
|
||||
@@ -102,44 +109,44 @@ internal sealed class GameClient(uint playerId, WebSocket socket)
|
||||
continue;
|
||||
}
|
||||
|
||||
var waitReliable = reliable.WaitToReadAsync(cancellationToken).AsTask();
|
||||
var waitOutbox = outbox.WaitToReadAsync(cancellationToken).AsTask();
|
||||
// A completed channel is empty for good; waiting on it again would spin.
|
||||
if (!reliable.Completion.IsCompleted)
|
||||
{
|
||||
waitReliable ??= reliable.WaitToReadAsync(cancellationToken).AsTask();
|
||||
}
|
||||
|
||||
if (!outbox.Completion.IsCompleted)
|
||||
{
|
||||
waitOutbox ??= outbox.WaitToReadAsync(cancellationToken).AsTask();
|
||||
}
|
||||
|
||||
if (waitReliable is null && waitOutbox is null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Task<bool> finished;
|
||||
try
|
||||
{
|
||||
finished = await Task.WhenAny(waitReliable, waitOutbox).ConfigureAwait(false);
|
||||
finished = waitReliable is null ? waitOutbox!
|
||||
: waitOutbox is null ? waitReliable
|
||||
: await Task.WhenAny(waitReliable, waitOutbox).ConfigureAwait(false);
|
||||
|
||||
await finished.ConfigureAwait(false);
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
bool hasData;
|
||||
try
|
||||
// Only the wait that finished is dropped; the other one stays queued on its channel.
|
||||
if (ReferenceEquals(finished, waitReliable))
|
||||
{
|
||||
hasData = await finished.ConfigureAwait(false);
|
||||
waitReliable = null;
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
else
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (hasData)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var other = ReferenceEquals(finished, waitReliable) ? waitOutbox : waitReliable;
|
||||
try
|
||||
{
|
||||
if (!await other.ConfigureAwait(false))
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
return;
|
||||
waitOutbox = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user