fix: memory issue
This commit is contained in:
@@ -194,6 +194,12 @@ function Main:SetupSlashCommands()
|
||||
addon:Print("ItemDetection Cache: %d hits, %d misses (%.1f%% hit rate, %d items)",
|
||||
stats.hits, stats.misses, stats.hitRate, stats.size)
|
||||
end
|
||||
-- Show button pool stats
|
||||
if Guda_GetButtonPoolStats then
|
||||
local stats = Guda_GetButtonPoolStats()
|
||||
addon:Print("Button Pool: %d total (%d shown, %d hidden, %d inUse, %d available, max %d)",
|
||||
stats.total, stats.shown, stats.hidden, stats.inUse, stats.available, stats.maxSize)
|
||||
end
|
||||
|
||||
elseif msg == "perfreset" then
|
||||
-- Reset performance statistics
|
||||
@@ -214,6 +220,21 @@ function Main:SetupSlashCommands()
|
||||
addon.Modules.ItemDetection:ClearCache()
|
||||
end
|
||||
|
||||
elseif msg == "poolreset" then
|
||||
-- Reset button pool (only safe when no frames are visible)
|
||||
local bagFrame = getglobal("Guda_BagFrame")
|
||||
local bankFrame = getglobal("Guda_BankFrame")
|
||||
if (bagFrame and bagFrame:IsShown()) or (bankFrame and bankFrame:IsShown()) then
|
||||
addon:Print("Cannot reset pool while bag/bank frames are open. Close them first.")
|
||||
else
|
||||
if Guda_ResetButtonPool then
|
||||
Guda_ResetButtonPool()
|
||||
addon:Print("Button pool reset. Pool is now empty.")
|
||||
else
|
||||
addon:Print("Button pool reset function not available.")
|
||||
end
|
||||
end
|
||||
|
||||
elseif msg == "help" then
|
||||
-- Show help
|
||||
addon:Print("Commands:")
|
||||
@@ -229,6 +250,7 @@ function Main:SetupSlashCommands()
|
||||
addon:Print("/guda cleanup - Remove old characters")
|
||||
addon:Print("/guda perf - Show performance stats")
|
||||
addon:Print("/guda perfreset - Reset performance stats")
|
||||
addon:Print("/guda poolreset - Reset button pool (debug)")
|
||||
|
||||
else
|
||||
addon:Print("Unknown command. Type /guda help for commands")
|
||||
|
||||
+24
-43
@@ -144,10 +144,10 @@ function Guda_BagFrame_OnHide(self)
|
||||
end
|
||||
|
||||
-- Clean up all buttons when frame is hidden (safe since we're not displaying)
|
||||
-- Use itemButtons hash instead of GetChildren() to avoid table allocation
|
||||
for _, bagParent in pairs(bagParents) do
|
||||
if bagParent then
|
||||
local buttons = { bagParent:GetChildren() }
|
||||
for _, button in ipairs(buttons) do
|
||||
if bagParent and bagParent.itemButtons then
|
||||
for button in pairs(bagParent.itemButtons) do
|
||||
if button.hasItem ~= nil then
|
||||
button:Hide()
|
||||
button:ClearAllPoints()
|
||||
@@ -434,11 +434,11 @@ function BagFrame:Update()
|
||||
-- BUT only if we already have items displayed - otherwise we need to do initial build
|
||||
if CursorHasItem and CursorHasItem() then
|
||||
-- Check if we have any displayed items
|
||||
-- Use itemButtons hash instead of GetChildren() to avoid table allocation
|
||||
local hasDisplayedItems = false
|
||||
for _, bagParent in pairs(bagParents) do
|
||||
if bagParent then
|
||||
local children = { bagParent:GetChildren() }
|
||||
for _, button in ipairs(children) do
|
||||
if bagParent and bagParent.itemButtons then
|
||||
for button in pairs(bagParent.itemButtons) do
|
||||
if button.hasItem and button:IsShown() then
|
||||
hasDisplayedItems = true
|
||||
break
|
||||
@@ -456,12 +456,12 @@ function BagFrame:Update()
|
||||
end
|
||||
|
||||
-- Mark all existing buttons as not in use (we'll mark active ones during display)
|
||||
-- Use itemButtons hash instead of GetChildren() to avoid table allocation
|
||||
local totalButtonsBefore = 0
|
||||
local shownButtonsBefore = 0
|
||||
for _, bagParent in pairs(bagParents) do
|
||||
if bagParent then
|
||||
local buttons = { bagParent:GetChildren() }
|
||||
for _, button in ipairs(buttons) do
|
||||
if bagParent and bagParent.itemButtons then
|
||||
for button in pairs(bagParent.itemButtons) do
|
||||
if button.hasItem ~= nil then
|
||||
totalButtonsBefore = totalButtonsBefore + 1
|
||||
if button:IsShown() then
|
||||
@@ -556,12 +556,12 @@ function BagFrame:Update()
|
||||
self:UpdateBaglineLayout()
|
||||
|
||||
-- Clean up unused buttons AFTER display is complete (prevents drag/drop issues)
|
||||
-- Use itemButtons hash instead of GetChildren() to avoid table allocation
|
||||
local hiddenCount = 0
|
||||
local stillShownCount = 0
|
||||
for _, bagParent in pairs(bagParents) do
|
||||
if bagParent then
|
||||
local buttons = { bagParent:GetChildren() }
|
||||
for _, button in ipairs(buttons) do
|
||||
if bagParent and bagParent.itemButtons then
|
||||
for button in pairs(bagParent.itemButtons) do
|
||||
if button.hasItem ~= nil and not button.inUse then
|
||||
button:Hide()
|
||||
button:ClearAllPoints()
|
||||
@@ -2543,13 +2543,12 @@ end
|
||||
|
||||
-- Highlight all item slots belonging to a specific bag by dimming others
|
||||
function Guda_BagFrame_HighlightBagSlots(bagID)
|
||||
-- Iterate through bagParents to get all item buttons (same approach as BankFrame)
|
||||
-- Use itemButtons hash instead of GetChildren() to avoid table allocation
|
||||
local highlightCount, dimCount = 0, 0
|
||||
|
||||
for _, bagParent in pairs(bagParents) do
|
||||
if bagParent then
|
||||
local children = { bagParent:GetChildren() }
|
||||
for _, button in ipairs(children) do
|
||||
if bagParent and bagParent.itemButtons then
|
||||
for button in pairs(bagParent.itemButtons) do
|
||||
if button and button:IsShown() and button.hasItem ~= nil and not button.isBagSlot then
|
||||
if button.bagID == bagID then
|
||||
button:SetAlpha(1.0)
|
||||
@@ -2567,12 +2566,12 @@ end
|
||||
-- Clear all highlighting by restoring full opacity to all slots
|
||||
function Guda_BagFrame_ClearHighlightedSlots()
|
||||
-- Restore alpha to whatever the search filter dictates (pfUI style). If no search, full opacity.
|
||||
-- Use itemButtons hash instead of GetChildren() to avoid table allocation
|
||||
local searchActive = BagFrame and BagFrame.IsSearchActive and BagFrame:IsSearchActive()
|
||||
|
||||
for _, bagParent in pairs(bagParents) do
|
||||
if bagParent then
|
||||
local children = { bagParent:GetChildren() }
|
||||
for _, button in ipairs(children) do
|
||||
if bagParent and bagParent.itemButtons then
|
||||
for button in pairs(bagParent.itemButtons) do
|
||||
if button and button:IsShown() and button.hasItem ~= nil and not button.isBagSlot then
|
||||
if searchActive and BagFrame and BagFrame.PassesSearchFilter then
|
||||
local matches = BagFrame:PassesSearchFilter(button.itemData)
|
||||
@@ -2813,32 +2812,14 @@ end
|
||||
|
||||
-- Refresh cooldown overlays for all visible item buttons
|
||||
function BagFrame:RefreshCooldowns()
|
||||
local itemContainer = getglobal("Guda_BagFrame_ItemContainer")
|
||||
if not itemContainer then return end
|
||||
|
||||
-- Iterate through all children; our item buttons are direct children of per-bag parents inside the container,
|
||||
-- so iterate all descendants by scanning children of children as well.
|
||||
local function refreshChildren(parent)
|
||||
local children = { parent:GetChildren() }
|
||||
for _, child in ipairs(children) do
|
||||
if child and child.hasItem ~= nil then
|
||||
if child:IsShown() and Guda_ItemButton_UpdateCooldown then
|
||||
Guda_ItemButton_UpdateCooldown(child)
|
||||
end
|
||||
end
|
||||
-- Recurse one level to reach actual buttons under bag parents
|
||||
local grandChildren = { child:GetChildren() }
|
||||
if table.getn(grandChildren) > 0 then
|
||||
for _, gc in ipairs(grandChildren) do
|
||||
if gc and gc.hasItem ~= nil then
|
||||
if gc:IsShown() and Guda_ItemButton_UpdateCooldown then
|
||||
Guda_ItemButton_UpdateCooldown(gc)
|
||||
end
|
||||
end
|
||||
-- Use itemButtons hash instead of GetChildren() to avoid table allocation
|
||||
for _, bagParent in pairs(bagParents) do
|
||||
if bagParent and bagParent.itemButtons then
|
||||
for button in pairs(bagParent.itemButtons) do
|
||||
if button and button.hasItem and button:IsShown() and Guda_ItemButton_UpdateCooldown then
|
||||
Guda_ItemButton_UpdateCooldown(button)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
refreshChildren(itemContainer)
|
||||
end
|
||||
+146
-7
@@ -4,6 +4,58 @@ local addon = Guda
|
||||
-- Item button pool
|
||||
local buttonPool = {}
|
||||
local nextButtonID = 1
|
||||
local BUTTON_POOL_MAX = 350 -- Maximum buttons to create (bags ~80 + bank ~124 + keyring ~96 + buffer)
|
||||
|
||||
-- Get button pool statistics (for /guda perf command)
|
||||
function Guda_GetButtonPoolStats()
|
||||
local total = 0
|
||||
local shown = 0
|
||||
local hidden = 0
|
||||
local inUse = 0
|
||||
local available = 0
|
||||
for _, button in pairs(buttonPool) do
|
||||
total = total + 1
|
||||
if button:IsShown() then
|
||||
shown = shown + 1
|
||||
else
|
||||
hidden = hidden + 1
|
||||
end
|
||||
if button.inUse then
|
||||
inUse = inUse + 1
|
||||
else
|
||||
available = available + 1
|
||||
end
|
||||
end
|
||||
return {
|
||||
total = total,
|
||||
shown = shown,
|
||||
hidden = hidden,
|
||||
inUse = inUse,
|
||||
available = available,
|
||||
maxSize = BUTTON_POOL_MAX,
|
||||
}
|
||||
end
|
||||
|
||||
-- Reset button pool (for testing/debugging)
|
||||
-- WARNING: Only call this when no bag/bank frames are visible!
|
||||
function Guda_ResetButtonPool()
|
||||
-- Hide and clear all buttons
|
||||
for id, button in pairs(buttonPool) do
|
||||
button:Hide()
|
||||
button:ClearAllPoints()
|
||||
-- Clear from parent tracking
|
||||
local parent = button:GetParent()
|
||||
if parent and parent.itemButtons then
|
||||
parent.itemButtons[button] = nil
|
||||
end
|
||||
end
|
||||
-- Clear the pool table
|
||||
for k in pairs(buttonPool) do
|
||||
buttonPool[k] = nil
|
||||
end
|
||||
-- Reset counter
|
||||
nextButtonID = 1
|
||||
end
|
||||
|
||||
-- Use shared tooltip from Utils module (retrieved on-demand to ensure Utils is loaded)
|
||||
|
||||
@@ -172,30 +224,117 @@ local function Guda_ItemButton_UpdateUsableTint(self)
|
||||
end
|
||||
|
||||
|
||||
-- Check if a button is available for reuse
|
||||
-- A button is available if it's either:
|
||||
-- 1. Hidden (not shown)
|
||||
-- 2. Marked as not in use (inUse == false) during an update cycle
|
||||
local function IsButtonAvailable(button)
|
||||
if not button:IsShown() then
|
||||
return true
|
||||
end
|
||||
-- During update cycles, buttons are marked inUse = false before display
|
||||
-- and inUse = true when assigned. This allows reuse without hiding first.
|
||||
if button.inUse == false then
|
||||
return true
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
-- Create or get a button from the pool
|
||||
function Guda_GetItemButton(parent)
|
||||
-- Try to reuse existing button from same parent
|
||||
local reuseCandidate = nil -- Button from different parent we can reparent
|
||||
|
||||
-- Try to reuse existing button from pool
|
||||
for _, button in pairs(buttonPool) do
|
||||
-- Skip bag slot buttons
|
||||
if not button.isBagSlot and not button:IsShown() and button:GetParent() == parent then
|
||||
-- Re-register with parent (itemButtons hash may have been cleared)
|
||||
if not button.isBagSlot and IsButtonAvailable(button) then
|
||||
if button:GetParent() == parent then
|
||||
-- Same parent - best case, reuse immediately
|
||||
-- Mark as in use to prevent double-assignment
|
||||
button.inUse = true
|
||||
-- Re-register with parent (itemButtons hash may have been cleared)
|
||||
if Guda_RegisterItemButton then
|
||||
Guda_RegisterItemButton(parent, button)
|
||||
end
|
||||
return button
|
||||
elseif not reuseCandidate then
|
||||
-- Different parent but available - save as candidate for reparenting
|
||||
reuseCandidate = button
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- If we found an available button from a different parent, reparent it
|
||||
if reuseCandidate then
|
||||
-- Mark as in use
|
||||
reuseCandidate.inUse = true
|
||||
|
||||
-- Unregister from old parent
|
||||
local oldParent = reuseCandidate:GetParent()
|
||||
if oldParent and oldParent.itemButtons then
|
||||
oldParent.itemButtons[reuseCandidate] = nil
|
||||
end
|
||||
|
||||
-- Reparent to new parent
|
||||
reuseCandidate:SetParent(parent)
|
||||
|
||||
-- Register with new parent
|
||||
if Guda_RegisterItemButton then
|
||||
Guda_RegisterItemButton(parent, reuseCandidate)
|
||||
end
|
||||
|
||||
return reuseCandidate
|
||||
end
|
||||
|
||||
-- Only create new button if under pool limit
|
||||
if nextButtonID <= BUTTON_POOL_MAX then
|
||||
local button = CreateFrame("Button", "Guda_ItemButton" .. nextButtonID, parent, "Guda_ItemButtonTemplate")
|
||||
buttonPool[nextButtonID] = button
|
||||
nextButtonID = nextButtonID + 1
|
||||
button.inUse = true
|
||||
|
||||
-- Register button with parent for tracking (avoids GetChildren() allocation)
|
||||
if Guda_RegisterItemButton then
|
||||
Guda_RegisterItemButton(parent, button)
|
||||
end
|
||||
|
||||
return button
|
||||
end
|
||||
|
||||
-- Pool is at max and no available button found - this shouldn't normally happen
|
||||
-- but as a fallback, force-reuse the first non-bag-slot button we find
|
||||
for _, button in pairs(buttonPool) do
|
||||
if not button.isBagSlot then
|
||||
-- Hide it first (in case it was shown)
|
||||
button:Hide()
|
||||
button.inUse = true
|
||||
|
||||
-- Unregister from old parent
|
||||
local oldParent = button:GetParent()
|
||||
if oldParent and oldParent.itemButtons then
|
||||
oldParent.itemButtons[button] = nil
|
||||
end
|
||||
|
||||
-- Reparent
|
||||
button:SetParent(parent)
|
||||
|
||||
-- Register with new parent
|
||||
if Guda_RegisterItemButton then
|
||||
Guda_RegisterItemButton(parent, button)
|
||||
end
|
||||
|
||||
return button
|
||||
end
|
||||
end
|
||||
|
||||
-- Create new button
|
||||
-- Ultimate fallback (should never reach here) - create one more button
|
||||
local button = CreateFrame("Button", "Guda_ItemButton" .. nextButtonID, parent, "Guda_ItemButtonTemplate")
|
||||
buttonPool[nextButtonID] = button
|
||||
nextButtonID = nextButtonID + 1
|
||||
|
||||
-- Register button with parent for tracking (avoids GetChildren() allocation)
|
||||
button.inUse = true
|
||||
if Guda_RegisterItemButton then
|
||||
Guda_RegisterItemButton(parent, button)
|
||||
end
|
||||
|
||||
return button
|
||||
end
|
||||
|
||||
|
||||
Reference in New Issue
Block a user