From 7b488f40d95c2d68db898d7cb228e17e001cea73 Mon Sep 17 00:00:00 2001 From: orwell96 Date: Mon, 26 Aug 2019 23:08:02 +0200 Subject: Add lines scheduler for reliable railway-time scheduling(which is also safer than the atlatc scheduler) and document new atlatc functions --- advtrains_line_automation/init.lua | 6 +- advtrains_line_automation/scheduler.lua | 104 ++++++++++++++++++++++++++++++ advtrains_luaautomation/README.txt | 12 ++++ advtrains_luaautomation/active_common.lua | 16 +++++ advtrains_luaautomation/depends.txt | 1 + 5 files changed, 138 insertions(+), 1 deletion(-) create mode 100644 advtrains_line_automation/scheduler.lua diff --git a/advtrains_line_automation/init.lua b/advtrains_line_automation/init.lua index 0f4a4eb..7b758bc 100644 --- a/advtrains_line_automation/init.lua +++ b/advtrains_line_automation/init.lua @@ -19,6 +19,7 @@ advtrains.lines = { local modpath = minetest.get_modpath(minetest.get_current_modname()) .. DIR_DELIM dofile(modpath.."railwaytime.lua") +dofile(modpath.."scheduler.lua") dofile(modpath.."stoprail.lua") @@ -27,6 +28,7 @@ function advtrains.lines.load(data) advtrains.lines.stations = data.stations or {} advtrains.lines.stops = data.stops or {} advtrains.lines.rwt.set_time(data.rwt_time) + advtrains.lines.sched.load(data.scheduler_queue) end end @@ -34,10 +36,12 @@ function advtrains.lines.save() return { stations = advtrains.lines.stations, stops = advtrains.lines.stops, - rwt_time = advtrains.lines.rwt.get_time() + rwt_time = advtrains.lines.rwt.get_time(), + scheduler_queue = advtrains.lines.sched.save() } end function advtrains.lines.step(dtime) advtrains.lines.rwt.step(dtime) + advtrains.lines.sched.run() end diff --git a/advtrains_line_automation/scheduler.lua b/advtrains_line_automation/scheduler.lua new file mode 100644 index 0000000..da639df --- /dev/null +++ b/advtrains_line_automation/scheduler.lua @@ -0,0 +1,104 @@ +-- 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 = {} +--Function signature is function(d) +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 + 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("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 + +function sched.enqueue(rwtime, handler, evtdata, unitid, unitlim) + local qtime = ln.rwt.to_secs(rwtime) + + local cnt=1 + local ucn, elem + + ucn = (units_cnt[unitid] or 0) + local ulim=(unitlim or UNITS_THRESH) + if ucn >= ulim then + atwarn("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 + +function sched.enqueue_in(rwtime, handler, evtdata, unitid, unitlim) + local ctime = ln.rwt.get_time() + sched.enqueue(ctime + rwtime, handler, evtdata, unitid, unitlim) +end + +ln.sched = sched diff --git a/advtrains_luaautomation/README.txt b/advtrains_luaautomation/README.txt index a07bbb4..20ef816 100644 --- a/advtrains_luaautomation/README.txt +++ b/advtrains_luaautomation/README.txt @@ -114,6 +114,18 @@ aspect = { } As of August 2018, only the aspect.main.free field is ever used by the interlocking system. +# Lines + +The advtrains_line_automation component adds a few contraptions that should make creating timeable systems easier. +Part of its functionality is also available in LuaATC: + +- rwt.* - all Railway Time functions are included as documented in https://advtrains.de/wiki/doku.php?id=dev:lines:rwt + +- schedule(rw_time, msg) +- schedule_in(rw_dtime, msg) +Schedules an event of type {type="schedule", schedule=true, msg=msg} at (resp. after) the specified railway time. +(which can be in any format). You can only schedule one event this way. (uses the new lines-internal scheduler) + ## Components and events The event table is a table of the following format: diff --git a/advtrains_luaautomation/active_common.lua b/advtrains_luaautomation/active_common.lua index 5d8cc48..8d0975f 100644 --- a/advtrains_luaautomation/active_common.lua +++ b/advtrains_luaautomation/active_common.lua @@ -123,6 +123,15 @@ function ac.run_in_env(pos, evtdata, customfct_p) digiline:receptor_send(pos, digiline.rules.default, channel, msg) end end + -- add lines scheduler if enabled + if advtrains.lines and advtrains.lines.sched then + customfct.schedule = function(rwtime, msg) + advtrains.lines.sched.enqueue(rwtime, "atlatc_env", {pos=pos, msg=msg}, advtrains.encode_pos(pos), 1) + end + customfct.schedule_in = function(rwtime, msg) + advtrains.lines.sched.enqueue_in(rwtime, "atlatc_env", {pos=pos, msg=msg}, advtrains.encode_pos(pos), 1) + end + end local datain=nodetbl.data or {} local succ, dataout = atlatc.envs[nodetbl.env]:execute_code(datain, nodetbl.code, evtdata, customfct) @@ -144,4 +153,11 @@ function ac.on_digiline_receive(pos, node, channel, msg) atlatc.interrupt.add(0, pos, {type="digiline", digiline=true, channel = channel, msg = msg}) end +if advtrains.lines and advtrains.lines.sched then + advtrains.lines.sched.register_callback("atlatc_env", function(data) + -- This adds another interrupt to the atlatc queue... there might be a better way + atlatc.interrupt.add(0, data.pos, {type="schedule",schedule=true, msg=data.msg}) + end) +end + atlatc.active=ac diff --git a/advtrains_luaautomation/depends.txt b/advtrains_luaautomation/depends.txt index 1b4fd87..d5523e1 100644 --- a/advtrains_luaautomation/depends.txt +++ b/advtrains_luaautomation/depends.txt @@ -1,3 +1,4 @@ advtrains advtrains_interlocking? +advtrains_line_automation? mesecons_switch? \ No newline at end of file -- cgit v1.2.3