Reapply "OBT1.1.0 Merge branch 'dev_pte' into dev"

This reverts commit 046483b9da.
This commit is contained in:
NotAlwaysTrue
2026-04-30 21:59:54 +08:00
parent 02689d0d86
commit 25683dcf39
85 changed files with 2413 additions and 779 deletions
@@ -4,12 +4,69 @@ using FarseerPhysics.Dynamics;
using Microsoft.Xna.Framework;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Xml.Linq;
using LimbParams = Barotrauma.RagdollParams.LimbParams;
using ColliderParams = Barotrauma.RagdollParams.ColliderParams;
namespace Barotrauma
{
/// <summary>
/// Thread-safe wrapper for PhysicsBody list operations.
/// Uses copy-on-write pattern for lock-free reads.
/// </summary>
internal class ThreadSafePhysicsBodyList : IEnumerable<PhysicsBody>
{
private volatile List<PhysicsBody> _list = new List<PhysicsBody>();
private readonly object _writeLock = new object();
public int Count => _list.Count;
public void Add(PhysicsBody body)
{
lock (_writeLock)
{
var newList = new List<PhysicsBody>(_list) { body };
Interlocked.Exchange(ref _list, newList);
}
}
public bool Remove(PhysicsBody body)
{
lock (_writeLock)
{
var newList = new List<PhysicsBody>(_list);
bool removed = newList.Remove(body);
if (removed)
{
Interlocked.Exchange(ref _list, newList);
}
return removed;
}
}
public void Clear()
{
Interlocked.Exchange(ref _list, new List<PhysicsBody>());
}
public bool Contains(PhysicsBody body) => _list.Contains(body);
public PhysicsBody this[int index] => _list[index];
public IEnumerator<PhysicsBody> GetEnumerator() => _list.GetEnumerator();
System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() => GetEnumerator();
// LINQ-friendly methods
public List<PhysicsBody> ToList() => new List<PhysicsBody>(_list);
public PhysicsBody FirstOrDefault(Func<PhysicsBody, bool> predicate) => _list.FirstOrDefault(predicate);
public PhysicsBody Find(Predicate<PhysicsBody> predicate) => _list.Find(predicate);
public IEnumerable<PhysicsBody> Where(Func<PhysicsBody, bool> predicate) => _list.Where(predicate);
public bool Any() => _list.Any();
public bool Any(Func<PhysicsBody, bool> predicate) => _list.Any(predicate);
}
class PosInfo
{
public Vector2 Position
@@ -92,8 +149,8 @@ namespace Barotrauma
public const float MinDensity = 0.01f;
public const float DefaultAngularDamping = 5.0f;
private static readonly List<PhysicsBody> list = new List<PhysicsBody>();
public static List<PhysicsBody> List
private static readonly ThreadSafePhysicsBodyList list = new ThreadSafePhysicsBodyList();
public static ThreadSafePhysicsBodyList List
{
get { return list; }
}
@@ -0,0 +1,155 @@
using System;
using System.Collections.Generic;
using System.Threading;
namespace Barotrauma
{
/// <summary>
/// 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
/// </summary>
/// <start>
/// ├─> 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<Action> _pendingOperations = new Queue<Action>();
/// <summary>
/// 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.
/// </summary>
[ThreadStatic]
private static bool _isInParallelContext;
/// <summary>
/// Gets or sets whether the current thread is in a parallel update context.
/// When true, physics operations should be queued instead of executed immediately.
/// </summary>
public static bool IsInParallelContext
{
get => _isInParallelContext;
set => _isInParallelContext = value;
}
/// <summary>
/// Enqueues a physics operation to be executed on the main thread.
/// This method is thread-safe and can be called from parallel update loops.
/// </summary>
/// <param name="operation">The physics operation to defer</param>
public static void Enqueue(Action operation)
{
if (operation == null) { return; }
lock (_lock)
{
_pendingOperations.Enqueue(operation);
}
}
/// <summary>
/// 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.
/// </summary>
/// <param name="createAction">The action that creates the physics body</param>
public static void EnqueueCreation(Action createAction)
{
Enqueue(createAction);
}
/// <summary>
/// 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.
/// </summary>
/// <param name="operation">The physics operation to execute</param>
public static void ExecuteOrDefer(Action operation)
{
if (operation == null) { return; }
if (_isInParallelContext)
{
Enqueue(operation);
}
else
{
operation();
}
}
/// <summary>
/// Gets the number of pending physics operations.
/// </summary>
public static int PendingCount
{
get
{
lock (_lock)
{
return _pendingOperations.Count;
}
}
}
/// <summary>
/// Processes all pending physics operations.
/// Must be called on the main thread, outside of any parallel loops.
/// </summary>
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);
}
}
}
/// <summary>
/// Legacy method for backwards compatibility.
/// Calls ProcessPendingOperations().
/// </summary>
public static void ProcessPendingCreations()
{
ProcessPendingOperations();
}
/// <summary>
/// Clears all pending physics operations.
/// Should be called when ending a round or cleaning up.
/// </summary>
public static void Clear()
{
lock (_lock)
{
_pendingOperations.Clear();
}
}
}
}