From e7e13826934768804ecfad5edf11feaafcec0540 Mon Sep 17 00:00:00 2001 From: Brues <5278969+brues-code@users.noreply.github.com> Date: Fri, 23 Oct 2026 12:39:07 -0500 Subject: [PATCH] Port unitframe and actionbar performance fixes from Duciel/pfUI@45f8ca6 Unitframes: only set the unit attribute when it changes, poll group frame indicators every 3s since events already cover them, and limit full refreshes on eventless frames to unit changes or once a second. Actionbar: handle UNIT_INVENTORY_CHANGED with a light count/cooldown/usable pass instead of a full update of every button, and skip hidden buttons in the cached passes, catching them up once they become visible. --- api/unitframes.lua | 40 ++++++++++++++++++---- modules/actionbar.lua | 77 ++++++++++++++++++++++++++++++------------- 2 files changed, 88 insertions(+), 29 deletions(-) diff --git a/api/unitframes.lua b/api/unitframes.lua index 54d2c03e..1f27a3d9 100644 --- a/api/unitframes.lua +++ b/api/unitframes.lua @@ -318,10 +318,16 @@ function pfUI.uf:UpdateVisibility() self._label, self._id = nil, nil end - local unitstr = ("%s%s"):format(self.label or "", self.id or "") - self.unitstr = unitstr ~= "" and unitstr or nil - self:SetAttribute("unit", self.unitstr) - local visibility = ("[target=%s,exists] show; hide"):format(unitstr) + -- this runs for every frame five times a second, so only touch the + -- attribute when the unit actually changed + local unitstr = (self.label or "") .. (self.id or "") + local unit = unitstr ~= "" and unitstr or nil + if unit ~= self.unitstr then + self.unitstr = unit + self:SetAttribute("unit", unit) + end + -- anything but "show"/"hide" means "show if the unit exists" + local visibility = "exists" -- Group frames are redundant when the group is already shown as a raid grid: -- either an actual raid, or a party promoted to the raid grid via @@ -1088,7 +1094,18 @@ function pfUI.uf.OnUpdate() end else pfUI.uf:RefreshUnitState(this) - pfUI.uf:RefreshIndicators(this) + -- group frames get their leader/loot/raid mark icons refreshed by + -- events through update_indicators, so polling them is only a safety + -- net; frames whose unit changes without events keep polling per tick + local label = this.label + if label == "raid" or label == "party" or label == "raidpet" or label == "partypet" then + if (this.lastIndicatorPoll or 0) < now then + this.lastIndicatorPoll = now + 3 + pfUI.uf:RefreshIndicators(this) + end + else + pfUI.uf:RefreshIndicators(this) + end end if this.config and this.config.glowaggro == "1" and pfUI.api.UnitHasAggro(unitstr) > 0 then @@ -1101,9 +1118,18 @@ function pfUI.uf.OnUpdate() this.glow:Hide() end - -- update everything on eventless frames (targettarget, etc) + -- update eventless frames (targettarget, etc). A full refresh rescans + -- every aura and the portrait, so only do it when the unit behind the + -- frame changes or once a second; other ticks refresh the bars only. if this.tick then - pfUI.uf:RefreshUnit(this, "all") + local guid = UnitGUID(unitstr) + if guid ~= this.tickGuid or (this.tickFull or 0) < now then + this.tickGuid = guid + this.tickFull = now + 1 + pfUI.uf:RefreshUnit(this, "all") + else + pfUI.uf:RefreshUnit(this, "base") + end end end diff --git a/modules/actionbar.lua b/modules/actionbar.lua index c1aba5a0..10526a66 100644 --- a/modules/actionbar.lua +++ b/modules/actionbar.lua @@ -81,6 +81,9 @@ pfUI:RegisterModule("actionbar", function () ["ACTIONBAR_UPDATE_COOLDOWN"] = true, ["ACTIONBAR_UPDATE_USABLE"] = true, ["ACTIONBAR_UPDATE_STATE"] = true, + -- fires on every ammo shot / consumable use, but only counts, equipped + -- state, cooldowns and usability can change from it + ["UNIT_INVENTORY_CHANGED"] = true, } -- events that are shared across all buttons @@ -97,8 +100,6 @@ pfUI:RegisterModule("actionbar", function () ["TRADE_SKILL_CLOSE"] = true, ["PLAYER_ENTER_COMBAT"] = true, ["PLAYER_LEAVE_COMBAT"] = true, - -- cooldown updates - ["UNIT_INVENTORY_CHANGED"] = true, -- auto repeat action ["START_AUTOREPEAT_SPELL"] = true, ["STOP_AUTOREPEAT_SPELL"] = true, @@ -400,6 +401,28 @@ pfUI:RegisterModule("actionbar", function () GameTooltip:Hide() end + -- item counts and equipped border, the parts of a slot an inventory change touches + local function ButtonInventoryUpdate(self) + if self.bar == 11 or self.bar == 12 then return end + local sid = self.id + + -- update consumables + if IsConsumableAction(sid) then + self.count:SetText(GetActionCount(sid)) + elseif IsReagentAction and IsReagentAction(sid) then + self.count:SetText(GetReagentCount(sid)) + else + self.count:SetText(nil) + end + + -- equipped item + if IsEquippedAction(sid) and C.bars.showequipped == "1" then + self.equipped:Show() + else + self.equipped:Hide() + end + end + local start, duration, enable, castable, autocast, token local grid, sid, id, bar, active, texture, _ local function ButtonSlotUpdate(self) @@ -460,23 +483,9 @@ pfUI:RegisterModule("actionbar", function () self.active:Hide() end + ButtonInventoryUpdate(self) + if self.bar ~= 11 and self.bar ~= 12 then - -- update consumables - if IsConsumableAction(sid) then - self.count:SetText(GetActionCount(sid)) - elseif IsReagentAction and IsReagentAction(sid) then - self.count:SetText(GetReagentCount(sid)) - else - self.count:SetText(nil) - end - - -- equipped item - if IsEquippedAction(sid) and C.bars.showequipped == "1" then - self.equipped:Show() - else - self.equipped:Hide() - end - -- update macro text if C.bars["bar"..self.bar] and C.bars["bar"..self.bar].showmacro == "1" then self.macro:SetText(GetActionText(sid)) @@ -696,11 +705,29 @@ pfUI:RegisterModule("actionbar", function () end end + -- The cached passes below only touch visible buttons. Hidden ones (other + -- pages, hidden bars) are flagged stale and get a full update once the + -- range pass sees them visible again. + + -- run cached inventory changes (ammo, reagents, consumables) + if eventcache["UNIT_INVENTORY_CHANGED"] then + eventcache["UNIT_INVENTORY_CHANGED"] = nil + for id, button in pairs(buttoncache) do + if button:IsVisible() then + ButtonInventoryUpdate(button) + ButtonCooldownUpdate(button) + ButtonUsableUpdate(button) + else + button.stale = true + end + end + end + -- run cached usable usable actions if eventcache["ACTIONBAR_UPDATE_USABLE"] then eventcache["ACTIONBAR_UPDATE_USABLE"] = nil for id, button in pairs(buttoncache) do - ButtonUsableUpdate(button) + if button:IsVisible() then ButtonUsableUpdate(button) else button.stale = true end end end @@ -708,7 +735,7 @@ pfUI:RegisterModule("actionbar", function () if eventcache["ACTIONBAR_UPDATE_COOLDOWN"] then eventcache["ACTIONBAR_UPDATE_COOLDOWN"] = nil for id, button in pairs(buttoncache) do - ButtonCooldownUpdate(button) + if button:IsVisible() then ButtonCooldownUpdate(button) else button.stale = true end end end @@ -716,7 +743,7 @@ pfUI:RegisterModule("actionbar", function () if eventcache["ACTIONBAR_UPDATE_STATE"] then eventcache["ACTIONBAR_UPDATE_STATE"] = nil for id, button in pairs(buttoncache) do - ButtonIsActiveUpdate(button) + if button:IsVisible() then ButtonIsActiveUpdate(button) else button.stale = true end end end @@ -738,7 +765,13 @@ pfUI:RegisterModule("actionbar", function () if ( this.tick or .2) > GetTime() then return else this.tick = GetTime() + .2 end for id, button in pairs(buttoncache) do - if button:IsShown() then ButtonRangeUpdate(button) end + if button.stale and button:IsVisible() then + -- catch up on updates skipped while hidden + button.stale = nil + ButtonFullUpdate(button) + elseif button:IsShown() then + ButtonRangeUpdate(button) + end end end