Skip to content
Open
66 changes: 63 additions & 3 deletions api/api.lua
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -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

Expand Down
4 changes: 2 additions & 2 deletions modules/focus.lua
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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"))
Expand Down
2 changes: 1 addition & 1 deletion modules/mouseover.lua
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
42 changes: 35 additions & 7 deletions modules/superwow.lua
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -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

Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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")
Expand All @@ -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)
Expand Down Expand Up @@ -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, _
Expand Down