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@5
|
5 -- AceTimer is currently limited to firing timers at a frequency of 0.01s as this is what the WoW timer API
|
tercio@5
|
6 -- restricts us to.
|
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@20
|
18 -- @release $Id: AceTimer-3.0.lua 1170 2018-03-29 17:38:58Z funkydude $
|
tercio@0
|
19
|
tercio@5
|
20 local MAJOR, MINOR = "AceTimer-3.0", 17 -- 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@5
|
24 AceTimer.activeTimers = AceTimer.activeTimers or {} -- Active timer list
|
tercio@5
|
25 local activeTimers = AceTimer.activeTimers -- Upvalue our private data
|
tercio@0
|
26
|
tercio@0
|
27 -- Lua APIs
|
tercio@5
|
28 local type, unpack, next, error, select = type, unpack, next, error, select
|
tercio@5
|
29 -- WoW APIs
|
tercio@5
|
30 local GetTime, C_TimerAfter = GetTime, C_Timer.After
|
tercio@0
|
31
|
tercio@5
|
32 local function new(self, loop, func, delay, ...)
|
tercio@5
|
33 if delay < 0.01 then
|
tercio@5
|
34 delay = 0.01 -- Restrict to the lowest time that the C_Timer API allows us
|
tercio@0
|
35 end
|
tercio@0
|
36
|
Tercio@20
|
37 local timer = {
|
Tercio@20
|
38 object = self,
|
Tercio@20
|
39 func = func,
|
Tercio@20
|
40 looping = loop,
|
Tercio@20
|
41 argsCount = select("#", ...),
|
Tercio@20
|
42 delay = delay,
|
Tercio@20
|
43 ends = GetTime() + delay,
|
Tercio@20
|
44 ...
|
Tercio@20
|
45 }
|
tercio@0
|
46
|
tercio@5
|
47 activeTimers[timer] = timer
|
tercio@5
|
48
|
tercio@5
|
49 -- Create new timer closure to wrap the "timer" object
|
tercio@5
|
50 timer.callback = function()
|
tercio@5
|
51 if not timer.cancelled then
|
tercio@5
|
52 if type(timer.func) == "string" then
|
tercio@5
|
53 -- We manually set the unpack count to prevent issues with an arg set that contains nil and ends with nil
|
tercio@5
|
54 -- e.g. local t = {1, 2, nil, 3, nil} print(#t) will result in 2, instead of 5. This fixes said issue.
|
tercio@5
|
55 timer.object[timer.func](timer.object, unpack(timer, 1, timer.argsCount))
|
tercio@5
|
56 else
|
tercio@5
|
57 timer.func(unpack(timer, 1, timer.argsCount))
|
tercio@5
|
58 end
|
tercio@5
|
59
|
tercio@5
|
60 if timer.looping and not timer.cancelled then
|
tercio@5
|
61 -- Compensate delay to get a perfect average delay, even if individual times don't match up perfectly
|
tercio@5
|
62 -- due to fps differences
|
tercio@5
|
63 local time = GetTime()
|
tercio@5
|
64 local delay = timer.delay - (time - timer.ends)
|
tercio@5
|
65 -- Ensure the delay doesn't go below the threshold
|
tercio@5
|
66 if delay < 0.01 then delay = 0.01 end
|
tercio@5
|
67 C_TimerAfter(delay, timer.callback)
|
tercio@5
|
68 timer.ends = time + delay
|
tercio@5
|
69 else
|
tercio@5
|
70 activeTimers[timer.handle or timer] = nil
|
tercio@5
|
71 end
|
tercio@5
|
72 end
|
tercio@0
|
73 end
|
tercio@0
|
74
|
tercio@5
|
75 C_TimerAfter(delay, timer.callback)
|
tercio@5
|
76 return timer
|
tercio@0
|
77 end
|
tercio@0
|
78
|
tercio@0
|
79 --- Schedule a new one-shot timer.
|
tercio@0
|
80 -- The timer will fire once in `delay` seconds, unless canceled before.
|
tercio@0
|
81 -- @param callback Callback function for the timer pulse (funcref or method name).
|
tercio@0
|
82 -- @param delay Delay for the timer, in seconds.
|
tercio@0
|
83 -- @param ... An optional, unlimited amount of arguments to pass to the callback function.
|
tercio@0
|
84 -- @usage
|
tercio@0
|
85 -- MyAddOn = LibStub("AceAddon-3.0"):NewAddon("MyAddOn", "AceTimer-3.0")
|
tercio@0
|
86 --
|
tercio@0
|
87 -- function MyAddOn:OnEnable()
|
tercio@0
|
88 -- self:ScheduleTimer("TimerFeedback", 5)
|
tercio@0
|
89 -- end
|
tercio@0
|
90 --
|
tercio@0
|
91 -- function MyAddOn:TimerFeedback()
|
tercio@0
|
92 -- print("5 seconds passed")
|
tercio@0
|
93 -- end
|
tercio@0
|
94 function AceTimer:ScheduleTimer(func, delay, ...)
|
tercio@0
|
95 if not func or not delay then
|
tercio@0
|
96 error(MAJOR..": ScheduleTimer(callback, delay, args...): 'callback' and 'delay' must have set values.", 2)
|
tercio@0
|
97 end
|
tercio@0
|
98 if type(func) == "string" then
|
tercio@0
|
99 if type(self) ~= "table" then
|
tercio@0
|
100 error(MAJOR..": ScheduleTimer(callback, delay, args...): 'self' - must be a table.", 2)
|
tercio@0
|
101 elseif not self[func] then
|
tercio@0
|
102 error(MAJOR..": ScheduleTimer(callback, delay, args...): Tried to register '"..func.."' as the callback, but it doesn't exist in the module.", 2)
|
tercio@0
|
103 end
|
tercio@0
|
104 end
|
tercio@0
|
105 return new(self, nil, func, delay, ...)
|
tercio@0
|
106 end
|
tercio@0
|
107
|
tercio@0
|
108 --- Schedule a repeating timer.
|
tercio@0
|
109 -- The timer will fire every `delay` seconds, until canceled.
|
tercio@0
|
110 -- @param callback Callback function for the timer pulse (funcref or method name).
|
tercio@0
|
111 -- @param delay Delay for the timer, in seconds.
|
tercio@0
|
112 -- @param ... An optional, unlimited amount of arguments to pass to the callback function.
|
tercio@0
|
113 -- @usage
|
tercio@0
|
114 -- MyAddOn = LibStub("AceAddon-3.0"):NewAddon("MyAddOn", "AceTimer-3.0")
|
tercio@0
|
115 --
|
tercio@0
|
116 -- function MyAddOn:OnEnable()
|
tercio@0
|
117 -- self.timerCount = 0
|
tercio@0
|
118 -- self.testTimer = self:ScheduleRepeatingTimer("TimerFeedback", 5)
|
tercio@0
|
119 -- end
|
tercio@0
|
120 --
|
tercio@0
|
121 -- function MyAddOn:TimerFeedback()
|
tercio@0
|
122 -- self.timerCount = self.timerCount + 1
|
tercio@0
|
123 -- print(("%d seconds passed"):format(5 * self.timerCount))
|
tercio@0
|
124 -- -- run 30 seconds in total
|
tercio@0
|
125 -- if self.timerCount == 6 then
|
tercio@0
|
126 -- self:CancelTimer(self.testTimer)
|
tercio@0
|
127 -- end
|
tercio@0
|
128 -- end
|
tercio@0
|
129 function AceTimer:ScheduleRepeatingTimer(func, delay, ...)
|
tercio@0
|
130 if not func or not delay then
|
tercio@0
|
131 error(MAJOR..": ScheduleRepeatingTimer(callback, delay, args...): 'callback' and 'delay' must have set values.", 2)
|
tercio@0
|
132 end
|
tercio@0
|
133 if type(func) == "string" then
|
tercio@0
|
134 if type(self) ~= "table" then
|
tercio@0
|
135 error(MAJOR..": ScheduleRepeatingTimer(callback, delay, args...): 'self' - must be a table.", 2)
|
tercio@0
|
136 elseif not self[func] then
|
tercio@0
|
137 error(MAJOR..": ScheduleRepeatingTimer(callback, delay, args...): Tried to register '"..func.."' as the callback, but it doesn't exist in the module.", 2)
|
tercio@0
|
138 end
|
tercio@0
|
139 end
|
tercio@0
|
140 return new(self, true, func, delay, ...)
|
tercio@0
|
141 end
|
tercio@0
|
142
|
tercio@0
|
143 --- Cancels a timer with the given id, registered by the same addon object as used for `:ScheduleTimer`
|
tercio@0
|
144 -- Both one-shot and repeating timers can be canceled with this function, as long as the `id` is valid
|
tercio@0
|
145 -- and the timer has not fired yet or was canceled before.
|
tercio@0
|
146 -- @param id The id of the timer, as returned by `:ScheduleTimer` or `:ScheduleRepeatingTimer`
|
tercio@0
|
147 function AceTimer:CancelTimer(id)
|
tercio@0
|
148 local timer = activeTimers[id]
|
tercio@0
|
149
|
tercio@5
|
150 if not timer then
|
tercio@5
|
151 return false
|
tercio@5
|
152 else
|
tercio@5
|
153 timer.cancelled = true
|
tercio@5
|
154 activeTimers[id] = nil
|
tercio@5
|
155 return true
|
tercio@5
|
156 end
|
tercio@0
|
157 end
|
tercio@0
|
158
|
tercio@0
|
159 --- Cancels all timers registered to the current addon object ('self')
|
tercio@0
|
160 function AceTimer:CancelAllTimers()
|
Tercio@20
|
161 for k,v in next, activeTimers do
|
tercio@0
|
162 if v.object == self then
|
tercio@0
|
163 AceTimer.CancelTimer(self, k)
|
tercio@0
|
164 end
|
tercio@0
|
165 end
|
tercio@0
|
166 end
|
tercio@0
|
167
|
tercio@0
|
168 --- Returns the time left for a timer with the given id, registered by the current addon object ('self').
|
tercio@0
|
169 -- This function will return 0 when the id is invalid.
|
tercio@0
|
170 -- @param id The id of the timer, as returned by `:ScheduleTimer` or `:ScheduleRepeatingTimer`
|
tercio@0
|
171 -- @return The time left on the timer.
|
tercio@0
|
172 function AceTimer:TimeLeft(id)
|
tercio@0
|
173 local timer = activeTimers[id]
|
tercio@5
|
174 if not timer then
|
tercio@5
|
175 return 0
|
tercio@5
|
176 else
|
tercio@5
|
177 return timer.ends - GetTime()
|
tercio@5
|
178 end
|
tercio@0
|
179 end
|
tercio@0
|
180
|
tercio@0
|
181
|
tercio@0
|
182 -- ---------------------------------------------------------------------
|
tercio@0
|
183 -- Upgrading
|
tercio@0
|
184
|
tercio@5
|
185 -- Upgrade from old hash-bucket based timers to C_Timer.After timers.
|
tercio@0
|
186 if oldminor and oldminor < 10 then
|
tercio@0
|
187 -- disable old timer logic
|
tercio@0
|
188 AceTimer.frame:SetScript("OnUpdate", nil)
|
tercio@0
|
189 AceTimer.frame:SetScript("OnEvent", nil)
|
tercio@0
|
190 AceTimer.frame:UnregisterAllEvents()
|
tercio@0
|
191 -- convert timers
|
Tercio@20
|
192 for object,timers in next, AceTimer.selfs do
|
Tercio@20
|
193 for handle,timer in next, timers do
|
tercio@0
|
194 if type(timer) == "table" and timer.callback then
|
tercio@5
|
195 local newTimer
|
tercio@0
|
196 if timer.delay then
|
tercio@5
|
197 newTimer = AceTimer.ScheduleRepeatingTimer(timer.object, timer.callback, timer.delay, timer.arg)
|
tercio@0
|
198 else
|
tercio@5
|
199 newTimer = AceTimer.ScheduleTimer(timer.object, timer.callback, timer.when - GetTime(), timer.arg)
|
tercio@0
|
200 end
|
tercio@5
|
201 -- Use the old handle for old timers
|
tercio@5
|
202 activeTimers[newTimer] = nil
|
tercio@5
|
203 activeTimers[handle] = newTimer
|
tercio@5
|
204 newTimer.handle = handle
|
tercio@0
|
205 end
|
tercio@0
|
206 end
|
tercio@0
|
207 end
|
tercio@0
|
208 AceTimer.selfs = nil
|
tercio@0
|
209 AceTimer.hash = nil
|
tercio@0
|
210 AceTimer.debug = nil
|
tercio@5
|
211 elseif oldminor and oldminor < 17 then
|
tercio@5
|
212 -- Upgrade from old animation based timers to C_Timer.After timers.
|
tercio@5
|
213 AceTimer.inactiveTimers = nil
|
tercio@5
|
214 AceTimer.frame = nil
|
tercio@5
|
215 local oldTimers = AceTimer.activeTimers
|
tercio@5
|
216 -- Clear old timer table and update upvalue
|
tercio@5
|
217 AceTimer.activeTimers = {}
|
tercio@5
|
218 activeTimers = AceTimer.activeTimers
|
Tercio@20
|
219 for handle, timer in next, oldTimers do
|
tercio@5
|
220 local newTimer
|
tercio@5
|
221 -- Stop the old timer animation
|
tercio@5
|
222 local duration, elapsed = timer:GetDuration(), timer:GetElapsed()
|
tercio@5
|
223 timer:GetParent():Stop()
|
tercio@5
|
224 if timer.looping then
|
tercio@5
|
225 newTimer = AceTimer.ScheduleRepeatingTimer(timer.object, timer.func, duration, unpack(timer.args, 1, timer.argsCount))
|
tercio@5
|
226 else
|
tercio@5
|
227 newTimer = AceTimer.ScheduleTimer(timer.object, timer.func, duration - elapsed, unpack(timer.args, 1, timer.argsCount))
|
tercio@0
|
228 end
|
tercio@5
|
229 -- Use the old handle for old timers
|
tercio@5
|
230 activeTimers[newTimer] = nil
|
tercio@5
|
231 activeTimers[handle] = newTimer
|
tercio@5
|
232 newTimer.handle = handle
|
tercio@0
|
233 end
|
tercio@0
|
234
|
tercio@5
|
235 -- Migrate transitional handles
|
tercio@5
|
236 if oldminor < 13 and AceTimer.hashCompatTable then
|
Tercio@20
|
237 for handle, id in next, AceTimer.hashCompatTable do
|
tercio@5
|
238 local t = activeTimers[id]
|
tercio@5
|
239 if t then
|
tercio@5
|
240 activeTimers[id] = nil
|
tercio@5
|
241 activeTimers[handle] = t
|
tercio@5
|
242 t.handle = handle
|
tercio@5
|
243 end
|
tercio@5
|
244 end
|
tercio@5
|
245 AceTimer.hashCompatTable = nil
|
tercio@5
|
246 end
|
tercio@0
|
247 end
|
tercio@0
|
248
|
tercio@0
|
249 -- ---------------------------------------------------------------------
|
tercio@0
|
250 -- Embed handling
|
tercio@0
|
251
|
tercio@0
|
252 AceTimer.embeds = AceTimer.embeds or {}
|
tercio@0
|
253
|
tercio@0
|
254 local mixins = {
|
tercio@0
|
255 "ScheduleTimer", "ScheduleRepeatingTimer",
|
tercio@0
|
256 "CancelTimer", "CancelAllTimers",
|
tercio@0
|
257 "TimeLeft"
|
tercio@0
|
258 }
|
tercio@0
|
259
|
tercio@0
|
260 function AceTimer:Embed(target)
|
tercio@0
|
261 AceTimer.embeds[target] = true
|
Tercio@20
|
262 for _,v in next, mixins do
|
tercio@0
|
263 target[v] = AceTimer[v]
|
tercio@0
|
264 end
|
tercio@0
|
265 return target
|
tercio@0
|
266 end
|
tercio@0
|
267
|
tercio@0
|
268 -- AceTimer:OnEmbedDisable(target)
|
tercio@0
|
269 -- target (object) - target object that AceTimer is embedded in.
|
tercio@0
|
270 --
|
tercio@0
|
271 -- cancel all timers registered for the object
|
tercio@0
|
272 function AceTimer:OnEmbedDisable(target)
|
tercio@0
|
273 target:CancelAllTimers()
|
tercio@0
|
274 end
|
tercio@0
|
275
|
Tercio@20
|
276 for addon in next, AceTimer.embeds do
|
tercio@0
|
277 AceTimer:Embed(addon)
|
tercio@0
|
278 end
|