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
This commit is contained in:
NotAlwaysTrue
2026-01-09 18:09:49 +08:00
parent caec44c57d
commit e7e444e9b2
5 changed files with 24 additions and 36 deletions
@@ -382,8 +382,13 @@ namespace Barotrauma
} }
steeringBuffer = Math.Clamp(steeringBuffer, minSteeringBuffer, maxSteeringBuffer); steeringBuffer = Math.Clamp(steeringBuffer, minSteeringBuffer, maxSteeringBuffer);
AnimController.Crouching = shouldCrouch; // in case of somehow AnimController was a null, eg. something removed AnimController in the middle of an update
CheckCrouching(deltaTime); if (AnimController != null)
{
AnimController.Crouching = shouldCrouch;
CheckCrouching(deltaTime);
}
Character.ClearInputs(); Character.ClearInputs();
if (SortTimer > 0.0f) if (SortTimer > 0.0f)
@@ -1638,7 +1643,8 @@ namespace Barotrauma
if (mode == AIObjectiveCombat.CombatMode.None) { return; } if (mode == AIObjectiveCombat.CombatMode.None) { return; }
if (Character.IsDead || Character.IsIncapacitated || Character.Removed) { return; } if (Character.IsDead || Character.IsIncapacitated || Character.Removed) { return; }
if (!Character.IsBot) { return; } if (!Character.IsBot) { return; }
if (ObjectiveManager.Objectives.FirstOrDefault(o => o is AIObjectiveCombat) is AIObjectiveCombat combatObjective) List<AIObjective> ObjectivesLocal = ObjectiveManager.Objectives;
if (ObjectivesLocal.FirstOrDefault(o => o is AIObjectiveCombat) is AIObjectiveCombat combatObjective)
{ {
// Don't replace offensive mode with something else // Don't replace offensive mode with something else
if (combatObjective.Mode == AIObjectiveCombat.CombatMode.Offensive && mode != AIObjectiveCombat.CombatMode.Offensive) { return; } if (combatObjective.Mode == AIObjectiveCombat.CombatMode.Offensive && mode != AIObjectiveCombat.CombatMode.Offensive) { return; }
@@ -126,7 +126,7 @@ namespace Barotrauma
} }
else else
{ {
Objectives.RemoveAll(o => o.GetType() == type); Objectives.RemoveAll(o => o?.GetType() == type);
} }
Objectives.Add(objective); Objectives.Add(objective);
} }
@@ -345,7 +345,8 @@ namespace Barotrauma.Items.Components
{ {
Connection recipient = wire.OtherConnection(this); Connection recipient = wire.OtherConnection(this);
if (recipient == null) { continue; } if (recipient == null) { continue; }
if (recipient.item == this.item || signal.source?.LastSentSignalRecipients.LastOrDefault() == recipient) { continue; } List<Connection> LastSentSignalRecipientsCopy = signal.source?.LastSentSignalRecipients.ToList();
if (recipient.item == this.item || LastSentSignalRecipientsCopy.LastOrDefault() == recipient) { continue; }
signal.source?.LastSentSignalRecipients.Add(recipient); signal.source?.LastSentSignalRecipients.Add(recipient);
#if CLIENT #if CLIENT
@@ -816,7 +816,7 @@ namespace Barotrauma
// Gap update (has order dependencies, keep random order but execute sequentially) // Gap update (has order dependencies, keep random order but execute sequentially)
var shuffledGaps = gapList.OrderBy(g => Rand.Int(int.MaxValue)).ToList(); var shuffledGaps = gapList.OrderBy(g => Rand.Int(int.MaxValue)).ToList();
Parallel.ForEach(gapList, parallelOptions, gap => Parallel.ForEach(shuffledGaps, parallelOptions, gap =>
{ {
PhysicsBodyQueue.IsInParallelContext = true; PhysicsBodyQueue.IsInParallelContext = true;
try try
@@ -25,7 +25,7 @@ namespace Barotrauma
private static readonly ParallelOptions parallelOptions = new ParallelOptions private static readonly ParallelOptions parallelOptions = new ParallelOptions
{ {
MaxDegreeOfParallelism = Environment.ProcessorCount * 2, MaxDegreeOfParallelism = Math.Max(4,Environment.ProcessorCount - 1),
}; };
#if CLIENT #if CLIENT
@@ -259,33 +259,15 @@ namespace Barotrauma
Character.Controlled?.UpdateLocalCursor(cam); Character.Controlled?.UpdateLocalCursor(cam);
#elif SERVER #elif SERVER
Parallel.Invoke(parallelOptions, // DO NOT PARALLELIZE THESE TWO OR IT MAY STUCK HERE
() => // SO FOLLOW THE ORIGINAL SINGLE-THREAD LOGIC STRICTLY
{
PhysicsBodyQueue.IsInParallelContext = true; if (Level.Loaded != null) Level.Loaded.Update((float)deltaTime, Camera.Instance);
try
{ Character.UpdateAll((float)deltaTime, Camera.Instance);
if (Level.Loaded != null) Level.Loaded.Update((float)deltaTime, Camera.Instance);
} StatusEffect.UpdateAll((float)deltaTime);
finally
{
PhysicsBodyQueue.IsInParallelContext = false;
}
},
() =>
{
PhysicsBodyQueue.IsInParallelContext = true;
try
{
Character.UpdateAll((float)deltaTime, Camera.Instance);
}
finally
{
PhysicsBodyQueue.IsInParallelContext = false;
}
}
);
PhysicsBodyQueue.ProcessPendingOperations(); PhysicsBodyQueue.ProcessPendingOperations();
#endif #endif
@@ -310,8 +292,6 @@ namespace Barotrauma
MapEntity.UpdateAll((float)deltaTime, Camera.Instance, parallelOptions); MapEntity.UpdateAll((float)deltaTime, Camera.Instance, parallelOptions);
StatusEffect.UpdateAll((float)deltaTime);
#endif #endif
#if CLIENT #if CLIENT
@@ -321,6 +301,7 @@ namespace Barotrauma
#endif #endif
//Character.UpdateAnimAll is not thread-safe and must be executed on the main thread //Character.UpdateAnimAll is not thread-safe and must be executed on the main thread
Character.UpdateAnimAll((float)deltaTime); Character.UpdateAnimAll((float)deltaTime);
PhysicsBodyQueue.ProcessPendingOperations();
#if CLIENT #if CLIENT
Ragdoll.UpdateAll((float)deltaTime, cam); Ragdoll.UpdateAll((float)deltaTime, cam);