Various improvements to reduce pressure
This commit is contained in:
@@ -0,0 +1,47 @@
|
||||
using System.Collections.Concurrent;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
namespace AetherBags.Helpers;
|
||||
|
||||
/// <summary>
|
||||
/// Thread-safe cache for compiled Regex objects to avoid repeated compilation overhead.
|
||||
/// </summary>
|
||||
internal static class RegexCache
|
||||
{
|
||||
private const int MaxCacheSize = 128;
|
||||
private static readonly ConcurrentDictionary<string, Regex> Cache = new();
|
||||
|
||||
/// <summary>
|
||||
/// Gets or creates a compiled Regex for the given pattern with case-insensitive matching.
|
||||
/// Returns null if the pattern is invalid.
|
||||
/// </summary>
|
||||
public static Regex? GetOrCreate(string pattern)
|
||||
{
|
||||
if (string.IsNullOrEmpty(pattern))
|
||||
return null;
|
||||
|
||||
if (Cache.TryGetValue(pattern, out var cached))
|
||||
return cached;
|
||||
|
||||
try
|
||||
{
|
||||
var regex = new Regex(pattern, RegexOptions.CultureInvariant | RegexOptions.IgnoreCase | RegexOptions.Compiled);
|
||||
|
||||
if (Cache.Count < MaxCacheSize)
|
||||
{
|
||||
Cache.TryAdd(pattern, regex);
|
||||
}
|
||||
|
||||
return regex;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Clears the regex cache. Call when configuration changes significantly.
|
||||
/// </summary>
|
||||
public static void Clear() => Cache.Clear();
|
||||
}
|
||||
@@ -10,6 +10,7 @@ public sealed class CategoryBucket
|
||||
public List<ItemInfo> Items = null!;
|
||||
public List<ItemInfo> FilteredItems = null!;
|
||||
public bool Used;
|
||||
public bool NeedsSorting = true;
|
||||
}
|
||||
|
||||
public sealed class ItemCountDescComparer : IComparer<ItemInfo>
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using AetherBags.Configuration;
|
||||
using AetherBags.Inventory.Items;
|
||||
using KamiToolKit.Classes;
|
||||
@@ -49,6 +48,7 @@ public static class CategoryBucketManager
|
||||
bucket.Used = false;
|
||||
bucket.Items.Clear();
|
||||
bucket.FilteredItems.Clear();
|
||||
bucket.NeedsSorting = true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -302,8 +302,9 @@ public static class CategoryBucketManager
|
||||
CategoryInfo miscInfo;
|
||||
if (itemInfoByKey.Count > 0)
|
||||
{
|
||||
var sample = itemInfoByKey.Values.First();
|
||||
miscInfo = GetCategoryInfoCached(0u, sample);
|
||||
using var enumerator = itemInfoByKey.Values.GetEnumerator();
|
||||
enumerator.MoveNext();
|
||||
miscInfo = GetCategoryInfoCached(0u, enumerator.Current);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -353,7 +354,12 @@ public static class CategoryBucketManager
|
||||
continue;
|
||||
|
||||
// TODO: Make configurable
|
||||
bucket.Items.Sort(ItemCountDescComparer.Instance);
|
||||
// Only sort if items changed
|
||||
if (bucket.NeedsSorting)
|
||||
{
|
||||
bucket.Items.Sort(ItemCountDescComparer.Instance);
|
||||
bucket.NeedsSorting = false;
|
||||
}
|
||||
sortedCategoryKeys.Add(bucket.Key);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text.RegularExpressions;
|
||||
using AetherBags.Helpers;
|
||||
using AetherBags.Inventory.Items;
|
||||
|
||||
namespace AetherBags.Inventory.Categories;
|
||||
@@ -17,17 +18,8 @@ public static class InventoryFilter
|
||||
if (string.IsNullOrEmpty(filterString))
|
||||
return allCategories;
|
||||
|
||||
Regex? re = null;
|
||||
bool regexValid = true;
|
||||
|
||||
try
|
||||
{
|
||||
re = new Regex(filterString, RegexOptions.CultureInvariant | RegexOptions.IgnoreCase);
|
||||
}
|
||||
catch
|
||||
{
|
||||
regexValid = false;
|
||||
}
|
||||
Regex? re = RegexCache.GetOrCreate(filterString);
|
||||
bool regexValid = re != null;
|
||||
|
||||
filteredCategories.Clear();
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
using System;
|
||||
using System.Text.RegularExpressions;
|
||||
using AetherBags.Configuration;
|
||||
using AetherBags.Helpers;
|
||||
using AetherBags.Inventory.Items;
|
||||
|
||||
namespace AetherBags.Inventory.Categories;
|
||||
@@ -30,17 +30,11 @@ internal static class UserCategoryMatcher
|
||||
if (string.IsNullOrWhiteSpace(pattern))
|
||||
continue;
|
||||
|
||||
try
|
||||
var regex = RegexCache.GetOrCreate(pattern);
|
||||
if (regex != null && regex.IsMatch(item.Name))
|
||||
{
|
||||
if (Regex.IsMatch(item.Name, pattern, RegexOptions.CultureInvariant | RegexOptions.IgnoreCase))
|
||||
{
|
||||
matchesAnyIdentification = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Invalid regex: ignore it.
|
||||
matchesAnyIdentification = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,13 +18,27 @@ public static class HighlightState
|
||||
private static readonly Dictionary<HighlightSource, (HashSet<uint> ids, Vector3 color)> Labels = new();
|
||||
private static readonly Dictionary<HighlightSource, Dictionary<uint, HighlightEntry>> PerItemLabels = new();
|
||||
|
||||
// Flat cache for O(1) lookups
|
||||
private static readonly Dictionary<uint, HighlightEntry> CachedEntries = new(capacity: 512);
|
||||
private static bool _cacheValid;
|
||||
private static int _version;
|
||||
|
||||
/// <summary>
|
||||
/// Version counter that increments when highlight state changes.
|
||||
/// Used by ItemInfo to detect when cached visual state is stale.
|
||||
/// </summary>
|
||||
public static int Version => _version;
|
||||
|
||||
public static string? SelectedAllaganToolsFilterKey { get; set; } = string.Empty;
|
||||
public static string? SelectedBisBuddyFilterKey { get; set; } = string.Empty;
|
||||
|
||||
public static bool IsFilterActive => Filters.Count > 0;
|
||||
|
||||
public static void SetFilter(HighlightSource source, IEnumerable<uint> ids)
|
||||
=> Filters[source] = new HashSet<uint>(ids);
|
||||
{
|
||||
Filters[source] = new HashSet<uint>(ids);
|
||||
_version++;
|
||||
}
|
||||
|
||||
public static bool IsInActiveFilters(uint itemId)
|
||||
{
|
||||
@@ -36,19 +50,42 @@ public static class HighlightState
|
||||
|
||||
public static HighlightEntry? GetHighlightEntry(uint itemId)
|
||||
{
|
||||
EnsureCacheValid();
|
||||
return CachedEntries.TryGetValue(itemId, out var entry) ? entry : null;
|
||||
}
|
||||
|
||||
private static void EnsureCacheValid()
|
||||
{
|
||||
if (_cacheValid) return;
|
||||
|
||||
CachedEntries.Clear();
|
||||
|
||||
// PerItemLabels have priority - add them first
|
||||
foreach (var perItemLabel in PerItemLabels.Values)
|
||||
{
|
||||
if (perItemLabel.TryGetValue(itemId, out var entry))
|
||||
return entry;
|
||||
foreach (var (id, entry) in perItemLabel)
|
||||
{
|
||||
CachedEntries.TryAdd(id, entry);
|
||||
}
|
||||
}
|
||||
|
||||
// Labels are fallback - only add if not already present
|
||||
foreach (var label in Labels.Values)
|
||||
{
|
||||
if (label.ids.Contains(itemId))
|
||||
return new HighlightEntry(itemId, label.color);
|
||||
var color = label.color;
|
||||
foreach (var id in label.ids)
|
||||
{
|
||||
CachedEntries.TryAdd(id, new HighlightEntry(id, color));
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
_cacheValid = true;
|
||||
}
|
||||
|
||||
private static void InvalidateCache()
|
||||
{
|
||||
_cacheValid = false;
|
||||
_version++;
|
||||
}
|
||||
|
||||
public static Vector3? GetLabelColor(uint itemId)
|
||||
@@ -58,6 +95,7 @@ public static class HighlightState
|
||||
{
|
||||
PerItemLabels.Remove(source);
|
||||
Labels[source] = (new HashSet<uint>(ids), color);
|
||||
InvalidateCache();
|
||||
}
|
||||
|
||||
public static void SetLabelWithColors(HighlightSource source, Dictionary<uint, Vector4> itemColors)
|
||||
@@ -76,6 +114,7 @@ public static class HighlightState
|
||||
}
|
||||
|
||||
PerItemLabels[source] = entries;
|
||||
InvalidateCache();
|
||||
}
|
||||
|
||||
public static void SetLabelWithColors(HighlightSource source, IEnumerable<HighlightEntry> entries)
|
||||
@@ -89,6 +128,7 @@ public static class HighlightState
|
||||
}
|
||||
|
||||
PerItemLabels[source] = dict;
|
||||
InvalidateCache();
|
||||
}
|
||||
|
||||
public static void SetLabelWithColors(HighlightSource source, Dictionary<uint, Vector3> itemColors)
|
||||
@@ -102,6 +142,7 @@ public static class HighlightState
|
||||
}
|
||||
|
||||
PerItemLabels[source] = entries;
|
||||
InvalidateCache();
|
||||
}
|
||||
|
||||
public static void ClearAll()
|
||||
@@ -109,14 +150,22 @@ public static class HighlightState
|
||||
Filters.Clear();
|
||||
Labels.Clear();
|
||||
PerItemLabels.Clear();
|
||||
CachedEntries.Clear();
|
||||
_cacheValid = true; // Empty cache is valid
|
||||
_version++;
|
||||
SelectedAllaganToolsFilterKey = string.Empty;
|
||||
}
|
||||
|
||||
public static void ClearFilter(HighlightSource source) => Filters.Remove(source);
|
||||
public static void ClearFilter(HighlightSource source)
|
||||
{
|
||||
Filters.Remove(source);
|
||||
_version++;
|
||||
}
|
||||
|
||||
public static void ClearLabel(HighlightSource source)
|
||||
{
|
||||
Labels.Remove(source);
|
||||
PerItemLabels.Remove(source);
|
||||
InvalidateCache();
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
using System;
|
||||
using System.Numerics;
|
||||
using System.Text.RegularExpressions;
|
||||
using AetherBags.Helpers;
|
||||
using AetherBags.Inventory.Context;
|
||||
using FFXIVClientStructs.FFXIV.Client.Game;
|
||||
using Lumina.Excel;
|
||||
@@ -23,6 +24,12 @@ public sealed class ItemInfo : IEquatable<ItemInfo>
|
||||
|
||||
private string? _name;
|
||||
private string? _description;
|
||||
private string? _levelString;
|
||||
private string? _itemLevelString;
|
||||
|
||||
private int _cachedHighlightVersion = -1;
|
||||
private float _cachedVisualAlpha;
|
||||
private Vector3 _cachedHighlightColor;
|
||||
|
||||
private ref readonly Item Row
|
||||
{
|
||||
@@ -44,6 +51,8 @@ public sealed class ItemInfo : IEquatable<ItemInfo>
|
||||
|
||||
public int Level => Row.LevelEquip;
|
||||
public int ItemLevel => (int)Row.LevelItem.RowId;
|
||||
private string LevelString => _levelString ??= Level.ToString();
|
||||
private string ItemLevelString => _itemLevelString ??= ItemLevel.ToString();
|
||||
public int Rarity => Row.Rarity;
|
||||
public uint VendorPrice => Row.PriceLow;
|
||||
public uint StackSize => Row.StackSize;
|
||||
@@ -90,19 +99,37 @@ public sealed class ItemInfo : IEquatable<ItemInfo>
|
||||
}
|
||||
}
|
||||
|
||||
public float VisualAlpha => IsEligibleForContext ? 1.0f : 0.4f;
|
||||
public float VisualAlpha
|
||||
{
|
||||
get
|
||||
{
|
||||
EnsureVisualStateCached();
|
||||
return _cachedVisualAlpha;
|
||||
}
|
||||
}
|
||||
|
||||
public Vector3 HighlightOverlayColor
|
||||
{
|
||||
get
|
||||
{
|
||||
if (!System.Config.Categories.BisBuddyEnabled)
|
||||
return Vector3.Zero;
|
||||
|
||||
return HighlightState.GetLabelColor(Item.ItemId) ?? Vector3.Zero;
|
||||
EnsureVisualStateCached();
|
||||
return _cachedHighlightColor;
|
||||
}
|
||||
}
|
||||
|
||||
private void EnsureVisualStateCached()
|
||||
{
|
||||
int currentVersion = HighlightState.Version;
|
||||
if (_cachedHighlightVersion == currentVersion)
|
||||
return;
|
||||
|
||||
_cachedVisualAlpha = IsEligibleForContext ? 1.0f : 0.4f;
|
||||
_cachedHighlightColor = System.Config.Categories.BisBuddyEnabled
|
||||
? HighlightState.GetLabelColor(Item.ItemId) ?? Vector3.Zero
|
||||
: Vector3.Zero;
|
||||
_cachedHighlightVersion = currentVersion;
|
||||
}
|
||||
|
||||
private bool CheckNativeContextEligibility()
|
||||
{
|
||||
uint contextId = InventoryContextState.ActiveContextId;
|
||||
@@ -138,14 +165,16 @@ public sealed class ItemInfo : IEquatable<ItemInfo>
|
||||
if (string.IsNullOrEmpty(searchTerms))
|
||||
return true;
|
||||
|
||||
var re = new Regex(searchTerms, RegexOptions.CultureInvariant | RegexOptions.IgnoreCase);
|
||||
var re = RegexCache.GetOrCreate(searchTerms);
|
||||
if (re == null)
|
||||
return false;
|
||||
|
||||
if (re.IsMatch(Name)) return true;
|
||||
|
||||
if (re.IsMatch(Description)) return true;
|
||||
|
||||
if (re.IsMatch(Level.ToString())) return true;
|
||||
if (re.IsMatch(ItemLevel.ToString())) return true;
|
||||
if (re.IsMatch(LevelString)) return true;
|
||||
if (re.IsMatch(ItemLevelString)) return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
@@ -155,8 +184,8 @@ public sealed class ItemInfo : IEquatable<ItemInfo>
|
||||
if (re.IsMatch(Name)) return true;
|
||||
if (re.IsMatch(Description)) return true;
|
||||
|
||||
if (re.IsMatch(Level.ToString())) return true;
|
||||
if (re.IsMatch(ItemLevel.ToString())) return true;
|
||||
if (re.IsMatch(LevelString)) return true;
|
||||
if (re.IsMatch(ItemLevelString)) return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using AetherBags.Configuration;
|
||||
using AetherBags.Currency;
|
||||
using AetherBags.Inventory.Categories;
|
||||
@@ -109,7 +108,7 @@ public abstract class InventoryStateBase
|
||||
}
|
||||
else
|
||||
{
|
||||
UpdateAllaganHighlight(HighlightState.SelectedBisBuddyFilterKey);
|
||||
UpdateBisBuddyHighlight(HighlightState.SelectedBisBuddyFilterKey);
|
||||
}
|
||||
}
|
||||
else
|
||||
@@ -156,10 +155,10 @@ public abstract class InventoryStateBase
|
||||
return;
|
||||
}
|
||||
|
||||
var filterItems = System.IPC.AllaganTools.GetFilterItems(filterKey);
|
||||
if (filterItems != null)
|
||||
var bisItems = System.IPC.BisBuddy.ItemLookup;
|
||||
if (bisItems.Count > 0)
|
||||
{
|
||||
HighlightState.SetFilter(HighlightSource.BiSBuddy, filterItems.Keys);
|
||||
HighlightState.SetFilter(HighlightSource.BiSBuddy, bisItems.Keys);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user