Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 5 additions & 8 deletions Core/MultiBot.lua
Original file line number Diff line number Diff line change
Expand Up @@ -1198,15 +1198,12 @@ function MultiBot.GM_DetectFromSystem(msg)
end
--if MultiBot.RaidPool then MultiBot.RaidPool("player") end
if MultiBot.RaidPool then
-- petit helper timer si absent
C_Timer_After = C_Timer_After or function(sec, func)
local f, t = CreateFrame("Frame"), 0
f:SetScript("OnUpdate", function(_, dt)
t = t + dt
if t >= sec then f:SetScript("OnUpdate", nil); func() end
end)
local timerAfter = MultiBot.TimerAfter or _G.TimerAfter
if type(timerAfter) == "function" then
timerAfter(0.2, function() MultiBot.RaidPool("player") end)
else
MultiBot.RaidPool("player")
end
C_Timer_After(0.2, function() MultiBot.RaidPool("player") end)
end
return true
end
Expand Down
14 changes: 13 additions & 1 deletion Core/MultiBotAsync.lua
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ if type(sharedTimerAfter) ~= "function" then
return C_Timer.After(waitTime, callback)
end

-- M11 ownership: keep this fallback OnUpdate local to the scheduler wrapper only.
-- Reason: legacy compatibility when C_Timer.After is unavailable.
local timerFrame = CreateFrame("Frame")
local elapsed = 0

Expand All @@ -43,4 +45,14 @@ if type(sharedTimerAfter) ~= "function" then
end

MultiBot.TimerAfter = sharedTimerAfter
_G.TimerAfter = sharedTimerAfter
_G.TimerAfter = sharedTimerAfter

-- M11 scheduler contract:
-- TimerAfter/NextTick are the only delay APIs that should be used outside this file.
function MultiBot.NextTick(callback)
if type(callback) ~= "function" then
return nil
end

return sharedTimerAfter(0, callback)
end
30 changes: 17 additions & 13 deletions Core/MultiBotEngine.lua
Original file line number Diff line number Diff line change
Expand Up @@ -881,19 +881,11 @@ MultiBot._clickBlockerQueue = _mbEnsureRuntimeTable("_clickBlockerQueue")
local function _mbQueueClickBlockerUpdate(f)
if(not f or not f.clickBlocker) then return end
MultiBot._clickBlockerQueue[f] = true
if MultiBot._clickBlockerFlushQueued then return end
MultiBot._clickBlockerFlushQueued = true

if(not MultiBot._clickBlockerTicker) then
MultiBot._clickBlockerTicker = CreateFrame("Frame", nil, UIParent)
MultiBot._clickBlockerTicker.running = false
end

local t = MultiBot._clickBlockerTicker
if(t.running) then return end

t.running = true
t:SetScript("OnUpdate", function(self)
self:SetScript("OnUpdate", nil)
self.running = false
local function flushQueue()
MultiBot._clickBlockerFlushQueued = false

local queue = MultiBot._clickBlockerQueue
MultiBot._clickBlockerQueue = {}
Expand All @@ -902,7 +894,19 @@ local function _mbQueueClickBlockerUpdate(f)
MultiBot.UpdateClickBlocker(frame)
end
end
end)
end

local nextTick = MultiBot.NextTick
if type(nextTick) == "function" then
nextTick(flushQueue)
else
local timerAfter = MultiBot.TimerAfter or _G.TimerAfter
if type(timerAfter) == "function" then
timerAfter(0, flushQueue)
else
flushQueue()
end
end
end

-- Demande une mise à jour pour le frame et tous ses parents MultiBot.newFrame (cascade).
Expand Down
2 changes: 2 additions & 0 deletions Core/MultiBotHandler.lua
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
-- TIMER --
-- M11 ownership: keep this OnUpdate local.
-- Reason: automation core hot path (invite/talent/stats/sort) depends on frame-level cadence.

function MultiBot.HandleOnUpdate(pElapsed)
if(MultiBot.auto.invite) then MultiBot.timer.invite.elapsed = MultiBot.timer.invite.elapsed + pElapsed end
Expand Down
2 changes: 2 additions & 0 deletions Core/MultiBotThrottle.lua
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ function MultiBot.Throttle_Init()
local tokens = BURST
local queue = {}

