From 089bad9d8a5be32e4bb6f1b8db7e6abfa61d8be4 Mon Sep 17 00:00:00 2001 From: Alex Dcnh <140754794+Wishmaster117@users.noreply.github.com> Date: Wed, 15 Apr 2026 14:59:01 +0200 Subject: [PATCH 1/2] Milestone 11 Final --- Core/MultiBot.lua | 13 +++++------ Core/MultiBotAsync.lua | 14 +++++++++++- Core/MultiBotEngine.lua | 30 ++++++++++++++----------- Core/MultiBotHandler.lua | 2 ++ Core/MultiBotThrottle.lua | 2 ++ Features/MultiBotRaidus.lua | 28 +++++++++++++---------- ROADMAP.md | 10 ++++----- TODO.md | 3 ++- UI/MultiBotHunterQuickFrame.lua | 17 ++++++++------ UI/MultiBotMainUI.lua | 2 ++ UI/MultiBotMinimap.lua | 2 ++ UI/MultiBotQuestsMenu.lua | 8 +++---- UI/MultiBotSpecUI.lua | 19 +++++----------- UI/MultiBotStats.lua | 2 +- UI/MultiBotTalentFrame.lua | 8 +++---- UI/MultiBotUnitsRootUI.lua | 8 +++---- docs/ace3-expansion-checklist.md | 6 ++--- docs/ace3-ui-frame-inventory.md | 2 +- docs/milestone11-scheduler-inventory.md | 18 ++++++++++++++- 19 files changed, 115 insertions(+), 79 deletions(-) diff --git a/Core/MultiBot.lua b/Core/MultiBot.lua index 1197781..31c71f7 100644 --- a/Core/MultiBot.lua +++ b/Core/MultiBot.lua @@ -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 diff --git a/Core/MultiBotAsync.lua b/Core/MultiBotAsync.lua index 0576881..c17347e 100644 --- a/Core/MultiBotAsync.lua +++ b/Core/MultiBotAsync.lua @@ -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 @@ -43,4 +45,14 @@ if type(sharedTimerAfter) ~= "function" then end MultiBot.TimerAfter = sharedTimerAfter -_G.TimerAfter = sharedTimerAfter \ No newline at end of file +_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 \ No newline at end of file diff --git a/Core/MultiBotEngine.lua b/Core/MultiBotEngine.lua index 83cc3a9..debe1f8 100644 --- a/Core/MultiBotEngine.lua +++ b/Core/MultiBotEngine.lua @@ -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 = {} @@ -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). diff --git a/Core/MultiBotHandler.lua b/Core/MultiBotHandler.lua index 5ae47ed..734ac31 100644 --- a/Core/MultiBotHandler.lua +++ b/Core/MultiBotHandler.lua @@ -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 diff --git a/Core/MultiBotThrottle.lua b/Core/MultiBotThrottle.lua index ecb4599..cd5d535 100644 --- a/Core/MultiBotThrottle.lua +++ b/Core/MultiBotThrottle.lua @@ -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() diff --git a/Features/MultiBotRaidus.lua b/Features/MultiBotRaidus.lua index 083bd3c..38a6050 100644 --- a/Features/MultiBotRaidus.lua +++ b/Features/MultiBotRaidus.lua @@ -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 @@ -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) @@ -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 diff --git a/ROADMAP.md b/ROADMAP.md index 84a95a9..64b9a7a 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -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. @@ -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. diff --git a/TODO.md b/TODO.md index fed6adc..5cd5ccd 100644 --- a/TODO.md +++ b/TODO.md @@ -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 \ No newline at end of file +* implémenter RTI +* trouver un moyen de charger tous les skins des pets hunter \ No newline at end of file diff --git a/UI/MultiBotHunterQuickFrame.lua b/UI/MultiBotHunterQuickFrame.lua index a3b3a01..41f2e0b 100644 --- a/UI/MultiBotHunterQuickFrame.lua +++ b/UI/MultiBotHunterQuickFrame.lua @@ -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 diff --git a/UI/MultiBotMainUI.lua b/UI/MultiBotMainUI.lua index b00a4a8..8875c9e 100644 --- a/UI/MultiBotMainUI.lua +++ b/UI/MultiBotMainUI.lua @@ -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 diff --git a/UI/MultiBotMinimap.lua b/UI/MultiBotMinimap.lua index f8f1813..ec2f0c4 100644 --- a/UI/MultiBotMinimap.lua +++ b/UI/MultiBotMinimap.lua @@ -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) diff --git a/UI/MultiBotQuestsMenu.lua b/UI/MultiBotQuestsMenu.lua index a601ae4..4d3c973 100644 --- a/UI/MultiBotQuestsMenu.lua +++ b/UI/MultiBotQuestsMenu.lua @@ -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() @@ -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 @@ -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() @@ -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 diff --git a/UI/MultiBotSpecUI.lua b/UI/MultiBotSpecUI.lua index 72c0a6b..4dea5ff 100644 --- a/UI/MultiBotSpecUI.lua +++ b/UI/MultiBotSpecUI.lua @@ -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) + -- print("|cffffff00[SpecDEBUG]|r Message talents spec list envoyé!!!!!!!!!!!!!!!!!") end end) end diff --git a/UI/MultiBotStats.lua b/UI/MultiBotStats.lua index 535f0dd..2aed89f 100644 --- a/UI/MultiBotStats.lua +++ b/UI/MultiBotStats.lua @@ -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() diff --git a/UI/MultiBotTalentFrame.lua b/UI/MultiBotTalentFrame.lua index 69163ce..af89737 100644 --- a/UI/MultiBotTalentFrame.lua +++ b/UI/MultiBotTalentFrame.lua @@ -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, @@ -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 @@ -2401,7 +2401,7 @@ function MultiBot.InitializeTalentFrameModule() end MultiBot.talent.refreshApplyTabVisibility() - return applied + return applied end function MultiBot.talent.onCopyTabClick() diff --git a/UI/MultiBotUnitsRootUI.lua b/UI/MultiBotUnitsRootUI.lua index 30ff617..44d17a0 100644 --- a/UI/MultiBotUnitsRootUI.lua +++ b/UI/MultiBotUnitsRootUI.lua @@ -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) @@ -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 @@ -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 diff --git a/docs/ace3-expansion-checklist.md b/docs/ace3-expansion-checklist.md index 9cb93af..7e8635e 100644 --- a/docs/ace3-expansion-checklist.md +++ b/docs/ace3-expansion-checklist.md @@ -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 diff --git a/docs/ace3-ui-frame-inventory.md b/docs/ace3-ui-frame-inventory.md index b9ffdc1..144fe9a 100644 --- a/docs/ace3-ui-frame-inventory.md +++ b/docs/ace3-ui-frame-inventory.md @@ -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. diff --git a/docs/milestone11-scheduler-inventory.md b/docs/milestone11-scheduler-inventory.md index ea39d33..eb387a6 100644 --- a/docs/milestone11-scheduler-inventory.md +++ b/docs/milestone11-scheduler-inventory.md @@ -97,6 +97,7 @@ Objectif: établir la cartographie complète des mécanismes temporels avant con ### PR-M11-1 — Fondations scheduler unifié - **But**: verrouiller un point d'entrée unique. +- **Statut**: ✅ Implémenté le 2026-04-15. - **Changements**: - Ajouter `MultiBot.NextTick(callback)` dans `Core/MultiBotAsync.lua` (implémenté via `MultiBot.TimerAfter(0, callback)` avec garde `type(callback) == "function"`). - Documenter le contrat: `TimerAfter`/`NextTick` sont les seules APIs de délai autorisées. @@ -106,6 +107,7 @@ Objectif: établir la cartographie complète des mécanismes temporels avant con ### PR-M11-2 — Suppression des duplications runtime - **But**: converger les one-shots historiques runtime. +- **Statut**: ✅ Implémenté le 2026-04-15. - **Changements**: - Migrer P11 (`Core/MultiBot.lua`) vers `MultiBot.TimerAfter`. - Migrer P9 (`Core/MultiBotEngine.lua`) vers `MultiBot.NextTick`. @@ -115,6 +117,7 @@ Objectif: établir la cartographie complète des mécanismes temporels avant con ### PR-M11-3 — Migration safe UI one-shot - **But**: retirer les `OnUpdate` temporaires qui ne sont pas des animations continues. +- **Statut**: ✅ Implémenté le 2026-04-15. - **Changements**: - Migrer P4 (`Features/MultiBotRaidus.lua`) vers one-shot timer. - Migrer P6 (`UI/MultiBotSpecUI.lua`) vers chaînage `TimerAfter`. @@ -125,6 +128,7 @@ Objectif: établir la cartographie complète des mécanismes temporels avant con ### PR-M11-4 — Stabilisation hot paths conservés - **But**: figer explicitement ce qui reste en `OnUpdate` local. +- **Statut**: ✅ Implémenté le 2026-04-15. - **Changements**: - Ajouter commentaires d'ownership et raison de conservation pour P1/P2/P3/P5/P7/P10. - Harmoniser constantes d'intervalle/nommage là où pertinent (sans changer les valeurs). @@ -141,4 +145,16 @@ Objectif: établir la cartographie complète des mécanismes temporels avant con - Chaque migration doit vérifier: 1. absence de régression UX (autohide, drag, pulse), 2. absence de double exécution, - 3. absence de ticker/frame non libéré. \ No newline at end of file + 3. absence de ticker/frame non libéré. + +## 9) État de clôture M11 + +- **Statut global**: ✅ **Terminé** (2026-04-15). +- **Livré**: + - PR-M11-1 (fondations scheduler unifié) ✅ + - PR-M11-2 (suppression duplications runtime) ✅ + - PR-M11-3 (migration UI one-shot safe) ✅ + - PR-M11-4 (stabilisation hot paths conservés) ✅ +- **Décision finale**: + - Conserver `OnUpdate` local uniquement pour hot paths / interactions / animations frame-level. + - Utiliser `MultiBot.TimerAfter` / `MultiBot.NextTick` pour tous les délais one-shot. \ No newline at end of file From c7f04764072febb92cf88bb1b0a52bc65e4c0815 Mon Sep 17 00:00:00 2001 From: Alex Dcnh <140754794+Wishmaster117@users.noreply.github.com> Date: Wed, 15 Apr 2026 15:02:19 +0200 Subject: [PATCH 2/2] Lua Lint fix --- Features/MultiBotRaidus.lua | 2 +- UI/MultiBotMainUI.lua | 2 +- UI/MultiBotTalentFrame.lua | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Features/MultiBotRaidus.lua b/Features/MultiBotRaidus.lua index 38a6050..a03a300 100644 --- a/Features/MultiBotRaidus.lua +++ b/Features/MultiBotRaidus.lua @@ -541,7 +541,7 @@ 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. + -- 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 diff --git a/UI/MultiBotMainUI.lua b/UI/MultiBotMainUI.lua index 8875c9e..e3d0d85 100644 --- a/UI/MultiBotMainUI.lua +++ b/UI/MultiBotMainUI.lua @@ -629,7 +629,7 @@ function MultiBot.InitializeMainUI(tMultiBar) 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. + -- 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 diff --git a/UI/MultiBotTalentFrame.lua b/UI/MultiBotTalentFrame.lua index af89737..92a6745 100644 --- a/UI/MultiBotTalentFrame.lua +++ b/UI/MultiBotTalentFrame.lua @@ -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,