From de65cd88bcdf158cb7b498650c189010ba22667f Mon Sep 17 00:00:00 2001 From: eero Date: Tue, 23 Dec 2025 00:07:47 +0800 Subject: [PATCH] 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. --- .../ServerEntityEventManager.cs | 113 +++++++++++++----- 1 file changed, 85 insertions(+), 28 deletions(-) diff --git a/Barotrauma/BarotraumaServer/ServerSource/Networking/NetEntityEvent/ServerEntityEventManager.cs b/Barotrauma/BarotraumaServer/ServerSource/Networking/NetEntityEvent/ServerEntityEventManager.cs index 0e83ff46b..283eeb789 100644 --- a/Barotrauma/BarotraumaServer/ServerSource/Networking/NetEntityEvent/ServerEntityEventManager.cs +++ b/Barotrauma/BarotraumaServer/ServerSource/Networking/NetEntityEvent/ServerEntityEventManager.cs @@ -123,29 +123,35 @@ namespace Barotrauma.Networking private readonly Task createEventTask; + private readonly CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); + private readonly SemaphoreSlim eventSignal = new SemaphoreSlim(0); + public ServerEntityEventManager(GameServer server) { events = new List(); - this.server = server; - bufferedEvents = new List(); - uniqueEvents = new List(); - pendingCreateQueue = new ConcurrentQueue(); - lastWarningTime = -10.0; - 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 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 clients) { - foreach (BufferedEvent bufferedEvent in bufferedEvents) { if (bufferedEvent.Character == null || bufferedEvent.Character.IsDead) @@ -258,25 +285,45 @@ namespace Barotrauma.Networking bufferedEvent.IsProcessed = true; } - var inGameClients = clients.FindAll(c => c.InGame && !c.NeedsMidRoundSync); - if (inGameClients.Count > 0) + List inGameClients = null; + List midRoundSyncClients = null; + Client ownerClient = null; + + foreach (var c in clients) { - lastSentToAnyone = inGameClients[0].LastRecvEntityEventID; - lastSentToAll = inGameClients[0].LastRecvEntityEventID; - - if (server.OwnerConnection != null) + if (c.InGame) { - var owner = clients.Find(c => c.Connection == server.OwnerConnection); - if (owner != null) + if (c.NeedsMidRoundSync) { - lastSentToAll = owner.LastRecvEntityEventID; + (midRoundSyncClients ??= new List()).Add(c); + } + else + { + (inGameClients ??= new List()).Add(c); } } - inGameClients.ForEach(c => + if (server.OwnerConnection != null && c.Connection == server.OwnerConnection) { - if (NetIdUtils.IdMoreRecent(lastSentToAll, c.LastRecvEntityEventID)) { lastSentToAll = c.LastRecvEntityEventID; } - if (NetIdUtils.IdMoreRecent(c.LastRecvEntityEventID, lastSentToAnyone)) { lastSentToAnyone = c.LastRecvEntityEventID; } - }); + ownerClient = c; + } + } + + 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; 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); - foreach (Client timedOutClient in timedOutClients) + if (midRoundSyncClients != null) { - GameServer.Log("Disconnecting client " + GameServer.ClientLogName(timedOutClient) + ". Syncing the client with the server took too long.", ServerLog.MessageType.Error); - GameMain.Server.DisconnectClient(timedOutClient, PeerDisconnectPacket.WithReason(DisconnectReason.SyncTimeout)); + foreach (var c in midRoundSyncClients) + { + 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);