Tercio@4: --- **AceTimer-3.0** provides a central facility for registering timers. Tercio@4: -- AceTimer supports one-shot timers and repeating timers. All timers are stored in an efficient Tercio@4: -- data structure that allows easy dispatching and fast rescheduling. Timers can be registered Tercio@4: -- or canceled at any time, even from within a running timer, without conflict or large overhead.\\ Tercio@4: -- AceTimer is currently limited to firing timers at a frequency of 0.01s as this is what the WoW timer API Tercio@4: -- restricts us to. Tercio@4: -- Tercio@4: -- All `:Schedule` functions will return a handle to the current timer, which you will need to store if you Tercio@4: -- need to cancel the timer you just registered. Tercio@4: -- Tercio@4: -- **AceTimer-3.0** can be embeded into your addon, either explicitly by calling AceTimer:Embed(MyAddon) or by Tercio@4: -- specifying it as an embeded library in your AceAddon. All functions will be available on your addon object Tercio@4: -- and can be accessed directly, without having to explicitly call AceTimer itself.\\ Tercio@4: -- It is recommended to embed AceTimer, otherwise you'll have to specify a custom `self` on all calls you Tercio@4: -- make into AceTimer. Tercio@4: -- @class file Tercio@4: -- @name AceTimer-3.0 Tercio@4: -- @release $Id: AceTimer-3.0.lua 1119 2014-10-14 17:23:29Z nevcairiel $ Tercio@4: Tercio@4: local MAJOR, MINOR = "AceTimer-3.0", 17 -- Bump minor on changes Tercio@4: local AceTimer, oldminor = LibStub:NewLibrary(MAJOR, MINOR) Tercio@4: Tercio@4: if not AceTimer then return end -- No upgrade needed Tercio@4: AceTimer.activeTimers = AceTimer.activeTimers or {} -- Active timer list Tercio@4: local activeTimers = AceTimer.activeTimers -- Upvalue our private data Tercio@4: Tercio@4: -- Lua APIs Tercio@4: local type, unpack, next, error, select = type, unpack, next, error, select Tercio@4: -- WoW APIs Tercio@4: local GetTime, C_TimerAfter = GetTime, C_Timer.After Tercio@4: Tercio@4: local function new(self, loop, func, delay, ...) Tercio@4: if delay < 0.01 then Tercio@4: delay = 0.01 -- Restrict to the lowest time that the C_Timer API allows us Tercio@4: end Tercio@4: Tercio@4: local timer = {...} Tercio@4: timer.object = self Tercio@4: timer.func = func Tercio@4: timer.looping = loop Tercio@4: timer.argsCount = select("#", ...) Tercio@4: timer.delay = delay Tercio@4: timer.ends = GetTime() + delay Tercio@4: Tercio@4: activeTimers[timer] = timer Tercio@4: Tercio@4: -- Create new timer closure to wrap the "timer" object Tercio@4: timer.callback = function() Tercio@4: if not timer.cancelled then Tercio@4: if type(timer.func) == "string" then Tercio@4: -- We manually set the unpack count to prevent issues with an arg set that contains nil and ends with nil Tercio@4: -- e.g. local t = {1, 2, nil, 3, nil} print(#t) will result in 2, instead of 5. This fixes said issue. Tercio@4: timer.object[timer.func](timer.object, unpack(timer, 1, timer.argsCount)) Tercio@4: else Tercio@4: timer.func(unpack(timer, 1, timer.argsCount)) Tercio@4: end Tercio@4: Tercio@4: if timer.looping and not timer.cancelled then Tercio@4: -- Compensate delay to get a perfect average delay, even if individual times don't match up perfectly Tercio@4: -- due to fps differences Tercio@4: local time = GetTime() Tercio@4: local delay = timer.delay - (time - timer.ends) Tercio@4: -- Ensure the delay doesn't go below the threshold Tercio@4: if delay < 0.01 then delay = 0.01 end Tercio@4: C_TimerAfter(delay, timer.callback) Tercio@4: timer.ends = time + delay Tercio@4: else Tercio@4: activeTimers[timer.handle or timer] = nil Tercio@4: end Tercio@4: end Tercio@4: end Tercio@4: Tercio@4: C_TimerAfter(delay, timer.callback) Tercio@4: return timer Tercio@4: end Tercio@4: Tercio@4: --- Schedule a new one-shot timer. Tercio@4: -- The timer will fire once in `delay` seconds, unless canceled before. Tercio@4: -- @param callback Callback function for the timer pulse (funcref or method name). Tercio@4: -- @param delay Delay for the timer, in seconds. Tercio@4: -- @param ... An optional, unlimited amount of arguments to pass to the callback function. Tercio@4: -- @usage Tercio@4: -- MyAddOn = LibStub("AceAddon-3.0"):NewAddon("MyAddOn", "AceTimer-3.0") Tercio@4: -- Tercio@4: -- function MyAddOn:OnEnable() Tercio@4: -- self:ScheduleTimer("TimerFeedback", 5) Tercio@4: -- end Tercio@4: -- Tercio@4: -- function MyAddOn:TimerFeedback() Tercio@4: -- print("5 seconds passed") Tercio@4: -- end Tercio@4: function AceTimer:ScheduleTimer(func, delay, ...) Tercio@4: if not func or not delay then Tercio@4: error(MAJOR..": ScheduleTimer(callback, delay, args...): 'callback' and 'delay' must have set values.", 2) Tercio@4: end Tercio@4: if type(func) == "string" then Tercio@4: if type(self) ~= "table" then Tercio@4: error(MAJOR..": ScheduleTimer(callback, delay, args...): 'self' - must be a table.", 2) Tercio@4: elseif not self[func] then Tercio@4: error(MAJOR..": ScheduleTimer(callback, delay, args...): Tried to register '"..func.."' as the callback, but it doesn't exist in the module.", 2) Tercio@4: end Tercio@4: end Tercio@4: return new(self, nil, func, delay, ...) Tercio@4: end Tercio@4: Tercio@4: --- Schedule a repeating timer. Tercio@4: -- The timer will fire every `delay` seconds, until canceled. Tercio@4: -- @param callback Callback function for the timer pulse (funcref or method name). Tercio@4: -- @param delay Delay for the timer, in seconds. Tercio@4: -- @param ... An optional, unlimited amount of arguments to pass to the callback function. Tercio@4: -- @usage Tercio@4: -- MyAddOn = LibStub("AceAddon-3.0"):NewAddon("MyAddOn", "AceTimer-3.0") Tercio@4: -- Tercio@4: -- function MyAddOn:OnEnable() Tercio@4: -- self.timerCount = 0 Tercio@4: -- self.testTimer = self:ScheduleRepeatingTimer("TimerFeedback", 5) Tercio@4: -- end Tercio@4: -- Tercio@4: -- function MyAddOn:TimerFeedback() Tercio@4: -- self.timerCount = self.timerCount + 1 Tercio@4: -- print(("%d seconds passed"):format(5 * self.timerCount)) Tercio@4: -- -- run 30 seconds in total Tercio@4: -- if self.timerCount == 6 then Tercio@4: -- self:CancelTimer(self.testTimer) Tercio@4: -- end Tercio@4: -- end Tercio@4: function AceTimer:ScheduleRepeatingTimer(func, delay, ...) Tercio@4: if not func or not delay then Tercio@4: error(MAJOR..": ScheduleRepeatingTimer(callback, delay, args...): 'callback' and 'delay' must have set values.", 2) Tercio@4: end Tercio@4: if type(func) == "string" then Tercio@4: if type(self) ~= "table" then Tercio@4: error(MAJOR..": ScheduleRepeatingTimer(callback, delay, args...): 'self' - must be a table.", 2) Tercio@4: elseif not self[func] then Tercio@4: error(MAJOR..": ScheduleRepeatingTimer(callback, delay, args...): Tried to register '"..func.."' as the callback, but it doesn't exist in the module.", 2) Tercio@4: end Tercio@4: end Tercio@4: return new(self, true, func, delay, ...) Tercio@4: end Tercio@4: Tercio@4: --- Cancels a timer with the given id, registered by the same addon object as used for `:ScheduleTimer` Tercio@4: -- Both one-shot and repeating timers can be canceled with this function, as long as the `id` is valid Tercio@4: -- and the timer has not fired yet or was canceled before. Tercio@4: -- @param id The id of the timer, as returned by `:ScheduleTimer` or `:ScheduleRepeatingTimer` Tercio@4: function AceTimer:CancelTimer(id) Tercio@4: local timer = activeTimers[id] Tercio@4: Tercio@4: if not timer then Tercio@4: return false Tercio@4: else Tercio@4: timer.cancelled = true Tercio@4: activeTimers[id] = nil Tercio@4: return true Tercio@4: end Tercio@4: end Tercio@4: Tercio@4: --- Cancels all timers registered to the current addon object ('self') Tercio@4: function AceTimer:CancelAllTimers() Tercio@4: for k,v in pairs(activeTimers) do Tercio@4: if v.object == self then Tercio@4: AceTimer.CancelTimer(self, k) Tercio@4: end Tercio@4: end Tercio@4: end Tercio@4: Tercio@4: --- Returns the time left for a timer with the given id, registered by the current addon object ('self'). Tercio@4: -- This function will return 0 when the id is invalid. Tercio@4: -- @param id The id of the timer, as returned by `:ScheduleTimer` or `:ScheduleRepeatingTimer` Tercio@4: -- @return The time left on the timer. Tercio@4: function AceTimer:TimeLeft(id) Tercio@4: local timer = activeTimers[id] Tercio@4: if not timer then Tercio@4: return 0 Tercio@4: else Tercio@4: return timer.ends - GetTime() Tercio@4: end Tercio@4: end Tercio@4: Tercio@4: Tercio@4: -- --------------------------------------------------------------------- Tercio@4: -- Upgrading Tercio@4: Tercio@4: -- Upgrade from old hash-bucket based timers to C_Timer.After timers. Tercio@4: if oldminor and oldminor < 10 then Tercio@4: -- disable old timer logic Tercio@4: AceTimer.frame:SetScript("OnUpdate", nil) Tercio@4: AceTimer.frame:SetScript("OnEvent", nil) Tercio@4: AceTimer.frame:UnregisterAllEvents() Tercio@4: -- convert timers Tercio@4: for object,timers in pairs(AceTimer.selfs) do Tercio@4: for handle,timer in pairs(timers) do Tercio@4: if type(timer) == "table" and timer.callback then Tercio@4: local newTimer Tercio@4: if timer.delay then Tercio@4: newTimer = AceTimer.ScheduleRepeatingTimer(timer.object, timer.callback, timer.delay, timer.arg) Tercio@4: else Tercio@4: newTimer = AceTimer.ScheduleTimer(timer.object, timer.callback, timer.when - GetTime(), timer.arg) Tercio@4: end Tercio@4: -- Use the old handle for old timers Tercio@4: activeTimers[newTimer] = nil Tercio@4: activeTimers[handle] = newTimer Tercio@4: newTimer.handle = handle Tercio@4: end Tercio@4: end Tercio@4: end Tercio@4: AceTimer.selfs = nil Tercio@4: AceTimer.hash = nil Tercio@4: AceTimer.debug = nil Tercio@4: elseif oldminor and oldminor < 17 then Tercio@4: -- Upgrade from old animation based timers to C_Timer.After timers. Tercio@4: AceTimer.inactiveTimers = nil Tercio@4: AceTimer.frame = nil Tercio@4: local oldTimers = AceTimer.activeTimers Tercio@4: -- Clear old timer table and update upvalue Tercio@4: AceTimer.activeTimers = {} Tercio@4: activeTimers = AceTimer.activeTimers Tercio@4: for handle, timer in pairs(oldTimers) do Tercio@4: local newTimer Tercio@4: -- Stop the old timer animation Tercio@4: local duration, elapsed = timer:GetDuration(), timer:GetElapsed() Tercio@4: timer:GetParent():Stop() Tercio@4: if timer.looping then Tercio@4: newTimer = AceTimer.ScheduleRepeatingTimer(timer.object, timer.func, duration, unpack(timer.args, 1, timer.argsCount)) Tercio@4: else Tercio@4: newTimer = AceTimer.ScheduleTimer(timer.object, timer.func, duration - elapsed, unpack(timer.args, 1, timer.argsCount)) Tercio@4: end Tercio@4: -- Use the old handle for old timers Tercio@4: activeTimers[newTimer] = nil Tercio@4: activeTimers[handle] = newTimer Tercio@4: newTimer.handle = handle Tercio@4: end Tercio@4: Tercio@4: -- Migrate transitional handles Tercio@4: if oldminor < 13 and AceTimer.hashCompatTable then Tercio@4: for handle, id in pairs(AceTimer.hashCompatTable) do Tercio@4: local t = activeTimers[id] Tercio@4: if t then Tercio@4: activeTimers[id] = nil Tercio@4: activeTimers[handle] = t Tercio@4: t.handle = handle Tercio@4: end Tercio@4: end Tercio@4: AceTimer.hashCompatTable = nil Tercio@4: end Tercio@4: end Tercio@4: Tercio@4: -- --------------------------------------------------------------------- Tercio@4: -- Embed handling Tercio@4: Tercio@4: AceTimer.embeds = AceTimer.embeds or {} Tercio@4: Tercio@4: local mixins = { Tercio@4: "ScheduleTimer", "ScheduleRepeatingTimer", Tercio@4: "CancelTimer", "CancelAllTimers", Tercio@4: "TimeLeft" Tercio@4: } Tercio@4: Tercio@4: function AceTimer:Embed(target) Tercio@4: AceTimer.embeds[target] = true Tercio@4: for _,v in pairs(mixins) do Tercio@4: target[v] = AceTimer[v] Tercio@4: end Tercio@4: return target Tercio@4: end Tercio@4: Tercio@4: -- AceTimer:OnEmbedDisable(target) Tercio@4: -- target (object) - target object that AceTimer is embedded in. Tercio@4: -- Tercio@4: -- cancel all timers registered for the object Tercio@4: function AceTimer:OnEmbedDisable(target) Tercio@4: target:CancelAllTimers() Tercio@4: end Tercio@4: Tercio@4: for addon in pairs(AceTimer.embeds) do Tercio@4: AceTimer:Embed(addon) Tercio@4: end