feat: add debug message throttle

This commit is contained in:
Xurkon
2026-06-06 13:44:49 -05:00
parent 31bd1e7d9b
commit 2cb129ae4b
6 changed files with 117 additions and 0 deletions
+1
View File
@@ -4,6 +4,7 @@
### Performance ### Performance
- **[Questie Debug - Message Throttle]** Added a live debug-message throttle so non-fatal debug output cannot spam chat faster than it can be read. The throttle is configurable in the Advanced tab and keeps fatal output separate.
- **[QuestieLearner - Kill/Pin Refresh Throttling]** Debounced learner-triggered map-pin refreshes so heavy kill streaks do not redraw pins on every event. Added a maximum wait cap so batched updates still flush predictably instead of being pushed out forever by constant activity. - **[QuestieLearner - Kill/Pin Refresh Throttling]** Debounced learner-triggered map-pin refreshes so heavy kill streaks do not redraw pins on every event. Added a maximum wait cap so batched updates still flush predictably instead of being pushed out forever by constant activity.
- **[QuestieLearner - Bystander Kill Suppression]** Changed visible nearby `UNIT_DIED` handling so kills from other players can update short-lived correlation evidence without immediately running full learner injection or pin refresh work. - **[QuestieLearner - Bystander Kill Suppression]** Changed visible nearby `UNIT_DIED` handling so kills from other players can update short-lived correlation evidence without immediately running full learner injection or pin refresh work.
- **[QuestieLearner - PARTY_KILL Event-Order Fix]** Fixed an edge case where a `UNIT_DIED` debounce entry could suppress a later authoritative `PARTY_KILL` for the same GUID. The debounce now tracks event type and allows the player's/group's kill event through while still suppressing true duplicates. - **[QuestieLearner - PARTY_KILL Event-Order Fix]** Fixed an edge case where a `UNIT_DIED` debounce entry could suppress a later authoritative `PARTY_KILL` for the same GUID. The debounce now tracks event type and allows the player's/group's kill event through while still suppressing true duplicates.
@@ -794,6 +794,21 @@ function QuestieOptions.tabs.advanced:Initialize()
end end
end, end,
}, },
debugMessageThrottle = {
type = "range",
order = 5.10,
name = function() return l10n('Debug message throttle'); end,
desc = function() return l10n('Minimum seconds between debug lines. Higher values reduce spam while keeping fatal output separate.') end,
width = "full",
min = 0,
max = 1,
step = 0.05,
disabled = function() return not (Questie.db.profile.debugEnabledPrint and Questie.db.profile.debugEnabled); end,
get = function() return Questie.db.profile.debugMessageThrottle or 0 end,
set = function(_, value)
Questie.db.profile.debugMessageThrottle = value
end,
},
compat_header = { compat_header = {
type = "header", type = "header",
order = 6, order = 6,
@@ -98,6 +98,7 @@ function QuestieOptionsDefaults:Load()
questieShutUp = false, questieShutUp = false,
bugWorkarounds = true, bugWorkarounds = true,
hideIconsOnContinents = false, hideIconsOnContinents = false,
debugMessageThrottle = 0.15,
-- Tracker Settings Tab -- Tracker Settings Tab
autoTrackQuests = true, autoTrackQuests = true,
+13
View File
@@ -1,4 +1,7 @@
local band = bit.band local band = bit.band
local debugThrottleState = {
lastPrint = nil,
}
------------------------- -------------------------
--Import modules. --Import modules.
@@ -169,6 +172,16 @@ function Questie:Debug(msgDebugLevel, ...)
return return
end end
local throttleSeconds = tonumber(Questie.db and Questie.db.profile and Questie.db.profile.debugMessageThrottle) or 0
local now = GetTime and GetTime() or time()
if throttleSeconds > 0 and debugThrottleState.lastPrint and (now - debugThrottleState.lastPrint) < throttleSeconds then
return
end
if throttleSeconds > 0 then
debugThrottleState.lastPrint = now
end
local prefix = "" local prefix = ""
if (band(msgDebugLevel, Questie.DEBUG_CRITICAL) ~= 0) then prefix = prefix .. "|cff00f2e6[CRITICAL]|r " end if (band(msgDebugLevel, Questie.DEBUG_CRITICAL) ~= 0) then prefix = prefix .. "|cff00f2e6[CRITICAL]|r " end
if (band(msgDebugLevel, Questie.DEBUG_ELEVATED) ~= 0) then prefix = prefix .. "|cffebf441[ELEVATED]|r " end if (band(msgDebugLevel, Questie.DEBUG_ELEVATED) ~= 0) then prefix = prefix .. "|cffebf441[ELEVATED]|r " end
+86
View File
@@ -0,0 +1,86 @@
describe("Questie debug throttle", function()
local originalPrint
local originalGetTime
local originalImport
before_each(function()
dofile("Tests/wow_api_mock.lua")
Questie.Print = nil
_G.IsLoggedIn = function()
return false
end
_G.C_Timer = _G.C_Timer or {}
_G.C_Timer.After = function()
end
originalImport = QuestieLoader.ImportModule
QuestieLoader.ImportModule = function(self, name)
if name == "QuestieLib" then
return { AddonPath = "" }
elseif name == "QuestieValidateGameCache" then
return { StartCheck = function() end }
end
return originalImport(self, name)
end
dofile("Questie.lua")
originalPrint = _G.print
originalGetTime = _G.GetTime
_G._questie_debug_lines = {}
_G.print = function(...)
local parts = {}
for i = 1, select("#", ...) do
parts[#parts + 1] = tostring(select(i, ...))
end
table.insert(_G._questie_debug_lines, table.concat(parts, " "))
end
end)
after_each(function()
_G.print = originalPrint
_G.GetTime = originalGetTime
QuestieLoader.ImportModule = originalImport
_G._questie_debug_lines = nil
end)
it("suppresses rapid debug lines until the throttle window reopens", function()
local now = 0
_G.GetTime = function()
return now
end
Questie.db.profile.debugEnabled = true
Questie.db.profile.debugEnabledPrint = true
Questie.db.profile.debugLevel = Questie.DEBUG_INFO
Questie.db.profile.debugMessageThrottle = 0.5
Questie:Debug(Questie.DEBUG_INFO, "alpha")
now = 0.1
Questie:Debug(Questie.DEBUG_INFO, "beta")
now = 0.6
Questie:Debug(Questie.DEBUG_INFO, "gamma")
assert.equals(2, table.getn(_G._questie_debug_lines))
assert.is_true(string.find(_G._questie_debug_lines[1], "alpha", 1, true) ~= nil)
assert.is_true(string.find(_G._questie_debug_lines[2], "gamma", 1, true) ~= nil)
end)
it("throttles critical messages too because fatal output is separate", function()
local now = 0
_G.GetTime = function()
return now
end
Questie.db.profile.debugEnabled = true
Questie.db.profile.debugEnabledPrint = true
Questie.db.profile.debugLevel = Questie.DEBUG_INFO + Questie.DEBUG_CRITICAL
Questie.db.profile.debugMessageThrottle = 0.5
Questie:Debug(Questie.DEBUG_INFO, "alpha")
now = 0.1
Questie:Debug(Questie.DEBUG_CRITICAL, "boom")
assert.equals(1, table.getn(_G._questie_debug_lines))
assert.is_true(string.find(_G._questie_debug_lines[1], "alpha", 1, true) ~= nil)
end)
end)
+1
View File
@@ -179,6 +179,7 @@
<h2 id="unreleased-performance-refactor">[Unreleased] &mdash; Performance Refactor Branches</h2> <h2 id="unreleased-performance-refactor">[Unreleased] &mdash; Performance Refactor Branches</h2>
<ul> <ul>
<li><strong>[QuestieDB &mdash; Required Source Item Guard]</strong> Fixed a fresh-load crash in <code>QuestieDB.GetQuest()</code> where quests that had <code>requiredSourceItems</code> but no <code>objectives</code> table would dereference <code>objectives[3]</code> while building <code>SpecialObjectives</code>. This was a code-side nil guard bug on our end, not a saved-variable problem, and it is now covered by a regression test.</li> <li><strong>[QuestieDB &mdash; Required Source Item Guard]</strong> Fixed a fresh-load crash in <code>QuestieDB.GetQuest()</code> where quests that had <code>requiredSourceItems</code> but no <code>objectives</code> table would dereference <code>objectives[3]</code> while building <code>SpecialObjectives</code>. This was a code-side nil guard bug on our end, not a saved-variable problem, and it is now covered by a regression test.</li>
<li><strong>[Questie Debug &mdash; Message Throttle]</strong> Added a live debug-message throttle so non-fatal debug output cannot spam chat faster than it can be read. The throttle is configurable in the Advanced tab and keeps fatal output separate.</li>
<li><strong>[QuestieLearner &mdash; Learner Sunstrider Clustering]</strong> Relaxed the Sunstrider Isle clustering override in learner mode so the clustering/deduplication knobs can still consolidate newly learned pins there. Static/auto keeps the original Sunstrider visibility behavior, but learner mode now preserves clustering so nearby learner spawns do not render as an unbounded fan-out of separate icons.</li> <li><strong>[QuestieLearner &mdash; Learner Sunstrider Clustering]</strong> Relaxed the Sunstrider Isle clustering override in learner mode so the clustering/deduplication knobs can still consolidate newly learned pins there. Static/auto keeps the original Sunstrider visibility behavior, but learner mode now preserves clustering so nearby learner spawns do not render as an unbounded fan-out of separate icons.</li>
<li><strong>[QuestieLearner &mdash; Turn-In Arrow Spawn Promotion]</strong> Allowed quest-related NPC and object spawns to keep their live learner coordinates even when the static database marks those spawn fields as protected. Learner mode now merges the learner's live override tables back into the getter path too, so quest giver and turn-in locations can point from learner-discovered hand-in targets without relaxing protection for unrelated world spawns.</li> <li><strong>[QuestieLearner &mdash; Turn-In Arrow Spawn Promotion]</strong> Allowed quest-related NPC and object spawns to keep their live learner coordinates even when the static database marks those spawn fields as protected. Learner mode now merges the learner's live override tables back into the getter path too, so quest giver and turn-in locations can point from learner-discovered hand-in targets without relaxing protection for unrelated world spawns.</li>
<li><strong>[QuestieLearner &mdash; Quest-Only Item Learning]</strong> Hardened item learning so plain junk loot no longer gets recorded as learner state. Only quest-relevant items are accepted now, including items that already have quest references in learned quest data. Quest-item drops still learn and still build source pins, but non-quest loot like generic trade goods and junk no longer pollutes the item learner or source cache.</li> <li><strong>[QuestieLearner &mdash; Quest-Only Item Learning]</strong> Hardened item learning so plain junk loot no longer gets recorded as learner state. Only quest-relevant items are accepted now, including items that already have quest references in learned quest data. Quest-item drops still learn and still build source pins, but non-quest loot like generic trade goods and junk no longer pollutes the item learner or source cache.</li>