aboutsummaryrefslogtreecommitdiff
path: root/advtrains_interlocking/distant.lua
blob: 32ada8227cf7f8c162e8a3a6ac38dc189e3b9f6d (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
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
--- Distant signaling.
-- This module implements a database backend for distant signal assignments.
-- The actual modifications to signal aspects are still done by signal aspect accessors.
-- @module advtrains.interlocking.distant

local db_distant = {}
local db_distant_of = {}

local pts = advtrains.encode_pos
local stp = advtrains.decode_pos

--- Replace the distant signal assignment database.
-- @function load
-- @param db The new database to load.
local function db_load(x)
	if type(x) ~= "table" then
		return
	end
	db_distant = x.distant
	db_distant_of = x.distant_of
end

--- Retrieve the current distant signal assignment database.
-- @function save
-- @return The current database.
local function db_save()
	return {
		distant = db_distant,
		distant_of = db_distant_of,
	}
end

local update_signal, update_main, update_dst

--- Unassign a distant signal.
-- @function unassign_dst
-- @param dst The position of the distant signal.
-- @param[opt=false] force Whether to skip callbacks.
local function unassign_dst(dst, force)
	local pts_dst = pts(dst)
	local main = db_distant_of[pts_dst]
	db_distant_of[pts_dst] = nil
	if main then
		local pts_main = main[1]
		local t = db_distant[pts_main]
		if t then
			t[pts_dst] = nil
		end
	end
	if not force then
		update_dst(dst)
	end
end

--- Unassign a main signal.
-- @function unassign_main
-- @param main The position of the main signal.
-- @param[opt=false] force Whether to skip callbacks.
local function unassign_main(main, force)
	local pts_main = pts(main)
	local t = db_distant[pts_main]
	if not t then
		return
	end
	for pts_dst in pairs(t) do
		local realmain = db_distant_of[pts_dst]
		if realmain and realmain[1] == pts_main then
			db_distant_of[pts_dst] = nil
			if not force then
				local dst = stp(pts_dst)
				update_dst(dst)
			end
		end
	end
	db_distant[pts_main] = nil
end

--- Remove all (main and distant) signal assignments from a signal.
-- @function unassign_all
-- @param pos The position of the signal.
-- @param[opt=false] force Whether to skip callbacks.
local function unassign_all(pos, force)
	unassign_main(pos)
	unassign_dst(pos, force)
end

--- Check whether a signal is "appropriate" for the distant signal system.
-- Currently, a signal is considered appropriate if its signal aspect can be set.
-- @function appropriate_signal
-- @param pos The position of the signal
local function appropriate_signal(pos)
	local node = advtrains.ndb.get_node(pos)
	local ndef = minetest.registered_nodes[node.name] or {}
	if not ndef then
		return false
	end
	local atdef = ndef.advtrains
	if not atdef then
		return false
	end
	return atdef.supported_aspects and atdef.set_aspect and true
end

--- Assign a distant signal to a main signal.
-- @function assign
-- @param main The position of the main signal.
-- @param dst The position of the distant signal.
-- @param[opt="manual"] by The method of assignment.
-- @param[opt=false] skip_update Whether to skip callbacks.
local function assign(main, dst, by, skip_update)
	if not (appropriate_signal(main) and appropriate_signal(dst)) then
		return
	end
	local pts_main = pts(main)
	local pts_dst = pts(dst)
	local t = db_distant[pts_main]
	if not t then
		t = {}
		db_distant[pts_main] = t
	end
	if not by then
		by = "manual"
	end
	unassign_dst(dst, true)
	t[pts_dst] = by
	db_distant_of[pts_dst] = {pts_main, by}
	if not skip_update then
		update_dst(dst)
	end
end

--- Get the distant signals assigned to a main signal.
-- @function get_distant
-- @param main The position of the main signal.
-- @treturn {[pos]=by,...} A table of distant signals, with the positions encoded using `advtrains.encode_pos`.
local function get_distant(main)
	local pts_main = pts(main)
	return db_distant[pts_main] or {}
end

--- Get the main signal assigned the a distant signal.
-- @function get_main
-- @param dst The position of the distant signal.
-- @return The position of the main signal.
-- @return The method of assignment.
local function get_main(dst)
	local pts_dst = pts(dst)
	local main = db_distant_of[pts_dst]
	if not main then
		return
	end
	if main[1] then
		return stp(main[1]), unpack(main, 2)
	else
		return unpack(main)
	end
end

--- Update all distant signals assigned to a main signal.
-- @function update_main
-- @param main The position of the main signal.
update_main = function(main)
	local pts_main = pts(main)
	local t = get_distant(main)
	for pts_dst in pairs(t) do
		local dst = stp(pts_dst)
		advtrains.interlocking.signal_readjust_aspect(dst)
	end
end

--- Update the aspect of a distant signal.
-- @function update_dst
-- @param dst The position of the distant signal.
update_dst = function(dst)
	advtrains.interlocking.signal_readjust_aspect(dst)
end

--- Update the aspect of a combined (main and distant) signal and all distant signals assigned to it.
-- @function update_signal
-- @param pos The position of the signal.
update_signal = function(pos)
	update_main(pos)
	update_dst(pos)
end

advtrains.distant = {
	load = db_load,
	save = db_save,
	assign = assign,
	unassign_dst = unassign_dst,
	unassign_main = unassign_main,
	unassign_all = unassign_all,
	get_distant = get_distant,
	get_dst = get_distant,
	get_main = get_main,
	update_main = update_main,
	update_dst = update_dst,
	update_signal = update_signal,
	appropriate_signal = appropriate_signal,
}