Compare with Previous | Blame | View Log
--[[---------------------------------------------------------------------------- Name: LibSimpleTimer-1.0.lua Description: A simple timer Revision: $Revision: 32 $ Author: Whitetooth Email: hotdogee [at] gmail [dot] com Credits: Dongle Development Team Encoding: UTF-8 Features: * Schedule/Cancel one-time or repeating timer by id. * Able to schedule a timer without any callback. Why? Used with :IsTimerScheduled(id) for checks * Able to overwrite existing timer. * Fast OnUpdate utilizing a heap. * Embedable. ------------------------------------------------------------------------------]] local MAJOR_VERSION = "LibSimpleTimer-1.0" local MINOR_VERSION = 90000 + tonumber(("$Revision: 32 $"):match("%d+")) local SimpleTimer, oldMinor = LibStub:NewLibrary(MAJOR_VERSION, MINOR_VERSION) if not SimpleTimer then return end -- No upgrade needed -------------------------------------------------------------------------------- -- Base code taken from Dongle, sightly modified -------------------------------------------------------------------------------- SimpleTimer.timers = SimpleTimer.timers or {} SimpleTimer.heap = SimpleTimer.heap or {} SimpleTimer.frame = SimpleTimer.frame or CreateFrame("Frame", "SimpleTimer10Frame") local timers = SimpleTimer.timers local heap = SimpleTimer.heap local frame = SimpleTimer.frame local GetTime = GetTime local pairs = pairs local unpack = unpack local floor = floor local select = select local type = type local error = error local strjoin = strjoin local debugstack = debugstack local strmatch = strmatch local format = format local pcall = pcall local geterrorhandler = geterrorhandler local function argcheck(value, num, ...) if type(num) ~= "number" then error(("bad argument #%d to '%s' (%s expected, got %s)"):format(2, "argcheck", "number", type(num)), 1) end for i=1,select("#", ...) do if type(value) == select(i, ...) then return end end local types = strjoin(", ", ...) local name = strmatch(debugstack(2,2,0), ": in function [`<](.-)['>]") error(("bad argument #%d to '%s' (%s expected, got %s)"):format(num, name, types, type(value)), 3) end local function safecall(func,...) local success,err = pcall(func,...) if not success then geterrorhandler()(err) end end local function HeapBubbleUp(index) while index > 1 do local parentIndex = floor(index / 2) if heap[index].timeToFire < heap[parentIndex].timeToFire then heap[index], heap[parentIndex] = heap[parentIndex], heap[index] index = parentIndex else break end end end local function HeapBubbleDown(index) while 2 * index <= heap.lastIndex do local leftIndex = 2 * index local rightIndex = leftIndex + 1 local current = heap[index] local leftChild = heap[leftIndex] local rightChild = heap[rightIndex] if not rightChild then if leftChild.timeToFire < current.timeToFire then heap[index], heap[leftIndex] = heap[leftIndex], heap[index] index = leftIndex else break end else if leftChild.timeToFire < current.timeToFire or rightChild.timeToFire < current.timeToFire then if leftChild.timeToFire < rightChild.timeToFire then heap[index], heap[leftIndex] = heap[leftIndex], heap[index] index = leftIndex else heap[index], heap[rightIndex] = heap[rightIndex], heap[index] index = rightIndex end else break end end end end local function OnUpdate() local schedule = heap[1] while schedule and schedule.timeToFire < GetTime() do if schedule.cancelled then local last = heap.lastIndex heap[1], heap[last] = heap[last], heap[1] heap[heap.lastIndex] = nil heap.lastIndex = heap.lastIndex - 1 HeapBubbleDown(1) else if schedule.func then if schedule.args then safecall(schedule.func, unpack(schedule.args)) else safecall(schedule.func) end end if schedule.repeating then schedule.timeToFire = schedule.timeToFire + schedule.repeating HeapBubbleDown(1) else local last = heap.lastIndex heap[1], heap[last] = heap[last], heap[1] heap[heap.lastIndex] = nil heap.lastIndex = heap.lastIndex - 1 HeapBubbleDown(1) timers[schedule.name] = nil end end schedule = heap[1] end if not schedule then frame:Hide() end end frame:SetScript("OnUpdate", OnUpdate) -------------------------------------------------------------------------------- -- :ScheduleTimer(name, func, delay, ...) -- Notes: -- * Schedule a timer to expire in delay seconds at which point it will call the callback func. name is an identifier for this timer. -- * If you try to schedule a timer with the same name a second time, the old schedule will be overwritten. -- Arguments: -- name (variant) - The name of the timer to be scheduled. You can use this name to check if this timer's status and/or cancel it. -- func (function or nil) - A function to be called when the timer expires, can be nil. -- delay (number) - The number of seconds it takes for this timer to expire. -- ... - Any additional arguments to pass to the callback. -- Callback Signature: -- func(...) -- Example: -- :ScheduleTimer("EncounterEnd", self.EncounterEnd, 10, self, 10) -- :ScheduleTimer("EncounterEnd", nil, 10) -- why? used with :IsTimerScheduled("EncounterEnd") -------------------------------------------------------------------------------- function SimpleTimer:ScheduleTimer(name, func, delay, ...) argcheck(self, 1, "table") argcheck(func, 3, "function", "nil") argcheck(delay, 4, "number") if SimpleTimer:IsTimerScheduled(name) then SimpleTimer:CancelTimer(name) end local schedule = {} timers[name] = schedule schedule.timeToFire = GetTime() + delay schedule.func = func schedule.name = name if select('#', ...) ~= 0 then schedule.args = { ... } end if heap.lastIndex then heap.lastIndex = heap.lastIndex + 1 else heap.lastIndex = 1 end heap[heap.lastIndex] = schedule HeapBubbleUp(heap.lastIndex) if not frame:IsShown() then frame:Show() end end -------------------------------------------------------------------------------- -- :ScheduleRepeatingTimer(name, func, delay, ...) -- Notes: -- * Schedule a repeating timer that expires every delay seconds. -- * If you try to schedule a timer with the same name a second time, the old schedule will be overwritten. -- Arguments: -- name (variant) - The name of the timer to be scheduled. You can use this name to check if this timer's status and/or cancel it. -- func (function) - A function to be called when the timer expires. -- delay (number) - The number of seconds it takes for this timer to expire. -- ... - Any additional arguments to pass to the callback. -- Callback Signature: -- func(...) -------------------------------------------------------------------------------- function SimpleTimer:ScheduleRepeatingTimer(name, func, delay, ...) argcheck(func, 3, "function") SimpleTimer:ScheduleTimer(name, func, delay, ...) timers[name].repeating = delay end -------------------------------------------------------------------------------- -- :IsTimerScheduled(name) -- Notes: -- * Returns if the timer with the name specified is scheduled or not and also returns the time remaining for it to expire. -- Arguments: -- name (variant) - The name of the timer to query. -- Returns: -- nil - if the timer is not scheduled. -- true, seconds (number) - if the timer is scheduled. seconds is the number of seconds required for the timer to expire. -------------------------------------------------------------------------------- function SimpleTimer:IsTimerScheduled(name) argcheck(self, 1, "table") local schedule = timers[name] if schedule then return true, schedule.timeToFire - GetTime() end end -------------------------------------------------------------------------------- -- :CancelTimer(name) -- Notes: -- * Cancels the timer scheduled with name. -- Arguments: -- name (variant) - The name of the timer to cancel. -------------------------------------------------------------------------------- function SimpleTimer:CancelTimer(name) argcheck(self, 1, "table") local schedule = timers[name] if not schedule then return end schedule.cancelled = true timers[name] = nil end -------------------------------------------------------------------------------- -- Embed handling -------------------------------------------------------------------------------- SimpleTimer.embeds = SimpleTimer.embeds or {} local mixins = { "ScheduleTimer", "ScheduleRepeatingTimer", "IsTimerScheduled", "CancelTimer", } -------------------------------------------------------------------------------- -- :Embed(target) -- Notes: -- * Embeds "ScheduleTimer", "ScheduleRepeatingTimer", "IsTimerScheduled", "CancelTimer" -- Arguments: -- target (table) - The table with which to export methods onto. -- Returns: -- The table provided, after embedding. -------------------------------------------------------------------------------- function SimpleTimer:Embed(target) SimpleTimer.embeds[target] = true for _, v in pairs(mixins) do target[v] = SimpleTimer[v] end return target end for addon in pairs(SimpleTimer.embeds) do SimpleTimer:Embed(addon) end