fix: memory issue

This commit is contained in:
Salikh Gurgenidze
2026-01-24 15:17:50 +04:00
parent 59f9defd13
commit f9628b7d02
3 changed files with 192 additions and 50 deletions
+22
View File
@@ -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
View File
@@ -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
View File
@@ -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