diff --git a/api/api.lua b/api/api.lua index e4a94402..03a200c9 100644 --- a/api/api.lua +++ b/api/api.lua @@ -7,6 +7,62 @@ setfenv(1, pfUI:GetEnvironment()) gfind = string.gmatch or string.gfind mod = math.mod or mod +local loadstring_cache = {} +local loadstring_cache_size = 0 +local LOADSTRING_CACHE_MAX_SIZE = 1024 -- in practice such a max-size is more than enough we will rarely have to wipe the cache during the session +-- [ TryMemoizedFuncLoadstringForSpellCasts ] +-- Returns a function for the given string if the given msg is a string and a valid +-- lua-function can indeed be loaded from it, otherwise nil. +-- +-- If msg is an actual raw-lua-function is passed it is returned as-is without any processing. +-- This allows to use both raw functions and string-based scriptlets with the same API. +-- +-- If msg is any other type (number, boolean, ...) then nil is returned. +-- +-- The function is memoized for better performance on repeated calls with the same string. +-- +-- The cache is automatically cleared when it exceeds a certain size (1024 entries) to prevent abuse. +-- +-- In practice it is very unlikely to have more than a few dozen different scriptlets during a session, +-- so cache-nuking should be a very rare event. +function pfUI.api.TryMemoizedFuncLoadstringForSpellCasts(msg) + local msgType = type(msg) + if msgType == "function" then --10 order + return msg -- return as-is + end + + if msgType ~= "string" then --20 order + return nil -- numbers and the like are not valid for loadstring + end + + local result = loadstring_cache[msg] + if result ~= nil then -- already seen? + return result + end + + result = loadstring(msg) + if result == nil then -- invalid code + return nil + end + + if loadstring_cache_size > LOADSTRING_CACHE_MAX_SIZE then --90 just in case + loadstring_cache = {} + loadstring_cache_size = 0 + end + + loadstring_cache[msg] = result -- order + loadstring_cache_size = loadstring_cache_size + 1 -- order + + return result + + --10 special cases for when using /pfcast with funcs or scriptlets if a raw func is passed we dont need to + -- to loadstring it all + -- + --20 if the user passes a direct integer-spell-id or something else, we should not attempt to loadstring it + -- + --90 prevent abuse by capping the cache size +end + -- [ strsplit ] -- Splits a string using a delimiter. -- 'delimiter' [string] characters that will be interpreted as delimiter @@ -27,7 +83,7 @@ end -- return: [boolean] result of the check. function pfUI.api.isempty(tbl) if not tbl then return true end - for k, v in pairs(tbl) do + for _ in pairs(tbl) do return false end return true @@ -80,8 +136,12 @@ function pfUI.api.RunOOC(func) if not frame then frame = CreateFrame("Frame") frame:SetScript("OnUpdate", function() - if InCombatLockdown and InCombatLockdown() then return end - for key, func in pairs(queue) do func(); queue[key] = nil end + if InCombatLockdown and InCombatLockdown() then return end + + for k, f in pairs(queue) do + f() + queue[k] = nil + end end) end diff --git a/modules/focus.lua b/modules/focus.lua index a2e24582..2857f4cb 100644 --- a/modules/focus.lua +++ b/modules/focus.lua @@ -77,7 +77,7 @@ function SlashCmdList.PFCASTFOCUS(msg) end end - local func = loadstring(msg or "") + local func = pfUI.api.TryMemoizedFuncLoadstringForSpellCasts(msg) if func then func() else @@ -95,7 +95,7 @@ function SlashCmdList.PFCASTFOCUS(msg) end SLASH_PFSWAPFOCUS1, SLASH_PFSWAPFOCUS2 = '/swapfocus', '/pfswapfocus' -function SlashCmdList.PFSWAPFOCUS(msg) +function SlashCmdList.PFSWAPFOCUS() if not pfUI.uf or not pfUI.uf.focus then return end local oldunit = UnitExists("target") and strlower(UnitName("target")) diff --git a/modules/mouseover.lua b/modules/mouseover.lua index 918a3976..c33a6b5b 100644 --- a/modules/mouseover.lua +++ b/modules/mouseover.lua @@ -34,7 +34,7 @@ pfUI:RegisterModule("mouseover", "vanilla", function () _G.SLASH_PFCAST1, _G.SLASH_PFCAST2 = "/pfcast", "/pfmouse" function SlashCmdList.PFCAST(msg) local restore_target = true - local func = loadstring(msg or "") + local func = pfUI.api.TryMemoizedFuncLoadstringForSpellCasts(msg) local unit = "mouseover" if not UnitExists(unit) then diff --git a/modules/superwow.lua b/modules/superwow.lua index af30210d..7d71c32c 100644 --- a/modules/superwow.lua +++ b/modules/superwow.lua @@ -36,7 +36,7 @@ pfUI:RegisterModule("superwow", "vanilla", function () QueueFunction(function() local pfCombatText_AddMessage = _G.CombatText_AddMessage _G.CombatText_AddMessage = function(message, a, b, c, d, e, f) - local match, _, hex = string.find(message, ".+ %[(0x.+)%]") + local _, _, hex = string.find(message, ".+ %[(0x.+)%]") if hex and UnitName(hex) then message = string.gsub(message, hex, UnitName(hex)) end @@ -49,7 +49,7 @@ pfUI:RegisterModule("superwow", "vanilla", function () -- Add native mouseover support if SUPERWOW_VERSION and pfUI.uf and pfUI.uf.mouseover then _G.SlashCmdList.PFCAST = function(msg) - local func = loadstring(msg or "") + local func = pfUI.api.TryMemoizedFuncLoadstringForSpellCasts(msg) local unit = "mouseover" if not UnitExists(unit) then @@ -90,7 +90,7 @@ pfUI:RegisterModule("superwow", "vanilla", function () local config = pfUI.uf.player.config local mana = config.defcolor == "0" and config.manacolor or pfUI_config.unitframes.manacolor local r, g, b, a = pfUI.api.strsplit(",", mana) - local rawborder, default_border = GetBorderSize("unitframes") + local _, default_border = GetBorderSize("unitframes") local _, class = UnitClass("player") local width = config.pwidth ~= "-1" and config.pwidth or config.width @@ -194,6 +194,34 @@ pfUI:RegisterModule("superwow", "vanilla", function () return guid end + -- optimize the builtin /castfocus and /pfcastfocus slash commands when possible by using superwow + -- to cast directly to the focus-target-unit-guid thus skipping the need for complex target-swapping + local legacy_cast_focus = SlashCmdList.PFCASTFOCUS + function SlashCmdList.PFCASTFOCUS(msg) + local func = pfUI.api.TryMemoizedFuncLoadstringForSpellCasts(msg) --10 caution + if func then --10 caution + legacy_cast_focus(func) + return + end + + if not pfUI.uf.focus.label then --50 + UIErrorsFrame:AddMessage(SPELL_FAILED_BAD_TARGETS, 1, 0, 0) + return + end + + CastSpellByName(msg, pfUI.uf.focus.label) --90 + + --10 if the spellcast is in fact raw lua-function we cant cast by guid we have to fallback + -- to the legacy method which does support func-scriptlets + -- + --50 the superwow-approach requires just the unit-guid-label it doesnt care about the focus.id + -- which is typically dud anyway + -- + --90 by using superwow to cast directly to a unit-guid we completely sidestep the complex mechanics + -- of target-swapping altogether which is the entire point here for vastly improved ui-performance + -- when spamming spells + end + -- extend the builtin /focus slash command local legacyfocus = SlashCmdList.PFFOCUS function SlashCmdList.PFFOCUS(msg) @@ -229,7 +257,7 @@ pfUI:RegisterModule("superwow", "vanilla", function () superdebuff:RegisterEvent("UNIT_CASTEVENT") superdebuff:SetScript("OnEvent", function() -- variable assignments - local caster, target, event, spell, duration = arg1, arg2, arg3, arg4 + local caster, target, event, spell = arg1, arg2, arg3 -- skip other caster and empty target events local _, guid = UnitExists("player") @@ -242,7 +270,7 @@ pfUI:RegisterModule("superwow", "vanilla", function () local unitlevel = UnitLevel(target) local effect, rank = SpellInfo(spell) local duration = libdebuff:GetDuration(effect, rank) - local caster = "player" + caster = "player" -- add effect to current debuff data libdebuff:AddEffect(unit, unitlevel, effect, duration, caster) @@ -278,11 +306,11 @@ pfUI:RegisterModule("superwow", "vanilla", function () if arg3 == "START" or arg3 == "CAST" or arg3 == "CHANNEL" then -- human readable argument list local guid = arg1 - local target = arg2 + -- local target = arg2 local event_type = arg3 local spell_id = arg4 local timer = arg5 - local start = GetTime() + -- local start = GetTime() -- get spell info from spell id local spell, icon, _