perf: add learner performance controls
This commit is contained in:
@@ -141,6 +141,26 @@ local function IsDuplicateMessage(serializedData)
|
||||
return false
|
||||
end
|
||||
|
||||
local function GetLearnerSettings()
|
||||
if Questie and Questie.dbLearner and Questie.dbLearner.global and Questie.dbLearner.global.settings then
|
||||
return Questie.dbLearner.global.settings
|
||||
end
|
||||
return {}
|
||||
end
|
||||
|
||||
local function GetCommsTuning()
|
||||
local intensity = GetLearnerSettings().learnerCommsIntensity or "normal"
|
||||
if intensity == "off" then
|
||||
return false, 0, 999999, 0, 0
|
||||
elseif intensity == "low" then
|
||||
return true, 4, 6.0, 2, 1
|
||||
elseif intensity == "fast" then
|
||||
return true, 15, 1.5, 10, 4
|
||||
end
|
||||
|
||||
return true, 9, 3.5, 6, 2
|
||||
end
|
||||
|
||||
function QuestieLearnerComms:Initialize()
|
||||
DebugLog("DEVELOP", "Initializing QuestieLearnerComms")
|
||||
|
||||
@@ -195,6 +215,8 @@ function _QuestieLearnerComms:ProcessReinforcement()
|
||||
end
|
||||
|
||||
function QuestieLearnerComms:BroadcastLearnedData(op, entityType, entityId, data)
|
||||
local commsEnabled = GetCommsTuning()
|
||||
if not commsEnabled then return end
|
||||
if not data or type(data) ~= "table" then return end
|
||||
|
||||
-- 1. Create Payload (sanitize data to remove functions before serialization)
|
||||
@@ -226,11 +248,20 @@ function QuestieLearnerComms:BroadcastLearnedData(op, entityType, entityId, data
|
||||
end
|
||||
|
||||
function _QuestieLearnerComms:QueueMessage(encodedMessage)
|
||||
local commsEnabled = GetCommsTuning()
|
||||
if not commsEnabled then return end
|
||||
rateLimitQueueTail = rateLimitQueueTail + 1
|
||||
rateLimitQueue[rateLimitQueueTail] = encodedMessage
|
||||
end
|
||||
|
||||
function _QuestieLearnerComms:ProcessQueues()
|
||||
local commsEnabled, tunedBucketCapacity, tunedMinChatInterval, normalIncomingCount, combatIncomingCount = GetCommsTuning()
|
||||
bucketCapacity = tunedBucketCapacity
|
||||
tokenRefillRate = bucketCapacity / bucketWindow
|
||||
minChatInterval = tunedMinChatInterval
|
||||
currentTokens = math_min(bucketCapacity, currentTokens)
|
||||
if not commsEnabled then return end
|
||||
|
||||
-- 1. Refill Tokens
|
||||
local now = GetTime()
|
||||
local elapsed = now - lastTokenUpdate
|
||||
@@ -260,7 +291,7 @@ function _QuestieLearnerComms:ProcessQueues()
|
||||
end
|
||||
|
||||
-- 3. Process Incoming Queue (Combat Aware)
|
||||
local processCount = InCombatLockdown() and 2 or 6
|
||||
local processCount = InCombatLockdown() and combatIncomingCount or normalIncomingCount
|
||||
for i = 1, processCount do
|
||||
if incomingMessageQueueHead > incomingMessageQueueTail then break end
|
||||
local rawMsg = incomingMessageQueue[incomingMessageQueueHead]
|
||||
@@ -278,6 +309,8 @@ end
|
||||
local frame = CreateFrame("Frame")
|
||||
frame:RegisterEvent("CHAT_MSG_CHANNEL")
|
||||
frame:SetScript("OnEvent", function(self, event, msg, sender, _, _, _, _, _, channelId, channelName)
|
||||
local commsEnabled = GetCommsTuning()
|
||||
if not commsEnabled then return end
|
||||
if channelName == hiddenChannelName and sender ~= UnitName("player") then
|
||||
incomingMessageQueueTail = incomingMessageQueueTail + 1
|
||||
incomingMessageQueue[incomingMessageQueueTail] = {text = msg, sender = sender}
|
||||
@@ -285,6 +318,8 @@ frame:SetScript("OnEvent", function(self, event, msg, sender, _, _, _, _, _, cha
|
||||
end)
|
||||
|
||||
function QuestieLearnerComms:OnCommReceived(prefix, message, distribution, sender)
|
||||
local commsEnabled = GetCommsTuning()
|
||||
if not commsEnabled then return end
|
||||
if prefix == addonPrefix and sender ~= UnitName("player") then
|
||||
incomingMessageQueueTail = incomingMessageQueueTail + 1
|
||||
incomingMessageQueue[incomingMessageQueueTail] = {text = message, sender = sender}
|
||||
|
||||
@@ -24,6 +24,57 @@ QuestieOptions.tabs.advanced = {}
|
||||
local optionsDefaults = QuestieOptionsDefaults:Load()
|
||||
local _GetLanguages
|
||||
|
||||
local function GetLearnerSettings()
|
||||
Questie.dbLearner = Questie.dbLearner or {}
|
||||
Questie.dbLearner.global = Questie.dbLearner.global or {}
|
||||
Questie.dbLearner.global.settings = Questie.dbLearner.global.settings or {}
|
||||
local settings = Questie.dbLearner.global.settings
|
||||
if settings.performanceMode == nil then
|
||||
settings.performanceMode = "balanced"
|
||||
end
|
||||
if settings.pinRefreshDelay == nil then
|
||||
settings.pinRefreshDelay = 0.5
|
||||
end
|
||||
if settings.pinRefreshMode == nil then
|
||||
settings.pinRefreshMode = "batched"
|
||||
end
|
||||
if settings.liveNpcUpdateDelay == nil then
|
||||
settings.liveNpcUpdateDelay = 0.5
|
||||
end
|
||||
if settings.learnerCommsIntensity == nil then
|
||||
settings.learnerCommsIntensity = "normal"
|
||||
end
|
||||
if settings.minConfidencePins == nil then
|
||||
settings.minConfidencePins = 1
|
||||
end
|
||||
return settings
|
||||
end
|
||||
|
||||
local function ApplyLearnerPerformancePreset(mode)
|
||||
local settings = GetLearnerSettings()
|
||||
settings.performanceMode = mode
|
||||
|
||||
if mode == "realtime" then
|
||||
settings.pinRefreshDelay = 0.1
|
||||
settings.pinRefreshMode = "immediate"
|
||||
settings.liveNpcUpdateDelay = 0.25
|
||||
settings.learnerCommsIntensity = "fast"
|
||||
settings.minConfidencePins = 1
|
||||
elseif mode == "low" then
|
||||
settings.pinRefreshDelay = 2.0
|
||||
settings.pinRefreshMode = "batched"
|
||||
settings.liveNpcUpdateDelay = 2.0
|
||||
settings.learnerCommsIntensity = "low"
|
||||
settings.minConfidencePins = 3
|
||||
elseif mode == "balanced" then
|
||||
settings.pinRefreshDelay = 0.5
|
||||
settings.pinRefreshMode = "batched"
|
||||
settings.liveNpcUpdateDelay = 0.5
|
||||
settings.learnerCommsIntensity = "normal"
|
||||
settings.minConfidencePins = 1
|
||||
end
|
||||
end
|
||||
|
||||
function QuestieOptions.tabs.advanced:Initialize()
|
||||
-- This needs to be called inside of the Init process for l10n to be fully loaded
|
||||
StaticPopupDialogs["QUESTIE_LANG_CHANGED_RELOAD"] = {
|
||||
@@ -157,6 +208,120 @@ function QuestieOptions.tabs.advanced:Initialize()
|
||||
},
|
||||
},
|
||||
|
||||
learnerPerformanceSpacer = QuestieOptionsUtils:Spacer(1.9),
|
||||
learnerPerformanceHeader = {
|
||||
type = "header",
|
||||
order = 2,
|
||||
name = function() return l10n('QuestieLearner Performance'); end,
|
||||
},
|
||||
learnerPerformanceMode = {
|
||||
type = "select",
|
||||
order = 2.1,
|
||||
values = {
|
||||
realtime = l10n("Realtime"),
|
||||
balanced = l10n("Balanced"),
|
||||
low = l10n("Low Impact"),
|
||||
manual = l10n("Manual"),
|
||||
},
|
||||
style = "dropdown",
|
||||
name = function() return l10n('Performance Mode'); end,
|
||||
desc = function() return l10n('Controls how aggressively QuestieLearner updates learned pins, live data, and learner comms. Low Impact is recommended for heavy activity zones or low-end computers.'); end,
|
||||
get = function() return GetLearnerSettings().performanceMode or "balanced" end,
|
||||
set = function(_, value)
|
||||
ApplyLearnerPerformancePreset(value)
|
||||
end,
|
||||
},
|
||||
learnerPinRefreshMode = {
|
||||
type = "select",
|
||||
order = 2.2,
|
||||
values = {
|
||||
immediate = l10n("Immediate"),
|
||||
batched = l10n("Batched"),
|
||||
manual = l10n("Manual / Reload"),
|
||||
},
|
||||
style = "dropdown",
|
||||
name = function() return l10n('Pin Refresh Behavior'); end,
|
||||
desc = function() return l10n('Controls when learned pins refresh after QuestieLearner records new data. Manual / Reload records data but avoids live pin redraws until reload or another Questie refresh.'); end,
|
||||
get = function() return GetLearnerSettings().pinRefreshMode or "batched" end,
|
||||
set = function(_, value)
|
||||
local settings = GetLearnerSettings()
|
||||
settings.pinRefreshMode = value
|
||||
settings.performanceMode = "manual"
|
||||
end,
|
||||
},
|
||||
learnerPinRefreshDelay = {
|
||||
type = "range",
|
||||
order = 2.3,
|
||||
name = function() return l10n('Pin Refresh Delay'); end,
|
||||
desc = function() return l10n('Seconds to wait before refreshing learned quest pins after learner activity. Higher values reduce stutter during kill or loot bursts.'); end,
|
||||
min = 0.1,
|
||||
max = 5,
|
||||
step = 0.1,
|
||||
width = 1.5,
|
||||
get = function() return GetLearnerSettings().pinRefreshDelay or 0.5 end,
|
||||
set = function(_, value)
|
||||
local settings = GetLearnerSettings()
|
||||
settings.pinRefreshDelay = value
|
||||
settings.performanceMode = "manual"
|
||||
end,
|
||||
},
|
||||
learnerLiveNpcUpdateDelay = {
|
||||
type = "range",
|
||||
order = 2.4,
|
||||
name = function() return l10n('Live NPC Update Delay'); end,
|
||||
desc = function() return l10n('Seconds to batch learned NPC live database updates. Higher values reduce work during combat and crowded zones.'); end,
|
||||
min = 0.25,
|
||||
max = 5,
|
||||
step = 0.25,
|
||||
width = 1.5,
|
||||
get = function() return GetLearnerSettings().liveNpcUpdateDelay or 0.5 end,
|
||||
set = function(_, value)
|
||||
local settings = GetLearnerSettings()
|
||||
settings.liveNpcUpdateDelay = value
|
||||
settings.performanceMode = "manual"
|
||||
end,
|
||||
},
|
||||
learnerMinConfidencePins = {
|
||||
type = "range",
|
||||
order = 2.5,
|
||||
name = function() return l10n('Minimum Kills Before Learned Pins'); end,
|
||||
desc = function() return l10n('How many matching NPC sightings are needed before QuestieLearner shows learned pins. Higher values reduce one-off pin churn.'); end,
|
||||
min = 1,
|
||||
max = 10,
|
||||
step = 1,
|
||||
width = 1.5,
|
||||
get = function() return GetLearnerSettings().minConfidencePins or 1 end,
|
||||
set = function(_, value)
|
||||
local settings = GetLearnerSettings()
|
||||
settings.minConfidencePins = value
|
||||
settings.performanceMode = "manual"
|
||||
end,
|
||||
},
|
||||
learnerCommsIntensity = {
|
||||
type = "select",
|
||||
order = 2.6,
|
||||
values = {
|
||||
off = l10n("Off"),
|
||||
low = l10n("Low"),
|
||||
normal = l10n("Normal"),
|
||||
fast = l10n("Fast"),
|
||||
},
|
||||
style = "dropdown",
|
||||
name = function() return l10n('Learner Comms Intensity'); end,
|
||||
desc = function() return l10n('Controls how much learner data Questie processes and sends through learner comms. Lower values reduce CPU and chat-channel work.'); end,
|
||||
get = function() return GetLearnerSettings().learnerCommsIntensity or "normal" end,
|
||||
set = function(_, value)
|
||||
local settings = GetLearnerSettings()
|
||||
settings.learnerCommsIntensity = value
|
||||
settings.performanceMode = "manual"
|
||||
if value == "off" then
|
||||
Questie.db.profile.learnerBroadcast = false
|
||||
elseif Questie.db.profile.learnerBroadcast == false then
|
||||
Questie.db.profile.learnerBroadcast = true
|
||||
end
|
||||
end,
|
||||
},
|
||||
|
||||
Spacer_A = QuestieOptionsUtils:Spacer(2.9),
|
||||
locale_header = {
|
||||
type = "header",
|
||||
|
||||
@@ -77,6 +77,7 @@ function QuestieOptionsDefaults:Load()
|
||||
enableTooltipsQuestLevel = true,
|
||||
showQuestXpAtMaxLevel = true,
|
||||
enableTooltipsNextInChain = true,
|
||||
learnerBroadcast = true,
|
||||
enableMapIcons = true,
|
||||
enableMiniMapIcons = true,
|
||||
questieShutUp = false,
|
||||
|
||||
@@ -321,6 +321,11 @@ local function EnsureLearnedData()
|
||||
prioritizeMyData = true,
|
||||
staleThreshold = 90, -- days
|
||||
pruneVerified = false, -- protect verified data by default
|
||||
performanceMode = "balanced",
|
||||
pinRefreshDelay = 0.5,
|
||||
pinRefreshMode = "batched",
|
||||
liveNpcUpdateDelay = 0.5,
|
||||
learnerCommsIntensity = "normal",
|
||||
}
|
||||
else
|
||||
-- Backfill sub-tables that may be missing from older SavedVariables
|
||||
@@ -337,6 +342,27 @@ local function EnsureLearnedData()
|
||||
if s.learnObjects == nil then s.learnObjects = true end
|
||||
if s.minConfidencePins == nil then s.minConfidencePins = 1 end
|
||||
if s.prioritizeMyData == nil then s.prioritizeMyData = true end
|
||||
if s.staleThreshold == nil then
|
||||
s.staleThreshold = 90
|
||||
end
|
||||
if s.pruneVerified == nil then
|
||||
s.pruneVerified = false
|
||||
end
|
||||
if s.performanceMode == nil then
|
||||
s.performanceMode = "balanced"
|
||||
end
|
||||
if s.pinRefreshDelay == nil then
|
||||
s.pinRefreshDelay = 0.5
|
||||
end
|
||||
if s.pinRefreshMode == nil then
|
||||
s.pinRefreshMode = "batched"
|
||||
end
|
||||
if s.liveNpcUpdateDelay == nil then
|
||||
s.liveNpcUpdateDelay = 0.5
|
||||
end
|
||||
if s.learnerCommsIntensity == nil then
|
||||
s.learnerCommsIntensity = "normal"
|
||||
end
|
||||
end
|
||||
return true
|
||||
end
|
||||
@@ -355,6 +381,16 @@ function QuestieLearner:GetSettings()
|
||||
return Questie.dbLearner.global.settings
|
||||
end
|
||||
|
||||
local function GetLearnerSetting(key, defaultValue)
|
||||
if Questie and Questie.dbLearner and Questie.dbLearner.global and Questie.dbLearner.global.settings then
|
||||
local value = Questie.dbLearner.global.settings[key]
|
||||
if value ~= nil then
|
||||
return value
|
||||
end
|
||||
end
|
||||
return defaultValue
|
||||
end
|
||||
|
||||
------------------------------------------------------------------------
|
||||
-- Cross-link engine
|
||||
-- After ANY entity is learned, scan all other learned data and stitch
|
||||
@@ -427,6 +463,7 @@ local function _GetDB() return Questie.dbLearner.global end
|
||||
-- Called after cross-linking so map pins refresh immediately.
|
||||
local _pendingQuestPinRefreshes = {}
|
||||
local _pendingQuestPinRefreshTimer = nil
|
||||
local _pendingQuestFrameUnloads = {}
|
||||
|
||||
local function _FlushActiveQuestPins()
|
||||
local questIdSet = _pendingQuestPinRefreshes
|
||||
@@ -439,14 +476,24 @@ local function _FlushActiveQuestPins()
|
||||
|
||||
for questId in pairs(questIdSet) do
|
||||
if QuestiePlayer.currentQuestlog[questId] then
|
||||
if QuestieMap and QuestieMap.UnloadQuestFrames and _pendingQuestFrameUnloads[questId] then
|
||||
QuestieMap:UnloadQuestFrames(questId)
|
||||
end
|
||||
QuestieQuest:UpdateQuest(questId)
|
||||
end
|
||||
end
|
||||
_pendingQuestFrameUnloads = {}
|
||||
end
|
||||
|
||||
local function _RefreshActiveQuestPins(questIdSet)
|
||||
-- Skip scheduling when the set is empty (avoids no-op timer callbacks)
|
||||
if not next(questIdSet) then return end
|
||||
if GetLearnerSetting("pinRefreshMode", "batched") == "manual" then
|
||||
for questId in pairs(questIdSet) do
|
||||
_pendingQuestFrameUnloads[questId] = nil
|
||||
end
|
||||
return
|
||||
end
|
||||
for questId in pairs(questIdSet) do
|
||||
_pendingQuestPinRefreshes[questId] = true
|
||||
end
|
||||
@@ -458,7 +505,7 @@ local function _RefreshActiveQuestPins(questIdSet)
|
||||
local timer = (C_Timer) or (QuestieCompat and QuestieCompat.C_Timer)
|
||||
if timer and timer.After then
|
||||
_pendingQuestPinRefreshTimer = true
|
||||
timer.After(0.15, _FlushActiveQuestPins)
|
||||
timer.After(GetLearnerSetting("pinRefreshDelay", 0.5), _FlushActiveQuestPins)
|
||||
else
|
||||
_FlushActiveQuestPins()
|
||||
end
|
||||
@@ -577,8 +624,10 @@ local function _InvalidateSpawnListsForNPC(npcId)
|
||||
and (now - _invalidateDebounce[debounceKey]) < debounceWindow
|
||||
if not suppressUnload then
|
||||
_invalidateDebounce[debounceKey] = now
|
||||
if QuestieMap and QuestieMap.UnloadQuestFrames then
|
||||
if GetLearnerSetting("pinRefreshMode", "batched") == "immediate" and QuestieMap and QuestieMap.UnloadQuestFrames then
|
||||
QuestieMap:UnloadQuestFrames(questId)
|
||||
else
|
||||
_pendingQuestFrameUnloads[questId] = true
|
||||
end
|
||||
end
|
||||
questsToRefresh[questId] = true -- always refresh when data changed
|
||||
@@ -607,7 +656,6 @@ end
|
||||
-- arrive within a few frames. Saved learner evidence is updated immediately,
|
||||
-- but live QuestieDB override/cache invalidation is batched so the large DB
|
||||
-- layer is not churned on every single kill.
|
||||
local LIVE_NPC_UPDATE_DELAY = 0.5
|
||||
|
||||
local function _ApplyNpcLiveUpdate(npcId)
|
||||
local existing = Questie.dbLearner
|
||||
@@ -676,7 +724,7 @@ local function _QueueNpcLiveUpdate(npcId)
|
||||
local timer = QuestieCompat and QuestieCompat.C_Timer
|
||||
if timer and timer.After then
|
||||
_Learner.pendingNpcLiveUpdateTimer = true
|
||||
timer.After(LIVE_NPC_UPDATE_DELAY, _FlushNpcLiveUpdates)
|
||||
timer.After(GetLearnerSetting("liveNpcUpdateDelay", 0.5), _FlushNpcLiveUpdates)
|
||||
else
|
||||
_FlushNpcLiveUpdates()
|
||||
end
|
||||
@@ -3684,6 +3732,13 @@ local function _ValidateLearnedSpawnData(data)
|
||||
end
|
||||
|
||||
function _Learner:BroadcastIfCommsAvailable(typ, id, data)
|
||||
if Questie and Questie.db and Questie.db.profile and Questie.db.profile.learnerBroadcast == false then
|
||||
return
|
||||
end
|
||||
if GetLearnerSetting("learnerCommsIntensity", "normal") == "off" then
|
||||
return
|
||||
end
|
||||
|
||||
local QuestieLearnerComms = QuestieLoader:ImportModule("QuestieLearnerComms")
|
||||
if not (QuestieLearnerComms and QuestieLearnerComms.BroadcastLearnedData) then
|
||||
return
|
||||
@@ -3720,14 +3775,19 @@ function _Learner:BroadcastIfCommsAvailable(typ, id, data)
|
||||
|
||||
if timer and timer.After then
|
||||
_Learner.pendingBroadcastTimer = true
|
||||
timer.After(2, FlushBroadcasts)
|
||||
local delay = 2
|
||||
local intensity = GetLearnerSetting("learnerCommsIntensity", "normal")
|
||||
if intensity == "low" then
|
||||
delay = 4
|
||||
elseif intensity == "fast" then
|
||||
delay = 1
|
||||
end
|
||||
timer.After(delay, FlushBroadcasts)
|
||||
else
|
||||
FlushBroadcasts()
|
||||
end
|
||||
end
|
||||
|
||||
local NETWORK_MERGE_DELAY = 0.5
|
||||
|
||||
local function _QueueIncomingNetworkMerge(typ, id, data, op)
|
||||
_Learner.pendingNetworkMerges = _Learner.pendingNetworkMerges or {}
|
||||
local key = typ .. ":" .. tostring(id)
|
||||
@@ -3760,7 +3820,7 @@ local function _QueueIncomingNetworkMerge(typ, id, data, op)
|
||||
|
||||
if timer and timer.After then
|
||||
_Learner.pendingNetworkMergeTimer = true
|
||||
timer.After(NETWORK_MERGE_DELAY, FlushNetworkMerges)
|
||||
timer.After(GetLearnerSetting("liveNpcUpdateDelay", 0.5), FlushNetworkMerges)
|
||||
else
|
||||
FlushNetworkMerges()
|
||||
end
|
||||
|
||||
Reference in New Issue
Block a user