-- 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 = e = d = u = } 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