using System; using System.Collections.Generic; using System.Threading; namespace Barotrauma { /// /// Thread-safe queue for deferring physics operations to the main thread. /// This is necessary because Farseer Physics' DynamicTree is not thread-safe, /// and physics operations cannot be safely performed during parallel updates. /// /// Supported operations include: /// - Physics body creation /// - Physics body transform updates (SetTransform, SetTransformIgnoreContacts) /// - Any other operation that modifies the Farseer physics world /// /// /// ├─> PhysicsBodyQueue.IsInParallelContext = true (ThreadStatic) /// ├─> Item.Update() /// │ └─> StatusEffect.Apply() /// │ └─> Character.Kill() /// │ └─> Item.Drop() /// │ └─> Check if IsInParallelContext == true /// │ └─> PhysicsBodyQueue.Enqueue(Physics operation) /// ├──> PhysicsBodyQueue.IsInParallelContext = false /// └──> PhysicsBodyQueue.ProcessPendingOperations() ← Main thread executes /// └─> body.SetTransformIgnoreContacts() static class PhysicsBodyQueue { private static readonly object _lock = new object(); private static readonly Queue _pendingOperations = new Queue(); /// /// Thread-local flag indicating whether the current thread is in a parallel physics update context. /// When true, physics operations should be deferred using this queue instead of executing immediately. /// [ThreadStatic] private static bool _isInParallelContext; /// /// Gets or sets whether the current thread is in a parallel update context. /// When true, physics operations should be queued instead of executed immediately. /// public static bool IsInParallelContext { get => _isInParallelContext; set => _isInParallelContext = value; } /// /// Enqueues a physics operation to be executed on the main thread. /// This method is thread-safe and can be called from parallel update loops. /// /// The physics operation to defer public static void Enqueue(Action operation) { if (operation == null) { return; } lock (_lock) { _pendingOperations.Enqueue(operation); } } /// /// Enqueues a physics body creation action to be executed on the main thread. /// This method is thread-safe and can be called from parallel update loops. /// /// The action that creates the physics body public static void EnqueueCreation(Action createAction) { Enqueue(createAction); } /// /// Executes a physics operation, either immediately or deferred depending on context. /// If called from a parallel context, the operation will be queued for later execution. /// If called from the main thread (outside parallel loops), the operation executes immediately. /// /// The physics operation to execute public static void ExecuteOrDefer(Action operation) { if (operation == null) { return; } if (_isInParallelContext) { Enqueue(operation); } else { operation(); } } /// /// Gets the number of pending physics operations. /// public static int PendingCount { get { lock (_lock) { return _pendingOperations.Count; } } } /// /// Processes all pending physics operations. /// Must be called on the main thread, outside of any parallel loops. /// public static void ProcessPendingOperations() { while (true) { Action action; lock (_lock) { if (_pendingOperations.Count == 0) { break; } action = _pendingOperations.Dequeue(); } try { action?.Invoke(); } catch (Exception e) { DebugConsole.ThrowError($"Error processing deferred physics operation: {e.Message}", e); } } } /// /// Legacy method for backwards compatibility. /// Calls ProcessPendingOperations(). /// public static void ProcessPendingCreations() { ProcessPendingOperations(); } /// /// Clears all pending physics operations. /// Should be called when ending a round or cleaning up. /// public static void Clear() { lock (_lock) { _pendingOperations.Clear(); } } } }