Asa@0
|
1 --- **AceTimer-3.0** provides a central facility for registering timers.
|
Asa@0
|
2 -- AceTimer supports one-shot timers and repeating timers. All timers are stored in an efficient
|
Asa@0
|
3 -- data structure that allows easy dispatching and fast rescheduling. Timers can be registered, rescheduled
|
Asa@0
|
4 -- or canceled at any time, even from within a running timer, without conflict or large overhead.\\
|
Asa@0
|
5 -- AceTimer is currently limited to firing timers at a frequency of 0.1s. This constant may change
|
Asa@0
|
6 -- in the future, but for now it seemed like a good compromise in efficiency and accuracy.
|
Asa@0
|
7 --
|
Asa@0
|
8 -- All `:Schedule` functions will return a handle to the current timer, which you will need to store if you
|
Asa@0
|
9 -- need to cancel or reschedule the timer you just registered.
|
Asa@0
|
10 --
|
Asa@0
|
11 -- **AceTimer-3.0** can be embeded into your addon, either explicitly by calling AceTimer:Embed(MyAddon) or by
|
Asa@0
|
12 -- specifying it as an embeded library in your AceAddon. All functions will be available on your addon object
|
Asa@0
|
13 -- and can be accessed directly, without having to explicitly call AceTimer itself.\\
|
Asa@0
|
14 -- It is recommended to embed AceTimer, otherwise you'll have to specify a custom `self` on all calls you
|
Asa@0
|
15 -- make into AceTimer.
|
Asa@0
|
16 -- @class file
|
Asa@0
|
17 -- @name AceTimer-3.0
|
Asa@0
|
18 -- @release $Id: AceTimer-3.0.lua 895 2009-12-06 16:28:55Z nevcairiel $
|
Asa@0
|
19
|
Asa@0
|
20 --[[
|
Asa@0
|
21 Basic assumptions:
|
Asa@0
|
22 * In a typical system, we do more re-scheduling per second than there are timer pulses per second
|
Asa@0
|
23 * Regardless of timer implementation, we cannot guarantee timely delivery due to FPS restriction (may be as low as 10)
|
Asa@0
|
24
|
Asa@0
|
25 This implementation:
|
Asa@0
|
26 CON: The smallest timer interval is constrained by HZ (currently 1/10s).
|
Asa@0
|
27 PRO: It will still correctly fire any timer slower than HZ over a length of time, e.g. 0.11s interval -> 90 times over 10 seconds
|
Asa@0
|
28 PRO: In lag bursts, the system simly skips missed timer intervals to decrease load
|
Asa@0
|
29 CON: Algorithms depending on a timer firing "N times per minute" will fail
|
Asa@0
|
30 PRO: (Re-)scheduling is O(1) with a VERY small constant. It's a simple linked list insertion in a hash bucket.
|
Asa@0
|
31 CAUTION: The BUCKETS constant constrains how many timers can be efficiently handled. With too many hash collisions, performance will decrease.
|
Asa@0
|
32
|
Asa@0
|
33 Major assumptions upheld:
|
Asa@0
|
34 - ALLOWS scheduling multiple timers with the same funcref/method
|
Asa@0
|
35 - ALLOWS scheduling more timers during OnUpdate processing
|
Asa@0
|
36 - ALLOWS unscheduling ANY timer (including the current running one) at any time, including during OnUpdate processing
|
Asa@0
|
37 ]]
|
Asa@0
|
38
|
Asa@0
|
39 local MAJOR, MINOR = "AceTimer-3.0", 5
|
Asa@0
|
40 local AceTimer, oldminor = LibStub:NewLibrary(MAJOR, MINOR)
|
Asa@0
|
41
|
Asa@0
|
42 if not AceTimer then return end -- No upgrade needed
|
Asa@0
|
43
|
Asa@0
|
44 AceTimer.hash = AceTimer.hash or {} -- Array of [0..BUCKET-1] = linked list of timers (using .next member)
|
Asa@0
|
45 -- Linked list gets around ACE-88 and ACE-90.
|
Asa@0
|
46 AceTimer.selfs = AceTimer.selfs or {} -- Array of [self]={[handle]=timerobj, [handle2]=timerobj2, ...}
|
Asa@0
|
47 AceTimer.frame = AceTimer.frame or CreateFrame("Frame", "AceTimer30Frame")
|
Asa@0
|
48
|
Asa@0
|
49 -- Lua APIs
|
Asa@0
|
50 local assert, error, loadstring = assert, error, loadstring
|
Asa@0
|
51 local setmetatable, rawset, rawget = setmetatable, rawset, rawget
|
Asa@0
|
52 local select, pairs, type, next, tostring = select, pairs, type, next, tostring
|
Asa@0
|
53 local floor, max, min = math.floor, math.max, math.min
|
Asa@0
|
54 local tconcat = table.concat
|
Asa@0
|
55
|
Asa@0
|
56 -- WoW APIs
|
Asa@0
|
57 local GetTime = GetTime
|
Asa@0
|
58
|
Asa@0
|
59 -- Global vars/functions that we don't upvalue since they might get hooked, or upgraded
|
Asa@0
|
60 -- List them here for Mikk's FindGlobals script
|
Asa@0
|
61 -- GLOBALS: DEFAULT_CHAT_FRAME, geterrorhandler
|
Asa@0
|
62
|
Asa@0
|
63 -- Simple ONE-SHOT timer cache. Much more efficient than a full compost for our purposes.
|
Asa@0
|
64 local timerCache = nil
|
Asa@0
|
65
|
Asa@0
|
66 --[[
|
Asa@0
|
67 Timers will not be fired more often than HZ-1 times per second.
|
Asa@0
|
68 Keep at intended speed PLUS ONE or we get bitten by floating point rounding errors (n.5 + 0.1 can be n.599999)
|
Asa@0
|
69 If this is ever LOWERED, all existing timers need to be enforced to have a delay >= 1/HZ on lib upgrade.
|
Asa@0
|
70 If this number is ever changed, all entries need to be rehashed on lib upgrade.
|
Asa@0
|
71 ]]
|
Asa@0
|
72 local HZ = 11
|
Asa@0
|
73
|
Asa@0
|
74 --[[
|
Asa@0
|
75 Prime for good distribution
|
Asa@0
|
76 If this number is ever changed, all entries need to be rehashed on lib upgrade.
|
Asa@0
|
77 ]]
|
Asa@0
|
78 local BUCKETS = 131
|
Asa@0
|
79
|
Asa@0
|
80 local hash = AceTimer.hash
|
Asa@0
|
81 for i=1,BUCKETS do
|
Asa@0
|
82 hash[i] = hash[i] or false -- make it an integer-indexed array; it's faster than hashes
|
Asa@0
|
83 end
|
Asa@0
|
84
|
Asa@0
|
85 --[[
|
Asa@0
|
86 xpcall safecall implementation
|
Asa@0
|
87 ]]
|
Asa@0
|
88 local xpcall = xpcall
|
Asa@0
|
89
|
Asa@0
|
90 local function errorhandler(err)
|
Asa@0
|
91 return geterrorhandler()(err)
|
Asa@0
|
92 end
|
Asa@0
|
93
|
Asa@0
|
94 local function CreateDispatcher(argCount)
|
Asa@0
|
95 local code = [[
|
Asa@0
|
96 local xpcall, eh = ... -- our arguments are received as unnamed values in "..." since we don't have a proper function declaration
|
Asa@0
|
97 local method, ARGS
|
Asa@0
|
98 local function call() return method(ARGS) end
|
Asa@0
|
99
|
Asa@0
|
100 local function dispatch(func, ...)
|
Asa@0
|
101 method = func
|
Asa@0
|
102 if not method then return end
|
Asa@0
|
103 ARGS = ...
|
Asa@0
|
104 return xpcall(call, eh)
|
Asa@0
|
105 end
|
Asa@0
|
106
|
Asa@0
|
107 return dispatch
|
Asa@0
|
108 ]]
|
Asa@0
|
109
|
Asa@0
|
110 local ARGS = {}
|
Asa@0
|
111 for i = 1, argCount do ARGS[i] = "arg"..i end
|
Asa@0
|
112 code = code:gsub("ARGS", tconcat(ARGS, ", "))
|
Asa@0
|
113 return assert(loadstring(code, "safecall Dispatcher["..argCount.."]"))(xpcall, errorhandler)
|
Asa@0
|
114 end
|
Asa@0
|
115
|
Asa@0
|
116 local Dispatchers = setmetatable({}, {
|
Asa@0
|
117 __index=function(self, argCount)
|
Asa@0
|
118 local dispatcher = CreateDispatcher(argCount)
|
Asa@0
|
119 rawset(self, argCount, dispatcher)
|
Asa@0
|
120 return dispatcher
|
Asa@0
|
121 end
|
Asa@0
|
122 })
|
Asa@0
|
123 Dispatchers[0] = function(func)
|
Asa@0
|
124 return xpcall(func, errorhandler)
|
Asa@0
|
125 end
|
Asa@0
|
126
|
Asa@0
|
127 local function safecall(func, ...)
|
Asa@0
|
128 return Dispatchers[select('#', ...)](func, ...)
|
Asa@0
|
129 end
|
Asa@0
|
130
|
Asa@0
|
131 local lastint = floor(GetTime() * HZ)
|
Asa@0
|
132
|
Asa@0
|
133 -- --------------------------------------------------------------------
|
Asa@0
|
134 -- OnUpdate handler
|
Asa@0
|
135 --
|
Asa@0
|
136 -- traverse buckets, always chasing "now", and fire timers that have expired
|
Asa@0
|
137
|
Asa@0
|
138 local function OnUpdate()
|
Asa@0
|
139 local now = GetTime()
|
Asa@0
|
140 local nowint = floor(now * HZ)
|
Asa@0
|
141
|
Asa@0
|
142 -- Have we passed into a new hash bucket?
|
Asa@0
|
143 if nowint == lastint then return end
|
Asa@0
|
144
|
Asa@0
|
145 local soon = now + 1 -- +1 is safe as long as 1 < HZ < BUCKETS/2
|
Asa@0
|
146
|
Asa@0
|
147 -- Pass through each bucket at most once
|
Asa@0
|
148 -- Happens on e.g. instance loads, but COULD happen on high local load situations also
|
Asa@0
|
149 for curint = (max(lastint, nowint - BUCKETS) + 1), nowint do -- loop until we catch up with "now", usually only 1 iteration
|
Asa@0
|
150 local curbucket = (curint % BUCKETS)+1
|
Asa@0
|
151 -- Yank the list of timers out of the bucket and empty it. This allows reinsertion in the currently-processed bucket from callbacks.
|
Asa@0
|
152 local nexttimer = hash[curbucket]
|
Asa@0
|
153 hash[curbucket] = false -- false rather than nil to prevent the array from becoming a hash
|
Asa@0
|
154
|
Asa@0
|
155 while nexttimer do
|
Asa@0
|
156 local timer = nexttimer
|
Asa@0
|
157 nexttimer = timer.next
|
Asa@0
|
158 local when = timer.when
|
Asa@0
|
159
|
Asa@0
|
160 if when < soon then
|
Asa@0
|
161 -- Call the timer func, either as a method on given object, or a straight function ref
|
Asa@0
|
162 local callback = timer.callback
|
Asa@0
|
163 if type(callback) == "string" then
|
Asa@0
|
164 safecall(timer.object[callback], timer.object, timer.arg)
|
Asa@0
|
165 elseif callback then
|
Asa@0
|
166 safecall(callback, timer.arg)
|
Asa@0
|
167 else
|
Asa@0
|
168 -- probably nilled out by CancelTimer
|
Asa@0
|
169 timer.delay = nil -- don't reschedule it
|
Asa@0
|
170 end
|
Asa@0
|
171
|
Asa@0
|
172 local delay = timer.delay -- NOW make a local copy, can't do it earlier in case the timer cancelled itself in the callback
|
Asa@0
|
173
|
Asa@0
|
174 if not delay then
|
Asa@0
|
175 -- single-shot timer (or cancelled)
|
Asa@0
|
176 AceTimer.selfs[timer.object][tostring(timer)] = nil
|
Asa@0
|
177 timerCache = timer
|
Asa@0
|
178 else
|
Asa@0
|
179 -- repeating timer
|
Asa@0
|
180 local newtime = when + delay
|
Asa@0
|
181 if newtime < now then -- Keep lag from making us firing a timer unnecessarily. (Note that this still won't catch too-short-delay timers though.)
|
Asa@0
|
182 newtime = now + delay
|
Asa@0
|
183 end
|
Asa@0
|
184 timer.when = newtime
|
Asa@0
|
185
|
Asa@0
|
186 -- add next timer execution to the correct bucket
|
Asa@0
|
187 local bucket = (floor(newtime * HZ) % BUCKETS) + 1
|
Asa@0
|
188 timer.next = hash[bucket]
|
Asa@0
|
189 hash[bucket] = timer
|
Asa@0
|
190 end
|
Asa@0
|
191 else -- if when>=soon
|
Asa@0
|
192 -- reinsert (yeah, somewhat expensive, but shouldn't be happening too often either due to hash distribution)
|
Asa@0
|
193 timer.next = hash[curbucket]
|
Asa@0
|
194 hash[curbucket] = timer
|
Asa@0
|
195 end -- if when<soon ... else
|
Asa@0
|
196 end -- while nexttimer do
|
Asa@0
|
197 end -- for curint=lastint,nowint
|
Asa@0
|
198
|
Asa@0
|
199 lastint = nowint
|
Asa@0
|
200 end
|
Asa@0
|
201
|
Asa@0
|
202 -- ---------------------------------------------------------------------
|
Asa@0
|
203 -- Reg( callback, delay, arg, repeating )
|
Asa@0
|
204 --
|
Asa@0
|
205 -- callback( function or string ) - direct function ref or method name in our object for the callback
|
Asa@0
|
206 -- delay(int) - delay for the timer
|
Asa@0
|
207 -- arg(variant) - any argument to be passed to the callback function
|
Asa@0
|
208 -- repeating(boolean) - repeating timer, or oneshot
|
Asa@0
|
209 --
|
Asa@0
|
210 -- returns the handle of the timer for later processing (canceling etc)
|
Asa@0
|
211 local function Reg(self, callback, delay, arg, repeating)
|
Asa@0
|
212 if type(callback) ~= "string" and type(callback) ~= "function" then
|
Asa@0
|
213 local error_origin = repeating and "ScheduleRepeatingTimer" or "ScheduleTimer"
|
Asa@0
|
214 error(MAJOR..": " .. error_origin .. "(callback, delay, arg): 'callback' - function or method name expected.", 3)
|
Asa@0
|
215 end
|
Asa@0
|
216 if type(callback) == "string" then
|
Asa@0
|
217 if type(self)~="table" then
|
Asa@0
|
218 local error_origin = repeating and "ScheduleRepeatingTimer" or "ScheduleTimer"
|
Asa@0
|
219 error(MAJOR..": " .. error_origin .. "(\"methodName\", delay, arg): 'self' - must be a table.", 3)
|
Asa@0
|
220 end
|
Asa@0
|
221 if type(self[callback]) ~= "function" then
|
Asa@0
|
222 local error_origin = repeating and "ScheduleRepeatingTimer" or "ScheduleTimer"
|
Asa@0
|
223 error(MAJOR..": " .. error_origin .. "(\"methodName\", delay, arg): 'methodName' - method not found on target object.", 3)
|
Asa@0
|
224 end
|
Asa@0
|
225 end
|
Asa@0
|
226
|
Asa@0
|
227 if delay < (1 / (HZ - 1)) then
|
Asa@0
|
228 delay = 1 / (HZ - 1)
|
Asa@0
|
229 end
|
Asa@0
|
230
|
Asa@0
|
231 -- Create and stuff timer in the correct hash bucket
|
Asa@0
|
232 local now = GetTime()
|
Asa@0
|
233
|
Asa@0
|
234 local timer = timerCache or {} -- Get new timer object (from cache if available)
|
Asa@0
|
235 timerCache = nil
|
Asa@0
|
236
|
Asa@0
|
237 timer.object = self
|
Asa@0
|
238 timer.callback = callback
|
Asa@0
|
239 timer.delay = (repeating and delay)
|
Asa@0
|
240 timer.arg = arg
|
Asa@0
|
241 timer.when = now + delay
|
Asa@0
|
242
|
Asa@0
|
243 local bucket = (floor((now+delay)*HZ) % BUCKETS) + 1
|
Asa@0
|
244 timer.next = hash[bucket]
|
Asa@0
|
245 hash[bucket] = timer
|
Asa@0
|
246
|
Asa@0
|
247 -- Insert timer in our self->handle->timer registry
|
Asa@0
|
248 local handle = tostring(timer)
|
Asa@0
|
249
|
Asa@0
|
250 local selftimers = AceTimer.selfs[self]
|
Asa@0
|
251 if not selftimers then
|
Asa@0
|
252 selftimers = {}
|
Asa@0
|
253 AceTimer.selfs[self] = selftimers
|
Asa@0
|
254 end
|
Asa@0
|
255 selftimers[handle] = timer
|
Asa@0
|
256 selftimers.__ops = (selftimers.__ops or 0) + 1
|
Asa@0
|
257
|
Asa@0
|
258 return handle
|
Asa@0
|
259 end
|
Asa@0
|
260
|
Asa@0
|
261 --- Schedule a new one-shot timer.
|
Asa@0
|
262 -- The timer will fire once in `delay` seconds, unless canceled before.
|
Asa@0
|
263 -- @param callback Callback function for the timer pulse (funcref or method name).
|
Asa@0
|
264 -- @param delay Delay for the timer, in seconds.
|
Asa@0
|
265 -- @param arg An optional argument to be passed to the callback function.
|
Asa@0
|
266 -- @usage
|
Asa@0
|
267 -- MyAddon = LibStub("AceAddon-3.0"):NewAddon("TimerTest", "AceTimer-3.0")
|
Asa@0
|
268 --
|
Asa@0
|
269 -- function MyAddon:OnEnable()
|
Asa@0
|
270 -- self:ScheduleTimer("TimerFeedback", 5)
|
Asa@0
|
271 -- end
|
Asa@0
|
272 --
|
Asa@0
|
273 -- function MyAddon:TimerFeedback()
|
Asa@0
|
274 -- print("5 seconds passed")
|
Asa@0
|
275 -- end
|
Asa@0
|
276 function AceTimer:ScheduleTimer(callback, delay, arg)
|
Asa@0
|
277 return Reg(self, callback, delay, arg)
|
Asa@0
|
278 end
|
Asa@0
|
279
|
Asa@0
|
280 --- Schedule a repeating timer.
|
Asa@0
|
281 -- The timer will fire every `delay` seconds, until canceled.
|
Asa@0
|
282 -- @param callback Callback function for the timer pulse (funcref or method name).
|
Asa@0
|
283 -- @param delay Delay for the timer, in seconds.
|
Asa@0
|
284 -- @param arg An optional argument to be passed to the callback function.
|
Asa@0
|
285 -- @usage
|
Asa@0
|
286 -- MyAddon = LibStub("AceAddon-3.0"):NewAddon("TimerTest", "AceTimer-3.0")
|
Asa@0
|
287 --
|
Asa@0
|
288 -- function MyAddon:OnEnable()
|
Asa@0
|
289 -- self.timerCount = 0
|
Asa@0
|
290 -- self.testTimer = self:ScheduleRepeatingTimer("TimerFeedback", 5)
|
Asa@0
|
291 -- end
|
Asa@0
|
292 --
|
Asa@0
|
293 -- function MyAddon:TimerFeedback()
|
Asa@0
|
294 -- self.timerCount = self.timerCount + 1
|
Asa@0
|
295 -- print(("%d seconds passed"):format(5 * self.timerCount))
|
Asa@0
|
296 -- -- run 30 seconds in total
|
Asa@0
|
297 -- if self.timerCount == 6 then
|
Asa@0
|
298 -- self:CancelTimer(self.testTimer)
|
Asa@0
|
299 -- end
|
Asa@0
|
300 -- end
|
Asa@0
|
301 function AceTimer:ScheduleRepeatingTimer(callback, delay, arg)
|
Asa@0
|
302 return Reg(self, callback, delay, arg, true)
|
Asa@0
|
303 end
|
Asa@0
|
304
|
Asa@0
|
305 --- Cancels a timer with the given handle, registered by the same addon object as used for `:ScheduleTimer`
|
Asa@0
|
306 -- Both one-shot and repeating timers can be canceled with this function, as long as the `handle` is valid
|
Asa@0
|
307 -- and the timer has not fired yet or was canceled before.
|
Asa@0
|
308 -- @param handle The handle of the timer, as returned by `:ScheduleTimer` or `:ScheduleRepeatingTimer`
|
Asa@0
|
309 -- @param silent If true, no error is raised if the timer handle is invalid (expired or already canceled)
|
Asa@0
|
310 -- @return True if the timer was successfully cancelled.
|
Asa@0
|
311 function AceTimer:CancelTimer(handle, silent)
|
Asa@0
|
312 if not handle then return end -- nil handle -> bail out without erroring
|
Asa@0
|
313 if type(handle) ~= "string" then
|
Asa@0
|
314 error(MAJOR..": CancelTimer(handle): 'handle' - expected a string", 2) -- for now, anyway
|
Asa@0
|
315 end
|
Asa@0
|
316 local selftimers = AceTimer.selfs[self]
|
Asa@0
|
317 local timer = selftimers and selftimers[handle]
|
Asa@0
|
318 if silent then
|
Asa@0
|
319 if timer then
|
Asa@0
|
320 timer.callback = nil -- don't run it again
|
Asa@0
|
321 timer.delay = nil -- if this is the currently-executing one: don't even reschedule
|
Asa@0
|
322 -- The timer object is removed in the OnUpdate loop
|
Asa@0
|
323 end
|
Asa@0
|
324 return not not timer -- might return "true" even if we double-cancel. we'll live.
|
Asa@0
|
325 else
|
Asa@0
|
326 if not timer then
|
Asa@0
|
327 geterrorhandler()(MAJOR..": CancelTimer(handle[, silent]): '"..tostring(handle).."' - no such timer registered")
|
Asa@0
|
328 return false
|
Asa@0
|
329 end
|
Asa@0
|
330 if not timer.callback then
|
Asa@0
|
331 geterrorhandler()(MAJOR..": CancelTimer(handle[, silent]): '"..tostring(handle).."' - timer already cancelled or expired")
|
Asa@0
|
332 return false
|
Asa@0
|
333 end
|
Asa@0
|
334 timer.callback = nil -- don't run it again
|
Asa@0
|
335 timer.delay = nil -- if this is the currently-executing one: don't even reschedule
|
Asa@0
|
336 return true
|
Asa@0
|
337 end
|
Asa@0
|
338 end
|
Asa@0
|
339
|
Asa@0
|
340 --- Cancels all timers registered to the current addon object ('self')
|
Asa@0
|
341 function AceTimer:CancelAllTimers()
|
Asa@0
|
342 if not(type(self) == "string" or type(self) == "table") then
|
Asa@0
|
343 error(MAJOR..": CancelAllTimers(): 'self' - must be a string or a table",2)
|
Asa@0
|
344 end
|
Asa@0
|
345 if self == AceTimer then
|
Asa@0
|
346 error(MAJOR..": CancelAllTimers(): supply a meaningful 'self'", 2)
|
Asa@0
|
347 end
|
Asa@0
|
348
|
Asa@0
|
349 local selftimers = AceTimer.selfs[self]
|
Asa@0
|
350 if selftimers then
|
Asa@0
|
351 for handle,v in pairs(selftimers) do
|
Asa@0
|
352 if type(v) == "table" then -- avoid __ops, etc
|
Asa@0
|
353 AceTimer.CancelTimer(self, handle, true)
|
Asa@0
|
354 end
|
Asa@0
|
355 end
|
Asa@0
|
356 end
|
Asa@0
|
357 end
|
Asa@0
|
358
|
Asa@0
|
359 --- Returns the time left for a timer with the given handle, registered by the current addon object ('self').
|
Asa@0
|
360 -- This function will raise a warning when the handle is invalid, but not stop execution.
|
Asa@0
|
361 -- @param handle The handle of the timer, as returned by `:ScheduleTimer` or `:ScheduleRepeatingTimer`
|
Asa@0
|
362 -- @return The time left on the timer, or false if the handle is invalid.
|
Asa@0
|
363 function AceTimer:TimeLeft(handle)
|
Asa@0
|
364 if not handle then return end
|
Asa@0
|
365 if type(handle) ~= "string" then
|
Asa@0
|
366 error(MAJOR..": TimeLeft(handle): 'handle' - expected a string", 2) -- for now, anyway
|
Asa@0
|
367 end
|
Asa@0
|
368 local selftimers = AceTimer.selfs[self]
|
Asa@0
|
369 local timer = selftimers and selftimers[handle]
|
Asa@0
|
370 if not timer then
|
Asa@0
|
371 geterrorhandler()(MAJOR..": TimeLeft(handle): '"..tostring(handle).."' - no such timer registered")
|
Asa@0
|
372 return false
|
Asa@0
|
373 end
|
Asa@0
|
374 return timer.when - GetTime()
|
Asa@0
|
375 end
|
Asa@0
|
376
|
Asa@0
|
377
|
Asa@0
|
378 -- ---------------------------------------------------------------------
|
Asa@0
|
379 -- PLAYER_REGEN_ENABLED: Run through our .selfs[] array step by step
|
Asa@0
|
380 -- and clean it out - otherwise the table indices can grow indefinitely
|
Asa@0
|
381 -- if an addon starts and stops a lot of timers. AceBucket does this!
|
Asa@0
|
382 --
|
Asa@0
|
383 -- See ACE-94 and tests/AceTimer-3.0-ACE-94.lua
|
Asa@0
|
384
|
Asa@0
|
385 local lastCleaned = nil
|
Asa@0
|
386
|
Asa@0
|
387 local function OnEvent(this, event)
|
Asa@0
|
388 if event~="PLAYER_REGEN_ENABLED" then
|
Asa@0
|
389 return
|
Asa@0
|
390 end
|
Asa@0
|
391
|
Asa@0
|
392 -- Get the next 'self' to process
|
Asa@0
|
393 local selfs = AceTimer.selfs
|
Asa@0
|
394 local self = next(selfs, lastCleaned)
|
Asa@0
|
395 if not self then
|
Asa@0
|
396 self = next(selfs)
|
Asa@0
|
397 end
|
Asa@0
|
398 lastCleaned = self
|
Asa@0
|
399 if not self then -- should only happen if .selfs[] is empty
|
Asa@0
|
400 return
|
Asa@0
|
401 end
|
Asa@0
|
402
|
Asa@0
|
403 -- Time to clean it out?
|
Asa@0
|
404 local list = selfs[self]
|
Asa@0
|
405 if (list.__ops or 0) < 250 then -- 250 slosh indices = ~10KB wasted (max!). For one 'self'.
|
Asa@0
|
406 return
|
Asa@0
|
407 end
|
Asa@0
|
408
|
Asa@0
|
409 -- Create a new table and copy all members over
|
Asa@0
|
410 local newlist = {}
|
Asa@0
|
411 local n=0
|
Asa@0
|
412 for k,v in pairs(list) do
|
Asa@0
|
413 newlist[k] = v
|
Asa@0
|
414 n=n+1
|
Asa@0
|
415 end
|
Asa@0
|
416 newlist.__ops = 0 -- Reset operation count
|
Asa@0
|
417
|
Asa@0
|
418 -- And since we now have a count of the number of live timers, check that it's reasonable. Emit a warning if not.
|
Asa@0
|
419 if n>BUCKETS then
|
Asa@0
|
420 DEFAULT_CHAT_FRAME:AddMessage(MAJOR..": Warning: The addon/module '"..tostring(self).."' has "..n.." live timers. Surely that's not intended?")
|
Asa@0
|
421 end
|
Asa@0
|
422
|
Asa@0
|
423 selfs[self] = newlist
|
Asa@0
|
424 end
|
Asa@0
|
425
|
Asa@0
|
426 -- ---------------------------------------------------------------------
|
Asa@0
|
427 -- Embed handling
|
Asa@0
|
428
|
Asa@0
|
429 AceTimer.embeds = AceTimer.embeds or {}
|
Asa@0
|
430
|
Asa@0
|
431 local mixins = {
|
Asa@0
|
432 "ScheduleTimer", "ScheduleRepeatingTimer",
|
Asa@0
|
433 "CancelTimer", "CancelAllTimers",
|
Asa@0
|
434 "TimeLeft"
|
Asa@0
|
435 }
|
Asa@0
|
436
|
Asa@0
|
437 function AceTimer:Embed(target)
|
Asa@0
|
438 AceTimer.embeds[target] = true
|
Asa@0
|
439 for _,v in pairs(mixins) do
|
Asa@0
|
440 target[v] = AceTimer[v]
|
Asa@0
|
441 end
|
Asa@0
|
442 return target
|
Asa@0
|
443 end
|
Asa@0
|
444
|
Asa@0
|
445 -- AceTimer:OnEmbedDisable( target )
|
Asa@0
|
446 -- target (object) - target object that AceTimer is embedded in.
|
Asa@0
|
447 --
|
Asa@0
|
448 -- cancel all timers registered for the object
|
Asa@0
|
449 function AceTimer:OnEmbedDisable( target )
|
Asa@0
|
450 target:CancelAllTimers()
|
Asa@0
|
451 end
|
Asa@0
|
452
|
Asa@0
|
453
|
Asa@0
|
454 for addon in pairs(AceTimer.embeds) do
|
Asa@0
|
455 AceTimer:Embed(addon)
|
Asa@0
|
456 end
|
Asa@0
|
457
|
Asa@0
|
458 -- ---------------------------------------------------------------------
|
Asa@0
|
459 -- Debug tools (expose copies of internals to test suites)
|
Asa@0
|
460 AceTimer.debug = AceTimer.debug or {}
|
Asa@0
|
461 AceTimer.debug.HZ = HZ
|
Asa@0
|
462 AceTimer.debug.BUCKETS = BUCKETS
|
Asa@0
|
463
|
Asa@0
|
464 -- ---------------------------------------------------------------------
|
Asa@0
|
465 -- Finishing touchups
|
Asa@0
|
466
|
Asa@0
|
467 AceTimer.frame:SetScript("OnUpdate", OnUpdate)
|
Asa@0
|
468 AceTimer.frame:SetScript("OnEvent", OnEvent)
|
Asa@0
|
469 AceTimer.frame:RegisterEvent("PLAYER_REGEN_ENABLED")
|
Asa@0
|
470
|
Asa@0
|
471 -- In theory, we should hide&show the frame based on there being timers or not.
|
Asa@0
|
472 -- However, this job is fairly expensive, and the chance that there will
|
Asa@0
|
473 -- actually be zero timers running is diminuitive to say the lest.
|