255 lines
7.7 KiB
Lua
255 lines
7.7 KiB
Lua
-- Guda ClassicAPI ItemButton performance layer
|
|
-- Keeps the original ItemButton behavior but removes permanent idle polling
|
|
-- and consolidates per-drop-target glow OnUpdate work into one shared driver.
|
|
-- Lua 5.0 compatible.
|
|
|
|
local addon = Guda
|
|
|
|
local ItemButtonPerformance = {}
|
|
addon.Modules.ItemButtonPerformance = ItemButtonPerformance
|
|
|
|
local cursorWatcher = nil
|
|
local originalCursorOnEvent = nil
|
|
local originalCursorOnUpdate = nil
|
|
local cursorUpdateWrapper = nil
|
|
local cursorEventWrapper = nil
|
|
local cursorPollingEnabled = false
|
|
|
|
local glowDrivers = {}
|
|
local glowDriverCount = 0
|
|
local glowScanAccum = 0
|
|
local glowPulseTime = 0
|
|
local GLOW_RESCAN_INTERVAL = 0.75
|
|
|
|
local sharedGlowDriver = CreateFrame("Frame", "Guda_SharedDropGlowDriver", UIParent)
|
|
sharedGlowDriver:Hide()
|
|
|
|
local function IsShown(frame)
|
|
return frame and frame.IsShown and frame:IsShown()
|
|
end
|
|
|
|
local function IsRelevantFrameOpen()
|
|
if IsShown(getglobal("Guda_BagFrame")) then return true end
|
|
if IsShown(getglobal("Guda_BankFrame")) then return true end
|
|
return false
|
|
end
|
|
|
|
local function CursorIsCarrying()
|
|
return CursorHasItem and CursorHasItem() and true or false
|
|
end
|
|
|
|
local function FindCursorWatcher()
|
|
if type(GetFramesRegisteredForEvent) ~= "function" then
|
|
return nil
|
|
end
|
|
|
|
local frames = { GetFramesRegisteredForEvent("CURSOR_UPDATE") }
|
|
for i = 1, table.getn(frames) do
|
|
local frame = frames[i]
|
|
if frame
|
|
and frame._lastCarrying ~= nil
|
|
and frame._pollAccum ~= nil
|
|
and frame.GetScript
|
|
and frame:GetScript("OnUpdate") then
|
|
return frame
|
|
end
|
|
end
|
|
|
|
return nil
|
|
end
|
|
|
|
local function ShouldCursorPoll()
|
|
if IsRelevantFrameOpen() then return true end
|
|
if CursorIsCarrying() then return true end
|
|
if cursorWatcher and cursorWatcher.pendingCheck then return true end
|
|
return false
|
|
end
|
|
|
|
local function ScanGlowDrivers()
|
|
if type(EnumerateFrames) ~= "function" then return end
|
|
|
|
local frame = EnumerateFrames()
|
|
while frame do
|
|
if frame._t ~= nil and frame.GetParent then
|
|
local parent = frame:GetParent()
|
|
if parent
|
|
and parent.dropGlowDriver == frame
|
|
and parent.dropGlow then
|
|
if not glowDrivers[frame] then
|
|
glowDrivers[frame] = parent
|
|
glowDriverCount = glowDriverCount + 1
|
|
end
|
|
|
|
-- The original driver only computes the same sine wave for
|
|
-- its own button. The shared driver below does that once for
|
|
-- every active glow, so the per-button OnUpdate is redundant.
|
|
if frame.GetScript and frame:GetScript("OnUpdate") then
|
|
frame:SetScript("OnUpdate", nil)
|
|
end
|
|
end
|
|
end
|
|
|
|
frame = EnumerateFrames(frame)
|
|
end
|
|
end
|
|
|
|
local function StartSharedGlowDriver()
|
|
if not sharedGlowDriver:IsShown() then
|
|
-- BagFrame:SetDragging() redraws drop targets synchronously, so by the
|
|
-- time the cursor watcher reaches here the glow drivers normally
|
|
-- already exist. Discover them once immediately; only rare late-created
|
|
-- drivers require the slow fallback rescan below.
|
|
ScanGlowDrivers()
|
|
glowScanAccum = 0
|
|
sharedGlowDriver:Show()
|
|
end
|
|
end
|
|
|
|
sharedGlowDriver:SetScript("OnUpdate", function()
|
|
glowPulseTime = glowPulseTime + arg1
|
|
glowScanAccum = glowScanAccum + arg1
|
|
|
|
-- Rare fallback for drivers created after the initial drag redraw. Keep
|
|
-- this deliberately infrequent: EnumerateFrames is much more expensive
|
|
-- than the single shared sine calculation we are trying to optimize.
|
|
if glowScanAccum >= GLOW_RESCAN_INTERVAL then
|
|
glowScanAccum = 0
|
|
ScanGlowDrivers()
|
|
end
|
|
|
|
local phase = (math.sin(glowPulseTime * 5.24) + 1) * 0.5
|
|
local alpha = 0.15 + phase * 0.30
|
|
local anyActive = false
|
|
|
|
for driver, parent in pairs(glowDrivers) do
|
|
if driver and parent and driver.IsShown and driver:IsShown()
|
|
and parent.dropGlow and parent.dropGlow.IsShown
|
|
and parent.dropGlow:IsShown() then
|
|
parent.dropGlow:SetAlpha(alpha)
|
|
anyActive = true
|
|
end
|
|
end
|
|
|
|
if not CursorIsCarrying() and not anyActive then
|
|
this:Hide()
|
|
end
|
|
end)
|
|
|
|
local function SetCursorPolling(enabled)
|
|
if not cursorWatcher or not originalCursorOnUpdate then return end
|
|
|
|
enabled = enabled and true or false
|
|
if enabled == cursorPollingEnabled then return end
|
|
cursorPollingEnabled = enabled
|
|
|
|
if enabled then
|
|
cursorWatcher:SetScript("OnUpdate", cursorUpdateWrapper)
|
|
else
|
|
cursorWatcher:SetScript("OnUpdate", nil)
|
|
end
|
|
end
|
|
|
|
local function RefreshCursorPolling()
|
|
if ShouldCursorPoll() then
|
|
SetCursorPolling(true)
|
|
if CursorIsCarrying() then
|
|
StartSharedGlowDriver()
|
|
end
|
|
else
|
|
SetCursorPolling(false)
|
|
end
|
|
end
|
|
|
|
local function HookVisibilityFrame(frame)
|
|
if not frame or frame._gudaClassicAPIPerfHooked then return end
|
|
frame._gudaClassicAPIPerfHooked = true
|
|
|
|
local oldOnShow = frame:GetScript("OnShow")
|
|
local oldOnHide = frame:GetScript("OnHide")
|
|
|
|
frame:SetScript("OnShow", function()
|
|
if oldOnShow then oldOnShow() end
|
|
RefreshCursorPolling()
|
|
end)
|
|
|
|
frame:SetScript("OnHide", function()
|
|
if oldOnHide then oldOnHide() end
|
|
|
|
-- The other bag/bank frame can change visibility in the same call
|
|
-- stack, so defer the idle decision one frame.
|
|
if Guda_ScheduleTimer then
|
|
Guda_ScheduleTimer(0, RefreshCursorPolling)
|
|
else
|
|
RefreshCursorPolling()
|
|
end
|
|
end)
|
|
end
|
|
|
|
function ItemButtonPerformance:Initialize()
|
|
if cursorWatcher then return true end
|
|
|
|
cursorWatcher = FindCursorWatcher()
|
|
if not cursorWatcher then
|
|
addon:Debug("ClassicAPI ItemButton perf: cursor watcher not found")
|
|
return false
|
|
end
|
|
|
|
originalCursorOnEvent = cursorWatcher:GetScript("OnEvent")
|
|
originalCursorOnUpdate = cursorWatcher:GetScript("OnUpdate")
|
|
|
|
cursorUpdateWrapper = function()
|
|
-- Preserve the original 1.12 safety-net logic while relevant UI is
|
|
-- open or an item is being carried.
|
|
originalCursorOnUpdate()
|
|
|
|
if CursorIsCarrying() then
|
|
StartSharedGlowDriver()
|
|
end
|
|
|
|
if not ShouldCursorPoll() then
|
|
SetCursorPolling(false)
|
|
end
|
|
end
|
|
|
|
cursorEventWrapper = function()
|
|
if originalCursorOnEvent then
|
|
originalCursorOnEvent()
|
|
end
|
|
RefreshCursorPolling()
|
|
end
|
|
|
|
cursorWatcher:SetScript("OnEvent", cursorEventWrapper)
|
|
|
|
HookVisibilityFrame(getglobal("Guda_BagFrame"))
|
|
HookVisibilityFrame(getglobal("Guda_BankFrame"))
|
|
|
|
-- Prime discovery once so any lazily-created glow drivers that already
|
|
-- exist are converted to the shared driver immediately.
|
|
ScanGlowDrivers()
|
|
RefreshCursorPolling()
|
|
|
|
addon:Debug("ClassicAPI ItemButton perf initialized: idle cursor polling gated, shared glow driver active")
|
|
return true
|
|
end
|
|
|
|
function ItemButtonPerformance:GetStats()
|
|
return {
|
|
cursorWatcherFound = cursorWatcher ~= nil,
|
|
cursorPollingEnabled = cursorPollingEnabled,
|
|
sharedGlowDrivers = glowDriverCount,
|
|
sharedGlowActive = sharedGlowDriver:IsShown(),
|
|
}
|
|
end
|
|
|
|
-- All XML frames are guaranteed to exist by PLAYER_LOGIN. The fallback retry
|
|
-- handles unusual load orders without keeping another permanent OnUpdate alive.
|
|
local initFrame = CreateFrame("Frame")
|
|
initFrame:RegisterEvent("PLAYER_LOGIN")
|
|
initFrame:RegisterEvent("PLAYER_ENTERING_WORLD")
|
|
initFrame:SetScript("OnEvent", function()
|
|
if ItemButtonPerformance:Initialize() then
|
|
this:UnregisterEvent("PLAYER_LOGIN")
|
|
this:UnregisterEvent("PLAYER_ENTERING_WORLD")
|
|
end
|
|
end)
|