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
parent aaba09a17c
commit ed06968fa3
+54 -116
View File
@@ -56,23 +56,13 @@ pfUI:RegisterModule("nameplates", function ()
["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
local savedHostileState = nil
local savedFriendlyState = nil
local inFriendlyZone = false
local parentcount = 0
local platecount = 0
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)
-- Helper function to safely access libdebuff cast data
@@ -266,18 +256,6 @@ pfUI:RegisterModule("nameplates", function ()
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)
if not object 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("RAID_ROSTER_UPDATE")
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
nameplates:RegisterEvent("UNIT_FLAGS_GUID")
end
@@ -565,7 +546,7 @@ end
if not guid then return end
-- GUID is actual GUID (0xF13000...) from Nampower events
local plate = guidRegistry[guid]
local plate = C_NamePlate.GetNamePlateForGUID(guid)
if plate and plate.nameplate then
-- Mark nameplate for aura update in next OnUpdate cycle
plate.nameplate.auraUpdate = true
@@ -575,7 +556,7 @@ end
-- Hook into libdebuff timer signal (fires when slotTimers written or cleared)
pfUI.libdebuff_on_unit_updated = pfUI.libdebuff_on_unit_updated or {}
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
plate.nameplate.auraUpdate = true
end
@@ -645,10 +626,43 @@ end
elseif event == "RAID_ROSTER_UPDATE" or event == "PARTY_MEMBERS_CHANGED" then
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
-- 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
local plate = guidRegistry[arg1]
local plate = C_NamePlate.GetNamePlateForGUID(arg1)
if plate and plate.nameplate then
plate.nameplate.eventcache = true
end
@@ -657,7 +671,7 @@ end
-- Flag target plate for update via GUID registry
local targetGuid = UnitGUID("target")
if targetGuid then
local plate = guidRegistry[targetGuid]
local plate = C_NamePlate.GetNamePlateForGUID(targetGuid)
if plate and plate.nameplate then
plate.nameplate.targetUpdate = true
end
@@ -669,7 +683,7 @@ end
-- Only flag the target plate for combo point update
local targetGuid = UnitGUID("target")
if targetGuid then
local plate = guidRegistry[targetGuid]
local plate = C_NamePlate.GetNamePlateForGUID(targetGuid)
if plate and plate.nameplate then
plate.nameplate.comboUpdate = true
end
@@ -702,77 +716,13 @@ end
-- PERF: Update visible plate count periodically for adaptive throttling
if frameState.now - lastVisibleCheck > 0.5 then
lastVisibleCheck = frameState.now
local count = 0
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
visiblePlateCount = table.getn(C_NamePlate.GetNamePlates())
end
-- Central OnUpdate for all visible plates
for plate in pairs(registry) do
if plate:IsVisible() then
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)
@@ -939,12 +889,12 @@ end
nameplate.tick = GetTime() + mathmod(platecount, 10) * 0.05
parent.nameplate = nameplate
HookScript(parent, "OnShow", nameplates.OnShow)
-- NOTE: OnUpdate is now handled centrally, not per-plate/
parent:SetScript("OnUpdate", nil) -- Disable Blizzard's OnUpdate
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
nameplates.OnConfigChange = function(frame)
@@ -1413,17 +1363,13 @@ end
local frame = frame or this
local nameplate = frame.nameplate
-- Register GUID when plate becomes visible
local guid = frame:GetName(1)
if guid then
nameplate.cachedGuid = guid
guidRegistry[guid] = frame
-- cachedGuid is set by NAME_PLATE_UNIT_ADDED before this fires
local guid = nameplate.cachedGuid
if guid and pfUI.api.libunitscan and pfUI.api.libunitscan.ScanGuid then
-- notify libunitscan so it can cache unit data without mouseover
if pfUI.api.libunitscan and pfUI.api.libunitscan.ScanGuid then
local name = frame.nameplate.original.name:GetText()
local npcFlags = GetUnitField(guid, "npcFlags") or 0
pfUI.api.libunitscan.ScanGuid(guid, name, npcFlags == 0)
end
local name = nameplate.original.name:GetText()
local npcFlags = GetUnitField(guid, "npcFlags") or 0
pfUI.api.libunitscan.ScanGuid(guid, name, npcFlags == 0)
end
nameplates:OnDataChanged(nameplate)
@@ -1432,17 +1378,9 @@ end
nameplates.OnUpdate = function(frame, state)
local nameplate = frame.nameplate
local now = state and state.now or GetTime()
-- Update GUID registry (lightweight, needed for event routing)
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
-- cachedGuid is maintained by NAME_PLATE_UNIT_ADDED / _REMOVED events.
-- 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)
@@ -1842,7 +1780,7 @@ end
local targetGuid = UnitExists("target") and UnitGUID("target")
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
nameplates.UpdateCastbar(frame.nameplate, now)