diff --git a/CHANGELOG.md b/CHANGELOG.md index 9c60c84..6ab3abb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,10 @@ Changes in OEVG/ShaguTweaks-ClassicAPI, newest first. Entries summarize released Based on [Dusk/ShaguTweaks-ClassicAPI](https://octowow.st/git/Dusk/ShaguTweaks-ClassicAPI) at `4391564` (2026-09-08). Earlier history is available upstream. +## 3.8.4 — 2026-11-06 — Action-bar proc highlights + +- Add **Proc & Reactive Spell Highlights** (Action Bar, off): event-driven pale light-blue glows for supported spells and macros, with native pulsing animation where available. Central proc alerts remain with dedicated addons such as Proc Doc. + ## 3.8.3 — 2026-11-05 — Personal Resource Display - Replace **Standalone Player Frame** with **Personal Resource Display**: independent, movable and resizable copies of player health and power, leaving the original player frame intact. Existing health/power positions migrate. diff --git a/Docs/PERFORMANCE_TEST_383.md b/Docs/PERFORMANCE_TEST_383.md new file mode 100644 index 0000000..f8f5c61 --- /dev/null +++ b/Docs/PERFORMANCE_TEST_383.md @@ -0,0 +1,119 @@ +# 3.8.3 profiling: current bottlenecks and theme cost + +Only the player can run these checks in the game. No performance gain has been +measured by the automated tests. The live game folder was not modified. + +## Focused theme comparison + +First measure the current configuration, then repeat with both recoloring options +off, Golden UI only, and Darkened UI only. Reload after each setting change. +Use the same addons, character, location, FPS limit and GentleGC configuration. + +For every mode, warm up for two minutes and open the same windows once before +sampling. Then take three 60-second idle samples and three 60-second samples +opening/closing the same map, bags, mail and auction windows the same number of +times. Record CPU ms/s, memory before/after, and any hitches. Measure initial +window creation separately from repeated openings if only the first opening hitches. + +| Mode | Scenario | CPU ms/s, three runs | Memory before/after | Hitches | +| --- | --- | --- | --- | --- | +| Both themes off | Idle / windows | | | | +| Golden UI only | Idle / windows | | | | +| Darkened UI only | Idle / windows | | | | + +The reported 6.2 MB versus 6.9 MB is approximately 0.7 MB extra retained Lua +memory. These two snapshots do not measure allocation rate, CPU cost or a leak. +After several identical window-opening cycles and natural GC cycles, check +whether memory settles or keeps rising. Do not manually collect during CPU runs. + +Also profile the current build in combat/forms/healing as below. Theme comparisons +isolate one option's cost; they do not establish gains over an older release. + +## First verify behavior + +- Real Health Numbers: take damage, heal, spend/regenerate power, switch targets + and summon/dismiss a pet. Druid forms should show the appropriate resource and + update the separate mana display. Check Big Health and Personal Resource Display layouts. +- Swing/Wand Timer: attack, stop, move out of range, change targets, and change + weapons. Dual-wield bars and wand Shoot should reset and stop correctly. +- Druid Maul or Warrior Heroic Strike/Cleave: queue and cancel, checking colors. + Hunter Auto Shot: check the last-half-second red warning if a Hunter is available. +- Hold Ctrl+Shift+Alt during a swing: preview labels remain stable, countdowns + resume on release, and drag/reset/saved positions still work after reload. +- Energy & Mana Tick: test a fresh login, Cat/Bear/caster transitions, spending + mana, channel completion/cancellation, a potion and a drain if practical. + Verify the spark after the first observed gain, its five-second cast-cost + sweep, and resizing with Big Health and Personal Resource Display bars. +- Macros: test bare/explicit/conditional #showtooltip, item macros, castsequence, + chosen icons and any legacy --showtooltip or /pfcast display hints you use. + Edit/delete a custom hint and reload; check for stale overrides. Check missing + /equip, /use, focus and attack aliases without replacing another addon's aliases. +- Shift-compare rings, trinkets and weapons from bags/vendors/chat links; check + one empty paired slot, enchanted/random-suffix items, Shift release, hover + changes and tooltip closure. Compare with other tooltip addons enabled. + +## Find the remaining bottlenecks + +Profile the current build first. The purpose is to identify expensive workloads +and then narrow them down to modules/functions, not to prove the recent changes +made the addon faster. Older builds are not required for this investigation. + +Keep the same character, location, target, addon list, UI settings and frame-rate +limit. Enable Real Health Numbers and the applicable Swing/Wand Timer in both. +Warm up for two minutes after loading; do not include loading or option changes +in a measured run. Keep GentleGC and other client mods configured identically. + +Enable profiling, then reload: + +``` +/console scriptProfile 1 +/reload +``` + +Start each sample with: + +``` +/script ResetCPUUsage(); STPerfStart=GetTime() +``` + +Perform the chosen scenario for about 60 seconds, then print average addon Lua +CPU milliseconds per elapsed second: + +``` +/script UpdateAddOnCPUUsage(); DEFAULT_CHAT_FRAME:AddMessage(string.format("ShaguTweaks: %.3f ms/s over %.1f s",GetAddOnCPUUsage("ShaguTweaks")/(GetTime()-STPerfStart),GetTime()-STPerfStart)) +``` + +Scenarios: (1) idle with no windows open; (2) steady melee/wand attacks; +(3) repeated damage/healing and power changes, including druid form changes; +(4) repeated map, inventory, mail and auction-window interactions. Record which +windows were used and how many times. Repeat each three times on the current +build and compare medians. Repeating comparable activity matters more than +identical duration alone. + +These addon totals are the first triage step, not module/function attribution. +For workloads with meaningful cost, follow with focused function CPU profiling +and, where practical, repeat the same scenario with one relevant option disabled +(reload after changing options). That separates module cost from unrelated +activity and tells us where a code change might be worthwhile. Respect the +decision to preserve the working theme recoloring behavior. + +At the same point after warm-up and after a fixed test session, sample Lua memory: + +``` +/script UpdateAddOnMemoryUsage(); DEFAULT_CHAT_FRAME:AddMessage(string.format("ShaguTweaks: %.1f KiB",GetAddOnMemoryUsage("ShaguTweaks"))) +``` + +Memory includes uncollected objects; compare at equivalent GentleGC collection +points. Do not force collections during CPU samples. Native textures/DLL memory +and total garbage production are not represented by this single memory number. + +Send the build, scenario, enabled options, three CPU results and memory readings. +Those results guide the next targeted investigation. Lower addon Lua +CPU does not by itself prove higher FPS: native timer work moves out of Lua, +profiling adds overhead, and other addons/client rendering may dominate. Do not +use GetFrameCPUUsage for this comparison. Afterward, turn profiling off: + +``` +/console scriptProfile 0 +/reload +``` diff --git a/README.md b/README.md index de3eabb..c315822 100644 --- a/README.md +++ b/README.md @@ -9,12 +9,14 @@ This is an English-only customization of [Dusk/ShaguTweaks-ClassicAPI](https://o > This fork requires ClassicAPI and targets **Turtle WoW-like server/client environments**. -**Version 3.8.3.** What changed in each update: see the [changelog](CHANGELOG.md). +**Version 3.8.4.** What changed in each update: see the [changelog](CHANGELOG.md). > **Upgrading to 3.7:** the modules of the separate *ShaguTweaks Extras ClassicAPI* addon are now built into ShaguTweaks. **Disable or delete `ShaguTweaks-extras`** after updating, otherwise its older copies replace the built-in ones. Your on/off settings carry over. Raid Frames, Bag Item Click, Bag Search Bar and Auction Alt-Buy were not merged. See [Docs/EXTRAS_MERGE.md](Docs/EXTRAS_MERGE.md) for where each module went and why some were left out. For druids, Personal Resource Display also shows a small mana bar beneath the power bar while shapeshifted. It stays centered at 60% of the power bar width and mirrors native alternate mana, including when the original resources are hidden. Energy & Mana Tick also adds its mana-regeneration spark. +**Proc & Reactive Spell Highlights** (Action Bar, off): pale light-blue action-button glows for supported procs and reactive spells, including macros. Uses ClassicAPI glow events and native animation where available, with a static fallback. Central artwork is left to Proc Doc or other alert addons. Requires ClassicAPI spell-overlay support; older supported DLLs leave this feature inactive. + ## Screenshots Personal Resource Display, independently positioned player name and movable action bars with Golden UI. diff --git a/ShaguTweaks.toc b/ShaguTweaks.toc index 5aae787..c3f7142 100644 --- a/ShaguTweaks.toc +++ b/ShaguTweaks.toc @@ -2,7 +2,7 @@ ## Title: |cffffcc00Shagu|cffffffffTweaks |cffcccccc[ClassicAPI] ## Author: Shagu ## Notes: A collection of tweaks and enhancements to the Default UI -## Version: 3.8.3 +## Version: 3.8.4 ## Dependencies: !!!ClassicAPI ## SavedVariables: ShaguTweaks_player_cache, ShaguTweaks_social_cache ## SavedVariablesPerCharacter: ShaguTweaks_config, ShaguTweaks_cache @@ -42,6 +42,7 @@ mods\minimap-clean.lua mods\minimap-zoom.lua mods\minimap-tracking.lua mods\actionbar-range-color.lua +mods\actionbar-proc-highlights.lua mods\free-slots.lua mods\xpbar-text.lua mods\sell-junk.lua diff --git a/mods/actionbar-proc-highlights.lua b/mods/actionbar-proc-highlights.lua new file mode 100644 index 0000000..d23fb90 --- /dev/null +++ b/mods/actionbar-proc-highlights.lua @@ -0,0 +1,104 @@ +local _G = ShaguTweaks.GetGlobalEnv() +local T = ShaguTweaks.T +local API = ShaguTweaks.API +local module = ShaguTweaks:register({ + title = T["Proc & Reactive Spell Highlights"], + description = T["Highlights action buttons for active procs and reactive spells using ClassicAPI. Supports spells and macros; central proc artwork is left to other addons."], + expansions = { ["vanilla"] = true }, category = T["Action Bar"], enabled = nil, +}) +module.enable = function(self) + if self.states then return end + local query = _G.C_SpellActivationOverlay and _G.C_SpellActivationOverlay.IsSpellOverlayed or _G.IsSpellOverlayed + local valid = _G.C_EventUtils and _G.C_EventUtils.IsEventValid + if type(query) ~= "function" or not API.GetActionInfo or not valid + or not valid("SPELL_ACTIVATION_OVERLAY_GLOW_SHOW") + or not valid("SPELL_ACTIVATION_OVERLAY_GLOW_HIDE") then return end + local states, byButton = {}, {} + self.states = states + local function Paint(state, active) + if active then + if not state.glow:IsShown() then + state.glow:Show() + if state.pulse then state.pulse:Play() end + end + else + if state.pulse then state.pulse:Stop() end + state.glow:Hide() + end + end + local function Sync(state) + local spell + if state.button:IsVisible() then + local slot = ActionButton_GetPagedID(state.button) + if slot then + local kind, id = API.GetActionInfo(slot) + if kind == "spell" then spell = id + elseif kind == "macro" and _G.GetMacroSpell then + local name, rank, spellID = _G.GetMacroSpell(id) + spell = spellID + end + end + end + state.spell = spell + Paint(state, spell and query(spell)) + end + for _, prefix in ipairs({"ActionButton", "BonusActionButton", "MultiBarBottomLeftButton", + "MultiBarBottomRightButton", "MultiBarRightButton", "MultiBarLeftButton"}) do + for i = 1, 12 do + local button = _G[prefix .. i] + if button then + local glow = CreateFrame("Frame", nil, button) + glow:SetAllPoints(button) + glow:SetFrameLevel(button:GetFrameLevel() + 5) + glow:SetBackdrop({ edgeFile = "Interface\\Tooltips\\UI-Tooltip-Border", edgeSize = 12 }) + glow:SetBackdropBorderColor(.7, .9, 1, 1) + -- Additive light stands out against both dark and gold button rims. + -- Use the client's existing action-button glow artwork. + local halo = glow:CreateTexture(nil, "OVERLAY") + halo:SetTexture("Interface\\Buttons\\UI-ActionButton-Border") + halo:SetBlendMode("ADD") + halo:SetVertexColor(.55, .85, 1, 1) + halo:SetPoint("TOPLEFT", button, "TOPLEFT", -8, 8) + halo:SetPoint("BOTTOMRIGHT", button, "BOTTOMRIGHT", 8, -8) + glow:EnableMouse(false) + glow:Hide() + local state = { button = button, glow = glow, halo = halo } + if type(glow.CreateAnimationGroup) == "function" then + local pulse = glow:CreateAnimationGroup() + local fade = pulse:CreateAnimation("Alpha") + if type(fade.SetFromAlpha) == "function" and type(fade.SetToAlpha) == "function" then + pulse:SetLooping("BOUNCE") + fade:SetFromAlpha(1); fade:SetToAlpha(.7) + fade:SetDuration(.4); fade:SetSmoothing("IN_OUT") + state.pulse = pulse + end + end + table.insert(states, state); byButton[button] = state + local function Changed() Sync(state) end + ShaguTweaks.HookScript(button, "OnShow", Changed) + ShaguTweaks.HookScript(button, "OnHide", Changed) + Sync(state) + end + end + end + ShaguTweaks.hooksecurefunc("ActionButton_Update", function() + local state = byButton[this] + if state then Sync(state) end + end) + local events = CreateFrame("Frame") + self.events = events + for _, name in ipairs({"SPELL_ACTIVATION_OVERLAY_GLOW_SHOW", "SPELL_ACTIVATION_OVERLAY_GLOW_HIDE", + "PLAYER_ENTERING_WORLD", "ACTIONBAR_SLOT_CHANGED", "ACTIONBAR_PAGE_CHANGED", + "UPDATE_BONUS_ACTIONBAR", "UPDATE_MACROS", "ACTIONBAR_UPDATE_STATE", + "PLAYER_TARGET_CHANGED", "MODIFIER_STATE_CHANGED", "SPELLS_CHANGED"}) do + if valid(name) then events:RegisterEvent(name) end + end + events:SetScript("OnEvent", function() + local proc = event == "SPELL_ACTIVATION_OVERLAY_GLOW_SHOW" or event == "SPELL_ACTIVATION_OVERLAY_GLOW_HIDE" + for _, state in ipairs(states) do + if proc then + if state.spell == arg1 then Paint(state, state.button:IsVisible() and query(arg1)) end + else Sync(state) end + end + end) +end diff --git a/tests/README.md b/tests/README.md index c0c725f..f4f51a7 100644 --- a/tests/README.md +++ b/tests/README.md @@ -9,7 +9,7 @@ sh tests/run_all.sh ../tools/lua5.0/lua5.0 The interpreter defaults to `lua5.0` if omitted. The runner discovers all `tests/test_*.lua` files, reports every failure, prints a final count, and exits unsuccessfully if any suite fails. It is developer tooling, is not loaded by the -addon, and has no in-game overhead. The current suite contains 26 test files. +addon, and has no in-game overhead. The current suite contains 27 test files. Individual checks can also be run from the addon root: @@ -107,3 +107,5 @@ An independently hidden comparison recovers without a new source event. The unchanged visible case still avoids native rebuilds. Changed links/equipment rebuild; a non-item or persistently hidden source removes comparisons after the deferred lifecycle check. + +Proc-highlight checks cover capability detection, pre-existing procs, direct spells/macros, paging, hidden buttons, event-only updates and native-animation/static paths. diff --git a/tests/test_proc_highlights.lua b/tests/test_proc_highlights.lua new file mode 100644 index 0000000..18c8836 --- /dev/null +++ b/tests/test_proc_highlights.lua @@ -0,0 +1,65 @@ +local root=(arg[1] or '.')..'/' +local function Check(supported,animated) + local module,frames,updates=nil,{},0 + local methods={} + local function noop() end + local function new() + return setmetatable({shown=true,scripts={},events={}},{__index=function(t,k) + if k=='CreateAnimationGroup' and not animated then return nil end + return methods[k] or (string.find(k,'^[A-Z]') and noop) + end}) + end + function methods:Show() self.shown=true end + function methods:Hide() self.shown=false end + function methods:IsShown() return self.shown end + function methods:IsVisible() return self.shown end + function methods:GetFrameLevel() return 1 end + function methods:SetScript(k,v) self.scripts[k]=v end + function methods:RegisterEvent(k) self.events[k]=true end + function methods:CreateAnimationGroup() return new() end + function methods:CreateAnimation() return new() end + function methods:CreateTexture() return new() end + function methods:SetTexture(path) self.path=path end + function methods:SetBlendMode(mode) self.blend=mode end + function methods:SetToAlpha(value) self.minimum=value end + function methods:Play() self.playing=true end + function methods:Stop() self.playing=false end + CreateFrame=function() local f=new();table.insert(frames,f);return f end + ActionButton1=new();BonusActionButton1=new();MultiBarBottomLeftButton1=new() + local slots={[ActionButton1]=1,[BonusActionButton1]=73,[MultiBarBottomLeftButton1]=61} + local actions={[1]={'spell',10},[73]={'macro',2},[61]={'item',30}} + local active={[10]=true,[20]=true} + ActionButton_GetPagedID=function(b) return slots[b] end + GetMacroSpell=function(id) return 'Spell','Rank',20 end + C_SpellActivationOverlay={IsSpellOverlayed=function(id) return active[id] end} + C_EventUtils={IsEventValid=function() return supported end} + ShaguTweaks={T=setmetatable({},{__index=function(t,k) return k end}), + GetGlobalEnv=function() return _G end,API={GetActionInfo=function(slot) + updates=updates+1;local a=actions[slot];if a then return unpack(a) end + end}} + function ShaguTweaks:register(m) module=m;return m end + function ShaguTweaks.HookScript(f,k,fn) f.scripts[k]=fn end + local refresh + function ShaguTweaks.hooksecurefunc(name,fn) assert(name=='ActionButton_Update');refresh=fn end + dofile(root..'mods/actionbar-proc-highlights.lua');module:enable() + if not supported then assert(not module.states and table.getn(frames)==0);return end + assert(table.getn(module.states)==3) + local a,b,c=module.states[1],module.states[2],module.states[3] + assert(a.glow.shown and b.glow.shown and not c.glow.shown) + assert(a.halo.path=='Interface\\Buttons\\UI-ActionButton-Border' and a.halo.blend=='ADD') + assert(not module.events.scripts.OnUpdate and not a.glow.scripts.OnUpdate) + if animated then assert(a.pulse.playing) else assert(not a.pulse) end + local count=table.getn(frames);module:enable();assert(table.getn(frames)==count) + local function emit(e,id) event,arg1=e,id;module.events.scripts.OnEvent() end + local before=updates;active[20]=false;emit('SPELL_ACTIVATION_OVERLAY_GLOW_HIDE',20) + assert(not b.glow.shown and a.glow.shown and updates==before) + actions[13]={'spell',20};slots[ActionButton1]=13;emit('ACTIONBAR_PAGE_CHANGED') + assert(not a.glow.shown and a.spell==20) + active[20]=true;emit('SPELL_ACTIVATION_OVERLAY_GLOW_SHOW',20);assert(a.glow.shown and b.glow.shown) + ActionButton1:Hide();a.button.scripts.OnHide();assert(not a.glow.shown and not a.spell) + ActionButton1:Show();a.button.scripts.OnShow();assert(a.glow.shown) + actions[13]={'item',1};this=ActionButton1;refresh();this=nil;assert(not a.glow.shown) + GetMacroSpell=function() return nil end;emit('UPDATE_MACROS');assert(not b.glow.shown) +end +Check(false,true);Check(true,true);Check(true,false) +print('Proc highlights: capability guard, existing procs, ranks/macros, event-only changes, paging, visibility, native animation/static fallback and idempotence passed')