-- ------------------------------------------------------------------------------ -- -- TradeSkillMaster -- -- https://tradeskillmaster.com -- -- All Rights Reserved - Detailed license information included with addon. -- -- ------------------------------------------------------------------------------ -- local TSM = select(2, ...) ---@type TSM local Delay = TSM.Init("Util.Delay") ---@class Util.Delay local Log = TSM.Include("Util.Log") local DelayTimer = TSM.Include("LibTSMClass").DefineClass("DelayTimer") ---@class DelayTimer local private = { activeTimers = {}, frameNumber = 0, frame = nil, } local CALLBACK_TIME_WARNING_THRESHOLD_MS = 20 local MIN_TIME_DURATION = 0.0001 local MIN_FRAMES = 1 -- ============================================================================ -- Module Loading -- ============================================================================ Delay:OnModuleLoad(function() private.frame = CreateFrame("Frame") private.frame:SetScript("OnUpdate", private.ProcessDelays) private.frame:Show() end) -- ============================================================================ -- Module Functions -- ============================================================================ ---Creates a new timer. ---@param label string A label which is used for debugging purposes ---@param callback function The function to call when the timer expires ---@return DelayTimer function Delay.CreateTimer(label, callback) assert(type(label) == "string" and type(callback) == "function") return DelayTimer(label, callback) end -- ============================================================================ -- DelayTimer Class Methods -- ============================================================================ function DelayTimer:__init(name, callback) self._name = name self._callback = callback self._endTime = nil self._endFrame = nil end function DelayTimer:RunForTime(seconds) if self._endTime then -- Already running return end assert(not self._endFrame) self._endTime = GetTime() + max(seconds, MIN_TIME_DURATION) assert(not private.activeTimers[self]) private.activeTimers[self] = true end function DelayTimer:RunForFrames(frames) if self._endFrame then -- Already running return end assert(not self._endTime) self._endFrame = private.frameNumber + max(frames, MIN_FRAMES) assert(not private.activeTimers[self]) private.activeTimers[self] = true end function DelayTimer:Cancel() if not self._endTime and not self._endFrame then -- Not running return end assert(private.activeTimers[self]) private.activeTimers[self] = nil self._endTime = nil self._endFrame = nil end function DelayTimer:_CheckIfDone() assert(private.activeTimers[self]) if (self._endTime or math.huge) <= GetTime() or (self._endFrame or math.huge) <= private.frameNumber then self._endTime = nil self._endFrame = nil private.activeTimers[self] = nil local startTime = debugprofilestop() self._callback() local timeTaken = debugprofilestop() - startTime if timeTaken > CALLBACK_TIME_WARNING_THRESHOLD_MS then Log.Warn("Delay callback (%s) took %0.2fms", self._name, timeTaken) end return true end return false end -- ============================================================================ -- Main Delay Callback -- ============================================================================ function private.ProcessDelays() private.frameNumber = private.frameNumber + 1 -- The active timers can change as we complete them, so only do one per loop and keep looping until they're all processed local hadDoneTimer = true while hadDoneTimer do hadDoneTimer = false for timer in pairs(private.activeTimers) do if timer:_CheckIfDone() then hadDoneTimer = true break end end end end