diff --git a/Conditionals.lua b/Conditionals.lua index 94e2a30..70f5fc2 100644 --- a/Conditionals.lua +++ b/Conditionals.lua @@ -461,6 +461,66 @@ local stat_checks = { shadow_res = function() local _, val = UnitResistance("player", 6); return val end } +-- ============================================================================ +-- PERFORMANCE: Specialized comparison functions to avoid closure allocation +-- These functions replace common patterns like: +-- return Or(t, function(v) return (i == tonumber(v)) end) +-- with: +-- return OrEqualsNumber(t, i) +-- ============================================================================ + +-- Or where any value equals target number (after tonumber conversion) +local function OrEqualsNumber(t, target) + if type(t) ~= "table" then + return tonumber(t) == target + end + local k, v = next(t) + while k do + if tonumber(v) == target then return true end + k, v = next(t, k) + end + return false +end + +-- Or where any value equals target string (after string.lower conversion) +local function OrEqualsStringLower(t, target) + if type(t) ~= "table" then + return string.lower(t) == target + end + local k, v = next(t) + while k do + if string.lower(v) == target then return true end + k, v = next(t, k) + end + return false +end + +-- Or where any value does NOT equal target number (for negated conditionals) +local function OrNotEqualsNumber(t, target) + if type(t) ~= "table" then + return tonumber(t) ~= target + end + local k, v = next(t) + while k do + if tonumber(v) ~= target then return true end + k, v = next(t, k) + end + return false +end + +-- And where ALL values do NOT equal target number (for negated conditionals with AND logic) +local function AndNotEqualsNumber(t, target) + if type(t) ~= "table" then + return tonumber(t) ~= target + end + local k, v = next(t) + while k do + if tonumber(v) == target then return false end + k, v = next(t, k) + end + return true +end + -- PERFORMANCE: Avoid creating wrapper tables for single values local function And(t, func) if type(func) ~= "function" then return false end @@ -3076,9 +3136,8 @@ CleveRoids.Keywords = { stance = function(conditionals) local i = CleveRoids.GetCurrentShapeshiftIndex() - return Or(conditionals.stance, function (v) - return (i == tonumber(v)) - end) + -- PERFORMANCE: Use specialized function to avoid closure allocation + return OrEqualsNumber(conditionals.stance, i) end, nostance = function(conditionals) @@ -3087,9 +3146,8 @@ CleveRoids.Keywords = { if type(forbiddenStances) ~= "table" then return i == 0 end - return NegatedMulti(forbiddenStances, function (v) - return (i ~= tonumber(v)) - end, conditionals, "nostance") + -- PERFORMANCE: Use specialized function to avoid closure allocation + return AndNotEqualsNumber(forbiddenStances, i) end, noform = function(conditionals) @@ -3098,16 +3156,14 @@ CleveRoids.Keywords = { if type(forbiddenForms) ~= "table" then return i == 0 end - return NegatedMulti(forbiddenForms, function (v) - return (i ~= tonumber(v)) - end, conditionals, "noform") + -- PERFORMANCE: Use specialized function to avoid closure allocation + return AndNotEqualsNumber(forbiddenForms, i) end, form = function(conditionals) local i = CleveRoids.GetCurrentShapeshiftIndex() - return Or(conditionals.form, function (v) - return (i == tonumber(v)) - end) + -- PERFORMANCE: Use specialized function to avoid closure allocation + return OrEqualsNumber(conditionals.form, i) end, mod = function(conditionals) @@ -3141,7 +3197,12 @@ CleveRoids.Keywords = { end, conditionals, "combat") else -- Otherwise, this is a bare [combat]. The value might be 'true' or a spell name. - -- In either case, it should safely default to checking the player. + -- PERFORMANCE: Use event-driven cache for player combat state + local cached = CleveRoids._cachedPlayerInCombat + if cached ~= nil then + return cached + end + -- Fallback if cache not yet initialized return UnitAffectingCombat("player") end end, @@ -3158,6 +3219,12 @@ CleveRoids.Keywords = { end, conditionals, "nocombat") else -- Otherwise, this is a bare [nocombat]. Default to checking the player. + -- PERFORMANCE: Use event-driven cache for player combat state + local cached = CleveRoids._cachedPlayerInCombat + if cached ~= nil then + return not cached + end + -- Fallback if cache not yet initialized return not UnitAffectingCombat("player") end end, diff --git a/Core.lua b/Core.lua index 4efe6f1..8705cc9 100644 --- a/Core.lua +++ b/Core.lua @@ -3392,6 +3392,24 @@ function CleveRoids.OnUpdate(self) CleveRoids.isActionUpdateQueued = false end end + -- PERFORMANCE: Check for modifier key state changes (no events for these in vanilla WoW) + -- Only queue update if modifier state actually changed - avoids full TestForAllActiveActions + local altDown = IsAltKeyDown() and true or false + local shiftDown = IsShiftKeyDown() and true or false + local ctrlDown = IsControlKeyDown() and true or false + + if altDown ~= CleveRoids._lastAltDown or + shiftDown ~= CleveRoids._lastShiftDown or + ctrlDown ~= CleveRoids._lastCtrlDown then + CleveRoids._lastAltDown = altDown + CleveRoids._lastShiftDown = shiftDown + CleveRoids._lastCtrlDown = ctrlDown + -- Modifier changed - queue update in event-driven mode, or just mark for realtime + if CleveRoidMacros.realtime == 0 then + CleveRoids.isActionUpdateQueued = true + end + end + -- Check the saved variable to decide which update mode to use. if CleveRoidMacros.realtime == 1 then -- Realtime Mode: Force an update on every throttled tick for maximum responsiveness. @@ -4008,8 +4026,10 @@ CleveRoids.Frame:RegisterEvent("UNIT_PET") -- == STATE CHANGE EVENT REGISTRATION (for performance) == CleveRoids.Frame:RegisterEvent("PLAYER_TARGET_CHANGED") CleveRoids.Frame:RegisterEvent("PLAYER_FOCUS_CHANGED") -- For focus addons -CleveRoids.Frame:RegisterEvent("PLAYER_ENTER_COMBAT") -CleveRoids.Frame:RegisterEvent("PLAYER_LEAVE_COMBAT") +CleveRoids.Frame:RegisterEvent("PLAYER_ENTER_COMBAT") -- Auto-attack started +CleveRoids.Frame:RegisterEvent("PLAYER_LEAVE_COMBAT") -- Auto-attack stopped +CleveRoids.Frame:RegisterEvent("PLAYER_REGEN_DISABLED") -- Entered actual combat (has threat) +CleveRoids.Frame:RegisterEvent("PLAYER_REGEN_ENABLED") -- Left actual combat (no threat) CleveRoids.Frame:RegisterEvent("UPDATE_SHAPESHIFT_FORM") CleveRoids.Frame:RegisterEvent("SPELL_UPDATE_COOLDOWN") CleveRoids.Frame:RegisterEvent("UNIT_AURA") @@ -4060,6 +4080,9 @@ function CleveRoids.Frame:PLAYER_LOGIN() CleveRoids.IndexPetSpells() CleveRoids.initializationTimer = GetTime() + 1.5 + -- PERFORMANCE: Initialize event-driven cache states + CleveRoids._cachedPlayerInCombat = UnitAffectingCombat("player") and true or false + -- Schedule delayed WDB warmup (loads items into client cache via tooltip scan) -- This ensures GetItemInfo() works for all inventory items after a WDB clear CleveRoids.wdbWarmupTime = GetTime() + 3.0 -- 3 second delay after login @@ -4448,6 +4471,7 @@ function CleveRoids.Frame:SPELLCAST_INTERRUPTED() end end +-- PLAYER_ENTER_COMBAT/PLAYER_LEAVE_COMBAT are for AUTO-ATTACK state (not actual combat) function CleveRoids.Frame:PLAYER_ENTER_COMBAT() CleveRoids.CurrentSpell.autoAttack = true CleveRoids.CurrentSpell.autoAttackLock = false @@ -4483,6 +4507,24 @@ function CleveRoids.Frame:PLAYER_LEAVE_COMBAT() end end +-- PLAYER_REGEN_DISABLED/PLAYER_REGEN_ENABLED are for ACTUAL combat state (threat/aggro) +-- These events track what UnitAffectingCombat("player") reports +function CleveRoids.Frame:PLAYER_REGEN_DISABLED() + -- PERFORMANCE: Cache actual combat state for event-driven [combat]/[nocombat] conditionals + CleveRoids._cachedPlayerInCombat = true + if CleveRoidMacros.realtime == 0 then + CleveRoids.QueueActionUpdate() + end +end + +function CleveRoids.Frame:PLAYER_REGEN_ENABLED() + -- PERFORMANCE: Cache actual combat state for event-driven [combat]/[nocombat] conditionals + CleveRoids._cachedPlayerInCombat = false + if CleveRoidMacros.realtime == 0 then + CleveRoids.QueueActionUpdate() + end +end + function CleveRoids.Frame:PLAYER_TARGET_CHANGED() CleveRoids.CurrentSpell.autoAttack = false CleveRoids.CurrentSpell.autoAttackLock = false diff --git a/Init.lua b/Init.lua index 3c90014..599ebf5 100644 --- a/Init.lua +++ b/Init.lua @@ -147,6 +147,9 @@ CleveRoids.lastCastSpell = nil -- Macro execution control CleveRoids.stopMacroFlag = false +-- PERFORMANCE: Event-driven cached state (updated on events, not polled) +CleveRoids._cachedPlayerInCombat = nil -- Updated on PLAYER_REGEN_DISABLED, PLAYER_REGEN_ENABLED + CleveRoids.ignoreKeywords = { action = true, ignoretooltip = true,