WoWInterface SVN DisplacedEnergyWatcher

[/] [trunk/] [DisplacedEnergyWatcher/] [Libs/] [AceTimer-3.0/] [AceTimer-3.0.lua] - Rev 2

Compare with Previous | Blame | View Log

--- **AceTimer-3.0** provides a central facility for registering timers.
-- AceTimer supports one-shot timers and repeating timers. All timers are stored in an efficient
-- data structure that allows easy dispatching and fast rescheduling. Timers can be registered
-- or canceled at any time, even from within a running timer, without conflict or large overhead.\\
-- AceTimer is currently limited to firing timers at a frequency of 0.01s. This constant may change
-- in the future, but for now it's required as animations with lower frequencies are buggy.
--
-- All `:Schedule` functions will return a handle to the current timer, which you will need to store if you
-- need to cancel the timer you just registered.
--
-- **AceTimer-3.0** can be embeded into your addon, either explicitly by calling AceTimer:Embed(MyAddon) or by
-- specifying it as an embeded library in your AceAddon. All functions will be available on your addon object
-- and can be accessed directly, without having to explicitly call AceTimer itself.\\
-- It is recommended to embed AceTimer, otherwise you'll have to specify a custom `self` on all calls you
-- make into AceTimer.
-- @class file
-- @name AceTimer-3.0
-- @release $Id: AceTimer-3.0.lua 1079 2013-02-17 19:56:06Z funkydude $

local MAJOR, MINOR = "AceTimer-3.0", 16 -- Bump minor on changes
local AceTimer, oldminor = LibStub:NewLibrary(MAJOR, MINOR)

if not AceTimer then return end -- No upgrade needed

AceTimer.frame = AceTimer.frame or CreateFrame("Frame", "AceTimer30Frame") -- Animation parent
AceTimer.inactiveTimers = AceTimer.inactiveTimers or {}                    -- Timer recycling storage
AceTimer.activeTimers = AceTimer.activeTimers or {}                        -- Active timer list

-- Lua APIs
local type, unpack, next, error, pairs, tostring, select = type, unpack, next, error, pairs, tostring, select

-- Upvalue our private data
local inactiveTimers = AceTimer.inactiveTimers
local activeTimers = AceTimer.activeTimers

