using System; using System.Runtime.CompilerServices; using System.Threading; using System.Threading.Channels; namespace Barotrauma { /// /// High-performance lock-free 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. /// /// Uses System.Threading.Channels for optimal throughput with single-reader pattern. /// Channel<T> provides better performance than ConcurrentQueue in producer-consumer scenarios. /// /// Supported operations include: /// - Physics body creation /// - Physics body transform updates (SetTransform, SetTransformIgnoreContacts) /// - Any other operation that modifies the Farseer physics world /// /// /// Workflow: /// /// ├─> 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 { // High-performance unbounded channel optimized for single-reader scenario private static readonly Channel _channel = Channel.CreateUnbounded( new UnboundedChannelOptions { SingleReader = true, // Only main thread reads - enables optimizations SingleWriter = false, // Multiple parallel threads may write AllowSynchronousContinuations = false // Prevent stack dives, improve throughput }); private static readonly ChannelWriter _writer = _channel.Writer; private static readonly ChannelReader _reader = _channel.Reader; /// /// 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 lock-free and can be safely called from parallel update loops. /// Uses Channel's optimized TryWrite which is faster than ConcurrentQueue.Enqueue. /// /// The physics operation to defer [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void Enqueue(Action operation) { if (operation == null) { return; } _writer.TryWrite(operation); } /// /// Enqueues a physics body creation action to be executed on the main thread. /// This method is lock-free and can be safely 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. /// /// Hot path optimization: Most calls occur outside parallel context, so we check /// the non-parallel case first to improve branch prediction. /// /// The physics operation to execute [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void ExecuteOrDefer(Action operation) { if (operation == null) { return; } // Hot path: Most calls are outside parallel context - execute immediately if (!_isInParallelContext) { operation(); return; } // Cold path: In parallel context - defer to queue _writer.TryWrite(operation); } /// /// Gets whether there are any pending physics operations. /// This is an O(1) operation. /// public static bool HasPending => _reader.TryPeek(out _); /// /// Gets the approximate number of pending physics operations. /// Note: This may have some overhead compared to the previous atomic counter. /// Use HasPending for simple empty checks. /// public static int PendingCount => _reader.Count; /// /// Processes all pending physics operations. /// Must be called on the main thread, outside of any parallel loops. /// Uses Channel's optimized TryRead for single-reader scenario. /// public static void ProcessPendingOperations() { while (_reader.TryRead(out Action action)) { 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() { while (_reader.TryRead(out _)) { } } } }