Make entity lists thread-safe with copy-on-write wrappers
Replaced static entity lists (e.g., HullList, GapList, MapEntityList, etc.) with thread-safe copy-on-write wrappers to improve concurrency and prevent race conditions. Updated usages and related methods to support the new thread-safe collections, ensuring atomic operations and lock-free reads throughout the codebase.
This commit is contained in:
@@ -156,7 +156,8 @@ namespace Barotrauma
|
||||
Reactor reactor = item.GetComponent<Reactor>();
|
||||
if (reactor != null && reactor.Item.Condition > 0.0f) { roundData.Reactors.Add(reactor); }
|
||||
}
|
||||
pathFinder = new PathFinder(WayPoint.WayPointList, false);
|
||||
|
||||
pathFinder = new PathFinder(WayPoint.WayPointList.ToList(), false);
|
||||
cachedDistances.Clear();
|
||||
|
||||
#if CLIENT
|
||||
@@ -323,7 +324,7 @@ namespace Barotrauma
|
||||
|
||||
static CachedDistance CalculateNewCachedDistance(Character c)
|
||||
{
|
||||
pathFinder ??= new PathFinder(WayPoint.WayPointList, false);
|
||||
pathFinder ??= new PathFinder(WayPoint.WayPointList.ToList(), false);
|
||||
var path = pathFinder.FindPath(ConvertUnits.ToSimUnits(c.WorldPosition), ConvertUnits.ToSimUnits(Submarine.MainSub.WorldPosition));
|
||||
if (path.Unreachable) { return null; }
|
||||
return new CachedDistance(c.WorldPosition, Submarine.MainSub.WorldPosition, path.TotalLength, Timing.TotalTime + Rand.Range(1.0f, 5.0f));
|
||||
|
||||
@@ -160,7 +160,7 @@ namespace Barotrauma
|
||||
MissionAction.ResetMissionsUnlockedThisRound();
|
||||
UnlockPathAction.ResetPathsUnlockedThisRound();
|
||||
#endif
|
||||
pathFinder = new PathFinder(WayPoint.WayPointList, false);
|
||||
pathFinder = new PathFinder(WayPoint.WayPointList.ToList(), false);
|
||||
totalPathLength = 0.0f;
|
||||
if (level != null)
|
||||
{
|
||||
|
||||
@@ -283,7 +283,7 @@ namespace Barotrauma
|
||||
|
||||
if (!IsClient)
|
||||
{
|
||||
PathFinder pathFinder = new PathFinder(WayPoint.WayPointList, false);
|
||||
PathFinder pathFinder = new PathFinder(WayPoint.WayPointList.ToList(), false);
|
||||
var path = pathFinder.FindPath(ConvertUnits.ToSimUnits(patrolPos), ConvertUnits.ToSimUnits(preferredSpawnPos));
|
||||
if (!path.Unreachable)
|
||||
{
|
||||
|
||||
@@ -637,7 +637,7 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
if (pathFinder == null)
|
||||
{
|
||||
pathFinder = new PathFinder(WayPoint.WayPointList, false)
|
||||
pathFinder = new PathFinder(WayPoint.WayPointList.ToList(), false)
|
||||
{
|
||||
GetNodePenalty = GetNodePenalty
|
||||
};
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using Microsoft.Xna.Framework;
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using Barotrauma.IO;
|
||||
@@ -20,10 +21,10 @@ namespace Barotrauma
|
||||
|
||||
public const ushort MaxEntityCount = ushort.MaxValue - 4; //ushort.MaxValue - 4 because the 4 values above are reserved values
|
||||
|
||||
private static readonly Dictionary<ushort, Entity> dictionary = new Dictionary<ushort, Entity>();
|
||||
private static readonly ConcurrentDictionary<ushort, Entity> dictionary = new ConcurrentDictionary<ushort, Entity>();
|
||||
public static IReadOnlyCollection<Entity> GetEntities()
|
||||
{
|
||||
return dictionary.Values;
|
||||
return (IReadOnlyCollection<Entity>)dictionary.Values;
|
||||
}
|
||||
|
||||
public static int EntityCount => dictionary.Count;
|
||||
@@ -122,13 +123,11 @@ namespace Barotrauma
|
||||
//give a unique ID
|
||||
ID = DetermineID(id, submarine);
|
||||
|
||||
if (dictionary.ContainsKey(ID))
|
||||
if (!dictionary.TryAdd(ID, this))
|
||||
{
|
||||
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);
|
||||
@@ -324,7 +323,7 @@ namespace Barotrauma
|
||||
}
|
||||
else
|
||||
{
|
||||
dictionary.Remove(ID);
|
||||
dictionary.TryRemove(ID, out _);
|
||||
}
|
||||
IdFreed = true;
|
||||
}
|
||||
|
||||
@@ -8,13 +8,71 @@ 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 List<Gap> GapList = new List<Gap>();
|
||||
public static ThreadSafeGapList GapList = new ThreadSafeGapList();
|
||||
|
||||
const float MaxFlowForce = 500.0f;
|
||||
|
||||
|
||||
@@ -14,6 +14,116 @@ 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;
|
||||
@@ -114,8 +224,8 @@ namespace Barotrauma
|
||||
|
||||
partial class Hull : MapEntity, ISerializableEntity, IServerSerializable
|
||||
{
|
||||
public readonly static List<Hull> HullList = new List<Hull>();
|
||||
public readonly static List<EntityGrid> EntityGrids = new List<EntityGrid>();
|
||||
public readonly static ThreadSafeHullList HullList = new ThreadSafeHullList();
|
||||
public readonly static ThreadSafeEntityGridList EntityGrids = new ThreadSafeEntityGridList();
|
||||
|
||||
public static bool ShowHulls = true;
|
||||
|
||||
|
||||
@@ -6,14 +6,111 @@ 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 List<MapEntity> MapEntityList = new List<MapEntity>();
|
||||
public readonly static ThreadSafeMapEntityList MapEntityList = new ThreadSafeMapEntityList();
|
||||
|
||||
public readonly MapEntityPrefab Prefab;
|
||||
|
||||
@@ -559,45 +656,51 @@ namespace Barotrauma
|
||||
return;
|
||||
}
|
||||
|
||||
//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)
|
||||
// Use atomic insertion to ensure thread-safety
|
||||
MapEntityList.InsertWithAction(this, (list, entity) =>
|
||||
{
|
||||
//insertion sort according to draw depth
|
||||
float drawDepth = structure.SpriteDepth;
|
||||
while (i < MapEntityList.Count)
|
||||
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)
|
||||
{
|
||||
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);
|
||||
//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);
|
||||
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 < MapEntityList.Count)
|
||||
{
|
||||
i++;
|
||||
Sprite existingSprite = MapEntityList[i - 1].Sprite;
|
||||
if (existingSprite == null) { continue; }
|
||||
if (existingSprite.Texture == this.Sprite.Texture) { break; }
|
||||
}
|
||||
i = 0;
|
||||
while (i < list.Count)
|
||||
{
|
||||
i++;
|
||||
Sprite existingSprite = list[i - 1].Sprite;
|
||||
if (existingSprite == null) { continue; }
|
||||
if (existingSprite.Texture == mapEntity.Sprite?.Texture) { break; }
|
||||
}
|
||||
#endif
|
||||
MapEntityList.Insert(i, this);
|
||||
list.Insert(i, entity);
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -7,6 +7,7 @@ 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;
|
||||
@@ -18,6 +19,63 @@ 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;
|
||||
@@ -48,7 +106,7 @@ namespace Barotrauma
|
||||
partial class Structure : MapEntity, IDamageable, IServerSerializable, ISerializableEntity
|
||||
{
|
||||
public const int WallSectionSize = 96;
|
||||
public static List<Structure> WallList = new List<Structure>();
|
||||
public static ThreadSafeStructureList WallList = new ThreadSafeStructureList();
|
||||
|
||||
const float LeakThreshold = 0.1f;
|
||||
const float BigGapThreshold = 0.7f;
|
||||
|
||||
@@ -18,6 +18,64 @@ 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
|
||||
@@ -73,7 +131,7 @@ namespace Barotrauma
|
||||
get { return MainSubs[0]; }
|
||||
set { MainSubs[0] = value; }
|
||||
}
|
||||
private static readonly List<Submarine> loaded = new List<Submarine>();
|
||||
private static readonly ThreadSafeSubmarineList loaded = new ThreadSafeSubmarineList();
|
||||
|
||||
private readonly Identifier upgradeEventIdentifier;
|
||||
|
||||
@@ -148,7 +206,7 @@ namespace Barotrauma
|
||||
|
||||
public List<WayPoint> ForcedOutpostModuleWayPoints = new List<WayPoint>();
|
||||
|
||||
public static List<Submarine> Loaded
|
||||
public static ThreadSafeSubmarineList Loaded
|
||||
{
|
||||
get { return loaded; }
|
||||
}
|
||||
@@ -1515,13 +1573,13 @@ namespace Barotrauma
|
||||
|
||||
public List<Hull> GetHulls(bool alsoFromConnectedSubs) => GetEntities(alsoFromConnectedSubs, Hull.HullList);
|
||||
public List<Gap> GetGaps(bool alsoFromConnectedSubs) => GetEntities(alsoFromConnectedSubs, Gap.GapList);
|
||||
public List<Item> GetItems(bool alsoFromConnectedSubs) => GetEntities(alsoFromConnectedSubs, Item.ItemList).ToList();
|
||||
public List<Item> GetItems(bool alsoFromConnectedSubs) => GetEntities(alsoFromConnectedSubs, Item.ItemList);
|
||||
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, List<T> list) where T : MapEntity
|
||||
public List<T> GetEntities<T>(bool includingConnectedSubs, IEnumerable<T> list) where T : MapEntity
|
||||
{
|
||||
return list.FindAll(e => IsEntityFoundOnThisSub(e, includingConnectedSubs));
|
||||
return list.Where(e => IsEntityFoundOnThisSub(e, includingConnectedSubs)).ToList();
|
||||
}
|
||||
|
||||
public List<(ItemContainer container, int freeSlots)> GetCargoContainers()
|
||||
@@ -1546,11 +1604,6 @@ 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; }
|
||||
@@ -1674,9 +1727,8 @@ namespace Barotrauma
|
||||
HiddenSubPosition += Vector2.UnitY * GameMain.GameSession.LevelData.Size.Y;
|
||||
}
|
||||
|
||||
for (int i = 0; i < loaded.Count; i++)
|
||||
foreach (Submarine sub in loaded)
|
||||
{
|
||||
Submarine sub = loaded[i];
|
||||
HiddenSubPosition =
|
||||
new Vector2(
|
||||
//1st sub on the left side, 2nd on the right, etc
|
||||
@@ -1808,10 +1860,9 @@ namespace Barotrauma
|
||||
}
|
||||
entityGrid = Hull.GenerateEntityGrid(this);
|
||||
|
||||
for (int i = 0; i < MapEntity.MapEntityList.Count; i++)
|
||||
foreach (MapEntity me in MapEntity.MapEntityList.Where(e => e.Submarine == this))
|
||||
{
|
||||
if (MapEntity.MapEntityList[i].Submarine != this) { continue; }
|
||||
MapEntity.MapEntityList[i].Move(HiddenSubPosition, ignoreContacts: true);
|
||||
me.Move(HiddenSubPosition, ignoreContacts: true);
|
||||
}
|
||||
|
||||
Loading = false;
|
||||
|
||||
@@ -5,17 +5,75 @@ 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 List<WayPoint> WayPointList = new List<WayPoint>();
|
||||
public static ThreadSafeWayPointList WayPointList = new ThreadSafeWayPointList();
|
||||
|
||||
public static bool ShowWayPoints = true, ShowSpawnPoints = true;
|
||||
|
||||
@@ -932,7 +990,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.GetRandom(wp =>
|
||||
return WayPointList.ToList().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
|
||||
|
||||
@@ -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; }
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user