From 67ace4bde0edd0d40c63168758ac4b4878135946 Mon Sep 17 00:00:00 2001 From: orwell Date: Tue, 10 Dec 2024 23:07:06 +0100 Subject: Smartroute: Never replace already existing intact routes, change colorcoding, other fixes --- advtrains_interlocking/database.lua | 17 +++-- advtrains_interlocking/route_ui.lua | 25 ++++--- advtrains_interlocking/smartroute.lua | 118 ++++++++++++++++++++-------------- advtrains_interlocking/tcb_ts_ui.lua | 52 +++++++++++++-- 4 files changed, 134 insertions(+), 78 deletions(-) (limited to 'advtrains_interlocking') diff --git a/advtrains_interlocking/database.lua b/advtrains_interlocking/database.lua index 09b1c72..3104a20 100644 --- a/advtrains_interlocking/database.lua +++ b/advtrains_interlocking/database.lua @@ -245,6 +245,14 @@ routes = { 800080008000 = st } next = S[(-23,9,0)/2] -- the start TCB of the next route segment (pointing forward) + -- Signal info: relates to the signal at the start of this section: + main_aspect = "_free" -- The main aspect that the route start signal is to show + assign_dst = false -- Whether to assign distant signal (affects only the signal at the start of the route) + -- false: start signal does not set distant signal (the default), for long blocks + -- it is assumed that the next main signal will have its own distant sig + -- true: start signal sets distant signal to the next signal on the route with route_role "main" (typically the end signal) + -- for short blocks where end signal doesn't have its own distant sig + call_on = false -- if true, when this route is set, section is allowed to be occupied by a train (but it must not have a route set in) } 2 = { locks = {} @@ -254,15 +262,6 @@ routes = { ars = { } use_rscache = false -- if true, the track section's rs_cache will be used to set locks in addition to the locks table -- this is disabled for legacy routes, but enabled for all new routes by default - -- Fields to specify the signal aspect of the signal - main_aspect = "_free" -- The main aspect that the route start signal is to show - assign_dst = false -- Whether to assign distant signal (affects only the signal at the start of the route) - -- false: start signal does not set distant signal (the default), for long blocks - -- it is assumed that the next main signal will have its own distant sig - -- true: start signal sets distant signal to the next signal on the route with route_role "main" (typically the end signal) - -- for short blocks where end signal doesn't have its own distant sig - terminal = -- the sigd describing the end of the route (e.g. the "next" entry in the final route segment). - -- Might be missing or wrong. Routesetting currently does not care about this value being present. default_autoworking = false -- if true, when route is set autoworking will be by default on. Used for Blocksignal mode } } diff --git a/advtrains_interlocking/route_ui.lua b/advtrains_interlocking/route_ui.lua index fa707d9..b75f9d7 100644 --- a/advtrains_interlocking/route_ui.lua +++ b/advtrains_interlocking/route_ui.lua @@ -43,7 +43,7 @@ function atil.show_route_edit_form(pname, sigd, routeid, sel_rpartidx) -- we start at the tc designated by signal local c_sigd = sigd local i = 1 - local c_tcbs, c_ts_id, c_ts, c_rseg, c_lckp + local c_tcbs, c_ts_id, c_ts, c_rseg while c_sigd and i<=#route do c_tcbs = ildb.get_tcbs(c_sigd) if not c_tcbs then @@ -58,7 +58,6 @@ function atil.show_route_edit_form(pname, sigd, routeid, sel_rpartidx) c_ts = ildb.get_ts(c_ts_id) c_rseg = route[i] - c_lckp = {} local signame = "-" if c_tcbs and c_tcbs.signal then signame = c_tcbs.signal_name or "o" end @@ -145,9 +144,9 @@ function atil.show_route_edit_form(pname, sigd, routeid, sel_rpartidx) form = form.."button[2.5,6;1,1;next;>>>]" - if route.smartroute_generated or route.default_autoworking then - form = form.."button[3.5,6;2,1;noautogen;Clr Autogen]" - end + --if route.smartroute_generated or route.default_autoworking then + -- form = form.."button[3.5,6;2,1;noautogen;Clr Autogen]" + --end form = form.."button[5.5,6;3,1;delete;Delete Route]" form = form.."button[0.5,7;3,1;back;Back to signal]" form = form.."button[3.5,7;2,1;clone;Clone Route]" @@ -235,13 +234,13 @@ minetest.register_on_player_receive_fields(function(player, formname, fields) end end - if fields.noautogen then - route.smartroute_generated = nil - route.default_autoworking = nil - -- reshow form for the button to disappear - atil.show_route_edit_form(pname, sigd, routeid, sel_rpart and sel_rpart.idx) - return - end + --if fields.noautogen then + -- route.smartroute_generated = nil + -- route.default_autoworking = nil + -- -- reshow form for the button to disappear + -- atil.show_route_edit_form(pname, sigd, routeid, sel_rpart and sel_rpart.idx) + -- return + --end if fields.delete then -- if something set the route in the meantime, make sure this doesn't break. @@ -298,7 +297,7 @@ minetest.register_on_player_receive_fields(function(player, formname, fields) end end - if field.quit then + if fields.quit then -- cleanup sel_rpartcache[pname] = nil end diff --git a/advtrains_interlocking/smartroute.lua b/advtrains_interlocking/smartroute.lua index 770c379..07cdf46 100644 --- a/advtrains_interlocking/smartroute.lua +++ b/advtrains_interlocking/smartroute.lua @@ -6,6 +6,23 @@ local ildb = atil.db local sr = {} +-- Start the SmartRoute process. This searches for routes and tries to match them with existing routes, showing them in a form +function sr.start(pname, sigd) + -- is start signal a shunt signal? This becomes default setting for searching_shunt + local is_startsignal_shunt = false + local tcbs = ildb.get_tcbs(sigd) + if tcbs.signal then + local ndef = advtrains.ndb.get_ndef(tcbs.signal) + if ndef and ndef.advtrains then + if ndef.advtrains.route_role == "shunt" then + is_startsignal_shunt = true + end + end + end + sr.propose_next(pname, sigd, 10, is_startsignal_shunt) -- TODO set tscnt_limit to 2 initially and then increase it. Do this when form is implemented +end + + local function otherside(s) if s==1 then return 2 else return 1 end end @@ -13,8 +30,8 @@ end --route search implementation -- Note this is similar to recursively_find_routes in database.lua, there used for the rscache -local function recursively_find_routes(s_pos, s_connid, searching_shunt, tcbseq, mark_pos, result_table, scan_limit) - --atdebug("Recursively restarting at ",s_pos, s_connid, "limit left", scan_limit) +local function recursively_find_routes(s_pos, s_connid, searching_shunt, tcbseq, mark_pos, result_table, scan_limit, tscnt_limit) + atdebug("(SmartRoute) Recursively restarting at ",s_pos, s_connid, "limit left", scan_limit,"tscnt",tscnt_limit) local ti = advtrains.get_track_iterator(s_pos, s_connid, scan_limit, false) local pos, connid, bconnid = ti:next_branch() pos, connid, bconnid = ti:next_track()-- step once to get ahead of previous turnout @@ -25,7 +42,7 @@ local function recursively_find_routes(s_pos, s_connid, searching_shunt, tcbseq, mark_pos[pts] = true local node = advtrains.ndb.get_node_or_nil(pos) - atdebug("(SmartRoute) Walk ",pos, "nodename", node.name, "entering at conn",bconnid) + --atdebug("(SmartRoute) Walk ",pos, "nodename", node.name, "entering at conn",bconnid) local ndef = minetest.registered_nodes[node.name] if ndef.advtrains and ndef.advtrains.node_state_map then -- Stop, this is a switchable node. Find out which conns we can go at @@ -33,7 +50,9 @@ local function recursively_find_routes(s_pos, s_connid, searching_shunt, tcbseq, local out_conns = ildb.get_possible_out_connids(node.name, bconnid) for oconnid, state in pairs(out_conns) do --atdebug("Going in direction",oconnid,"state",state) - recursively_find_routes(pos, oconnid, searching_shunt, table.copy(tcbseq), table.copy(mark_pos), result_table, ti.limit) + recursively_find_routes(pos, oconnid, searching_shunt, + table.copy(tcbseq), table.copy(mark_pos), + result_table, ti.limit, tscnt_limit) end return end @@ -68,12 +87,18 @@ local function recursively_find_routes(s_pos, s_connid, searching_shunt, tcbseq, end end end + -- decrease tscnt + tscnt_limit = tscnt_limit - 1 + atdebug("(SmartRoute) Remaining TS Count:",tscnt_limit) + if tscnt_limit <= 0 then + break + end end -- Go forward last_pos = pos pos, connid, bconnid = ti:next_track() until not pos -- this stops the loop when either the track end is reached or the limit is hit - --atdebug("recursively_find_routes: Reached track end or limit at", last_pos, ". This path is not saved, returning") + atdebug("(SmartRoute) Reached track end or limit at", last_pos, ". This path is not saved, returning") end local function build_route_from_foundroute(froute, name) @@ -91,59 +116,54 @@ end -- Maximum scan length for track iterator local TS_MAX_SCAN = 1000 -function sr.init(pname, sigd) - -- is start signal a shunt signal? - local is_startsignal_shunt = false +function sr.rescan(pname, sigd, tscnt_limit, searching_shunt) + local result_table = {} + recursively_find_routes(sigd.p, sigd.s, is_startsignal_shunt, {}, {}, result_table, TS_MAX_SCAN, tscnt_limit) + return result_table +end + +-- Propose to pname the smartroute actions in a form, with the current settings as passed to this function +function sr.propose_next(pname, sigd, tscnt_limit, searching_shunt) local tcbs = ildb.get_tcbs(sigd) - if tcbs.signal then - local ndef = advtrains.ndb.get_ndef(tcbs.signal) - if ndef and ndef.advtrains then - if ndef.advtrains.route_role == "shunt" then - is_startsignal_shunt = true - end + if not tcbs or not tcbs.routes then + minetest.chat_send_player(pname, "Smartroute: TCBS or routes don't exist here!") + return + end + -- Step 1: search for routes using the current settings + local found_routes = sr.rescan(pname, sigd, tscnt_limit, searching_shunt) + -- Step 2: remove routes for endpoints for which routes already exist + local ex_endpts = {} -- key = sigd_to_string + for rtid, route in ipairs(tcbs.routes) do + local valid = advtrains.interlocking.check_route_valid(route, sigd) + local endpoint = route[#route].next -- 'next' field of the last route segment (the segment with index==len) + if valid and endpoint then + local endstr = advtrains.interlocking.sigd_to_string(endpoint) + atdebug("(Smartroute) Find existing endpoint:",route.name,"ends at",endstr) + ex_endpts[endstr] = route.name + else + atdebug("(Smartroute) Find existing endpoint:",route.name," not considered, endpoint",endpoint,"valid",valid) end end - local result_table = {} - recursively_find_routes(sigd.p, sigd.s, is_startsignal_shunt, {}, {}, result_table, TS_MAX_SCAN) - - atdebug("Smartroute search finished:",result_table) - - -- Short-circuit logic right now for testing - -- go through and delete all routes that are autogenerated - local i = 1 - while i<=#tcbs.routes do - if tcbs.routes[i].smartroute_generated then - table.remove(tcbs.routes, i) + local new_frte = {} + for _,froute in ipairs(found_routes) do + local endpoint = froute.tcbseq[#froute.tcbseq] + local endstr = advtrains.interlocking.sigd_to_string(endpoint) + if not ex_endpts[endstr] then + new_frte[#new_frte+1] = froute else - i=i+1 + atdebug("(Smartroute) Throwing away",froute.name,"because endpoint",endstr,"already reached by route",ex_endpts[endstr]) end end - -- just plainly create routes! - for idx, froute in ipairs(result_table) do + + -- All remaining routes will be shown to user now. + -- TODO: show a form. Right now still shortcircuit + local sel_rte = #tcbs.routes+1 + for idx, froute in ipairs(new_frte) do tcbs.routes[#tcbs.routes+1] = build_route_from_foundroute(froute) end - atwarn("Smartroute done!") + atdebug("Smartroute done!") + advtrains.interlocking.show_signalling_form(sigd, pname, sel_rte) end - ---[[ - player1 = { - origin = - found_routes = { - { tcbseq = {, , }, mark_pos = { table with keys being encoded_pos of rails constituting route }, to_end_of_track = false, shunt_route = false } - } - } -]]-- -local player_smartroute = {} - -minetest.register_on_punchnode(function(pos, node, player, pointed_thing) - local pname = player:get_player_name() - if not minetest.check_player_privs(pname, "interlocking") then - return - end - -- TODO -end) - - advtrains.interlocking.smartroute = sr diff --git a/advtrains_interlocking/tcb_ts_ui.lua b/advtrains_interlocking/tcb_ts_ui.lua index dfb2714..5bd6c08 100755 --- a/advtrains_interlocking/tcb_ts_ui.lua +++ b/advtrains_interlocking/tcb_ts_ui.lua @@ -6,6 +6,7 @@ local players_assign_xlink = {} local players_link_ts = {} local players_assign_fixedlocks = {} +local atil = advtrains.interlocking local ildb = advtrains.interlocking.db local ilrs = advtrains.interlocking.route @@ -746,6 +747,42 @@ function advtrains.interlocking.highlight_track_section(pos) end end +-- checks that the given route is still valid (i.e. all its TCBs, sections and locks exist) +-- returns true (ok) or false, reason (on issue) +function advtrains.interlocking.check_route_valid(route, sigd) + -- this code is partially copy-pasted from routesetting.lua + -- we start at the tc designated by signal + local c_sigd = sigd + local i = 1 + local c_tcbs, c_ts_id, c_ts, c_rseg + while c_sigd and i<=#route do + c_tcbs = ildb.get_tcbs(c_sigd) + if not c_tcbs then + return false, "No TCBS at "..sigd_to_string(c_sigd) + end + c_ts_id = c_tcbs.ts_id + if not c_ts_id then + 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] + + if c_rseg.locks then + for pts, state in pairs(c_rseg.locks) do + local pos = minetest.string_to_pos(pts) + if not advtrains.is_passive(pos) then + return false, "No passive component for lock at "..pts + end + end + end + -- advance + c_sigd = c_rseg.next + i = i + 1 + end + return true, nil, c_sigd +end + -- Signalling formspec - set routes a.s.o -- textlist selection temporary storage @@ -800,17 +837,19 @@ function advtrains.interlocking.show_signalling_form(sigd, pname, sel_rte, calle -- at least one route is defined, show normal dialog local strtab = {} for idx, route in ipairs(tcbs.routes) do + local rname = route.name + local valid = atil.check_route_valid(route, sigd) local clr = "" - if route.smartroute_generated then - clr = "#FFFF55" - end - if route.ars then + if not valid then clr = "#FF5555" + rname = rname.." (invalid)" + elseif route.ars then + clr = "#FFFF55" if route.ars.default then clr = "#55FF55" end end - strtab[#strtab+1] = clr .. minetest.formspec_escape(route.name) + strtab[#strtab+1] = clr .. minetest.formspec_escape(rname) end form = form.."label[0.5,2.5;Routes:]" form = form.."textlist[0.5,3;5,3;rtelist;"..table.concat(strtab, ",") @@ -961,8 +1000,7 @@ minetest.register_on_player_receive_fields(function(player, formname, fields) return end if fields.smartroute and hasprivs then - advtrains.interlocking.smartroute.init(pname, sigd) - minetest.close_formspec(pname, formname) + advtrains.interlocking.smartroute.start(pname, sigd) tcbs.ars_ignore_next = nil return end -- cgit v1.2.3