From 6f5c225ec36dbeb747896a595481668dafb8e2de Mon Sep 17 00:00:00 2001 From: Brues <5278969+brues-code@users.noreply.github.com> Date: Sat, 31 Oct 2026 02:04:20 -0500 Subject: [PATCH] Drive unit frame visibility from events Every unit frame re-checked its visibility five times a second. Now group, target and pet events re-check it in OnEvent, which also runs while a frame is hidden, and the focus module already re-checks focus and focustarget on PLAYER_FOCUS_CHANGED. Only the frames with a tick keep the 0.2 second poll. They show another unit's target or a pet that can go out of sight, and no event reports either. The focus target frame now gets the same tick as targettarget, because no event reports the focus changing its target. Unlock mode and /pftest have no event, so they re-check every frame when they change. RegisterUnitWatch does not fit here. It polls on the same 0.2 second timer, and it only shows a unit that exists, without the pet range, raid grid and group hiding rules. --- api/unitframes.lua | 57 ++++++++++++++++++++++++++++++++-------------- modules/focus.lua | 7 +++--- modules/unlock.lua | 4 ++++ 3 files changed, 48 insertions(+), 20 deletions(-) diff --git a/api/unitframes.lua b/api/unitframes.lua index 0f889159..1921cfbc 100644 --- a/api/unitframes.lua +++ b/api/unitframes.lua @@ -116,6 +116,7 @@ end pfUI.api.RegisterSlashCommand("PFTEST", { "/pftest", "/pfuftest" }, function() pfUI.uf.showall = not pfUI.uf.showall if pfUI.uf.raid and pfUI.uf.raid.LayoutPets then pfUI.uf.raid:LayoutPets() end + pfUI.uf:UpdateAllVisibility() end, true) -- Buff indicators are identified by a spell id, resolved once into the aura's @@ -214,13 +215,32 @@ local function DebuffOnClick() end end -local visibilityscan = CreateFrame("Frame", "pfUnitFrameVisibility", UIParent) -visibilityscan.frames = {} -visibilityscan:SetScript("OnUpdate", function() - if ( this.limit or 1) > GetTime() then return else this.limit = GetTime() + .2 end - for frame in pairs(this.frames) do frame:UpdateVisibility() end +-- Every enabled unit frame. Group, target and pet events re-check a frame's +-- visibility in OnEvent. A frame with a tick shows another unit's target or a +-- pet that can go out of sight, and no event reports either, so a timer +-- re-checks those frames, hidden ones included. +local visibilityframes = {} +C_Timer.NewTicker(.2, function() + for frame in pairs(visibilityframes) do + if frame.tick then frame:UpdateVisibility() end + end end) +-- Re-check every unit frame, for the changes no event reports: unlock mode +-- and /pftest. +function pfUI.uf:UpdateAllVisibility() + for frame in pairs(visibilityframes) do frame:UpdateVisibility() end +end + +-- the events that can bring a frame's unit into or out of existence +local VISIBILITY_EVENTS = { + PLAYER_ENTERING_WORLD = true, + PARTY_MEMBERS_CHANGED = true, + RAID_ROSTER_UPDATE = true, + PLAYER_TARGET_CHANGED = true, + UNIT_PET = true, +} + -- ============================================================================ -- GetUnitStats - health + power for a unit token. -- Returns: hp, maxHp, power, maxPower, powerType @@ -380,8 +400,8 @@ function pfUI.uf:UpdateVisibility() self._label, self._id = nil, nil end - -- this runs for every frame five times a second, so only touch the - -- attribute when the unit actually changed + -- this runs on every refresh, so only touch the attribute when the unit + -- actually changed local unitstr = self.label .. self.id if unitstr ~= self.unitstr then self.unitstr = unitstr @@ -422,8 +442,7 @@ function pfUI.uf:UpdateVisibility() -- unit's events, and OnEvent compares against the cached string instead of -- rebuilding label..id -- and calling UnitGUID -- for every unit event fired -- by anything, anywhere. A frame that is not in use drops them entirely; the - -- roster events that would bring it back are registered plainly, and - -- visibilityscan re-runs this every 0.2s regardless, so it recovers on its own. + -- VISIBILITY_EVENTS that bring it back are registered plainly. self:RegisterUnitEvents(visibility ~= "hide" and self.unitstr or nil) -- vanilla visibility @@ -969,10 +988,10 @@ function pfUI.uf.OnEvent() this:UnregisterAllEvents() this:SetScript("OnEvent", nil) this:SetScript("OnUpdate", nil) - -- visibilityscan is a separate frame and keeps ticking, so leaving this one - -- on its list would have UpdateVisibility re-register the unit events we - -- just dropped -- straight back into the crash 132 this branch prevents - visibilityscan.frames[this] = nil + -- the visibility timer keeps ticking, so leaving this frame on its list + -- would have UpdateVisibility re-register the unit events we just + -- dropped -- straight back into the crash 132 this branch prevents + visibilityframes[this] = nil return end @@ -990,6 +1009,12 @@ function pfUI.uf.OnEvent() -- abort on broken unitframes (e.g focus) if not this.label then return end + -- a hidden frame runs no OnUpdate, so its unit coming into existence has to + -- show it from here + if VISIBILITY_EVENTS[event] and not (event == "PLAYER_TARGET_CHANGED" and pfScanActive) then + this:UpdateVisibility() + end + -- update regular frames if event == "PLAYER_ENTERING_WORLD" then this.update_full = true @@ -1307,8 +1332,7 @@ local UNIT_EVENTS = { -- Point this frame's unit-event subscriptions at `unitstr`, or drop them when -- the frame has no unit. Cheap to call repeatedly: it no-ops unless the unit --- actually changed, which matters because visibilityscan runs UpdateVisibility --- for every frame five times a second. +-- actually changed, which matters because every refresh runs UpdateVisibility. function pfUI.uf:RegisterUnitEvents(unitstr) if self.eventunit == unitstr then return end self.eventunit = unitstr @@ -1415,8 +1439,7 @@ function pfUI.uf:EnableScripts() f:SetScript("OnEvent", pfUI.uf.OnEvent) f:SetScript("OnUpdate", pfUI.uf.OnUpdate) - -- add frame to visibility refresh handler - visibilityscan.frames[f] = true + visibilityframes[f] = true end function pfUI.uf:CreateUnitFrame(unit, id, config, tick) diff --git a/modules/focus.lua b/modules/focus.lua index dbb17e75..ac808c7c 100644 --- a/modules/focus.lua +++ b/modules/focus.lua @@ -8,7 +8,7 @@ pfUI:RegisterModule("focus", function () UpdateMovable(pfUI.uf.focus) pfUI.uf.focus:Hide() - pfUI.uf.focustarget = pfUI.uf:CreateUnitFrame("FocusTarget", nil, C.unitframes.focustarget) + pfUI.uf.focustarget = pfUI.uf:CreateUnitFrame("FocusTarget", nil, C.unitframes.focustarget, .2) pfUI.uf.focustarget:UpdateFrameSize() pfUI.uf.focustarget:SetPoint("BOTTOMLEFT", pfUI.uf.focus, "TOP", 0, 10) UpdateMovable(pfUI.uf.focustarget) @@ -16,8 +16,9 @@ pfUI:RegisterModule("focus", function () -- PLAYER_FOCUS_CHANGED drives immediate refresh on focus assign / clear. -- Between events, ClassicAPI fires UNIT_* (health/mana/aura/...) with - -- arg1 == "focus" and arg1 == "focustarget", so both frames update - -- event-driven like target and need no polling tick. + -- arg1 == "focus" and arg1 == "focustarget", so the bars update + -- event-driven like target. No event reports the focus changing its + -- target, so focustarget ticks like targettarget. pfUI.events:RegisterFrameEventAndCallback("PLAYER_FOCUS_CHANGED", function() pfUI.uf.focus.instantRefresh = true pfUI.uf:RefreshUnit(pfUI.uf.focus, "all") diff --git a/modules/unlock.lua b/modules/unlock.lua index 043233b3..fcf2e1fe 100644 --- a/modules/unlock.lua +++ b/modules/unlock.lua @@ -508,6 +508,10 @@ pfUI:RegisterModule("unlock", function () UpdateMovable(frame) end + -- show the unit frames whose unit came and hide the ones whose unit went + -- while unlocked + pfUI.uf:UpdateAllVisibility() + SetDockToFrame(nil) pfUI.unlock:Hide() pfUI.gui:Show()