tercio@0
|
1 --- **AceTimer-3.0** provides a central facility for registering timers.
|
tercio@0
|
2 -- AceTimer supports one-shot timers and repeating timers. All timers are stored in an efficient
|
tercio@0
|
3 -- data structure that allows easy dispatching and fast rescheduling. Timers can be registered
|
tercio@0
|
4 -- or canceled at any time, even from within a running timer, without conflict or large overhead.\\
|
tercio@0
|
5 -- AceTimer is currently limited to firing timers at a frequency of 0.01s. This constant may change
|
tercio@0
|
6 -- in the future, but for now it's required as animations with lower frequencies are buggy.
|
tercio@0
|
7 --
|
tercio@0
|
8 -- All `:Schedule` functions will return a handle to the current timer, which you will need to store if you
|
tercio@0
|
9 -- need to cancel the timer you just registered.
|
tercio@0
|
10 --
|
tercio@0
|
11 -- **AceTimer-3.0** can be embeded into your addon, either explicitly by calling AceTimer:Embed(MyAddon) or by
|
tercio@0
|
12 -- specifying it as an embeded library in your AceAddon. All functions will be available on your addon object
|
tercio@0
|
13 -- and can be accessed directly, without having to explicitly call AceTimer itself.\\
|
tercio@0
|
14 -- It is recommended to embed AceTimer, otherwise you'll have to specify a custom `self` on all calls you
|
tercio@0
|
15 -- make into AceTimer.
|
tercio@0
|
16 -- @class file
|
tercio@0
|
17 -- @name AceTimer-3.0
|
tercio@0
|
18 -- @release $Id: AceTimer-3.0.lua 1079 2013-02-17 19:56:06Z funkydude $
|
tercio@0
|
19
|
tercio@0
|
20 local MAJOR, MINOR = "AceTimer-3.0", 16 -- Bump minor on changes
|
tercio@0
|
21 local AceTimer, oldminor = LibStub:NewLibrary(MAJOR, MINOR)
|
tercio@0
|
22
|
tercio@0
|
23 if not AceTimer then return end -- No upgrade needed
|
tercio@0
|
24
|
tercio@0
|
25 AceTimer.frame = AceTimer.frame or CreateFrame("Frame", "AceTimer30Frame") -- Animation parent
|
tercio@0
|
26 AceTimer.inactiveTimers = AceTimer.inactiveTimers or {} -- Timer recycling storage
|
tercio@0
|
27 AceTimer.activeTimers = AceTimer.activeTimers or {} -- Active timer list
|
tercio@0
|
28
|
tercio@0
|
29 -- Lua APIs
|
tercio@0
|
30 local type, unpack, next, error, pairs, tostring, select = type, unpack, next, error, pairs, tostring, select
|
tercio@0
|
31
|
tercio@0
|
32 -- Upvalue our private data
|
tercio@0
|
33 local inactiveTimers = AceTimer.inactiveTimers
|
tercio@0
|
34 local activeTimers = AceTimer.activeTimers
|
tercio@0
|
35
|
tercio@0
|
36 local function OnFinished(self)
|
tercio@0
|
37 local id = self.id
|
tercio@0
|
38 if type(self.func) == "string" then
|
tercio@0
|
39 -- We manually set the unpack count to prevent issues with an arg set that contains nil and ends with nil
|
tercio@0
|
40 -- e.g. local t = {1, 2, nil, 3, nil} print(#t) will result in 2, instead of 5. This fixes said issue.
|
tercio@0
|
41 self.object[self.func](self.object, unpack(self.args, 1, self.argsCount))
|
tercio@0
|
42 else
|
tercio@0
|
43 self.func(unpack(self.args, 1, self.argsCount))
|
tercio@0
|
44 end
|
tercio@0
|
45
|
tercio@0
|
46 -- If the id is different it means that the timer was already cancelled
|
tercio@0
|
47 -- and has been used to create a new timer during the OnFinished callback.
|
tercio@0
|
48 if not self.looping and id == self.id then
|
tercio@0
|
49 activeTimers[self.id] = nil
|
tercio@0
|
50 self.args = nil
|
tercio@0
|
51 inactiveTimers[self] = true
|
tercio@0
|
52 end
|
tercio@0
|
53 end
|
tercio@0
|
54
|
tercio@0
|
55 local function new(self, loop, func, delay, ...)
|
tercio@0
|
56 local timer = next(inactiveTimers)
|
tercio@0
|
57 if timer then
|
tercio@0
|
58 inactiveTimers[timer] = nil
|
tercio@0
|
59 else
|
tercio@0
|
60 local anim = AceTimer.frame:CreateAnimationGroup()
|
tercio@0
|
61 timer = anim:CreateAnimation()
|
tercio@0
|
62 timer:SetScript("OnFinished", OnFinished)
|
tercio@0
|
63 end
|
tercio@0
|
64
|
tercio@0
|
65 -- Very low delays cause the animations to fail randomly.
|
tercio@0
|
66 -- A limited resolution of 0.01 seems reasonable.
|
tercio@0
|
67 if delay < 0.01 then
|
tercio@0
|
68 delay = 0.01
|
tercio@0
|
69 end
|
tercio@0
|
70
|
tercio@0
|
71 timer.object = self
|
tercio@0
|
72 timer.func = func
|
tercio@0
|
73 timer.looping = loop
|
tercio@0
|
74 timer.args = {...}
|
tercio@0
|
75 timer.argsCount = select("#", ...)
|
tercio@0
|
76
|
tercio@0
|
77 local anim = timer:GetParent()
|
tercio@0
|
78 if loop then
|
tercio@0
|
79 anim:SetLooping("REPEAT")
|
tercio@0
|
80 else
|
tercio@0
|
81 anim:SetLooping("NONE")
|
tercio@0
|
82 end
|
tercio@0
|
83 timer:SetDuration(delay)
|
tercio@0
|
84
|
tercio@0
|
85 local id = tostring(timer.args)
|
tercio@0
|
86 timer.id = id
|
tercio@0
|
87 activeTimers[id] = timer
|
tercio@0
|
88
|
tercio@0
|
89 anim:Play()
|
tercio@0
|
90 return id
|
tercio@0
|
91 end
|
tercio@0
|
92
|
tercio@0
|
93 --- Schedule a new one-shot timer.
|
tercio@0
|
94 -- The timer will fire once in `delay` seconds, unless canceled before.
|
tercio@0
|
95 -- @param callback Callback function for the timer pulse (funcref or method name).
|
tercio@0
|
96 -- @param delay Delay for the timer, in seconds.
|
tercio@0
|
97 -- @param ... An optional, unlimited amount of arguments to pass to the callback function.
|
tercio@0
|
98 -- @usage
|
tercio@0
|
99 -- MyAddOn = LibStub("AceAddon-3.0"):NewAddon("MyAddOn", "AceTimer-3.0")
|
tercio@0
|
100 --
|
tercio@0
|
101 -- function MyAddOn:OnEnable()
|
tercio@0
|
102 -- self:ScheduleTimer("TimerFeedback", 5)
|
tercio@0
|
103 -- end
|
tercio@0
|
104 --
|
tercio@0
|
105 -- function MyAddOn:TimerFeedback()
|
tercio@0
|
106 -- print("5 seconds passed")
|
tercio@0
|
107 -- end
|
tercio@0
|
108 function AceTimer:ScheduleTimer(func, delay, ...)
|
tercio@0
|
109 if not func or not delay then
|
tercio@0
|
110 error(MAJOR..": ScheduleTimer(callback, delay, args...): 'callback' and 'delay' must have set values.", 2)
|
tercio@0
|
111 end
|
tercio@0
|
112 if type(func) == "string" then
|
tercio@0
|
113 if type(self) ~= "table" then
|
tercio@0
|
114 error(MAJOR..": ScheduleTimer(callback, delay, args...): 'self' - must be a table.", 2)
|
tercio@0
|
115 elseif not self[func] then
|
tercio@0
|
116 error(MAJOR..": ScheduleTimer(callback, delay, args...): Tried to register '"..func.."' as the callback, but it doesn't exist in the module.", 2)
|
tercio@0
|
117 end
|
tercio@0
|
118 end
|
tercio@0
|
119 return new(self, nil, func, delay, ...)
|
tercio@0
|
120 end
|
tercio@0
|
121
|
tercio@0
|
122 --- Schedule a repeating timer.
|
tercio@0
|
123 -- The timer will fire every `delay` seconds, until canceled.
|
tercio@0
|
124 -- @param callback Callback function for the timer pulse (funcref or method name).
|
tercio@0
|
125 -- @param delay Delay for the timer, in seconds.
|
tercio@0
|
126 -- @param ... An optional, unlimited amount of arguments to pass to the callback function.
|
tercio@0
|
127 -- @usage
|
tercio@0
|
128 -- MyAddOn = LibStub("AceAddon-3.0"):NewAddon("MyAddOn", "AceTimer-3.0")
|
tercio@0
|
129 --
|
tercio@0
|
130 -- function MyAddOn:OnEnable()
|
tercio@0
|
131 -- self.timerCount = 0
|
tercio@0
|
132 -- self.testTimer = self:ScheduleRepeatingTimer("TimerFeedback", 5)
|
tercio@0
|
133 -- end
|
tercio@0
|
134 --
|
tercio@0
|
135 -- function MyAddOn:TimerFeedback()
|
tercio@0
|
136 -- self.timerCount = self.timerCount + 1
|
tercio@0
|
137 -- print(("%d seconds passed"):format(5 * self.timerCount))
|
tercio@0
|
138 -- -- run 30 seconds in total
|
tercio@0
|
139 -- if self.timerCount == 6 then
|
tercio@0
|
140 -- self:CancelTimer(self.testTimer)
|
tercio@0
|
141 -- end
|
tercio@0
|
142 -- end
|
tercio@0
|
143 function AceTimer:ScheduleRepeatingTimer(func, delay, ...)
|
tercio@0
|
144 if not func or not delay then
|
tercio@0
|
145 error(MAJOR..": ScheduleRepeatingTimer(callback, delay, args...): 'callback' and 'delay' must have set values.", 2)
|
tercio@0
|
146 end
|
tercio@0
|
147 if type(func) == "string" then
|
tercio@0
|
148 if type(self) ~= "table" then
|
tercio@0
|
149 error(MAJOR..": ScheduleRepeatingTimer(callback, delay, args...): 'self' - must be a table.", 2)
|
tercio@0
|
150 elseif not self[func] then
|
tercio@0
|
151 error(MAJOR..": ScheduleRepeatingTimer(callback, delay, args...): Tried to register '"..func.."' as the callback, but it doesn't exist in the module.", 2)
|
tercio@0
|
152 end
|
tercio@0
|
153 end
|
tercio@0
|
154 return new(self, true, func, delay, ...)
|
tercio@0
|
155 end
|
tercio@0
|
156
|
tercio@0
|
157 --- Cancels a timer with the given id, registered by the same addon object as used for `:ScheduleTimer`
|
tercio@0
|
158 -- Both one-shot and repeating timers can be canceled with this function, as long as the `id` is valid
|
tercio@0
|
159 -- and the timer has not fired yet or was canceled before.
|
tercio@0
|
160 -- @param id The id of the timer, as returned by `:ScheduleTimer` or `:ScheduleRepeatingTimer`
|
tercio@0
|
161 function AceTimer:CancelTimer(id)
|
tercio@0
|
162 local timer = activeTimers[id]
|
tercio@0
|
163 if not timer then return false end
|
tercio@0
|
164
|
tercio@0
|
165 local anim = timer:GetParent()
|
tercio@0
|
166 anim:Stop()
|
tercio@0
|
167
|
tercio@0
|
168 activeTimers[id] = nil
|
tercio@0
|
169 timer.args = nil
|
tercio@0
|
170 inactiveTimers[timer] = true
|
tercio@0
|
171 return true
|
tercio@0
|
172 end
|
tercio@0
|
173
|
tercio@0
|
174 --- Cancels all timers registered to the current addon object ('self')
|
tercio@0
|
175 function AceTimer:CancelAllTimers()
|
tercio@0
|
176 for k,v in pairs(activeTimers) do
|
tercio@0
|
177 if v.object == self then
|
tercio@0
|
178 AceTimer.CancelTimer(self, k)
|
tercio@0
|
179 end
|
tercio@0
|
180 end
|
tercio@0
|
181 end
|
tercio@0
|
182
|
tercio@0
|
183 --- Returns the time left for a timer with the given id, registered by the current addon object ('self').
|
tercio@0
|
184 -- This function will return 0 when the id is invalid.
|
tercio@0
|
185 -- @param id The id of the timer, as returned by `:ScheduleTimer` or `:ScheduleRepeatingTimer`
|
tercio@0
|
186 -- @return The time left on the timer.
|
tercio@0
|
187 function AceTimer:TimeLeft(id)
|
tercio@0
|
188 local timer = activeTimers[id]
|
tercio@0
|
189 if not timer then return 0 end
|
tercio@0
|
190 return timer:GetDuration() - timer:GetElapsed()
|
tercio@0
|
191 end
|
tercio@0
|
192
|
tercio@0
|
193
|
tercio@0
|
194 -- ---------------------------------------------------------------------
|
tercio@0
|
195 -- Upgrading
|
tercio@0
|
196
|
tercio@0
|
197 -- Upgrade from old hash-bucket based timers to animation timers
|
tercio@0
|
198 if oldminor and oldminor < 10 then
|
tercio@0
|
199 -- disable old timer logic
|
tercio@0
|
200 AceTimer.frame:SetScript("OnUpdate", nil)
|
tercio@0
|
201 AceTimer.frame:SetScript("OnEvent", nil)
|
tercio@0
|
202 AceTimer.frame:UnregisterAllEvents()
|
tercio@0
|
203 -- convert timers
|
tercio@0
|
204 for object,timers in pairs(AceTimer.selfs) do
|
tercio@0
|
205 for handle,timer in pairs(timers) do
|
tercio@0
|
206 if type(timer) == "table" and timer.callback then
|
tercio@0
|
207 local id
|
tercio@0
|
208 if timer.delay then
|
tercio@0
|
209 id = AceTimer.ScheduleRepeatingTimer(timer.object, timer.callback, timer.delay, timer.arg)
|
tercio@0
|
210 else
|
tercio@0
|
211 id = AceTimer.ScheduleTimer(timer.object, timer.callback, timer.when - GetTime(), timer.arg)
|
tercio@0
|
212 end
|
tercio@0
|
213 -- change id to the old handle
|
tercio@0
|
214 local t = activeTimers[id]
|
tercio@0
|
215 activeTimers[id] = nil
|
tercio@0
|
216 activeTimers[handle] = t
|
tercio@0
|
217 t.id = handle
|
tercio@0
|
218 end
|
tercio@0
|
219 end
|
tercio@0
|
220 end
|
tercio@0
|
221 AceTimer.selfs = nil
|
tercio@0
|
222 AceTimer.hash = nil
|
tercio@0
|
223 AceTimer.debug = nil
|
tercio@0
|
224 elseif oldminor and oldminor < 13 then
|
tercio@0
|
225 for handle, id in pairs(AceTimer.hashCompatTable) do
|
tercio@0
|
226 local t = activeTimers[id]
|
tercio@0
|
227 if t then
|
tercio@0
|
228 activeTimers[id] = nil
|
tercio@0
|
229 activeTimers[handle] = t
|
tercio@0
|
230 t.id = handle
|
tercio@0
|
231 end
|
tercio@0
|
232 end
|
tercio@0
|
233 AceTimer.hashCompatTable = nil
|
tercio@0
|
234 end
|
tercio@0
|
235
|
tercio@0
|
236 -- upgrade existing timers to the latest OnFinished
|
tercio@0
|
237 for timer in pairs(inactiveTimers) do
|
tercio@0
|
238 timer:SetScript("OnFinished", OnFinished)
|
tercio@0
|
239 end
|
tercio@0
|
240
|
tercio@0
|
241 for _,timer in pairs(activeTimers) do
|
tercio@0
|
242 timer:SetScript("OnFinished", OnFinished)
|
tercio@0
|
243 end
|
tercio@0
|
244
|
tercio@0
|
245 -- ---------------------------------------------------------------------
|
tercio@0
|
246 -- Embed handling
|
tercio@0
|
247
|
tercio@0
|
248 AceTimer.embeds = AceTimer.embeds or {}
|
tercio@0
|
249
|
tercio@0
|
250 local mixins = {
|
tercio@0
|
251 "ScheduleTimer", "ScheduleRepeatingTimer",
|
tercio@0
|
252 "CancelTimer", "CancelAllTimers",
|
tercio@0
|
253 "TimeLeft"
|
tercio@0
|
254 }
|
tercio@0
|
255
|
tercio@0
|
256 function AceTimer:Embed(target)
|
tercio@0
|
257 AceTimer.embeds[target] = true
|
tercio@0
|
258 for _,v in pairs(mixins) do
|
tercio@0
|
259 target[v] = AceTimer[v]
|
tercio@0
|
260 end
|
tercio@0
|
261 return target
|
tercio@0
|
262 end
|
tercio@0
|
263
|
tercio@0
|
264 -- AceTimer:OnEmbedDisable(target)
|
tercio@0
|
265 -- target (object) - target object that AceTimer is embedded in.
|
tercio@0
|
266 --
|
tercio@0
|
267 -- cancel all timers registered for the object
|
tercio@0
|
268 function AceTimer:OnEmbedDisable(target)
|
tercio@0
|
269 target:CancelAllTimers()
|
tercio@0
|
270 end
|
tercio@0
|
271
|
tercio@0
|
272 for addon in pairs(AceTimer.embeds) do
|
tercio@0
|
273 AceTimer:Embed(addon)
|
tercio@0
|
274 end
|