Revert "OBT1.1.0 Merge branch 'dev_pte' into dev"
This reverts commit177cf89756, reversing changes made to42ba733cd4.
This commit is contained in:
@@ -1,56 +1,26 @@
|
||||
using Barotrauma.Items.Components;
|
||||
using Microsoft.Xna.Framework;
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
class DelayedListElement
|
||||
{
|
||||
public readonly long Id;
|
||||
public readonly DelayedEffect Parent;
|
||||
public readonly Entity Entity;
|
||||
|
||||
private Vector2? _worldPosition;
|
||||
private readonly object _worldPositionLock = new object();
|
||||
public Vector2? WorldPosition
|
||||
{
|
||||
get
|
||||
{
|
||||
lock (_worldPositionLock)
|
||||
{
|
||||
return _worldPosition;
|
||||
}
|
||||
}
|
||||
set
|
||||
{
|
||||
lock (_worldPositionLock)
|
||||
{
|
||||
_worldPosition = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public Vector2? WorldPosition;
|
||||
/// <summary>
|
||||
/// Should the delayed effect attempt to determine the position of the effect based on the targets, or just use the position that was passed to the constructor.
|
||||
/// </summary>
|
||||
public bool GetPositionBasedOnTargets;
|
||||
public readonly Vector2? StartPosition;
|
||||
public readonly List<ISerializableEntity> Targets;
|
||||
|
||||
private volatile float _delay;
|
||||
public float Delay
|
||||
{
|
||||
get => _delay;
|
||||
set => _delay = value;
|
||||
}
|
||||
public float Delay;
|
||||
|
||||
public DelayedListElement(DelayedEffect parentEffect, Entity parentEntity, IEnumerable<ISerializableEntity> targets, float delay, Vector2? worldPosition, Vector2? startPosition)
|
||||
{
|
||||
Id = Interlocked.Increment(ref DelayedEffect._delayElementIdCounter);
|
||||
Parent = parentEffect;
|
||||
Entity = parentEntity;
|
||||
Targets = new List<ISerializableEntity>(targets);
|
||||
@@ -59,19 +29,9 @@ namespace Barotrauma
|
||||
StartPosition = startPosition;
|
||||
}
|
||||
}
|
||||
|
||||
class DelayedEffect : StatusEffect
|
||||
{
|
||||
// Thread-safe counter for generating unique IDs for DelayedListElement
|
||||
internal static long _delayElementIdCounter;
|
||||
|
||||
// Thread-safe dictionary for delayed effects
|
||||
public static readonly ConcurrentDictionary<long, DelayedListElement> DelayListDict = new ConcurrentDictionary<long, DelayedListElement>();
|
||||
|
||||
/// <summary>
|
||||
/// Provides a thread-safe enumerable view of the delay list for iteration.
|
||||
/// </summary>
|
||||
public static IEnumerable<DelayedListElement> DelayList => DelayListDict.Values;
|
||||
public static readonly List<DelayedListElement> DelayList = new List<DelayedListElement>();
|
||||
|
||||
private enum DelayTypes
|
||||
{
|
||||
@@ -102,10 +62,9 @@ namespace Barotrauma
|
||||
if (this.type != type || !HasRequiredItems(entity)) { return; }
|
||||
if (!Stackable)
|
||||
{
|
||||
// Thread-safe iteration over ConcurrentDictionary
|
||||
foreach (var kvp in DelayListDict)
|
||||
foreach (var existingEffect in DelayList)
|
||||
{
|
||||
if (kvp.Value.Parent == this && kvp.Value.Targets.FirstOrDefault() == target)
|
||||
if (existingEffect.Parent == this && existingEffect.Targets.FirstOrDefault() == target)
|
||||
{
|
||||
return;
|
||||
}
|
||||
@@ -113,19 +72,18 @@ namespace Barotrauma
|
||||
}
|
||||
if (!IsValidTarget(target)) { return; }
|
||||
|
||||
var targets = CurrentTargets;
|
||||
targets.Clear();
|
||||
targets.Add(target);
|
||||
if (!HasRequiredConditions(targets)) { return; }
|
||||
currentTargets.Clear();
|
||||
currentTargets.Add(target);
|
||||
if (!HasRequiredConditions(currentTargets)) { return; }
|
||||
|
||||
switch (delayType)
|
||||
{
|
||||
case DelayTypes.Timer:
|
||||
var newDelayListElement = new DelayedListElement(this, entity, targets, delay, worldPosition ?? GetPosition(entity, targets, worldPosition), startPosition: null)
|
||||
var newDelayListElement = new DelayedListElement(this, entity, currentTargets, delay, worldPosition ?? GetPosition(entity, currentTargets, worldPosition), startPosition: null)
|
||||
{
|
||||
GetPositionBasedOnTargets = worldPosition == null
|
||||
};
|
||||
DelayListDict.TryAdd(newDelayListElement.Id, newDelayListElement);
|
||||
DelayList.Add(newDelayListElement);
|
||||
break;
|
||||
case DelayTypes.ReachCursor:
|
||||
Projectile projectile = (entity as Item)?.GetComponent<Projectile>();
|
||||
@@ -147,8 +105,7 @@ namespace Barotrauma
|
||||
return;
|
||||
}
|
||||
|
||||
var reachCursorElement = new DelayedListElement(this, entity, targets, Vector2.Distance(entity.WorldPosition, projectile.User.CursorWorldPosition), worldPosition, entity.WorldPosition);
|
||||
DelayListDict.TryAdd(reachCursorElement.Id, reachCursorElement);
|
||||
DelayList.Add(new DelayedListElement(this, entity, currentTargets, Vector2.Distance(entity.WorldPosition, projectile.User.CursorWorldPosition), worldPosition, entity.WorldPosition));
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -162,28 +119,25 @@ namespace Barotrauma
|
||||
if (delayType == DelayTypes.ReachCursor && Character.Controlled == null) { return; }
|
||||
if (!Stackable)
|
||||
{
|
||||
// Thread-safe iteration over ConcurrentDictionary
|
||||
foreach (var kvp in DelayListDict)
|
||||
foreach (var existingEffect in DelayList)
|
||||
{
|
||||
if (kvp.Value.Parent == this && kvp.Value.Targets.SequenceEqual(targets)) { return; }
|
||||
if (existingEffect.Parent == this && existingEffect.Targets.SequenceEqual(targets)) { return; }
|
||||
}
|
||||
}
|
||||
|
||||
var localTargets = CurrentTargets;
|
||||
localTargets.Clear();
|
||||
currentTargets.Clear();
|
||||
foreach (ISerializableEntity target in targets)
|
||||
{
|
||||
if (!IsValidTarget(target)) { continue; }
|
||||
localTargets.Add(target);
|
||||
currentTargets.Add(target);
|
||||
}
|
||||
|
||||
if (!HasRequiredConditions(localTargets)) { return; }
|
||||
if (!HasRequiredConditions(currentTargets)) { return; }
|
||||
|
||||
switch (delayType)
|
||||
{
|
||||
case DelayTypes.Timer:
|
||||
var timerElement = new DelayedListElement(this, entity, localTargets, delay, worldPosition, null);
|
||||
DelayListDict.TryAdd(timerElement.Id, timerElement);
|
||||
DelayList.Add(new DelayedListElement(this, entity, currentTargets, delay, worldPosition, null));
|
||||
break;
|
||||
case DelayTypes.ReachCursor:
|
||||
Projectile projectile = (entity as Item)?.GetComponent<Projectile>();
|
||||
@@ -207,21 +161,19 @@ namespace Barotrauma
|
||||
return;
|
||||
}
|
||||
|
||||
var reachCursorElement = new DelayedListElement(this, entity, localTargets, Vector2.Distance(entity.WorldPosition, user.CursorWorldPosition), worldPosition, entity.WorldPosition);
|
||||
DelayListDict.TryAdd(reachCursorElement.Id, reachCursorElement);
|
||||
DelayList.Add(new DelayedListElement(this, entity, currentTargets, Vector2.Distance(entity.WorldPosition, user.CursorWorldPosition), worldPosition, entity.WorldPosition));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public static void Update(float deltaTime)
|
||||
{
|
||||
// Thread-safe iteration over ConcurrentDictionary
|
||||
foreach (var kvp in DelayListDict)
|
||||
for (int i = DelayList.Count - 1; i >= 0; i--)
|
||||
{
|
||||
DelayedListElement element = kvp.Value;
|
||||
DelayedListElement element = DelayList[i];
|
||||
if (element.Parent.CheckConditionalAlways && !element.Parent.HasRequiredConditions(element.Targets))
|
||||
{
|
||||
DelayListDict.TryRemove(element.Id, out _);
|
||||
DelayList.Remove(element);
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -235,7 +187,7 @@ namespace Barotrauma
|
||||
//keep refreshing the position until the effect runs (so e.g. a delayed effect runs at the last known position of a monster before it despawned)
|
||||
if (element.GetPositionBasedOnTargets && element.Entity is { Removed: false })
|
||||
{
|
||||
element.WorldPosition = element.Parent.GetPosition(element.Entity, element.Parent.CurrentTargets);
|
||||
element.WorldPosition = element.Parent.GetPosition(element.Entity, element.Parent.currentTargets);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
@@ -246,8 +198,8 @@ namespace Barotrauma
|
||||
}
|
||||
|
||||
element.Parent.Apply(deltaTime, element.Entity, element.Targets, element.WorldPosition);
|
||||
DelayListDict.TryRemove(element.Id, out _);
|
||||
DelayList.Remove(element);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -7,18 +7,15 @@ using FarseerPhysics.Dynamics;
|
||||
using Microsoft.Xna.Framework;
|
||||
using Steamworks;
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
class DurationListElement
|
||||
{
|
||||
public readonly long Id;
|
||||
public readonly StatusEffect Parent;
|
||||
public readonly Entity Entity;
|
||||
public float Duration
|
||||
@@ -27,24 +24,12 @@ namespace Barotrauma
|
||||
private set;
|
||||
}
|
||||
public readonly List<ISerializableEntity> Targets;
|
||||
|
||||
private volatile Character _user;
|
||||
public Character User
|
||||
{
|
||||
get => _user;
|
||||
private set => _user = value;
|
||||
}
|
||||
public Character User { get; private set; }
|
||||
|
||||
private volatile float _timer;
|
||||
public float Timer
|
||||
{
|
||||
get => _timer;
|
||||
set => _timer = value;
|
||||
}
|
||||
public float Timer;
|
||||
|
||||
public DurationListElement(StatusEffect parentEffect, Entity parentEntity, IEnumerable<ISerializableEntity> targets, float duration, Character user)
|
||||
{
|
||||
Id = Interlocked.Increment(ref StatusEffect._durationElementIdCounter);
|
||||
Parent = parentEffect;
|
||||
Entity = parentEntity;
|
||||
Targets = new List<ISerializableEntity>(targets);
|
||||
@@ -54,9 +39,8 @@ namespace Barotrauma
|
||||
|
||||
public void Reset(float duration, Character newUser)
|
||||
{
|
||||
Duration = duration;
|
||||
Volatile.Write(ref _timer, duration);
|
||||
_user = newUser;
|
||||
Timer = Duration = duration;
|
||||
User = newUser;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -617,23 +601,14 @@ namespace Barotrauma
|
||||
private readonly float lifeTime;
|
||||
private float lifeTimer;
|
||||
|
||||
private ConcurrentDictionary<Entity, float> intervalTimers;
|
||||
private Dictionary<Entity, float> intervalTimers;
|
||||
|
||||
/// <summary>
|
||||
/// Makes the effect only execute once. After it has executed, it'll never execute again (during the same round).
|
||||
/// </summary>
|
||||
private readonly bool oneShot;
|
||||
|
||||
// Thread-safe counter for generating unique IDs for DurationListElement
|
||||
internal static long _durationElementIdCounter;
|
||||
|
||||
// Thread-safe dictionary for duration effects
|
||||
public static readonly ConcurrentDictionary<long, DurationListElement> DurationListDict = new ConcurrentDictionary<long, DurationListElement>();
|
||||
|
||||
/// <summary>
|
||||
/// Provides a thread-safe enumerable view of the duration list for iteration.
|
||||
/// </summary>
|
||||
public static IEnumerable<DurationListElement> DurationList => DurationListDict.Values;
|
||||
public static readonly List<DurationListElement> DurationList = new List<DurationListElement>();
|
||||
|
||||
/// <summary>
|
||||
/// Only applicable for StatusEffects with a duration or delay. Should the conditional checks only be done when the effect triggers,
|
||||
@@ -1649,52 +1624,22 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
// Thread-local list to avoid contention when cleaning up removed entities
|
||||
[ThreadStatic]
|
||||
private static List<Entity> _threadLocalIntervalsToRemove;
|
||||
|
||||
private static List<Entity> IntervalsToRemove
|
||||
{
|
||||
get
|
||||
{
|
||||
_threadLocalIntervalsToRemove ??= new List<Entity>();
|
||||
return _threadLocalIntervalsToRemove;
|
||||
}
|
||||
}
|
||||
private static readonly List<Entity> intervalsToRemove = new List<Entity>();
|
||||
|
||||
public bool ShouldWaitForInterval(Entity entity, float deltaTime)
|
||||
{
|
||||
if (Interval > 0.0f && entity != null)
|
||||
if (Interval > 0.0f && entity != null && intervalTimers != null)
|
||||
{
|
||||
// Thread-safe lazy initialization
|
||||
if (intervalTimers == null)
|
||||
if (intervalTimers.ContainsKey(entity))
|
||||
{
|
||||
Interlocked.CompareExchange(ref intervalTimers, new ConcurrentDictionary<Entity, float>(), null);
|
||||
intervalTimers[entity] -= deltaTime;
|
||||
if (intervalTimers[entity] > 0.0f) { return true; }
|
||||
}
|
||||
|
||||
if (intervalTimers.TryGetValue(entity, out float currentTimer))
|
||||
intervalsToRemove.Clear();
|
||||
intervalsToRemove.AddRange(intervalTimers.Keys.Where(e => e.Removed));
|
||||
foreach (var toRemove in intervalsToRemove)
|
||||
{
|
||||
float newTimer = currentTimer - deltaTime;
|
||||
if (newTimer > 0.0f)
|
||||
{
|
||||
intervalTimers.AddOrUpdate(entity, newTimer, (_, __) => newTimer);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// Clean up removed entities using thread-local list
|
||||
var toRemove = IntervalsToRemove;
|
||||
toRemove.Clear();
|
||||
foreach (var key in intervalTimers.Keys)
|
||||
{
|
||||
if (key.Removed)
|
||||
{
|
||||
toRemove.Add(key);
|
||||
}
|
||||
}
|
||||
foreach (var key in toRemove)
|
||||
{
|
||||
intervalTimers.TryRemove(key, out _);
|
||||
intervalTimers.Remove(toRemove);
|
||||
}
|
||||
}
|
||||
return false;
|
||||
@@ -1710,7 +1655,7 @@ namespace Barotrauma
|
||||
if (Duration > 0.0f && !Stackable)
|
||||
{
|
||||
//ignore if not stackable and there's already an identical statuseffect
|
||||
DurationListElement existingEffect = FindExistingDurationEffect(target);
|
||||
DurationListElement existingEffect = DurationList.Find(d => d.Parent == this && d.Targets.FirstOrDefault() == target);
|
||||
if (existingEffect != null)
|
||||
{
|
||||
if (ResetDurationWhenReapplied)
|
||||
@@ -1721,74 +1666,30 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
var targets = CurrentTargets;
|
||||
targets.Clear();
|
||||
targets.Add(target);
|
||||
if (!HasRequiredConditions(targets)) { return; }
|
||||
Apply(deltaTime, entity, targets, worldPosition);
|
||||
currentTargets.Clear();
|
||||
currentTargets.Add(target);
|
||||
if (!HasRequiredConditions(currentTargets)) { return; }
|
||||
Apply(deltaTime, entity, currentTargets, worldPosition);
|
||||
}
|
||||
|
||||
// Thread-local list to avoid contention when collecting targets
|
||||
[ThreadStatic]
|
||||
private static List<ISerializableEntity> _threadLocalCurrentTargets;
|
||||
|
||||
protected List<ISerializableEntity> CurrentTargets
|
||||
{
|
||||
get
|
||||
{
|
||||
_threadLocalCurrentTargets ??= new List<ISerializableEntity>();
|
||||
return _threadLocalCurrentTargets;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Thread-safe method to find an existing duration effect for a single target.
|
||||
/// </summary>
|
||||
private DurationListElement FindExistingDurationEffect(ISerializableEntity target)
|
||||
{
|
||||
foreach (var element in DurationListDict.Values)
|
||||
{
|
||||
if (element.Parent == this && element.Targets.FirstOrDefault() == target)
|
||||
{
|
||||
return element;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Thread-safe method to find an existing duration effect for multiple targets.
|
||||
/// </summary>
|
||||
private DurationListElement FindExistingDurationEffect(IReadOnlyList<ISerializableEntity> targets)
|
||||
{
|
||||
foreach (var element in DurationListDict.Values)
|
||||
{
|
||||
if (element.Parent == this && element.Targets.SequenceEqual(targets))
|
||||
{
|
||||
return element;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
protected readonly List<ISerializableEntity> currentTargets = new List<ISerializableEntity>();
|
||||
public virtual void Apply(ActionType type, float deltaTime, Entity entity, IReadOnlyList<ISerializableEntity> targets, Vector2? worldPosition = null)
|
||||
{
|
||||
if (Disabled) { return; }
|
||||
if (this.type != type) { return; }
|
||||
if (ShouldWaitForInterval(entity, deltaTime)) { return; }
|
||||
|
||||
var localTargets = CurrentTargets;
|
||||
localTargets.Clear();
|
||||
currentTargets.Clear();
|
||||
foreach (ISerializableEntity target in targets)
|
||||
{
|
||||
if (!IsValidTarget(target)) { continue; }
|
||||
localTargets.Add(target);
|
||||
currentTargets.Add(target);
|
||||
}
|
||||
|
||||
if (TargetIdentifiers != null && localTargets.Count == 0) { return; }
|
||||
if (TargetIdentifiers != null && currentTargets.Count == 0) { return; }
|
||||
|
||||
bool hasRequiredItems = HasRequiredItems(entity);
|
||||
if (!hasRequiredItems || !HasRequiredConditions(localTargets))
|
||||
if (!hasRequiredItems || !HasRequiredConditions(currentTargets))
|
||||
{
|
||||
#if CLIENT
|
||||
if (!hasRequiredItems && playSoundOnRequiredItemFailure)
|
||||
@@ -1802,15 +1703,15 @@ namespace Barotrauma
|
||||
if (Duration > 0.0f && !Stackable)
|
||||
{
|
||||
//ignore if not stackable and there's already an identical statuseffect
|
||||
DurationListElement existingEffect = FindExistingDurationEffect(localTargets);
|
||||
DurationListElement existingEffect = DurationList.Find(d => d.Parent == this && d.Targets.SequenceEqual(currentTargets));
|
||||
if (existingEffect != null)
|
||||
{
|
||||
existingEffect.Reset(Math.Max(existingEffect.Timer, Duration), user);
|
||||
existingEffect?.Reset(Math.Max(existingEffect.Timer, Duration), user);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
Apply(deltaTime, entity, localTargets, worldPosition);
|
||||
Apply(deltaTime, entity, currentTargets, worldPosition);
|
||||
}
|
||||
|
||||
private Hull GetHull(Entity entity)
|
||||
@@ -2023,8 +1924,7 @@ namespace Barotrauma
|
||||
|
||||
if (Duration > 0.0f)
|
||||
{
|
||||
var element = new DurationListElement(this, entity, targets, Duration, user);
|
||||
DurationListDict.TryAdd(element.Id, element);
|
||||
DurationList.Add(new DurationListElement(this, entity, targets, Duration, user));
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -2552,7 +2452,7 @@ namespace Barotrauma
|
||||
}
|
||||
if (Interval > 0.0f && entity != null)
|
||||
{
|
||||
intervalTimers ??= new ConcurrentDictionary<Entity, float>();
|
||||
intervalTimers ??= new Dictionary<Entity, float>();
|
||||
intervalTimers[entity] = Interval;
|
||||
}
|
||||
}
|
||||
@@ -2949,15 +2849,13 @@ namespace Barotrauma
|
||||
UpdateAllProjSpecific(deltaTime);
|
||||
|
||||
DelayedEffect.Update(deltaTime);
|
||||
|
||||
// Thread-safe iteration over ConcurrentDictionary
|
||||
foreach (var kvp in DurationListDict)
|
||||
for (int i = DurationList.Count - 1; i >= 0; i--)
|
||||
{
|
||||
DurationListElement element = kvp.Value;
|
||||
DurationListElement element = DurationList[i];
|
||||
|
||||
if (element.Parent.CheckConditionalAlways && !element.Parent.HasRequiredConditions(element.Targets))
|
||||
{
|
||||
DurationListDict.TryRemove(element.Id, out _);
|
||||
DurationList.RemoveAt(i);
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -2966,7 +2864,7 @@ namespace Barotrauma
|
||||
(t is Limb limb && (limb.character == null || limb.character.Removed)));
|
||||
if (element.Targets.Count == 0)
|
||||
{
|
||||
DurationListDict.TryRemove(element.Id, out _);
|
||||
DurationList.RemoveAt(i);
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -3061,7 +2959,7 @@ namespace Barotrauma
|
||||
element.Timer -= deltaTime;
|
||||
|
||||
if (element.Timer > 0.0f) { continue; }
|
||||
DurationListDict.TryRemove(element.Id, out _);
|
||||
DurationList.Remove(element);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3161,8 +3059,8 @@ namespace Barotrauma
|
||||
public static void StopAll()
|
||||
{
|
||||
CoroutineManager.StopCoroutines("statuseffect");
|
||||
DelayedEffect.DelayListDict.Clear();
|
||||
DurationListDict.Clear();
|
||||
DelayedEffect.DelayList.Clear();
|
||||
DurationList.Clear();
|
||||
}
|
||||
|
||||
public void AddTag(Identifier tag)
|
||||
|
||||
Reference in New Issue
Block a user