Cheaper window open, lock-only button refresh, re-sort only changed groups

- UiFrame_OnShow requests a full categorization pass only if the last one
  was more than 60 seconds ago.
- Lock-only cache updates refresh just the affected item buttons
  (AssignItemToItemButton) instead of running UpdateWindow().
- SortGroups() reuses a group's previous sorted order when none of its
  items were re-categorized and it holds exactly the same items; any
  full categorization pass invalidates all sorted orders.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019W3rwt5PZSpEDkXYzRuRhH
This commit is contained in:
2026-11-02 00:00:41 +00:00
co-authored by Claude Opus 5.5
parent aa9cdef81e
commit b85eb2ce53
4 changed files with 134 additions and 2 deletions
+115 -1
View File
@@ -60,6 +60,9 @@ Bagshui:AddComponent(function()
-- Before doing anything else, we need to know if we're online.
self:UpdateOnlineStatus()
-- Was a full redraw already requested before the cache update?
local windowUpdateWasNeeded = self.windowUpdateNeeded
-- Always update the cache if we get this far.
self:UpdateCache()
@@ -70,6 +73,25 @@ Bagshui:AddComponent(function()
return
end
-- Only items locking/unlocking (picked up, or moving between bags and bank): refresh those
-- buttons so they turn gray and back, instead of redrawing the whole window.
if
self.cacheOnlyLockChanges
and not windowUpdateWasNeeded
and not self.forceResort
and not self.resortNeeded
and not self.lookupTablesStale
and not self.editMode
then
self:RefreshLockChangedItemButtons()
self.windowUpdateNeeded = false
self.windowUpdateBlocked = false
if update_cascadeInventory then
update_cascadeInventory:Update()
end
return
end
-- Nothing changed and nothing is waiting for a redraw -- skip the entire pipeline.
-- This is the primary optimization for spurious BAG_UPDATE events.
if not self.cacheChanged and not self.windowUpdateNeeded and not self.forceResort and not self.lookupTablesStale then
@@ -139,6 +161,21 @@ Bagshui:AddComponent(function()
self:Update(cascade)
end
--- Re-apply the look of item buttons whose item only locked or unlocked in the last cache update
--- (gray while locked, badges dimmed). Same as what a full redraw does for those buttons.
function Inventory:RefreshLockChangedItemButtons()
local changed = self.lockOnlyChangedItems
if not changed then
return
end
for _, button in ipairs(self.ui.buttons.itemSlots) do
local buttonInfo = button.bagshuiData
if buttonInfo and buttonInfo.item and changed[buttonInfo.item] and button:IsVisible() then
self.ui:AssignItemToItemButton(button, buttonInfo.item, buttonInfo.groupId)
end
end
end
--- Refresh only item slot colors without all the overhead of calculating the window layout.
--- High-performance -- much better than `UpdateWindow()`/`ForceUpdateWindow()`, so it should be used for
--- changing highlights or opacity (searching, bag bar mouseover, Edit Mode highlighting, etc.).
@@ -416,6 +453,9 @@ Bagshui:AddComponent(function()
end
end
-- Sorted-order cache helpers, defined below SortGroups()'s cache tables.
local MarkGroupForResort, InvalidateAllSortedGroups
--- Assign categories and groups to all items in the inventory cache.
--- **Should not be called directly!** Use `Inventory:Update()` to coordinate the process.
---
@@ -447,6 +487,9 @@ Bagshui:AddComponent(function()
if fullPass then
self.categorizeEpoch = (self.categorizeEpoch or 0) + 1
self.fullCategorizeNeeded = false
self.lastFullCategorizeTime = _G.GetTime()
-- Every group gets re-sorted after a full pass (see SortGroups()).
InvalidateAllSortedGroups()
end
-- Perform the actual categorization.
@@ -461,6 +504,7 @@ Bagshui:AddComponent(function()
-- items are always categorized so they never show up with an unknown category in the
-- Bagshui tooltip; unchanged items keep the category from the last pass.
if fullPass or self:CategoryCacheStale(self.inventory[bagNum][slotNum]) then
local previousGroupId = self.inventory[bagNum][slotNum].bagshuiGroupId
BsCategories:Categorize(
self.inventory[bagNum][slotNum],
nil, -- Categorization as any alternate character is not currently enabled.
@@ -468,6 +512,9 @@ Bagshui:AddComponent(function()
self.categoryIdsGroupedBySequence
)
self:StoreCategorySignature(self.inventory[bagNum][slotNum])
-- The group this item left and the group it's in now both need re-sorting.
MarkGroupForResort(previousGroupId)
MarkGroupForResort(self.inventory[bagNum][slotNum].bagshuiGroupId)
end
-- Fall back to the default group if one wasn't assigned.
@@ -500,6 +547,69 @@ Bagshui:AddComponent(function()
self.errorText = BsCategories:GetErrors(self.activeCategoryIds)
end
-- Sorted-order cache for groups (see SortGroups()). A group's cached order is reused when none of
-- its items were re-categorized since it was sorted (an item that changed in any way the sort can
-- see is re-categorized, see CategoryCacheStale()) and it still holds exactly the same items.
-- `groupSortVersions[groupId]` goes up whenever an item enters or leaves a group or changes;
-- `sortEpoch` goes up after every full categorization pass (rules, settings, sort orders...).
local groupSortVersions = {}
local sortEpoch = 0
-- { [groupItems table] = { list = {...}, set = { [item] = true }, count, version, epoch } }
local sortedGroupCache = setmetatable({}, { __mode = "k" })
MarkGroupForResort = function(groupId)
if groupId and groupId ~= "" then
groupSortVersions[groupId] = (groupSortVersions[groupId] or 0) + 1
end
end
InvalidateAllSortedGroups = function()
sortEpoch = sortEpoch + 1
end
--- Put a group's items back in their last sorted order if nothing relevant changed.
---@return boolean restored
local function RestoreSortedGroup(items, groupId)
local cached = sortedGroupCache[items]
local count = table.getn(items)
if
not cached
or cached.epoch ~= sortEpoch
or cached.version ~= (groupSortVersions[groupId] or 0)
or cached.count ~= count
then
return false
end
for i = 1, count do
if not cached.set[items[i]] then
return false
end
end
for i = 1, count do
items[i] = cached.list[i]
end
return true
end
--- Remember a group's sorted order.
local function StoreSortedGroup(items, groupId)
local cached = sortedGroupCache[items]
if not cached then
cached = { list = {}, set = {} }
sortedGroupCache[items] = cached
end
BsUtil.TableClear(cached.list)
BsUtil.TableClear(cached.set)
local count = table.getn(items)
for i = 1, count do
table.insert(cached.list, items[i])
cached.set[items[i]] = true
end
cached.count = count
cached.epoch = sortEpoch
cached.version = groupSortVersions[groupId] or 0
end
-- What each item's last categorization was based on: { [item] = signature }.
-- Weak keys so replaced item tables don't linger; kept out of the item tables so nothing extra
-- ends up in SavedVariables.
@@ -551,7 +661,11 @@ Bagshui:AddComponent(function()
-- **Should not be called directly!** Use `Inventory:Update()` to coordinate the process.
function Inventory:SortGroups()
for groupId, groupConfig in pairs(self.activeGroups) do
BsSortOrders:SortGroup(self.groupItems[groupId], groupConfig, self.settings.defaultSortOrder)
-- Groups whose items didn't change keep their previous sorted order (see RestoreSortedGroup()).
if not RestoreSortedGroup(self.groupItems[groupId], groupId) then
BsSortOrders:SortGroup(self.groupItems[groupId], groupConfig, self.settings.defaultSortOrder)
StoreSortedGroup(self.groupItems[groupId], groupId)
end
end
end