C_NamePlate (#1)

* nameplates: drive discovery + GUID lookup via C_NamePlate

ClassicAPI now provides the modern C_NamePlate API and the matching
NAME_PLATE_CREATED / NAME_PLATE_UNIT_ADDED / NAME_PLATE_UNIT_REMOVED
events. Replace the WorldFrame:GetChildren polling + manual GUID
dictionary with event-driven plumbing.

- Drop the per-tick WorldFrame:GetNumChildren / GetChildren scan
- Delete IsNamePlate() border-texture filter (events only deliver
  nameplate frames)
- Delete the guidRegistry table + its OnShow/OnUpdate maintenance;
  every lookup now goes through C_NamePlate.GetNamePlateForGUID
- Move per-GUID cache cleanup (debuffCache / threatMemory /
  combatColorCache / libdebuff_casts) from the OnUpdate visibility
  scan to a NAME_PLATE_UNIT_REMOVED handler — fires exactly once
  per unit going out of range
- NAME_PLATE_CREATED drives nameplates.OnCreate; NAME_PLATE_UNIT_ADDED
  sets cachedGuid and drives nameplates.OnShow (so OnCreate no longer
  calls OnShow directly and no longer HookScripts the parent)
- visiblePlateCount uses table.getn(C_NamePlate.GetNamePlates())

Net -64 lines, with a meaningful reduction in per-tick CPU (no more
WorldFrame child iteration + region/texture filter every 50ms).

* nameplates: switch UNIT_ADDED/REMOVED handlers to nameplateN tokens

ClassicAPI's NAME_PLATE_UNIT_ADDED / _REMOVED events now ship the
"nameplateN" unit token as arg1 (matching modern WoW) instead of the
GUID string.

- UNIT_ADDED: resolve plate via C_NamePlate.GetNamePlateForUnit(arg1);
  derive the cache-key GUID via UnitGUID(arg1)
- UNIT_REMOVED: same lookup pattern; compute UnitGUID(arg1) inside the
  handler before the slot recycles (per docs guarantee)

The other six GetNamePlateForGUID call sites (libdebuff aura callbacks,
UNIT_FLAGS_GUID, PLAYER_TARGET_CHANGED, combo-point handler, castbar
frame) stay on the GUID flavor — they receive real GUIDs from other
event paths.

---------

Co-authored-by: Brues <5278969+brues-code@users.noreply.github.com>
This commit is contained in:
brues-code
2026-05-27 00:40:31 -05:00
committed by GitHub
co-authored by Brues
parent aaba09a17c
commit ed06968fa3
+52 -114
View File
@@ -56,23 +56,13 @@ pfUI:RegisterModule("nameplates", function ()
["boss"] = "B" ["boss"] = "B"
} }
-- catch all nameplates
local childs = {} -- PERF: Reuse table instead of creating new one each scan
local regions, plate
local initialized = 0
-- Friendly zone nameplate disable state -- Friendly zone nameplate disable state
local savedHostileState = nil local savedHostileState = nil
local savedFriendlyState = nil local savedFriendlyState = nil
local inFriendlyZone = false local inFriendlyZone = false
local parentcount = 0
local platecount = 0 local platecount = 0
local registry = {} local registry = {}
-- ============================================================================
-- OPTIMIZATION: GUID-based registries for O(1) lookups
-- ============================================================================
local guidRegistry = {} -- guid -> plate (for direct event routing)
local raidGuidCache = {} -- guid -> name (rebuilt on RAID_ROSTER_UPDATE/PARTY_MEMBERS_CHANGED) local raidGuidCache = {} -- guid -> name (rebuilt on RAID_ROSTER_UPDATE/PARTY_MEMBERS_CHANGED)
-- Helper function to safely access libdebuff cast data -- Helper function to safely access libdebuff cast data
@@ -266,18 +256,6 @@ pfUI:RegisterModule("nameplates", function ()
end end
end end
local function IsNamePlate(frame)
if frame:GetObjectType() ~= NAMEPLATE_FRAMETYPE then return nil end
regions = plate:GetRegions()
if not regions then return nil end
if not regions.GetObjectType then return nil end
if not regions.GetTexture then return nil end
if regions:GetObjectType() ~= "Texture" then return nil end
return regions:GetTexture() == "Interface\\Tooltips\\Nameplate-Border" or nil
end
local function DisableObject(object) local function DisableObject(object)
if not object then return end if not object then return end
if not object.GetObjectType then return end if not object.GetObjectType then return end
@@ -553,6 +531,9 @@ nameplates:RegisterEvent("PLAYER_COMBO_POINTS")
nameplates:RegisterEvent("ZONE_CHANGED_NEW_AREA") nameplates:RegisterEvent("ZONE_CHANGED_NEW_AREA")
nameplates:RegisterEvent("RAID_ROSTER_UPDATE") nameplates:RegisterEvent("RAID_ROSTER_UPDATE")
nameplates:RegisterEvent("PARTY_MEMBERS_CHANGED") nameplates:RegisterEvent("PARTY_MEMBERS_CHANGED")
nameplates:RegisterEvent("NAME_PLATE_CREATED")
nameplates:RegisterEvent("NAME_PLATE_UNIT_ADDED")
nameplates:RegisterEvent("NAME_PLATE_UNIT_REMOVED")
if GetUnitField then if GetUnitField then
nameplates:RegisterEvent("UNIT_FLAGS_GUID") nameplates:RegisterEvent("UNIT_FLAGS_GUID")
end end
@@ -565,7 +546,7 @@ end
if not guid then return end if not guid then return end
-- GUID is actual GUID (0xF13000...) from Nampower events -- GUID is actual GUID (0xF13000...) from Nampower events
local plate = guidRegistry[guid] local plate = C_NamePlate.GetNamePlateForGUID(guid)
if plate and plate.nameplate then if plate and plate.nameplate then
-- Mark nameplate for aura update in next OnUpdate cycle -- Mark nameplate for aura update in next OnUpdate cycle
plate.nameplate.auraUpdate = true plate.nameplate.auraUpdate = true
@@ -575,7 +556,7 @@ end
-- Hook into libdebuff timer signal (fires when slotTimers written or cleared) -- Hook into libdebuff timer signal (fires when slotTimers written or cleared)
pfUI.libdebuff_on_unit_updated = pfUI.libdebuff_on_unit_updated or {} pfUI.libdebuff_on_unit_updated = pfUI.libdebuff_on_unit_updated or {}
table.insert(pfUI.libdebuff_on_unit_updated, function(guid) table.insert(pfUI.libdebuff_on_unit_updated, function(guid)
local plate = guidRegistry[guid] local plate = C_NamePlate.GetNamePlateForGUID(guid)
if plate and plate.nameplate then if plate and plate.nameplate then
plate.nameplate.auraUpdate = true plate.nameplate.auraUpdate = true
end end
@@ -645,10 +626,43 @@ end
elseif event == "RAID_ROSTER_UPDATE" or event == "PARTY_MEMBERS_CHANGED" then elseif event == "RAID_ROSTER_UPDATE" or event == "PARTY_MEMBERS_CHANGED" then
RebuildRaidGuidCache() RebuildRaidGuidCache()
elseif event == "NAME_PLATE_CREATED" then
-- arg1 = the nameplate Frame; build the pfUI overlay once per pool slot
if arg1 and not registry[arg1] then
nameplates.OnCreate(arg1)
registry[arg1] = arg1
end
elseif event == "NAME_PLATE_UNIT_ADDED" then
-- arg1 = "nameplateN" unit token; resolve to GUID for cache keys
local plate = C_NamePlate.GetNamePlateForUnit(arg1)
if plate and plate.nameplate then
plate.nameplate.cachedGuid = UnitGUID(arg1)
nameplates.OnShow(plate)
end
elseif event == "NAME_PLATE_UNIT_REMOVED" then
-- arg1 = "nameplateN" unit token; UnitGUID still resolves inside the
-- handler (the slot is freed after dispatch returns)
local guid = UnitGUID(arg1)
if guid then
if debuffCache[guid] then debuffCache[guid] = nil end
if threatMemory[guid] then threatMemory[guid] = nil end
if combatColorCache[guid] then combatColorCache[guid] = nil end
local castInfo = GetCastInfo(guid)
if castInfo and castInfo.endTime and castInfo.endTime < GetTime() then
if pfUI.libdebuff_casts then pfUI.libdebuff_casts[guid] = nil end
end
local plate = C_NamePlate.GetNamePlateForUnit(arg1)
if plate and plate.nameplate and plate.nameplate.cachedGuid == guid then
plate.nameplate.cachedGuid = nil
end
end
elseif event == "UNIT_FLAGS_GUID" then elseif event == "UNIT_FLAGS_GUID" then
-- Nampower: fires instantly when any unit's flags change (e.g. stun, combat enter/leave) -- Nampower: fires instantly when any unit's flags change (e.g. stun, combat enter/leave)
-- arg1 = guid — directly flag that nameplate for immediate update, bypassing throttle -- arg1 = guid — directly flag that nameplate for immediate update, bypassing throttle
local plate = guidRegistry[arg1] local plate = C_NamePlate.GetNamePlateForGUID(arg1)
if plate and plate.nameplate then if plate and plate.nameplate then
plate.nameplate.eventcache = true plate.nameplate.eventcache = true
end end
@@ -657,7 +671,7 @@ end
-- Flag target plate for update via GUID registry -- Flag target plate for update via GUID registry
local targetGuid = UnitGUID("target") local targetGuid = UnitGUID("target")
if targetGuid then if targetGuid then
local plate = guidRegistry[targetGuid] local plate = C_NamePlate.GetNamePlateForGUID(targetGuid)
if plate and plate.nameplate then if plate and plate.nameplate then
plate.nameplate.targetUpdate = true plate.nameplate.targetUpdate = true
end end
@@ -669,7 +683,7 @@ end
-- Only flag the target plate for combo point update -- Only flag the target plate for combo point update
local targetGuid = UnitGUID("target") local targetGuid = UnitGUID("target")
if targetGuid then if targetGuid then
local plate = guidRegistry[targetGuid] local plate = C_NamePlate.GetNamePlateForGUID(targetGuid)
if plate and plate.nameplate then if plate and plate.nameplate then
plate.nameplate.comboUpdate = true plate.nameplate.comboUpdate = true
end end
@@ -702,77 +716,13 @@ end
-- PERF: Update visible plate count periodically for adaptive throttling -- PERF: Update visible plate count periodically for adaptive throttling
if frameState.now - lastVisibleCheck > 0.5 then if frameState.now - lastVisibleCheck > 0.5 then
lastVisibleCheck = frameState.now lastVisibleCheck = frameState.now
local count = 0 visiblePlateCount = table.getn(C_NamePlate.GetNamePlates())
for plate in pairs(registry) do
if plate:IsVisible() then count = count + 1 end
end
visiblePlateCount = count
end
-- Throttle ONLY the nameplate scanner
local scanThrottle = nameplates.combat and nameplates.combat.inCombat and 0.1 or 0.05
local shouldScan = (this.tick or 0) <= frameState.now
if shouldScan then
this.tick = frameState.now + scanThrottle
-- detect new nameplates
parentcount = WorldFrame:GetNumChildren()
if initialized < parentcount then
-- PERF: Reuse childs table instead of creating new one
local newchilds = { WorldFrame:GetChildren() }
for i = 1, parentcount do
childs[i] = newchilds[i]
end
for i = initialized + 1, parentcount do
plate = childs[i]
if IsNamePlate(plate) and not registry[plate] then
nameplates.OnCreate(plate)
registry[plate] = plate
end
end
initialized = parentcount
end
end end
-- Central OnUpdate for all visible plates -- Central OnUpdate for all visible plates
for plate in pairs(registry) do for plate in pairs(registry) do
if plate:IsVisible() then if plate:IsVisible() then
nameplates.OnUpdate(plate, frameState) nameplates.OnUpdate(plate, frameState)
else
-- PERF: Clean up ALL caches for hidden plates to prevent memory leak
local guid = plate.nameplate and plate.nameplate.cachedGuid
if guid then
-- Remove from guidRegistry
if guidRegistry[guid] == plate then
guidRegistry[guid] = nil
end
-- Clean cast cache ONLY if cast has expired
-- (Don't delete active casts just because plate was hidden briefly)
local castInfo = GetCastInfo(guid)
if castInfo and castInfo.endTime and castInfo.endTime < frameState.now then
if pfUI.libdebuff_casts then
pfUI.libdebuff_casts[guid] = nil
end
end
-- Clean debuffCache
if debuffCache[guid] then
debuffCache[guid] = nil
end
-- Clean threatMemory
if threatMemory[guid] then
threatMemory[guid] = nil
end
-- Clean combatColorCache
if combatColorCache[guid] then
combatColorCache[guid] = nil
end
end
end end
end end
end) end)
@@ -939,12 +889,12 @@ end
nameplate.tick = GetTime() + mathmod(platecount, 10) * 0.05 nameplate.tick = GetTime() + mathmod(platecount, 10) * 0.05
parent.nameplate = nameplate parent.nameplate = nameplate
HookScript(parent, "OnShow", nameplates.OnShow)
-- NOTE: OnUpdate is now handled centrally, not per-plate/ -- NOTE: OnUpdate is now handled centrally, not per-plate/
parent:SetScript("OnUpdate", nil) -- Disable Blizzard's OnUpdate parent:SetScript("OnUpdate", nil) -- Disable Blizzard's OnUpdate
nameplates.OnConfigChange(parent) nameplates.OnConfigChange(parent)
nameplates.OnShow(parent) -- NOTE: OnShow is driven by NAME_PLATE_UNIT_ADDED, not called here.
-- At NAME_PLATE_CREATED time the unit hasn't been bound yet.
end end
nameplates.OnConfigChange = function(frame) nameplates.OnConfigChange = function(frame)
@@ -1413,17 +1363,13 @@ end
local frame = frame or this local frame = frame or this
local nameplate = frame.nameplate local nameplate = frame.nameplate
-- Register GUID when plate becomes visible -- cachedGuid is set by NAME_PLATE_UNIT_ADDED before this fires
local guid = frame:GetName(1) local guid = nameplate.cachedGuid
if guid then if guid and pfUI.api.libunitscan and pfUI.api.libunitscan.ScanGuid then
nameplate.cachedGuid = guid
guidRegistry[guid] = frame
-- notify libunitscan so it can cache unit data without mouseover -- notify libunitscan so it can cache unit data without mouseover
if pfUI.api.libunitscan and pfUI.api.libunitscan.ScanGuid then local name = nameplate.original.name:GetText()
local name = frame.nameplate.original.name:GetText() local npcFlags = GetUnitField(guid, "npcFlags") or 0
local npcFlags = GetUnitField(guid, "npcFlags") or 0 pfUI.api.libunitscan.ScanGuid(guid, name, npcFlags == 0)
pfUI.api.libunitscan.ScanGuid(guid, name, npcFlags == 0)
end
end end
nameplates:OnDataChanged(nameplate) nameplates:OnDataChanged(nameplate)
@@ -1433,15 +1379,7 @@ end
local nameplate = frame.nameplate local nameplate = frame.nameplate
local now = state and state.now or GetTime() local now = state and state.now or GetTime()
-- Update GUID registry (lightweight, needed for event routing) -- cachedGuid is maintained by NAME_PLATE_UNIT_ADDED / _REMOVED events.
local guid = frame:GetName(1)
if guid and guid ~= nameplate.cachedGuid then
if nameplate.cachedGuid and guidRegistry[nameplate.cachedGuid] == frame then
guidRegistry[nameplate.cachedGuid] = nil
end
nameplate.cachedGuid = guid
guidRegistry[guid] = frame
end
-- PERF: Intelligent throttling based on target/castbar status and plate count -- PERF: Intelligent throttling based on target/castbar status and plate count
-- Use GUID comparison as primary target detection: instant, immune to alpha transitions, -- Use GUID comparison as primary target detection: instant, immune to alpha transitions,
@@ -1842,7 +1780,7 @@ end
local targetGuid = UnitExists("target") and UnitGUID("target") local targetGuid = UnitExists("target") and UnitGUID("target")
if not targetGuid then return end if not targetGuid then return end
local frame = guidRegistry[targetGuid] local frame = C_NamePlate.GetNamePlateForGUID(targetGuid)
if not frame or not frame.nameplate then return end if not frame or not frame.nameplate then return end
nameplates.UpdateCastbar(frame.nameplate, now) nameplates.UpdateCastbar(frame.nameplate, now)