diff --git a/Sorting/SortEngine.lua b/Sorting/SortEngine.lua index f806dc5..80d0702 100644 --- a/Sorting/SortEngine.lua +++ b/Sorting/SortEngine.lua @@ -12,7 +12,8 @@ SortEngine.sortingInProgress = false -- Performance: Max items to move per cycle -- Baganator uses 5 for manual transfers, but sorting needs more for smooth operation local MAX_MOVES_PER_CYCLE = 20 -local MAX_BANK_MOVES_PER_CYCLE = 30 -- Bank has 50% more slots, needs more moves per cycle +-- Bank uses fewer moves per cycle to avoid lock conflicts (bank ops are slower) +local MAX_BANK_MOVES_PER_CYCLE = 15 -- Current sort context (set by ExecuteSort, used by ApplySort) local currentSortType = "bags" @@ -1135,6 +1136,11 @@ local function ApplySort(bagIDs, items, targetPositions) end end + -- Debug: report locked items if any + if lockedCount > 0 then + addon:DebugSort("ApplySort: %d moves completed, %d items were locked", moveCount, lockedCount) + end + return moveCount end @@ -1791,9 +1797,10 @@ function SortEngine:ExecuteSort(sortFunction, analyzeFunction, updateFrame, sort sortType, passCount, moveCount, currentAnalysis.itemsOutOfPlace, currentAnalysis.totalItems, remainingRatio * 100) -- PROGRESSIVE DELAY: Short delay to let server process moves - -- Reduced from 0.9-3.4s to 0.2-0.5s for smoother sorting - local baseDelay = 0.2 - local complexityDelay = math.min(currentAnalysis.itemsOutOfPlace * 0.01, 0.3) -- max 0.3 seconds + -- Bank needs longer delays because bank operations take longer to complete + local baseDelay = (sortType == "bank") and 0.35 or 0.2 + local maxComplexityDelay = (sortType == "bank") and 0.5 or 0.3 + local complexityDelay = math.min(currentAnalysis.itemsOutOfPlace * 0.01, maxComplexityDelay) local totalDelay = baseDelay + complexityDelay addon:DebugSort("Waiting %.1f seconds before next pass...", totalDelay) diff --git a/UI/BagFrame.lua b/UI/BagFrame.lua index e89eb33..37c7142 100644 --- a/UI/BagFrame.lua +++ b/UI/BagFrame.lua @@ -182,6 +182,111 @@ function BagFrame:UpdateLockStates() Guda_UpdateLockStates(bagParents) end +-- Update a single slot without full frame redraw (used for manual item moves) +-- Returns true if successful, false if full redraw is needed +function BagFrame:UpdateSingleSlot(bagID, slotID) + if not Guda_BagFrame:IsShown() then return false end + if currentViewChar then return false end -- Can't do single-slot for other characters + + -- Find the button for this slot in itemButtons + local targetButton = nil + for _, button in ipairs(itemButtons) do + if button.bagID == bagID and button.slot == slotID then + targetButton = button + break + end + end + + if not targetButton then return false end + + -- Get fresh item data for this slot + local itemLink = GetContainerItemLink(bagID, slotID) + local itemData = nil + + if itemLink then + local texture, itemCount, locked = GetContainerItemInfo(bagID, slotID) + local itemID = nil + local _, _, idStr = string.find(itemLink, "item:(%d+)") + if idStr then itemID = tonumber(idStr) end + + if itemID then + local name, link, quality, iLevel, _, itemType, stackCount, subType, _, equipLoc = GetItemInfo(itemID) + itemData = { + link = itemLink, + texture = texture, + count = itemCount or 1, + quality = quality or 0, + name = name, + iLevel = iLevel, + type = itemType, + subclass = subType, + equipLoc = equipLoc, + stackSize = stackCount or 1, + locked = locked, + } + end + end + + -- Update the button + local matchesFilter = self:PassesSearchFilter(itemData) + Guda_ItemButton_SetItem(targetButton, bagID, slotID, itemData, false, nil, matchesFilter, false) + + return true +end + +-- Update changed slots in a bag by comparing with cached data +-- Returns number of slots updated, or -1 if full redraw is needed +function BagFrame:UpdateChangedSlots(bagID) + if not Guda_BagFrame:IsShown() then return -1 end + if currentViewChar then return -1 end + + local numSlots = GetContainerNumSlots(bagID) + if not numSlots or numSlots == 0 then return -1 end + + local updatedCount = 0 + for slotID = 1, numSlots do + -- Find button for this slot + local targetButton = nil + for _, button in ipairs(itemButtons) do + if button.bagID == bagID and button.slot == slotID then + targetButton = button + break + end + end + + if not targetButton then + -- Button not found, need full redraw + return -1 + end + + -- Compare current item with button's cached data + local currentLink = GetContainerItemLink(bagID, slotID) + local cachedLink = targetButton.itemData and targetButton.itemData.link or nil + + -- Check if slot changed (different item or count) + local needsUpdate = false + if currentLink ~= cachedLink then + needsUpdate = true + elseif currentLink then + local _, currentCount = GetContainerItemInfo(bagID, slotID) + local cachedCount = targetButton.itemData and targetButton.itemData.count or 0 + if currentCount ~= cachedCount then + needsUpdate = true + end + end + + if needsUpdate then + if self:UpdateSingleSlot(bagID, slotID) then + updatedCount = updatedCount + 1 + else + return -1 -- Update failed, need full redraw + end + end + end + + return updatedCount +end + -- Update bagline layout (hover option) function BagFrame:UpdateBaglineLayout() local hideFooter = addon.Modules.DB:GetSetting("hideFooter") @@ -2587,12 +2692,30 @@ function BagFrame:Initialize() if currentViewChar then return end if not Guda_BagFrame:IsShown() then return end - -- Invalidate only the specific bag that changed (arg1 = bagID) - if arg1 and arg1 >= 0 and arg1 <= 4 then + -- Only handle player bags (0-4) + if not arg1 or arg1 < 0 or arg1 > 4 then return end + + -- Check if sorting is in progress - use full redraw with throttle + local isSorting = addon.Modules.SortEngine and addon.Modules.SortEngine.sortingInProgress + + if not isSorting then + -- Try incremental update for manual item moves addon.Modules.BagScanner:InvalidateBag(arg1) + if addon.Modules.ItemDetection then + addon.Modules.ItemDetection:ClearCache() + end + + -- Try to update only changed slots in this bag + local result = BagFrame:UpdateChangedSlots(arg1) + if result >= 0 then + -- Success - updated slots without full redraw + return + end + -- Fall through to full redraw if incremental update failed end - -- Use standard delay, will auto-extend if sorting is in progress + -- Sorting in progress or incremental update failed - use throttled full redraw + addon.Modules.BagScanner:InvalidateBag(arg1) ScheduleBagFrameUpdate(0.1) end) diff --git a/UI/BankFrame.lua b/UI/BankFrame.lua index 9393b6c..e13385e 100644 --- a/UI/BankFrame.lua +++ b/UI/BankFrame.lua @@ -130,6 +130,60 @@ function BankFrame:UpdateLockStates() Guda_UpdateLockStates(bankBagParents) end +-- Update a single slot without full frame redraw (used for manual item moves) +function BankFrame:UpdateSingleSlot(bagID, slotID) + if not Guda_BankFrame:IsShown() then return false end + if currentViewChar then return false end -- Can't do single-slot for other characters + + -- Find the button for this slot + local bankBagParent = bankBagParents[bagID] + if not bankBagParent or not bankBagParent.itemButtons then return false end + + local targetButton = nil + for button in pairs(bankBagParent.itemButtons) do + if button.bagID == bagID and button.slot == slotID then + targetButton = button + break + end + end + + if not targetButton then return false end + + -- Get fresh item data for this slot + local itemLink = GetContainerItemLink(bagID, slotID) + local itemData = nil + + if itemLink then + local texture, itemCount, locked = GetContainerItemInfo(bagID, slotID) + local itemID = nil + local _, _, idStr = string.find(itemLink, "item:(%d+)") + if idStr then itemID = tonumber(idStr) end + + if itemID then + local name, link, quality, iLevel, _, itemType, stackCount, subType, _, equipLoc = GetItemInfo(itemID) + itemData = { + link = itemLink, + texture = texture, + count = itemCount or 1, + quality = quality or 0, + name = name, + iLevel = iLevel, + type = itemType, + subclass = subType, + equipLoc = equipLoc, + stackSize = stackCount or 1, + locked = locked, + } + end + end + + -- Update the button + local matchesFilter = self:PassesSearchFilter(itemData) + Guda_ItemButton_SetItem(targetButton, bagID, slotID, itemData, true, nil, matchesFilter, isReadOnlyMode) + + return true +end + -- Deferred update state for frame budgeting local bankPendingUpdate = false local bankUpdateDebounceFrame = nil @@ -1626,11 +1680,25 @@ function BankFrame:Initialize() if not addon.Modules.BankScanner:IsBankOpen() then return end if currentViewChar then return end + -- Check if sorting is in progress - use full redraw with throttle + local isSorting = addon.Modules.SortEngine and addon.Modules.SortEngine.sortingInProgress + if event == "PLAYERBANKSLOTS_CHANGED" and arg1 then - -- Invalidate the entire main bank bag to ensure fresh data - -- (MarkSlotDirty was causing timing issues with item data) + -- arg1 is the slot number (1-28 for main bank) + -- Try single-slot update if not sorting + if not isSorting then + -- Invalidate cache for fresh data + addon.Modules.BankScanner:InvalidateBag(-1) + if addon.Modules.ItemDetection then + addon.Modules.ItemDetection:ClearCache() + end + -- Try single-slot update + if BankFrame:UpdateSingleSlot(-1, arg1) then + return -- Success, no full redraw needed + end + end + -- Fallback: full redraw (sorting or single-slot failed) addon.Modules.BankScanner:InvalidateBag(-1) - -- Clear ItemDetection cache to ensure fresh detection after item swap if addon.Modules.ItemDetection then addon.Modules.ItemDetection:ClearCache() end