annotate Libs/AceTimer-3.0/AceTimer-3.0.lua @ 8:1b2d819b4fa8

Now using the global MailAddonBusy to indicate MailOpener is busy, read comments in Core.lua for more info. Default status is now ?enabled without automatic mail opening? to let first time users look around before MailOpener messes up their heads. A StaticPopupDialog will be added later to ask if they want to auto-enable. When ?enabled without automatic mail opening? is on and you toggle the mail opening checkbox on, mail opening will automatically start.
author Zerotorescue
date Thu, 09 Sep 2010 10:53:19 +0200
parents 823e33465b6e
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.