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
@@ -1,7 +1,6 @@
using Barotrauma.Extensions;
using Microsoft.Xna.Framework;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Xml.Linq;
@@ -14,12 +13,10 @@ namespace Barotrauma.Items.Components
private readonly HashSet<Connection> signalConnections = new HashSet<Connection>();
private readonly ConcurrentDictionary<Connection, bool> connectionDirty = new ConcurrentDictionary<Connection, bool>();
private readonly Dictionary<Connection, bool> connectionDirty = new Dictionary<Connection, bool>();
//a list of connections a given connection is connected to, either directly or via other power transfer components
//Uses ConcurrentDictionary<Connection, byte> as a thread-safe HashSet replacement
private readonly ConcurrentDictionary<Connection, ConcurrentDictionary<Connection, byte>> connectedRecipients =
new ConcurrentDictionary<Connection, ConcurrentDictionary<Connection, byte>>();
private readonly Dictionary<Connection, HashSet<Connection>> connectedRecipients = new Dictionary<Connection, HashSet<Connection>>();
private float overloadCooldownTimer;
private const float OverloadCooldown = 5.0f;
@@ -135,7 +132,7 @@ namespace Barotrauma.Items.Components
partial void InitProjectSpecific(XElement element);
private static readonly System.Collections.Concurrent.ConcurrentDictionary<PowerTransfer, byte> _recipientsToRefresh = new System.Collections.Concurrent.ConcurrentDictionary<PowerTransfer, byte>();
private static readonly HashSet<PowerTransfer> recipientsToRefresh = new HashSet<PowerTransfer>();
public override void UpdateBroken(float deltaTime, Camera cam)
{
base.UpdateBroken(deltaTime, cam);
@@ -147,21 +144,20 @@ namespace Barotrauma.Items.Components
powerLoad = 0.0f;
currPowerConsumption = 0.0f;
SetAllConnectionsDirty();
_recipientsToRefresh.Clear();
// Take snapshot for thread-safe iteration (no locks needed with ConcurrentDictionary)
foreach (var recipientDict in connectedRecipients.Values)
recipientsToRefresh.Clear();
foreach (HashSet<Connection> recipientList in connectedRecipients.Values)
{
foreach (Connection c in recipientDict.Keys)
foreach (Connection c in recipientList)
{
if (c.Item == item) { continue; }
var recipientPowerTransfer = c.Item.GetComponent<PowerTransfer>();
if (recipientPowerTransfer != null)
{
_recipientsToRefresh.TryAdd(recipientPowerTransfer, 0);
recipientsToRefresh.Add(recipientPowerTransfer);
}
}
}
foreach (PowerTransfer recipientPowerTransfer in _recipientsToRefresh.Keys)
foreach (PowerTransfer recipientPowerTransfer in recipientsToRefresh)
{
recipientPowerTransfer.SetAllConnectionsDirty();
recipientPowerTransfer.RefreshConnections();
@@ -308,56 +304,58 @@ namespace Barotrauma.Items.Components
protected void RefreshConnections()
{
var connections = item.Connections;
if (connections == null) { return; }
// Take a snapshot of connections for thread-safe iteration
var connectionSnapshot = connections.ToList();
foreach (Connection c in connectionSnapshot)
foreach (Connection c in connections)
{
if (!connectionDirty.TryGetValue(c, out bool isDirty))
if (!connectionDirty.ContainsKey(c))
{
connectionDirty[c] = true;
isDirty = true;
}
if (!isDirty)
else if (!connectionDirty[c])
{
continue;
}
//find all connections that are connected to this one (directly or via another PowerTransfer)
var tempConnected = connectedRecipients.GetOrAdd(c, _ => new ConcurrentDictionary<Connection, byte>());
// Get previous recipients and clear
var previousRecipients = tempConnected.Keys.ToList();
tempConnected.Clear();
//mark all previous recipients as dirty
foreach (Connection recipient in previousRecipients)
HashSet<Connection> tempConnected;
if (!connectedRecipients.ContainsKey(c))
{
var pt = recipient.Item.GetComponent<PowerTransfer>();
if (pt != null) { pt.connectionDirty[recipient] = true; }
tempConnected = new HashSet<Connection>();
connectedRecipients.Add(c, tempConnected);
}
else
{
tempConnected = connectedRecipients[c];
tempConnected.Clear();
//mark all previous recipients as dirty
foreach (Connection recipient in tempConnected)
{
var pt = recipient.Item.GetComponent<PowerTransfer>();
if (pt != null) { pt.connectionDirty[recipient] = true; }
}
}
tempConnected.TryAdd(c, 0);
tempConnected.Add(c);
if (item.Condition > 0.0f)
{
GetConnected(c, tempConnected);
//go through all the PowerTransfers that we're connected to and set their connections to match the ones we just calculated
//(no need to go through the recursive GetConnected method again)
// Take snapshot for thread-safe iteration (no locks needed)
var tempConnectedSnapshot = tempConnected.Keys.ToList();
foreach (Connection recipient in tempConnectedSnapshot)
foreach (Connection recipient in tempConnected)
{
if (recipient == c) { continue; }
var recipientPowerTransfer = recipient.Item.GetComponent<PowerTransfer>();
if (recipientPowerTransfer == null) { continue; }
var recipientSet = recipientPowerTransfer.connectedRecipients.GetOrAdd(recipient, _ => new ConcurrentDictionary<Connection, byte>());
recipientSet.Clear();
foreach (var connection in tempConnectedSnapshot)
if (!recipientPowerTransfer.connectedRecipients.ContainsKey(recipient))
{
recipientSet.TryAdd(connection, 0);
recipientPowerTransfer.connectedRecipients.Add(recipient, new HashSet<Connection>());
}
else
{
recipientPowerTransfer.connectedRecipients[recipient].Clear();
}
foreach (var connection in tempConnected)
{
recipientPowerTransfer.connectedRecipients[recipient].Add(connection);
}
recipientPowerTransfer.connectionDirty[recipient] = false;
}
@@ -366,20 +364,19 @@ namespace Barotrauma.Items.Components
}
}
//Finds all the connections that can receive a signal sent into the given connection and stores them in the concurrent dictionary.
private void GetConnected(Connection c, ConcurrentDictionary<Connection, byte> connected)
//Finds all the connections that can receive a signal sent into the given connection and stores them in the hashset.
private void GetConnected(Connection c, HashSet<Connection> connected)
{
// Take snapshot for thread-safe iteration
var recipients = c.Recipients.ToList();
var recipients = c.Recipients;
foreach (Connection recipient in recipients)
{
if (recipient == null || connected.ContainsKey(recipient)) { continue; }
if (recipient == null || connected.Contains(recipient)) { continue; }
Item it = recipient.Item;
if (it == null || it.Condition <= 0.0f) { continue; }
connected.TryAdd(recipient, 0);
connected.Add(recipient);
var powerTransfer = it.GetComponent<PowerTransfer>();
if (powerTransfer != null && powerTransfer.CanTransfer && powerTransfer.IsActive)
@@ -397,14 +394,10 @@ namespace Barotrauma.Items.Components
connectionDirty[c] = true;
if (c.IsPower)
{
MarkConnectionChanged(c);
ChangedConnections.Add(c);
if (connectedRecipients.TryGetValue(c, out var recipients))
{
// No lock needed - ConcurrentDictionary.Keys is thread-safe
foreach (var conn in recipients.Keys.Where(conn => conn.IsPower))
{
MarkConnectionChanged(conn);
}
recipients.Where(c => c.IsPower).ForEach(c => ChangedConnections.Add(c));
}
}
}
@@ -417,14 +410,10 @@ namespace Barotrauma.Items.Components
connectionDirty[connection] = true;
if (connection.IsPower)
{
MarkConnectionChanged(connection);
ChangedConnections.Add(connection);
if (connectedRecipients.TryGetValue(connection, out var recipients))
{
// No lock needed - ConcurrentDictionary.Keys is thread-safe
foreach (var conn in recipients.Keys.Where(conn => conn.IsPower))
{
MarkConnectionChanged(conn);
}
recipients.Where(c => c.IsPower).ForEach(c => ChangedConnections.Add(c));
}
}
}
@@ -463,19 +452,16 @@ namespace Barotrauma.Items.Components
public override void ReceiveSignal(Signal signal, Connection connection)
{
if (item.Condition <= 0.0f || connection.IsPower) { return; }
if (!connectedRecipients.TryGetValue(connection, out var recipients)) { return; }
if (!connectedRecipients.ContainsKey(connection)) { return; }
if (!signalConnections.Contains(connection)) { return; }
// No lock needed - ConcurrentDictionary.Keys is thread-safe
// Use ToList() snapshot for thread-safe iteration
foreach (Connection recipient in recipients.Keys.ToList())
foreach (Connection recipient in connectedRecipients[connection])
{
if (recipient.Item == item || recipient.Item == signal.source) { continue; }
signal.source?.LastSentSignalRecipients.Add(recipient);
// Use ToArray() snapshot for thread-safe iteration
foreach (ItemComponent ic in recipient.Item.Components.ToArray())
foreach (ItemComponent ic in recipient.Item.Components)
{
//other junction boxes don't need to receive the signal in the pass-through signal connections
//because we relay it straight to the connected items without going through the whole chain of junction boxes
@@ -485,8 +471,7 @@ namespace Barotrauma.Items.Components
if (recipient.Effects != null && signal.value != "0" && !string.IsNullOrEmpty(signal.value))
{
// Use ToArray() snapshot for thread-safe iteration
foreach (StatusEffect effect in recipient.Effects.ToArray())
foreach (StatusEffect effect in recipient.Effects)
{
recipient.Item.ApplyStatusEffect(effect, ActionType.OnUse, 1.0f);
}
@@ -499,7 +484,7 @@ namespace Barotrauma.Items.Components
base.RemoveComponentSpecific();
connectedRecipients?.Clear();
connectionDirty?.Clear();
_recipientsToRefresh.Clear();
recipientsToRefresh.Clear();
}
}
}
@@ -1,6 +1,4 @@
using System;
using System.Collections.Concurrent;
using System.Threading;
using Microsoft.Xna.Framework;
using System.Collections.Generic;
using System.Linq;
@@ -64,77 +62,17 @@ namespace Barotrauma.Items.Components
protected const float UpdateInterval = (float)Timing.Step;
/// <summary>
/// List of all powered ItemComponents (thread-safe)
/// List of all powered ItemComponents
/// </summary>
private static readonly ConcurrentDictionary<Powered, byte> _poweredDict = new ConcurrentDictionary<Powered, byte>();
/// <summary>
/// Cached list for iteration - updated when collection changes
/// </summary>
private static volatile List<Powered> _cachedPoweredList;
private static int _poweredListVersion;
private static readonly List<Powered> poweredList = new List<Powered>();
public static IEnumerable<Powered> PoweredList
{
get
{
var cached = _cachedPoweredList;
if (cached != null) return cached;
return GetCachedPoweredList();
}
}
private static List<Powered> GetCachedPoweredList()
{
var newList = _poweredDict.Keys.ToList();
_cachedPoweredList = newList;
return newList;
}
private static void InvalidatePoweredListCache()
{
_cachedPoweredList = null;
Interlocked.Increment(ref _poweredListVersion);
get { return poweredList; }
}
/// <summary>
/// Thread-safe set of changed connections
/// </summary>
private static readonly ConcurrentDictionary<Connection, byte> _changedConnections = new ConcurrentDictionary<Connection, byte>();
/// <summary>
/// Gets all changed connections (snapshot)
/// </summary>
public static ICollection<Connection> ChangedConnections => _changedConnections.Keys;
/// <summary>
/// Add a connection to the changed set
/// </summary>
public static void MarkConnectionChanged(Connection c)
{
_changedConnections.TryAdd(c, 0);
}
/// <summary>
/// Clear all changed connections
/// </summary>
public static void ClearChangedConnections()
{
_changedConnections.Clear();
}
/// <summary>
/// Remove a connection from the changed set
/// </summary>
public static void UnmarkConnectionChanged(Connection c)
{
_changedConnections.TryRemove(c, out _);
}
public static readonly HashSet<Connection> ChangedConnections = new HashSet<Connection>();
/// <summary>
/// Thread-safe grid dictionary
/// </summary>
public readonly static ConcurrentDictionary<int, GridInfo> Grids = new ConcurrentDictionary<int, GridInfo>();
public readonly static Dictionary<int, GridInfo> Grids = new Dictionary<int, GridInfo>();
/// <summary>
/// The amount of power currently consumed by the item. Negative values mean that the item is providing power to connected items
@@ -271,8 +209,7 @@ namespace Barotrauma.Items.Components
public Powered(Item item, ContentXElement element)
: base(item, element)
{
_poweredDict.TryAdd(this, 0);
InvalidatePoweredListCache();
poweredList.Add(this);
InitProjectSpecific(element);
}
@@ -385,20 +322,17 @@ namespace Barotrauma.Items.Components
//don't use cache if there are no existing grids
if (Grids.Count > 0 && useCache)
{
// Take a snapshot of changed connections for iteration
var changedSnapshot = ChangedConnections.ToList();
//delete all grids that were affected
foreach (Connection c in changedSnapshot)
foreach (Connection c in ChangedConnections)
{
if (c.Grid != null)
{
Grids.TryRemove(c.Grid.ID, out _);
Grids.Remove(c.Grid.ID);
c.Grid = null;
}
}
foreach (Connection c in changedSnapshot)
foreach (Connection c in ChangedConnections)
{
//Make sure the connection grid hasn't been resolved by another connection update
//Ensure the connection has other connections
@@ -412,7 +346,7 @@ namespace Barotrauma.Items.Components
else
{
//Clear all grid IDs from connections
foreach (Powered powered in PoweredList)
foreach (Powered powered in poweredList)
{
//Only check devices with connectors
if (powered.powerIn != null)
@@ -427,7 +361,7 @@ namespace Barotrauma.Items.Components
Grids.Clear();
foreach (Powered powered in PoweredList)
foreach (Powered powered in poweredList)
{
if (powered.Item.Condition <= 0f) { continue; }
@@ -458,7 +392,7 @@ namespace Barotrauma.Items.Components
}
//Clear changed connections after each update
ClearChangedConnections();
ChangedConnections.Clear();
}
private static GridInfo PropagateGrid(Connection conn)
@@ -488,8 +422,8 @@ namespace Barotrauma.Items.Components
c.Grid = grid;
grid.AddConnection(c);
//Add on recipients - use ToList() snapshot for thread-safe iteration
foreach (Connection otherC in c.Recipients.ToList())
//Add on recipients
foreach (Connection otherC in c.Recipients)
{
//Only add valid connections
if (otherC.Grid != grid && (otherC.Grid == null || !Grids.ContainsKey(otherC.Grid.ID)) && ValidPowerConnection(c, otherC))
@@ -560,7 +494,7 @@ namespace Barotrauma.Items.Components
}
//Determine if devices are adding a load or providing power, also resolve solo nodes
foreach (Powered powered in PoweredList)
foreach (Powered powered in poweredList)
{
//Make voltage decay to ensure the device powers down.
//This only effects devices with no power input (whose voltage is set by other means, e.g. status effects from a contained battery)
@@ -796,8 +730,7 @@ namespace Barotrauma.Items.Components
{
if (item.Connections != null && powerIn != null)
{
// Use ToList() snapshot for thread-safe iteration
foreach (Connection recipient in powerIn.Recipients.ToList())
foreach (Connection recipient in powerIn.Recipients)
{
if (!recipient.IsPower || !recipient.IsOutput) { continue; }
if (recipient.Item?.GetComponent<PowerContainer>() is PowerContainer battery)
@@ -817,14 +750,13 @@ namespace Barotrauma.Items.Components
{
if (c.IsPower && c.Grid != null)
{
MarkConnectionChanged(c);
ChangedConnections.Add(c);
}
}
}
base.RemoveComponentSpecific();
_poweredDict.TryRemove(this, out _);
InvalidatePoweredListCache();
poweredList.Remove(this);
}
}
@@ -848,9 +780,9 @@ namespace Barotrauma.Items.Components
Connections.Remove(c);
//Remove the grid if it has no devices
if (Connections.Count == 0)
if (Connections.Count == 0 && Powered.Grids.ContainsKey(ID))
{
Powered.Grids.TryRemove(ID, out _);
Powered.Grids.Remove(ID);
}
}