diff --git a/AetherBags/Addons/AddonInventoryWindow.cs b/AetherBags/Addons/AddonInventoryWindow.cs index 5053139..b0b6934 100644 --- a/AetherBags/Addons/AddonInventoryWindow.cs +++ b/AetherBags/Addons/AddonInventoryWindow.cs @@ -79,7 +79,7 @@ public unsafe class AddonInventoryWindow : InventoryAddonBase LayoutContent(); - addon->SubscribeAtkArrayData(1, (int)NumberArrayType.Inventory); + //addon->SubscribeAtkArrayData(1, (int)NumberArrayType.Inventory); System.LootedItemsTracker.OnLootedItemsChanged += OnLootedItemsChanged; diff --git a/AetherBags/Addons/InventoryAddonBase.cs b/AetherBags/Addons/InventoryAddonBase.cs index d07c8d6..444e92d 100644 --- a/AetherBags/Addons/InventoryAddonBase.cs +++ b/AetherBags/Addons/InventoryAddonBase.cs @@ -62,6 +62,10 @@ public abstract unsafe class InventoryAddonBase : NativeAddon, IInventoryWindow private readonly HashSet _searchMatchScratch = new(); private bool _isRefreshing; + private int _requestedUpdateCount; + private int _refreshFromLifecycleCount; + private long _lastLogTick; + public void ManualRefresh() { if (!IsOpen) return; @@ -104,6 +108,10 @@ public abstract unsafe class InventoryAddonBase : NativeAddon, IInventoryWindow try { _isRefreshing = true; + + _refreshFromLifecycleCount++; + LogRefreshStats(); + InventoryState.RefreshFromGame(); RefreshCategoriesCore(autosize: true); } @@ -405,8 +413,24 @@ public abstract unsafe class InventoryAddonBase : NativeAddon, IInventoryWindow RefreshCategoriesCore(false); } + private void LogRefreshStats() + { + long now = Environment.TickCount64; + if (now - _lastLogTick > 1000) // Log every second + { + Services.Logger.DebugOnly($"[Perf] Last 1s: OnRequestedUpdate={_requestedUpdateCount}, RefreshFromLifecycle={_refreshFromLifecycleCount}"); + _requestedUpdateCount = 0; + _refreshFromLifecycleCount = 0; + _lastLogTick = now; + } + } + + /* protected override void OnRequestedUpdate(AtkUnitBase* addon, NumberArrayData** numberArrayData, StringArrayData** stringArrayData) { + _requestedUpdateCount++; + LogRefreshStats(); + base.OnRequestedUpdate(addon, numberArrayData, stringArrayData); if (DragDropState.IsDragging) @@ -415,6 +439,7 @@ public abstract unsafe class InventoryAddonBase : NativeAddon, IInventoryWindow InventoryState.RefreshFromGame(); RefreshCategoriesCore(autosize: true); } + */ protected override void OnSetup(AtkUnitBase* addon) { diff --git a/AetherBags/Helpers/Util.cs b/AetherBags/Helpers/Util.cs index f498e4d..044e1a3 100644 --- a/AetherBags/Helpers/Util.cs +++ b/AetherBags/Helpers/Util.cs @@ -88,7 +88,7 @@ public static class Util { FileInfo file = JsonFileHelper.GetFileInfo(SystemConfiguration.FileName); var config = JsonFileHelper.LoadFile(file.FullName); - config?.EnsureInitialized(); + config.EnsureInitialized(); return config; } diff --git a/AetherBags/Inventory/LootedItemsTracker.cs b/AetherBags/Inventory/LootedItemsTracker.cs index 2aeeec4..5463bc5 100644 --- a/AetherBags/Inventory/LootedItemsTracker.cs +++ b/AetherBags/Inventory/LootedItemsTracker.cs @@ -20,6 +20,8 @@ public sealed unsafe class LootedItemsTracker : IDisposable private readonly List _lootedItems = new(capacity: 64); private readonly Dictionary<(uint ItemId, bool IsHq), (InventoryItem Item, int Quantity)> _pendingChanges = new(capacity: 32); + private static HashSet? _filteredCategoryItems; + private bool _isEnabled; private long _batchStartTick; private bool _hasPendingRemoval; @@ -171,12 +173,18 @@ public sealed unsafe class LootedItemsTracker : IDisposable private static bool ShouldFilterItem(uint itemId) { - if (!Services.DataManager.GetExcelSheet().TryGetRow(itemId, out var item)) - return false; + if (_filteredCategoryItems == null) + { + _filteredCategoryItems = new HashSet(); + var sheet = Services.DataManager.GetExcelSheet(); + foreach (var row in sheet) + { + if (row.ItemUICategory.RowId == 62) + _filteredCategoryItems.Add(row.RowId); + } + Services.Logger.DebugOnly($"[LootedItemsTracker] Built filter cache with {_filteredCategoryItems.Count} items"); + } - if (item.ItemUICategory.RowId == 62) - return true; - - return false; + return _filteredCategoryItems.Contains(itemId); } } diff --git a/AetherBags/Inventory/State/InventoryStateBase.cs b/AetherBags/Inventory/State/InventoryStateBase.cs index 813fe26..c4d9cc1 100644 --- a/AetherBags/Inventory/State/InventoryStateBase.cs +++ b/AetherBags/Inventory/State/InventoryStateBase.cs @@ -69,7 +69,13 @@ public abstract class InventoryStateBase bool allaganCategoriesEnabled = config.Categories.AllaganToolsCategoriesEnabled && categoriesEnabled; bool bisCategoriesEnabled = config.Categories.BisBuddyEnabled && categoriesEnabled; // TODO: Cache this when config changes - var userCategories = config.Categories.UserCategories.Where(c => c.Enabled).ToList(); + UserCategoriesSortedScratch.Clear(); + foreach (var cat in config.Categories. UserCategories) + { + if (cat.Enabled) + UserCategoriesSortedScratch.Add(cat); + } + var userCategories = UserCategoriesSortedScratch; if (userCategoriesEnabled && userCategories.Count > 0) { diff --git a/AetherBags/Nodes/Inventory/LootedItemDisplayNode.cs b/AetherBags/Nodes/Inventory/LootedItemDisplayNode.cs index 04f1834..5f683b7 100644 --- a/AetherBags/Nodes/Inventory/LootedItemDisplayNode.cs +++ b/AetherBags/Nodes/Inventory/LootedItemDisplayNode.cs @@ -1,5 +1,6 @@ using System; using AetherBags.Inventory.Items; +using FFXIVClientStructs.FFXIV.Client.Game; using FFXIVClientStructs.FFXIV.Client.UI; using FFXIVClientStructs.FFXIV.Common.Math; using FFXIVClientStructs.FFXIV.Component.GUI; @@ -42,17 +43,28 @@ public sealed unsafe class LootedItemDisplayNode : SimpleComponentNode _quantityTextNode.AttachNode(this); } - public LootedItemInfo LootedItem + public LootedItemInfo? LootedItem { get; set { bool needsCollisionUpdate = field is null && value is not null; field = value; - var item = value.Item; - _iconNode.IconId = item.IconId; - _iconNode.ItemTooltip = item.ItemId; - _quantityTextNode.String = value.Quantity > 1 ? value.Quantity.ToString() : string.Empty; + + if (value is not null) + { + InventoryItem item = value.Item; + _iconNode.IconId = item.IconId; + _iconNode.ItemTooltip = item.ItemId; + _quantityTextNode.String = value.Quantity > 1 ? value.Quantity.ToString() : string.Empty; + _iconNode.IsVisible = true; + _quantityTextNode.IsVisible = true; + } + else + { + _iconNode.IsVisible = false; + _quantityTextNode.String = string.Empty; + } if (needsCollisionUpdate) { @@ -61,7 +73,7 @@ public sealed unsafe class LootedItemDisplayNode : SimpleComponentNode addon->UpdateCollisionNodeList(false); } } - } = null!; + } = null; private void OnMouseClick(AtkEventListener* thisPtr, AtkEventType eventType, int eventParam, AtkEvent* atkEvent, AtkEventData* atkEventData) { diff --git a/AetherBags/Nodes/Layout/DeferrableLayoutListNode.cs b/AetherBags/Nodes/Layout/DeferrableLayoutListNode.cs index 5efaf83..e9632e7 100644 --- a/AetherBags/Nodes/Layout/DeferrableLayoutListNode.cs +++ b/AetherBags/Nodes/Layout/DeferrableLayoutListNode.cs @@ -180,6 +180,7 @@ public abstract class DeferrableLayoutListNode : SimpleComponentNode private List? _desiredScratch; private List? _toRemoveScratch; private HashSet? _dataKeysScratch; + private Dictionary? _byKeyScratch; [MethodImpl(MethodImplOptions.AggressiveInlining)] private List RentExistingList(int capacity) @@ -309,7 +310,16 @@ public abstract class DeferrableLayoutListNode : SimpleComponentNode Dictionary? byKey = null; if (existing.Count > 0) { - byKey = new Dictionary(existing.Count, keyComparer); + if (_byKeyScratch is Dictionary reusable) + { + byKey = reusable; + byKey.Clear(); + } + else + { + byKey = new Dictionary(existing.Count, keyComparer); + } + for (int i = 0; i < existing.Count; i++) { var tu = (TU)existing[i]; @@ -407,6 +417,12 @@ public abstract class DeferrableLayoutListNode : SimpleComponentNode RecalculateLayout(); } + if (byKey != null) + { + byKey.Clear(); + _byKeyScratch = byKey as Dictionary; + } + return structureChanged || orderChanged; }