aboutsummaryrefslogtreecommitdiff
path: root/advtrains_interlocking/smartroute.lua
blob: a26f2d14eba2a693bf4109f5cdd46e85a15c7fec (plain)
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
-- 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, 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

--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

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.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
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 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, 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 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 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
	atdebug("Smartroute done!")
	advtrains.interlocking.show_signalling_form(sigd, pname, sel_rte)
end


advtrains.interlocking.smartroute = sr