perf: reduce CPU hotspots identified in profiler

- HBD.lua: Cache GetPlayerWorldPosition/GetPlayerZonePosition at 50ms
  intervals instead of hammering GetPlayerMapPosition() every frame.
  Invalidate cache on PLAYER_ENTERING_WORLD and ZONE_CHANGED_* events.
  Expected ~97% reduction in C API position calls (45,570 -> ~1,200 / 10min).

- zoneDB.lua: Replace O(n) linear scan in GetAreaIdByUiMapId with an
  O(1) reverse lookup cache (uiMapIdToAreaIdCache) built at Initialize().
  Cache is kept in sync by ApplyCustomZones and name-match fallback now
  caches its result so subsequent calls are also O(1).

- Tooltip.lua: Throttle GameTooltip OnUpdate hook to 100ms intervals
  (was firing every frame at 60-144 Hz). Added _tooltipLastText cache
  to avoid redundant GetText() + CountTooltip() calls when nothing changed.

- QuestieArrow.lua: Hoist _HasMissingCompletedFlag, _GetCompleteIconType,
  _CollectFinisherSpawns, and _CollectObjective out of _CollectQuestTargets
  to module-level functions. These were re-created as closures on every
  UpdateNearestTargets call (1 Hz). Shared per-cycle context is published
  via _arrow_* module upvalues to avoid closure capture overhead.

- QuestieLearnerComms.lua: Four micro-optimizations:
  (1) Reduce ProcessQueues ticker 0.2s -> 0.5s (still 7x faster than
      minChatInterval of 3.5s).
  (2) Cache hidden channel ID at init; lazy refresh on disconnect.
  (3) Drop LibDeflate compress level 9 -> 1 (fraction of CPU cost).
  (4) O(1) messageCacheCount counter replaces O(n) pairs() size scan.
This commit is contained in:
Xurkon
2026-03-29 16:42:43 -05:00
parent 2d9d83f265
commit 05ea31bc53
5 changed files with 288 additions and 142 deletions
+23 -15
View File
@@ -65,7 +65,10 @@ local rateLimitQueue = {}
-- Deduplication & Quarantine
local messageCache = {}
local messageCacheCount = 0 -- O(1) counter; avoids pairs() scan on every message
local incomingMessageQueue = {}
-- Cached hidden channel ID (avoids GetChannelName every ProcessQueues tick)
local _hiddenChannelId = 0
-- Sender Trust System
local senderTrust = {}
@@ -124,10 +127,11 @@ local function IsDuplicateMessage(serializedData)
if messageCache[hash] then return true end
local count = 0
for _ in pairs(messageCache) do count = count + 1 end
if count > 500 then
-- O(1) size tracking via explicit counter
messageCacheCount = messageCacheCount + 1
if messageCacheCount > 500 then
messageCache = {}
messageCacheCount = 0
end
messageCache[hash] = true
@@ -149,9 +153,11 @@ function QuestieLearnerComms:Initialize()
ChatFrame_RemoveChannel(DEFAULT_CHAT_FRAME, hiddenChannelName)
DebugLog("CRITICAL", "Joined hidden data-sharing channel: " .. hiddenChannelName)
end
-- Cache for use in ProcessQueues (avoids GetChannelName every tick)
_hiddenChannelId = GetChannelName(hiddenChannelName) or 0
-- Process incoming/outgoing queues
QuestieCompat.C_Timer.NewTicker(0.2, function() _QuestieLearnerComms:ProcessQueues() end)
QuestieCompat.C_Timer.NewTicker(0.5, function() _QuestieLearnerComms:ProcessQueues() end)
-- Start Reinforcement Loop (every 60 seconds)
QuestieCompat.C_Timer.NewTicker(60, function() _QuestieLearnerComms:ProcessReinforcement() end)
@@ -209,7 +215,7 @@ function QuestieLearnerComms:BroadcastLearnedData(op, entityType, entityId, data
return
end
serialized = err
local compressed = LibDeflate:CompressDeflate(serialized, {level = 9})
local compressed = LibDeflate:CompressDeflate(serialized, {level = 1})
local encoded = LibDeflate:EncodeForPrint(compressed)
-- 3. Broadcast (Token Bucket logic handled in QueueMessage)
@@ -224,28 +230,30 @@ function _QuestieLearnerComms:ProcessQueues()
-- 1. Refill Tokens
local now = GetTime()
local elapsed = now - lastTokenUpdate
currentTokens = math.min(bucketCapacity, currentTokens + (elapsed * tokenRefillRate))
currentTokens = math_min(bucketCapacity, currentTokens + (elapsed * tokenRefillRate))
lastTokenUpdate = now
-- 2. Drain Outgoing Queue
if table.getn(rateLimitQueue) > 0 and currentTokens >= 1 and (now - lastChatMessageTime) >= minChatInterval then
local msg = table.remove(rateLimitQueue, 1)
if table_getn(rateLimitQueue) > 0 and currentTokens >= 1 and (now - lastChatMessageTime) >= minChatInterval then
local msg = table_remove(rateLimitQueue, 1)
currentTokens = currentTokens - 1
lastChatMessageTime = now
-- Send via Hidden Channel (Global reach)
local channelId = GetChannelName(hiddenChannelName)
if channelId > 0 then
SendChatMessage(msg, "CHANNEL", nil, channelId)
-- Use cached channel ID; refresh lazily if 0 (e.g. after disconnect)
if _hiddenChannelId == 0 then
_hiddenChannelId = GetChannelName(hiddenChannelName) or 0
end
DebugLog("DEVELOP", "Broadcasted message. Tokens left: " .. math.floor(currentTokens))
if _hiddenChannelId > 0 then
SendChatMessage(msg, "CHANNEL", nil, _hiddenChannelId)
end
DebugLog("DEVELOP", "Broadcasted message. Tokens left: " .. math_floor(currentTokens))
end
-- 3. Process Incoming Queue (Combat Aware)
local processCount = InCombatLockdown() and 2 or 6
for i = 1, processCount do
if table.getn(incomingMessageQueue) == 0 then break end
local rawMsg = table.remove(incomingMessageQueue, 1)
if table_getn(incomingMessageQueue) == 0 then break end
local rawMsg = table_remove(incomingMessageQueue, 1)
_QuestieLearnerComms:ProcessRawMessage(rawMsg.text, rawMsg.sender)
end
end