mirror of
https://github.com/brues-code/pfUI.git
synced 2026-10-10 15:40:01 +00:00
Filter unit events to their units, rework energytick, drop macrotweak
Eight commits off classicapi_next. ClassicAPI's RegisterUnitEvent registers for an event but only delivers it when arg1 is one of the given units, so a handler for one unit stops waking for every other one in the world. The 26 registrations whose unit set is fixed at registration time now name it. The rule throughout is register the superset and keep the handler's own check -- the filter narrows what arrives, it does not decide what to act on. Guards that look unreachable stay put: the filter applies only when arg1 is a string, so an event that fires with a number or no argument is delivered as if plainly registered. Frames whose unit changes at runtime own their subscriptions instead of sorting events out per event. unitframes points them at the unitstr UpdateVisibility already computes -- replacing a string concat, and on a miss a second concat plus a UnitGUID call, for every frame on every unit event in the world -- and a frame that is not in use drops its unit events entirely. nameplates registers per plate against the plate's own token, which is also the only workable shape: slots have no cap, so any nameplate1..N list would have been a guess that fails in exactly the crowded scenes where plates matter. marktracking names mark1 through mark8. A registration keeps its kind, so none of these can be plain-registered first. Both teardown paths PLAYER_LOGOUT guards -- the crash 132 -- now cover the per-frame subscriptions: plates tear down rather than dispatching through logout, and a unit frame takes itself off the visibility scan so it cannot re-register what it just dropped. marktracking also drops its once-a-second full rebuild, which ran for the whole session whether or not a marker existed anywhere. The ticker is created and cancelled with group membership. It is deliberately not keyed on a mark being visible -- a marker on an out-of-range unit shows no row, and that is the case the poll exists to catch. nameplates gates the per-plate update against the floor across all four throttle categories before classifying it, instead of running a GetAlpha, a castbar IsShown, a cast lookup and up to two libthrottle:Get resolutions on plates throttled to 10fps that were going to return anyway. Nothing that would have updated can be turned away by a floor. The four throttles resolve in CacheConfig, where config changes already land. energytick sweeps the clock the server actually runs. There is one regen timer for every power, re-armed every 2s by Player::RegenerateAll and never touched by casting; the five-second rule changes what a tick pays, not when it lands. The sweep is a free-running phase lock on that clock, so Illumination refunds, potions and a Mana Spring totem on its own phase no longer snap the spark mid-cycle, and an 80ms band keeps a correct tick from hitching it at the wrap. The FSR window shades rather than predicting a share of spirit the client cannot compute -- the Casting Regen item ladder is equip auras absent from the buff list. The energy period is summed from SPELL_AURA_MOD_ENERGY_REGEN_TIME across the spellbook and buffs, so Blade Rush is found without GetTalentInfo(2, 16), an ordinal that does not fail when the tree changes but reads another talent's rank. macrotweak is gone -- ClassicAPI 1.15 covers it -- with its config entry, its GUI block, its translations in all eight locales, and actionbar's ButtonMacroScan, the #showtooltip scanner that fed it.
This commit is contained in:
+100
-74
@@ -5,10 +5,7 @@ pfUI:RegisterModule("nameplates", function ()
|
||||
-- Local function references for performance
|
||||
local GetTime = GetTime
|
||||
local UnitName = UnitName
|
||||
local UnitClass = UnitClass
|
||||
local UnitLevel = UnitLevel
|
||||
local UnitIsPlayer = UnitIsPlayer
|
||||
local UnitIsDead = UnitIsDead
|
||||
local UnitAffectingCombat = UnitAffectingCombat
|
||||
local UnitIsUnit = UnitIsUnit
|
||||
local UnitCanAssist = UnitCanAssist
|
||||
@@ -162,6 +159,24 @@ pfUI:RegisterModule("nameplates", function ()
|
||||
cfg.debuffanim = tonumber(C.nameplates.debuffanim) or 0
|
||||
cfg.debufftext = tonumber(C.nameplates.debufftext) or 1
|
||||
|
||||
-- Throttle delays, resolved once instead of per plate per tick.
|
||||
-- libthrottle:Get walks the saved-variable table, a defaults fallback and a
|
||||
-- preset table, and can build a "<category>_custom" key -- the per-plate
|
||||
-- OnUpdate was calling it one or two times for every visible plate, a
|
||||
-- hundred times a second, just to decide it had nothing to do.
|
||||
--
|
||||
-- cfg.throttle_min is the floor across all four. No plate can ever be due
|
||||
-- sooner than that, so the update can bail on it before working out which
|
||||
-- category it actually belongs to.
|
||||
cfg.throttle_target = pfUI.throttle:Get("nameplates_target")
|
||||
cfg.throttle_mass = pfUI.throttle:Get("nameplates_mass")
|
||||
cfg.throttle_normal = pfUI.throttle:Get("nameplates")
|
||||
cfg.throttle_castbar = pfUI.throttle:Get("nameplates_castbar")
|
||||
cfg.throttle_min = cfg.throttle_target
|
||||
if cfg.throttle_mass < cfg.throttle_min then cfg.throttle_min = cfg.throttle_mass end
|
||||
if cfg.throttle_normal < cfg.throttle_min then cfg.throttle_min = cfg.throttle_normal end
|
||||
if cfg.throttle_castbar < cfg.throttle_min then cfg.throttle_min = cfg.throttle_castbar end
|
||||
|
||||
-- Rebuild offtanks lookup table
|
||||
offtanks = {}
|
||||
for k, v in pairs({strsplit("#", C.nameplates.combatofftanks)}) do
|
||||
@@ -490,7 +505,7 @@ local nameplates = CreateFrame("Frame", "pfNameplates", UIParent)
|
||||
nameplates:RegisterEvent("PLAYER_ENTERING_WORLD")
|
||||
nameplates:RegisterEvent("PLAYER_TARGET_CHANGED")
|
||||
nameplates:RegisterEvent("PLAYER_LOGOUT")
|
||||
nameplates:RegisterEvent("UNIT_COMBO_POINTS")
|
||||
nameplates:RegisterUnitEvent("UNIT_COMBO_POINTS", "player")
|
||||
nameplates:RegisterEvent("PLAYER_COMBO_POINTS")
|
||||
nameplates:RegisterEvent("ZONE_CHANGED_NEW_AREA")
|
||||
nameplates:RegisterEvent("RAID_ROSTER_UPDATE")
|
||||
@@ -498,13 +513,10 @@ nameplates:RegisterEvent("PARTY_MEMBERS_CHANGED")
|
||||
nameplates:RegisterEvent("NAME_PLATE_CREATED")
|
||||
nameplates:RegisterEvent("NAME_PLATE_UNIT_ADDED")
|
||||
nameplates:RegisterEvent("NAME_PLATE_UNIT_REMOVED")
|
||||
nameplates:RegisterEvent("UNIT_AURA")
|
||||
nameplates:RegisterEvent("UNIT_FLAGS")
|
||||
nameplates:RegisterEvent("UPDATE_MOUSEOVER_UNIT")
|
||||
nameplates:RegisterEvent("UNIT_SPELLCAST_START")
|
||||
nameplates:RegisterEvent("UNIT_SPELLCAST_CHANNEL_START")
|
||||
nameplates:RegisterEvent("UNIT_SPELLCAST_STOP")
|
||||
nameplates:RegisterEvent("UNIT_SPELLCAST_CHANNEL_STOP")
|
||||
-- UNIT_AURA / UNIT_FLAGS / UNIT_SPELLCAST_* are registered per plate, on the
|
||||
-- plate's own frame, against its own token -- see OnCreate and
|
||||
-- NAME_PLATE_UNIT_ADDED.
|
||||
nameplates:RegisterEvent("PLAYER_GUILD_UPDATE")
|
||||
|
||||
nameplates:SetScript("OnEvent", function()
|
||||
@@ -516,6 +528,15 @@ nameplates:RegisterEvent("PLAYER_GUILD_UPDATE")
|
||||
if nameplates.mouselook then
|
||||
nameplates.mouselook:SetScript("OnUpdate", nil)
|
||||
end
|
||||
-- The plates hold their own unit subscriptions now, so silencing this
|
||||
-- frame alone would leave them dispatching through logout -- exactly what
|
||||
-- this branch exists to prevent.
|
||||
for plate in pairs(registry) do
|
||||
if plate.nameplate then
|
||||
plate.nameplate:UnregisterAllEvents()
|
||||
plate.nameplate:SetScript("OnEvent", nil)
|
||||
end
|
||||
end
|
||||
return
|
||||
|
||||
elseif event == "PLAYER_GUILD_UPDATE" and arg1 == 'player' then
|
||||
@@ -591,6 +612,16 @@ nameplates:RegisterEvent("PLAYER_GUILD_UPDATE")
|
||||
local guid = UnitGUID(arg1)
|
||||
plate.nameplate.cachedGuid = guid
|
||||
plate.nameplate.unit = arg1
|
||||
|
||||
-- Point this plate's own subscriptions at the token it just took. On a
|
||||
-- recycled frame these replace the previous unit rather than stacking:
|
||||
-- RegisterUnitEvent on an already-filtered registration swaps the units.
|
||||
plate.nameplate:RegisterUnitEvent("UNIT_AURA", arg1)
|
||||
plate.nameplate:RegisterUnitEvent("UNIT_FLAGS", arg1)
|
||||
plate.nameplate:RegisterUnitEvent("UNIT_SPELLCAST_START", arg1)
|
||||
plate.nameplate:RegisterUnitEvent("UNIT_SPELLCAST_CHANNEL_START", arg1)
|
||||
plate.nameplate:RegisterUnitEvent("UNIT_SPELLCAST_STOP", arg1)
|
||||
plate.nameplate:RegisterUnitEvent("UNIT_SPELLCAST_CHANNEL_STOP", arg1)
|
||||
plate.nameplate.creatureType = nil -- recompute for the new unit
|
||||
plate.nameplate.totemIcon = nil
|
||||
plate.nameplate.totemSpell = nil
|
||||
@@ -621,14 +652,9 @@ nameplates:RegisterEvent("PLAYER_GUILD_UPDATE")
|
||||
if plate and plate.nameplate and plate.nameplate.cachedGuid == guid then
|
||||
plate.nameplate.cachedGuid = nil
|
||||
plate.nameplate.unit = nil
|
||||
end
|
||||
end
|
||||
|
||||
elseif event == "UNIT_FLAGS" then
|
||||
if arg1 and strfind(arg1, "^nameplate") then
|
||||
local plate = C_NamePlate.GetNamePlateForUnit(arg1)
|
||||
if plate and plate.nameplate then
|
||||
plate.nameplate.eventcache = true
|
||||
-- Drop the subscriptions with the token: this slot is now free and the
|
||||
-- next plate to take it would otherwise feed this frame its events.
|
||||
plate.nameplate:UnregisterAllEvents()
|
||||
end
|
||||
end
|
||||
|
||||
@@ -643,48 +669,6 @@ nameplates:RegisterEvent("PLAYER_GUILD_UPDATE")
|
||||
if pn then pn.eventcache = true end
|
||||
end
|
||||
|
||||
elseif event == "UNIT_SPELLCAST_START" or event == "UNIT_SPELLCAST_CHANNEL_START" then
|
||||
-- ClassicAPI fires UNIT_SPELLCAST_* per unit token, including the caster's
|
||||
-- "nameplateN". The payload has no timing, so poll it (PollCastInfo picks
|
||||
-- cast vs channel) and cache -- only for a unit we have a plate for, so
|
||||
-- the table stays bounded to on-screen casters.
|
||||
if arg1 and strfind(arg1, "^nameplate") then
|
||||
local guid = UnitGUID(arg1)
|
||||
local plate = guid and plateByGuid[guid]
|
||||
if plate then
|
||||
castState[guid] = PollCastInfo(arg1)
|
||||
if castState[guid] then
|
||||
plate.castUpdate = true -- bypass the throttle so the bar shows now
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
elseif event == "UNIT_SPELLCAST_STOP" or event == "UNIT_SPELLCAST_CHANNEL_STOP" then
|
||||
-- Cast/channel ended (natural, interrupted, or cancelled -- the poll fires
|
||||
-- STOP for all three). Clear the cached cast and refresh its plate.
|
||||
if arg1 and strfind(arg1, "^nameplate") then
|
||||
local guid = UnitGUID(arg1)
|
||||
if guid and castState[guid] then
|
||||
castState[guid] = nil
|
||||
local plate = plateByGuid[guid]
|
||||
if plate then plate.castUpdate = true end
|
||||
end
|
||||
end
|
||||
|
||||
elseif event == "UNIT_AURA" then
|
||||
-- ClassicAPI: fires with arg1 == "nameplateN" when a unit's aura set
|
||||
-- changes (add/remove/modify). Flag the matching plate so OnUpdate does a
|
||||
-- fresh C_UnitAuras read next tick instead of waiting on the 0.5s
|
||||
-- throttle -- covers expirations, dispels, refreshes, and stack changes
|
||||
-- in one event. Guard on the token prefix (UNIT_AURA also fires for
|
||||
-- target/party/raid).
|
||||
if arg1 and strfind(arg1, "^nameplate") then
|
||||
local plate = C_NamePlate.GetNamePlateForUnit(arg1)
|
||||
if plate and plate.nameplate then
|
||||
plate.nameplate.auraUpdate = true
|
||||
end
|
||||
end
|
||||
|
||||
elseif event == "PLAYER_TARGET_CHANGED" then
|
||||
frameState.targetGuid = UnitGUID('target')
|
||||
-- Flag the target's plate for update
|
||||
@@ -766,6 +750,40 @@ nameplates:RegisterEvent("PLAYER_GUILD_UPDATE")
|
||||
nameplate.cache = {}
|
||||
nameplate.original = {}
|
||||
|
||||
-- Each plate watches its own unit. With RegisterUnitEvent the token IS the
|
||||
-- subscription, so there is no central listener sifting every unit event in
|
||||
-- the world for a "^nameplate" prefix and then resolving the plate back out
|
||||
-- of arg1 -- the event arrives only at the plate it concerns, and `this` is
|
||||
-- already that plate. NAME_PLATE_UNIT_ADDED points the registration at the
|
||||
-- new token; _REMOVED drops it, which matters because freed slots are
|
||||
-- reused and a stale token would feed this frame another unit's events.
|
||||
nameplate:SetScript("OnEvent", function()
|
||||
if event == "UNIT_AURA" then
|
||||
-- a fresh C_UnitAuras read next tick rather than waiting out the 0.5s
|
||||
-- throttle -- covers expiry, dispels, refreshes and stack changes
|
||||
this.auraUpdate = true
|
||||
elseif event == "UNIT_FLAGS" then
|
||||
this.eventcache = true
|
||||
elseif event == "UNIT_SPELLCAST_START" or event == "UNIT_SPELLCAST_CHANNEL_START" then
|
||||
-- the payload carries no timing, so poll it (PollCastInfo picks cast
|
||||
-- vs channel)
|
||||
local guid = this.cachedGuid
|
||||
if guid then
|
||||
castState[guid] = PollCastInfo(this.unit)
|
||||
if castState[guid] then
|
||||
this.castUpdate = true -- bypass the throttle so the bar shows now
|
||||
end
|
||||
end
|
||||
elseif event == "UNIT_SPELLCAST_STOP" or event == "UNIT_SPELLCAST_CHANNEL_STOP" then
|
||||
-- ended: natural, interrupted or cancelled -- the poll fires STOP for all
|
||||
local guid = this.cachedGuid
|
||||
if guid and castState[guid] then
|
||||
castState[guid] = nil
|
||||
this.castUpdate = true
|
||||
end
|
||||
end
|
||||
end)
|
||||
|
||||
-- create shortcuts for all known elements and disable them
|
||||
nameplate.original.healthbar, nameplate.original.castbar = parent:GetChildren()
|
||||
DisableObject(nameplate.original.healthbar)
|
||||
@@ -1409,6 +1427,16 @@ nameplates:RegisterEvent("PLAYER_GUILD_UPDATE")
|
||||
|
||||
-- cachedGuid is maintained by NAME_PLATE_UNIT_ADDED / _REMOVED events.
|
||||
|
||||
-- Cheap gate first. The central loop calls this for every visible plate ~100
|
||||
-- times a second, and classifying the plate below costs two C calls, a cast
|
||||
-- lookup and a throttle resolution -- all of it wasted on a plate that is
|
||||
-- throttled to 10fps. cfg.throttle_min is the floor across every category,
|
||||
-- so nothing that would have updated can be turned away here; the real
|
||||
-- category-specific throttle is still applied after the classification.
|
||||
-- Event flags bypass both gates, as before.
|
||||
local hasEventUpdate = nameplate.eventcache or nameplate.auraUpdate or nameplate.castUpdate or nameplate.targetUpdate or nameplate.comboUpdate
|
||||
if not hasEventUpdate and (nameplate.lasttick or 0) + cfg.throttle_min > now then return end
|
||||
|
||||
-- PERF: Intelligent throttling based on target/castbar status and plate count
|
||||
-- Use GUID comparison as primary target detection: instant, immune to alpha transitions,
|
||||
-- and immediately correct on de-target (unlike istarget which updates one tick later)
|
||||
@@ -1435,25 +1463,24 @@ nameplates:RegisterEvent("PLAYER_GUILD_UPDATE")
|
||||
end
|
||||
end
|
||||
|
||||
-- Resolved in CacheConfig, so these are table reads rather than a walk
|
||||
-- through the saved variables and preset tables.
|
||||
local throttle
|
||||
if target then
|
||||
throttle = pfUI.throttle:Get("nameplates_target")
|
||||
throttle = cfg.throttle_target
|
||||
elseif visiblePlateCount > 20 then
|
||||
throttle = pfUI.throttle:Get("nameplates_mass")
|
||||
throttle = cfg.throttle_mass
|
||||
else
|
||||
throttle = pfUI.throttle:Get("nameplates")
|
||||
throttle = cfg.throttle_normal
|
||||
end
|
||||
|
||||
-- Non-target plates with active castbar use the castbar throttle
|
||||
if isCastingNonTarget then
|
||||
local cbThrottle = pfUI.throttle:Get("nameplates_castbar")
|
||||
if cbThrottle < throttle then throttle = cbThrottle end
|
||||
if isCastingNonTarget and cfg.throttle_castbar < throttle then
|
||||
throttle = cfg.throttle_castbar
|
||||
end
|
||||
|
||||
-- Check for pending event updates (these bypass throttle for immediate response)
|
||||
local hasEventUpdate = nameplate.eventcache or nameplate.auraUpdate or nameplate.castUpdate or nameplate.targetUpdate or nameplate.comboUpdate
|
||||
|
||||
-- Event updates bypass throttle
|
||||
-- The category-specific gate. hasEventUpdate was read above, before the
|
||||
-- classification, and still bypasses the throttle.
|
||||
if not hasEventUpdate and (nameplate.lasttick or 0) + throttle > now then return end
|
||||
nameplate.lasttick = now
|
||||
|
||||
@@ -1655,10 +1682,9 @@ nameplates:RegisterEvent("PLAYER_GUILD_UPDATE")
|
||||
-- engine framerate, decoupled from central loop). Only update non-target castbars here.
|
||||
local isTargetPlate = target or nameplate.istarget or (nameplate.health and nameplate.health.zoomed)
|
||||
if cfg.showcastbar and not cfg.targetcastbar and not isTargetPlate then
|
||||
local cbThrottle = pfUI.throttle:Get("nameplates_castbar")
|
||||
if visiblePlateCount > 20 then
|
||||
local massThrottle = pfUI.throttle:Get("nameplates_mass")
|
||||
if massThrottle > cbThrottle then cbThrottle = massThrottle end
|
||||
local cbThrottle = cfg.throttle_castbar
|
||||
if visiblePlateCount > 20 and cfg.throttle_mass > cbThrottle then
|
||||
cbThrottle = cfg.throttle_mass
|
||||
end
|
||||
if (nameplate.castbar_tick or 0) + cbThrottle <= now then
|
||||
nameplate.castbar_tick = now
|
||||
|
||||
Reference in New Issue
Block a user