aboutsummaryrefslogtreecommitdiff
path: root/advtrains_interlocking
diff options
context:
space:
mode:
Diffstat (limited to 'advtrains_interlocking')
-rw-r--r--advtrains_interlocking/database.lua1
-rw-r--r--advtrains_interlocking/smartroute.lua295
-rwxr-xr-xadvtrains_interlocking/tcb_ts_ui.lua10
3 files changed, 184 insertions, 122 deletions
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