Skip to content

Commit

Permalink
perf: add caching around IsUsableSpell
Browse files Browse the repository at this point in the history
  • Loading branch information
ascott18 committed Aug 15, 2024
1 parent e71adce commit 5ec41a8
Show file tree
Hide file tree
Showing 10 changed files with 306 additions and 88 deletions.
17 changes: 15 additions & 2 deletions Components/Core/Common/Cooldowns.lua
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,8 @@ if C_Spell.GetSpellCooldown then
local cached = CachedCooldowns[spell]
if cached then
if cached == false then return end
if (cached.duration - (TMW.time - cached.startTime)) <= 0 then
local duration = cached.duration
if duration ~= 0 and (duration - (TMW.time - cached.startTime)) <= 0 then
-- Cooldown has elapsed. Discard this cache entry
else
return cached
Expand All @@ -53,7 +54,8 @@ else
local cached = CachedCooldowns[spell]
if cached then
if cached == false then return end
if (cached.duration - (TMW.time - cached.startTime)) <= 0 then
local duration = cached.duration
if duration ~= 0 and (duration - (TMW.time - cached.startTime)) <= 0 then
-- Cooldown has elapsed. Discard this cache entry
else
return cached
Expand Down Expand Up @@ -102,12 +104,23 @@ else
end
end

Cooldowns:RegisterEvent("SPELLS_CHANGED")
Cooldowns:RegisterEvent("SPELL_UPDATE_COOLDOWN")
Cooldowns:RegisterEvent("SPELL_UPDATE_CHARGES")
Cooldowns:SetScript("OnEvent", function(self, event, action, inRange, checksRange)
if event == "SPELL_UPDATE_COOLDOWN" then
wipe(CachedCooldowns)
TMW:Fire("TMW_SPELL_UPDATE_COOLDOWN")

elseif event == "SPELL_UPDATE_CHARGES" then
wipe(CachedCharges)
TMW:Fire("TMW_SPELL_UPDATE_CHARGES")

elseif event == "SPELLS_CHANGED" then
-- Spells may have been learned/unlearned (e.g. pvp talents activating/deactivating)
wipe(CachedCooldowns)
wipe(CachedCharges)
TMW:Fire("TMW_SPELL_UPDATE_COOLDOWN")
TMW:Fire("TMW_SPELL_UPDATE_CHARGES")
end
end)
6 changes: 4 additions & 2 deletions Components/Core/Common/SpellRange.lua
Original file line number Diff line number Diff line change
Expand Up @@ -44,8 +44,6 @@ if not C_ActionBar or not C_ActionBar.EnableActionRangeCheck then
return
end

SpellRange.IsSpellInRange = C_Spell.IsSpellInRange

SpellRange:RegisterEvent("ACTION_RANGE_CHECK_UPDATE")

local SpellsToActions = Actions.SpellsToActions
Expand Down Expand Up @@ -109,11 +107,15 @@ TMW:RegisterCallback("TMW_ACTIONS_UPDATED", function()
end
end
end

TMW:Fire("TMW_SPELL_UPDATE_RANGE")
end)

SpellRange:SetScript("OnEvent", function(self, event, action, inRange, checksRange)
if event == "ACTION_RANGE_CHECK_UPDATE" then
CachedRange[action] = { inRange, checksRange }
-- We don't bother with a payload for this event because range check updates
-- are quite uncommon in combat, so it just isn't worth it.
TMW:Fire("TMW_SPELL_UPDATE_RANGE")
end
end)
Expand Down
193 changes: 193 additions & 0 deletions Components/Core/Common/SpellUsable.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,193 @@
-- --------------------
-- TellMeWhen
-- Originally by Nephthys of Hyjal <[email protected]>

-- Other contributions by:
-- Sweetmms of Blackrock, Oozebull of Twisting Nether, Oodyboo of Mug'thol,
-- Banjankri of Blackrock, Predeter of Proudmoore, Xenyr of Aszune

-- Currently maintained by
-- Cybeloras of Aerie Peak
-- --------------------

if not TMW then return end


local TMW = TMW
local L = TMW.L
local print = TMW.print
local strlowerCache = TMW.strlowerCache

local select, wipe, setmetatable
= select, wipe, setmetatable

local GetSpellName = TMW.GetSpellName
local IsUsableSpell = C_Spell.IsSpellUsable or _G.IsUsableSpell
local IsUsableAction = IsUsableAction

