Unstable Add thread-safe queue for deferred physics body creation

Introduces PhysicsBodyQueue to safely defer physics body creation to the main thread, addressing thread-safety issues with Farseer Physics during parallel updates. Updates LevelResource, TriggerComponent, BallastFloraBehavior, and MapEntity to use the queue for all physics body creation and refresh operations, ensuring they are processed outside of parallel loops. Also adds cleanup of the queue at round end.
This commit is contained in:
Eero
2025-12-28 14:42:17 +08:00
parent 45312af297
commit 49355fe32b
6 changed files with 191 additions and 9 deletions
@@ -307,6 +307,12 @@ namespace Barotrauma.MapCreatures.Behavior
public readonly List<BallastFloraBranch> Branches = new List<BallastFloraBranch>();
private BallastFloraBranch? root;
private readonly List<Body> bodies = new List<Body>();
/// <summary>
/// Branches that need physics bodies created on the main thread.
/// </summary>
private readonly List<BallastFloraBranch> pendingBodyCreations = new List<BallastFloraBranch>();
private readonly object pendingBodyCreationsLock = new object();
private bool isDead;
@@ -347,7 +353,8 @@ namespace Barotrauma.MapCreatures.Behavior
}
}
UpdateConnections(branch);
CreateBody(branch);
// OnMapLoaded runs on the main thread, so we can create bodies immediately
CreateBody(branch, immediate: true);
}
}
@@ -998,10 +1005,52 @@ namespace Barotrauma.MapCreatures.Behavior
}
/// <summary>
/// Create a body for a branch which works as the hitbox for flamer
/// Queue a physics body creation for a branch.
/// The actual body will be created on the main thread to ensure thread safety.
/// </summary>
/// <param name="branch"></param>
private void CreateBody(BallastFloraBranch branch)
/// <param name="branch">The branch to create a body for</param>
/// <param name="immediate">If true, create the body immediately (only safe when called from main thread)</param>
private void CreateBody(BallastFloraBranch branch, bool immediate = false)
{
if (immediate)
{
CreateBodyImmediate(branch);
return;
}
lock (pendingBodyCreationsLock)
{
pendingBodyCreations.Add(branch);
}
PhysicsBodyQueue.EnqueueCreation(() => ProcessPendingBodyCreations());
}
/// <summary>
/// Process all pending body creations on the main thread.
/// This ensures Farseer Physics operations are thread-safe.
/// </summary>
private void ProcessPendingBodyCreations()
{
List<BallastFloraBranch> branchesToProcess;
lock (pendingBodyCreationsLock)
{
if (pendingBodyCreations.Count == 0) { return; }
branchesToProcess = new List<BallastFloraBranch>(pendingBodyCreations);
pendingBodyCreations.Clear();
}
foreach (var branch in branchesToProcess)
{
if (branch.Removed) { continue; }
CreateBodyImmediate(branch);
}
}
/// <summary>
/// Actually create the physics body for a branch.
/// Must be called on the main thread.
/// </summary>
private void CreateBodyImmediate(BallastFloraBranch branch)
{
Rectangle rect = branch.Rect;
Vector2 pos = Parent.Position + Offset + branch.Position;
@@ -686,6 +686,10 @@ namespace Barotrauma
}
);
// Process any physics body creation operations queued during Hull/Structure updates.
// BallastFlora growth (from Hull.Update) may queue physics body creations.
PhysicsBodyQueue.ProcessPendingCreations();
#if CLIENT
// Hull Cheats need to be executed after Hull update
Hull.UpdateCheats(deltaTime, cam);
@@ -727,6 +731,10 @@ namespace Barotrauma
throw new InvalidOperationException($"Error while updating item {lastUpdatedItem?.Name ?? "null"}", innerException: e);
}
// Process any physics body creation operations that were queued during the parallel update.
// This must be done on the main thread because Farseer Physics is not thread-safe.
PhysicsBodyQueue.ProcessPendingCreations();
UpdateAllProjSpecific(scaledDeltaTime);
Spawner?.Update();