WIP Make static collections thread-safe using ThreadStatic and ThreadLocal
Refactored various static and instance collections to use [ThreadStatic], ThreadLocal, or local variables to prevent concurrent modification issues during parallel updates. This affects status effect targets, affliction lists, damage modifiers, and cached data in Character, CharacterHealth, Limb, Explosion, Hull, Submarine, and ToolBox classes. Also replaced Dictionary caches with ConcurrentDictionary where appropriate for thread safety.
This commit is contained in:
@@ -4880,7 +4880,11 @@ namespace Barotrauma
|
|||||||
HealthUpdateInterval = 0.0f;
|
HealthUpdateInterval = 0.0f;
|
||||||
}
|
}
|
||||||
|
|
||||||
private readonly List<ISerializableEntity> targets = new List<ISerializableEntity>();
|
// Thread-static to avoid concurrent modification in parallel item updates
|
||||||
|
[ThreadStatic]
|
||||||
|
private static List<ISerializableEntity> t_statusEffectTargets;
|
||||||
|
private static List<ISerializableEntity> StatusEffectTargets => t_statusEffectTargets ??= new List<ISerializableEntity>();
|
||||||
|
|
||||||
public void ApplyStatusEffects(ActionType actionType, float deltaTime)
|
public void ApplyStatusEffects(ActionType actionType, float deltaTime)
|
||||||
{
|
{
|
||||||
if (actionType == ActionType.OnEating)
|
if (actionType == ActionType.OnEating)
|
||||||
@@ -4909,6 +4913,7 @@ namespace Barotrauma
|
|||||||
if (statusEffect.HasTargetType(StatusEffect.TargetType.NearbyItems) ||
|
if (statusEffect.HasTargetType(StatusEffect.TargetType.NearbyItems) ||
|
||||||
statusEffect.HasTargetType(StatusEffect.TargetType.NearbyCharacters))
|
statusEffect.HasTargetType(StatusEffect.TargetType.NearbyCharacters))
|
||||||
{
|
{
|
||||||
|
var targets = StatusEffectTargets;
|
||||||
targets.Clear();
|
targets.Clear();
|
||||||
statusEffect.AddNearbyTargets(WorldPosition, targets);
|
statusEffect.AddNearbyTargets(WorldPosition, targets);
|
||||||
statusEffect.Apply(actionType, deltaTime, this, targets);
|
statusEffect.Apply(actionType, deltaTime, this, targets);
|
||||||
|
|||||||
@@ -537,20 +537,25 @@ namespace Barotrauma
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
private readonly List<Affliction> matchingAfflictions = new List<Affliction>();
|
// Thread-static to avoid concurrent modification in parallel item updates
|
||||||
|
[ThreadStatic]
|
||||||
|
private static List<Affliction> t_matchingAfflictions;
|
||||||
|
private static List<Affliction> MatchingAfflictions => t_matchingAfflictions ??= new List<Affliction>();
|
||||||
|
|
||||||
public void ReduceAllAfflictionsOnAllLimbs(float amount, ActionType? treatmentAction = null)
|
public void ReduceAllAfflictionsOnAllLimbs(float amount, ActionType? treatmentAction = null)
|
||||||
{
|
{
|
||||||
|
var matchingAfflictions = MatchingAfflictions;
|
||||||
matchingAfflictions.Clear();
|
matchingAfflictions.Clear();
|
||||||
matchingAfflictions.AddRange(afflictions.Keys);
|
matchingAfflictions.AddRange(afflictions.Keys);
|
||||||
|
|
||||||
ReduceMatchingAfflictions(amount, treatmentAction);
|
ReduceMatchingAfflictions(matchingAfflictions, amount, treatmentAction);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void ReduceAfflictionOnAllLimbs(Identifier afflictionIdOrType, float amount, ActionType? treatmentAction = null, Character attacker = null)
|
public void ReduceAfflictionOnAllLimbs(Identifier afflictionIdOrType, float amount, ActionType? treatmentAction = null, Character attacker = null)
|
||||||
{
|
{
|
||||||
if (afflictionIdOrType.IsEmpty) { throw new ArgumentException($"{nameof(afflictionIdOrType)} is empty"); }
|
if (afflictionIdOrType.IsEmpty) { throw new ArgumentException($"{nameof(afflictionIdOrType)} is empty"); }
|
||||||
|
|
||||||
|
var matchingAfflictions = MatchingAfflictions;
|
||||||
matchingAfflictions.Clear();
|
matchingAfflictions.Clear();
|
||||||
foreach (var affliction in afflictions)
|
foreach (var affliction in afflictions)
|
||||||
{
|
{
|
||||||
@@ -560,7 +565,7 @@ namespace Barotrauma
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ReduceMatchingAfflictions(amount, treatmentAction, attacker);
|
ReduceMatchingAfflictions(matchingAfflictions, amount, treatmentAction, attacker);
|
||||||
}
|
}
|
||||||
|
|
||||||
private IEnumerable<Affliction> GetAfflictionsForLimb(Limb targetLimb)
|
private IEnumerable<Affliction> GetAfflictionsForLimb(Limb targetLimb)
|
||||||
@@ -570,10 +575,11 @@ namespace Barotrauma
|
|||||||
{
|
{
|
||||||
if (targetLimb is null) { throw new ArgumentNullException(nameof(targetLimb)); }
|
if (targetLimb is null) { throw new ArgumentNullException(nameof(targetLimb)); }
|
||||||
|
|
||||||
|
var matchingAfflictions = MatchingAfflictions;
|
||||||
matchingAfflictions.Clear();
|
matchingAfflictions.Clear();
|
||||||
matchingAfflictions.AddRange(GetAfflictionsForLimb(targetLimb));
|
matchingAfflictions.AddRange(GetAfflictionsForLimb(targetLimb));
|
||||||
|
|
||||||
ReduceMatchingAfflictions(amount, treatmentAction);
|
ReduceMatchingAfflictions(matchingAfflictions, amount, treatmentAction);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void ReduceAfflictionOnLimb(Limb targetLimb, Identifier afflictionIdOrType, float amount, ActionType? treatmentAction = null, Character attacker = null)
|
public void ReduceAfflictionOnLimb(Limb targetLimb, Identifier afflictionIdOrType, float amount, ActionType? treatmentAction = null, Character attacker = null)
|
||||||
@@ -581,6 +587,7 @@ namespace Barotrauma
|
|||||||
if (afflictionIdOrType.IsEmpty) { throw new ArgumentException($"{nameof(afflictionIdOrType)} is empty"); }
|
if (afflictionIdOrType.IsEmpty) { throw new ArgumentException($"{nameof(afflictionIdOrType)} is empty"); }
|
||||||
if (targetLimb is null) { throw new ArgumentNullException(nameof(targetLimb)); }
|
if (targetLimb is null) { throw new ArgumentNullException(nameof(targetLimb)); }
|
||||||
|
|
||||||
|
var matchingAfflictions = MatchingAfflictions;
|
||||||
matchingAfflictions.Clear();
|
matchingAfflictions.Clear();
|
||||||
var targetLimbHealth = limbHealths[targetLimb.HealthIndex];
|
var targetLimbHealth = limbHealths[targetLimb.HealthIndex];
|
||||||
foreach (var affliction in afflictions)
|
foreach (var affliction in afflictions)
|
||||||
@@ -591,10 +598,10 @@ namespace Barotrauma
|
|||||||
matchingAfflictions.Add(affliction.Key);
|
matchingAfflictions.Add(affliction.Key);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ReduceMatchingAfflictions(amount, treatmentAction, attacker);
|
ReduceMatchingAfflictions(matchingAfflictions, amount, treatmentAction, attacker);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void ReduceMatchingAfflictions(float amount, ActionType? treatmentAction, Character attacker = null)
|
private void ReduceMatchingAfflictions(List<Affliction> matchingAfflictions, float amount, ActionType? treatmentAction, Character attacker = null)
|
||||||
{
|
{
|
||||||
if (matchingAfflictions.Count == 0) { return; }
|
if (matchingAfflictions.Count == 0) { return; }
|
||||||
|
|
||||||
@@ -681,12 +688,19 @@ namespace Barotrauma
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private readonly static List<Affliction> afflictionsToRemove = new List<Affliction>();
|
// Thread-static to avoid concurrent modification when multiple characters are updated in parallel
|
||||||
private readonly static List<KeyValuePair<Affliction, LimbHealth>> afflictionsToUpdate = new List<KeyValuePair<Affliction, LimbHealth>>();
|
[ThreadStatic]
|
||||||
|
private static List<Affliction> t_afflictionsToRemove;
|
||||||
|
[ThreadStatic]
|
||||||
|
private static List<KeyValuePair<Affliction, LimbHealth>> t_afflictionsToUpdate;
|
||||||
|
private static List<Affliction> AfflictionsToRemove => t_afflictionsToRemove ??= new List<Affliction>();
|
||||||
|
private static List<KeyValuePair<Affliction, LimbHealth>> AfflictionsToUpdate => t_afflictionsToUpdate ??= new List<KeyValuePair<Affliction, LimbHealth>>();
|
||||||
|
|
||||||
public void SetAllDamage(float damageAmount, float bleedingDamageAmount, float burnDamageAmount)
|
public void SetAllDamage(float damageAmount, float bleedingDamageAmount, float burnDamageAmount)
|
||||||
{
|
{
|
||||||
if (Unkillable || Character.GodMode) { return; }
|
if (Unkillable || Character.GodMode) { return; }
|
||||||
|
|
||||||
|
var afflictionsToRemove = AfflictionsToRemove;
|
||||||
afflictionsToRemove.Clear();
|
afflictionsToRemove.Clear();
|
||||||
afflictionsToRemove.AddRange(afflictions.Keys.Where(a =>
|
afflictionsToRemove.AddRange(afflictions.Keys.Where(a =>
|
||||||
a.Prefab.AfflictionType == AfflictionPrefab.InternalDamage.AfflictionType ||
|
a.Prefab.AfflictionType == AfflictionPrefab.InternalDamage.AfflictionType ||
|
||||||
@@ -737,6 +751,7 @@ namespace Barotrauma
|
|||||||
|
|
||||||
public void RemoveAfflictions(Func<Affliction, bool> predicate)
|
public void RemoveAfflictions(Func<Affliction, bool> predicate)
|
||||||
{
|
{
|
||||||
|
var afflictionsToRemove = AfflictionsToRemove;
|
||||||
afflictionsToRemove.Clear();
|
afflictionsToRemove.Clear();
|
||||||
afflictionsToRemove.AddRange(afflictions.Keys.Where(affliction => predicate(affliction)));
|
afflictionsToRemove.AddRange(afflictions.Keys.Where(affliction => predicate(affliction)));
|
||||||
foreach (var affliction in afflictionsToRemove)
|
foreach (var affliction in afflictionsToRemove)
|
||||||
@@ -748,6 +763,7 @@ namespace Barotrauma
|
|||||||
|
|
||||||
public void RemoveAllAfflictions()
|
public void RemoveAllAfflictions()
|
||||||
{
|
{
|
||||||
|
var afflictionsToRemove = AfflictionsToRemove;
|
||||||
afflictionsToRemove.Clear();
|
afflictionsToRemove.Clear();
|
||||||
afflictionsToRemove.AddRange(afflictions.Keys.Where(a => !irremovableAfflictions.ContainsKey(a)));
|
afflictionsToRemove.AddRange(afflictions.Keys.Where(a => !irremovableAfflictions.ContainsKey(a)));
|
||||||
foreach (var affliction in afflictionsToRemove)
|
foreach (var affliction in afflictionsToRemove)
|
||||||
@@ -765,6 +781,7 @@ namespace Barotrauma
|
|||||||
|
|
||||||
public void RemoveNegativeAfflictions()
|
public void RemoveNegativeAfflictions()
|
||||||
{
|
{
|
||||||
|
var afflictionsToRemove = AfflictionsToRemove;
|
||||||
afflictionsToRemove.Clear();
|
afflictionsToRemove.Clear();
|
||||||
afflictionsToRemove.AddRange(afflictions.Keys.Where(a =>
|
afflictionsToRemove.AddRange(afflictions.Keys.Where(a =>
|
||||||
!irremovableAfflictions.ContainsKey(a) &&
|
!irremovableAfflictions.ContainsKey(a) &&
|
||||||
@@ -904,6 +921,8 @@ namespace Barotrauma
|
|||||||
|
|
||||||
if (!Character.GodMode)
|
if (!Character.GodMode)
|
||||||
{
|
{
|
||||||
|
var afflictionsToRemove = AfflictionsToRemove;
|
||||||
|
var afflictionsToUpdate = AfflictionsToUpdate;
|
||||||
afflictionsToRemove.Clear();
|
afflictionsToRemove.Clear();
|
||||||
afflictionsToUpdate.Clear();
|
afflictionsToUpdate.Clear();
|
||||||
foreach (KeyValuePair<Affliction, LimbHealth> kvp in afflictions)
|
foreach (KeyValuePair<Affliction, LimbHealth> kvp in afflictions)
|
||||||
@@ -1198,9 +1217,14 @@ namespace Barotrauma
|
|||||||
return (causeOfDeath, strongestAffliction);
|
return (causeOfDeath, strongestAffliction);
|
||||||
}
|
}
|
||||||
|
|
||||||
private readonly List<Affliction> allAfflictions = new List<Affliction>();
|
// Thread-static to avoid concurrent modification in parallel item updates
|
||||||
|
[ThreadStatic]
|
||||||
|
private static List<Affliction> t_allAfflictions;
|
||||||
|
private static List<Affliction> AllAfflictionsList => t_allAfflictions ??= new List<Affliction>();
|
||||||
|
|
||||||
private IEnumerable<Affliction> GetAllAfflictions(bool mergeSameAfflictions, Func<Affliction, bool> predicate = null)
|
private IEnumerable<Affliction> GetAllAfflictions(bool mergeSameAfflictions, Func<Affliction, bool> predicate = null)
|
||||||
{
|
{
|
||||||
|
var allAfflictions = AllAfflictionsList;
|
||||||
allAfflictions.Clear();
|
allAfflictions.Clear();
|
||||||
if (!mergeSameAfflictions)
|
if (!mergeSameAfflictions)
|
||||||
{
|
{
|
||||||
@@ -1383,10 +1407,17 @@ namespace Barotrauma
|
|||||||
return MathHelper.Clamp(strength, 0.0f, affliction.Prefab.MaxStrength);
|
return MathHelper.Clamp(strength, 0.0f, affliction.Prefab.MaxStrength);
|
||||||
}
|
}
|
||||||
|
|
||||||
private readonly List<Affliction> activeAfflictions = new List<Affliction>();
|
// Thread-static to avoid concurrent modification in parallel updates
|
||||||
private readonly List<(LimbHealth limbHealth, Affliction affliction)> limbAfflictions = new List<(LimbHealth limbHealth, Affliction affliction)>();
|
[ThreadStatic]
|
||||||
|
private static List<Affliction> t_activeAfflictions;
|
||||||
|
[ThreadStatic]
|
||||||
|
private static List<(LimbHealth limbHealth, Affliction affliction)> t_limbAfflictions;
|
||||||
|
private static List<Affliction> ActiveAfflictionsList => t_activeAfflictions ??= new List<Affliction>();
|
||||||
|
private static List<(LimbHealth limbHealth, Affliction affliction)> LimbAfflictionsList => t_limbAfflictions ??= new List<(LimbHealth limbHealth, Affliction affliction)>();
|
||||||
|
|
||||||
public void ServerWrite(IWriteMessage msg)
|
public void ServerWrite(IWriteMessage msg)
|
||||||
{
|
{
|
||||||
|
var activeAfflictions = ActiveAfflictionsList;
|
||||||
activeAfflictions.Clear();
|
activeAfflictions.Clear();
|
||||||
foreach (KeyValuePair<Affliction, LimbHealth> kvp in afflictions)
|
foreach (KeyValuePair<Affliction, LimbHealth> kvp in afflictions)
|
||||||
{
|
{
|
||||||
@@ -1412,6 +1443,7 @@ namespace Barotrauma
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var limbAfflictions = LimbAfflictionsList;
|
||||||
limbAfflictions.Clear();
|
limbAfflictions.Clear();
|
||||||
foreach (KeyValuePair<Affliction, LimbHealth> kvp in afflictions)
|
foreach (KeyValuePair<Affliction, LimbHealth> kvp in afflictions)
|
||||||
{
|
{
|
||||||
@@ -1441,8 +1473,9 @@ namespace Barotrauma
|
|||||||
public void Remove()
|
public void Remove()
|
||||||
{
|
{
|
||||||
RemoveProjSpecific();
|
RemoveProjSpecific();
|
||||||
afflictionsToRemove.Clear();
|
// Clear thread-static lists to help with garbage collection
|
||||||
afflictionsToUpdate.Clear();
|
AfflictionsToRemove.Clear();
|
||||||
|
AfflictionsToUpdate.Clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
partial void RemoveProjSpecific();
|
partial void RemoveProjSpecific();
|
||||||
|
|||||||
@@ -797,16 +797,14 @@ namespace Barotrauma
|
|||||||
return AddDamage(simPosition, afflictions, playSound);
|
return AddDamage(simPosition, afflictions, playSound);
|
||||||
}
|
}
|
||||||
|
|
||||||
private readonly List<DamageModifier> appliedDamageModifiers = new List<DamageModifier>();
|
// Thread-safe: using local variables instead of instance fields to avoid concurrent modification
|
||||||
private readonly List<DamageModifier> tempModifiers = new List<DamageModifier>();
|
|
||||||
private readonly List<Affliction> afflictionsCopy = new List<Affliction>();
|
|
||||||
public AttackResult AddDamage(Vector2 simPosition, IEnumerable<Affliction> afflictions, bool playSound, float damageMultiplier = 1, float penetration = 0f, Character attacker = null)
|
public AttackResult AddDamage(Vector2 simPosition, IEnumerable<Affliction> afflictions, bool playSound, float damageMultiplier = 1, float penetration = 0f, Character attacker = null)
|
||||||
{
|
{
|
||||||
appliedDamageModifiers.Clear();
|
var appliedDamageModifiers = new List<DamageModifier>();
|
||||||
afflictionsCopy.Clear();
|
var afflictionsCopy = new List<Affliction>();
|
||||||
foreach (var affliction in afflictions)
|
foreach (var affliction in afflictions)
|
||||||
{
|
{
|
||||||
tempModifiers.Clear();
|
var tempModifiers = new List<DamageModifier>();
|
||||||
var newAffliction = affliction;
|
var newAffliction = affliction;
|
||||||
float random = Rand.Value(Rand.RandSync.Unsynced);
|
float random = Rand.Value(Rand.RandSync.Unsynced);
|
||||||
bool foundMatchingModifier = false;
|
bool foundMatchingModifier = false;
|
||||||
@@ -1022,13 +1020,18 @@ namespace Barotrauma
|
|||||||
|
|
||||||
partial void UpdateProjSpecific(float deltaTime);
|
partial void UpdateProjSpecific(float deltaTime);
|
||||||
|
|
||||||
private readonly List<Body> contactBodies = new List<Body>();
|
// Thread-static to avoid concurrent modification in parallel item updates
|
||||||
|
[ThreadStatic]
|
||||||
|
private static List<Body> t_contactBodies;
|
||||||
|
private static List<Body> ContactBodies => t_contactBodies ??= new List<Body>();
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Returns true if the attack successfully hit something. If the distance is not given, it will be calculated.
|
/// Returns true if the attack successfully hit something. If the distance is not given, it will be calculated.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public bool UpdateAttack(float deltaTime, Vector2 attackSimPos, IDamageable damageTarget, out AttackResult attackResult, float distance = -1, Limb targetLimb = null)
|
public bool UpdateAttack(float deltaTime, Vector2 attackSimPos, IDamageable damageTarget, out AttackResult attackResult, float distance = -1, Limb targetLimb = null)
|
||||||
{
|
{
|
||||||
attackResult = default;
|
attackResult = default;
|
||||||
|
var contactBodies = ContactBodies;
|
||||||
Vector2 simPos = ragdoll.SimplePhysicsEnabled ? character.SimPosition : SimPosition;
|
Vector2 simPos = ragdoll.SimplePhysicsEnabled ? character.SimPosition : SimPosition;
|
||||||
float dist = distance > -1 ? distance : ConvertUnits.ToDisplayUnits(Vector2.Distance(simPos, attackSimPos));
|
float dist = distance > -1 ? distance : ConvertUnits.ToDisplayUnits(Vector2.Distance(simPos, attackSimPos));
|
||||||
bool wasRunning = attack.IsRunning;
|
bool wasRunning = attack.IsRunning;
|
||||||
@@ -1287,7 +1290,11 @@ namespace Barotrauma
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private readonly List<ISerializableEntity> targets = new List<ISerializableEntity>();
|
// Thread-static to avoid concurrent modification in parallel item updates
|
||||||
|
[ThreadStatic]
|
||||||
|
private static List<ISerializableEntity> t_statusEffectTargets;
|
||||||
|
private static List<ISerializableEntity> StatusEffectTargets => t_statusEffectTargets ??= new List<ISerializableEntity>();
|
||||||
|
|
||||||
public void ApplyStatusEffects(ActionType actionType, float deltaTime)
|
public void ApplyStatusEffects(ActionType actionType, float deltaTime)
|
||||||
{
|
{
|
||||||
if (!statusEffects.TryGetValue(actionType, out var statusEffectList)) { return; }
|
if (!statusEffects.TryGetValue(actionType, out var statusEffectList)) { return; }
|
||||||
@@ -1310,6 +1317,7 @@ namespace Barotrauma
|
|||||||
if (statusEffect.HasTargetType(StatusEffect.TargetType.NearbyItems) ||
|
if (statusEffect.HasTargetType(StatusEffect.TargetType.NearbyItems) ||
|
||||||
statusEffect.HasTargetType(StatusEffect.TargetType.NearbyCharacters))
|
statusEffect.HasTargetType(StatusEffect.TargetType.NearbyCharacters))
|
||||||
{
|
{
|
||||||
|
var targets = StatusEffectTargets;
|
||||||
targets.Clear();
|
targets.Clear();
|
||||||
statusEffect.AddNearbyTargets(WorldPosition, targets);
|
statusEffect.AddNearbyTargets(WorldPosition, targets);
|
||||||
statusEffect.Apply(actionType, deltaTime, character, targets);
|
statusEffect.Apply(actionType, deltaTime, character, targets);
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ using Microsoft.Xna.Framework;
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using System.Threading;
|
||||||
|
|
||||||
namespace Barotrauma
|
namespace Barotrauma
|
||||||
{
|
{
|
||||||
@@ -648,7 +649,11 @@ namespace Barotrauma
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static readonly Dictionary<Structure, float> damagedStructures = new Dictionary<Structure, float>();
|
// 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;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Returns a dictionary where the keys are the structures that took damage and the values are the amount of damage taken
|
/// Returns a dictionary where the keys are the structures that took damage and the values are the amount of damage taken
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ using System;
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using System.Threading;
|
||||||
using System.Xml.Linq;
|
using System.Xml.Linq;
|
||||||
using Barotrauma.MapCreatures.Behavior;
|
using Barotrauma.MapCreatures.Behavior;
|
||||||
using Barotrauma.Items.Components;
|
using Barotrauma.Items.Components;
|
||||||
@@ -1133,13 +1134,18 @@ namespace Barotrauma
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Used in <see cref="GetApproximateDistance"/>
|
/// Used in <see cref="GetApproximateDistance"/> - ThreadLocal for thread safety
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private static readonly Dictionary<Hull, float> cachedDistances = [];
|
private static readonly ThreadLocal<Dictionary<Hull, float>> cachedDistancesLocal =
|
||||||
|
new ThreadLocal<Dictionary<Hull, float>>(() => new Dictionary<Hull, float>());
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Used in <see cref="GetApproximateDistance"/>
|
/// Used in <see cref="GetApproximateDistance"/> - ThreadLocal for thread safety
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private static readonly PriorityQueue<(Hull hull, Vector2 pos), float> priorityQueue = new PriorityQueue<(Hull hull, Vector2 pos), float>();
|
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;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Approximate distance from this hull to the target hull, moving through open gaps without passing through walls.
|
/// Approximate distance from this hull to the target hull, moving through open gaps without passing through walls.
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ using System.Collections.Generic;
|
|||||||
using System.Collections.Immutable;
|
using System.Collections.Immutable;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using System.Threading;
|
||||||
using System.Xml.Linq;
|
using System.Xml.Linq;
|
||||||
using Voronoi2;
|
using Voronoi2;
|
||||||
|
|
||||||
@@ -97,10 +98,11 @@ namespace Barotrauma
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static Vector2 lastPickedPosition;
|
// ThreadLocal for thread-safe ray casting results
|
||||||
private static float lastPickedFraction;
|
private static readonly ThreadLocal<Vector2> lastPickedPositionLocal = new ThreadLocal<Vector2>();
|
||||||
private static Fixture lastPickedFixture;
|
private static readonly ThreadLocal<float> lastPickedFractionLocal = new ThreadLocal<float>();
|
||||||
private static Vector2 lastPickedNormal;
|
private static readonly ThreadLocal<Fixture> lastPickedFixtureLocal = new ThreadLocal<Fixture>();
|
||||||
|
private static readonly ThreadLocal<Vector2> lastPickedNormalLocal = new ThreadLocal<Vector2>();
|
||||||
|
|
||||||
private Vector2 prevPosition;
|
private Vector2 prevPosition;
|
||||||
|
|
||||||
@@ -114,22 +116,22 @@ namespace Barotrauma
|
|||||||
|
|
||||||
public static Vector2 LastPickedPosition
|
public static Vector2 LastPickedPosition
|
||||||
{
|
{
|
||||||
get { return lastPickedPosition; }
|
get { return lastPickedPositionLocal.Value; }
|
||||||
}
|
}
|
||||||
|
|
||||||
public static float LastPickedFraction
|
public static float LastPickedFraction
|
||||||
{
|
{
|
||||||
get { return lastPickedFraction; }
|
get { return lastPickedFractionLocal.Value; }
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Fixture LastPickedFixture
|
public static Fixture LastPickedFixture
|
||||||
{
|
{
|
||||||
get { return lastPickedFixture; }
|
get { return lastPickedFixtureLocal.Value; }
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Vector2 LastPickedNormal
|
public static Vector2 LastPickedNormal
|
||||||
{
|
{
|
||||||
get { return lastPickedNormal; }
|
get { return lastPickedNormalLocal.Value; }
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool Loading
|
public bool Loading
|
||||||
@@ -854,10 +856,10 @@ namespace Barotrauma
|
|||||||
}, ref aabb);
|
}, ref aabb);
|
||||||
if (closestFraction <= 0.0f)
|
if (closestFraction <= 0.0f)
|
||||||
{
|
{
|
||||||
lastPickedPosition = rayStart;
|
lastPickedPositionLocal.Value = rayStart;
|
||||||
lastPickedFraction = closestFraction;
|
lastPickedFractionLocal.Value = closestFraction;
|
||||||
lastPickedFixture = closestFixture;
|
lastPickedFixtureLocal.Value = closestFixture;
|
||||||
lastPickedNormal = closestNormal;
|
lastPickedNormalLocal.Value = closestNormal;
|
||||||
return closestBody;
|
return closestBody;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -876,16 +878,22 @@ namespace Barotrauma
|
|||||||
return fraction;
|
return fraction;
|
||||||
}, rayStart, rayEnd, collisionCategory ?? Category.All);
|
}, rayStart, rayEnd, collisionCategory ?? Category.All);
|
||||||
|
|
||||||
lastPickedPosition = rayStart + (rayEnd - rayStart) * closestFraction;
|
lastPickedPositionLocal.Value = rayStart + (rayEnd - rayStart) * closestFraction;
|
||||||
lastPickedFraction = closestFraction;
|
lastPickedFractionLocal.Value = closestFraction;
|
||||||
lastPickedFixture = closestFixture;
|
lastPickedFixtureLocal.Value = closestFixture;
|
||||||
lastPickedNormal = closestNormal;
|
lastPickedNormalLocal.Value = closestNormal;
|
||||||
|
|
||||||
return closestBody;
|
return closestBody;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static readonly Dictionary<Body, float> bodyDist = new Dictionary<Body, float>();
|
// ThreadLocal for thread-safe body picking
|
||||||
private static readonly List<Body> bodies = new List<Body>();
|
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;
|
||||||
|
|
||||||
public static float LastPickedBodyDist(Body body)
|
public static float LastPickedBodyDist(Body body)
|
||||||
{
|
{
|
||||||
@@ -919,10 +927,10 @@ namespace Barotrauma
|
|||||||
}
|
}
|
||||||
if (fraction < closestFraction)
|
if (fraction < closestFraction)
|
||||||
{
|
{
|
||||||
lastPickedPosition = rayStart + (rayEnd - rayStart) * fraction;
|
lastPickedPositionLocal.Value = rayStart + (rayEnd - rayStart) * fraction;
|
||||||
lastPickedFraction = fraction;
|
lastPickedFractionLocal.Value = fraction;
|
||||||
lastPickedNormal = normal;
|
lastPickedNormalLocal.Value = normal;
|
||||||
lastPickedFixture = fixture;
|
lastPickedFixtureLocal.Value = fixture;
|
||||||
}
|
}
|
||||||
//continue
|
//continue
|
||||||
return -1;
|
return -1;
|
||||||
@@ -940,10 +948,10 @@ namespace Barotrauma
|
|||||||
if (!fixture.Shape.TestPoint(ref transform, ref rayStart)) { return true; }
|
if (!fixture.Shape.TestPoint(ref transform, ref rayStart)) { return true; }
|
||||||
|
|
||||||
closestFraction = 0.0f;
|
closestFraction = 0.0f;
|
||||||
lastPickedPosition = rayStart;
|
lastPickedPositionLocal.Value = rayStart;
|
||||||
lastPickedFraction = 0.0f;
|
lastPickedFractionLocal.Value = 0.0f;
|
||||||
lastPickedNormal = Vector2.Normalize(rayEnd - rayStart);
|
lastPickedNormalLocal.Value = Vector2.Normalize(rayEnd - rayStart);
|
||||||
lastPickedFixture = fixture;
|
lastPickedFixtureLocal.Value = fixture;
|
||||||
bodies.Add(fixture.Body);
|
bodies.Add(fixture.Body);
|
||||||
bodyDist[fixture.Body] = 0.0f;
|
bodyDist[fixture.Body] = 0.0f;
|
||||||
return false;
|
return false;
|
||||||
@@ -1011,7 +1019,7 @@ namespace Barotrauma
|
|||||||
|
|
||||||
if (Vector2.DistanceSquared(rayStart, rayEnd) < 0.01f)
|
if (Vector2.DistanceSquared(rayStart, rayEnd) < 0.01f)
|
||||||
{
|
{
|
||||||
lastPickedPosition = rayEnd;
|
lastPickedPositionLocal.Value = rayEnd;
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1053,10 +1061,10 @@ namespace Barotrauma
|
|||||||
, rayStart, rayEnd);
|
, rayStart, rayEnd);
|
||||||
|
|
||||||
|
|
||||||
lastPickedPosition = rayStart + (rayEnd - rayStart) * closestFraction;
|
lastPickedPositionLocal.Value = rayStart + (rayEnd - rayStart) * closestFraction;
|
||||||
lastPickedFraction = closestFraction;
|
lastPickedFractionLocal.Value = closestFraction;
|
||||||
lastPickedFixture = closestFixture;
|
lastPickedFixtureLocal.Value = closestFixture;
|
||||||
lastPickedNormal = closestNormal;
|
lastPickedNormalLocal.Value = closestNormal;
|
||||||
return closestBody;
|
return closestBody;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
using Barotrauma.Networking;
|
using Barotrauma.Networking;
|
||||||
using Microsoft.Xna.Framework;
|
using Microsoft.Xna.Framework;
|
||||||
using System;
|
using System;
|
||||||
|
using System.Collections.Concurrent;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Collections.Immutable;
|
using System.Collections.Immutable;
|
||||||
using System.Diagnostics.CodeAnalysis;
|
using System.Diagnostics.CodeAnalysis;
|
||||||
@@ -75,7 +76,7 @@ namespace Barotrauma
|
|||||||
return !corrected;
|
return !corrected;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static readonly Dictionary<string, string> cachedFileNames = new Dictionary<string, string>();
|
private static readonly ConcurrentDictionary<string, string> cachedFileNames = new ConcurrentDictionary<string, string>();
|
||||||
|
|
||||||
public static string CorrectFilenameCase(string filename, out bool corrected, string directory = "")
|
public static string CorrectFilenameCase(string filename, out bool corrected, string directory = "")
|
||||||
{
|
{
|
||||||
@@ -153,7 +154,7 @@ namespace Barotrauma
|
|||||||
if (i < subDirs.Length - 1) { filename += "/"; }
|
if (i < subDirs.Length - 1) { filename += "/"; }
|
||||||
}
|
}
|
||||||
|
|
||||||
cachedFileNames.Add(originalFilename, filename);
|
cachedFileNames.TryAdd(originalFilename, filename);
|
||||||
return filename;
|
return filename;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -355,32 +356,26 @@ namespace Barotrauma
|
|||||||
return text;
|
return text;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static Dictionary<string, List<string>> cachedLines = new Dictionary<string, List<string>>();
|
private static readonly ConcurrentDictionary<string, List<string>> cachedLines = new ConcurrentDictionary<string, List<string>>();
|
||||||
public static string GetRandomLine(string filePath, Rand.RandSync randSync = Rand.RandSync.ServerAndClient)
|
public static string GetRandomLine(string filePath, Rand.RandSync randSync = Rand.RandSync.ServerAndClient)
|
||||||
{
|
{
|
||||||
List<string> lines;
|
List<string> lines = cachedLines.GetOrAdd(filePath, path =>
|
||||||
if (cachedLines.ContainsKey(filePath))
|
|
||||||
{
|
|
||||||
lines = cachedLines[filePath];
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
lines = File.ReadAllLines(filePath, catchUnauthorizedAccessExceptions: false).ToList();
|
var fileLines = File.ReadAllLines(path, catchUnauthorizedAccessExceptions: false).ToList();
|
||||||
cachedLines.Add(filePath, lines);
|
if (fileLines.Count == 0)
|
||||||
if (lines.Count == 0)
|
|
||||||
{
|
{
|
||||||
DebugConsole.ThrowError("File \"" + filePath + "\" is empty!");
|
DebugConsole.ThrowError("File \"" + path + "\" is empty!");
|
||||||
return "";
|
|
||||||
}
|
}
|
||||||
|
return fileLines;
|
||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
DebugConsole.ThrowError("Couldn't open file \"" + filePath + "\"!", e);
|
DebugConsole.ThrowError("Couldn't open file \"" + path + "\"!", e);
|
||||||
return "";
|
return new List<string>();
|
||||||
}
|
}
|
||||||
}
|
});
|
||||||
|
|
||||||
if (lines.Count == 0) return "";
|
if (lines.Count == 0) return "";
|
||||||
return lines[Rand.Range(0, lines.Count, randSync)];
|
return lines[Rand.Range(0, lines.Count, randSync)];
|
||||||
|
|||||||
Reference in New Issue
Block a user