From 1db14631dfa1a22a8954dcbd6cf2f0ad653ae60f Mon Sep 17 00:00:00 2001 From: Eero Date: Sun, 28 Dec 2025 17:14:16 +0800 Subject: [PATCH] Defer physics transforms to ensure thread safety Refactored multiple components to defer Farseer physics transform operations using PhysicsBodyQueue, preventing unsafe calls from parallel contexts. This change addresses thread safety issues with Farseer's DynamicTree and ensures transforms are executed in a safe context. Also increased the spawn amount limit in DebugConsole from 100 to 100000. --- .../SharedSource/DebugConsole.cs | 2 +- .../SharedSource/Items/Components/Door.cs | 30 ++++++++++---- .../Components/Holdable/LevelResource.cs | 12 ++++-- .../Items/Components/ItemContainer.cs | 6 ++- .../Items/Components/TriggerComponent.cs | 9 ++++- .../SharedSource/Items/Item.cs | 39 +++++++++++++++---- 6 files changed, 76 insertions(+), 22 deletions(-) diff --git a/Barotrauma/BarotraumaShared/SharedSource/DebugConsole.cs b/Barotrauma/BarotraumaShared/SharedSource/DebugConsole.cs index 5cdfefe6a..fb2cacc66 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/DebugConsole.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/DebugConsole.cs @@ -3225,7 +3225,7 @@ namespace Barotrauma if (args.Length > spawnLocationIndex + 1) { if (!int.TryParse(args[spawnLocationIndex + 1], NumberStyles.Any, CultureInfo.InvariantCulture, out amount)) { amount = 1; } - amount = Math.Min(amount, 100); + amount = Math.Min(amount, 100000); } if (args.Length > spawnLocationIndex + 2) diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Door.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Door.cs index fad7c7fab..b3de4b02b 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Door.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Door.cs @@ -314,13 +314,21 @@ namespace Barotrauma.Items.Components public override void Move(Vector2 amount, bool ignoreContacts = false) { - if (ignoreContacts) + // Defer physics operation if in parallel context (Farseer is not thread-safe) + if (Body != null) { - Body?.SetTransformIgnoreContacts(Body.SimPosition + ConvertUnits.ToSimUnits(amount), 0.0f); - } - else - { - Body?.SetTransform(Body.SimPosition + ConvertUnits.ToSimUnits(amount), 0.0f); + var capturedBody = Body; + var capturedNewPos = Body.SimPosition + ConvertUnits.ToSimUnits(amount); + if (ignoreContacts) + { + PhysicsBodyQueue.ExecuteOrDefer(() => + capturedBody.SetTransformIgnoreContacts(capturedNewPos, 0.0f)); + } + else + { + PhysicsBodyQueue.ExecuteOrDefer(() => + capturedBody.SetTransform(capturedNewPos, 0.0f)); + } } #if CLIENT UpdateConvexHulls(); @@ -786,13 +794,19 @@ namespace Barotrauma.Items.Components //immediately teleport it to the correct side if (Math.Sign(diff) != dir) { + // Defer physics operation if in parallel context (Farseer is not thread-safe) + var capturedBody = body; if (IsHorizontal) { - body.SetTransformIgnoreContacts(new Vector2(body.SimPosition.X, item.SimPosition.Y + dir * doorRectSimSize.Y * 2.0f), body.Rotation); + Vector2 newPos = new Vector2(body.SimPosition.X, item.SimPosition.Y + dir * doorRectSimSize.Y * 2.0f); + float rotation = body.Rotation; + PhysicsBodyQueue.ExecuteOrDefer(() => capturedBody.SetTransformIgnoreContacts(newPos, rotation)); } else { - body.SetTransformIgnoreContacts(new Vector2(item.SimPosition.X + dir * doorRectSimSize.X * 1.2f, body.SimPosition.Y), body.Rotation); + Vector2 newPos = new Vector2(item.SimPosition.X + dir * doorRectSimSize.X * 1.2f, body.SimPosition.Y); + float rotation = body.Rotation; + PhysicsBodyQueue.ExecuteOrDefer(() => capturedBody.SetTransformIgnoreContacts(newPos, rotation)); } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Holdable/LevelResource.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Holdable/LevelResource.cs index 797ec14e7..5c672cda5 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Holdable/LevelResource.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Holdable/LevelResource.cs @@ -92,13 +92,16 @@ namespace Barotrauma.Items.Components { if (trigger != null && amount.LengthSquared() > 0.00001f) { + // Defer physics operation if in parallel context (Farseer is not thread-safe) + var capturedTrigger = trigger; + var capturedPos = item.SimPosition; if (ignoreContacts) { - trigger.SetTransformIgnoreContacts(item.SimPosition, 0.0f); + PhysicsBodyQueue.ExecuteOrDefer(() => capturedTrigger.SetTransformIgnoreContacts(capturedPos, 0.0f)); } else { - trigger.SetTransform(item.SimPosition, 0.0f); + PhysicsBodyQueue.ExecuteOrDefer(() => capturedTrigger.SetTransform(capturedPos, 0.0f)); } } } @@ -134,7 +137,10 @@ namespace Barotrauma.Items.Components } if (trigger != null && Vector2.DistanceSquared(item.SimPosition, trigger.SimPosition) > 0.01f) { - trigger.SetTransform(item.SimPosition, 0.0f); + // Defer physics operation if in parallel context (Farseer is not thread-safe) + var capturedTrigger = trigger; + var capturedPos = item.SimPosition; + PhysicsBodyQueue.ExecuteOrDefer(() => capturedTrigger.SetTransform(capturedPos, 0.0f)); } IsActive = false; } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/ItemContainer.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/ItemContainer.cs index 722b63588..992b4bfc6 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/ItemContainer.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/ItemContainer.cs @@ -1011,7 +1011,11 @@ namespace Barotrauma.Items.Components if (flippedX ^ flippedY) { rotation = -rotation; } rotation += -item.RotationRad; } - contained.Item.body.FarseerBody.SetTransformIgnoreContacts(ref simPos, rotation); + // Defer physics operation if in parallel context (Farseer is not thread-safe) + var capturedBody = contained.Item.body.FarseerBody; + var capturedSimPos = simPos; + var capturedRotation = rotation; + PhysicsBodyQueue.ExecuteOrDefer(() => capturedBody.SetTransformIgnoreContacts(ref capturedSimPos, capturedRotation)); contained.Item.body.UpdateDrawPosition(interpolate: false); } catch (Exception e) diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/TriggerComponent.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/TriggerComponent.cs index c839d488c..228df8dc9 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/TriggerComponent.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/TriggerComponent.cs @@ -312,13 +312,18 @@ namespace Barotrauma.Items.Components Matrix transform = Matrix.CreateRotationZ(-item.RotationRad); offset = Vector2.Transform(offset, transform); } + + // Defer physics operations if in parallel context (Farseer is not thread-safe) + var capturedBody = PhysicsBody; + var capturedPos = item.SimPosition + offset; + var capturedRot = -item.RotationRad; if (ignoreContacts) { - PhysicsBody.SetTransformIgnoreContacts(item.SimPosition + offset, -item.RotationRad); + PhysicsBodyQueue.ExecuteOrDefer(() => capturedBody.SetTransformIgnoreContacts(capturedPos, capturedRot)); } else { - PhysicsBody.SetTransform(item.SimPosition + offset, -item.RotationRad); + PhysicsBodyQueue.ExecuteOrDefer(() => capturedBody.SetTransform(capturedPos, capturedRot)); } PhysicsBody.UpdateDrawPosition(); } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Item.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Item.cs index 2ce5e3d2d..8ad7bfea1 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Item.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Item.cs @@ -1822,7 +1822,13 @@ namespace Barotrauma try { #endif - body.SetTransformIgnoreContacts(simPosition, rotation, setPrevTransform); + // Defer physics operation if in parallel context (Farseer is not thread-safe) + var capturedBody = body; + var capturedSimPos = simPosition; + var capturedRotation = rotation; + var capturedSetPrevTransform = setPrevTransform; + PhysicsBodyQueue.ExecuteOrDefer(() => + capturedBody.SetTransformIgnoreContacts(capturedSimPos, capturedRotation, capturedSetPrevTransform)); #if DEBUG } catch (Exception e) @@ -1899,13 +1905,19 @@ namespace Barotrauma if (ItemList != null && body != null) { + // Defer physics operation if in parallel context (Farseer is not thread-safe) + var capturedBody = body; + var capturedNewPos = body.SimPosition + ConvertUnits.ToSimUnits(amount); + var capturedRotation = body.Rotation; if (ignoreContacts) { - body.SetTransformIgnoreContacts(body.SimPosition + ConvertUnits.ToSimUnits(amount), body.Rotation); + PhysicsBodyQueue.ExecuteOrDefer(() => + capturedBody.SetTransformIgnoreContacts(capturedNewPos, capturedRotation)); } else { - body.SetTransform(body.SimPosition + ConvertUnits.ToSimUnits(amount), body.Rotation); + PhysicsBodyQueue.ExecuteOrDefer(() => + capturedBody.SetTransform(capturedNewPos, capturedRotation)); } } foreach (ItemComponent ic in components) @@ -2563,7 +2575,12 @@ namespace Barotrauma if (item != this) { item.body.Enabled = false; - item.body.SetTransformIgnoreContacts(this.SimPosition, body.Rotation); + // Defer physics operation if in parallel context (Farseer is not thread-safe) + var capturedItemBody = item.body; + var capturedSimPos = this.SimPosition; + var capturedRotation = body.Rotation; + PhysicsBodyQueue.ExecuteOrDefer(() => + capturedItemBody.SetTransformIgnoreContacts(capturedSimPos, capturedRotation)); } } } @@ -2755,17 +2772,25 @@ namespace Barotrauma FindHull(); } + // Defer physics transform operations if in parallel context. + // Farseer's DynamicTree is not thread-safe. if (Submarine == null && prevSub != null) { - body.SetTransformIgnoreContacts(body.SimPosition + prevSub.SimPosition, body.Rotation); + Vector2 newPos = body.SimPosition + prevSub.SimPosition; + float rotation = body.Rotation; + PhysicsBodyQueue.ExecuteOrDefer(() => body.SetTransformIgnoreContacts(newPos, rotation)); } else if (Submarine != null && prevSub == null) { - body.SetTransformIgnoreContacts(body.SimPosition - Submarine.SimPosition, body.Rotation); + Vector2 newPos = body.SimPosition - Submarine.SimPosition; + float rotation = body.Rotation; + PhysicsBodyQueue.ExecuteOrDefer(() => body.SetTransformIgnoreContacts(newPos, rotation)); } else if (Submarine != null && prevSub != null && Submarine != prevSub) { - body.SetTransformIgnoreContacts(body.SimPosition + prevSub.SimPosition - Submarine.SimPosition, body.Rotation); + Vector2 newPos = body.SimPosition + prevSub.SimPosition - Submarine.SimPosition; + float rotation = body.Rotation; + PhysicsBodyQueue.ExecuteOrDefer(() => body.SetTransformIgnoreContacts(newPos, rotation)); } if (Submarine != prevSub)