Improve thread safety and performance in core systems

Refactors event, entity, and physics management to use thread-safe and lock-free data structures (Immutable collections, ConcurrentQueue, ConcurrentDictionary, Channel) for improved concurrency and performance. Replaces O(n) queue lookups with O(1) set/dictionary checks, ensures atomic updates for shared state, and optimizes queue draining and deferred action processing. Updates related code to use new APIs and patterns, and adds documentation for thread safety and workflow.
This commit is contained in:
Eero
2025-12-29 16:47:10 +08:00
parent e167a34f32
commit 7b8275100d
17 changed files with 368 additions and 191 deletions
@@ -1397,12 +1397,12 @@ namespace Barotrauma
if (me.SimPosition.Length() > 2000.0f)
{
NewMessage("Removed " + me.Name + " (simposition " + me.SimPosition + ")", Color.Orange);
MapEntity.MapEntityList.RemoveAt(i);
MapEntity.MapEntityList.Remove(me);
}
else if (!me.ShouldBeSaved)
{
NewMessage("Removed " + me.Name + " (!ShouldBeSaved)", Color.Orange);
MapEntity.MapEntityList.RemoveAt(i);
MapEntity.MapEntityList.Remove(me);
}
else if (me is Item)
{
@@ -28,7 +28,7 @@ namespace Barotrauma
public void DebugDraw(SpriteBatch spriteBatch)
{
foreach (Event ev in activeEvents)
foreach (Event ev in _activeEvents)
{
Vector2 drawPos = ev.DebugDrawPos;
drawPos.Y = -drawPos.Y;
@@ -41,7 +41,7 @@ namespace Barotrauma
public void DebugDrawHUD(SpriteBatch spriteBatch, float y)
{
foreach (ScriptedEvent scriptedEvent in activeEvents.Where(ev => !ev.IsFinished && ev is ScriptedEvent).Cast<ScriptedEvent>())
foreach (ScriptedEvent scriptedEvent in _activeEvents.Where(ev => !ev.IsFinished && ev is ScriptedEvent).Cast<ScriptedEvent>())
{
DrawEventTargetTags(spriteBatch, scriptedEvent);
}
@@ -156,7 +156,7 @@ namespace Barotrauma
{
if (isGraphHovered || isGraphSelected)
{
foreach (var timeStamp in timeStamps)
foreach (var timeStamp in _timeStamps)
{
int t = (int)Math.Abs(Math.Round((timeStamp.Time - lastIntensityUpdate) / intensityGraphUpdateInterval));
if (t == order)
@@ -205,7 +205,7 @@ namespace Barotrauma
}
adjustedYStep = GUI.AdjustForTextScale(12);
foreach (EventSet eventSet in pendingEventSets)
foreach (EventSet eventSet in _pendingEventSets)
{
if (Submarine.MainSub == null) { break; }
@@ -263,7 +263,7 @@ namespace Barotrauma
y += yStep;
adjustedYStep = GUI.AdjustForTextScale(18);
foreach (Event ev in activeEvents.Where(ev => !ev.IsFinished || PlayerInput.IsShiftDown()))
foreach (Event ev in _activeEvents.Where(ev => !ev.IsFinished || PlayerInput.IsShiftDown()))
{
GUI.DrawString(spriteBatch, new Vector2(x + 5, y), ev.ToString(), (!ev.IsFinished ? Color.White : Color.Red) * 0.8f, null, 0, GUIStyle.SmallFont);
@@ -752,4 +752,4 @@ namespace Barotrauma
entry.CanBeCompleted);
}
}
}
}
@@ -1913,7 +1913,7 @@ namespace Barotrauma.Items.Components
void CalculateDistance()
{
pathFinder ??= new PathFinder(WayPoint.WayPointList, false);
pathFinder ??= new PathFinder(WayPoint.WayPointList.ToList(), false);
var path = pathFinder.FindPath(ConvertUnits.ToSimUnits(transducerPosition), ConvertUnits.ToSimUnits(worldPosition));
if (!path.Unreachable)
{
@@ -105,7 +105,7 @@ namespace Barotrauma
public static void Draw(SpriteBatch spriteBatch, bool editing = false)
{
var entitiesToRender = !editing && visibleEntities != null ? visibleEntities : MapEntity.MapEntityList;
var entitiesToRender = !editing && visibleEntities != null ? visibleEntities : MapEntity.MapEntityList.ToList();
foreach (MapEntity e in entitiesToRender)
{
@@ -115,7 +115,7 @@ namespace Barotrauma
public static void DrawFront(SpriteBatch spriteBatch, bool editing = false, Predicate<MapEntity> predicate = null)
{
var entitiesToRender = !editing && visibleEntities != null ? visibleEntities : MapEntity.MapEntityList;
var entitiesToRender = !editing && visibleEntities != null ? visibleEntities : MapEntity.MapEntityList.ToList();
foreach (MapEntity e in entitiesToRender)
{
@@ -164,7 +164,7 @@ namespace Barotrauma
public static void DrawDamageable(SpriteBatch spriteBatch, Effect damageEffect, bool editing = false, Predicate<MapEntity> predicate = null)
{
var entitiesToRender = !editing && visibleEntities != null ? visibleEntities : MapEntity.MapEntityList;
var entitiesToRender = !editing && visibleEntities != null ? visibleEntities : MapEntity.MapEntityList.ToList();
depthSortedDamageable.Clear();
@@ -197,7 +197,7 @@ namespace Barotrauma
public static void DrawPaintedColors(SpriteBatch spriteBatch, bool editing = false, Predicate<MapEntity> predicate = null)
{
var entitiesToRender = !editing && visibleEntities != null ? visibleEntities : MapEntity.MapEntityList;
var entitiesToRender = !editing && visibleEntities != null ? visibleEntities : MapEntity.MapEntityList.ToList();
foreach (MapEntity e in entitiesToRender)
{
@@ -217,7 +217,7 @@ namespace Barotrauma
public static void DrawBack(SpriteBatch spriteBatch, bool editing = false, Predicate<MapEntity> predicate = null)
{
var entitiesToRender = !editing && visibleEntities != null ? visibleEntities : MapEntity.MapEntityList;
var entitiesToRender = !editing && visibleEntities != null ? visibleEntities : MapEntity.MapEntityList.ToList();
foreach (MapEntity e in entitiesToRender)
{
@@ -1,12 +1,32 @@
using Barotrauma.Items.Components;
using Barotrauma.Networking;
using System.Collections.Concurrent;
using System.Collections.Generic;
namespace Barotrauma
{
partial class EntitySpawner : Entity, IServerSerializable
{
public readonly List<(Entity entity, bool isRemoval)> receivedEvents = new List<(Entity entity, bool isRemoval)>();
/// <summary>
/// Thread-safe queue for received entity spawn/remove events from the server.
/// </summary>
private readonly ConcurrentQueue<(Entity entity, bool isRemoval)> receivedEventsQueue = new ConcurrentQueue<(Entity entity, bool isRemoval)>();
/// <summary>
/// Gets a thread-safe snapshot of received events.
/// </summary>
public IEnumerable<(Entity entity, bool isRemoval)> GetReceivedEventsSnapshot()
{
return receivedEventsQueue.ToArray();
}
/// <summary>
/// Clears all received events from the queue.
/// </summary>
partial void ResetReceivedEvents()
{
while (receivedEventsQueue.TryDequeue(out _)) { }
}
public void ClientEventRead(IReadMessage message, float sendingTime)
{
@@ -34,7 +54,7 @@ namespace Barotrauma
{
DebugConsole.Log("Received entity removal message for ID " + entityId + ". Entity with a matching ID not found.");
}
receivedEvents.Add((entity, true));
receivedEventsQueue.Enqueue((entity, true));
}
else
{
@@ -57,7 +77,7 @@ namespace Barotrauma
GameAnalyticsManager.AddDesignEvent("ItemFabricated:" + (GameMain.GameSession?.GameMode?.Preset.Identifier ?? "none".ToIdentifier()) + ":" + newItem.Prefab.Identifier);
}
}
receivedEvents.Add((newItem, false));
receivedEventsQueue.Enqueue((newItem, false));
}
break;
case (byte)SpawnableType.Character:
@@ -68,7 +88,7 @@ namespace Barotrauma
}
else
{
receivedEvents.Add((character, false));
receivedEventsQueue.Enqueue((character, false));
}
break;
default:
@@ -3918,7 +3918,7 @@ namespace Barotrauma.Networking
{
errorLines.Add("");
errorLines.Add("EntitySpawner events:");
foreach ((Entity entity, bool isRemoval) in Entity.Spawner.receivedEvents)
foreach ((Entity entity, bool isRemoval) in Entity.Spawner.GetReceivedEventsSnapshot())
{
errorLines.Add(
(isRemoval ? "Remove " : "Create ") +