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

This reverts commit 177cf89756, reversing
changes made to 42ba733cd4.
This commit is contained in:
Eero
2025-12-29 11:18:11 +08:00
parent 177cf89756
commit 046483b9da
86 changed files with 800 additions and 2496 deletions
@@ -4,7 +4,6 @@ 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;
@@ -15,55 +14,6 @@ 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;
@@ -182,7 +132,7 @@ namespace Barotrauma.MapCreatures.Behavior
public List<Tuple<Vector2, Vector2>> debugSearchLines = new List<Tuple<Vector2, Vector2>>();
#endif
private readonly static ThreadSafeBallastFloraList _entityList = new ThreadSafeBallastFloraList();
private readonly static List<BallastFloraBehavior> _entityList = new List<BallastFloraBehavior>();
public static IEnumerable<BallastFloraBehavior> EntityList => _entityList;
public enum NetworkHeader
@@ -357,12 +307,6 @@ 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;
@@ -403,8 +347,7 @@ namespace Barotrauma.MapCreatures.Behavior
}
}
UpdateConnections(branch);
// OnMapLoaded runs on the main thread, so we can create bodies immediately
CreateBody(branch, immediate: true);
CreateBody(branch);
}
}
@@ -1055,52 +998,10 @@ namespace Barotrauma.MapCreatures.Behavior
}
/// <summary>
/// Queue a physics body creation for a branch.
/// The actual body will be created on the main thread to ensure thread safety.
/// Create a body for a branch which works as the hitbox for flamer
/// </summary>
/// <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)
/// <param name="branch"></param>
private void CreateBody(BallastFloraBranch branch)
{
Rectangle rect = branch.Rect;
Vector2 pos = Parent.Position + Offset + branch.Position;
@@ -1,6 +1,5 @@
using Microsoft.Xna.Framework;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Diagnostics;
using Barotrauma.IO;
@@ -21,10 +20,10 @@ namespace Barotrauma
public const ushort MaxEntityCount = ushort.MaxValue - 4; //ushort.MaxValue - 4 because the 4 values above are reserved values
private static readonly ConcurrentDictionary<ushort, Entity> dictionary = new ConcurrentDictionary<ushort, Entity>();
private static readonly Dictionary<ushort, Entity> dictionary = new Dictionary<ushort, Entity>();
public static IReadOnlyCollection<Entity> GetEntities()
{
return (IReadOnlyCollection<Entity>)dictionary.Values;
return dictionary.Values;
}
public static int EntityCount => dictionary.Count;
@@ -123,11 +122,13 @@ namespace Barotrauma
//give a unique ID
ID = DetermineID(id, submarine);
if (!dictionary.TryAdd(ID, this))
if (dictionary.ContainsKey(ID))
{
throw new Exception($"ID {ID} is taken by {dictionary[ID]}");
}
dictionary.Add(ID, this);
CreationStackTrace = "";
#if DEBUG
var st = new StackTrace(skipFrames: 2, fNeedFileInfo: true);
@@ -146,6 +147,7 @@ namespace Barotrauma
CreationStackTrace += $"{fileName}@{fileLineNumber}; ";
}
#endif
#warning TODO: consider removing this mutex, entity creation probably shouldn't be multithreaded
lock (creationCounterMutex)
{
CreationIndex = creationCounter;
@@ -259,7 +261,7 @@ namespace Barotrauma
DebugConsole.ThrowError($"Error while removing item \"{item}\"", exception);
}
}
Item.ClearAllItemCollections();
Item.ItemList.Clear();
}
if (Character.CharacterList.Count > 0)
{
@@ -323,7 +325,7 @@ namespace Barotrauma
}
else
{
dictionary.TryRemove(ID, out _);
dictionary.Remove(ID);
}
IdFreed = true;
}
@@ -7,7 +7,6 @@ using Microsoft.Xna.Framework;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
namespace Barotrauma
{
@@ -649,11 +648,7 @@ namespace Barotrauma
}
}
// ThreadLocal for thread-safe structure damage tracking
private static readonly ThreadLocal<Dictionary<Structure, float>> damagedStructuresLocal =
new ThreadLocal<Dictionary<Structure, float>>(() => new Dictionary<Structure, float>());
private static Dictionary<Structure, float> damagedStructures => damagedStructuresLocal.Value;
private static readonly Dictionary<Structure, float> damagedStructures = new Dictionary<Structure, float>();
/// <summary>
/// Returns a dictionary where the keys are the structures that took damage and the values are the amount of damage taken
/// </summary>
@@ -8,71 +8,13 @@ using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Xml.Linq;
namespace Barotrauma
{
/// <summary>
/// Thread-safe wrapper for Gap list operations.
/// Uses copy-on-write pattern for lock-free reads.
/// </summary>
internal class ThreadSafeGapList : IEnumerable<Gap>
{
private volatile List<Gap> _list = new List<Gap>();
private readonly object _writeLock = new object();
public int Count => _list.Count;
public void Add(Gap gap)
{
lock (_writeLock)
{
var newList = new List<Gap>(_list) { gap };
Interlocked.Exchange(ref _list, newList);
}
}
public bool Remove(Gap gap)
{
lock (_writeLock)
{
var newList = new List<Gap>(_list);
bool removed = newList.Remove(gap);
if (removed)
{
Interlocked.Exchange(ref _list, newList);
}
return removed;
}
}
public void Clear()
{
Interlocked.Exchange(ref _list, new List<Gap>());
}
public bool Contains(Gap gap) => _list.Contains(gap);
public Gap this[int index] => _list[index];
public IEnumerator<Gap> GetEnumerator() => _list.GetEnumerator();
System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() => GetEnumerator();
// LINQ-friendly methods
public List<Gap> ToList() => new List<Gap>(_list);
public Gap FirstOrDefault(Func<Gap, bool> predicate) => _list.FirstOrDefault(predicate);
public Gap Find(Predicate<Gap> predicate) => _list.Find(predicate);
public List<Gap> FindAll(Predicate<Gap> predicate) => _list.FindAll(predicate);
public IEnumerable<Gap> Where(Func<Gap, bool> predicate) => _list.Where(predicate);
public bool Any() => _list.Any();
public bool Any(Func<Gap, bool> predicate) => _list.Any(predicate);
public IOrderedEnumerable<Gap> OrderBy<TKey>(Func<Gap, TKey> keySelector) => _list.OrderBy(keySelector);
}
partial class Gap : MapEntity, ISerializableEntity
{
public static ThreadSafeGapList GapList = new ThreadSafeGapList();
public static List<Gap> GapList = new List<Gap>();
const float MaxFlowForce = 500.0f;
@@ -6,7 +6,6 @@ using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Threading;
using System.Xml.Linq;
using Barotrauma.MapCreatures.Behavior;
using Barotrauma.Items.Components;
@@ -14,116 +13,6 @@ using Barotrauma.Extensions;
namespace Barotrauma
{
/// <summary>
/// Thread-safe wrapper for Hull list operations.
/// Uses copy-on-write pattern for lock-free reads.
/// </summary>
internal class ThreadSafeHullList : IEnumerable<Hull>
{
private volatile List<Hull> _list = new List<Hull>();
private readonly object _writeLock = new object();
public int Count => _list.Count;
public void Add(Hull hull)
{
lock (_writeLock)
{
var newList = new List<Hull>(_list) { hull };
Interlocked.Exchange(ref _list, newList);
}
}
public bool Remove(Hull hull)
{
lock (_writeLock)
{
var newList = new List<Hull>(_list);
bool removed = newList.Remove(hull);
if (removed)
{
Interlocked.Exchange(ref _list, newList);
}
return removed;
}
}
public void Clear()
{
Interlocked.Exchange(ref _list, new List<Hull>());
}
public bool Contains(Hull hull) => _list.Contains(hull);
public Hull this[int index] => _list[index];
public IEnumerator<Hull> GetEnumerator() => _list.GetEnumerator();
System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() => GetEnumerator();
// LINQ-friendly methods
public List<Hull> ToList() => new List<Hull>(_list);
public Hull FirstOrDefault(Func<Hull, bool> predicate) => _list.FirstOrDefault(predicate);
public Hull Find(Predicate<Hull> predicate) => _list.Find(predicate);
public List<Hull> FindAll(Predicate<Hull> predicate) => _list.FindAll(predicate);
public IEnumerable<Hull> Where(Func<Hull, bool> predicate) => _list.Where(predicate);
public bool Any() => _list.Any();
public bool Any(Func<Hull, bool> predicate) => _list.Any(predicate);
public bool Exists(Predicate<Hull> predicate) => _list.Exists(predicate);
public void ForEach(Action<Hull> action) => _list.ForEach(action);
}
/// <summary>
/// Thread-safe wrapper for EntityGrid list operations.
/// Uses copy-on-write pattern for lock-free reads.
/// </summary>
internal class ThreadSafeEntityGridList : IEnumerable<EntityGrid>
{
private volatile List<EntityGrid> _list = new List<EntityGrid>();
private readonly object _writeLock = new object();
public int Count => _list.Count;
public void Add(EntityGrid grid)
{
lock (_writeLock)
{
var newList = new List<EntityGrid>(_list) { grid };
Interlocked.Exchange(ref _list, newList);
}
}
public bool Remove(EntityGrid grid)
{
lock (_writeLock)
{
var newList = new List<EntityGrid>(_list);
bool removed = newList.Remove(grid);
if (removed)
{
Interlocked.Exchange(ref _list, newList);
}
return removed;
}
}
public void Clear()
{
Interlocked.Exchange(ref _list, new List<EntityGrid>());
}
public EntityGrid this[int index] => _list[index];
public IEnumerator<EntityGrid> GetEnumerator() => _list.GetEnumerator();
System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() => GetEnumerator();
// LINQ-friendly methods
public List<EntityGrid> ToList() => new List<EntityGrid>(_list);
public EntityGrid FirstOrDefault(Func<EntityGrid, bool> predicate) => _list.FirstOrDefault(predicate);
public EntityGrid Find(Predicate<EntityGrid> predicate) => _list.Find(predicate);
public IEnumerable<EntityGrid> Where(Func<EntityGrid, bool> predicate) => _list.Where(predicate);
public bool Any() => _list.Any();
}
partial class BackgroundSection
{
public Rectangle Rect;
@@ -224,8 +113,8 @@ namespace Barotrauma
partial class Hull : MapEntity, ISerializableEntity, IServerSerializable
{
public readonly static ThreadSafeHullList HullList = new ThreadSafeHullList();
public readonly static ThreadSafeEntityGridList EntityGrids = new ThreadSafeEntityGridList();
public readonly static List<Hull> HullList = new List<Hull>();
public readonly static List<EntityGrid> EntityGrids = new List<EntityGrid>();
public static bool ShowHulls = true;
@@ -1244,18 +1133,13 @@ namespace Barotrauma
}
/// <summary>
/// Used in <see cref="GetApproximateDistance"/> - ThreadLocal for thread safety
/// Used in <see cref="GetApproximateDistance"/>
/// </summary>
private static readonly ThreadLocal<Dictionary<Hull, float>> cachedDistancesLocal =
new ThreadLocal<Dictionary<Hull, float>>(() => new Dictionary<Hull, float>());
private static readonly Dictionary<Hull, float> cachedDistances = [];
/// <summary>
/// Used in <see cref="GetApproximateDistance"/> - ThreadLocal for thread safety
/// Used in <see cref="GetApproximateDistance"/>
/// </summary>
private static readonly ThreadLocal<PriorityQueue<(Hull hull, Vector2 pos), float>> priorityQueueLocal =
new ThreadLocal<PriorityQueue<(Hull hull, Vector2 pos), float>>(() => new PriorityQueue<(Hull hull, Vector2 pos), float>());
private static Dictionary<Hull, float> cachedDistances => cachedDistancesLocal.Value;
private static PriorityQueue<(Hull hull, Vector2 pos), float> priorityQueue => priorityQueueLocal.Value;
private static readonly PriorityQueue<(Hull hull, Vector2 pos), float> priorityQueue = new PriorityQueue<(Hull hull, Vector2 pos), float>();
/// <summary>
/// Approximate distance from this hull to the target hull, moving through open gaps without passing through walls.
@@ -4775,7 +4775,7 @@ namespace Barotrauma
// BeaconStation.FlipX();
// }
Item sonarItem = Item.ItemList.FirstOrDefault(it => it.Submarine == BeaconStation && it.GetComponent<Sonar>() != null);
Item sonarItem = Item.ItemList.Find(it => it.Submarine == BeaconStation && it.GetComponent<Sonar>() != null);
if (sonarItem == null)
{
DebugConsole.ThrowError($"No sonar found in the beacon station \"{beaconStationName}\"!");
@@ -4794,7 +4794,7 @@ namespace Barotrauma
throw new InvalidOperationException("Failed to prepare beacon station (no beacon station in the level).");
}
List<Item> beaconItems = Item.ItemList.Where(it => it.Submarine == BeaconStation).ToList();
List<Item> beaconItems = Item.ItemList.FindAll(it => it.Submarine == BeaconStation);
Item reactorItem = beaconItems.Find(it => it.GetComponent<Reactor>() != null);
Reactor reactorComponent = null;
@@ -4840,7 +4840,7 @@ namespace Barotrauma
if (BeaconStation?.Info?.BeaconStationInfo is { AllowDisconnectedWires: false }) { return; }
if (disconnectWireProbability <= 0.0f) { return; }
List<Item> beaconItems = Item.ItemList.Where(it => it.Submarine == BeaconStation).ToList();
List<Item> beaconItems = Item.ItemList.FindAll(it => it.Submarine == BeaconStation);
foreach (Item item in beaconItems.Where(it => it.GetComponent<Wire>() != null).ToList())
{
if (item.NonInteractable || item.InvulnerableToDamage) { continue; }
@@ -4878,7 +4878,7 @@ namespace Barotrauma
if (breakDeviceProbability <= 0.0f) { return; }
//break powered items
List<Item> beaconItems = Item.ItemList.Where(it => it.Submarine == BeaconStation).ToList();
List<Item> beaconItems = Item.ItemList.FindAll(it => it.Submarine == BeaconStation);
foreach (Item item in beaconItems.Where(it => it.Components.Any(c => c is Powered) && it.Components.Any(c => c is Repairable)))
{
if (item.NonInteractable || item.InvulnerableToDamage) { continue; }
@@ -1371,7 +1371,7 @@ namespace Barotrauma
{
foreach (TakenItem takenItem in takenItems)
{
Item item = Item.ItemList.FirstOrDefault(it => takenItem.Matches(it));
Item item = Item.ItemList.Find(it => takenItem.Matches(it));
item?.Remove();
}
}
@@ -6,111 +6,14 @@ using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Reflection;
using System.Threading;
using System.Threading.Tasks;
using System.Xml.Linq;
namespace Barotrauma
{
/// <summary>
/// Thread-safe wrapper for MapEntity list operations.
/// Uses copy-on-write pattern for lock-free reads.
/// </summary>
internal class ThreadSafeMapEntityList : IEnumerable<MapEntity>
{
private volatile List<MapEntity> _list = new List<MapEntity>();
private readonly object _writeLock = new object();
public int Count => _list.Count;
public void Add(MapEntity entity)
{
lock (_writeLock)
{
var newList = new List<MapEntity>(_list) { entity };
Interlocked.Exchange(ref _list, newList);
}
}
public void Insert(int index, MapEntity entity)
{
lock (_writeLock)
{
var newList = new List<MapEntity>(_list);
newList.Insert(index, entity);
Interlocked.Exchange(ref _list, newList);
}
}
/// <summary>
/// Atomically inserts an entity at a position determined by the insertAction.
/// The insertAction is executed within the lock to ensure thread-safety.
/// </summary>
public void InsertWithAction(MapEntity entity, Action<List<MapEntity>, MapEntity> insertAction)
{
lock (_writeLock)
{
var newList = new List<MapEntity>(_list);
insertAction(newList, entity);
Interlocked.Exchange(ref _list, newList);
}
}
public bool Remove(MapEntity entity)
{
lock (_writeLock)
{
var newList = new List<MapEntity>(_list);
bool removed = newList.Remove(entity);
if (removed)
{
Interlocked.Exchange(ref _list, newList);
}
return removed;
}
}
public int RemoveAll(Predicate<MapEntity> match)
{
lock (_writeLock)
{
var newList = new List<MapEntity>(_list);
int count = newList.RemoveAll(match);
if (count > 0)
{
Interlocked.Exchange(ref _list, newList);
}
return count;
}
}
public void Clear()
{
Interlocked.Exchange(ref _list, new List<MapEntity>());
}
public bool Contains(MapEntity entity) => _list.Contains(entity);
public MapEntity this[int index] => _list[index];
public IEnumerator<MapEntity> GetEnumerator() => _list.GetEnumerator();
System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() => GetEnumerator();
// LINQ-friendly methods that work on a snapshot
public List<MapEntity> ToList() => new List<MapEntity>(_list);
public MapEntity FirstOrDefault(Func<MapEntity, bool> predicate) => _list.FirstOrDefault(predicate);
public MapEntity Find(Predicate<MapEntity> predicate) => _list.Find(predicate);
public List<MapEntity> FindAll(Predicate<MapEntity> predicate) => _list.FindAll(predicate);
public IEnumerable<MapEntity> Where(Func<MapEntity, bool> predicate) => _list.Where(predicate);
public bool Any(Func<MapEntity, bool> predicate) => _list.Any(predicate);
public bool Exists(Predicate<MapEntity> predicate) => _list.Exists(predicate);
public IOrderedEnumerable<MapEntity> OrderBy<TKey>(Func<MapEntity, TKey> keySelector) => _list.OrderBy(keySelector);
public void ForEach(Action<MapEntity> action) => _list.ForEach(action);
}
abstract partial class MapEntity : Entity, ISpatialEntity
{
public readonly static ThreadSafeMapEntityList MapEntityList = new ThreadSafeMapEntityList();
public readonly static List<MapEntity> MapEntityList = new List<MapEntity>();
public readonly MapEntityPrefab Prefab;
@@ -656,51 +559,45 @@ namespace Barotrauma
return;
}
// Use atomic insertion to ensure thread-safety
MapEntityList.InsertWithAction(this, (list, entity) =>
//sort damageable walls by sprite depth:
//necessary because rendering the damage effect starts a new sprite batch and breaks the order otherwise
int i = 0;
if (this is Structure { DrawDamageEffect: true } structure)
{
int i = 0;
//sort damageable walls by sprite depth:
//necessary because rendering the damage effect starts a new sprite batch and breaks the order otherwise
if (entity is Structure { DrawDamageEffect: true } structure)
//insertion sort according to draw depth
float drawDepth = structure.SpriteDepth;
while (i < MapEntityList.Count)
{
//insertion sort according to draw depth
float drawDepth = structure.SpriteDepth;
while (i < list.Count)
{
float otherDrawDepth = (list[i] as Structure)?.SpriteDepth ?? 1.0f;
if (otherDrawDepth < drawDepth) { break; }
i++;
}
list.Insert(i, entity);
float otherDrawDepth = (MapEntityList[i] as Structure)?.SpriteDepth ?? 1.0f;
if (otherDrawDepth < drawDepth) { break; }
i++;
}
MapEntityList.Insert(i, this);
return;
}
i = 0;
while (i < MapEntityList.Count)
{
i++;
if (MapEntityList[i - 1]?.Prefab == Prefab)
{
MapEntityList.Insert(i, this);
return;
}
i = 0;
var mapEntity = (MapEntity)entity;
while (i < list.Count)
{
i++;
if (list[i - 1]?.Prefab == mapEntity.Prefab)
{
list.Insert(i, entity);
return;
}
}
}
#if CLIENT
i = 0;
while (i < list.Count)
{
i++;
Sprite existingSprite = list[i - 1].Sprite;
if (existingSprite == null) { continue; }
if (existingSprite.Texture == mapEntity.Sprite?.Texture) { break; }
}
i = 0;
while (i < MapEntityList.Count)
{
i++;
Sprite existingSprite = MapEntityList[i - 1].Sprite;
if (existingSprite == null) { continue; }
if (existingSprite.Texture == this.Sprite.Texture) { break; }
}
#endif
list.Insert(i, entity);
});
MapEntityList.Insert(i, this);
}
/// <summary>
@@ -763,31 +660,15 @@ namespace Barotrauma
// basically nothing here is thread-safe so
foreach(var hull in hullList)
{
PhysicsBodyQueue.IsInParallelContext = true;
try
{
hull.Update(deltaTime, cam);
}
finally
{
PhysicsBodyQueue.IsInParallelContext = false;
}
});
hull.Update(deltaTime, cam);
}
},
// Structure parallel update
() =>
{
Parallel.ForEach(structureList, parallelOptions, structure =>
{
PhysicsBodyQueue.IsInParallelContext = true;
try
{
structure.Update(deltaTime, cam);
}
finally
{
PhysicsBodyQueue.IsInParallelContext = false;
}
structure.Update(deltaTime, cam);
});
},
// Gap reset (must be done before update)
@@ -805,10 +686,6 @@ namespace Barotrauma
}
);
// Process any physics operations queued during Hull/Structure updates.
// BallastFlora growth (from Hull.Update) may queue physics body creations/transforms.
PhysicsBodyQueue.ProcessPendingOperations();
#if CLIENT
// Hull Cheats need to be executed after Hull update
Hull.UpdateCheats(deltaTime, cam);
@@ -818,19 +695,8 @@ namespace Barotrauma
var shuffledGaps = gapList.OrderBy(g => Rand.Int(int.MaxValue)).ToList();
Parallel.ForEach(gapList, parallelOptions, gap =>
{
PhysicsBodyQueue.IsInParallelContext = true;
try
{
gap.Update(deltaTime, cam);
}
finally
{
PhysicsBodyQueue.IsInParallelContext = false;
}
gap.Update(deltaTime, cam);
});
// Process any physics operations queued during Gap updates.
PhysicsBodyQueue.ProcessPendingOperations();
#if CLIENT
sw.Stop();
@@ -846,19 +712,11 @@ namespace Barotrauma
try
{
Parallel.ForEach(itemList, parallelOptions, item =>
foreach (Item item in itemList)
{
PhysicsBodyQueue.IsInParallelContext = true;
try
{
lastUpdatedItem = item;
item.Update(scaledDeltaTime, cam);
}
finally
{
PhysicsBodyQueue.IsInParallelContext = false;
}
});
lastUpdatedItem = item;
item.Update(scaledDeltaTime, cam);
}
}
catch (InvalidOperationException e)
{
@@ -869,10 +727,6 @@ namespace Barotrauma
throw new InvalidOperationException($"Error while updating item {lastUpdatedItem?.Name ?? "null"}", innerException: e);
}
// Process any physics operations that were queued during the parallel update.
// This must be done on the main thread because Farseer Physics is not thread-safe.
PhysicsBodyQueue.ProcessPendingOperations();
UpdateAllProjSpecific(scaledDeltaTime);
Spawner?.Update();
@@ -7,7 +7,6 @@ using Microsoft.Xna.Framework;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Xml.Linq;
using System.Collections.Immutable;
using Barotrauma.Abilities;
@@ -19,63 +18,6 @@ using Barotrauma.Lights;
namespace Barotrauma
{
/// <summary>
/// Thread-safe wrapper for Structure list operations.
/// Uses copy-on-write pattern for lock-free reads.
/// </summary>
internal class ThreadSafeStructureList : IEnumerable<Structure>
{
private volatile List<Structure> _list = new List<Structure>();
private readonly object _writeLock = new object();
public int Count => _list.Count;
public void Add(Structure structure)
{
lock (_writeLock)
{
var newList = new List<Structure>(_list) { structure };
Interlocked.Exchange(ref _list, newList);
}
}
public bool Remove(Structure structure)
{
lock (_writeLock)
{
var newList = new List<Structure>(_list);
bool removed = newList.Remove(structure);
if (removed)
{
Interlocked.Exchange(ref _list, newList);
}
return removed;
}
}
public void Clear()
{
Interlocked.Exchange(ref _list, new List<Structure>());
}
public bool Contains(Structure structure) => _list.Contains(structure);
public Structure this[int index] => _list[index];
public IEnumerator<Structure> GetEnumerator() => _list.GetEnumerator();
System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() => GetEnumerator();
// LINQ-friendly methods
public List<Structure> ToList() => new List<Structure>(_list);
public Structure FirstOrDefault(Func<Structure, bool> predicate) => _list.FirstOrDefault(predicate);
public Structure Find(Predicate<Structure> predicate) => _list.Find(predicate);
public List<Structure> FindAll(Predicate<Structure> predicate) => _list.FindAll(predicate);
public IEnumerable<Structure> Where(Func<Structure, bool> predicate) => _list.Where(predicate);
public bool Any() => _list.Any();
public bool Any(Func<Structure, bool> predicate) => _list.Any(predicate);
public void ForEach(Action<Structure> action) => _list.ForEach(action);
}
partial class WallSection : IIgnorable
{
public Rectangle rect;
@@ -106,7 +48,7 @@ namespace Barotrauma
partial class Structure : MapEntity, IDamageable, IServerSerializable, ISerializableEntity
{
public const int WallSectionSize = 96;
public static ThreadSafeStructureList WallList = new ThreadSafeStructureList();
public static List<Structure> WallList = new List<Structure>();
const float LeakThreshold = 0.1f;
const float BigGapThreshold = 0.7f;
@@ -12,70 +12,11 @@ using System.Collections.Generic;
using System.Collections.Immutable;
using System.Diagnostics;
using System.Linq;
using System.Threading;
using System.Xml.Linq;
using Voronoi2;
namespace Barotrauma
{
/// <summary>
/// Thread-safe wrapper for Submarine list operations.
/// Uses copy-on-write pattern for lock-free reads.
/// </summary>
internal class ThreadSafeSubmarineList : IEnumerable<Submarine>
{
private volatile List<Submarine> _list = new List<Submarine>();
private readonly object _writeLock = new object();
public int Count => _list.Count;
public void Add(Submarine submarine)
{
lock (_writeLock)
{
var newList = new List<Submarine>(_list) { submarine };
Interlocked.Exchange(ref _list, newList);
}
}
public bool Remove(Submarine submarine)
{
lock (_writeLock)
{
var newList = new List<Submarine>(_list);
bool removed = newList.Remove(submarine);
if (removed)
{
Interlocked.Exchange(ref _list, newList);
}
return removed;
}
}
public void Clear()
{
Interlocked.Exchange(ref _list, new List<Submarine>());
}
public bool Contains(Submarine submarine) => _list.Contains(submarine);
public Submarine this[int index] => _list[index];
public IEnumerator<Submarine> GetEnumerator() => _list.GetEnumerator();
System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() => GetEnumerator();
// LINQ-friendly methods
public List<Submarine> ToList() => new List<Submarine>(_list);
public Submarine FirstOrDefault(Func<Submarine, bool> predicate) => _list.FirstOrDefault(predicate);
public Submarine Find(Predicate<Submarine> predicate) => _list.Find(predicate);
public List<Submarine> FindAll(Predicate<Submarine> predicate) => _list.FindAll(predicate);
public IEnumerable<Submarine> Where(Func<Submarine, bool> predicate) => _list.Where(predicate);
public bool Any() => _list.Any();
public bool Any(Func<Submarine, bool> predicate) => _list.Any(predicate);
public float Sum(Func<Submarine, float> selector) => _list.Sum(selector);
public IEnumerable<TResult> Select<TResult>(Func<Submarine, TResult> selector) => _list.Select(selector);
}
public enum Direction : byte
{
None = 0, Left = 1, Right = 2
@@ -131,7 +72,7 @@ namespace Barotrauma
get { return MainSubs[0]; }
set { MainSubs[0] = value; }
}
private static readonly ThreadSafeSubmarineList loaded = new ThreadSafeSubmarineList();
private static readonly List<Submarine> loaded = new List<Submarine>();
private readonly Identifier upgradeEventIdentifier;
@@ -156,11 +97,10 @@ namespace Barotrauma
}
}
// ThreadLocal for thread-safe ray casting results
private static readonly ThreadLocal<Vector2> lastPickedPositionLocal = new ThreadLocal<Vector2>();
private static readonly ThreadLocal<float> lastPickedFractionLocal = new ThreadLocal<float>();
private static readonly ThreadLocal<Fixture> lastPickedFixtureLocal = new ThreadLocal<Fixture>();
private static readonly ThreadLocal<Vector2> lastPickedNormalLocal = new ThreadLocal<Vector2>();
private static Vector2 lastPickedPosition;
private static float lastPickedFraction;
private static Fixture lastPickedFixture;
private static Vector2 lastPickedNormal;
private Vector2 prevPosition;
@@ -174,22 +114,22 @@ namespace Barotrauma
public static Vector2 LastPickedPosition
{
get { return lastPickedPositionLocal.Value; }
get { return lastPickedPosition; }
}
public static float LastPickedFraction
{
get { return lastPickedFractionLocal.Value; }
get { return lastPickedFraction; }
}
public static Fixture LastPickedFixture
{
get { return lastPickedFixtureLocal.Value; }
get { return lastPickedFixture; }
}
public static Vector2 LastPickedNormal
{
get { return lastPickedNormalLocal.Value; }
get { return lastPickedNormal; }
}
public bool Loading
@@ -206,7 +146,7 @@ namespace Barotrauma
public List<WayPoint> ForcedOutpostModuleWayPoints = new List<WayPoint>();
public static ThreadSafeSubmarineList Loaded
public static List<Submarine> Loaded
{
get { return loaded; }
}
@@ -914,10 +854,10 @@ namespace Barotrauma
}, ref aabb);
if (closestFraction <= 0.0f)
{
lastPickedPositionLocal.Value = rayStart;
lastPickedFractionLocal.Value = closestFraction;
lastPickedFixtureLocal.Value = closestFixture;
lastPickedNormalLocal.Value = closestNormal;
lastPickedPosition = rayStart;
lastPickedFraction = closestFraction;
lastPickedFixture = closestFixture;
lastPickedNormal = closestNormal;
return closestBody;
}
}
@@ -936,22 +876,16 @@ namespace Barotrauma
return fraction;
}, rayStart, rayEnd, collisionCategory ?? Category.All);
lastPickedPositionLocal.Value = rayStart + (rayEnd - rayStart) * closestFraction;
lastPickedFractionLocal.Value = closestFraction;
lastPickedFixtureLocal.Value = closestFixture;
lastPickedNormalLocal.Value = closestNormal;
lastPickedPosition = rayStart + (rayEnd - rayStart) * closestFraction;
lastPickedFraction = closestFraction;
lastPickedFixture = closestFixture;
lastPickedNormal = closestNormal;
return closestBody;
}
// ThreadLocal for thread-safe body picking
private static readonly ThreadLocal<Dictionary<Body, float>> bodyDistLocal =
new ThreadLocal<Dictionary<Body, float>>(() => new Dictionary<Body, float>());
private static readonly ThreadLocal<List<Body>> bodiesLocal =
new ThreadLocal<List<Body>>(() => new List<Body>());
private static Dictionary<Body, float> bodyDist => bodyDistLocal.Value;
private static List<Body> bodies => bodiesLocal.Value;
private static readonly Dictionary<Body, float> bodyDist = new Dictionary<Body, float>();
private static readonly List<Body> bodies = new List<Body>();
public static float LastPickedBodyDist(Body body)
{
@@ -985,10 +919,10 @@ namespace Barotrauma
}
if (fraction < closestFraction)
{
lastPickedPositionLocal.Value = rayStart + (rayEnd - rayStart) * fraction;
lastPickedFractionLocal.Value = fraction;
lastPickedNormalLocal.Value = normal;
lastPickedFixtureLocal.Value = fixture;
lastPickedPosition = rayStart + (rayEnd - rayStart) * fraction;
lastPickedFraction = fraction;
lastPickedNormal = normal;
lastPickedFixture = fixture;
}
//continue
return -1;
@@ -1006,10 +940,10 @@ namespace Barotrauma
if (!fixture.Shape.TestPoint(ref transform, ref rayStart)) { return true; }
closestFraction = 0.0f;
lastPickedPositionLocal.Value = rayStart;
lastPickedFractionLocal.Value = 0.0f;
lastPickedNormalLocal.Value = Vector2.Normalize(rayEnd - rayStart);
lastPickedFixtureLocal.Value = fixture;
lastPickedPosition = rayStart;
lastPickedFraction = 0.0f;
lastPickedNormal = Vector2.Normalize(rayEnd - rayStart);
lastPickedFixture = fixture;
bodies.Add(fixture.Body);
bodyDist[fixture.Body] = 0.0f;
return false;
@@ -1077,7 +1011,7 @@ namespace Barotrauma
if (Vector2.DistanceSquared(rayStart, rayEnd) < 0.01f)
{
lastPickedPositionLocal.Value = rayEnd;
lastPickedPosition = rayEnd;
return null;
}
@@ -1119,10 +1053,10 @@ namespace Barotrauma
, rayStart, rayEnd);
lastPickedPositionLocal.Value = rayStart + (rayEnd - rayStart) * closestFraction;
lastPickedFractionLocal.Value = closestFraction;
lastPickedFixtureLocal.Value = closestFixture;
lastPickedNormalLocal.Value = closestNormal;
lastPickedPosition = rayStart + (rayEnd - rayStart) * closestFraction;
lastPickedFraction = closestFraction;
lastPickedFixture = closestFixture;
lastPickedNormal = closestNormal;
return closestBody;
}
@@ -1143,7 +1077,7 @@ namespace Barotrauma
Item.UpdateHulls();
List<Item> bodyItems = Item.ItemList.Where(it => it.Submarine == this && it.body != null).ToList();
List<Item> bodyItems = Item.ItemList.FindAll(it => it.Submarine == this && it.body != null);
List<MapEntity> subEntities = MapEntity.MapEntityList.FindAll(me => me.Submarine == this);
foreach (MapEntity e in subEntities)
@@ -1577,9 +1511,9 @@ namespace Barotrauma
public List<WayPoint> GetWaypoints(bool alsoFromConnectedSubs) => GetEntities(alsoFromConnectedSubs, WayPoint.WayPointList);
public List<Structure> GetWalls(bool alsoFromConnectedSubs) => GetEntities(alsoFromConnectedSubs, Structure.WallList);
public List<T> GetEntities<T>(bool includingConnectedSubs, IEnumerable<T> list) where T : MapEntity
public List<T> GetEntities<T>(bool includingConnectedSubs, List<T> list) where T : MapEntity
{
return list.Where(e => IsEntityFoundOnThisSub(e, includingConnectedSubs)).ToList();
return list.FindAll(e => IsEntityFoundOnThisSub(e, includingConnectedSubs));
}
public List<(ItemContainer container, int freeSlots)> GetCargoContainers()
@@ -1604,6 +1538,11 @@ namespace Barotrauma
return containers;
}
public IEnumerable<T> GetEntities<T>(bool includingConnectedSubs, IEnumerable<T> list) where T : MapEntity
{
return list.Where(e => IsEntityFoundOnThisSub(e, includingConnectedSubs));
}
public bool IsEntityFoundOnThisSub(MapEntity entity, bool includingConnectedSubs, bool allowDifferentTeam = false, bool allowDifferentType = false)
{
if (entity == null) { return false; }
@@ -1727,8 +1666,9 @@ namespace Barotrauma
HiddenSubPosition += Vector2.UnitY * GameMain.GameSession.LevelData.Size.Y;
}
foreach (Submarine sub in loaded)
for (int i = 0; i < loaded.Count; i++)
{
Submarine sub = loaded[i];
HiddenSubPosition =
new Vector2(
//1st sub on the left side, 2nd on the right, etc
@@ -1860,9 +1800,10 @@ namespace Barotrauma
}
entityGrid = Hull.GenerateEntityGrid(this);
foreach (MapEntity me in MapEntity.MapEntityList.Where(e => e.Submarine == this))
for (int i = 0; i < MapEntity.MapEntityList.Count; i++)
{
me.Move(HiddenSubPosition, ignoreContacts: true);
if (MapEntity.MapEntityList[i].Submarine != this) { continue; }
MapEntity.MapEntityList[i].Move(HiddenSubPosition, ignoreContacts: true);
}
Loading = false;
@@ -2207,7 +2148,7 @@ namespace Barotrauma
DebugConsole.ThrowError("Error while removing \"" + item.Name + "\"!", e);
}
}
Item.ClearAllItemCollections();
Item.ItemList.Clear();
}
Ragdoll.RemoveAll();
@@ -2216,7 +2157,7 @@ namespace Barotrauma
GameMain.World = null;
Powered.Grids.Clear();
Powered.ClearChangedConnections();
Powered.ChangedConnections.Clear();
GC.Collect();
@@ -2256,7 +2197,7 @@ namespace Barotrauma
ConnectedDockingPorts?.Clear();
Powered.ClearChangedConnections();
Powered.ChangedConnections.Clear();
Powered.Grids.Clear();
loaded.Remove(this);
@@ -5,75 +5,17 @@ using Microsoft.Xna.Framework;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Xml.Linq;
using Barotrauma.Extensions;
namespace Barotrauma
{
/// <summary>
/// Thread-safe wrapper for WayPoint list operations.
/// Uses copy-on-write pattern for lock-free reads.
/// </summary>
internal class ThreadSafeWayPointList : IEnumerable<WayPoint>
{
private volatile List<WayPoint> _list = new List<WayPoint>();
private readonly object _writeLock = new object();
public int Count => _list.Count;
public void Add(WayPoint waypoint)
{
lock (_writeLock)
{
var newList = new List<WayPoint>(_list) { waypoint };
Interlocked.Exchange(ref _list, newList);
}
}
public bool Remove(WayPoint waypoint)
{
lock (_writeLock)
{
var newList = new List<WayPoint>(_list);
bool removed = newList.Remove(waypoint);
if (removed)
{
Interlocked.Exchange(ref _list, newList);
}
return removed;
}
}
public void Clear()
{
Interlocked.Exchange(ref _list, new List<WayPoint>());
}
public bool Contains(WayPoint waypoint) => _list.Contains(waypoint);
public WayPoint this[int index] => _list[index];
public IEnumerator<WayPoint> GetEnumerator() => _list.GetEnumerator();
System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() => GetEnumerator();
// LINQ-friendly methods
public List<WayPoint> ToList() => new List<WayPoint>(_list);
public WayPoint FirstOrDefault(Func<WayPoint, bool> predicate) => _list.FirstOrDefault(predicate);
public WayPoint Find(Predicate<WayPoint> predicate) => _list.Find(predicate);
public List<WayPoint> FindAll(Predicate<WayPoint> predicate) => _list.FindAll(predicate);
public IEnumerable<WayPoint> Where(Func<WayPoint, bool> predicate) => _list.Where(predicate);
public bool Any() => _list.Any();
public bool Any(Func<WayPoint, bool> predicate) => _list.Any(predicate);
public bool Exists(Predicate<WayPoint> predicate) => _list.Exists(predicate);
}
[Flags]
public enum SpawnType { Path = 0, Human = 1, Enemy = 2, Cargo = 4, Corpse = 8, Submarine = 16, ExitPoint = 32, Disabled = 64 };
partial class WayPoint : MapEntity
{
public static ThreadSafeWayPointList WayPointList = new ThreadSafeWayPointList();
public static List<WayPoint> WayPointList = new List<WayPoint>();
public static bool ShowWayPoints = true, ShowSpawnPoints = true;
@@ -990,7 +932,7 @@ namespace Barotrauma
public static WayPoint GetRandom(SpawnType spawnType = SpawnType.Human, JobPrefab assignedJob = null, Submarine sub = null, bool useSyncedRand = false, string spawnPointTag = null, bool ignoreSubmarine = false)
{
return WayPointList.ToList().GetRandom(wp =>
return WayPointList.GetRandom(wp =>
(ignoreSubmarine || wp.Submarine == sub) &&
//checking for the disabled flag is not strictly necessary because we check for equality of the spawn type,
//but lets do that anyway in case we change the handling of the spawn type at some point