diff --git a/Barotrauma/BarotraumaServer/ServerSource/Program.cs b/Barotrauma/BarotraumaServer/ServerSource/Program.cs index 1f5a9727b..c5e19b32c 100644 --- a/Barotrauma/BarotraumaServer/ServerSource/Program.cs +++ b/Barotrauma/BarotraumaServer/ServerSource/Program.cs @@ -93,6 +93,7 @@ namespace Barotrauma private static bool hasShutDown = false; private static void ShutDown() { + SingleThreadWorker.Instance.Dispose(); if (hasShutDown) { return; } hasShutDown = true; diff --git a/Barotrauma/BarotraumaShared/SharedSource/Map/Gap.cs b/Barotrauma/BarotraumaShared/SharedSource/Map/Gap.cs index 0dbb7f23a..12e26a9e1 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Map/Gap.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Map/Gap.cs @@ -814,7 +814,7 @@ namespace Barotrauma if (outsideCollisionBlocker == null) { return false; } if (IsRoomToRoom || Submarine == null || open <= 0.0f || linkedTo.Count == 0 || linkedTo[0] is not Hull) { - SingleThreadWorker.GlobalWorker.AddAction(() => + SingleThreadWorker.Instance.AddAction(() => { if (outsideCollisionBlocker == null) { return; } outsideCollisionBlocker.Enabled = false; diff --git a/Barotrauma/BarotraumaShared/SharedSource/Map/Hull.cs b/Barotrauma/BarotraumaShared/SharedSource/Map/Hull.cs index 7c4391e1f..3d7ea4c3c 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Map/Hull.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Map/Hull.cs @@ -888,22 +888,24 @@ namespace Barotrauma Oxygen -= OxygenDeteriorationSpeed * deltaTime; - if (FakeFireSources.Count > 0) + SingleThreadWorker.Instance.AddAction(() => { - if ((Character.Controlled?.CharacterHealth?.GetAffliction("psychosis")?.Strength ?? 0.0f) <= 0.0f) + if (FakeFireSources.Count > 0) { - for (int i = FakeFireSources.Count - 1; i >= 0; i--) + if ((Character.Controlled?.CharacterHealth?.GetAffliction("psychosis")?.Strength ?? 0.0f) <= 0.0f) { - if (FakeFireSources[i].CausedByPsychosis) + for (int i = FakeFireSources.Count - 1; i >= 0; i--) { - FakeFireSources[i].Remove(); + if (FakeFireSources[i].CausedByPsychosis) + { + FakeFireSources[i].Remove(); + } } } + FireSource.UpdateAll(FakeFireSources, deltaTime); } - FireSource.UpdateAll(FakeFireSources, deltaTime); - } - - FireSource.UpdateAll(FireSources, deltaTime); + FireSource.UpdateAll(FireSources, deltaTime); + }); foreach (Decal decal in decals) { diff --git a/Barotrauma/BarotraumaShared/SharedSource/Map/MapEntity.cs b/Barotrauma/BarotraumaShared/SharedSource/Map/MapEntity.cs index cc04729fc..a5066076b 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Map/MapEntity.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Map/MapEntity.cs @@ -8,6 +8,7 @@ using System.Linq; using System.Reflection; using System.Threading.Tasks; using System.Xml.Linq; +using static OneOf.Types.TrueFalseOrNull; namespace Barotrauma { @@ -642,6 +643,7 @@ namespace Barotrauma /// public static void UpdateAll(float deltaTime, Camera cam, ParallelOptions parallelOptions) { + Random rand = new Random(); #if CLIENT var sw = new System.Diagnostics.Stopwatch(); sw.Start(); @@ -652,9 +654,15 @@ namespace Barotrauma var structureList = Structure.WallList.ToList(); List gapList = Gap.GapList.ToList(); - List shuffledGaps = new List(gapList?.OrderBy(g => Rand.Int(int.MaxValue))); - // In case if it failed, but why it would fail? - shuffledGaps = shuffledGaps ?? gapList; + + // This should never break again... right? + int n = gapList.Count; + while (n > 1) + { + n--; + int k = rand.Next(n + 1); + (gapList[n], gapList[k]) = (gapList[k], gapList[n]); + } var itemList = Item.ItemList.ToList(); @@ -662,11 +670,11 @@ namespace Barotrauma Parallel.Invoke(parallelOptions, () => { - // basically nothing here is thread-safe so - foreach (var hull in hullList) + Parallel.ForEach(hullList, parallelOptions, hull => { hull.Update(deltaTime, cam); - } + }); + }, // Structure parallel update () => @@ -685,7 +693,7 @@ namespace Barotrauma // moved waterflow reset here to see if we can reduce at least some time { // PLEASE WORK - Parallel.ForEach(shuffledGaps, parallelOptions, gap => + Parallel.ForEach(gapList, parallelOptions, gap => { gap.ResetWaterFlowThisFrame(); gap.Update(deltaTime, cam); @@ -698,8 +706,6 @@ namespace Barotrauma } ); - SingleThreadWorker.GlobalWorker.RunActions(); - #if CLIENT // Hull Cheats need to be executed after Hull update Hull.UpdateCheats(deltaTime, cam); diff --git a/Barotrauma/BarotraumaShared/SharedSource/Screens/GameScreen.cs b/Barotrauma/BarotraumaShared/SharedSource/Screens/GameScreen.cs index f413c54f1..c210eda8d 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Screens/GameScreen.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Screens/GameScreen.cs @@ -6,6 +6,7 @@ using System.Threading.Tasks; using System.Linq; using System.Collections.Generic; using System; +using static Barotrauma.SingleThreadWorker; #if DEBUG && CLIENT @@ -261,7 +262,6 @@ namespace Barotrauma Ragdoll.UpdateAll((float)deltaTime, Cam); - SingleThreadWorker.GlobalWorker.RunActions(); #if CLIENT sw.Stop(); @@ -279,6 +279,9 @@ namespace Barotrauma GameMain.PerformanceCounter.AddElapsedTicks("Update:Submarine", sw.ElapsedTicks); sw.Restart(); #endif + + SingleThreadActionStandbySignal.Wait(); + try { GameMain.World.Step((float)Timing.Step); @@ -290,6 +293,8 @@ namespace Barotrauma GameAnalyticsManager.AddErrorEventOnce("GameScreen.Update:WorldLockedException" + e.Message, GameAnalyticsManager.ErrorSeverity.Critical, errorMsg); } + SingleThreadActionStandbySignal.Release(); + #if CLIENT sw.Stop(); GameMain.PerformanceCounter.AddElapsedTicks("Update:Physics", sw.ElapsedTicks); diff --git a/Barotrauma/BarotraumaShared/SharedSource/SingleThreadWorker.cs b/Barotrauma/BarotraumaShared/SharedSource/SingleThreadWorker.cs index 8beef7b85..c16530aa8 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/SingleThreadWorker.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/SingleThreadWorker.cs @@ -1,5 +1,9 @@ -using System; +using Barotrauma.Networking; +using System; using System.Collections.Concurrent; +using System.Threading; +using System.Threading.Tasks; +using static Barotrauma.EosInterface.Ownership; namespace Barotrauma { @@ -7,7 +11,13 @@ namespace Barotrauma { private ConcurrentQueue ActionQueue; - public static SingleThreadWorker GlobalWorker = new SingleThreadWorker(); + public static SingleThreadWorker Instance = new SingleThreadWorker(); + + private readonly CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); + private readonly SemaphoreSlim actionSignal = new SemaphoreSlim(0); + private static Task WorkerTask; + + public static readonly SemaphoreSlim SingleThreadActionStandbySignal = new SemaphoreSlim(1); /// /// Initilize a SingleThreadWorker @@ -16,35 +26,75 @@ namespace Barotrauma public SingleThreadWorker() { ActionQueue = new ConcurrentQueue(); + WorkerTask = CreateProcessTask(cancellationTokenSource.Token); + } + + public void Dispose() + { + cancellationTokenSource.Cancel(); + WorkerTask.Wait(); + WorkerTask.Dispose(); + Instance = null; + cancellationTokenSource.Dispose(); + actionSignal.Dispose(); + SingleThreadActionStandbySignal.Dispose(); + } + + private async Task CreateProcessTask(CancellationToken token) + { + while (!token.IsCancellationRequested) + { + try + { + await actionSignal.WaitAsync(100, token); + SingleThreadActionStandbySignal.Wait(CancellationToken.None); + RunActions(); + } + catch (OperationCanceledException) + { + break; + } + finally + { + SingleThreadActionStandbySignal.Release(); + } + } } /// - /// Add a pending action in a STW queue + /// Add a pending action in a STW queue. + /// DO NOT ABUSE IT OR IT WILL SLOW DOWN MAIN THREAD!!!! /// /// public void AddAction(Action action) { + // enqueue and let background task handle the rest ActionQueue.Enqueue(action); + + if (actionSignal.CurrentCount == 0) + { + actionSignal.Release(); + } } /// /// Run all pending actions in the STW queue /// [STAThread] - public void RunActions() + private void RunActions() { while (ActionQueue.TryDequeue(out Action action)) { try { - action(); + action.Invoke(); } catch (Exception e) { // Just try-catch and do nothing but print errorlogs. We cannot afford crashing the game. ConsoleColor originalForeground = Console.ForegroundColor; Console.ForegroundColor = ConsoleColor.Yellow; - Console.WriteLine($"WARNING: Error occurred when running Single Thread Actions. " + + Console.WriteLine($"WARNING: Error occurred when running Single Thread Actions." + $"If the server didn't crash or stop responding then this should be fine \n{e}"); Console.ForegroundColor = Console.ForegroundColor; }