ace timer from retail
This commit is contained in:
@@ -2,8 +2,8 @@
|
||||
-- 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.
|
||||
-- AceTimer is currently limited to firing timers at a frequency of 0.01s as this is what the WoW timer API
|
||||
-- restricts us to.
|
||||
--
|
||||
-- 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.
|
||||
@@ -15,79 +15,65 @@
|
||||
-- make into AceTimer.
|
||||
-- @class file
|
||||
-- @name AceTimer-3.0
|
||||
-- @release $Id$
|
||||
-- @release $Id: AceTimer-3.0.lua 1202 2019-05-15 23:11:22Z nevcairiel $
|
||||
|
||||
local MAJOR, MINOR = "AceTimer-3.0", 16 -- Bump minor on changes
|
||||
local MAJOR, MINOR = "AceTimer-3.0", 17 -- 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
|
||||
AceTimer.activeTimers = AceTimer.activeTimers or {} -- Active timer list
|
||||
local activeTimers = AceTimer.activeTimers -- Upvalue our private data
|
||||
|
||||
-- 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 type, unpack, next, error, select = type, unpack, next, error, select
|
||||
-- WoW APIs
|
||||
local GetTime, C_TimerAfter = GetTime, C_Timer.After
|
||||
|
||||
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
|
||||
delay = 0.01 -- Restrict to the lowest time that the C_Timer API allows us
|
||||
end
|
||||
|
||||
timer.object = self
|
||||
timer.func = func
|
||||
timer.looping = loop
|
||||
timer.args = {...}
|
||||
timer.argsCount = select("#", ...)
|
||||
local timer = {
|
||||
object = self,
|
||||
func = func,
|
||||
looping = loop,
|
||||
argsCount = select("#", ...),
|
||||
delay = delay,
|
||||
ends = GetTime() + delay,
|
||||
...
|
||||
}
|
||||
|
||||
local anim = timer:GetParent()
|
||||
if loop then
|
||||
anim:SetLooping("REPEAT")
|
||||
else
|
||||
anim:SetLooping("NONE")
|
||||
activeTimers[timer] = timer
|
||||
|
||||
-- Create new timer closure to wrap the "timer" object
|
||||
timer.callback = function()
|
||||
if not timer.cancelled then
|
||||
if type(timer.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.
|
||||
timer.object[timer.func](timer.object, unpack(timer, 1, timer.argsCount))
|
||||
else
|
||||
timer.func(unpack(timer, 1, timer.argsCount))
|
||||
end
|
||||
|
||||
if timer.looping and not timer.cancelled then
|
||||
-- Compensate delay to get a perfect average delay, even if individual times don't match up perfectly
|
||||
-- due to fps differences
|
||||
local time = GetTime()
|
||||
local delay = timer.delay - (time - timer.ends)
|
||||
-- Ensure the delay doesn't go below the threshold
|
||||
if delay < 0.01 then delay = 0.01 end
|
||||
C_TimerAfter(delay, timer.callback)
|
||||
timer.ends = time + delay
|
||||
else
|
||||
activeTimers[timer.handle or timer] = nil
|
||||
end
|
||||
end
|
||||
end
|
||||
timer:SetDuration(delay)
|
||||
|
||||
local id = tostring(timer.args)
|
||||
timer.id = id
|
||||
activeTimers[id] = timer
|
||||
|
||||
anim:Play()
|
||||
return id
|
||||
C_TimerAfter(delay, timer.callback)
|
||||
return timer
|
||||
end
|
||||
|
||||
--- Schedule a new one-shot timer.
|
||||
@@ -160,20 +146,19 @@ end
|
||||
-- @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
|
||||
if not timer then
|
||||
return false
|
||||
else
|
||||
timer.cancelled = true
|
||||
activeTimers[id] = nil
|
||||
return true
|
||||
end
|
||||
end
|
||||
|
||||
--- Cancels all timers registered to the current addon object ('self')
|
||||
function AceTimer:CancelAllTimers()
|
||||
for k,v in pairs(activeTimers) do
|
||||
for k,v in next, activeTimers do
|
||||
if v.object == self then
|
||||
AceTimer.CancelTimer(self, k)
|
||||
end
|
||||
@@ -186,60 +171,79 @@ end
|
||||
-- @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()
|
||||
if not timer then
|
||||
return 0
|
||||
else
|
||||
return timer.ends - GetTime()
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
-- ---------------------------------------------------------------------
|
||||
-- Upgrading
|
||||
|
||||
-- Upgrade from old hash-bucket based timers to animation timers
|
||||
-- Upgrade from old hash-bucket based timers to C_Timer.After 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
|
||||
for object,timers in next, AceTimer.selfs do
|
||||
for handle,timer in next, timers do
|
||||
if type(timer) == "table" and timer.callback then
|
||||
local id
|
||||
local newTimer
|
||||
if timer.delay then
|
||||
id = AceTimer.ScheduleRepeatingTimer(timer.object, timer.callback, timer.delay, timer.arg)
|
||||
newTimer = AceTimer.ScheduleRepeatingTimer(timer.object, timer.callback, timer.delay, timer.arg)
|
||||
else
|
||||
id = AceTimer.ScheduleTimer(timer.object, timer.callback, timer.when - GetTime(), timer.arg)
|
||||
newTimer = 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
|
||||
-- Use the old handle for old timers
|
||||
activeTimers[newTimer] = nil
|
||||
activeTimers[handle] = newTimer
|
||||
newTimer.handle = 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
|
||||
elseif oldminor and oldminor < 17 then
|
||||
-- Upgrade from old animation based timers to C_Timer.After timers.
|
||||
AceTimer.inactiveTimers = nil
|
||||
AceTimer.frame = nil
|
||||
local oldTimers = AceTimer.activeTimers
|
||||
-- Clear old timer table and update upvalue
|
||||
AceTimer.activeTimers = {}
|
||||
activeTimers = AceTimer.activeTimers
|
||||
for handle, timer in next, oldTimers do
|
||||
local newTimer
|
||||
-- Stop the old timer animation
|
||||
local duration, elapsed = timer:GetDuration(), timer:GetElapsed()
|
||||
timer:GetParent():Stop()
|
||||
if timer.looping then
|
||||
newTimer = AceTimer.ScheduleRepeatingTimer(timer.object, timer.func, duration, unpack(timer.args, 1, timer.argsCount))
|
||||
else
|
||||
newTimer = AceTimer.ScheduleTimer(timer.object, timer.func, duration - elapsed, unpack(timer.args, 1, timer.argsCount))
|
||||
end
|
||||
-- Use the old handle for old timers
|
||||
activeTimers[newTimer] = nil
|
||||
activeTimers[handle] = newTimer
|
||||
newTimer.handle = handle
|
||||
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)
|
||||
-- Migrate transitional handles
|
||||
if oldminor < 13 and AceTimer.hashCompatTable then
|
||||
for handle, id in next, AceTimer.hashCompatTable do
|
||||
local t = activeTimers[id]
|
||||
if t then
|
||||
activeTimers[id] = nil
|
||||
activeTimers[handle] = t
|
||||
t.handle = handle
|
||||
end
|
||||
end
|
||||
AceTimer.hashCompatTable = nil
|
||||
end
|
||||
end
|
||||
|
||||
-- ---------------------------------------------------------------------
|
||||
@@ -255,7 +259,7 @@ local mixins = {
|
||||
|
||||
function AceTimer:Embed(target)
|
||||
AceTimer.embeds[target] = true
|
||||
for _,v in pairs(mixins) do
|
||||
for _,v in next, mixins do
|
||||
target[v] = AceTimer[v]
|
||||
end
|
||||
return target
|
||||
@@ -269,6 +273,6 @@ function AceTimer:OnEmbedDisable(target)
|
||||
target:CancelAllTimers()
|
||||
end
|
||||
|
||||
for addon in pairs(AceTimer.embeds) do
|
||||
for addon in next, AceTimer.embeds do
|
||||
AceTimer:Embed(addon)
|
||||
end
|
||||
|
||||
Reference in New Issue
Block a user