1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
|
-- smartroute.lua
-- Implementation of the advtrains auto-route search
local atil = advtrains.interlocking
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, 0, is_startsignal_shunt)
end
local function otherside(s)
if s==1 then return 2 else return 1 end
end
--route search implementation
-- 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 = {
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 num of sections for routes to be found
local RTE_MAX_SECS = 16
-- 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(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
--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, 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!")
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, 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)
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
-- 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
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)
players_smartroute_actions[pname] = nil
end
if fields.quit then
players_smartroute_actions[pname] = nil
end
end)
advtrains.interlocking.smartroute = sr
|