From c34794e8a1a28826a7e8443b46fa76e0de238978 Mon Sep 17 00:00:00 2001 From: orwell96 Date: Sat, 21 Jul 2018 16:31:00 +0200 Subject: Implement routesetting Missing things: signal aspect updating, waiting routes handling, management /info tool --- advtrains/tracks.lua | 3 +- advtrains_interlocking/database.lua | 30 ++++- advtrains_interlocking/init.lua | 1 + advtrains_interlocking/route_prog.lua | 2 +- advtrains_interlocking/routesetting.lua | 214 +++++++++++++++++++++++++++++++ advtrains_interlocking/tcb_ts_ui.lua | 25 +++- advtrains_interlocking/train_related.lua | 22 ++++ 7 files changed, 286 insertions(+), 11 deletions(-) diff --git a/advtrains/tracks.lua b/advtrains/tracks.lua index 559ada7..092a1ec 100644 --- a/advtrains/tracks.lua +++ b/advtrains/tracks.lua @@ -304,7 +304,8 @@ function advtrains.register_tracks(tracktype, def, preset) if var.switchalt and var.switchst then local switchfunc=function(pos, node, newstate) - if newstate~=var.switchst and not advtrains.get_train_at_pos(pos) then + if newstate~=var.switchst and not advtrains.get_train_at_pos(pos) + and not (advtrains.interlocking and advtrains.interlocking.route.has_route_lock(advtrains.roundfloorpts(pos)) ) then --TODO schöner machen advtrains.ndb.swap_node(pos, {name=def.nodename_prefix.."_"..var.switchalt..rotation, param2=node.param2}) advtrains.invalidate_all_paths(pos) end diff --git a/advtrains_interlocking/database.lua b/advtrains_interlocking/database.lua index 6d86e39..c19f516 100644 --- a/advtrains_interlocking/database.lua +++ b/advtrains_interlocking/database.lua @@ -64,6 +64,7 @@ Whenever train leaves a TC -> Clearing any routes set from this TC outward recursively - see "Reversing problem" Whenever train enters a TC -> Clear route status from the just entered TC +Note that this prohibits by design that the train clears the route ahead of it. == Reversing Problem == Encountered at the Royston simulation in SimSig. It is solved there by imposing a time limit on the set route. Call-on routes can somehow be set anyway. Imagine this setup: (T=Train, R=Route, >=in_dir TCB) @@ -108,10 +109,18 @@ function ildb.load(data) if data.signalass then signal_assignments = data.signalass end + if data.rs_locks then + advtrains.interlocking.route.rte_locks = data.rs_locks + end end function ildb.save() - return {tcbs = track_circuit_breaks, ts=track_sections, signalass = signal_assignments} + return { + tcbs = track_circuit_breaks, + ts=track_sections, + signalass = signal_assignments, + rs_locks = advtrains.interlocking.route.rte_locks, + } end -- @@ -144,9 +153,21 @@ Track section name = "Some human-readable name" tc_breaks = { ,... } -- Bounding TC's (signal specifiers) -- Can be direct ends (auto-detected), conflicting routes or TCBs that are too far away from each other - route = {origin = , from_tcb = } + route = { + origin = , -- route origin + entry = , -- supposed train entry point + rsn = , + first = + } + route_post = { + locks = {[n] = } + next = + } -- Set whenever a route has been set through this TC. It saves the origin tcb id and side - -- (=the origin signal). index is the TCB of the route origin + -- (=the origin signal). rsn is some description to be shown to the user + -- first says whether to clear the routesetting status from the origin signal. + -- locks contains the positions where locks are held by this ts. + -- 'route' is cleared when train enters the section, while 'route_post' cleared when train leaves section. trains = {, ...} -- Set whenever a train (or more) reside in this TC } @@ -379,10 +400,11 @@ function ildb.remove_from_interlocking(sigd) end function ildb.remove_tcb(pos) + local pts = advtrains.roundfloorpts(pos) + if not track_circuit_breaks[pts] then return end for connid=1,2 do ildb.remove_from_interlocking({p=pos, s=connid}) end - local pts = advtrains.roundfloorpts(pos) track_circuit_breaks[pts] = nil end diff --git a/advtrains_interlocking/init.lua b/advtrains_interlocking/init.lua index 05665e3..9bd6669 100644 --- a/advtrains_interlocking/init.lua +++ b/advtrains_interlocking/init.lua @@ -11,3 +11,4 @@ dofile(modpath.."signal_api.lua") dofile(modpath.."demosignals.lua") dofile(modpath.."train_related.lua") dofile(modpath.."route_prog.lua") +dofile(modpath.."routesetting.lua") diff --git a/advtrains_interlocking/route_prog.lua b/advtrains_interlocking/route_prog.lua index 2573ba4..fb12548 100644 --- a/advtrains_interlocking/route_prog.lua +++ b/advtrains_interlocking/route_prog.lua @@ -356,6 +356,6 @@ minetest.register_chatcommand("at_rp_discard", --TODO on route setting --- locked turnouts need to somehow know the TS they're associated to, which isn't possible with the current route programming and saving method -- unify luaautomation get/setstate interface to the core -- privileges for route programming +-- routes should end at signals. complete route setting by punching a signal, and command as exceptional route completion diff --git a/advtrains_interlocking/routesetting.lua b/advtrains_interlocking/routesetting.lua index bee183c..181b82d 100644 --- a/advtrains_interlocking/routesetting.lua +++ b/advtrains_interlocking/routesetting.lua @@ -1,7 +1,20 @@ -- Setting and clearing routes +-- TODO duplicate +local lntrans = { "A", "B" } +local function sigd_to_string(sigd) + return minetest.pos_to_string(sigd.p).." / "..lntrans[sigd.s] +end + +local ildb = advtrains.interlocking.db local ilrs = {} +-- table containing locked points +-- also manual locks (maintenance a.s.o.) are recorded here +-- [pts] = { +-- [n] = { [by = ], rsn = , [origin = ] } +-- } +ilrs.rte_locks = {} -- Requests the given route -- This function will try to set the designated route. @@ -10,3 +23,204 @@ local ilrs = {} function ilrs.request_route(signal, tcbs, routeid) end + +-- main route setting. First checks if everything can be set as designated, +-- then (if "try" is not set) actually sets it +-- returns: +-- true - route can be/was successfully set +-- false, message - something went wrong, what is contained in the message. +function ilrs.set_route(signal, route, try) + if not try then + atdebug("rteset real-run") + local tsuc, trsn = ilrs.set_route(signal, route, true) + if not tsuc then + return false, trsn + end + atdebug("doing stuff") + else + atdebug("rteset try-run") + end + + -- we start at the tc designated by signal + local c_sigd = signal + local first = true + local i = 1 + local rtename = route.name + local signalname = ildb.get_tcbs(signal).signal_name + local c_tcbs, c_ts_id, c_ts, c_rseg, c_lckp + while c_sigd and i<=#route do + c_tcbs = ildb.get_tcbs(c_sigd) + c_ts_id = c_tcbs.ts_id + if not c_ts_id then + if not try then atwarn("Encountered End-Of-Interlocking while setting route",rtename,"of",signal) end + return false, "No track section adjacent to "..sigd_to_string(c_sigd).."!" + end + c_ts = ildb.get_ts(c_ts_id) + c_rseg = route[i] + c_lckp = {} + + if c_ts.route then + if not try then atwarn("Encountered ts lock while a real run of routesetting routine, at ts=",c_ts_id,"while setting route",rtename,"of",signal) end + return false, "Section '"..c_ts.name.."' already has route set from "..sigd_to_string(c_ts.route.origin).."!" + end + if c_ts.trains and #c_ts.trains>0 then + if not try then atwarn("Encountered ts occupied while a real run of routesetting routine, at ts=",c_ts_id,"while setting route",rtename,"of",signal) end + return false, "Section '"..c_ts.name.."' is occupied!" + end + + for pts, state in pairs(c_rseg.locks) do + local confl = ilrs.has_route_lock(pts, state) + + local pos = minetest.string_to_pos(pts) + local node = advtrains.ndb.get_node(pos) + local ndef = minetest.registered_nodes[node.name] + if ndef and ndef.luaautomation and ndef.luaautomation.setstate and ndef.luaautomation.getstate then + local cstate = ndef.luaautomation.getstate + if type(cstate)=="function" then cstate = cstate(pos) end + if cstate ~= state then + local confl = ilrs.has_route_lock(pts) + if confl then + if not try then atwarn("Encountered route lock while a real run of routesetting routine, at position",pts,"while setting route",rtename,"of",signal) end + return false, "Lock conflict at "..pts..", Held locked by:\n"..confl + elseif not try then + ndef.luaautomation.setstate(pos, state) + end + end + if not try then + ilrs.add_route_lock(pts, c_ts_id, "Route '"..rtename.."' from signal '"..signalname.."'", signal) + c_lckp[#c_lckp+1] = pts + end + else + if not try then atwarn("Encountered route lock misconfiguration (no passive component) while a real run of routesetting routine, at position",pts,"while setting route",rtename,"of",signal) end + return false, "Route misconfiguration: No passive component at "..pts..". Please reconfigure route!" + end + end + -- reserve ts and write locks + if not try then + c_ts.route = { + origin = signal, + entry = c_sigd, + rsn = "Route '"..rtename.."' from signal '"..signalname.."', segment #"..i, + first = first, + } + c_ts.route_post = { + locks = c_lckp, + next = c_rseg.next, + } + end + -- advance + first = nil + c_sigd = c_rseg.next + i = i + 1 + end + + return true +end + +-- Checks whether there is a route lock that prohibits setting the component +-- to the wanted state. returns string with reasons on conflict +function ilrs.has_route_lock(pts) + -- look this up + local e = ilrs.rte_locks[pts] + if not e then return nil + elseif #e==0 then + ilrs.rte_locks[pts] = nil + return nil + end + local txts = {} + for _, ent in ipairs(e) do + txts[#txts+1] = ent.rsn + end + return table.concat(txts, "\n") +end + +-- adds route lock for position +function ilrs.add_route_lock(pts, ts, rsn, origin) + ilrs.free_route_locks_indiv(pts, ts, true) + local elm = {by=ts, rsn=rsn, origin=origin} + if not ilrs.rte_locks[pts] then + ilrs.rte_locks[pts] = { elm } + else + table.insert(ilrs.rte_locks[pts], elm) + end +end + +-- adds route lock for position +function ilrs.add_manual_route_lock(pts, rsn) + local elm = {rsn=rsn} + if not ilrs.rte_locks[pts] then + ilrs.rte_locks[pts] = { elm } + else + table.insert(ilrs.rte_locks[pts], elm) + end +end + +-- frees route locking for all points (components) that were set by this ts +function ilrs.free_route_locks(ts, lcks, nocallbacks) + for _,pts in pairs(lcks) do + ilrs.free_route_locks_indiv(pts, ts, nocallbacks) + end +end + +function ilrs.free_route_locks_indiv(pts, ts, nocallbacks) + local e = ilrs.rte_locks[pts] + if not e then return nil + elseif #e==0 then + ilrs.rte_locks[pts] = nil + return nil + end + local i = 1 + while i <= #e do + if e[i].by == ts then + atdebug("free_route_locks_indiv",pts,"clearing entry",e[i].by,e[i].rsn) + table.remove(e,i) + else + i = i + 1 + end + end + + --TODO callbacks +end +-- frees all route locks, even manual ones set with the tool, at a specific position +function ilrs.remove_route_locks(pts, nocallbacks) + ilrs.rte_locks[pts] = nil + --TODO callbacks +end + +local function sigd_equal(sigd, cmp) + return vector.equals(sigd.p, cmp.p) and sigd.s==cmp.s +end + +-- starting from the designated sigd, clears all subsequent route and route_post +-- information from the track sections. +-- note that this does not clear the routesetting status from the entry signal, +-- only from the ts's +function ilrs.cancel_route_from(sigd) + -- we start at the tc designated by signal + local c_sigd = sigd + local c_tcbs, c_ts_id, c_ts, c_rseg, c_lckp + while c_sigd do + c_tcbs = ildb.get_tcbs(c_sigd) + c_ts_id = c_tcbs.ts_id + c_ts = ildb.get_ts(c_ts_id) + + if not c_ts + or not c_ts.route + or not sigd_equal(c_ts.route.entry, c_sigd) then + return + end + + c_ts.route = nil + + if c_ts.route_post then + advtrains.interlocking.route.free_route_locks(c_ts_id, c_ts.route_post.locks) + c_sigd = c_ts.route_post.next + else + c_sigd = nil + end + c_ts.route_post = nil + end +end + +advtrains.interlocking.route = ilrs + diff --git a/advtrains_interlocking/tcb_ts_ui.lua b/advtrains_interlocking/tcb_ts_ui.lua index 8745626..e35e5b9 100644 --- a/advtrains_interlocking/tcb_ts_ui.lua +++ b/advtrains_interlocking/tcb_ts_ui.lua @@ -457,12 +457,12 @@ function advtrains.interlocking.show_signalling_form(sigd, pname, sel_rte) form = form.."label[0.5,2.5;A route is requested from this signal:]" form = form.."label[0.5,3.0;"..rte.name.."]" if tcbs.route_committed then - form = form.."label[0.5,2.5;Route has been set.]" + form = form.."label[0.5,3.5;Route has been set.]" else - form = form.."label[0.5,2.5;Waiting for route to be set...]" + form = form.."label[0.5,3.5;Waiting for route to be set...]" end - form = form.."button[0.5,6.5;1,6;cancelroute;Cancel Route]" + form = form.."button[0.5,6; 5,1;cancelroute;Cancel Route]" else local strtab = {} for idx, route in ipairs(tcbs.routes) do @@ -515,7 +515,14 @@ minetest.register_on_player_receive_fields(function(player, formname, fields) tcbs.signal_name = fields.name end if tcbs.routeset and fields.cancelroute then - --TODO + -- if route committed, cancel route ts info + if tcbs.route_committed then + advtrains.interlocking.route.cancel_route_from(sigd) + end + -- then clear own routeset state + --TODO callbacks + tcbs.routeset = nil + tcbs.route_committed = nil end if not tcbs.routeset then if fields.newroute then @@ -525,7 +532,15 @@ minetest.register_on_player_receive_fields(function(player, formname, fields) end if sel_rte and tcbs.routes[sel_rte] then if fields.setroute then - --TODO + tcbs.routeset = sel_rte + local succ, rsn = advtrains.interlocking.route.set_route(sigd, tcbs.routes[sel_rte]) + if not succ then + atwarn("Setting route failed:") + atwarn(rsn) + tcbs.routeset = nil + else + tcbs.route_committed = true + end end if fields.dsproute then local t = os.clock() diff --git a/advtrains_interlocking/train_related.lua b/advtrains_interlocking/train_related.lua index 3d7e280..2dfbd5c 100644 --- a/advtrains_interlocking/train_related.lua +++ b/advtrains_interlocking/train_related.lua @@ -73,6 +73,19 @@ local function setsection(tid, train, ts_id, ts, origin) table.insert(ts.trains, tid) end + -- route setting - clear route state + if ts.route then + if ts.route.first then + local tcbs = advtrains.interlocking.db.get_tcbs(ts.route.origin) + if tcbs then + --TODO callbacks + tcbs.routeset = nil + tcbs.route_committed = nil + end + end + ts.route = nil + end + end local function freesection(tid, train, ts_id, ts) @@ -84,6 +97,15 @@ local function freesection(tid, train, ts_id, ts) if not ts.trains then ts.trains = {} end itremove(ts.trains, tid) + if ts.route_post then + advtrains.interlocking.route.free_route_locks(ts_id, ts.route_post.locks) + if ts.route_post.next then + --this does nothing when the train went the right way, because + -- "route" info is already cleared. + advtrains.interlocking.route.cancel_route_from(ts.route_post.next) + end + ts.route_post = nil + end end -- cgit v1.2.3