Make collections thread-safe for AI and character systems

Refactored various collections (lists, dictionaries, queues, and caches) in AI, character, animation, and item component systems to use thread-safe patterns and concurrent data structures. This includes introducing copy-on-write wrappers, ConcurrentDictionary, ConcurrentQueue, and ThreadLocal where appropriate to ensure safe concurrent access and mutation, improving stability in multi-threaded scenarios.
This commit is contained in:
Eero
2025-12-28 12:53:10 +08:00
parent 46595b1399
commit 31812d524d
12 changed files with 281 additions and 122 deletions
@@ -5,8 +5,10 @@ using FarseerPhysics.Dynamics.Contacts;
using FarseerPhysics.Dynamics.Joints;
using Microsoft.Xna.Framework;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Xml.Linq;
using Barotrauma.Extensions;
using LimbParams = Barotrauma.RagdollParams.LimbParams;
@@ -25,7 +27,33 @@ namespace Barotrauma
/// </summary>
const float MaxImpactDamage = 0.1f;
private static readonly List<Ragdoll> list = new List<Ragdoll>();
// Thread-safe list using copy-on-write pattern (ConcurrentBag doesn't support indexer/Remove)
private static volatile List<Ragdoll> _list = new List<Ragdoll>();
private static readonly object _listLock = new object();
private static List<Ragdoll> list => _list;
private static void ListAdd(Ragdoll ragdoll)
{
lock (_listLock)
{
var newList = new List<Ragdoll>(_list) { ragdoll };
Interlocked.Exchange(ref _list, newList);
}
}
private static bool ListRemove(Ragdoll ragdoll)
{
lock (_listLock)
{
var newList = new List<Ragdoll>(_list);
bool removed = newList.Remove(ragdoll);
if (removed)
{
Interlocked.Exchange(ref _list, newList);
}
return removed;
}
}
struct Impact
{
@@ -45,7 +73,8 @@ namespace Barotrauma
}
}
private readonly Queue<Impact> impactQueue = new Queue<Impact>();
// Thread-safe queue for physics collision callbacks
private readonly ConcurrentQueue<Impact> impactQueue = new ConcurrentQueue<Impact>();
protected Hull currentHull;
@@ -467,7 +496,7 @@ namespace Barotrauma
public Ragdoll(Character character, string seed, RagdollParams ragdollParams = null)
{
list.Add(this);
ListAdd(this);
this.character = character;
Recreate(ragdollParams ?? RagdollParams);
}
@@ -744,10 +773,7 @@ namespace Barotrauma
{
if (!f2.IsSensor)
{
lock (impactQueue)
{
impactQueue.Enqueue(new Impact(f1, f2, contact, velocity));
}
impactQueue.Enqueue(new Impact(f1, f2, contact, velocity));
}
return true;
}
@@ -819,10 +845,7 @@ namespace Barotrauma
}
}
lock (impactQueue)
{
impactQueue.Enqueue(new Impact(f1, f2, contact, velocity));
}
impactQueue.Enqueue(new Impact(f1, f2, contact, velocity));
return true;
}
@@ -1274,9 +1297,8 @@ namespace Barotrauma
{
if (!character.Enabled || character.Removed || Frozen || Invalid || Collider == null || Collider.Removed) { return; }
while (impactQueue.Count > 0)
while (impactQueue.TryDequeue(out var impact))
{
var impact = impactQueue.Dequeue();
ApplyImpact(impact.F1, impact.F2, impact.LocalNormal, impact.ImpactPos, impact.Velocity);
}
@@ -2325,7 +2347,7 @@ namespace Barotrauma
LimbJoints = null;
}
list.Remove(this);
ListRemove(this);
}
public static void RemoveAll()