From e7e444e9b21c5a881ee8dab764559663e80ce55d Mon Sep 17 00:00:00 2001 From: NotAlwaysTrue <2136846186@qq.com> Date: Fri, 9 Jan 2026 18:09:49 +0800 Subject: [PATCH] Fixed multiple LINQ using shared resources and cause crashes Added an null check in AIObjectiveManager.cs to avoid accessing removed resources Use shuffledGaps instead of gapList to ensure update order requirement(already in master) Updated parallelism count --- .../Characters/AI/HumanAIController.cs | 12 ++++-- .../AI/Objectives/AIObjectiveManager.cs | 2 +- .../Items/Components/Signal/Connection.cs | 3 +- .../SharedSource/Map/MapEntity.cs | 2 +- .../SharedSource/Screens/GameScreen.cs | 41 +++++-------------- 5 files changed, 24 insertions(+), 36 deletions(-) diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/HumanAIController.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/HumanAIController.cs index 4479bdb81..e1eb0c40e 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/HumanAIController.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/HumanAIController.cs @@ -382,8 +382,13 @@ namespace Barotrauma } steeringBuffer = Math.Clamp(steeringBuffer, minSteeringBuffer, maxSteeringBuffer); - AnimController.Crouching = shouldCrouch; - CheckCrouching(deltaTime); + // in case of somehow AnimController was a null, eg. something removed AnimController in the middle of an update + if (AnimController != null) + { + AnimController.Crouching = shouldCrouch; + CheckCrouching(deltaTime); + } + Character.ClearInputs(); if (SortTimer > 0.0f) @@ -1638,7 +1643,8 @@ namespace Barotrauma if (mode == AIObjectiveCombat.CombatMode.None) { return; } if (Character.IsDead || Character.IsIncapacitated || Character.Removed) { return; } if (!Character.IsBot) { return; } - if (ObjectiveManager.Objectives.FirstOrDefault(o => o is AIObjectiveCombat) is AIObjectiveCombat combatObjective) + List ObjectivesLocal = ObjectiveManager.Objectives; + if (ObjectivesLocal.FirstOrDefault(o => o is AIObjectiveCombat) is AIObjectiveCombat combatObjective) { // Don't replace offensive mode with something else if (combatObjective.Mode == AIObjectiveCombat.CombatMode.Offensive && mode != AIObjectiveCombat.CombatMode.Offensive) { return; } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveManager.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveManager.cs index 83e475c37..ead5b6a87 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveManager.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveManager.cs @@ -126,7 +126,7 @@ namespace Barotrauma } else { - Objectives.RemoveAll(o => o.GetType() == type); + Objectives.RemoveAll(o => o?.GetType() == type); } Objectives.Add(objective); } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Signal/Connection.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Signal/Connection.cs index d55150237..37cb33d9c 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Signal/Connection.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Signal/Connection.cs @@ -345,7 +345,8 @@ namespace Barotrauma.Items.Components { Connection recipient = wire.OtherConnection(this); if (recipient == null) { continue; } - if (recipient.item == this.item || signal.source?.LastSentSignalRecipients.LastOrDefault() == recipient) { continue; } + List LastSentSignalRecipientsCopy = signal.source?.LastSentSignalRecipients.ToList(); + if (recipient.item == this.item || LastSentSignalRecipientsCopy.LastOrDefault() == recipient) { continue; } signal.source?.LastSentSignalRecipients.Add(recipient); #if CLIENT diff --git a/Barotrauma/BarotraumaShared/SharedSource/Map/MapEntity.cs b/Barotrauma/BarotraumaShared/SharedSource/Map/MapEntity.cs index b61d2e68e..fb3723934 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Map/MapEntity.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Map/MapEntity.cs @@ -816,7 +816,7 @@ namespace Barotrauma // Gap update (has order dependencies, keep random order but execute sequentially) var shuffledGaps = gapList.OrderBy(g => Rand.Int(int.MaxValue)).ToList(); - Parallel.ForEach(gapList, parallelOptions, gap => + Parallel.ForEach(shuffledGaps, parallelOptions, gap => { PhysicsBodyQueue.IsInParallelContext = true; try diff --git a/Barotrauma/BarotraumaShared/SharedSource/Screens/GameScreen.cs b/Barotrauma/BarotraumaShared/SharedSource/Screens/GameScreen.cs index e2fddef7c..be3e4bb12 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Screens/GameScreen.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Screens/GameScreen.cs @@ -25,7 +25,7 @@ namespace Barotrauma private static readonly ParallelOptions parallelOptions = new ParallelOptions { - MaxDegreeOfParallelism = Environment.ProcessorCount * 2, + MaxDegreeOfParallelism = Math.Max(4,Environment.ProcessorCount - 1), }; #if CLIENT @@ -259,33 +259,15 @@ namespace Barotrauma Character.Controlled?.UpdateLocalCursor(cam); #elif SERVER - Parallel.Invoke(parallelOptions, - () => - { - PhysicsBodyQueue.IsInParallelContext = true; - try - { - if (Level.Loaded != null) Level.Loaded.Update((float)deltaTime, Camera.Instance); - } - finally - { - PhysicsBodyQueue.IsInParallelContext = false; - } - }, - () => - { - PhysicsBodyQueue.IsInParallelContext = true; - try - { - Character.UpdateAll((float)deltaTime, Camera.Instance); - } - finally - { - PhysicsBodyQueue.IsInParallelContext = false; - } - } - ); - + // DO NOT PARALLELIZE THESE TWO OR IT MAY STUCK HERE + // SO FOLLOW THE ORIGINAL SINGLE-THREAD LOGIC STRICTLY + + if (Level.Loaded != null) Level.Loaded.Update((float)deltaTime, Camera.Instance); + + Character.UpdateAll((float)deltaTime, Camera.Instance); + + StatusEffect.UpdateAll((float)deltaTime); + PhysicsBodyQueue.ProcessPendingOperations(); #endif @@ -310,8 +292,6 @@ namespace Barotrauma MapEntity.UpdateAll((float)deltaTime, Camera.Instance, parallelOptions); - StatusEffect.UpdateAll((float)deltaTime); - #endif #if CLIENT @@ -321,6 +301,7 @@ namespace Barotrauma #endif //Character.UpdateAnimAll is not thread-safe and must be executed on the main thread Character.UpdateAnimAll((float)deltaTime); + PhysicsBodyQueue.ProcessPendingOperations(); #if CLIENT Ragdoll.UpdateAll((float)deltaTime, cam);