diff options
Diffstat (limited to 'advtrains_line_automation/scheduler.lua')
-rw-r--r-- | advtrains_line_automation/scheduler.lua | 133 |
1 files changed, 133 insertions, 0 deletions
diff --git a/advtrains_line_automation/scheduler.lua b/advtrains_line_automation/scheduler.lua new file mode 100644 index 0000000..6025b02 --- /dev/null +++ b/advtrains_line_automation/scheduler.lua @@ -0,0 +1,133 @@ +-- scheduler.lua +-- Implementation of a Railway time schedule queue +-- In contrast to the LuaATC interrupt queue, this one can handle many different +-- event receivers. This is done by registering a callback with the scheduler + +local ln = advtrains.lines +local sched = {} + +local UNITS_THRESH = 10 +local MAX_PER_ITER = 10 + +local callbacks = {} + +-- Register a handler callback to handle scheduler items. +-- e - a handler identifier (corresponds to "handler" in enqueue() ) +-- func - a function(evtdata) to be executed when a schedule item expires +-- evtdata - arbitrary data that has been passed into enqueue() +function sched.register_callback(e, func) + callbacks[e] = func +end + +--[[ +{ + t = <railway time in seconds> + e = <handler callback> + d = <data table> + u = <unit identifier> +} +The "unit identifier" is there to prevent schedule overflows. It can be, for example, the position hash +of a node or a train ID. If the number of schedules for a unit exceeds UNITS_THRESH, further schedules are +blocked. +]]-- +local queue = {} + +local units_cnt = {} + +function sched.load(data) + if data then + for i,elem in ipairs(data) do + table.insert(queue, elem) + units_cnt[elem.u] = (units_cnt[elem.u] or 0) + 1 + end + atlog("[lines][scheduler] Loaded the schedule queue,",#data,"items.") + end +end +function sched.save() + return queue +end + +function sched.run() + local ctime = ln.rwt.get_time() + local cnt = 0 + local ucn, elem + while cnt <= MAX_PER_ITER do + elem = queue[1] + if elem and elem.t <= ctime then + table.remove(queue, 1) + if callbacks[elem.e] then + -- run it + callbacks[elem.e](elem.d) + else + atwarn("[lines][scheduler] No callback to handle schedule",elem) + end + cnt=cnt+1 + ucn = units_cnt[elem.u] + if ucn and ucn>0 then + units_cnt[elem.u] = ucn - 1 + end + else + break + end + end +end + +-- Enqueue a new scheduled item to be executed at "rwtime" +-- handler: a string identifying the handler to use (registered with sched.register_callback()) +-- evtdata: Arbitrary Lua data to be passed to the handler callback +-- unitid: An arbitrary string uniquely identifying the thing that is issuing this enqueue. +-- used to prevent expotentially growing "scheduler bombs" +-- unitlim: Custom override for UNITS_THRESH (see there) +function sched.enqueue(rwtime, handler, evtdata, unitid, unitlim) + local qtime = ln.rwt.to_secs(rwtime) + assert(type(handler)=="string") + assert(type(unitid)=="string") + assert(type(unitlim)=="number") + + local cnt=1 + local ucn, elem + + ucn = (units_cnt[unitid] or 0) + local ulim=(unitlim or UNITS_THRESH) + if ucn >= ulim then + atlog("[lines][scheduler] discarding enqueue for",handler,"(limit",ulim,") because unit",unitid,"has already",ucn,"schedules enqueued") + return false + end + + while true do + elem = queue[cnt] + if not elem or elem.t > qtime then + table.insert(queue, cnt, { + t=qtime, + e=handler, + d=evtdata, + u=unitid, + }) + units_cnt[unitid] = ucn + 1 + return true + end + cnt = cnt+1 + end +end + +-- See enqueue(). Same meaning, except that rwtime is relative to now. +function sched.enqueue_in(rwtime, handler, evtdata, unitid, unitlim) + local ctime = ln.rwt.get_time() + local rwtime_s = ln.rwt.to_secs(rwtime) + sched.enqueue(ctime + rwtime_s, handler, evtdata, unitid, unitlim) +end + +-- Discards all schedules for unit "unitid" (removes them from the queue) +function sched.discard_all(unitid) + local i = 1 + while i<=#queue do + if queue[i].u == unitid then + table.remove(queue,i) + else + i=i+1 + end + end + units_cnt[unitid] = 0 +end + +ln.sched = sched |