Reapply "OBT1.1.0 Merge branch 'dev_pte' into dev"
This reverts commit 046483b9da.
This commit is contained in:
@@ -4,6 +4,7 @@ using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Xml.Linq;
|
||||
using Barotrauma.Extensions;
|
||||
using Barotrauma.Items.Components;
|
||||
@@ -14,6 +15,55 @@ using Microsoft.Xna.Framework;
|
||||
|
||||
namespace Barotrauma.MapCreatures.Behavior
|
||||
{
|
||||
/// <summary>
|
||||
/// Thread-safe wrapper for BallastFloraBehavior list operations.
|
||||
/// Uses copy-on-write pattern for lock-free reads.
|
||||
/// </summary>
|
||||
internal class ThreadSafeBallastFloraList : IEnumerable<BallastFloraBehavior>
|
||||
{
|
||||
private volatile List<BallastFloraBehavior> _list = new List<BallastFloraBehavior>();
|
||||
private readonly object _writeLock = new object();
|
||||
|
||||
public int Count => _list.Count;
|
||||
|
||||
public void Add(BallastFloraBehavior entity)
|
||||
{
|
||||
lock (_writeLock)
|
||||
{
|
||||
var newList = new List<BallastFloraBehavior>(_list) { entity };
|
||||
Interlocked.Exchange(ref _list, newList);
|
||||
}
|
||||
}
|
||||
|
||||
public bool Remove(BallastFloraBehavior entity)
|
||||
{
|
||||
lock (_writeLock)
|
||||
{
|
||||
var newList = new List<BallastFloraBehavior>(_list);
|
||||
bool removed = newList.Remove(entity);
|
||||
if (removed)
|
||||
{
|
||||
Interlocked.Exchange(ref _list, newList);
|
||||
}
|
||||
return removed;
|
||||
}
|
||||
}
|
||||
|
||||
public void Clear()
|
||||
{
|
||||
Interlocked.Exchange(ref _list, new List<BallastFloraBehavior>());
|
||||
}
|
||||
|
||||
public IEnumerator<BallastFloraBehavior> GetEnumerator() => _list.GetEnumerator();
|
||||
System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() => GetEnumerator();
|
||||
|
||||
// LINQ-friendly methods
|
||||
public List<BallastFloraBehavior> ToList() => new List<BallastFloraBehavior>(_list);
|
||||
public bool Any() => _list.Any();
|
||||
public bool Any(Func<BallastFloraBehavior, bool> predicate) => _list.Any(predicate);
|
||||
public IEnumerable<BallastFloraBehavior> Where(Func<BallastFloraBehavior, bool> predicate) => _list.Where(predicate);
|
||||
}
|
||||
|
||||
class BallastFloraBranch : VineTile
|
||||
{
|
||||
public readonly BallastFloraBehavior? ParentBallastFlora;
|
||||
@@ -132,7 +182,7 @@ namespace Barotrauma.MapCreatures.Behavior
|
||||
public List<Tuple<Vector2, Vector2>> debugSearchLines = new List<Tuple<Vector2, Vector2>>();
|
||||
#endif
|
||||
|
||||
private readonly static List<BallastFloraBehavior> _entityList = new List<BallastFloraBehavior>();
|
||||
private readonly static ThreadSafeBallastFloraList _entityList = new ThreadSafeBallastFloraList();
|
||||
public static IEnumerable<BallastFloraBehavior> EntityList => _entityList;
|
||||
|
||||
public enum NetworkHeader
|
||||
@@ -307,6 +357,12 @@ namespace Barotrauma.MapCreatures.Behavior
|
||||
public readonly List<BallastFloraBranch> Branches = new List<BallastFloraBranch>();
|
||||
private BallastFloraBranch? root;
|
||||
private readonly List<Body> bodies = new List<Body>();
|
||||
|
||||
/// <summary>
|
||||
/// Branches that need physics bodies created on the main thread.
|
||||
/// </summary>
|
||||
private readonly List<BallastFloraBranch> pendingBodyCreations = new List<BallastFloraBranch>();
|
||||
private readonly object pendingBodyCreationsLock = new object();
|
||||
|
||||
private bool isDead;
|
||||
|
||||
@@ -347,7 +403,8 @@ namespace Barotrauma.MapCreatures.Behavior
|
||||
}
|
||||
}
|
||||
UpdateConnections(branch);
|
||||
CreateBody(branch);
|
||||
// OnMapLoaded runs on the main thread, so we can create bodies immediately
|
||||
CreateBody(branch, immediate: true);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -998,10 +1055,52 @@ namespace Barotrauma.MapCreatures.Behavior
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create a body for a branch which works as the hitbox for flamer
|
||||
/// Queue a physics body creation for a branch.
|
||||
/// The actual body will be created on the main thread to ensure thread safety.
|
||||
/// </summary>
|
||||
/// <param name="branch"></param>
|
||||
private void CreateBody(BallastFloraBranch branch)
|
||||
/// <param name="branch">The branch to create a body for</param>
|
||||
/// <param name="immediate">If true, create the body immediately (only safe when called from main thread)</param>
|
||||
private void CreateBody(BallastFloraBranch branch, bool immediate = false)
|
||||
{
|
||||
if (immediate)
|
||||
{
|
||||
CreateBodyImmediate(branch);
|
||||
return;
|
||||
}
|
||||
|
||||
lock (pendingBodyCreationsLock)
|
||||
{
|
||||
pendingBodyCreations.Add(branch);
|
||||
}
|
||||
PhysicsBodyQueue.EnqueueCreation(() => ProcessPendingBodyCreations());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Process all pending body creations on the main thread.
|
||||
/// This ensures Farseer Physics operations are thread-safe.
|
||||
/// </summary>
|
||||
private void ProcessPendingBodyCreations()
|
||||
{
|
||||
List<BallastFloraBranch> branchesToProcess;
|
||||
lock (pendingBodyCreationsLock)
|
||||
{
|
||||
if (pendingBodyCreations.Count == 0) { return; }
|
||||
branchesToProcess = new List<BallastFloraBranch>(pendingBodyCreations);
|
||||
pendingBodyCreations.Clear();
|
||||
}
|
||||
|
||||
foreach (var branch in branchesToProcess)
|
||||
{
|
||||
if (branch.Removed) { continue; }
|
||||
CreateBodyImmediate(branch);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Actually create the physics body for a branch.
|
||||
/// Must be called on the main thread.
|
||||
/// </summary>
|
||||
private void CreateBodyImmediate(BallastFloraBranch branch)
|
||||
{
|
||||
Rectangle rect = branch.Rect;
|
||||
Vector2 pos = Parent.Position + Offset + branch.Position;
|
||||
|
||||
Reference in New Issue
Block a user