annotate Libs/AceTimer-3.0/AceTimer-3.0.lua @ 0:c6ff7ba0e8f6

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