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)",
|
addon:Print("ItemDetection Cache: %d hits, %d misses (%.1f%% hit rate, %d items)",
|
||||||
stats.hits, stats.misses, stats.hitRate, stats.size)
|
stats.hits, stats.misses, stats.hitRate, stats.size)
|
||||||
end
|
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
|
elseif msg == "perfreset" then
|
||||||
-- Reset performance statistics
|
-- Reset performance statistics
|
||||||
@@ -214,6 +220,21 @@ function Main:SetupSlashCommands()
|
|||||||
addon.Modules.ItemDetection:ClearCache()
|
addon.Modules.ItemDetection:ClearCache()
|
||||||
end
|
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
|
elseif msg == "help" then
|
||||||
-- Show help
|
-- Show help
|
||||||
addon:Print("Commands:")
|
addon:Print("Commands:")
|
||||||
@@ -229,6 +250,7 @@ function Main:SetupSlashCommands()
|
|||||||
addon:Print("/guda cleanup - Remove old characters")
|
addon:Print("/guda cleanup - Remove old characters")
|
||||||
addon:Print("/guda perf - Show performance stats")
|
addon:Print("/guda perf - Show performance stats")
|
||||||
addon:Print("/guda perfreset - Reset performance stats")
|
addon:Print("/guda perfreset - Reset performance stats")
|
||||||
|
addon:Print("/guda poolreset - Reset button pool (debug)")
|
||||||
|
|
||||||
else
|
else
|
||||||
addon:Print("Unknown command. Type /guda help for commands")
|
addon:Print("Unknown command. Type /guda help for commands")
|
||||||
|
|||||||
+24
-43
@@ -144,10 +144,10 @@ function Guda_BagFrame_OnHide(self)
|
|||||||
end
|
end
|
||||||
|
|
||||||
-- Clean up all buttons when frame is hidden (safe since we're not displaying)
|
-- 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
|
for _, bagParent in pairs(bagParents) do
|
||||||
if bagParent then
|
if bagParent and bagParent.itemButtons then
|
||||||
local buttons = { bagParent:GetChildren() }
|
for button in pairs(bagParent.itemButtons) do
|
||||||
for _, button in ipairs(buttons) do
|
|
||||||
if button.hasItem ~= nil then
|
if button.hasItem ~= nil then
|
||||||
button:Hide()
|
button:Hide()
|
||||||
button:ClearAllPoints()
|
button:ClearAllPoints()
|
||||||
@@ -434,11 +434,11 @@ function BagFrame:Update()
|
|||||||
-- BUT only if we already have items displayed - otherwise we need to do initial build
|
-- BUT only if we already have items displayed - otherwise we need to do initial build
|
||||||
if CursorHasItem and CursorHasItem() then
|
if CursorHasItem and CursorHasItem() then
|
||||||
-- Check if we have any displayed items
|
-- Check if we have any displayed items
|
||||||
|
-- Use itemButtons hash instead of GetChildren() to avoid table allocation
|
||||||
local hasDisplayedItems = false
|
local hasDisplayedItems = false
|
||||||
for _, bagParent in pairs(bagParents) do
|
for _, bagParent in pairs(bagParents) do
|
||||||
if bagParent then
|
if bagParent and bagParent.itemButtons then
|
||||||
local children = { bagParent:GetChildren() }
|
for button in pairs(bagParent.itemButtons) do
|
||||||
for _, button in ipairs(children) do
|
|
||||||
if button.hasItem and button:IsShown() then
|
if button.hasItem and button:IsShown() then
|
||||||
hasDisplayedItems = true
|
hasDisplayedItems = true
|
||||||
break
|
break
|
||||||
@@ -456,12 +456,12 @@ function BagFrame:Update()
|
|||||||
end
|
end
|
||||||
|
|
||||||
-- Mark all existing buttons as not in use (we'll mark active ones during display)
|
-- 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 totalButtonsBefore = 0
|
||||||
local shownButtonsBefore = 0
|
local shownButtonsBefore = 0
|
||||||
for _, bagParent in pairs(bagParents) do
|
for _, bagParent in pairs(bagParents) do
|
||||||
if bagParent then
|
if bagParent and bagParent.itemButtons then
|
||||||
local buttons = { bagParent:GetChildren() }
|
for button in pairs(bagParent.itemButtons) do
|
||||||
for _, button in ipairs(buttons) do
|
|
||||||
if button.hasItem ~= nil then
|
if button.hasItem ~= nil then
|
||||||
totalButtonsBefore = totalButtonsBefore + 1
|
totalButtonsBefore = totalButtonsBefore + 1
|
||||||
if button:IsShown() then
|
if button:IsShown() then
|
||||||
@@ -556,12 +556,12 @@ function BagFrame:Update()
|
|||||||
self:UpdateBaglineLayout()
|
self:UpdateBaglineLayout()
|
||||||
|
|
||||||
-- Clean up unused buttons AFTER display is complete (prevents drag/drop issues)
|
-- 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 hiddenCount = 0
|
||||||
local stillShownCount = 0
|
local stillShownCount = 0
|
||||||
for _, bagParent in pairs(bagParents) do
|
for _, bagParent in pairs(bagParents) do
|
||||||
if bagParent then
|
if bagParent and bagParent.itemButtons then
|
||||||
local buttons = { bagParent:GetChildren() }
|
for button in pairs(bagParent.itemButtons) do
|
||||||
for _, button in ipairs(buttons) do
|
|
||||||
if button.hasItem ~= nil and not button.inUse then
|
if button.hasItem ~= nil and not button.inUse then
|
||||||
button:Hide()
|
button:Hide()
|
||||||
button:ClearAllPoints()
|
button:ClearAllPoints()
|
||||||
@@ -2543,13 +2543,12 @@ end
|
|||||||
|
|
||||||
-- Highlight all item slots belonging to a specific bag by dimming others
|
-- Highlight all item slots belonging to a specific bag by dimming others
|
||||||
function Guda_BagFrame_HighlightBagSlots(bagID)
|
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
|
local highlightCount, dimCount = 0, 0
|
||||||
|
|
||||||
for _, bagParent in pairs(bagParents) do
|
for _, bagParent in pairs(bagParents) do
|
||||||
if bagParent then
|
if bagParent and bagParent.itemButtons then
|
||||||
local children = { bagParent:GetChildren() }
|
for button in pairs(bagParent.itemButtons) do
|
||||||
for _, button in ipairs(children) do
|
|
||||||
if button and button:IsShown() and button.hasItem ~= nil and not button.isBagSlot then
|
if button and button:IsShown() and button.hasItem ~= nil and not button.isBagSlot then
|
||||||
if button.bagID == bagID then
|
if button.bagID == bagID then
|
||||||
button:SetAlpha(1.0)
|
button:SetAlpha(1.0)
|
||||||
@@ -2567,12 +2566,12 @@ end
|
|||||||
-- Clear all highlighting by restoring full opacity to all slots
|
-- Clear all highlighting by restoring full opacity to all slots
|
||||||
function Guda_BagFrame_ClearHighlightedSlots()
|
function Guda_BagFrame_ClearHighlightedSlots()
|
||||||
-- Restore alpha to whatever the search filter dictates (pfUI style). If no search, full opacity.
|
-- 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()
|
local searchActive = BagFrame and BagFrame.IsSearchActive and BagFrame:IsSearchActive()
|
||||||
|
|
||||||
for _, bagParent in pairs(bagParents) do
|
for _, bagParent in pairs(bagParents) do
|
||||||
if bagParent then
|
if bagParent and bagParent.itemButtons then
|
||||||
local children = { bagParent:GetChildren() }
|
for button in pairs(bagParent.itemButtons) do
|
||||||
for _, button in ipairs(children) do
|
|
||||||
if button and button:IsShown() and button.hasItem ~= nil and not button.isBagSlot then
|
if button and button:IsShown() and button.hasItem ~= nil and not button.isBagSlot then
|
||||||
if searchActive and BagFrame and BagFrame.PassesSearchFilter then
|
if searchActive and BagFrame and BagFrame.PassesSearchFilter then
|
||||||
local matches = BagFrame:PassesSearchFilter(button.itemData)
|
local matches = BagFrame:PassesSearchFilter(button.itemData)
|
||||||
@@ -2813,32 +2812,14 @@ end
|
|||||||
|
|
||||||
-- Refresh cooldown overlays for all visible item buttons
|
-- Refresh cooldown overlays for all visible item buttons
|
||||||
function BagFrame:RefreshCooldowns()
|
function BagFrame:RefreshCooldowns()
|
||||||
local itemContainer = getglobal("Guda_BagFrame_ItemContainer")
|
-- Use itemButtons hash instead of GetChildren() to avoid table allocation
|
||||||
if not itemContainer then return end
|
for _, bagParent in pairs(bagParents) do
|
||||||
|
if bagParent and bagParent.itemButtons then
|
||||||
-- Iterate through all children; our item buttons are direct children of per-bag parents inside the container,
|
for button in pairs(bagParent.itemButtons) do
|
||||||
-- so iterate all descendants by scanning children of children as well.
|
if button and button.hasItem and button:IsShown() and Guda_ItemButton_UpdateCooldown then
|
||||||
local function refreshChildren(parent)
|
Guda_ItemButton_UpdateCooldown(button)
|
||||||
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
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
refreshChildren(itemContainer)
|
|
||||||
end
|
end
|
||||||
+146
-7
@@ -4,6 +4,58 @@ local addon = Guda
|
|||||||
-- Item button pool
|
-- Item button pool
|
||||||
local buttonPool = {}
|
local buttonPool = {}
|
||||||
local nextButtonID = 1
|
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)
|
-- 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
|
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
|
-- Create or get a button from the pool
|
||||||
function Guda_GetItemButton(parent)
|
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
|
for _, button in pairs(buttonPool) do
|
||||||
-- Skip bag slot buttons
|
-- Skip bag slot buttons
|
||||||
if not button.isBagSlot and not button:IsShown() and button:GetParent() == parent then
|
if not button.isBagSlot and IsButtonAvailable(button) then
|
||||||
-- Re-register with parent (itemButtons hash may have been cleared)
|
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
|
if Guda_RegisterItemButton then
|
||||||
Guda_RegisterItemButton(parent, button)
|
Guda_RegisterItemButton(parent, button)
|
||||||
end
|
end
|
||||||
|
|
||||||
return button
|
return button
|
||||||
end
|
end
|
||||||
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")
|
local button = CreateFrame("Button", "Guda_ItemButton" .. nextButtonID, parent, "Guda_ItemButtonTemplate")
|
||||||
buttonPool[nextButtonID] = button
|
buttonPool[nextButtonID] = button
|
||||||
nextButtonID = nextButtonID + 1
|
nextButtonID = nextButtonID + 1
|
||||||
|
button.inUse = true
|
||||||
-- Register button with parent for tracking (avoids GetChildren() allocation)
|
|
||||||
if Guda_RegisterItemButton then
|
if Guda_RegisterItemButton then
|
||||||
Guda_RegisterItemButton(parent, button)
|
Guda_RegisterItemButton(parent, button)
|
||||||
end
|
end
|
||||||
|
|
||||||
return button
|
return button
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user