perf: batch learner merges and guard debug scans
This commit is contained in:
+10
-4
@@ -128,14 +128,18 @@ local function loadFullDatabase()
|
|||||||
print("\124cFF4DDBFF [1/9] " .. l10n("Loading database") .. "...")
|
print("\124cFF4DDBFF [1/9] " .. l10n("Loading database") .. "...")
|
||||||
|
|
||||||
QuestieInit:LoadBaseDB()
|
QuestieInit:LoadBaseDB()
|
||||||
Questie:Debug(Questie.DEBUG_DEVELOP, "[DBDiag] After LoadBaseDB - quest:" .. _dbStats(QuestieDB.questData) .. " npc:" .. _dbStats(QuestieDB.npcData))
|
if Questie.db and Questie.db.profile and Questie.db.profile.debugEnabled then
|
||||||
Questie:Debug(Questie.DEBUG_DEVELOP, "[DBDiag] obj:" .. _dbStats(QuestieDB.objectData) .. " item:" .. _dbStats(QuestieDB.itemData))
|
Questie:Debug(Questie.DEBUG_DEVELOP, "[DBDiag] After LoadBaseDB - quest:" .. _dbStats(QuestieDB.questData) .. " npc:" .. _dbStats(QuestieDB.npcData))
|
||||||
|
Questie:Debug(Questie.DEBUG_DEVELOP, "[DBDiag] obj:" .. _dbStats(QuestieDB.objectData) .. " item:" .. _dbStats(QuestieDB.itemData))
|
||||||
|
end
|
||||||
|
|
||||||
print("\124cFF4DDBFF [2/9] " .. l10n("Applying database corrections") .. "...")
|
print("\124cFF4DDBFF [2/9] " .. l10n("Applying database corrections") .. "...")
|
||||||
|
|
||||||
coYield()
|
coYield()
|
||||||
QuestieCorrections:Initialize()
|
QuestieCorrections:Initialize()
|
||||||
Questie:Debug(Questie.DEBUG_DEVELOP, "[DBDiag] After Corrections - quest:" .. _dbStats(QuestieDB.questData) .. " npc:" .. _dbStats(QuestieDB.npcData))
|
if Questie.db and Questie.db.profile and Questie.db.profile.debugEnabled then
|
||||||
|
Questie:Debug(Questie.DEBUG_DEVELOP, "[DBDiag] After Corrections - quest:" .. _dbStats(QuestieDB.questData) .. " npc:" .. _dbStats(QuestieDB.npcData))
|
||||||
|
end
|
||||||
|
|
||||||
print("\124cFF4DDBFF [3/9] " .. l10n("Initializing townfolks") .. "...")
|
print("\124cFF4DDBFF [3/9] " .. l10n("Initializing townfolks") .. "...")
|
||||||
coYield()
|
coYield()
|
||||||
@@ -289,7 +293,9 @@ QuestieInit.Stages[1] = function() -- run as a coroutine
|
|||||||
l10n("Questie DB has updated!") ..
|
l10n("Questie DB has updated!") ..
|
||||||
"|r|cFFFF6F22 " .. l10n("Data is being processed, this may take a few moments and cause some lag..."))
|
"|r|cFFFF6F22 " .. l10n("Data is being processed, this may take a few moments and cause some lag..."))
|
||||||
loadFullDatabase()
|
loadFullDatabase()
|
||||||
Questie:Debug(Questie.DEBUG_DEVELOP, "[DBDiag] Before Compile - quest:" .. _dbStats(QuestieDB.questData) .. " npc:" .. _dbStats(QuestieDB.npcData))
|
if Questie.db and Questie.db.profile and Questie.db.profile.debugEnabled then
|
||||||
|
Questie:Debug(Questie.DEBUG_DEVELOP, "[DBDiag] Before Compile - quest:" .. _dbStats(QuestieDB.questData) .. " npc:" .. _dbStats(QuestieDB.npcData))
|
||||||
|
end
|
||||||
QuestieDBCompiler:Compile()
|
QuestieDBCompiler:Compile()
|
||||||
Questie:Debug(Questie.DEBUG_DEVELOP, "[DBDiag] After Compile - quest:type=" .. type(QuestieDB.questData) .. " npc:type=" .. type(QuestieDB.npcData))
|
Questie:Debug(Questie.DEBUG_DEVELOP, "[DBDiag] After Compile - quest:type=" .. type(QuestieDB.questData) .. " npc:type=" .. type(QuestieDB.npcData))
|
||||||
dbCompiled = true
|
dbCompiled = true
|
||||||
|
|||||||
+133
-37
@@ -123,7 +123,10 @@ _Learner.pendingNpcs = {}
|
|||||||
_Learner.pendingQuests = {}
|
_Learner.pendingQuests = {}
|
||||||
_Learner.pendingItems = {}
|
_Learner.pendingItems = {}
|
||||||
_Learner.pendingObjects = {}
|
_Learner.pendingObjects = {}
|
||||||
|
_Learner.pendingNetworkMerges = {}
|
||||||
_Learner.pendingItemLinks = {} -- queue for async GetItemInfo retries
|
_Learner.pendingItemLinks = {} -- queue for async GetItemInfo retries
|
||||||
|
_Learner.npcNameIndex = nil
|
||||||
|
_Learner.npcNameIndexDirty = true
|
||||||
|
|
||||||
-- Direct reference to learnedData, set on Initialize
|
-- Direct reference to learnedData, set on Initialize
|
||||||
QuestieLearner.data = nil
|
QuestieLearner.data = nil
|
||||||
@@ -658,6 +661,51 @@ local function _QueueNpcLiveUpdate(npcId)
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
local function _MarkNpcNameIndexDirty()
|
||||||
|
_Learner.npcNameIndexDirty = true
|
||||||
|
end
|
||||||
|
|
||||||
|
local function _RebuildNpcNameIndex()
|
||||||
|
local overrideIndex = {}
|
||||||
|
local baseIndex = {}
|
||||||
|
|
||||||
|
if QuestieDB and QuestieDB.npcDataOverrides then
|
||||||
|
for npcId, data in pairs(QuestieDB.npcDataOverrides) do
|
||||||
|
local name = data and data[1]
|
||||||
|
if type(name) == "string" and name ~= "" then
|
||||||
|
overrideIndex[string.lower(name)] = npcId
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if QuestieDB and QuestieDB.npcData then
|
||||||
|
for npcId, data in pairs(QuestieDB.npcData) do
|
||||||
|
local name = data and data[1]
|
||||||
|
if type(name) == "string" and name ~= "" then
|
||||||
|
local lowerName = string.lower(name)
|
||||||
|
if overrideIndex[lowerName] == nil and baseIndex[lowerName] == nil then
|
||||||
|
baseIndex[lowerName] = npcId
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
_Learner.npcNameIndex = {
|
||||||
|
override = overrideIndex,
|
||||||
|
base = baseIndex,
|
||||||
|
}
|
||||||
|
_Learner.npcNameIndexDirty = false
|
||||||
|
end
|
||||||
|
|
||||||
|
local function _EnsureNpcNameIndex()
|
||||||
|
if _Learner.npcNameIndex and not _Learner.npcNameIndexDirty then
|
||||||
|
return _Learner.npcNameIndex
|
||||||
|
end
|
||||||
|
|
||||||
|
_RebuildNpcNameIndex()
|
||||||
|
return _Learner.npcNameIndex
|
||||||
|
end
|
||||||
|
|
||||||
------------------------------------------------------------------------
|
------------------------------------------------------------------------
|
||||||
-- CrossLinkAfterNPC: called when a new NPC is first learned.
|
-- CrossLinkAfterNPC: called when a new NPC is first learned.
|
||||||
-- Scans all learned quests for any reference to this npcId and stitches
|
-- Scans all learned quests for any reference to this npcId and stitches
|
||||||
@@ -963,7 +1011,10 @@ function QuestieLearner:LearnNPC(npcId, name, level, subName, npcFlags, factionS
|
|||||||
Questie.dbLearner.global.npcs[npcId] = existing
|
Questie.dbLearner.global.npcs[npcId] = existing
|
||||||
end
|
end
|
||||||
|
|
||||||
if name and name ~= "" and (not existing[1] or existing[1] == "") then existing[1] = name end
|
if name and name ~= "" and (not existing[1] or existing[1] == "") then
|
||||||
|
existing[1] = name
|
||||||
|
_MarkNpcNameIndexDirty()
|
||||||
|
end
|
||||||
if level then
|
if level then
|
||||||
if not existing[4] or level < existing[4] then existing[4] = level end
|
if not existing[4] or level < existing[4] then existing[4] = level end
|
||||||
if not existing[5] or level > existing[5] then existing[5] = level end
|
if not existing[5] or level > existing[5] then existing[5] = level end
|
||||||
@@ -1997,6 +2048,7 @@ function QuestieLearner:InjectLearnedData()
|
|||||||
|
|
||||||
-- 1. NPCs
|
-- 1. NPCs
|
||||||
local npcIdsToFix = {}
|
local npcIdsToFix = {}
|
||||||
|
local npcNameIndexNeedsRebuild = false
|
||||||
for npcId, data in pairs(learned.npcs) do
|
for npcId, data in pairs(learned.npcs) do
|
||||||
local nid = tonumber(npcId)
|
local nid = tonumber(npcId)
|
||||||
if type(npcId) == "string" and nid then
|
if type(npcId) == "string" and nid then
|
||||||
@@ -2009,12 +2061,14 @@ function QuestieLearner:InjectLearnedData()
|
|||||||
-- and can pollute curated plugin spawn tables.
|
-- and can pollute curated plugin spawn tables.
|
||||||
QuestieDB.npcDataOverrides[nid or npcId] = CopyWithoutField(data, 7)
|
QuestieDB.npcDataOverrides[nid or npcId] = CopyWithoutField(data, 7)
|
||||||
npcCount = npcCount + 1
|
npcCount = npcCount + 1
|
||||||
|
if data[1] then npcNameIndexNeedsRebuild = true end
|
||||||
else
|
else
|
||||||
local existing = QuestieDB.npcDataOverrides[nid or npcId]
|
local existing = QuestieDB.npcDataOverrides[nid or npcId]
|
||||||
-- Adopt other fields if missing
|
-- Adopt other fields if missing
|
||||||
for k, v in pairs(data) do
|
for k, v in pairs(data) do
|
||||||
if k ~= "mc" and k ~= 7 and existing[k] == nil and not IsAscensionProtected("NPC", nid or npcId, k) then
|
if k ~= "mc" and k ~= 7 and existing[k] == nil and not IsAscensionProtected("NPC", nid or npcId, k) then
|
||||||
existing[k] = v
|
existing[k] = v
|
||||||
|
if k == 1 then npcNameIndexNeedsRebuild = true end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -2024,14 +2078,19 @@ function QuestieLearner:InjectLearnedData()
|
|||||||
learned.npcs[old] = nil
|
learned.npcs[old] = nil
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Diagnostic: log how many NPCs were injected with spawn overrides
|
if Questie.db and Questie.db.profile and Questie.db.profile.debugEnabled then
|
||||||
local spawnOverrideCount = 0
|
-- Diagnostic: log how many NPCs were injected with spawn overrides
|
||||||
for nid, ovr in pairs(QuestieDB.npcDataOverrides) do
|
local spawnOverrideCount = 0
|
||||||
if ovr[7] and next(ovr[7]) then
|
for nid, ovr in pairs(QuestieDB.npcDataOverrides) do
|
||||||
spawnOverrideCount = spawnOverrideCount + 1
|
if ovr[7] and next(ovr[7]) then
|
||||||
|
spawnOverrideCount = spawnOverrideCount + 1
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
Questie:Debug(Questie.DEBUG_CRITICAL, "[QuestieLearner] InjectLearnedData: injected", npcCount, "NPCs (", spawnOverrideCount, "with spawn overrides)")
|
||||||
|
end
|
||||||
|
if npcNameIndexNeedsRebuild then
|
||||||
|
_MarkNpcNameIndexDirty()
|
||||||
end
|
end
|
||||||
Questie:Debug(Questie.DEBUG_CRITICAL, "[QuestieLearner] InjectLearnedData: injected", npcCount, "NPCs (", spawnOverrideCount, "with spawn overrides)")
|
|
||||||
|
|
||||||
-- Purge garbage quest entries: quests with no name [1] and only mc/ls metadata.
|
-- Purge garbage quest entries: quests with no name [1] and only mc/ls metadata.
|
||||||
-- These are Ascension internal tracking artifacts (hash-like IDs) with no real quest data.
|
-- These are Ascension internal tracking artifacts (hash-like IDs) with no real quest data.
|
||||||
@@ -2523,30 +2582,16 @@ function QuestieLearner:OnQuestComplete()
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Helper to find an NPC ID by name (case-insensitive substring match)
|
-- Helper to find an NPC ID by name (case-insensitive exact match)
|
||||||
-- Used for proactive objective mapping when a quest is first accepted.
|
-- Used for proactive objective mapping when a quest is first accepted.
|
||||||
function QuestieLearner:GetNPCIdByName(npcName)
|
function QuestieLearner:GetNPCIdByName(npcName)
|
||||||
if not npcName or npcName == "" then return nil end
|
if not npcName or npcName == "" then return nil end
|
||||||
local lowerName = string.lower(npcName)
|
local lowerName = string.lower(npcName)
|
||||||
|
|
||||||
-- Check overrides first (most likely for custom servers)
|
|
||||||
if QuestieDB.npcDataOverrides then
|
|
||||||
for id, data in pairs(QuestieDB.npcDataOverrides) do
|
|
||||||
if data and data[1] and string.lower(data[1]) == lowerName then
|
|
||||||
return id
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
-- Check base NPC database
|
local index = _EnsureNpcNameIndex()
|
||||||
-- Note: QueryNPCSingle is a dummy until initialization, but we can fallback to raw access
|
local overrideId = index.override[lowerName]
|
||||||
local npcData = QuestieDB.npcData or {}
|
if overrideId then return overrideId end
|
||||||
for id, data in pairs(npcData) do
|
return index.base[lowerName]
|
||||||
if data and data[1] and string.lower(data[1]) == lowerName then
|
|
||||||
return id
|
|
||||||
end
|
|
||||||
end
|
|
||||||
return nil
|
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Fires when a quest is accepted.
|
-- Fires when a quest is accepted.
|
||||||
@@ -3664,42 +3709,89 @@ function _Learner:BroadcastIfCommsAvailable(typ, id, data)
|
|||||||
end
|
end
|
||||||
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)
|
||||||
|
_Learner.pendingNetworkMerges[key] = {
|
||||||
|
typ = typ,
|
||||||
|
id = id,
|
||||||
|
data = data,
|
||||||
|
op = op,
|
||||||
|
}
|
||||||
|
|
||||||
|
if _Learner.pendingNetworkMergeTimer then return end
|
||||||
|
|
||||||
|
local timer = QuestieCompat and QuestieCompat.C_Timer
|
||||||
|
local function FlushNetworkMerges()
|
||||||
|
local pending = _Learner.pendingNetworkMerges
|
||||||
|
_Learner.pendingNetworkMerges = {}
|
||||||
|
_Learner.pendingNetworkMergeTimer = nil
|
||||||
|
|
||||||
|
local anyChanged = false
|
||||||
|
for _, entry in pairs(pending) do
|
||||||
|
local changed = QuestieLearner:_ApplyIncomingNetworkMerge(entry.typ, entry.id, entry.data, entry.op)
|
||||||
|
anyChanged = anyChanged or changed
|
||||||
|
end
|
||||||
|
|
||||||
|
if anyChanged then
|
||||||
|
QuestieLearner:InjectLearnedData()
|
||||||
|
QuestieLearner.data = Questie.dbLearner.global
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if timer and timer.After then
|
||||||
|
_Learner.pendingNetworkMergeTimer = true
|
||||||
|
timer.After(NETWORK_MERGE_DELAY, FlushNetworkMerges)
|
||||||
|
else
|
||||||
|
FlushNetworkMerges()
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
-- Receives validated, decoded data from QuestieLearnerComms or QuestieLearnerExport:MergeImport
|
-- Receives validated, decoded data from QuestieLearnerComms or QuestieLearnerExport:MergeImport
|
||||||
function QuestieLearner:HandleNetworkData(typ, id, d, op)
|
function QuestieLearner:HandleNetworkData(typ, id, d, op)
|
||||||
if not self:IsEnabled() then return end
|
if not self:IsEnabled() then return end
|
||||||
if not EnsureLearnedData() then return end
|
if not EnsureLearnedData() then return end
|
||||||
if not typ or not id or not d then return end
|
if not typ or not id or not d then return end
|
||||||
|
|
||||||
|
_QueueIncomingNetworkMerge(typ, id, d, op)
|
||||||
|
end
|
||||||
|
|
||||||
|
function QuestieLearner:_ApplyIncomingNetworkMerge(typ, id, d, op)
|
||||||
|
if not typ or not id or not d then return false end
|
||||||
|
|
||||||
local store
|
local store
|
||||||
if typ == "NPC" then
|
if typ == "NPC" then
|
||||||
if not Questie.dbLearner.global.settings.learnNpcs then return end
|
if not Questie.dbLearner.global.settings.learnNpcs then return false end
|
||||||
store = Questie.dbLearner.global.npcs
|
store = Questie.dbLearner.global.npcs
|
||||||
elseif typ == "QUEST" then
|
elseif typ == "QUEST" then
|
||||||
if not Questie.dbLearner.global.settings.learnQuests then return end
|
if not Questie.dbLearner.global.settings.learnQuests then return false end
|
||||||
store = Questie.dbLearner.global.quests
|
store = Questie.dbLearner.global.quests
|
||||||
elseif typ == "ITEM" then
|
elseif typ == "ITEM" then
|
||||||
if not Questie.dbLearner.global.settings.learnItems then return end
|
if not Questie.dbLearner.global.settings.learnItems then return false end
|
||||||
store = Questie.dbLearner.global.items
|
store = Questie.dbLearner.global.items
|
||||||
elseif typ == "OBJECT" then
|
elseif typ == "OBJECT" then
|
||||||
if not Questie.dbLearner.global.settings.learnObjects then return end
|
if not Questie.dbLearner.global.settings.learnObjects then return false end
|
||||||
store = Questie.dbLearner.global.objects
|
store = Questie.dbLearner.global.objects
|
||||||
else
|
else
|
||||||
return
|
return false
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Validate external data before merging to prevent crash on malformed input
|
-- Validate external data before merging to prevent crash on malformed input
|
||||||
if not _ValidateLearnedSpawnData(d) then
|
if not _ValidateLearnedSpawnData(d) then
|
||||||
Questie:Debug(Questie.DEBUG_LEARNER, "[QuestieLearner] Rejected malformed network data", typ, id)
|
Questie:Debug(Questie.DEBUG_LEARNER, "[QuestieLearner] Rejected malformed network data", typ, id)
|
||||||
return
|
return false
|
||||||
end
|
end
|
||||||
|
|
||||||
local existing = store[id]
|
local existing = store[id]
|
||||||
if not existing then
|
if not existing then
|
||||||
store[id] = d
|
store[id] = d
|
||||||
store[id].mc = 1
|
store[id].mc = 1
|
||||||
self:InjectLearnedData()
|
if typ == "NPC" and type(d[1]) == "string" and d[1] ~= "" then
|
||||||
QuestieLearner.data = Questie.dbLearner.global
|
_MarkNpcNameIndexDirty()
|
||||||
return
|
end
|
||||||
|
return true
|
||||||
end
|
end
|
||||||
|
|
||||||
local changed = false
|
local changed = false
|
||||||
@@ -3708,6 +3800,9 @@ function QuestieLearner:HandleNetworkData(typ, id, d, op)
|
|||||||
if k ~= "mc" and existing[k] == nil then
|
if k ~= "mc" and existing[k] == nil then
|
||||||
existing[k] = v
|
existing[k] = v
|
||||||
changed = true
|
changed = true
|
||||||
|
if typ == "NPC" and k == 1 and type(v) == "string" and v ~= "" then
|
||||||
|
_MarkNpcNameIndexDirty()
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -3744,9 +3839,10 @@ function QuestieLearner:HandleNetworkData(typ, id, d, op)
|
|||||||
if changed or (op == "NEW" or op == "UPDATE") then
|
if changed or (op == "NEW" or op == "UPDATE") then
|
||||||
existing.ls = time() -- Refresh timestamp on network confirmation
|
existing.ls = time() -- Refresh timestamp on network confirmation
|
||||||
existing.mc = (existing.mc or 0) + 1
|
existing.mc = (existing.mc or 0) + 1
|
||||||
QuestieLearner.data = Questie.dbLearner.global
|
return true
|
||||||
self:InjectLearnedData()
|
|
||||||
end
|
end
|
||||||
|
|
||||||
|
return false
|
||||||
end
|
end
|
||||||
|
|
||||||
return QuestieLearner
|
return QuestieLearner
|
||||||
|
|||||||
Reference in New Issue
Block a user