From da1e12c20f589b05e108a1de26d9d4accb987668 Mon Sep 17 00:00:00 2001 From: Tercio Jose Date: Thu, 12 Oct 2023 15:13:42 -0300 Subject: [PATCH] Framework update --- Libs/DF/auras.lua | 55 +++++++++++++++++++++ Libs/DF/definitions.lua | 4 +- Libs/DF/fw.lua | 18 +++---- Libs/DF/pictureedit.lua | 2 +- Libs/DF/schedules.lua | 107 ++++++++++++++++++++++++++++++++-------- Libs/DF/scrollbox.lua | 100 +++++++++++++++++++++++++------------ Libs/DF/spells.lua | 6 +-- 7 files changed, 224 insertions(+), 68 deletions(-) diff --git a/Libs/DF/auras.lua b/Libs/DF/auras.lua index be234a207..18c255fa4 100644 --- a/Libs/DF/auras.lua +++ b/Libs/DF/auras.lua @@ -49,7 +49,62 @@ local default_text_for_aura_frame = { MANUAL_ADD_TRACKLIST_DEBUFF = "Add Debuff to Tracklist", } +--store spell caches, they load empty and are filled when an addon require a cache with all spells +local spellsHashMap +local spellsIndexTable +local spellsWithSameName + +local lazyLoadAllSpells = function(payload, iterationCount, maxIterations) + payload.nextIndex = payload.nextIndex or 0 + local startPoint = payload.nextIndex + --the goal is iterate over 500000 spell ids over 200 frames + local endPoint = startPoint + 2500 + payload.nextIndex = endPoint + local i = startPoint + 1 + + --make upvalues be closer + local toLowerCase = string.lower + local GetSpellInfo = GetSpellInfo + local hashMap = spellsHashMap + local indexTable = spellsIndexTable + + while (i < endPoint) do + local spellName = GetSpellInfo(i) + + if (spellName) then + spellName = toLowerCase(spellName) + hashMap[spellName] = i --[spellname] = spellId + indexTable[#indexTable+1] = spellName --array with all spellnames + + local sameNameTable = spellsWithSameName[spellName] + if (not sameNameTable) then + sameNameTable = {} + spellsWithSameName[spellName] = sameNameTable + end + sameNameTable[#sameNameTable+1] = i + end + + i = i + 1 + end +end + function DF:LoadAllSpells(hashMap, indexTable, allSpellsSameName) + if (spellsHashMap) then + return spellsHashMap, spellsIndexTable, spellsWithSameName + end + + assert(type(hashMap) == "table", "DetailsFramework:LoadAllSpells(): require a table on #1 parameter.") + assert(type(indexTable) == "table", "DetailsFramework:LoadAllSpells(): require a table on #2 parameter.") + assert(type(allSpellsSameName) == "table", "DetailsFramework:LoadAllSpells(): require a table on #3 parameter.") + + spellsHashMap = hashMap + spellsIndexTable = indexTable + spellsWithSameName = allSpellsSameName + + detailsFramework.Schedules.LazyExecute(lazyLoadAllSpells, {}, 200) + + if 1 then return end + --pre checking which tables to fill to avoid checking if the table exists during the gigantic loop for performance if (not DF.LoadingAuraAlertFrame) then DF.LoadingAuraAlertFrame = CreateFrame("frame", "DetailsFrameworkLoadingAurasAlert", UIParent, "BackdropTemplate") diff --git a/Libs/DF/definitions.lua b/Libs/DF/definitions.lua index 6294b9347..940e0b440 100644 --- a/Libs/DF/definitions.lua +++ b/Libs/DF/definitions.lua @@ -28,9 +28,9 @@ ---@field table df_table_functions ---@field IsValidSpecId fun(self:table, specId:number):boolean check if the passed specId is valid for the player class, also return false for tutorial specs ---@field DebugVisibility fun(self:table, object:uiobject) print the reason why the frame isn't shown in the screen ----@field Dispatch fun(self:table, callback:function, ...) : any dispatch a function call using xpcall +---@field Dispatch fun(self:table, callback:function, ...) : any dispatch a function call using xpcall, print to chat if the function passed is invalid ---@field QuickDispatch fun(self:table, callback:function, ...) : any dispatch a function call without errors if the function passed is invalid ----@field CoreDispatch fun(self:table, context:string, callback:function, ...) : any deprecated at this point, use Dispatch instead +---@field CoreDispatch fun(self:table, context:string, callback:function, ...) : any dispatch a function using xpcall, make an error if the function passed is invalid ---@field GetDefaultBackdropColor fun(self:table) : red, green, blue, alpha return the standard backdrop color used by blizzard on their own frames ---@field Msg fun(self:table, message:string, ...) show a message in the chat frame ---@field MsgWarning fun(self:table, message:string, ...) show a warning message in the chat frame diff --git a/Libs/DF/fw.lua b/Libs/DF/fw.lua index 12b6bb676..214d61c4c 100644 --- a/Libs/DF/fw.lua +++ b/Libs/DF/fw.lua @@ -1,6 +1,6 @@ -local dversion = 474 +local dversion = 475 local major, minor = "DetailsFramework-1.0", dversion local DF, oldminor = LibStub:NewLibrary(major, minor) @@ -4850,7 +4850,7 @@ local dispatch_error = function(context, errortext) DF:Msg( (context or "") .. " |cFFFF9900error|r: " .. (errortext or "")) end ---safe call an external func with payload and without telling who is calling +--call a function with payload, if the callback doesn't exists, quit silently function DF:QuickDispatch(func, ...) if (type(func) ~= "function") then return @@ -5918,15 +5918,15 @@ function DF:DebugVisibility(UIObject) print("Num Points:", numPoints > 0 and "|cFF00FF00" .. numPoints .. "|r" or "|cFFFF00000|r") end -local beenchmarkTime = 0 -local beenchmarkEnabled = false +local benchmarkTime = 0 +local bBenchmarkEnabled = false function _G.__benchmark(bNotPrintResult) - if (not beenchmarkEnabled) then - beenchmarkEnabled = true - beenchmarkTime = debugprofilestop() + if (not bBenchmarkEnabled) then + bBenchmarkEnabled = true + benchmarkTime = debugprofilestop() else - local elapsed = debugprofilestop() - beenchmarkTime - beenchmarkEnabled = false + local elapsed = debugprofilestop() - benchmarkTime + bBenchmarkEnabled = false if (bNotPrintResult) then return elapsed diff --git a/Libs/DF/pictureedit.lua b/Libs/DF/pictureedit.lua index dafa76a78..873132191 100644 --- a/Libs/DF/pictureedit.lua +++ b/Libs/DF/pictureedit.lua @@ -15,7 +15,7 @@ local CreateImageEditorFrame = function() background_frame:SetSize(790, 560) background_frame:SetClampedToScreen(true) - table.insert(UISpecialFrames, "DetailsFrameworkImageEditBackground") + tinsert(UISpecialFrames, "DetailsFrameworkImageEditBackground") background_frame:SetResizable(true) background_frame:SetMovable(true) diff --git a/Libs/DF/schedules.lua b/Libs/DF/schedules.lua index 1abac2331..a1a7aece0 100644 --- a/Libs/DF/schedules.lua +++ b/Libs/DF/schedules.lua @@ -1,16 +1,17 @@ -local DF = _G["DetailsFramework"] -if (not DF or not DetailsFrameworkCanLoad) then +local detailsFramework = _G["DetailsFramework"] +if (not detailsFramework or not DetailsFrameworkCanLoad) then return end local C_Timer = _G.C_Timer local unpack = table.unpack or _G.unpack +local GetTime = GetTime --make a namespace for schedules -DF.Schedules = DF.Schedules or {} +detailsFramework.Schedules = detailsFramework.Schedules or {} -DF.Schedules.AfterCombatSchedules = { +detailsFramework.Schedules.AfterCombatSchedules = { withId = {}, withoutId = {}, } @@ -24,21 +25,36 @@ DF.Schedules.AfterCombatSchedules = { ---@field SetName fun(object: timer, name: string) ---@field RunNextTick fun(callback: function) ---@field AfterCombat fun(callback:function, id:any, ...: any) +---@field CancelAfterCombat fun(id: any) +---@field CancelAllAfterCombat fun() +---@field IsAfterCombatScheduled fun(id: any): boolean +---@field LazyExecute fun(callback: function, payload: table?, maxIterations: number?, onEndCallback: function?): table + +---@class df_looper : table +---@field payload table +---@field callback function +---@field loopEndCallback function? +---@field checkPointCallback function? +---@field nextCheckPoint number +---@field lastLoop number +---@field currentLoop number +---@field Cancel fun() +---@field IsCancelled fun(): boolean local eventFrame = CreateFrame("frame") eventFrame:RegisterEvent("PLAYER_REGEN_ENABLED") eventFrame:SetScript("OnEvent", function(self, event) if (event == "PLAYER_REGEN_ENABLED") then - for _, schedule in ipairs(DF.Schedules.AfterCombatSchedules.withoutId) do + for _, schedule in ipairs(detailsFramework.Schedules.AfterCombatSchedules.withoutId) do xpcall(schedule.callback, geterrorhandler(), unpack(schedule.payload)) end - for _, schedule in pairs(DF.Schedules.AfterCombatSchedules.withId) do + for _, schedule in pairs(detailsFramework.Schedules.AfterCombatSchedules.withId) do xpcall(schedule.callback, geterrorhandler(), unpack(schedule.payload)) end - table.wipe(DF.Schedules.AfterCombatSchedules.withoutId) - table.wipe(DF.Schedules.AfterCombatSchedules.withId) + table.wipe(detailsFramework.Schedules.AfterCombatSchedules.withoutId) + table.wipe(detailsFramework.Schedules.AfterCombatSchedules.withId) end end) @@ -52,7 +68,7 @@ local triggerScheduledLoop = function(tickerObject) local result, errortext = pcall(callback, unpack(payload)) if (not result) then - DF:Msg("error on scheduler: ",tickerObject.path , tickerObject.name, errortext) + detailsFramework:Msg("error on scheduler: ",tickerObject.path , tickerObject.name, errortext) end local checkPointCallback = tickerObject.checkPointCallback @@ -90,8 +106,10 @@ end ---@param loopEndCallback function? ---@param checkPointCallback function? ---@vararg any -function DF.Schedules.NewLooper(time, callback, loopAmount, loopEndCallback, checkPointCallback, ...) +---@return df_looper +function detailsFramework.Schedules.NewLooper(time, callback, loopAmount, loopEndCallback, checkPointCallback, ...) local payload = {...} + ---@type df_looper local newLooper = C_Timer.NewTicker(time, triggerScheduledLoop, loopAmount) newLooper.payload = payload newLooper.callback = callback @@ -110,13 +128,13 @@ local triggerScheduledTick = function(tickerObject) local result, errortext = pcall(callback, unpack(payload)) if (not result) then - DF:Msg("error on scheduler: ",tickerObject.path , tickerObject.name, errortext) + detailsFramework:Msg("error on scheduler: ",tickerObject.path , tickerObject.name, errortext) end return result end --schedule to repeat a task with an interval of @time, keep ticking until cancelled -function DF.Schedules.NewTicker(time, callback, ...) +function detailsFramework.Schedules.NewTicker(time, callback, ...) local payload = {...} local newTicker = C_Timer.NewTicker(time, triggerScheduledTick) newTicker.payload = payload @@ -129,7 +147,7 @@ function DF.Schedules.NewTicker(time, callback, ...) end --schedule a task with an interval of @time -function DF.Schedules.NewTimer(time, callback, ...) +function detailsFramework.Schedules.NewTimer(time, callback, ...) local payload = {...} local newTimer = C_Timer.NewTimer(time, triggerScheduledTick) newTimer.payload = payload @@ -144,7 +162,7 @@ function DF.Schedules.NewTimer(time, callback, ...) end --cancel an ongoing ticker, the native call tickerObject:Cancel() also works with no problem -function DF.Schedules.Cancel(tickerObject) +function detailsFramework.Schedules.Cancel(tickerObject) --ignore if there's no ticker object if (tickerObject) then return tickerObject:Cancel() @@ -152,7 +170,7 @@ function DF.Schedules.Cancel(tickerObject) end --schedule a task to be executed when the player leaves combat -function DF.Schedules.AfterCombat(callback, id, ...) +function detailsFramework.Schedules.AfterCombat(callback, id, ...) local bInCombatLockdown = UnitAffectingCombat("player") or InCombatLockdown() if (not bInCombatLockdown) then @@ -163,28 +181,75 @@ function DF.Schedules.AfterCombat(callback, id, ...) local payload = {...} if (id) then - DF.Schedules.AfterCombatSchedules.withId[id] = { + detailsFramework.Schedules.AfterCombatSchedules.withId[id] = { callback = callback, payload = payload, id = id, } else - table.insert(DF.Schedules.AfterCombatSchedules.withoutId, { + table.insert(detailsFramework.Schedules.AfterCombatSchedules.withoutId, { callback = callback, payload = payload, }) end end +function detailsFramework.Schedules.CancelAfterCombat(id) + detailsFramework.Schedules.AfterCombatSchedules.withId[id] = nil +end + +function detailsFramework.Schedules.CancelAllAfterCombat() + table.wipe(detailsFramework.Schedules.AfterCombatSchedules.withId) + table.wipe(detailsFramework.Schedules.AfterCombatSchedules.withoutId) +end + +function detailsFramework.Schedules.IsAfterCombatScheduled(id) + return detailsFramework.Schedules.AfterCombatSchedules.withId[id] ~= nil +end + +---execute each frame a small portion of a big task +---the callback function receives a payload, the current iteration index and the max iterations +---if the callback function return true, the task is finished +---@param callback function +---@param payload table? +---@param maxIterations number? +---@param onEndCallback function? +function detailsFramework.Schedules.LazyExecute(callback, payload, maxIterations, onEndCallback) + assert(type(callback) == "function", "DetailsFramework.Schedules.LazyExecute() param #1 'callback' must be a function.") + maxIterations = maxIterations or 100000 + payload = payload or {} + local iterationIndex = 1 + + local function wrapFunc() + local bIsFinished = callback(payload, iterationIndex, maxIterations) + if (not bIsFinished) then + iterationIndex = iterationIndex + 1 + if (iterationIndex > maxIterations) then + detailsFramework:QuickDispatch(onEndCallback, payload) + return + end + C_Timer.After(0, function() wrapFunc() end) + else + detailsFramework:QuickDispatch(onEndCallback, payload) + return + end + end + + wrapFunc() + + return payload +end + + --schedule a task with an interval of @time without payload -function DF.Schedules.After(time, callback) +function detailsFramework.Schedules.After(time, callback) C_Timer.After(time, callback) end -function DF.Schedules.SetName(object, name) +function detailsFramework.Schedules.SetName(object, name) object.name = name end -function DF.Schedules.RunNextTick(callback) - return DF.Schedules.After(0, callback) +function detailsFramework.Schedules.RunNextTick(callback) + return detailsFramework.Schedules.After(0, callback) end \ No newline at end of file diff --git a/Libs/DF/scrollbox.lua b/Libs/DF/scrollbox.lua index 47c275b62..21bccb02c 100644 --- a/Libs/DF/scrollbox.lua +++ b/Libs/DF/scrollbox.lua @@ -414,10 +414,24 @@ function detailsFramework:CreateGridScrollBox(parent, name, refreshFunc, data, c return scrollBox end - +--Need to test this and check the "same_name_spells_add(value)" on the OnEnter function +--also need to make sure this can work with any data (global, class, spec) and aura type (buff, debuff) --aura scroll box ---default settings +---@class df_aurascrollbox_options : table +---@field line_height number? +---@field line_amount number? +---@field width number? +---@field height number? +---@field vertical_padding number? +---@field show_spell_tooltip boolean +---@field remove_icon_border boolean +---@field no_scroll boolean +---@field no_backdrop boolean +---@field backdrop_onenter number[]? +---@field backdrop_onleave number[]? +---@field font_size number? + local auraScrollDefaultSettings = { line_height = 18, line_amount = 18, @@ -433,8 +447,15 @@ local auraScrollDefaultSettings = { font_size = 12, } -function detailsFramework:CreateAuraScrollBox(parent, name, refreshFunc, data, onAuraRemoveCallback, options) +---@param parent frame +---@param name string? +---@param data table? --can be set later with :SetData() +---@param profile table? --can be set later with :SetProfile() +---@param onAuraRemoveCallback function? +---@param options df_aurascrollbox_options? +function detailsFramework:CreateAuraScrollBox(parent, name, data, profile, onAuraRemoveCallback, options) --hack the construction of the options table here, as the scrollbox is created much later + options = options or {} local scrollOptions = {} detailsFramework.OptionsFunctions.BuildOptionsTable(scrollOptions, auraScrollDefaultSettings, options) options = scrollOptions.options @@ -445,18 +466,18 @@ function detailsFramework:CreateAuraScrollBox(parent, name, refreshFunc, data, o local auraTable = data[index] if (auraTable) then local line = self:GetLine(i) - local spellId, spellName, spellIcon, lowerSpellName, flag = unpack(auraTable) + local spellId, spellName, spellIcon, lowerSpellName, bAddedBySpellName = unpack(auraTable) line.SpellID = spellId line.SpellName = spellName line.SpellNameLower = lowerSpellName line.SpellIcon = spellIcon - line.Flag = flag + line.Flag = bAddedBySpellName - if (flag) then + if (bAddedBySpellName) then line.name:SetText(spellName) else - line.name:SetText(spellName .. "(" .. spellId .. ")") + line.name:SetText(spellName .. " (" .. spellId .. ")") end line.icon:SetTexture(spellIcon) @@ -474,7 +495,7 @@ function detailsFramework:CreateAuraScrollBox(parent, name, refreshFunc, data, o GameTooltip:Hide() end - local onEnterAuraLine = function(line, capsule, value) + local onEnterAuraLine = function(line) if (options.show_spell_tooltip and line.SpellID and GetSpellInfo(line.SpellID)) then GameTooltip:SetOwner(line, "ANCHOR_CURSOR") GameTooltip:SetSpellByID(line.SpellID) @@ -483,28 +504,18 @@ function detailsFramework:CreateAuraScrollBox(parent, name, refreshFunc, data, o end line:SetBackdropColor(unpack(options.backdrop_onenter)) - local bAddedBySpellName = line.Flag - value = value or line.SpellID + local scrollBox = line:GetParent() - if (not bAddedBySpellName) then - GameCooltip:Preset(2) - GameCooltip:SetOwner(line, "left", "right", 2, 0) - GameCooltip:SetOption("TextSize", 10) - - local spellName, _, spellIcon = GetSpellInfo(value) - if (spellName) then - GameCooltip:AddLine(spellName .. " (" .. value .. ")") - GameCooltip:AddIcon(spellIcon, 1, 1, 14, 14, .1, .9, .1, .9) - end - GameCooltip:Show() + local bAddedBySpellName = line.Flag --the user entered the spell name to track the spell (and not a spellId) + local spellId = line.SpellID - else - local spellName = GetSpellInfo(value) + if (bAddedBySpellName) then --the user entered the spell name to track the spell + local spellName = GetSpellInfo(spellId) if (spellName) then - local spellsWithSameName = db.aura_cache_by_name[string.lower(spellName)] + local spellsWithSameName = scrollBox.profile.aura_cache_by_name[string.lower(spellName)] if (not spellsWithSameName) then - same_name_spells_add(value) - spellsWithSameName = db.aura_cache_by_name[string.lower(spellName)] + --same_name_spells_add(value) + --spellsWithSameName = scrollBox.profile.aura_cache_by_name[string.lower(spellName)] end if (spellsWithSameName) then @@ -523,18 +534,30 @@ function detailsFramework:CreateAuraScrollBox(parent, name, refreshFunc, data, o GameCooltip:Show() end end + else --the user entered the spellId to track the spell + GameCooltip:Preset(2) + GameCooltip:SetOwner(line, "left", "right", 2, 0) + GameCooltip:SetOption("TextSize", 10) + local spellName, _, spellIcon = GetSpellInfo(spellId) + if (spellName) then + GameCooltip:AddLine(spellName .. " (" .. spellId .. ")") + GameCooltip:AddIcon(spellIcon, 1, 1, 14, 14, .1, .9, .1, .9) + end + GameCooltip:Show() end end local onClickAuraRemoveButton = function(self) local spellId = self:GetParent().SpellID if (spellId and type(spellId) == "number") then - data[spellId] = nil - data["" .. (spellId or "")] = nil -- cleanup... - --button > line > scrollbox - self:GetParent():GetParent():DoRefresh() + local scrollBox = self:GetParent():GetParent() + scrollBox.data_original[spellId] = nil + scrollBox.data_original["" .. (spellId or "")] = nil -- cleanup... + + scrollBox:TransformAuraData() + scrollBox:Refresh() if (onAuraRemoveCallback) then --upvalue detailsFramework:QuickDispatch(onAuraRemoveCallback, spellId) @@ -581,19 +604,28 @@ function detailsFramework:CreateAuraScrollBox(parent, name, refreshFunc, data, o ---@class df_aurascrollbox : df_scrollbox ---@field RefreshMe fun(self:df_aurascrollbox) ---@field TransformAuraData fun(self:df_aurascrollbox) + ---@field SetProfile fun(self:df_aurascrollbox, profile:table) ---@field data_original table + ---@field profile table data = data or {} + if (not name) then + name = "DetailsFrameworkAuraScrollBox" .. math.random(1, 9999999) + end + local auraScrollBox = detailsFramework:CreateScrollBox(parent, name, refreshAuraLines, data, options.width, options.height, options.line_amount, options.line_height) detailsFramework:ReskinSlider(auraScrollBox) ---@cast auraScrollBox df_aurascrollbox + auraScrollBox.data_original = data + auraScrollBox.profile = profile or {} + function auraScrollBox:TransformAuraData() local newData = {} local added = {} - for spellId, bAddedBySpellName in pairs(self.data) do + for spellId, bAddedBySpellName in pairs(self.data_original) do local spellName, _, spellIcon = GetSpellInfo(spellId) if (spellName and not added[tonumber(spellId) or 0]) then local lowerSpellName = spellName:lower() @@ -606,7 +638,9 @@ function detailsFramework:CreateAuraScrollBox(parent, name, refreshFunc, data, o self.data = newData end - auraScrollBox.data_original = data + function auraScrollBox.SetProfile(self, profile) + self.profile = profile + end auraScrollBox.SetData = function(self, data) self.data_original = data @@ -619,4 +653,6 @@ function detailsFramework:CreateAuraScrollBox(parent, name, refreshFunc, data, o end auraScrollBox:SetData(data) + + return auraScrollBox end diff --git a/Libs/DF/spells.lua b/Libs/DF/spells.lua index 5e4f5a832..6b06fc897 100644 --- a/Libs/DF/spells.lua +++ b/Libs/DF/spells.lua @@ -1449,15 +1449,15 @@ function DF:GetSpellsForEncounterFromJournal (instanceEJID, encounterEJID) if (sectionInfo) then if (sectionInfo.spellID and type(sectionInfo.spellID) == "number" and sectionInfo.spellID ~= 0) then - table.insert(spellIDs, sectionInfo.spellID) + tinsert(spellIDs, sectionInfo.spellID) end local nextChild, nextSibling = sectionInfo.firstChildSectionID, sectionInfo.siblingSectionID if (nextSibling) then - table.insert(nextID, nextSibling) + tinsert(nextID, nextSibling) end if (nextChild) then - table.insert(nextID, nextChild) + tinsert(nextID, nextChild) end else break