TradeSkillMaster/LibTSM/Util/Delay.lua

178 lines
6.1 KiB
Lua
Raw Normal View History

2020-11-13 14:13:12 -05:00
-- ------------------------------------------------------------------------------ --
-- TradeSkillMaster --
-- https://tradeskillmaster.com --
-- All Rights Reserved - Detailed license information included with addon. --
-- ------------------------------------------------------------------------------ --
--- Delay Functions
-- @module Delay
local _, TSM = ...
local Delay = TSM.Init("Util.Delay")
local Debug = TSM.Include("Util.Debug")
local Log = TSM.Include("Util.Log")
local TempTable = TSM.Include("Util.TempTable")
local private = {
delays = {},
frameNumber = 0,
frame = nil,
}
local CALLBACK_TIME_WARNING_THRESHOLD_MS = 20
local MIN_TIME_DURATION = 0.0001
-- ============================================================================
-- Module Loading
-- ============================================================================
Delay:OnModuleLoad(function()
private.frame = CreateFrame("Frame")
private.frame:SetScript("OnUpdate", private.ProcessDelays)
private.frame:Show()
end)
-- ============================================================================
-- Module Functions
-- ============================================================================
--- Call a callback after a set amount of time.
-- Note that the delay may be up to 1 frame time longer than requested.
-- @tparam[opt] string label A label for the delay (to allow it to be cancelled)
-- @tparam number duration The amount of time to delay for
-- @tparam function callback The function called when the delay is finished
-- @tparam[opt] number repeatDelay The amount of time to set this delay for once it completes
-- @param[opt=nil] context A context value to pass along to the callback (ignored if the delay was previously started)
function Delay.AfterTime(label, duration, callback, repeatDelay, context)
if type(label) == "number" then
-- no label specified
assert(not repeatDelay)
duration, callback, repeatDelay, context = label, duration, callback, repeatDelay
label = nil
end
assert(type(duration) == "number" and type(callback) == "function" and (not repeatDelay or type(repeatDelay) == "number"))
repeatDelay = repeatDelay and max(repeatDelay, MIN_TIME_DURATION) or nil
duration = max(duration, MIN_TIME_DURATION)
if label then
for _, delay in ipairs(private.delays) do
if delay.label == label then
-- delay is already running, so just return
return
end
end
else
label = Debug.GetStackLevelLocation(2)
end
local delayTbl = TempTable.Acquire()
delayTbl.endTime = GetTime() + duration
delayTbl.callback = callback
delayTbl.label = label
delayTbl.repeatDelay = repeatDelay
delayTbl.context = context
tinsert(private.delays, delayTbl)
end
--- Call a callback after a set number of frames.
-- Note that the delay may be up to 1 frame time longer than requested.
-- @tparam[opt] string label A label for the delay (to allow it to be cancelled)
-- @tparam number duration The number of frames to delay for
-- @tparam function callback The function called when the delay is finished
-- @tparam[opt] number repeatDelay The number of frames to set this delay for once it completes
-- @param[opt=nil] context A context value to pass along to the callback (ignored if the delay was previously started)
function Delay.AfterFrame(label, duration, callback, repeatDelay, context)
if type(label) == "number" then
-- no label specified
assert(not repeatDelay)
duration, callback, repeatDelay, context = label, duration, callback, repeatDelay
label = nil
end
assert(type(duration) == "number" and type(callback) == "function" and (not repeatDelay or type(repeatDelay) == "number"))
repeatDelay = repeatDelay and max(repeatDelay, 1) or nil
duration = max(duration, 1)
if label then
for _, delay in ipairs(private.delays) do
if delay.label == label then
-- delay is already running, so just return
return
end
end
else
label = Debug.GetStackLevelLocation(2)
assert(label)
end
local delayTbl = TempTable.Acquire()
delayTbl.endFrame = private.frameNumber + duration
delayTbl.callback = callback
delayTbl.label = label
delayTbl.repeatDelay = repeatDelay
delayTbl.context = context
tinsert(private.delays, delayTbl)
end
--- Cancel a delay.
-- This works for both time and frame delays.
-- @tparam string label The label the delay was created with
function Delay.Cancel(label)
for i, delay in ipairs(private.delays) do
if delay.label == label then
TempTable.Release(tremove(private.delays, i))
return
end
end
end
-- ============================================================================
-- Main Delay Callback
-- ============================================================================
function private.ProcessDelays()
private.frameNumber = private.frameNumber + 1
-- the delays can change as we do our callbacks, so keep looping through them until there are no more pending
while true do
local pendingLabel, pendingCallback, pendingContext = nil, nil, nil
for i, delay in ipairs(private.delays) do
assert(delay.endFrame or delay.endTime)
if delay.endFrame and delay.endFrame <= private.frameNumber then
pendingLabel = delay.label
pendingCallback = delay.callback
pendingContext = delay.context
if delay.repeatDelay then
delay.endFrame = private.frameNumber + delay.repeatDelay
else
TempTable.Release(tremove(private.delays, i))
end
break
elseif delay.endTime and delay.endTime <= GetTime() then
pendingLabel = delay.label
pendingCallback = delay.callback
pendingContext = delay.context
if delay.repeatDelay then
delay.endTime = GetTime() + delay.repeatDelay
else
TempTable.Release(tremove(private.delays, i))
end
break
end
end
if not pendingLabel then
-- no more pending delays to process
assert(not pendingCallback)
break
end
local startTime = debugprofilestop()
pendingCallback(pendingContext)
local timeTaken = debugprofilestop() - startTime
if timeTaken > CALLBACK_TIME_WARNING_THRESHOLD_MS then
Log.Warn("Delay callback (%s) took %0.2fms", pendingLabel, timeTaken)
end
end
end