local function OnFinished(self)
        local id = self.id
        if type(self.func) == "string" then
                -- We manually set the unpack count to prevent issues with an arg set that contains nil and ends with nil
                -- e.g. local t = {1, 2, nil, 3, nil} print(#t) will result in 2, instead of 5. This fixes said issue.
                self.object[self.func](self.object, unpack(self.args, 1, self.argsCount))
        else
                self.func(unpack(self.args, 1, self.argsCount))
        end

        -- If the id is different it means that the timer was already cancelled
        -- and has been used to create a new timer during the OnFinished callback.
        if not self.looping and id == self.id then
                activeTimers[self.id] = nil
                self.args = nil
                inactiveTimers[self] = true
        end
end

local function new(self, loop, func, delay, ...)
        local timer = next(inactiveTimers)
        if timer then
                inactiveTimers[timer] = nil
        else
                local anim = AceTimer.frame:CreateAnimationGroup()
                timer = anim:CreateAnimation()
                timer:SetScript("OnFinished", OnFinished)
        end

        -- Very low delays cause the animations to fail randomly.
        -- A limited resolution of 0.01 seems reasonable.
        if delay < 0.01 then
                delay = 0.01
        end

        timer.object = self
        timer.func = func
        timer.looping = loop
        timer.args = {...}
        timer.argsCount = select("#", ...)

        local anim = timer:GetParent()
        if loop then
                anim:SetLooping("REPEAT")
        else
                anim:SetLooping("NONE")
        end
        timer:SetDuration(delay)

        local id = tostring(timer.args)
        timer.id = id
        activeTimers[id] = timer

        anim:Play()
        return id
end

--- Schedule a new one-shot timer.
-- The timer will fire once in `delay` seconds, unless canceled before.
-- @param callback Callback function for the timer pulse (funcref or method name).
-- @param delay Delay for the timer, in seconds.
-- @param ... An optional, unlimited amount of arguments to pass to the callback function.
-- @usage
-- MyAddOn = LibStub("AceAddon-3.0"):NewAddon("MyAddOn", "AceTimer-3.0")
--
-- function MyAddOn:OnEnable()
--   self:ScheduleTimer("TimerFeedback", 5)
-- end
--
-- function MyAddOn:TimerFeedback()
--   print("5 seconds passed")
-- end
function AceTimer:ScheduleTimer(func, delay, ...)
        if not func or not delay then
                error(MAJOR..": ScheduleTimer(callback, delay, args...): 'callback' and 'delay' must have set values.", 2)
        end
        if type(func) == "string" then
                if type(self) ~= "table" then
                        error(MAJOR..": ScheduleTimer(callback, delay, args...): 'self' - must be a table.", 2)
                elseif not self[func] then
                        error(MAJOR..": ScheduleTimer(callback, delay, args...): Tried to register '"..func.."' as the callback, but it doesn't exist in the module.", 2)
                end
        end
        return new(self, nil, func, delay, ...)
end

--- Schedule a repeating timer.
-- The timer will fire every `delay` seconds, until canceled.
-- @param callback Callback function for the timer pulse (funcref or method name).
-- @param delay Delay for the timer, in seconds.
-- @param ... An optional, unlimited amount of arguments to pass to the callback function.
-- @usage
-- MyAddOn = LibStub("AceAddon-3.0"):NewAddon("MyAddOn", "AceTimer-3.0")
--
-- function MyAddOn:OnEnable()
--   self.timerCount = 0
--   self.testTimer = self:ScheduleRepeatingTimer("TimerFeedback", 5)
-- end
--
-- function MyAddOn:TimerFeedback()
--   self.timerCount = self.timerCount + 1
--   print(("%d seconds passed"):format(5 * self.timerCount))
--   -- run 30 seconds in total
--   if self.timerCount == 6 then
--     self:CancelTimer(self.testTimer)
--   end
-- end
function AceTimer:ScheduleRepeatingTimer(func, delay, ...)
        if not func or not delay then
                error(MAJOR..": ScheduleRepeatingTimer(callback, delay, args...): 'callback' and 'delay' must have set values.", 2)
        end
        if type(func) == "string" then
                if type(self) ~= "table" then
                        error(MAJOR..": ScheduleRepeatingTimer(callback, delay, args...): 'self' - must be a table.", 2)
                elseif not self[func] then
                        error(MAJOR..": ScheduleRepeatingTimer(callback, delay, args...): Tried to register '"..func.."' as the callback, but it doesn't exist in the module.", 2)
                end
        end
        return new(self, true, func, delay, ...)
end

--- Cancels a timer with the given id, registered by the same addon object as used for `:ScheduleTimer`
-- Both one-shot and repeating timers can be canceled with this function, as long as the `id` is valid
-- and the timer has not fired yet or was canceled before.
-- @param id The id of the timer, as returned by `:ScheduleTimer` or `:ScheduleRepeatingTimer`
function AceTimer:CancelTimer(id)
        local timer = activeTimers[id]
        if not timer then return false end

        local anim = timer:GetParent()
        anim:Stop()

        activeTimers[id] = nil
        timer.args = nil
        inactiveTimers[timer] = true
        return true
end

--- Cancels all timers registered to the current addon object ('self')
function AceTimer:CancelAllTimers()
        for k,v in pairs(activeTimers) do
                if v.object == self then
                        AceTimer.CancelTimer(self, k)
                end
        end
end

--- Returns the time left for a timer with the given id, registered by the current addon object ('self').
-- This function will return 0 when the id is invalid.
-- @param id The id of the timer, as returned by `:ScheduleTimer` or `:ScheduleRepeatingTimer`
-- @return The time left on the timer.
function AceTimer:TimeLeft(id)
        local timer = activeTimers[id]
        if not timer then return 0 end
        return timer:GetDuration() - timer:GetElapsed()
end


-- ---------------------------------------------------------------------
-- Upgrading

-- Upgrade from old hash-bucket based timers to animation timers
if oldminor and oldminor < 10 then
        -- disable old timer logic
        AceTimer.frame:SetScript("OnUpdate", nil)
        AceTimer.frame:SetScript("OnEvent", nil)
        AceTimer.frame:UnregisterAllEvents()
        -- convert timers
        for object,timers in pairs(AceTimer.selfs) do
                for handle,timer in pairs(timers) do
                        if type(timer) == "table" and timer.callback then
                                local id
                                if timer.delay then
                                        id = AceTimer.ScheduleRepeatingTimer(timer.object, timer.callback, timer.delay, timer.arg)
                                else
                                        id = AceTimer.ScheduleTimer(timer.object, timer.callback, timer.when - GetTime(), timer.arg)
                                end
                                -- change id to the old handle
                                local t = activeTimers[id]
                                activeTimers[id] = nil
                                activeTimers[handle] = t
                                t.id = handle
                        end
                end
        end
        AceTimer.selfs = nil
        AceTimer.hash = nil
        AceTimer.debug = nil
elseif oldminor and oldminor < 13 then
        for handle, id in pairs(AceTimer.hashCompatTable) do
                local t = activeTimers[id]
                if t then
                        activeTimers[id] = nil
                        activeTimers[handle] = t
                        t.id = handle
                end
        end
        AceTimer.hashCompatTable = nil
end

-- upgrade existing timers to the latest OnFinished
for timer in pairs(inactiveTimers) do
        timer:SetScript("OnFinished", OnFinished)
end

for _,timer in pairs(activeTimers) do
        timer:SetScript("OnFinished", OnFinished)
end

-- ---------------------------------------------------------------------
-- Embed handling

AceTimer.embeds = AceTimer.embeds or {}

local mixins = {
        "ScheduleTimer", "ScheduleRepeatingTimer",
        "CancelTimer", "CancelAllTimers",
        "TimeLeft"
}

function AceTimer:Embed(target)
        AceTimer.embeds[target] = true
        for _,v in pairs(mixins) do
                target[v] = AceTimer[v]
        end
        return target
end

-- AceTimer:OnEmbedDisable(target)
-- target (object) - target object that AceTimer is embedded in.
--
-- cancel all timers registered for the object
function AceTimer:OnEmbedDisable(target)
        target:CancelAllTimers()
end

for addon in pairs(AceTimer.embeds) do
        AceTimer:Embed(addon)
end

Compare with Previous | Blame