Reapply "OBT1.1.0 Merge branch 'dev_pte' into dev"
This reverts commit 046483b9da.
This commit is contained in:
@@ -1,13 +1,67 @@
|
||||
using Microsoft.Xna.Framework;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
/// <summary>
|
||||
/// Thread-safe wrapper for AITarget list operations.
|
||||
/// Uses copy-on-write pattern for lock-free reads.
|
||||
/// </summary>
|
||||
class ThreadSafeAITargetList : IEnumerable<AITarget>
|
||||
{
|
||||
private volatile List<AITarget> _list = new List<AITarget>();
|
||||
private readonly object _writeLock = new object();
|
||||
|
||||
public int Count => _list.Count;
|
||||
|
||||
public void Add(AITarget target)
|
||||
{
|
||||
lock (_writeLock)
|
||||
{
|
||||
var newList = new List<AITarget>(_list) { target };
|
||||
Interlocked.Exchange(ref _list, newList);
|
||||
}
|
||||
}
|
||||
|
||||
public bool Remove(AITarget target)
|
||||
{
|
||||
lock (_writeLock)
|
||||
{
|
||||
var newList = new List<AITarget>(_list);
|
||||
bool removed = newList.Remove(target);
|
||||
if (removed)
|
||||
{
|
||||
Interlocked.Exchange(ref _list, newList);
|
||||
}
|
||||
return removed;
|
||||
}
|
||||
}
|
||||
|
||||
public void Clear()
|
||||
{
|
||||
Interlocked.Exchange(ref _list, new List<AITarget>());
|
||||
}
|
||||
|
||||
public bool Contains(AITarget target) => _list.Contains(target);
|
||||
|
||||
public AITarget this[int index] => _list[index];
|
||||
|
||||
public IEnumerator<AITarget> GetEnumerator() => _list.GetEnumerator();
|
||||
System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() => GetEnumerator();
|
||||
|
||||
public List<AITarget> ToList() => new List<AITarget>(_list);
|
||||
public AITarget FirstOrDefault(Func<AITarget, bool> predicate) => _list.FirstOrDefault(predicate);
|
||||
public IEnumerable<AITarget> Where(Func<AITarget, bool> predicate) => _list.Where(predicate);
|
||||
public bool Any(Func<AITarget, bool> predicate) => _list.Any(predicate);
|
||||
}
|
||||
|
||||
partial class AITarget
|
||||
{
|
||||
public static List<AITarget> List = new List<AITarget>();
|
||||
public static ThreadSafeAITargetList List = new ThreadSafeAITargetList();
|
||||
|
||||
private Entity entity;
|
||||
public Entity Entity
|
||||
|
||||
@@ -5,6 +5,7 @@ using Microsoft.Xna.Framework;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
@@ -1817,7 +1818,9 @@ namespace Barotrauma
|
||||
public static bool HasDivingMask(Character character, float conditionPercentage = 0, bool requireOxygenTank = true)
|
||||
=> HasItem(character, Tags.LightDivingGear, out _, requireOxygenTank ? Tags.OxygenSource : Identifier.Empty, conditionPercentage, requireEquipped: true);
|
||||
|
||||
private static List<Item> matchingItems = new List<Item>();
|
||||
// ThreadLocal to ensure thread safety - each thread gets its own list instance
|
||||
private static readonly ThreadLocal<List<Item>> matchingItemsLocal = new ThreadLocal<List<Item>>(() => new List<Item>());
|
||||
private static List<Item> matchingItems => matchingItemsLocal.Value;
|
||||
|
||||
/// <summary>
|
||||
/// Note: uses a single list for matching items. The item is reused each time when the method is called. So if you use the method twice, and then refer to the first items, you'll actually get the second.
|
||||
@@ -1825,15 +1828,16 @@ namespace Barotrauma
|
||||
/// </summary>
|
||||
public static bool HasItem(Character character, Identifier tagOrIdentifier, out IEnumerable<Item> items, Identifier containedTag = default, float conditionPercentage = 0, bool requireEquipped = false, bool recursive = true, Func<Item, bool> predicate = null)
|
||||
{
|
||||
matchingItems.Clear();
|
||||
items = matchingItems;
|
||||
var localMatchingItems = matchingItems;
|
||||
localMatchingItems.Clear();
|
||||
items = localMatchingItems;
|
||||
if (character?.Inventory == null) { return false; }
|
||||
matchingItems = character.Inventory.FindAllItems(i => (i.Prefab.Identifier == tagOrIdentifier || i.HasTag(tagOrIdentifier)) &&
|
||||
character.Inventory.FindAllItems(i => (i.Prefab.Identifier == tagOrIdentifier || i.HasTag(tagOrIdentifier)) &&
|
||||
i.ConditionPercentage >= conditionPercentage &&
|
||||
(!requireEquipped || character.HasEquippedItem(i)) &&
|
||||
(predicate == null || predicate(i)), recursive, matchingItems);
|
||||
items = matchingItems;
|
||||
foreach (var item in matchingItems)
|
||||
(predicate == null || predicate(i)), recursive, localMatchingItems);
|
||||
items = localMatchingItems;
|
||||
foreach (var item in localMatchingItems)
|
||||
{
|
||||
if (item == null) { continue; }
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using Barotrauma.IO;
|
||||
using System.Linq;
|
||||
@@ -9,7 +10,8 @@ namespace Barotrauma
|
||||
{
|
||||
class NPCConversationCollection : Prefab
|
||||
{
|
||||
public static readonly Dictionary<LanguageIdentifier, PrefabCollection<NPCConversationCollection>> Collections = new Dictionary<LanguageIdentifier, PrefabCollection<NPCConversationCollection>>();
|
||||
// Thread-safe dictionary for language-based collections
|
||||
public static readonly ConcurrentDictionary<LanguageIdentifier, PrefabCollection<NPCConversationCollection>> Collections = new ConcurrentDictionary<LanguageIdentifier, PrefabCollection<NPCConversationCollection>>();
|
||||
|
||||
public readonly LanguageIdentifier Language;
|
||||
|
||||
@@ -160,7 +162,24 @@ namespace Barotrauma
|
||||
return currentFlags;
|
||||
}
|
||||
|
||||
private static readonly List<NPCConversation> previousConversations = new List<NPCConversation>();
|
||||
// Thread-safe previous conversations tracking using copy-on-write pattern
|
||||
private static volatile List<NPCConversation> _previousConversations = new List<NPCConversation>();
|
||||
private static readonly object _previousConversationsLock = new object();
|
||||
private static List<NPCConversation> previousConversations => _previousConversations;
|
||||
|
||||
private static void AddToPreviousConversations(NPCConversation conversation)
|
||||
{
|
||||
lock (_previousConversationsLock)
|
||||
{
|
||||
var newList = new List<NPCConversation>(_previousConversations);
|
||||
newList.Insert(0, conversation);
|
||||
if (newList.Count > MaxPreviousConversations)
|
||||
{
|
||||
newList.RemoveAt(MaxPreviousConversations);
|
||||
}
|
||||
_previousConversations = newList;
|
||||
}
|
||||
}
|
||||
|
||||
public static List<(Character speaker, string line)> CreateRandom(List<Character> availableSpeakers)
|
||||
{
|
||||
@@ -281,8 +300,7 @@ namespace Barotrauma
|
||||
|
||||
if (baseConversation == null)
|
||||
{
|
||||
previousConversations.Insert(0, selectedConversation);
|
||||
if (previousConversations.Count > MaxPreviousConversations) previousConversations.RemoveAt(MaxPreviousConversations);
|
||||
AddToPreviousConversations(selectedConversation);
|
||||
}
|
||||
lineList.Add((speaker, selectedConversation.Line));
|
||||
CreateConversation(availableSpeakers, assignedSpeakers, selectedConversation, lineList, availableConversations);
|
||||
|
||||
+1
-1
@@ -119,7 +119,7 @@ namespace Barotrauma
|
||||
|
||||
protected override bool CheckObjectiveState()
|
||||
{
|
||||
if (item.IgnoreByAI(character) || Item.DeconstructItems.Contains(item))
|
||||
if (item.IgnoreByAI(character) || Item.IsMarkedForDeconstruction(item))
|
||||
{
|
||||
Abandon = true;
|
||||
}
|
||||
|
||||
+1
-1
@@ -114,7 +114,7 @@ namespace Barotrauma
|
||||
if (!allowUnloading) { return false; }
|
||||
if (requireValidContainer && !IsValidContainer(item.Container, character)) { return false; }
|
||||
}
|
||||
if (ignoreItemsMarkedForDeconstruction && Item.DeconstructItems.Contains(item)) { return false; }
|
||||
if (ignoreItemsMarkedForDeconstruction && Item.IsMarkedForDeconstruction(item)) { return false; }
|
||||
if (!item.HasAccess(character)) { return false; }
|
||||
if (character != null && !IsItemInsideValidSubmarine(item, character)) { return false; }
|
||||
if (item.HasBallastFloraInHull) { return false; }
|
||||
|
||||
+3
-1
@@ -1,5 +1,6 @@
|
||||
#nullable enable
|
||||
using Microsoft.Xna.Framework;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Barotrauma.Items.Components;
|
||||
@@ -68,8 +69,9 @@ namespace Barotrauma
|
||||
|
||||
/// <summary>
|
||||
/// When did the character last inspect whether some other character has stolen items on them?
|
||||
/// Thread-safe dictionary for concurrent access.
|
||||
/// </summary>
|
||||
private static readonly Dictionary<Character, double> lastInspectionTimes = new Dictionary<Character, double>();
|
||||
private static readonly ConcurrentDictionary<Character, double> lastInspectionTimes = new ConcurrentDictionary<Character, double>();
|
||||
|
||||
private const float NormalInspectionInterval = 120.0f;
|
||||
private const float CriminalInspectionInterval = 30.0f;
|
||||
|
||||
@@ -440,7 +440,7 @@ namespace Barotrauma
|
||||
|
||||
if (Identifier == Tags.DeconstructThis && item.AllowDeconstruct)
|
||||
{
|
||||
if (item.AllowDeconstruct && !Item.DeconstructItems.Contains(item) &&
|
||||
if (item.AllowDeconstruct && !Item.IsMarkedForDeconstruction(item) &&
|
||||
//only allow deconstructing if there are no deconstruction recipes (= deconstructing yields nothing), or deconstruction recipes that
|
||||
(item.Prefab.DeconstructItems.None() ||
|
||||
item.Prefab.DeconstructItems.Any(deconstructItem =>
|
||||
@@ -454,7 +454,7 @@ namespace Barotrauma
|
||||
}
|
||||
else if (Identifier == Tags.DontDeconstructThis)
|
||||
{
|
||||
if (Item.DeconstructItems.Contains(item)) { return true; }
|
||||
if (Item.IsMarkedForDeconstruction(item)) { return true; }
|
||||
}
|
||||
|
||||
ImmutableArray<Identifier> targetItems = GetTargetItems(option);
|
||||
|
||||
Reference in New Issue
Block a user