cc-mek-scada/scada-common/tcallbackdsp.lua
2022-10-23 12:21:17 -04:00

108 lines
3.6 KiB
Lua

--
-- Timer Callback Dispatcher
--
local log = require("scada-common.log")
local util = require("scada-common.util")
local tcallbackdsp = {}
local registry = {}
local UNSERVICED_CALL_DELAY = util.TICK_TIME_S
-- request a function to be called after the specified time
---@param time number seconds
---@param f function callback function
function tcallbackdsp.dispatch(time, f)
local timer = util.start_timer(time)
registry[timer] = {
callback = f,
duration = time,
expiry = time + util.time_s()
}
-- log.debug(util.c("TCD: queued callback for ", f, " [timer: ", timer, "]"))
end
-- request a function to be called after the specified time, aborting any registered instances of that function reference
---@param time number seconds
---@param f function callback function
function tcallbackdsp.dispatch_unique(time, f)
-- ignore if already registered
for timer, entry in pairs(registry) do
if entry.callback == f then
-- found an instance of this function reference, abort it
log.debug(util.c("TCD: aborting duplicate timer callback [timer: ", timer, ", ", f, "]"))
-- cancel event and remove from registry (even if it fires it won't call)
util.cancel_timer(timer)
registry[timer] = nil
end
end
local timer = util.start_timer(time)
registry[timer] = {
callback = f,
duration = time,
expiry = time + util.time_s()
}
-- log.debug(util.c("TCD: queued callback for ", f, " [timer: ", timer, "]"))
end
-- lookup a timer event and execute the callback if found
---@param event integer timer event timer ID
function tcallbackdsp.handle(event)
if registry[event] ~= nil then
local callback = registry[event].callback
-- clear first so that dispatch_unique call from inside callback won't throw a debug message
registry[event] = nil
callback()
end
end
-- execute any callbacks that are overdo their time and have not been serviced
--
-- this can be called periodically to prevent loss of any callbacks do to timer events that are lost (see github issue #110)
function tcallbackdsp.call_unserviced()
local found_unserviced = true
while found_unserviced do
found_unserviced = false
-- go through registry, restart if unserviced entries were found due to mutating registry table
for timer, entry in pairs(registry) do
found_unserviced = util.time_s() > (entry.expiry + UNSERVICED_CALL_DELAY)
if found_unserviced then
local overtime = util.time_s() - entry.expiry
local callback = entry.callback
log.warning(util.c("TCD: executing unserviced callback ", entry.callback, " (", overtime, "s late) [timer: ", timer, "]"))
-- clear first so that dispatch_unique call from inside callback won't see it as a conflict
registry[timer] = nil
callback()
break
end
end
end
end
-- identify any overdo callbacks
--
-- prints to log debug output
function tcallbackdsp.diagnostics()
for timer, entry in pairs(registry) do
if entry.expiry < util.time_s() then
local overtime = util.time_s() - entry.expiry
log.debug(util.c("TCD: unserviced timer ", timer, " for callback ", entry.callback, " is at least ", overtime, "s late"))
else
local time = entry.expiry - util.time_s()
log.debug(util.c("TCD: pending timer ", timer, " for callback ", entry.callback, " (call after ", entry.duration, "s, expires ", time, ")"))
end
end
end
return tcallbackdsp