From c145e5db7473a0baab6438d7c2ed9616948d8387 Mon Sep 17 00:00:00 2001 From: orwell Date: Sat, 22 Jun 2024 21:11:50 +0200 Subject: SmartRoute: Implement auto route search and first prototype --- advtrains_interlocking/smartroute.lua | 149 ++++++++++++++++++++++++++++++++++ 1 file changed, 149 insertions(+) create mode 100644 advtrains_interlocking/smartroute.lua (limited to 'advtrains_interlocking/smartroute.lua') diff --git a/advtrains_interlocking/smartroute.lua b/advtrains_interlocking/smartroute.lua new file mode 100644 index 0000000..770c379 --- /dev/null +++ b/advtrains_interlocking/smartroute.lua @@ -0,0 +1,149 @@ +-- smartroute.lua +-- Implementation of the advtrains auto-route search + +local atil = advtrains.interlocking +local ildb = atil.db +local sr = {} + + +local function otherside(s) + if s==1 then return 2 else return 1 end +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 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 + local last_pos + repeat + -- record position in mark_pos + local pts = advtrains.encode_pos(pos) + mark_pos[pts] = true + + local node = advtrains.ndb.get_node_or_nil(pos) + 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 + atdebug("(SmartRoute) Found turnout ",pos, "nodename", node.name, "entering at conn",bconnid) + 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) + end + return + end + --otherwise, this might be a tcb + local tcb = ildb.get_tcb(pos) + if tcb then + local fsigd = { p = pos, s = connid } + atdebug("(SmartRoute) Encounter TCB ",fsigd) + tcbseq[#tcbseq+1] = fsigd + -- check if this is a possible route endpoint + local tcbs = tcb[connid] + if tcbs.signal then + local ndef = advtrains.ndb.get_ndef(tcbs.signal) + if ndef and ndef.advtrains then + if ndef.advtrains.route_role == "main" or ndef.advtrains.route_role == "main_distant" + or ndef.advtrains.route_role == "end" or ndef.advtrains.route_role == "shunt" then + -- signal is suitable target + local is_mainsignal = ndef.advtrains.route_role ~= "shunt" + -- record the found route in the results + result_table[#result_table+1] = { + tcbseq = table.copy(tcbseq), + mark_pos = table.copy(mark_pos), + shunt_route = not is_mainsignal, + to_end_of_track = false, + name = tcbs.signal_name or atil.sigd_to_string(fsigd) + } + -- if this is a main signal and/or we are only searching shunt routes, stop the search here + if is_mainsignal or searching_shunt then + atdebug("(SmartRoute) Terminating here because it is main or only shunt routes searched") + return + end + end + end + 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") +end + +local function build_route_from_foundroute(froute, name) + local route = { + name = froute.name, + use_rscache = true, + smartroute_generated = true, + } + for _, sigd in ipairs(froute.tcbseq) do + route[#route+1] = { next = sigd, locks = {} } + end + return route +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 + 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 + 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) + else + i=i+1 + end + end + -- just plainly create routes! + for idx, froute in ipairs(result_table) do + tcbs.routes[#tcbs.routes+1] = build_route_from_foundroute(froute) + end + atwarn("Smartroute done!") +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 -- cgit v1.2.3 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/smartroute.lua') 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 From 7d5f840579b74374698704f256479520bde25091 Mon Sep 17 00:00:00 2001 From: orwell Date: Fri, 13 Dec 2024 00:23:18 +0100 Subject: Repair sections on smartroute, detect start!=end TS in routesetting, create section with IL tool aux1 --- advtrains_interlocking/database.lua | 18 +++++++++++------- advtrains_interlocking/route_ui.lua | 13 ++++++++++++- advtrains_interlocking/routesetting.lua | 10 +++++++++- advtrains_interlocking/smartroute.lua | 23 ++++++++++++++++++----- advtrains_interlocking/tcb_ts_ui.lua | 10 +++++++++- advtrains_interlocking/tool.lua | 16 ++++++++++------ 6 files changed, 69 insertions(+), 21 deletions(-) (limited to 'advtrains_interlocking/smartroute.lua') diff --git a/advtrains_interlocking/database.lua b/advtrains_interlocking/database.lua index 3104a20..f84f60b 100644 --- a/advtrains_interlocking/database.lua +++ b/advtrains_interlocking/database.lua @@ -351,7 +351,7 @@ end -- Returns: -- ts_id - the track section that was found -- nil - No track section exists -function ildb.check_and_repair_ts_at_pos(pos, tcb_connid, notify_pname) +function ildb.check_and_repair_ts_at_pos(pos, tcb_connid, notify_pname, force_create) --atdebug("check_and_repair_ts_at_pos", pos, tcb_connid) -- check prereqs if ildb.get_tcb(pos) then @@ -378,7 +378,7 @@ function ildb.check_and_repair_ts_at_pos(pos, tcb_connid, notify_pname) -- inconsistency is found, repair it --atdebug("check_and_repair_ts_at_pos: Inconsistency is found!") tsrepair_notify(notify_pname, "Track section inconsistent here, repairing...") - return ildb.repair_ts_merge_all(all_tcbs, false, notify_pname) + return ildb.repair_ts_merge_all(all_tcbs, force_create, notify_pname) -- Step2 check is no longer necessary since we just created that new section end end @@ -386,9 +386,13 @@ function ildb.check_and_repair_ts_at_pos(pos, tcb_connid, notify_pname) -- only one found (it is either nil or a ts id) --atdebug("check_and_repair_ts_at_pos: TS consistent id=",ts_id,"") if not ts_id then - tsrepair_notify(notify_pname, "No track section found here.") - return - -- All TCBs agreed that there is no section here. + if force_create and next(all_tcbs) then --ensure at least one tcb is in list + return ildb.repair_ts_merge_all(all_tcbs, force_create, notify_pname) + else + --tsrepair_notify(notify_pname, "No track section found here.") + return nil + -- All TCBs agreed that there is no section here + end end local ts = ildb.get_ts(ts_id) @@ -403,9 +407,9 @@ function ildb.check_and_repair_ts_at_pos(pos, tcb_connid, notify_pname) if #ts.tc_breaks ~= #all_tcbs then --atdebug("check_and_repair_ts_at_pos: Partition is found!") tsrepair_notify(notify_pname, "Track section partition found, repairing...") - return ildb.repair_ts_merge_all(all_tcbs, false, notify_pname) + return ildb.repair_ts_merge_all(all_tcbs, force_create, notify_pname) end - tsrepair_notify(notify_pname, "Found section", ts.name or ts_id, "here.") + --tsrepair_notify(notify_pname, "Found section", ts.name or ts_id, "here.") return ts_id end diff --git a/advtrains_interlocking/route_ui.lua b/advtrains_interlocking/route_ui.lua index b75f9d7..7dddc6e 100644 --- a/advtrains_interlocking/route_ui.lua +++ b/advtrains_interlocking/route_ui.lua @@ -75,8 +75,17 @@ function atil.show_route_edit_form(pname, sigd, routeid, sel_rpartidx) end end end + -- sanity check, is section at next the same as the current? + local nvar = c_rseg.next + if nvar then + local re_tcbs = ildb.get_tcbs({p = nvar.p, s = (nvar.s==1) and 2 or 1}) + if not re_tcbs or not re_tcbs.ts_id or re_tcbs.ts_id~=c_ts_id then + itab(i, "-!- At "..sigd_to_string(c_sigd)..".Section Start and End do not match!", "err", nil) + break + end + end -- advance - c_sigd = c_rseg.next + c_sigd = nvar i = i + 1 end if c_sigd then @@ -135,6 +144,8 @@ function atil.show_route_edit_form(pname, sigd, routeid, sel_rpartidx) -- checkbox for call-on form = form..string.format("checkbox[4.5,4.0;se_callon;Call-on (section may be occupied);%s]", rseg.call_on) end + elseif sel_rpart and sel_rpart.err then + form = form.."textarea[4.5,2.5;4,4;errorta;Error:;"..tab[sel_rpartidx].."]" else form = form..F.label(4.5, 2, "<< Select a route part to edit options") end diff --git a/advtrains_interlocking/routesetting.lua b/advtrains_interlocking/routesetting.lua index 95ca63c..15c42aa 100644 --- a/advtrains_interlocking/routesetting.lua +++ b/advtrains_interlocking/routesetting.lua @@ -124,9 +124,17 @@ function ilrs.set_route(signal, route, try) return false, "No passive component at "..minetest.pos_to_string(pos)..". Please update track section or reconfigure route!" end end + -- sanity check, is section at next the same as the current? + local nvar = c_rseg.next + if nvar then + local re_tcbs = ildb.get_tcbs({p = nvar.p, s = (nvar.s==1) and 2 or 1}) + if not re_tcbs or not re_tcbs.ts_id or re_tcbs.ts_id~=c_ts_id then + if not try then atwarn("Encountered inconsistent ts (front~=back) while a real run of routesetting routine, at position",pts,"while setting route",rtename,"of",signal) end + return false, "TCB at "..minetest.pos_to_string(nvar.p).." has different section than previous TCB. Please update track section or reconfigure route!" + end + end -- reserve ts and write locks if not try then - local nvar = c_rseg.next if not route[i+1] then -- We shouldn't use the "next" value of the final route segment, because this can lead to accidental route-cancelling of already set routes from another signal. nvar = nil diff --git a/advtrains_interlocking/smartroute.lua b/advtrains_interlocking/smartroute.lua index 07cdf46..a26f2d1 100644 --- a/advtrains_interlocking/smartroute.lua +++ b/advtrains_interlocking/smartroute.lua @@ -30,7 +30,7 @@ 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, tscnt_limit) +local function recursively_find_routes(s_pos, s_connid, searching_shunt, tcbseq, mark_pos, result_table, scan_limit, tscnt_limit, cur_ts_id, notify_pname) 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() @@ -52,7 +52,7 @@ local function recursively_find_routes(s_pos, s_connid, searching_shunt, tcbseq, --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, tscnt_limit) + result_table, ti.limit, tscnt_limit, cur_ts_id, notify_pname) end return end @@ -62,6 +62,13 @@ local function recursively_find_routes(s_pos, s_connid, searching_shunt, tcbseq, local fsigd = { p = pos, s = connid } atdebug("(SmartRoute) Encounter TCB ",fsigd) tcbseq[#tcbseq+1] = fsigd + -- TS validity check: ensure that the TS the back connid refers to is the same as the one at the start + local re_ts_id = tcb[bconnid].ts_id + if re_ts_id ~= cur_ts_id then + atwarn("(SmartRoute) Found TS Inconsistency: entered in section",cur_ts_id,"but TCB backref is section",re_ts_id) + ildb.check_and_repair_ts_at_pos(pos, bconnid, notify_pname) + -- nothing needs to be updated on our side + end -- check if this is a possible route endpoint local tcbs = tcb[connid] if tcbs.signal then @@ -93,6 +100,9 @@ local function recursively_find_routes(s_pos, s_connid, searching_shunt, tcbseq, if tscnt_limit <= 0 then break end + -- update the cur_ts_id + cur_ts_id = tcb[connid].ts_id + atdebug("(SmartRoute) Now in section:",cur_ts_id) end -- Go forward last_pos = pos @@ -116,9 +126,9 @@ end -- Maximum scan length for track iterator local TS_MAX_SCAN = 1000 -function sr.rescan(pname, sigd, tscnt_limit, searching_shunt) +function sr.rescan(pname, sigd, tcbs, tscnt_limit, searching_shunt, pname) local result_table = {} - recursively_find_routes(sigd.p, sigd.s, is_startsignal_shunt, {}, {}, result_table, TS_MAX_SCAN, tscnt_limit) + recursively_find_routes(sigd.p, sigd.s, searching_shunt, {}, {}, result_table, TS_MAX_SCAN, tscnt_limit, tcbs.ts_id, pname) return result_table end @@ -128,9 +138,12 @@ function sr.propose_next(pname, sigd, tscnt_limit, searching_shunt) if not tcbs or not tcbs.routes then minetest.chat_send_player(pname, "Smartroute: TCBS or routes don't exist here!") return + elseif not tcbs.ts_id then + minetest.chat_send_player(pname, "Smartroute: No track section directly ahead!") + return end -- Step 1: search for routes using the current settings - local found_routes = sr.rescan(pname, sigd, tscnt_limit, searching_shunt) + local found_routes = sr.rescan(pname, sigd, tcbs, tscnt_limit, searching_shunt, pname) -- 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 diff --git a/advtrains_interlocking/tcb_ts_ui.lua b/advtrains_interlocking/tcb_ts_ui.lua index abcdf61..4f755af 100755 --- a/advtrains_interlocking/tcb_ts_ui.lua +++ b/advtrains_interlocking/tcb_ts_ui.lua @@ -776,8 +776,16 @@ function advtrains.interlocking.check_route_valid(route, sigd) end end end + -- sanity check, is section at next the same as the current? + local nvar = c_rseg.next + if nvar then + local re_tcbs = ildb.get_tcbs({p = nvar.p, s = (nvar.s==1) and 2 or 1}) + if not re_tcbs or not re_tcbs.ts_id or re_tcbs.ts_id~=c_ts_id then + return false, "TCB at "..minetest.pos_to_string(nvar.p).." has different section than previous TCB." + end + end -- advance - c_sigd = c_rseg.next + c_sigd = nvar i = i + 1 end -- check end TCB diff --git a/advtrains_interlocking/tool.lua b/advtrains_interlocking/tool.lua index 4ebc56c..560e129 100644 --- a/advtrains_interlocking/tool.lua +++ b/advtrains_interlocking/tool.lua @@ -3,7 +3,7 @@ local ilrs = advtrains.interlocking.route -local function node_right_click(pos, pname) +local function node_right_click(pos, pname, player) if advtrains.is_passive(pos) then local form = "size[7,5]label[0.5,0.5;Route lock inspector]" local pts = advtrains.encode_pos(pos) @@ -33,7 +33,7 @@ local function node_right_click(pos, pname) return end - local ts_id = advtrains.interlocking.db.check_and_repair_ts_at_pos(pos) + local ts_id = advtrains.interlocking.db.check_and_repair_ts_at_pos(pos, nil, pname) if ts_id then advtrains.interlocking.show_ts_form(ts_id, pname) else @@ -41,7 +41,7 @@ local function node_right_click(pos, pname) end end -local function node_left_click(pos, pname) +local function node_left_click(pos, pname, player) local node_ok, conns, rail_y=advtrains.get_rail_info_at(pos) if not node_ok then minetest.chat_send_player(pname, "Node is not a track!") @@ -52,8 +52,12 @@ local function node_left_click(pos, pname) advtrains.interlocking.show_tcb_marker(pos) return end + + -- create track section if aux1 button down + local pc = player:get_player_control() + local force_create = pc.aux1 - local ts_id = advtrains.interlocking.db.check_and_repair_ts_at_pos(pos, nil, pname) + local ts_id = advtrains.interlocking.db.check_and_repair_ts_at_pos(pos, nil, pname, force_create) if ts_id then advtrains.interlocking.db.update_rs_cache(ts_id) advtrains.interlocking.highlight_track_section(pos) @@ -80,7 +84,7 @@ minetest.register_craftitem("advtrains_interlocking:tool",{ end if pointed_thing.type=="node" then local pos=pointed_thing.under - node_right_click(pos, pname) + node_right_click(pos, pname, player) end end, on_use = function(itemstack, player, pointed_thing) @@ -94,7 +98,7 @@ minetest.register_craftitem("advtrains_interlocking:tool",{ end if pointed_thing.type=="node" then local pos=pointed_thing.under - node_left_click(pos, pname) + node_left_click(pos, pname, player) end end }) -- cgit v1.2.3 From 2e1681930c15954bead9c1b0ef9f4296508f60ee Mon Sep 17 00:00:00 2001 From: orwell Date: Tue, 7 Jan 2025 00:46:08 +0100 Subject: Smartroute: rework to use result of rs_cache instead of duplicating, use bread-first-search and incremental search further with formspec --- advtrains_interlocking/database.lua | 1 + advtrains_interlocking/smartroute.lua | 295 ++++++++++++++++++++-------------- advtrains_interlocking/tcb_ts_ui.lua | 10 +- 3 files changed, 184 insertions(+), 122 deletions(-) (limited to 'advtrains_interlocking/smartroute.lua') diff --git a/advtrains_interlocking/database.lua b/advtrains_interlocking/database.lua index f84f60b..38b1bc8 100644 --- a/advtrains_interlocking/database.lua +++ b/advtrains_interlocking/database.lua @@ -718,6 +718,7 @@ local function recursively_find_routes(s_pos, s_connid, locks_found, result_tabl return end local out_conns = ildb.get_possible_out_connids(node.name, bconnid) + -- note 2025-01-06: get_possible_out_connids contains some logic so that the correct switch state is selected even if the turnout is entered from the branch side (see "have_back_conn") for oconnid, state in pairs(out_conns) do --atdebug("Going in direction",oconnid,"state",state) locks_found[pts] = state diff --git a/advtrains_interlocking/smartroute.lua b/advtrains_interlocking/smartroute.lua index a26f2d1..f697d7b 100644 --- a/advtrains_interlocking/smartroute.lua +++ b/advtrains_interlocking/smartroute.lua @@ -19,7 +19,7 @@ function sr.start(pname, sigd) 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 + sr.propose_next(pname, sigd, 0, is_startsignal_shunt) end @@ -28,88 +28,10 @@ local function otherside(s) 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, tscnt_limit, cur_ts_id, notify_pname) - 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 - local last_pos - repeat - -- record position in mark_pos - local pts = advtrains.encode_pos(pos) - mark_pos[pts] = true - - local node = advtrains.ndb.get_node_or_nil(pos) - --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 - atdebug("(SmartRoute) Found turnout ",pos, "nodename", node.name, "entering at conn",bconnid) - 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, tscnt_limit, cur_ts_id, notify_pname) - end - return - end - --otherwise, this might be a tcb - local tcb = ildb.get_tcb(pos) - if tcb then - local fsigd = { p = pos, s = connid } - atdebug("(SmartRoute) Encounter TCB ",fsigd) - tcbseq[#tcbseq+1] = fsigd - -- TS validity check: ensure that the TS the back connid refers to is the same as the one at the start - local re_ts_id = tcb[bconnid].ts_id - if re_ts_id ~= cur_ts_id then - atwarn("(SmartRoute) Found TS Inconsistency: entered in section",cur_ts_id,"but TCB backref is section",re_ts_id) - ildb.check_and_repair_ts_at_pos(pos, bconnid, notify_pname) - -- nothing needs to be updated on our side - end - -- check if this is a possible route endpoint - local tcbs = tcb[connid] - if tcbs.signal then - local ndef = advtrains.ndb.get_ndef(tcbs.signal) - if ndef and ndef.advtrains then - if ndef.advtrains.route_role == "main" or ndef.advtrains.route_role == "main_distant" - or ndef.advtrains.route_role == "end" or ndef.advtrains.route_role == "shunt" then - -- signal is suitable target - local is_mainsignal = ndef.advtrains.route_role ~= "shunt" - -- record the found route in the results - result_table[#result_table+1] = { - tcbseq = table.copy(tcbseq), - mark_pos = table.copy(mark_pos), - shunt_route = not is_mainsignal, - to_end_of_track = false, - name = tcbs.signal_name or atil.sigd_to_string(fsigd) - } - -- if this is a main signal and/or we are only searching shunt routes, stop the search here - if is_mainsignal or searching_shunt then - atdebug("(SmartRoute) Terminating here because it is main or only shunt routes searched") - return - end - end - end - end - -- decrease tscnt - tscnt_limit = tscnt_limit - 1 - atdebug("(SmartRoute) Remaining TS Count:",tscnt_limit) - if tscnt_limit <= 0 then - break - end - -- update the cur_ts_id - cur_ts_id = tcb[connid].ts_id - atdebug("(SmartRoute) Now in section:",cur_ts_id) - 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("(SmartRoute) Reached track end or limit at", last_pos, ". This path is not saved, returning") -end +-- new 2025-01-06: rely on the already present info from rscache to traverse sections +-- this allows to implement a breadth first search +-- format of foundroute: +-- { name = "the name", tcbseq = { list of sigds in sequence, not containing the start sigd }} local function build_route_from_foundroute(froute, name) local route = { @@ -123,17 +45,99 @@ local function build_route_from_foundroute(froute, name) return route end --- Maximum scan length for track iterator -local TS_MAX_SCAN = 1000 +-- Maximum num of sections for routes to be found +local RTE_MAX_SECS = 16 -function sr.rescan(pname, sigd, tcbs, tscnt_limit, searching_shunt, pname) - local result_table = {} - recursively_find_routes(sigd.p, sigd.s, searching_shunt, {}, {}, result_table, TS_MAX_SCAN, tscnt_limit, tcbs.ts_id, pname) - return result_table +-- scan for possible routes from the start tcb in a bread-first-search manner +-- find_more_than: search is aborted only if more than the specified number of routes are found +function sr.rescan(pname, sigd, tcbs, find_more_than, searching_shunt, pname) + local found_routes = {} + local restart_tcbs = { {sigd = sigd, tcbseq = {} } } + local last_len = 0 + while true do + -- take first entry out of restart_tcbs (due to the way it is inserted the first entry will always be the one with the lowest length + local cur_restart + for idx, rst in ipairs(restart_tcbs) do + cur_restart = rst + table.remove(restart_tcbs, idx) + break + end + if not cur_restart then + -- we have no candidates left. Give up and return what we have + atdebug("(SR) No Candidates left, end rescan") + return found_routes + end + -- check if we need to stop due to having found enough routes + local cur_len = #cur_restart.tcbseq + if cur_len > last_len then + -- one level is finished, check if enoufh routes are found + if #found_routes > find_more_than then + atdebug("(SR) Layer finished and enough routes found, end rescan") + return found_routes + end + last_len = cur_len + end + -- our current restart point is nouw in cur_restart + local c_sigd = cur_restart.sigd + atdebug("(SR) Search continues at",c_sigd,"seqlen",#cur_restart.tcbseq) + -- do a TS repair, this also updates the RS cache should it be out of date + local c_ts_id = ildb.check_and_repair_ts_at_pos(c_sigd.p, c_sigd.s, pname, false) + if c_ts_id then + local c_ts = ildb.get_ts(c_ts_id) + local bgn_pts = advtrains.encode_pos(c_sigd.p) + local rsout = c_ts.rs_cache[bgn_pts] + if rsout then + for _, end_sigd in ipairs(c_ts.tc_breaks) do + end_pkey = advtrains.encode_pos(end_sigd.p) + if rsout[end_pkey] then + atdebug("(SR) Section",c_ts_id,c_ts.name,"has way",c_sigd,"->",end_sigd) + local nsigd = {p=end_sigd.p, s = end_sigd.s==1 and 2 or 1} -- invert to other side + -- record nsigd in the tcbseq + local ntcbseq = table.copy(cur_restart.tcbseq) + ntcbseq[#ntcbseq+1] = nsigd + local shall_continue = true + -- check if that sigd is a route target + local tcbs = ildb.get_tcbs(nsigd) + if tcbs.signal then + local ndef = advtrains.ndb.get_ndef(tcbs.signal) + if ndef and ndef.advtrains then + if ndef.advtrains.route_role == "main" or ndef.advtrains.route_role == "main_distant" + or ndef.advtrains.route_role == "end" or ndef.advtrains.route_role == "shunt" then + -- signal is suitable target + local is_mainsignal = ndef.advtrains.route_role ~= "shunt" + atdebug("(SR) Suitable end signal at",nsigd,", recording route!") + -- record the found route in the results + found_routes[#found_routes+1] = { + tcbseq = ntcbseq, + shunt_route = not is_mainsignal, + name = tcbs.signal_name or atil.sigd_to_string(fsigd) + } + -- if this is a main signal and/or we are only searching shunt routes, stop the search here + if is_mainsignal or searching_shunt then + atdebug("(SR) Not continuing this branch!") + shall_continue = false + end + end + end + end + -- unless overridden, insert the next restart point + if shall_continue then + restart_tcbs[#restart_tcbs+1] = {sigd = nsigd, tcbseq = ntcbseq } + end + end + end + else + atdebug("(SR) Section",c_ts_id,c_ts.name,"found no rscache entry for start ",bgn_pts) + end + else + atdebug("(SR) Stop at",c_sigd,"because no sec ahead") + end + end end +local players_smartroute_actions = {} -- 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) +function sr.propose_next(pname, sigd, find_more_than, searching_shunt) local tcbs = ildb.get_tcbs(sigd) if not tcbs or not tcbs.routes then minetest.chat_send_player(pname, "Smartroute: TCBS or routes don't exist here!") @@ -143,40 +147,93 @@ function sr.propose_next(pname, sigd, tscnt_limit, searching_shunt) return end -- Step 1: search for routes using the current settings - local found_routes = sr.rescan(pname, sigd, tcbs, tscnt_limit, searching_shunt, pname) - -- 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 found_routes = sr.rescan(pname, sigd, tcbs, find_more_than, searching_shunt, pname) + -- Step 2: store in actions table + players_smartroute_actions[pname] = { + sigd = sigd, + searching_shunt = searching_shunt, + found_routes = found_routes + } + -- step 3: build form + local form = "size[5,5]label[0,0;Route search: "..#found_routes.." found]" + local tab = {} + for idx, froute in ipairs(found_routes) do + tab[idx] = minetest.formspec_escape(froute.name.." (Len="..#froute.tcbseq..")") + end + form=form.."textlist[0.5,1;4,3;rtelist;"..table.concat(tab, ",").."]" + form=form.."button[0.5,4;2,1;continue;Search further]" + form=form.."button[2.5,4;2,1;apply;Apply]" + + minetest.show_formspec(pname, "at_il_smartroute_propose", form) +end + +minetest.register_on_player_receive_fields(function(player, formname, fields) + local pname = player:get_player_name() + if not minetest.check_player_privs(pname, "interlocking") then + return + end + if formname ~= "at_il_smartroute_propose" then + return + end + -- retrieve from the storage the current search result + local srtab = players_smartroute_actions[pname] + if not srtab then + return + end + local sigd = srtab.sigd + local found_routes = srtab.found_routes + + if fields.continue then + -- search on, but find at least one route more + sr.propose_next(pname, sigd, #found_routes, srtab.searching_shunt) + return + end + + if fields.apply then + -- user is happy with the found routes. Proceed to save them in the signal + local tcbs = ildb.get_tcbs(sigd) + if not tcbs then return end + -- 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 new_frte = {} + for _,froute in ipairs(found_routes) do + local endpoint = froute.tcbseq[#froute.tcbseq] 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) + if not ex_endpts[endstr] then + new_frte[#new_frte+1] = froute + else + atdebug("(Smartroute) Throwing away",froute.name,"because endpoint",endstr,"already reached by route",ex_endpts[endstr]) + end end - end - 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 - atdebug("(Smartroute) Throwing away",froute.name,"because endpoint",endstr,"already reached by route",ex_endpts[endstr]) + + -- All remaining routes will be applied to the signal + 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 + -- if only one route present and it is newly created (there was no route before, thus sel_rte==1), make default + if sel_rte == 1 and #tcbs.routes == 1 then + tcbs.routes[1].ars = {default=true} + end + atdebug("Smartroute done!") + advtrains.interlocking.show_signalling_form(sigd, pname, sel_rte) + players_smartroute_actions[pname] = nil end - - -- 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) + if fields.quit then + players_smartroute_actions[pname] = nil end - atdebug("Smartroute done!") - advtrains.interlocking.show_signalling_form(sigd, pname, sel_rte) -end +end) advtrains.interlocking.smartroute = sr diff --git a/advtrains_interlocking/tcb_ts_ui.lua b/advtrains_interlocking/tcb_ts_ui.lua index 4f755af..edc6921 100755 --- a/advtrains_interlocking/tcb_ts_ui.lua +++ b/advtrains_interlocking/tcb_ts_ui.lua @@ -871,14 +871,15 @@ function advtrains.interlocking.show_signalling_form(sigd, pname, sel_rte, calle form = form.."button[0.5,6; 5,1;setroute;Set Route]" form = form.."button[0.5,7;2,1;dsproute;Show]" if hasprivs then - form = form.."button[2.5,7;1,1;setarsdefault;Set Def.]" + form = form.."button[5.5,3.3;1,0.3;setarsdefault;D]tooltip[setarsdefault;Set ARS default route]" form = form.."button[3.5,7;2,1;editroute;Edit]" if sel_rte > 1 then - form = form .. "button[5.5,4;0.5,0.3;moveup;↑]" + form = form .. "button[5.5,4;1,0.3;moveup;↑]" end if sel_rte < #strtab then - form = form .. "button[5.5,4.7;0.5,0.3;movedown;↓]" + form = form .. "button[5.5,4.7;1,0.3;movedown;↓]" end + form = form.."button[5.5,5.4;1,0.3;delroute;X]tooltip[delroute;Delete this route]" end else form = form .. "]" @@ -1097,6 +1098,9 @@ minetest.register_on_player_receive_fields(function(player, formname, fields) end end end + if fields.delroute and hasprivs then + table.remove(tcbs.routes,sel_rte) + end end end -- cgit v1.2.3 From d42afaf9594fefbdf8d0828d43ba7091115bbfd1 Mon Sep 17 00:00:00 2001 From: orwell Date: Tue, 7 Jan 2025 23:24:57 +0100 Subject: Move logic to create block signalroutes into Smartroute instead of separate buttons, other UI enhancements --- advtrains_interlocking/database.lua | 2 + advtrains_interlocking/smartroute.lua | 22 +++++++- advtrains_interlocking/tcb_ts_ui.lua | 97 +++++++++-------------------------- 3 files changed, 45 insertions(+), 76 deletions(-) (limited to 'advtrains_interlocking/smartroute.lua') diff --git a/advtrains_interlocking/database.lua b/advtrains_interlocking/database.lua index 2152196..0a5094b 100644 --- a/advtrains_interlocking/database.lua +++ b/advtrains_interlocking/database.lua @@ -276,6 +276,8 @@ Track section -- start TCB end TCB switch pos -- Recalculated on every change via update_rs_cache -- Note that the tcb side number is not saved because it is unnecessary + fixed_locks = { "800080008000" = "st" } + -- table of fixed locks to be set for all routes thru this section (e.g. level crossings route = { origin = , -- route origin diff --git a/advtrains_interlocking/smartroute.lua b/advtrains_interlocking/smartroute.lua index f697d7b..6479e44 100644 --- a/advtrains_interlocking/smartroute.lua +++ b/advtrains_interlocking/smartroute.lua @@ -110,7 +110,7 @@ function sr.rescan(pname, sigd, tcbs, find_more_than, searching_shunt, pname) found_routes[#found_routes+1] = { tcbseq = ntcbseq, shunt_route = not is_mainsignal, - name = tcbs.signal_name or atil.sigd_to_string(fsigd) + name = tcbs.signal_name or atil.sigd_to_string(nsigd) } -- if this is a main signal and/or we are only searching shunt routes, stop the search here if is_mainsignal or searching_shunt then @@ -224,7 +224,25 @@ minetest.register_on_player_receive_fields(function(player, formname, fields) end -- if only one route present and it is newly created (there was no route before, thus sel_rte==1), make default if sel_rte == 1 and #tcbs.routes == 1 then - tcbs.routes[1].ars = {default=true} + local route1 = tcbs.routes[1] + route1.ars = {default=true} + -- if that only route furthermore is a suitable block signal route (1 section with no locks), set it into block signal mode + if #route1 == 1 then + local ts = tcbs.ts_id and advtrains.interlocking.db.get_ts(tcbs.ts_id) + if ts and #ts.tc_breaks == 2 then + -- check for presence of any locks + local epos1 = advtrains.encode_pos(ts.tc_breaks[1].p) + local epos2 = advtrains.encode_pos(ts.tc_breaks[2].p) + local haslocks = + (route1[1].locks and next(route1[1].locks)) -- the route itself has no locks + or (ts.fixed_locks and next(ts.fixed_locks)) -- the section has no fixedlocks + or (ts.rs_cache and ts.rs_cache[epos1] and ts.rs_cache[epos1][epos2] and next(ts.rs_cache[epos1][epos2])) -- the section has no locks in rscache + if not haslocks then + -- yeah, blocksignal! + route1.default_autoworking = true + end + end + end end atdebug("Smartroute done!") advtrains.interlocking.show_signalling_form(sigd, pname, sel_rte) diff --git a/advtrains_interlocking/tcb_ts_ui.lua b/advtrains_interlocking/tcb_ts_ui.lua index 9abd4f7..59d3be4 100755 --- a/advtrains_interlocking/tcb_ts_ui.lua +++ b/advtrains_interlocking/tcb_ts_ui.lua @@ -910,16 +910,6 @@ function advtrains.interlocking.show_signalling_form(sigd, pname, sel_rte, calle if hasprivs then form = form.."button[0.5,4;2.5,1;smartroute;Smart Route]" form = form.."button[ 3,4;2.5,1;newroute;New (Manual)]" - - form = form.."label[0.5,5.5;Setup block signal route (up to following signal):]" - form = form.."button[0.5,6;2.5,1;setupblocklong;Long (No Dst)]" - form = form.."tooltip[setupblocklong;Following track section must have no turnouts and end at another signal.\n" - .."Sets a route into the section ahead with auto-working set on\n" - .."Long block: This signal does not become distant signal.]" - form = form.."button[ 3,6;2.5,1;setupblockshort;Short (With Dst)]" - form = form.."tooltip[setupblockshort;Following track section must have no turnouts and end at another signal.\n" - .."Sets a route into the section ahead with auto-working set on\n" - .."Short block: This signal becomes distant signal for next signal.]" end elseif caps >= 3 then -- it's a buffer! @@ -927,7 +917,7 @@ function advtrains.interlocking.show_signalling_form(sigd, pname, sel_rte, calle .."No routes can be set from here.]" else -- signal caps say it cannot be route start/end - form = form.."label[0.5,2.5;This is a Non-Halt signal (e.g. pure distant signal)\n" + form = form.."label[0.5,2.5;This is a pure distant signal\n" .."No route is currently set through.]" end end @@ -968,10 +958,7 @@ minetest.register_on_player_receive_fields(function(player, formname, fields) end local hasprivs = minetest.check_player_privs(pname, "interlocking") - -- independent of the formspec, clear this whenever some formspec event happens local tpsi = sig_pselidx[pname] - sig_pselidx[pname] = nil - p_open_sig_form[pname] = nil local pts, connids = string.match(formname, "^at_il_signalling_([^_]+)_(%d)$") local pos, connid @@ -986,6 +973,8 @@ minetest.register_on_player_receive_fields(function(player, formname, fields) if not tcbs then return end if fields.quit then + sig_pselidx[pname] = nil + p_open_sig_form[pname] = nil -- form quit: disable temporary ARS ignore tcbs.ars_ignore_next = nil return @@ -994,7 +983,9 @@ minetest.register_on_player_receive_fields(function(player, formname, fields) local sel_rte if fields.rtelist then local tev = minetest.explode_textlist_event(fields.rtelist) - sel_rte = tev.index + if tev.type ~= "INV" then + sel_rte = tev.index + end elseif tpsi then sel_rte = tpsi end @@ -1024,63 +1015,6 @@ minetest.register_on_player_receive_fields(function(player, formname, fields) tcbs.ars_ignore_next = nil return end - if (fields.setupblocklong or fields.setupblockshort) and hasprivs then - -- check adjacent section - if not tcbs.ts_id then - minetest.chat_send_player(pname, "Block route not possible: No track section ahead") - return - end - local ts = ildb.get_ts(tcbs.ts_id) - if #ts.tc_breaks ~= 2 then - minetest.chat_send_player(pname, "Block route not possible: Section "..(ts.name or "-").." ("..tcbs.ts_id..") has "..#ts.tc_breaks.." ends, must be 2") - return - end - local e_sigd - if vector.equals(ts.tc_breaks[1].p, pos) then - e_sigd = { p = ts.tc_breaks[2].p, - s = ts.tc_breaks[2].s==1 and 2 or 1} - elseif vector.equals(ts.tc_breaks[2].p, pos) then - e_sigd = { p = ts.tc_breaks[1].p, - s = ts.tc_breaks[1].s==1 and 2 or 1} - else - minetest.chat_send_player(pname, "Block route not possible: Section "..(ts.name or "-").." ("..tcbs.ts_id..") TCBs are inconsistent, check section!") - return - end - local e_tcbs = ildb.get_tcbs(e_sigd) - if not e_tcbs then - minetest.chat_send_player(pname, "Block route not possible: Adjacent TCB not found, check section!") - return - end - -- now we have the TCB at the end of the following section. check that signal is set - if not e_tcbs.signal then - minetest.chat_send_player(pname, "Block route not possible: Adjacent TCB has no signal assigned!") - return - end - local caps = advtrains.interlocking.signal.get_signal_cap_level(e_tcbs.signal) - if caps < 3 then - minetest.chat_send_player(pname, "Block route not possible: Following signal is not capable of displaying a Halt aspect (caplevel "..caps..")") - return - end - -- all preconditions checked! go ahead and create route - local route = { - name = "BS", - [1] = { - next = e_sigd, -- of the next (note: next) TCB on the route - locks = {}, -- route locks of this route segment - assign_dst = fields.setupblockshort and true, -- assign dst, if short block was selected - }, - terminal = e_sigd, - use_rscache = true, - -- main_aspect = - default_autoworking = true, - } - local rid = #tcbs.routes + 1 -- typically 1 - tcbs.routes[rid] = route - -- directly set our newly created route - ilrs.update_route(sigd, tcbs, rid) - advtrains.interlocking.show_signalling_form(sigd, pname, nil, true) - return - end if sel_rte and tcbs.routes[sel_rte] then if fields.setroute then ilrs.update_route(sigd, tcbs, sel_rte) @@ -1098,14 +1032,29 @@ minetest.register_on_player_receive_fields(function(player, formname, fields) for rid, route in ipairs(tcbs.routes) do local isdefault = rid == sel_rte if route.ars then - route.ars.default = isdefault + if route.ars.default and isdefault then + -- D button pressed but route was already default - remove ars default field! + route.ars.default = nil + elseif isdefault then + route.ars.default = true + else + route.ars.default = nil + end + -- if the table is nouw empty delete it + if not next(route.ars) then + route.ars = nil + end elseif isdefault then route.ars = {default = true} end end end if fields.delroute and hasprivs then - table.remove(tcbs.routes,sel_rte) + if tcbs.routes[sel_rte] and tcbs.routes[sel_rte].ars then + minetest.chat_send_player(pname, "Cannot delete route which has ARS rules, please review and then delete through edit dialog!") + else + table.remove(tcbs.routes,sel_rte) + end end end end -- cgit v1.2.3 From 9bd34f738663cdc268db6399ecb6aaba2d4c9a28 Mon Sep 17 00:00:00 2001 From: orwell Date: Wed, 8 Jan 2025 00:02:03 +0100 Subject: Remove Smartroute debug prints --- advtrains_interlocking/database.lua | 8 ++++---- advtrains_interlocking/signal_api.lua | 2 +- advtrains_interlocking/smartroute.lua | 24 ++++++++++++------------ 3 files changed, 17 insertions(+), 17 deletions(-) (limited to 'advtrains_interlocking/smartroute.lua') diff --git a/advtrains_interlocking/database.lua b/advtrains_interlocking/database.lua index 0a5094b..9c72a72 100644 --- a/advtrains_interlocking/database.lua +++ b/advtrains_interlocking/database.lua @@ -84,7 +84,7 @@ function ildb.load(data) if pos then -- that was a pos_to_string local epos = advtrains.encode_pos(pos) - atdebug("ILDB converting TCB position format",pts,"->",epos) + --atdebug("ILDB converting TCB position format",pts,"->",epos) track_circuit_breaks[epos] = tcb else -- keep entry, it is already new @@ -100,7 +100,7 @@ function ildb.load(data) local lpos = minetest.string_to_pos(lpts) if lpos then local epos = advtrains.encode_pos(lpos) - atdebug("ILDB converting tcb",pts,"side",t_side,"route",t_route,"lock position format",lpts,"->",epos) + --atdebug("ILDB converting tcb",pts,"side",t_side,"route",t_route,"lock position format",lpts,"->",epos) locks_n[epos] = state else -- already correct format @@ -131,7 +131,7 @@ function ildb.load(data) if pos then -- that was a pos_to_string local epos = advtrains.encode_pos(pos) - atdebug("ILDB converting Route Lock position format",pts,"->",epos) + --atdebug("ILDB converting Route Lock position format",pts,"->",epos) advtrains.interlocking.route.rte_locks[epos] = lta else -- keep entry, it is already new @@ -535,7 +535,7 @@ function ildb.repair_ts_merge_all(all_tcbs, force_create, notify_pname) end -- Create a new fresh track section with all the TCBs we have in our collection local new_ts_id, new_ts = ildb.create_ts_from_tcb_list(all_tcbs) - tsrepair_notify(notify_pname, "Created track section",new_ts_id,"from TCBs:", all_tcbs) + tsrepair_notify(notify_pname, "Created track section",new_ts_id,"from",#all_tcbs,"TCBs") return new_ts_id end diff --git a/advtrains_interlocking/signal_api.lua b/advtrains_interlocking/signal_api.lua index 9989907..b607750 100644 --- a/advtrains_interlocking/signal_api.lua +++ b/advtrains_interlocking/signal_api.lua @@ -441,7 +441,7 @@ function signal.can_dig(pos, player) -- check privileges if not player or not minetest.check_player_privs(player:get_player_name(), "interlocking") then if not player then -- intermediate debug to uncover hard-to-find bugz - atdebug("advtrains.interlocking.signal.can_dig(",pos,") called with player==nil!") + atwarn("advtrains.interlocking.signal.can_dig(",pos,") called with player==nil!") end return false end diff --git a/advtrains_interlocking/smartroute.lua b/advtrains_interlocking/smartroute.lua index 6479e44..f03ece0 100644 --- a/advtrains_interlocking/smartroute.lua +++ b/advtrains_interlocking/smartroute.lua @@ -64,7 +64,7 @@ function sr.rescan(pname, sigd, tcbs, find_more_than, searching_shunt, pname) end if not cur_restart then -- we have no candidates left. Give up and return what we have - atdebug("(SR) No Candidates left, end rescan") + --atdebug("(SR) No Candidates left, end rescan") return found_routes end -- check if we need to stop due to having found enough routes @@ -72,14 +72,14 @@ function sr.rescan(pname, sigd, tcbs, find_more_than, searching_shunt, pname) if cur_len > last_len then -- one level is finished, check if enoufh routes are found if #found_routes > find_more_than then - atdebug("(SR) Layer finished and enough routes found, end rescan") + --atdebug("(SR) Layer finished and enough routes found, end rescan") return found_routes end last_len = cur_len end -- our current restart point is nouw in cur_restart local c_sigd = cur_restart.sigd - atdebug("(SR) Search continues at",c_sigd,"seqlen",#cur_restart.tcbseq) + --atdebug("(SR) Search continues at",c_sigd,"seqlen",#cur_restart.tcbseq) -- do a TS repair, this also updates the RS cache should it be out of date local c_ts_id = ildb.check_and_repair_ts_at_pos(c_sigd.p, c_sigd.s, pname, false) if c_ts_id then @@ -90,7 +90,7 @@ function sr.rescan(pname, sigd, tcbs, find_more_than, searching_shunt, pname) for _, end_sigd in ipairs(c_ts.tc_breaks) do end_pkey = advtrains.encode_pos(end_sigd.p) if rsout[end_pkey] then - atdebug("(SR) Section",c_ts_id,c_ts.name,"has way",c_sigd,"->",end_sigd) + --atdebug("(SR) Section",c_ts_id,c_ts.name,"has way",c_sigd,"->",end_sigd) local nsigd = {p=end_sigd.p, s = end_sigd.s==1 and 2 or 1} -- invert to other side -- record nsigd in the tcbseq local ntcbseq = table.copy(cur_restart.tcbseq) @@ -105,7 +105,7 @@ function sr.rescan(pname, sigd, tcbs, find_more_than, searching_shunt, pname) or ndef.advtrains.route_role == "end" or ndef.advtrains.route_role == "shunt" then -- signal is suitable target local is_mainsignal = ndef.advtrains.route_role ~= "shunt" - atdebug("(SR) Suitable end signal at",nsigd,", recording route!") + --atdebug("(SR) Suitable end signal at",nsigd,", recording route!") -- record the found route in the results found_routes[#found_routes+1] = { tcbseq = ntcbseq, @@ -114,7 +114,7 @@ function sr.rescan(pname, sigd, tcbs, find_more_than, searching_shunt, pname) } -- if this is a main signal and/or we are only searching shunt routes, stop the search here if is_mainsignal or searching_shunt then - atdebug("(SR) Not continuing this branch!") + --atdebug("(SR) Not continuing this branch!") shall_continue = false end end @@ -127,10 +127,10 @@ function sr.rescan(pname, sigd, tcbs, find_more_than, searching_shunt, pname) end end else - atdebug("(SR) Section",c_ts_id,c_ts.name,"found no rscache entry for start ",bgn_pts) + --atdebug("(SR) Section",c_ts_id,c_ts.name,"found no rscache entry for start ",bgn_pts) end else - atdebug("(SR) Stop at",c_sigd,"because no sec ahead") + --atdebug("(SR) Stop at",c_sigd,"because no sec ahead") end end end @@ -200,10 +200,10 @@ minetest.register_on_player_receive_fields(function(player, formname, fields) 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) + --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) + --atdebug("(Smartroute) Find existing endpoint:",route.name," not considered, endpoint",endpoint,"valid",valid) end end local new_frte = {} @@ -213,7 +213,7 @@ minetest.register_on_player_receive_fields(function(player, formname, fields) if not ex_endpts[endstr] then new_frte[#new_frte+1] = froute else - atdebug("(Smartroute) Throwing away",froute.name,"because endpoint",endstr,"already reached by route",ex_endpts[endstr]) + --atdebug("(Smartroute) Throwing away",froute.name,"because endpoint",endstr,"already reached by route",ex_endpts[endstr]) end end @@ -244,7 +244,7 @@ minetest.register_on_player_receive_fields(function(player, formname, fields) end end end - atdebug("Smartroute done!") + --atdebug("Smartroute done!") advtrains.interlocking.show_signalling_form(sigd, pname, sel_rte) players_smartroute_actions[pname] = nil end -- cgit v1.2.3 From 060f4372b2fe0cb6d41921384babab97378719c1 Mon Sep 17 00:00:00 2001 From: orwell Date: Wed, 9 Apr 2025 01:10:11 +0200 Subject: Smartroute: Append via section info to routes when ambiguous --- advtrains_interlocking/smartroute.lua | 33 ++++++++++++++++++++++++++++----- 1 file changed, 28 insertions(+), 5 deletions(-) (limited to 'advtrains_interlocking/smartroute.lua') diff --git a/advtrains_interlocking/smartroute.lua b/advtrains_interlocking/smartroute.lua index f03ece0..76c7814 100644 --- a/advtrains_interlocking/smartroute.lua +++ b/advtrains_interlocking/smartroute.lua @@ -52,7 +52,7 @@ local RTE_MAX_SECS = 16 -- find_more_than: search is aborted only if more than the specified number of routes are found function sr.rescan(pname, sigd, tcbs, find_more_than, searching_shunt, pname) local found_routes = {} - local restart_tcbs = { {sigd = sigd, tcbseq = {} } } + local restart_tcbs = { {sigd = sigd, tcbseq = {}, secseq = {} } } local last_len = 0 while true do -- take first entry out of restart_tcbs (due to the way it is inserted the first entry will always be the one with the lowest length @@ -94,7 +94,9 @@ function sr.rescan(pname, sigd, tcbs, find_more_than, searching_shunt, pname) local nsigd = {p=end_sigd.p, s = end_sigd.s==1 and 2 or 1} -- invert to other side -- record nsigd in the tcbseq local ntcbseq = table.copy(cur_restart.tcbseq) + local nsecseq = table.copy(cur_restart.secseq) ntcbseq[#ntcbseq+1] = nsigd + nsecseq[#nsecseq+1] = c_ts.name or c_ts_id local shall_continue = true -- check if that sigd is a route target local tcbs = ildb.get_tcbs(nsigd) @@ -109,6 +111,7 @@ function sr.rescan(pname, sigd, tcbs, find_more_than, searching_shunt, pname) -- record the found route in the results found_routes[#found_routes+1] = { tcbseq = ntcbseq, + secseq = nsecseq, shunt_route = not is_mainsignal, name = tcbs.signal_name or atil.sigd_to_string(nsigd) } @@ -122,7 +125,7 @@ function sr.rescan(pname, sigd, tcbs, find_more_than, searching_shunt, pname) end -- unless overridden, insert the next restart point if shall_continue then - restart_tcbs[#restart_tcbs+1] = {sigd = nsigd, tcbseq = ntcbseq } + restart_tcbs[#restart_tcbs+1] = {sigd = nsigd, tcbseq = ntcbseq, secseq = nsecseq } end end end @@ -155,12 +158,20 @@ function sr.propose_next(pname, sigd, find_more_than, searching_shunt) found_routes = found_routes } -- step 3: build form - local form = "size[5,5]label[0,0;Route search: "..#found_routes.." found]" + local form = "size[8,5]label[0,0;Route search: "..#found_routes.." found]" local tab = {} for idx, froute in ipairs(found_routes) do - tab[idx] = minetest.formspec_escape(froute.name.." (Len="..#froute.tcbseq..")") + local secfl = table.copy(froute.secseq) + table.remove(secfl, 1) -- remove first and last, because it will always be the same + secfl[#secfl]=nil + local viatext = "" + if next(secfl) then + froute.via = table.concat(secfl, ", ") + viatext = " (via "..froute.via..")" + end + tab[idx] = minetest.formspec_escape(froute.name..viatext) end - form=form.."textlist[0.5,1;4,3;rtelist;"..table.concat(tab, ",").."]" + form=form.."textlist[0.5,1;7,3;rtelist;"..table.concat(tab, ",").."]" form=form.."button[0.5,4;2,1;continue;Search further]" form=form.."button[2.5,4;2,1;apply;Apply]" @@ -207,11 +218,19 @@ minetest.register_on_player_receive_fields(function(player, formname, fields) end end local new_frte = {} + local endOnce = {} + local endTwice = {} 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 + -- record duplicate targets in froute + if endOnce[froute.name] then + endTwice[froute.name] = true + else + endOnce[froute.name] = true + end else --atdebug("(Smartroute) Throwing away",froute.name,"because endpoint",endstr,"already reached by route",ex_endpts[endstr]) end @@ -220,6 +239,10 @@ minetest.register_on_player_receive_fields(function(player, formname, fields) -- All remaining routes will be applied to the signal local sel_rte = #tcbs.routes+1 for idx, froute in ipairs(new_frte) do + if endTwice[froute.name] then + -- append via text to deduplicate name + froute.name = froute.name .. " (via "..(froute.via or "direct")..")" + end tcbs.routes[#tcbs.routes+1] = build_route_from_foundroute(froute) end -- if only one route present and it is newly created (there was no route before, thus sel_rte==1), make default -- cgit v1.2.3