-- M11 ownership: keep this OnUpdate local.
-- Reason: token-bucket anti-spam requires frame-level refill/flush behavior.
-- Frame de vidage
local f = CreateFrame("Frame")
f:Show()
Expand Down
28 changes: 16 additions & 12 deletions Features/MultiBotRaidus.lua
Original file line number Diff line number Diff line change
Expand Up @@ -485,23 +485,17 @@ local RAIDUS_FEEDBACK_ANCHOR = "TOPLEFT"
local RAIDUS_FEEDBACK_OFFSET_X = 26
local RAIDUS_FEEDBACK_OFFSET_Y = 14

local raidusFeedbackTimer = CreateFrame("Frame")
raidusFeedbackTimer:Hide()
raidusFeedbackTimer.remaining = 0
raidusFeedbackTimer:SetScript("OnUpdate", function(self, elapsed)
self.remaining = self.remaining - elapsed
if self.remaining > 0 then
local raidusFeedbackToken = 0
local function clearRaidusDropFeedback(token)
if token ~= raidusFeedbackToken then
return
end

local feedbackText = MultiBot.raidus and MultiBot.raidus.texts and MultiBot.raidus.texts["DropFeedback"]
if feedbackText then
feedbackText:SetText("")
feedbackText:Hide()
end

self:Hide()
end)
end

local function showRaidusDropFeedback(message)
if not MultiBot.raidus then
Expand All @@ -520,8 +514,16 @@ local function showRaidusDropFeedback(message)
MultiBot.raidus.addText("DropFeedback", "|cffb8b8b8" .. text .. "|r", RAIDUS_FEEDBACK_ANCHOR, RAIDUS_FEEDBACK_OFFSET_X, RAIDUS_FEEDBACK_OFFSET_Y, 15)
end

raidusFeedbackTimer.remaining = RAIDUS_FEEDBACK_DURATION
raidusFeedbackTimer:Show()
raidusFeedbackToken = raidusFeedbackToken + 1
local token = raidusFeedbackToken
local timerAfter = MultiBot.TimerAfter or _G.TimerAfter
if type(timerAfter) == "function" then
timerAfter(RAIDUS_FEEDBACK_DURATION, function()
clearRaidusDropFeedback(token)
end)
else
clearRaidusDropFeedback(token)
end
end

local function playRaidusDropPulse(slotFrame)
Expand All @@ -538,6 +540,8 @@ local function playRaidusDropPulse(slotFrame)

