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 _)) { }
}
}
}