TMW.COMMON.SpellUsable = CreateFrame("Frame")
local SpellUsable = TMW.COMMON.SpellUsable
local Actions = TMW.COMMON.Actions

-- todo: can we feature detect hasPreciseActionEvents?
local hasPreciseActionEvents = select(4, GetBuildInfo()) >= 110000

if hasPreciseActionEvents then
SpellUsable:RegisterEvent("ACTION_USABLE_CHANGED")
else
SpellUsable:RegisterEvent("ACTIONBAR_UPDATE_USABLE")
end
SpellUsable:RegisterEvent("SPELLS_CHANGED")
SpellUsable:RegisterEvent("ACTIONBAR_SLOT_CHANGED")
SpellUsable:RegisterEvent("SPELL_UPDATE_USABLE")

local SpellsToActions = Actions.SpellsToActions
local ActionToSpells = Actions.ActionToSpells
local EnabledSpells = {}
local EnabledActions = {}

local CachedActions = {}
SpellUsable.CachedActions = CachedActions
local CachedNonActions = {}
SpellUsable.CachedNonActions = CachedNonActions

function SpellUsable.IsUsableSpell(spell)

local actions = SpellsToActions[spell]
if actions then
-- If the spell is an action, use actionbar data.
-- On clients with ACTION_USABLE_CHANGED, we can get precise, surgical events for actions,
-- and even on clients without ACTION_USABLE_CHANGED, ACTIONBAR_UPDATE_USABLE is
-- more well-behaved than SPELL_UPDATE_USABLE, firing far fewer superfluous updates.
for i = 1, #actions do
local action = actions[i]
local usable = CachedActions[action]
if usable then
return usable.usable, usable.noMana
end
end

-- Cache miss, but the spell does map to an action, so populate the cache.
-- This path happens if `CachedRange` has been wiped and we haven't yet seen an event.
local action = actions[1]
local usable, noMana = IsUsableAction(action)
CachedActions[action] = { usable = usable, noMana = noMana }
return usable, noMana
else
-- Spell is not a known actionbar action. Have to use the plain spell APIs.
local usable = CachedNonActions[spell]
if usable then
return usable.usable, usable.noMana
end

local usable, noMana = IsUsableSpell(spell)
CachedNonActions[spell] = { usable = usable, noMana = noMana }
return usable, noMana
end
end

SpellUsable:SetScript("OnEvent", function(self, event, payload)
-- Lookup table of all the spells that were changed
-- that is sent as the payload of TMW_SPELL_UPDATE_USABLE.
local spells = {}

if event == "SPELLS_CHANGED" then
-- SPELLS_CHANGED covers occurrences like pvp talents becoming available/unavailable
-- when pvp combat is engaged/disengaged.
-- It effects both actionbar and non-actionbar spells.
-- Since its fairly infrequent, just easier to wipe all caches.

wipe(CachedActions)
wipe(CachedNonActions)
TMW:Fire("TMW_SPELL_UPDATE_USABLE")
return

elseif event == "ACTIONBAR_SLOT_CHANGED" then
-- When actions are dragged around the actionbar,
-- or when a spell becomes a different spell in combat (e.g. void eruption/void bolt)
local action = payload
local data = CachedActions[data]

if not data then
-- Nobody was listening to this action if it isn't cached,
-- so we don't care that it just changed.
return
end

local usable, noMana = IsUsableAction(action)
if data.usable ~= usable or data.noMana ~= noMana then
data.usable = usable
data.noMana = noMana

local actionSpells = ActionToSpells[action]
if actionSpells then
for spell in pairs(actionSpells) do
spells[spell] = true
end
end
end

elseif event == "ACTION_USABLE_CHANGED" then
-- Precise action updates. Added in WoW 11.0.
for _, payloadSpell in pairs(payload) do
-- payloadSpell is { usable: boolean, noMana: boolean }
local action = payloadSpell.slot
CachedActions[action] = payloadSpell

local actionSpells = ActionToSpells[action]
if actionSpells then
for spell in pairs(actionSpells) do
spells[spell] = true
end
end
end

elseif event == "ACTIONBAR_UPDATE_USABLE" then
-- Imprecise action updates. Some action changed, but we don't know what.
-- NOTE: this event is only registered if ACTION_USABLE_CHANGED isn't available.
for action, data in pairs(CachedActions) do
local usable, noMana = IsUsableAction(action)
if data.usable ~= usable or data.noMana ~= noMana then
data.usable = usable
data.noMana = noMana

local actionSpells = ActionToSpells[action]
if actionSpells then
for spell in pairs(actionSpells) do
spells[spell] = true
end
end
end
end

elseif event == "SPELL_UPDATE_USABLE" then
-- Some spell might have changed, but we don't know which.
-- Check all the ones that have been asked for to see which spells (if any) it was.
for spell, data in pairs(CachedNonActions) do
local usable, noMana = IsUsableSpell(spell)
if data.usable ~= usable or data.noMana ~= noMana then
data.usable = usable
data.noMana = noMana

spells[spell] = true
end
end
-- todo: do we still have to listen to UNIT_POWER_FREQUENT?
-- In all my testing, it doesn't seem so - SPELL_UPDATE_USABLE is sufficient for all spells.
end

if next(spells) then
TMW:Fire("TMW_SPELL_UPDATE_USABLE", spells)
end
end)