driver.elapsed = 0
driver:Show()
-- M11 ownership: keep this OnUpdate local.
-- Reason: pulse scale animation is frame-driven and intentionally not timer-based.
driver:SetScript("OnUpdate", function(self, elapsed)
self.elapsed = self.elapsed + elapsed
local progress = self.elapsed / RAIDUS_DROP_ANIM_DURATION
Expand Down
10 changes: 5 additions & 5 deletions ROADMAP.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,8 +37,8 @@
- Remaining UI literal cleanup is completed for Milestone 9 scope (GM shortcut labels, Raidus group title formatting, shared UI defaults for page/title labels) while preserving technical/protocol identifiers (e.g. internal "Inventory" button/event keys).
- **Milestone 10 (Data model and table lifecycle hardening):** Completed.
- Runtime/profile stores are centralized via `MultiBot.Store` with explicit `get*` vs `ensure*` semantics and read-path hardening validated in tracker `docs/milestone10-data-model-lifecycle-tracker.md`.
- **Milestone 11 (Scheduler/timers convergence):** Planned.
- Route scattered timers/OnUpdate loops to a constrained scheduler strategy (AceTimer where appropriate, existing loops retained when safer).
- **Milestone 11 (Scheduler/timers convergence):** Completed.
- Scattered one-shot timers were converged to `MultiBot.TimerAfter` / `MultiBot.NextTick`, while hot-path `OnUpdate` loops were intentionally retained and documented.
- **Milestone 12 (Observability, diagnostics and perf guardrails):** Planned.
- Add lightweight debug/perf toggles and migration diagnostics to validate behavior without chat spam.
- **Milestone 13 (Release hardening and deprecation window close):** Planned.
Expand Down Expand Up @@ -143,9 +143,9 @@
- Store normalization helpers are reused across modules.

### D4. Milestone 11 — Scheduler/timers convergence
1. Inventory `OnUpdate`, elapsed counters, delayed whisper/refresh loops.
2. Migrate safe candidates to a centralized scheduler strategy (AceTimer where applicable).
3. Keep ultra-hot paths local if conversion adds risk/regression.
1. Inventory `OnUpdate`, elapsed counters, delayed whisper/refresh loops.
2. Migrate safe candidates to a centralized scheduler strategy (`MultiBot.TimerAfter` / `MultiBot.NextTick`). ✅
3. Keep ultra-hot paths local if conversion adds risk/regression.

**Exit criteria**
- Timer responsibilities are documented and mapped to one owner per feature.
Expand Down
3 changes: 2 additions & 1 deletion TODO.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,5 @@ TODO
* faire en sorte que les menus déroulants de la main barre se ferment quand on on ouvre un autre
* revoir le fichiers UI/MultiBotTalent, la partie des glyphes et des talents car il y'a eu des modifications dans le fichiers .conf de multibot
* pourquoi les glyphes sont longues a afficher?
* implémenter RTI
* implémenter RTI
* trouver un moyen de charger tous les skins des pets hunter
17 changes: 10 additions & 7 deletions UI/MultiBotHunterQuickFrame.lua
Original file line number Diff line number Diff line change
Expand Up @@ -876,16 +876,19 @@ function HunterQuick:EnsureSearchFrame()
preview:SetUnit("none")
preview:ClearModel()
preview:Show()
preview:SetScript("OnUpdate", function(model)
model:SetScript("OnUpdate", nil)
model:SetModelScale(previewScale)
model:SetFacing(previewFacing)
MultiBot.TimerAfter(0, function()
if not preview:IsShown() or currentEntry ~= entryId then
return
end

preview:SetModelScale(previewScale)
preview:SetFacing(previewFacing)

local displayNumber = tonumber(displayId)
if displayNumber and displayNumber > 0 and type(model.SetDisplayInfo) == "function" then
model:SetDisplayInfo(displayNumber)
if displayNumber and displayNumber > 0 and type(preview.SetDisplayInfo) == "function" then
preview:SetDisplayInfo(displayNumber)
else
model:SetCreature(entryId)
preview:SetCreature(entryId)
end
end)
end
Expand Down
2 changes: 2 additions & 0 deletions UI/MultiBotMainUI.lua
Original file line number Diff line number Diff line change
Expand Up @@ -628,6 +628,8 @@ function MultiBot.InitializeMainUI(tMultiBar)
end)

if autoHideState.multiBar and autoHideState.multiBar.HookScript then
-- M11 ownership: keep this OnUpdate local for autohide.
-- Reason: hover/mouse interaction needs near real-time polling to preserve UX.
autoHideState.multiBar:HookScript("OnUpdate", function(_, elapsed)
autoHideState.elapsed = autoHideState.elapsed + elapsed
if autoHideState.elapsed < MAINBAR_AUTOHIDE_UPDATE_INTERVAL then
Expand Down
2 changes: 2 additions & 0 deletions UI/MultiBotMinimap.lua
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,8 @@ do
highlight:SetAllPoints(button)

button:SetScript("OnDragStart", function(self)
-- M11 ownership: keep this OnUpdate local.
-- Reason: angle update must follow cursor every frame while dragging.
self:SetScript("OnUpdate", saveAngleFromCursor)
end)

Expand Down
8 changes: 4 additions & 4 deletions UI/MultiBotQuestsMenu.lua
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,7 @@ local function sendIncomplete(method)
MultiBot._lastIncWhisperBot = bot
ensureRuntimeTable("_awaitingQuestsIncompleted")[bot] = true
ensureRuntimeTable("BotQuestsIncompleted")[bot] = {}
resetQuestResultFrame(frame, MultiBot.L("tips.quests.incomplist") or "")
resetQuestResultFrame(frame, MultiBot.L("tips.quests.incomplist") or "")
MultiBot.ActionToTarget("quests incompleted", bot)
frame:Show()
MultiBot.TimerAfter(0.5, function()
Expand All @@ -143,7 +143,7 @@ local function sendIncomplete(method)
end

clearTableInPlace(ensureRuntimeTable("BotQuestsIncompleted"))
resetQuestResultFrame(frame, MultiBot.L("tips.quests.incomplist") or "")
resetQuestResultFrame(frame, MultiBot.L("tips.quests.incomplist") or "")
MultiBot.ActionToGroup("quests incompleted")
frame:Show()
end
Expand All @@ -166,7 +166,7 @@ local function sendCompleted(method)
MultiBot._lastCompWhisperBot = bot
ensureRuntimeTable("_awaitingQuestsCompleted")[bot] = true
ensureRuntimeTable("BotQuestsCompleted")[bot] = {}
resetQuestResultFrame(frame, MultiBot.L("tips.quests.complist") or "")
resetQuestResultFrame(frame, MultiBot.L("tips.quests.complist") or "")
MultiBot.ActionToTarget("quests completed", bot)
frame:Show()
MultiBot.TimerAfter(0.5, function()
Expand All @@ -178,7 +178,7 @@ local function sendCompleted(method)
end

clearTableInPlace(ensureRuntimeTable("BotQuestsCompleted"))
resetQuestResultFrame(frame, MultiBot.L("tips.quests.complist") or "")
resetQuestResultFrame(frame, MultiBot.L("tips.quests.complist") or "")
MultiBot.ActionToGroup("quests completed")
frame:Show()
end
Expand Down
19 changes: 5 additions & 14 deletions UI/MultiBotSpecUI.lua
Original file line number Diff line number Diff line change
Expand Up @@ -282,20 +282,11 @@ function Spec:RequestList(bot, wrapper)
-- 1) on demande d'abord la spé courante
SendChatMessage("talents", "WHISPER", nil, bot)
-- 2) on attend ~0.2s puis on enchaîne sur la liste
local t = self._timerFrame or CreateFrame("Frame")
self._timerFrame = t
t.elapsed = 0
t:SetScript("OnUpdate", function(timerFrame, delta)
timerFrame.elapsed = timerFrame.elapsed + delta
if timerFrame.elapsed >= 0.2 then
-- si on est toujours sur le même bot, on demande la liste
if Spec.pending and Spec.pending.bot == bot then
SendChatMessage("talents spec list", "WHISPER", nil, bot)
-- print("|cffffff00[SpecDEBUG]|r Message talents spec list envoyé!!!!!!!!!!!!!!!!!")
end
-- on désactive l’OnUpdate et reset le timer
timerFrame:SetScript("OnUpdate", nil)
timerFrame.elapsed = 0
MultiBot.TimerAfter(0.2, function()
-- si on est toujours sur le même bot, on demande la liste
if Spec.pending and Spec.pending.bot == bot then
SendChatMessage("talents spec list", "WHISPER", nil, bot)
Comment on lines +285 to +288
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Debounce the delayed spec-list request

Spec:RequestList now schedules a new MultiBot.TimerAfter(0.2, ...) on every call without canceling/replacing the previous pending timer. If the user triggers RequestList multiple times quickly for the same bot (e.g., rapid clicks), each callback still satisfies Spec.pending.bot == bot and sends duplicate "talents spec list" whispers, which can spam commands and race UI state updates. The old single-frame timer logic implicitly coalesced these requests.

Useful? React with 👍 / 👎.

-- print("|cffffff00[SpecDEBUG]|r Message talents spec list envoyé!!!!!!!!!!!!!!!!!")
end
end)
end
Expand Down
2 changes: 1 addition & 1 deletion UI/MultiBotStats.lua
Original file line number Diff line number Diff line change
Expand Up @@ -185,7 +185,7 @@ function MultiBot.InitializeStatsUI()
end

local statsFrame = MultiBot.newFrame(MultiBot, STATS_ROOT_X, STATS_ROOT_Y, STATS_ROOT_SIZE)
applySavedStatsPoint(statsFrame)
applySavedStatsPoint(statsFrame)
statsFrame:SetMovable(true)
statsFrame:Hide()

Expand Down
8 changes: 4 additions & 4 deletions UI/MultiBotTalentFrame.lua
Original file line number Diff line number Diff line change
Expand Up @@ -1922,7 +1922,7 @@ function MultiBot.InitializeTalentFrameModule()
end,
resolveTalent = MultiBot.talent.buildActiveTalentsResolver(activeGroup),
onSuccess = function()
MultiBot.talent.__talentsTabApplyMode = MultiBot.talent.hasUnspentTalentPoints() and "apply" or "copy"
MultiBot.talent.__talentsTabApplyMode = MultiBot.talent.hasUnspentTalentPoints() and "apply" or "copy"
MultiBot.talent.activateTalentsTabContext()
MultiBot.auto.talent = false
end,
Expand All @@ -1947,8 +1947,8 @@ function MultiBot.InitializeTalentFrameModule()
end

MultiBot.talent.setTalents = function()
MultiBot.talent.__talentsTabApplyMode = nil
MultiBot.talent.__talentsApplyPending = false
MultiBot.talent.__talentsTabApplyMode = nil
MultiBot.talent.__talentsApplyPending = false
MultiBot.talent.renderTalentBuild(MultiBot.talent.getTalentsBuildOptions())
end

Expand Down Expand Up @@ -2401,7 +2401,7 @@ function MultiBot.InitializeTalentFrameModule()
end

MultiBot.talent.refreshApplyTabVisibility()
return applied
return applied
end

function MultiBot.talent.onCopyTabClick()
Expand Down
8 changes: 4 additions & 4 deletions UI/MultiBotUnitsRootUI.lua
Original file line number Diff line number Diff line change
Expand Up @@ -165,14 +165,14 @@ local function layoutVisibleUnits(unitsButton, unitsFrame, display, fromIndex, t
unitFrame:Show()
end
unitButton:Show()
table.insert(newVisible, name)
table.insert(newVisible, name)
end
end

unitsButton.from = startIndex
unitsButton.to = endIndex
unitsFrame.frames.Control.setPoint(-2, (unitsFrame.size + 2) * visibleCount)
unitsButton._visibleNames = newVisible
unitsButton._visibleNames = newVisible
end

local function refreshUnitsDisplay(unitsButton, requestedRoster, requestedFilter)
Expand Down Expand Up @@ -220,7 +220,7 @@ local function refreshUnitsDisplay(unitsButton, requestedRoster, requestedFilter
unitsButton.to = UNITS_PAGE_SIZE

local toIndex = math.min(unitsButton.limit, UNITS_PAGE_SIZE)
hideTrackedVisibleUnits(unitsButton, unitsFrame)
hideTrackedVisibleUnits(unitsButton, unitsFrame)
layoutVisibleUnits(unitsButton, unitsFrame, display, 1, toIndex)

if unitsButton.limit < UNITS_PAGE_SIZE + 1 then
Expand Down Expand Up @@ -631,7 +631,7 @@ local function createBrowseButton(controlFrame)
end

local display = getDisplayableUnits(unitsFrame, sourceTable)
hideTrackedVisibleUnits(unitsButton, unitsFrame)
hideTrackedVisibleUnits(unitsButton, unitsFrame)
layoutVisibleUnits(unitsButton, unitsFrame, display, fromIndex, math.min(toIndex, #display))
end
end
Expand Down
6 changes: 3 additions & 3 deletions docs/ace3-expansion-checklist.md
Original file line number Diff line number Diff line change
Expand Up @@ -57,9 +57,9 @@ Checklist for the full addon-wide ACE3 expansion after M7 completion.
## Milestone 11 — Scheduler/timers convergence

- [x] Inventory all `OnUpdate` loops and elapsed timers. *(2026-04-05: cartographie initiale livrée dans `docs/milestone11-scheduler-inventory.md`.)*
- [ ] Classify each loop (hot path/local, safe-to-centralize, keep-as-is).
- [ ] Migrate safe loops to a shared scheduler approach.
- [ ] Remove duplicate periodic loops after parity validation.
- [x] Classify each loop (hot path/local, safe-to-centralize, keep-as-is).
- [x] Migrate safe loops to a shared scheduler approach.
- [x] Remove duplicate one-shot loops after parity validation.

## Milestone 12 — Observability and perf guardrails

Expand Down
2 changes: 1 addition & 1 deletion docs/ace3-ui-frame-inventory.md
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,7 @@ Inventory of addon UI frame construction points found via `CreateFrame(...)` sca
File: `Core/MultiBotInit.lua`.

- [-] **Event/timer/dispatch helper frames** (`CreateFrame("Frame")` without visible UI).
Files: `Core/MultiBot.lua`, `Core/MultiBotThrottle.lua`, `Core/MultiBotHandler.lua`, `UI/MultiBotSpecUI.lua` (timer frame), `Core/MultiBotInit.lua` (misc helper frame usages).
Files: `Core/MultiBotThrottle.lua`, `Core/MultiBotHandler.lua`, `Core/MultiBotAsync.lua` (legacy fallback scheduler), `Core/MultiBotInit.lua` (misc helper frame usages).

- [-] **Engine widget factory primitives** in `Core/MultiBotEngine.lua` (button/check/model constructors for core UI system).
These are shared low-level primitives and should be migrated only when the owning screen is migrated.
Expand Down
Loading
Loading