2a7fd87 - Change to UnitIsPlayer
[wowui.git] / libs / AceTimer-3.0 / AceTimer-3.0.lua
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
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.\\
5 -- AceTimer is currently limited to firing timers at a frequency of 0.01s. This constant may change
6 -- in the future, but for now it's required as animations with lower frequencies are buggy.
7 --
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.
10 --
11 -- **AceTimer-3.0** can be embeded into your addon, either explicitly by calling AceTimer:Embed(MyAddon) or by
12 -- specifying it as an embeded library in your AceAddon. All functions will be available on your addon object
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
15 -- make into AceTimer.
16 -- @class file
17 -- @name AceTimer-3.0
18 -- @release $Id: AceTimer-3.0.lua 1079 2013-02-17 19:56:06Z funkydude $
19
20 local MAJOR, MINOR = "AceTimer-3.0", 16 -- Bump minor on changes
21 local AceTimer, oldminor = LibStub:NewLibrary(MAJOR, MINOR)
22
23 if not AceTimer then return end -- No upgrade needed
24
25 AceTimer.frame = AceTimer.frame or CreateFrame("Frame", "AceTimer30Frame") -- Animation parent
26 AceTimer.inactiveTimers = AceTimer.inactiveTimers or {}                    -- Timer recycling storage
27 AceTimer.activeTimers = AceTimer.activeTimers or {}                        -- Active timer list
28
29 -- Lua APIs
30 local type, unpack, next, error, pairs, tostring, select = type, unpack, next, error, pairs, tostring, select
31
32 -- Upvalue our private data
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
55 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
68                 delay = 0.01
69         end
70
71         timer.object = self
72         timer.func = func
73         timer.looping = loop
74         timer.args = {...}
75         timer.argsCount = select("#", ...)
76
77         local anim = timer:GetParent()
78         if loop then
79                 anim:SetLooping("REPEAT")
80         else
81                 anim:SetLooping("NONE")
82         end
83         timer:SetDuration(delay)
84
85         local id = tostring(timer.args)
86         timer.id = id
87         activeTimers[id] = timer
88
89         anim:Play()
90         return id
91 end
92
93 --- Schedule a new one-shot timer.
94 -- The timer will fire once in `delay` seconds, unless canceled before.
95 -- @param callback Callback function for the timer pulse (funcref or method name).
96 -- @param delay Delay for the timer, in seconds.
97 -- @param ... An optional, unlimited amount of arguments to pass to the callback function.
98 -- @usage
99 -- MyAddOn = LibStub("AceAddon-3.0"):NewAddon("MyAddOn", "AceTimer-3.0")
100 --
101 -- function MyAddOn:OnEnable()
102 --   self:ScheduleTimer("TimerFeedback", 5)
103 -- end
104 --
105 -- function MyAddOn:TimerFeedback()
106 --   print("5 seconds passed")
107 -- end
108 function AceTimer:ScheduleTimer(func, delay, ...)
109         if not func or not delay then
110                 error(MAJOR..": ScheduleTimer(callback, delay, args...): 'callback' and 'delay' must have set values.", 2)
111         end
112         if type(func) == "string" then
113                 if type(self) ~= "table" then
114                         error(MAJOR..": ScheduleTimer(callback, delay, args...): 'self' - must be a table.", 2)
115                 elseif not self[func] then
116                         error(MAJOR..": ScheduleTimer(callback, delay, args...): Tried to register '"..func.."' as the callback, but it doesn't exist in the module.", 2)
117                 end
118         end
119         return new(self, nil, func, delay, ...)
120 end
121
122 --- Schedule a repeating timer.
123 -- The timer will fire every `delay` seconds, until canceled.
124 -- @param callback Callback function for the timer pulse (funcref or method name).
125 -- @param delay Delay for the timer, in seconds.
126 -- @param ... An optional, unlimited amount of arguments to pass to the callback function.
127 -- @usage
128 -- MyAddOn = LibStub("AceAddon-3.0"):NewAddon("MyAddOn", "AceTimer-3.0")
129 --
130 -- function MyAddOn:OnEnable()
131 --   self.timerCount = 0
132 --   self.testTimer = self:ScheduleRepeatingTimer("TimerFeedback", 5)
133 -- end
134 --
135 -- function MyAddOn:TimerFeedback()
136 --   self.timerCount = self.timerCount + 1
137 --   print(("%d seconds passed"):format(5 * self.timerCount))
138 --   -- run 30 seconds in total
139 --   if self.timerCount == 6 then
140 --     self:CancelTimer(self.testTimer)
141 --   end
142 -- end
143 function AceTimer:ScheduleRepeatingTimer(func, delay, ...)
144         if not func or not delay then
145                 error(MAJOR..": ScheduleRepeatingTimer(callback, delay, args...): 'callback' and 'delay' must have set values.", 2)
146         end
147         if type(func) == "string" then
148                 if type(self) ~= "table" then
149                         error(MAJOR..": ScheduleRepeatingTimer(callback, delay, args...): 'self' - must be a table.", 2)
150                 elseif not self[func] then
151                         error(MAJOR..": ScheduleRepeatingTimer(callback, delay, args...): Tried to register '"..func.."' as the callback, but it doesn't exist in the module.", 2)
152                 end
153         end
154         return new(self, true, func, delay, ...)
155 end
156
157 --- Cancels a timer with the given id, registered by the same addon object as used for `:ScheduleTimer`
158 -- 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.
160 -- @param id The id of the timer, as returned by `:ScheduleTimer` or `:ScheduleRepeatingTimer`
161 function AceTimer:CancelTimer(id)
162         local timer = activeTimers[id]
163         if not timer then return false end
164
165         local anim = timer:GetParent()
166         anim:Stop()
167
168         activeTimers[id] = nil
169         timer.args = nil
170         inactiveTimers[timer] = true
171         return true
172 end
173
174 --- Cancels all timers registered to the current addon object ('self')
175 function AceTimer:CancelAllTimers()
176         for k,v in pairs(activeTimers) do
177                 if v.object == self then
178                         AceTimer.CancelTimer(self, k)
179                 end
180         end
181 end
182
183 --- Returns the time left for a timer with the given id, registered by the current addon object ('self').
184 -- This function will return 0 when the id is invalid.
185 -- @param id The id of the timer, as returned by `:ScheduleTimer` or `:ScheduleRepeatingTimer`
186 -- @return The time left on the timer.
187 function AceTimer:TimeLeft(id)
188         local timer = activeTimers[id]
189         if not timer then return 0 end
190         return timer:GetDuration() - timer:GetElapsed()
191 end
192
193
194 -- ---------------------------------------------------------------------
195 -- Upgrading
196
197 -- Upgrade from old hash-bucket based timers to animation timers
198 if oldminor and oldminor < 10 then
199         -- disable old timer logic
200         AceTimer.frame:SetScript("OnUpdate", nil)
201         AceTimer.frame:SetScript("OnEvent", nil)
202         AceTimer.frame:UnregisterAllEvents()
203         -- convert timers
204         for object,timers in pairs(AceTimer.selfs) do
205                 for handle,timer in pairs(timers) do
206                         if type(timer) == "table" and timer.callback then
207                                 local id
208                                 if timer.delay then
209                                         id = AceTimer.ScheduleRepeatingTimer(timer.object, timer.callback, timer.delay, timer.arg)
210                                 else
211                                         id = AceTimer.ScheduleTimer(timer.object, timer.callback, timer.when - GetTime(), timer.arg)
212                                 end
213                                 -- change id to the old handle
214                                 local t = activeTimers[id]
215                                 activeTimers[id] = nil
216                                 activeTimers[handle] = t
217                                 t.id = handle
218                         end
219                 end
220         end
221         AceTimer.selfs = nil
222         AceTimer.hash = nil
223         AceTimer.debug = nil
224 elseif oldminor and oldminor < 13 then
225         for handle, id in pairs(AceTimer.hashCompatTable) do
226                 local t = activeTimers[id]
227                 if t then
228                         activeTimers[id] = nil
229                         activeTimers[handle] = t
230                         t.id = handle
231                 end
232         end
233         AceTimer.hashCompatTable = nil
234 end
235
236 -- upgrade existing timers to the latest OnFinished
237 for timer in pairs(inactiveTimers) do
238         timer:SetScript("OnFinished", OnFinished)
239 end
240
241 for _,timer in pairs(activeTimers) do
242         timer:SetScript("OnFinished", OnFinished)
243 end
244
245 -- ---------------------------------------------------------------------
246 -- Embed handling
247
248 AceTimer.embeds = AceTimer.embeds or {}
249
250 local mixins = {
251         "ScheduleTimer", "ScheduleRepeatingTimer",
252         "CancelTimer", "CancelAllTimers",
253         "TimeLeft"
254 }
255
256 function AceTimer:Embed(target)
257         AceTimer.embeds[target] = true
258         for _,v in pairs(mixins) do
259                 target[v] = AceTimer[v]
260         end
261         return target
262 end
263
264 -- AceTimer:OnEmbedDisable(target)
265 -- target (object) - target object that AceTimer is embedded in.
266 --
267 -- cancel all timers registered for the object
268 function AceTimer:OnEmbedDisable(target)
269         target:CancelAllTimers()
270 end
271
272 for addon in pairs(AceTimer.embeds) do
273         AceTimer:Embed(addon)
274 end