comparison Libs/AceTimer-3.0/AceTimer-3.0.lua @ 5:c31ee4251181

Libs Update
author tercio
date Tue, 25 Nov 2014 21:15:10 -0200
parents fc346da3afd9
children 9ad7f3c634f1
comparison
equal deleted inserted replaced
4:453c68ff5d72 5:c31ee4251181
1 --- **AceTimer-3.0** provides a central facility for registering timers. 1 --- **AceTimer-3.0** provides a central facility for registering timers.
2 -- AceTimer supports one-shot timers and repeating timers. All timers are stored in an efficient 2 -- AceTimer supports one-shot timers and repeating timers. All timers are stored in an efficient
3 -- data structure that allows easy dispatching and fast rescheduling. Timers can be registered 3 -- data structure that allows easy dispatching and fast rescheduling. Timers can be registered
4 -- or canceled at any time, even from within a running timer, without conflict or large overhead.\\ 4 -- or canceled at any time, even from within a running timer, without conflict or large overhead.\\
5 -- AceTimer is currently limited to firing timers at a frequency of 0.01s. This constant may change 5 -- AceTimer is currently limited to firing timers at a frequency of 0.01s as this is what the WoW timer API
6 -- in the future, but for now it's required as animations with lower frequencies are buggy. 6 -- restricts us to.
7 -- 7 --
8 -- All `:Schedule` functions will return a handle to the current timer, which you will need to store if you 8 -- All `:Schedule` functions will return a handle to the current timer, which you will need to store if you
9 -- need to cancel the timer you just registered. 9 -- need to cancel the timer you just registered.
10 -- 10 --
11 -- **AceTimer-3.0** can be embeded into your addon, either explicitly by calling AceTimer:Embed(MyAddon) or by 11 -- **AceTimer-3.0** can be embeded into your addon, either explicitly by calling AceTimer:Embed(MyAddon) or by
13 -- and can be accessed directly, without having to explicitly call AceTimer itself.\\ 13 -- and can be accessed directly, without having to explicitly call AceTimer itself.\\
14 -- It is recommended to embed AceTimer, otherwise you'll have to specify a custom `self` on all calls you 14 -- It is recommended to embed AceTimer, otherwise you'll have to specify a custom `self` on all calls you
15 -- make into AceTimer. 15 -- make into AceTimer.
16 -- @class file 16 -- @class file
17 -- @name AceTimer-3.0 17 -- @name AceTimer-3.0
18 -- @release $Id: AceTimer-3.0.lua 1079 2013-02-17 19:56:06Z funkydude $ 18 -- @release $Id: AceTimer-3.0.lua 1119 2014-10-14 17:23:29Z nevcairiel $
19 19
20 local MAJOR, MINOR = "AceTimer-3.0", 16 -- Bump minor on changes 20 local MAJOR, MINOR = "AceTimer-3.0", 17 -- Bump minor on changes
21 local AceTimer, oldminor = LibStub:NewLibrary(MAJOR, MINOR) 21 local AceTimer, oldminor = LibStub:NewLibrary(MAJOR, MINOR)
22 22
23 if not AceTimer then return end -- No upgrade needed 23 if not AceTimer then return end -- No upgrade needed
24 24 AceTimer.activeTimers = AceTimer.activeTimers or {} -- Active timer list
25 AceTimer.frame = AceTimer.frame or CreateFrame("Frame", "AceTimer30Frame") -- Animation parent 25 local activeTimers = AceTimer.activeTimers -- Upvalue our private data
26 AceTimer.inactiveTimers = AceTimer.inactiveTimers or {} -- Timer recycling storage
27 AceTimer.activeTimers = AceTimer.activeTimers or {} -- Active timer list
28 26
29 -- Lua APIs 27 -- Lua APIs
30 local type, unpack, next, error, pairs, tostring, select = type, unpack, next, error, pairs, tostring, select 28 local type, unpack, next, error, select = type, unpack, next, error, select
31 29 -- WoW APIs
32 -- Upvalue our private data 30 local GetTime, C_TimerAfter = GetTime, C_Timer.After
33 local inactiveTimers = AceTimer.inactiveTimers
34 local activeTimers = AceTimer.activeTimers
35
36 local function OnFinished(self)
37 local id = self.id
38 if type(self.func) == "string" then
39 -- We manually set the unpack count to prevent issues with an arg set that contains nil and ends with nil
40 -- e.g. local t = {1, 2, nil, 3, nil} print(#t) will result in 2, instead of 5. This fixes said issue.
41 self.object[self.func](self.object, unpack(self.args, 1, self.argsCount))
42 else
43 self.func(unpack(self.args, 1, self.argsCount))
44 end
45
46 -- If the id is different it means that the timer was already cancelled
47 -- and has been used to create a new timer during the OnFinished callback.
48 if not self.looping and id == self.id then
49 activeTimers[self.id] = nil
50 self.args = nil
51 inactiveTimers[self] = true
52 end
53 end
54 31
55 local function new(self, loop, func, delay, ...) 32 local function new(self, loop, func, delay, ...)
56 local timer = next(inactiveTimers)
57 if timer then
58 inactiveTimers[timer] = nil
59 else
60 local anim = AceTimer.frame:CreateAnimationGroup()
61 timer = anim:CreateAnimation()
62 timer:SetScript("OnFinished", OnFinished)
63 end
64
65 -- Very low delays cause the animations to fail randomly.
66 -- A limited resolution of 0.01 seems reasonable.
67 if delay < 0.01 then 33 if delay < 0.01 then
68 delay = 0.01 34 delay = 0.01 -- Restrict to the lowest time that the C_Timer API allows us
69 end 35 end
70 36
37 local timer = {...}
71 timer.object = self 38 timer.object = self
72 timer.func = func 39 timer.func = func
73 timer.looping = loop 40 timer.looping = loop
74 timer.args = {...}
75 timer.argsCount = select("#", ...) 41 timer.argsCount = select("#", ...)
76 42 timer.delay = delay
77 local anim = timer:GetParent() 43 timer.ends = GetTime() + delay
78 if loop then 44
79 anim:SetLooping("REPEAT") 45 activeTimers[timer] = timer
80 else 46
81 anim:SetLooping("NONE") 47 -- Create new timer closure to wrap the "timer" object
82 end 48 timer.callback = function()
83 timer:SetDuration(delay) 49 if not timer.cancelled then
84 50 if type(timer.func) == "string" then
85 local id = tostring(timer.args) 51 -- We manually set the unpack count to prevent issues with an arg set that contains nil and ends with nil
86 timer.id = id 52 -- e.g. local t = {1, 2, nil, 3, nil} print(#t) will result in 2, instead of 5. This fixes said issue.
87 activeTimers[id] = timer 53 timer.object[timer.func](timer.object, unpack(timer, 1, timer.argsCount))
88 54 else
89 anim:Play() 55 timer.func(unpack(timer, 1, timer.argsCount))
90 return id 56 end
57
58 if timer.looping and not timer.cancelled then
59 -- Compensate delay to get a perfect average delay, even if individual times don't match up perfectly
60 -- due to fps differences
61 local time = GetTime()
62 local delay = timer.delay - (time - timer.ends)
63 -- Ensure the delay doesn't go below the threshold
64 if delay < 0.01 then delay = 0.01 end
65 C_TimerAfter(delay, timer.callback)
66 timer.ends = time + delay
67 else
68 activeTimers[timer.handle or timer] = nil
69 end
70 end
71 end
72
73 C_TimerAfter(delay, timer.callback)
74 return timer
91 end 75 end
92 76
93 --- Schedule a new one-shot timer. 77 --- Schedule a new one-shot timer.
94 -- The timer will fire once in `delay` seconds, unless canceled before. 78 -- The timer will fire once in `delay` seconds, unless canceled before.
95 -- @param callback Callback function for the timer pulse (funcref or method name). 79 -- @param callback Callback function for the timer pulse (funcref or method name).
158 -- Both one-shot and repeating timers can be canceled with this function, as long as the `id` is valid 142 -- Both one-shot and repeating timers can be canceled with this function, as long as the `id` is valid
159 -- and the timer has not fired yet or was canceled before. 143 -- and the timer has not fired yet or was canceled before.
160 -- @param id The id of the timer, as returned by `:ScheduleTimer` or `:ScheduleRepeatingTimer` 144 -- @param id The id of the timer, as returned by `:ScheduleTimer` or `:ScheduleRepeatingTimer`
161 function AceTimer:CancelTimer(id) 145 function AceTimer:CancelTimer(id)
162 local timer = activeTimers[id] 146 local timer = activeTimers[id]
163 if not timer then return false end 147
164 148 if not timer then
165 local anim = timer:GetParent() 149 return false
166 anim:Stop() 150 else
167 151 timer.cancelled = true
168 activeTimers[id] = nil 152 activeTimers[id] = nil
169 timer.args = nil 153 return true
170 inactiveTimers[timer] = true 154 end
171 return true
172 end 155 end
173 156
174 --- Cancels all timers registered to the current addon object ('self') 157 --- Cancels all timers registered to the current addon object ('self')
175 function AceTimer:CancelAllTimers() 158 function AceTimer:CancelAllTimers()
176 for k,v in pairs(activeTimers) do 159 for k,v in pairs(activeTimers) do
184 -- This function will return 0 when the id is invalid. 167 -- This function will return 0 when the id is invalid.
185 -- @param id The id of the timer, as returned by `:ScheduleTimer` or `:ScheduleRepeatingTimer` 168 -- @param id The id of the timer, as returned by `:ScheduleTimer` or `:ScheduleRepeatingTimer`
186 -- @return The time left on the timer. 169 -- @return The time left on the timer.
187 function AceTimer:TimeLeft(id) 170 function AceTimer:TimeLeft(id)
188 local timer = activeTimers[id] 171 local timer = activeTimers[id]
189 if not timer then return 0 end 172 if not timer then
190 return timer:GetDuration() - timer:GetElapsed() 173 return 0
174 else
175 return timer.ends - GetTime()
176 end
191 end 177 end
192 178
193 179
194 -- --------------------------------------------------------------------- 180 -- ---------------------------------------------------------------------
195 -- Upgrading 181 -- Upgrading
196 182
197 -- Upgrade from old hash-bucket based timers to animation timers 183 -- Upgrade from old hash-bucket based timers to C_Timer.After timers.
198 if oldminor and oldminor < 10 then 184 if oldminor and oldminor < 10 then
199 -- disable old timer logic 185 -- disable old timer logic
200 AceTimer.frame:SetScript("OnUpdate", nil) 186 AceTimer.frame:SetScript("OnUpdate", nil)
201 AceTimer.frame:SetScript("OnEvent", nil) 187 AceTimer.frame:SetScript("OnEvent", nil)
202 AceTimer.frame:UnregisterAllEvents() 188 AceTimer.frame:UnregisterAllEvents()
203 -- convert timers 189 -- convert timers
204 for object,timers in pairs(AceTimer.selfs) do 190 for object,timers in pairs(AceTimer.selfs) do
205 for handle,timer in pairs(timers) do 191 for handle,timer in pairs(timers) do
206 if type(timer) == "table" and timer.callback then 192 if type(timer) == "table" and timer.callback then
207 local id 193 local newTimer
208 if timer.delay then 194 if timer.delay then
209 id = AceTimer.ScheduleRepeatingTimer(timer.object, timer.callback, timer.delay, timer.arg) 195 newTimer = AceTimer.ScheduleRepeatingTimer(timer.object, timer.callback, timer.delay, timer.arg)
210 else 196 else
211 id = AceTimer.ScheduleTimer(timer.object, timer.callback, timer.when - GetTime(), timer.arg) 197 newTimer = AceTimer.ScheduleTimer(timer.object, timer.callback, timer.when - GetTime(), timer.arg)
212 end 198 end
213 -- change id to the old handle 199 -- Use the old handle for old timers
214 local t = activeTimers[id] 200 activeTimers[newTimer] = nil
215 activeTimers[id] = nil 201 activeTimers[handle] = newTimer
216 activeTimers[handle] = t 202 newTimer.handle = handle
217 t.id = handle
218 end 203 end
219 end 204 end
220 end 205 end
221 AceTimer.selfs = nil 206 AceTimer.selfs = nil
222 AceTimer.hash = nil 207 AceTimer.hash = nil
223 AceTimer.debug = nil 208 AceTimer.debug = nil
224 elseif oldminor and oldminor < 13 then 209 elseif oldminor and oldminor < 17 then
225 for handle, id in pairs(AceTimer.hashCompatTable) do 210 -- Upgrade from old animation based timers to C_Timer.After timers.
226 local t = activeTimers[id] 211 AceTimer.inactiveTimers = nil
227 if t then 212 AceTimer.frame = nil
228 activeTimers[id] = nil 213 local oldTimers = AceTimer.activeTimers
229 activeTimers[handle] = t 214 -- Clear old timer table and update upvalue
230 t.id = handle 215 AceTimer.activeTimers = {}
231 end 216 activeTimers = AceTimer.activeTimers
232 end 217 for handle, timer in pairs(oldTimers) do
233 AceTimer.hashCompatTable = nil 218 local newTimer
234 end 219 -- Stop the old timer animation
235 220 local duration, elapsed = timer:GetDuration(), timer:GetElapsed()
236 -- upgrade existing timers to the latest OnFinished 221 timer:GetParent():Stop()
237 for timer in pairs(inactiveTimers) do 222 if timer.looping then
238 timer:SetScript("OnFinished", OnFinished) 223 newTimer = AceTimer.ScheduleRepeatingTimer(timer.object, timer.func, duration, unpack(timer.args, 1, timer.argsCount))
239 end 224 else
240 225 newTimer = AceTimer.ScheduleTimer(timer.object, timer.func, duration - elapsed, unpack(timer.args, 1, timer.argsCount))
241 for _,timer in pairs(activeTimers) do 226 end
242 timer:SetScript("OnFinished", OnFinished) 227 -- Use the old handle for old timers
228 activeTimers[newTimer] = nil
229 activeTimers[handle] = newTimer
230 newTimer.handle = handle
231 end
232
233 -- Migrate transitional handles
234 if oldminor < 13 and AceTimer.hashCompatTable then
235 for handle, id in pairs(AceTimer.hashCompatTable) do
236 local t = activeTimers[id]
237 if t then
238 activeTimers[id] = nil
239 activeTimers[handle] = t
240 t.handle = handle
241 end
242 end
243 AceTimer.hashCompatTable = nil
244 end
243 end 245 end
244 246
245 -- --------------------------------------------------------------------- 247 -- ---------------------------------------------------------------------
246 -- Embed handling 248 -- Embed handling
247 249