-- Legacy backwards-compat for anything that might be using this
function TMW.SpellHasNoMana(spell)
local _, noMana = SpellUsable.IsUsableSpell(spell)
return noMana
end

TMW:RegisterCallback("TMW_GLOBAL_UPDATE", function()
wipe(CachedActions)
wipe(CachedNonActions)
end)
4 changes: 4 additions & 0 deletions Components/Core/Conditions/Categories/PlayerAttributes.lua
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,10 @@ ConditionCategory:RegisterCondition(3, "MOUNTED", {
IsMounted = IsMounted,
},
funcstr = [[BOOLCHECK( IsMounted() )]],
events = function(ConditionObject, c)
return
ConditionObject:GenerateNormalEventString("TMW_UNIT_AURA_PLAYER")
end,
})
ConditionCategory:RegisterCondition(4, "SWIMMING", {
text = L["CONDITIONPANEL_SWIMMING"],
Expand Down
33 changes: 14 additions & 19 deletions Components/Core/Conditions/Categories/Spells.lua
Original file line number Diff line number Diff line change
Expand Up @@ -101,8 +101,7 @@ ConditionCategory:RegisterCondition(1, "SPELLCD", {
funcstr = [[CooldownDuration(c.OwnSpells.First, c.Checked) c.Operator c.Level]],
events = function(ConditionObject, c)
return
ConditionObject:GenerateNormalEventString("SPELL_UPDATE_COOLDOWN"),
ConditionObject:GenerateNormalEventString("SPELL_UPDATE_USABLE")
ConditionObject:GenerateNormalEventString("TMW_SPELL_UPDATE_COOLDOWN")
end,
anticipate = function(c)
local str = [[
Expand Down Expand Up @@ -137,8 +136,7 @@ ConditionCategory:RegisterCondition(2, "SPELLCDCOMP", {
funcstr = [[CooldownDuration(c.OwnSpells.First, c.Checked) c.Operator CooldownDuration(c.OwnSpells2.First, c.Checked2)]],
events = function(ConditionObject, c)
return
ConditionObject:GenerateNormalEventString("SPELL_UPDATE_COOLDOWN"),
ConditionObject:GenerateNormalEventString("SPELL_UPDATE_USABLE")
ConditionObject:GenerateNormalEventString("TMW_SPELL_UPDATE_COOLDOWN")
end,
anticipate = function(c)
local str = [[
Expand Down Expand Up @@ -188,9 +186,7 @@ if TMW.isRetail then
funcstr = [[(GetSpellCharges(c.OwnSpells.First) or GetSpellCount(c.OwnSpells.First)) c.Operator c.Level]],
events = function(ConditionObject, c)
return
ConditionObject:GenerateNormalEventString("SPELL_UPDATE_COOLDOWN"),
ConditionObject:GenerateNormalEventString("SPELL_UPDATE_USABLE"),
ConditionObject:GenerateNormalEventString("SPELL_UPDATE_CHARGES")
ConditionObject:GenerateNormalEventString("TMW_SPELL_UPDATE_CHARGES")
end,
})
ConditionCategory:RegisterCondition(2.6, "SPELLCHARGETIME", {
Expand Down Expand Up @@ -219,9 +215,7 @@ if TMW.isRetail then
funcstr = [[RechargeDuration(c.OwnSpells.First) c.Operator c.Level]],
events = function(ConditionObject, c)
return
ConditionObject:GenerateNormalEventString("SPELL_UPDATE_COOLDOWN"),
ConditionObject:GenerateNormalEventString("SPELL_UPDATE_USABLE"),
ConditionObject:GenerateNormalEventString("SPELL_UPDATE_CHARGES")
ConditionObject:GenerateNormalEventString("TMW_SPELL_UPDATE_CHARGES")
end,
anticipate = [[
local _, _, start, duration = GetSpellCharges(c.OwnSpells.First)
Expand Down Expand Up @@ -303,11 +297,11 @@ ConditionCategory:RegisterCondition(2.8, "LASTCAST", {

ConditionCategory:RegisterSpacer(2.9)

local IsUsableSpell = C_Spell.IsSpellUsable or _G.IsUsableSpell
local IsUsableSpell = TMW.COMMON.SpellUsable.IsUsableSpell
function Env.ReactiveHelper(NameFirst, Checked)
local usable, nomana = IsUsableSpell(NameFirst)
local usable, noMana = IsUsableSpell(NameFirst)
if Checked then
return usable or nomana
return usable or noMana
else
return usable
end
Expand Down Expand Up @@ -360,7 +354,7 @@ ConditionCategory:RegisterCondition(3, "REACTIVE", {
funcstr = [[BOOLCHECK( ReactiveHelper(c.OwnSpells.First, c.Checked) )]],
events = function(ConditionObject, c)
return
ConditionObject:GenerateNormalEventString("SPELL_UPDATE_USABLE")
ConditionObject:GenerateNormalEventString("TMW_SPELL_UPDATE_USABLE")
end,
})
ConditionCategory:RegisterCondition(3.1, "CURRENTSPELL", {
Expand Down Expand Up @@ -473,12 +467,14 @@ ConditionCategory:RegisterCondition(4, "MANAUSABLE", {
tcoords = CNDT.COMMON.standardtcoords,
funcstr = [[not BOOLCHECK( SpellHasNoMana(c.OwnSpells.First) )]],
Env = {
SpellHasNoMana = TMW.SpellHasNoMana
SpellHasNoMana = function(spell)
local _, noMana = IsUsableSpell(spell)
return noMana
end
},
events = function(ConditionObject, c)
return
ConditionObject:GenerateNormalEventString("SPELL_UPDATE_USABLE"),
ConditionObject:GenerateNormalEventString("UNIT_POWER_FREQUENT", "player")
ConditionObject:GenerateNormalEventString("TMW_SPELL_UPDATE_USABLE")
end,
})
ConditionCategory:RegisterCondition(4.5, "SPELLCOST", {
Expand Down Expand Up @@ -543,8 +539,7 @@ ConditionCategory:RegisterCondition(6, "GCD", {
funcstr = [[BOOLCHECK( (TMW.GCD > 0 and TMW.GCD < 1.7) )]],
events = function(ConditionObject, c)
return
ConditionObject:GenerateNormalEventString("SPELL_UPDATE_COOLDOWN"),
ConditionObject:GenerateNormalEventString("SPELL_UPDATE_USABLE")
ConditionObject:GenerateNormalEventString("SPELL_UPDATE_COOLDOWN")
end,
anticipate = [[
local start, duration = GetSpellCooldown(TMW.GCDSpell)
Expand Down
10 changes: 0 additions & 10 deletions Components/Core/Utils.lua
Original file line number Diff line number Diff line change
Expand Up @@ -1353,16 +1353,6 @@ end
-- WoW API Helpers
---------------------------------

function TMW.SpellHasNoMana(spell)
-- TODO: in warlords, you can't determine spell costs anymore. Thanks, blizzard!
-- This function used to get the spell cost, and determine usability from that,
-- but we can't do that anymore. It was a more reliable method because IsUsableSpell
-- was broken for some abilities (like Jab)

local _, nomana = IsUsableSpell(spell)
return nomana
end

function TMW.GetRuneCooldownDuration()
-- Round to a precision of 3 decimal points for comparison with returns from GetSpellCooldown
local _, duration = GetRuneCooldown(1)
Expand Down
Loading

0 comments on commit 5ec41a8

Please sign in to comment.