Refactor ServerEntityEventManager event processing

Introduces async event processing with cancellation support using SemaphoreSlim and CancellationTokenSource. Improves client event handling logic, separates in-game and mid-round sync clients, and adds proper disposal of resources. Enhances robustness and maintainability of event management.
This commit is contained in:
eero
2025-12-23 00:07:47 +08:00
parent 83ca51a99b
commit de65cd88bc
@@ -123,29 +123,35 @@ namespace Barotrauma.Networking
private readonly Task createEventTask; private readonly Task createEventTask;
private readonly CancellationTokenSource cancellationTokenSource = new CancellationTokenSource();
private readonly SemaphoreSlim eventSignal = new SemaphoreSlim(0);
public ServerEntityEventManager(GameServer server) public ServerEntityEventManager(GameServer server)
{ {
events = new List<ServerEntityEvent>(); events = new List<ServerEntityEvent>();
this.server = server; this.server = server;
bufferedEvents = new List<BufferedEvent>(); bufferedEvents = new List<BufferedEvent>();
uniqueEvents = new List<ServerEntityEvent>(); uniqueEvents = new List<ServerEntityEvent>();
pendingCreateQueue = new ConcurrentQueue<PendingCreateEvent>(); pendingCreateQueue = new ConcurrentQueue<PendingCreateEvent>();
lastWarningTime = -10.0; lastWarningTime = -10.0;
SEM = this; SEM = this;
createEventTask = Task.Run(async () => await CreateEventProcessorLoop()); createEventTask = Task.Run(() => CreateEventProcessorLoop(cancellationTokenSource.Token));
} }
private Task CreateEventProcessorLoop()
private async Task CreateEventProcessorLoop(CancellationToken token)
{ {
while (true) while (!token.IsCancellationRequested)
{ {
ProcessPendingCreateEvents(); try
{
await eventSignal.WaitAsync(100, token);
ProcessPendingCreateEvents();
}
catch (OperationCanceledException)
{
break;
}
} }
} }
@@ -209,10 +215,31 @@ namespace Barotrauma.Networking
// enqueue and let background task handle the rest // enqueue and let background task handle the rest
pendingCreateQueue.Enqueue(new PendingCreateEvent(entity, extraData)); pendingCreateQueue.Enqueue(new PendingCreateEvent(entity, extraData));
if (eventSignal.CurrentCount == 0)
{
eventSignal.Release();
}
} }
public void Dispose()
{
cancellationTokenSource.Cancel();
eventSignal.Release();
try
{
createEventTask?.Wait(2000);
}
catch (AggregateException) { }
finally
{
cancellationTokenSource.Dispose();
eventSignal.Dispose();
}
}
public void Update(List<Client> clients) public void Update(List<Client> clients)
{ {
foreach (BufferedEvent bufferedEvent in bufferedEvents) foreach (BufferedEvent bufferedEvent in bufferedEvents)
{ {
if (bufferedEvent.Character == null || bufferedEvent.Character.IsDead) if (bufferedEvent.Character == null || bufferedEvent.Character.IsDead)
@@ -258,25 +285,45 @@ namespace Barotrauma.Networking
bufferedEvent.IsProcessed = true; bufferedEvent.IsProcessed = true;
} }
var inGameClients = clients.FindAll(c => c.InGame && !c.NeedsMidRoundSync); List<Client> inGameClients = null;
if (inGameClients.Count > 0) List<Client> midRoundSyncClients = null;
Client ownerClient = null;
foreach (var c in clients)
{ {
lastSentToAnyone = inGameClients[0].LastRecvEntityEventID; if (c.InGame)
lastSentToAll = inGameClients[0].LastRecvEntityEventID;
if (server.OwnerConnection != null)
{ {
var owner = clients.Find(c => c.Connection == server.OwnerConnection); if (c.NeedsMidRoundSync)
if (owner != null)
{ {
lastSentToAll = owner.LastRecvEntityEventID; (midRoundSyncClients ??= new List<Client>()).Add(c);
}
else
{
(inGameClients ??= new List<Client>()).Add(c);
} }
} }
inGameClients.ForEach(c => if (server.OwnerConnection != null && c.Connection == server.OwnerConnection)
{ {
if (NetIdUtils.IdMoreRecent(lastSentToAll, c.LastRecvEntityEventID)) { lastSentToAll = c.LastRecvEntityEventID; } ownerClient = c;
if (NetIdUtils.IdMoreRecent(c.LastRecvEntityEventID, lastSentToAnyone)) { lastSentToAnyone = c.LastRecvEntityEventID; } }
}); }
if (inGameClients != null && inGameClients.Count > 0)
{
lastSentToAnyone = inGameClients[0].LastRecvEntityEventID;
lastSentToAll = ownerClient?.LastRecvEntityEventID ?? inGameClients[0].LastRecvEntityEventID;
foreach (var c in inGameClients)
{
if (NetIdUtils.IdMoreRecent(lastSentToAll, c.LastRecvEntityEventID))
{
lastSentToAll = c.LastRecvEntityEventID;
}
if (NetIdUtils.IdMoreRecent(c.LastRecvEntityEventID, lastSentToAnyone))
{
lastSentToAnyone = c.LastRecvEntityEventID;
}
}
lastSentToAnyoneTime = events.Find(e => e.ID == lastSentToAnyone)?.CreateTime ?? Timing.TotalTime; lastSentToAnyoneTime = events.Find(e => e.ID == lastSentToAnyone)?.CreateTime ?? Timing.TotalTime;
if (Timing.TotalTime - lastWarningTime > 5.0 && if (Timing.TotalTime - lastWarningTime > 5.0 &&
@@ -332,11 +379,21 @@ namespace Barotrauma.Networking
} }
} }
var timedOutClients = clients.FindAll(c => c.Connection != GameMain.Server.OwnerConnection && c.InGame && c.NeedsMidRoundSync && Timing.TotalTime > c.MidRoundSyncTimeOut); if (midRoundSyncClients != null)
foreach (Client timedOutClient in timedOutClients)
{ {
GameServer.Log("Disconnecting client " + GameServer.ClientLogName(timedOutClient) + ". Syncing the client with the server took too long.", ServerLog.MessageType.Error); foreach (var c in midRoundSyncClients)
GameMain.Server.DisconnectClient(timedOutClient, PeerDisconnectPacket.WithReason(DisconnectReason.SyncTimeout)); {
if (NetIdUtils.IdMoreRecent(lastSentToAll, c.FirstNewEventID))
{
lastSentToAll = (ushort)(c.FirstNewEventID - 1);
}
if (c.Connection != GameMain.Server.OwnerConnection && Timing.TotalTime > c.MidRoundSyncTimeOut)
{
GameServer.Log("Disconnecting client " + GameServer.ClientLogName(c) + ". Syncing took too long.", ServerLog.MessageType.Error);
GameMain.Server.DisconnectClient(c, PeerDisconnectPacket.WithReason(DisconnectReason.SyncTimeout));
}
}
} }
bufferedEvents.RemoveAll(b => b.IsProcessed); bufferedEvents.RemoveAll(b => b.IsProcessed);