Add API documentation

This commit is contained in:
Y. Wang 2022-10-31 10:57:37 +01:00
parent 30a0f86248
commit 7c9fd9179d
No known key found for this signature in database
GPG Key ID: 54A05DDF18D7A0EB
6 changed files with 237 additions and 176 deletions

View File

@ -0,0 +1,96 @@
# Interlocking for Advtrains
The `advtrains_interlocking` mod provides various interlocking and signaling features for Advtrains.
## Signal types
There are two types of signals in Advtrains:
* Type 1 (speed signals): These signals only give speed information.
* Type 2 (route signals): These signals mainly provide route information, but sometimes also provide speed information.
## Signal aspect tables
Signal aspects are represented using tables with the following (optional) fields:
* `main`: The main signal aspect. It provides information on the permitted speed after passing the signal.
* `dst`: The distant signal aspect. It provides information on the permitted speed after passing the next signal.
* `shunt`: Whether the train may proceed in shunt mode and, if the main aspect is danger, proceed in shunt mode.
* `proceed_as_main`: Whether the train should exit shunt mode when proceeding.
* `type2group`: The type 2 group of the signal.
* `type2name`: The type 2 signal aspect name.
The `main` and `dst` fields may be:
* An positive number indicating the permitted speed,
* The number 0, indicating that the train should expect to stop at the current signal (or, for the `dst` field, the next signal),
* The number -1, indicating that the train can proceed (or, for the `dst` field, expect to proceed) at maximum speed, or
* The constant `false` or `nil`, indicating no change to the speed restriction.
### Node definitions
Signals should belong the following node groups:
* `advtrains_signal`: `1` for static signals, `2` for signals with variable aspects.
* `save_in_at_nodedb`: This should be set to `1` to make sure that Advtrains always has access to the signal.
* `not_blocking_trains`: Setting this to `1` prevents trains from colliding with the signal. Setting this is not necessary, but recommended.
The node definition should contain an `advtrains` field.
The `advtrains` field of the node definition should contain a `supported_aspects` table for signals with variable aspects.
For type 1 signals, the `supported_aspects` table should contain the following fields:
* `main`: A list of values supported for the main aspect.
* `dst`: A list of values supported for the distant aspect.
* `shunt`: The value for the `shunt` field of the signal aspect or `nil` if the value is variable.
* `proceed_as_main`: The value for the `proceed_as_main` field of the signal aspect.
For type 2 signals, the `supported_aspects` table should contain the following fields:
* `type`: The numeric constant `2`.
* `group`: The type 2 signal group.
* `dst_shift`: The phase shift for distant/repeater signals. This field should not be set for main signals.
The `advtrains` field of the node definition should contain a `get_aspect` function. This function is given the position of the signal and the node at the position. It should return the signal aspect table or, in the case of type 2 signals, the name of the signal aspect.
For signals with variable aspects, a corresponding `set_aspect` function should also be defined. This function is given the position of the signal, the node at the position, and the new aspect (or, in the case of type 2 signals, the name of the new signal aspect). For type 1 signals, the new aspect is not guranteed to be supported by the signal itself.
Signals should also have the following callbacks set:
* `on_rightclick` should be set to `advtrains.interlocking.signal_rc_handler`
* `can_dig` should be set to `advtrains.interlocking.signal_can_dig`
* `after_dig_node` should be set to `advtrains.interlocking.signal_after_dig`
Alternatively, custom callbacks should call the respective functions.
## Type 2 signal groups
Type 2 signals belong to signal gruops, which are registered using `advtrains.interlocking.aspects.register_type2`.
Signal group definitions include the following fields:
* `name`: The internal name of the signal group. It is recommended to use the mod name as a prefix to avoid name collisions.
* `label`: The description of the signal group.
* `main`: A list of signal aspects, from the least restrictive (i.e. proceed) to the most restrictive (i.e. danger).
Each aspect in the signal group definition table should contain the following fields:
* `name`: The internal name of the signal aspect.
* `label`: The description of the signal aspect.
* `main`, `shunt`, `proceed_as_main`: The fields corresponding to the ones in signal aspect tables.
Type 2 signal aspects are then referred to with the aspect names within the group.
## Notes
It is allowed to provide other methods of setting the signal aspect. However:
* These changes are ignored by the routesetting system.
* Please call `advtrains.interlocking.signal_readjust_aspect` after the signal aspect has changed.
## Examples
An example of type 1 signals can be found in `advtrains_signals_ks`, which provides a subset of German signals.
An example of type 2 signals can be found in `advtrains_signals_japan`, which provides a subset of Japanese signals.
The mods mentioned above are also used for demonstation purposes and can also be used for testing.

View File

@ -1,3 +1,8 @@
--- 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 = {}
@ -5,6 +10,9 @@ local A = advtrains.interlocking.aspects
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
@ -13,6 +21,9 @@ local function db_load(x)
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,
@ -22,6 +33,10 @@ 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]
@ -38,6 +53,10 @@ local function unassign_dst(dst, force)
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]
@ -57,11 +76,21 @@ local function unassign_main(main, force)
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
--- 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)
local pts_main = pts(main)
local pts_dst = pts(dst)
@ -87,11 +116,20 @@ local function pre_occupy(dst, by)
db_distant_of[pts_dst] = {nil, by}
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]
@ -105,6 +143,9 @@ local function get_main(dst)
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)
@ -114,10 +155,16 @@ update_main = function(main)
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)

View File

@ -1,5 +1,5 @@
-- Advtrains interlocking system
-- See database.lua for a detailed explanation
--- Advtrains interlocking system.
-- @module advtrains.interlocking
advtrains.interlocking = {}

View File

@ -1,170 +1,5 @@
-- Signal API implementation
--[[
Signal aspect table:
Note: All speeds are measured in m/s, aka the number of + signs in the HUD.
asp = {
main = <int speed>,
-- Main signal aspect, tells state and permitted speed of next section
-- 0 = section is blocked
-- >0 = section is free, speed limit is this value
-- -1 = section is free, maximum speed permitted
-- false/nil = Signal doesn't provide main signal information, retain current speed limit.
shunt = <boolean>,
-- Whether train may proceed as shunt move, on sight
-- main aspect takes precedence over this
-- When main==0, train switches to shunt move and is restricted to speed 6
proceed_as_main = <boolean>,
-- If an approaching train is a shunt move and 'shunt' is false,
-- the train may proceed as a train move under the "main" aspect
-- if the main aspect permits it (i.e. main!=0)
-- If this is not set, shunt moves are NOT allowed to switch to
-- a train move, and must stop even if "main" would permit passing.
-- This is intended to be used for "Halt for shunt moves" signs.
dst = <int speed>,
-- Distant signal aspect, tells state and permitted speed of the section after next section
-- The character of these information is purely informational
-- At this time, this field is not actively used
-- 0 = section is blocked
-- >0 = section is free, speed limit is this value
-- -1 = section is free, maximum speed permitted
-- false/nil = Signal doesn't provide distant signal information.
-- the character of call_on and dead_end is purely informative
call_on = <boolean>, -- Call-on route, expect train in track ahead (not implemented yet)
dead_end = <boolean>, -- Route ends on a dead end (e.g. bumper) (not implemented yet)
w_speed = <integer>,
-- "Warning speed restriction". Supposed for short-term speed
-- restrictions which always override any other restrictions
-- imposed by "speed" fields, until lifted by a value of -1
-- (Example: german Langsamfahrstellen-Signale)
}
}
== How signals actually work in here ==
Each signal (in the advtrains universe) is some node that has at least the
following things:
- An "influence point" that is set somewhere on a rail
- An aspect which trains that pass the "influence point" have to obey
There can be static and dynamic signals. Static signals are, roughly
spoken, signs, while dynamic signals are "real" signals which can display
different things.
The node definition of a signal node should contain those fields:
groups = {
advtrains_signal = 2,
save_in_at_nodedb = 1,
}
advtrains = {
set_aspect = function(pos, node, asp)
-- This function gets called whenever the signal should display
-- a new or changed signal aspect. It is not required that
-- the signal actually displays the exact same aspect, since
-- some signals can not do this by design. However, it must
-- display an aspect that is at least as restrictive as the passed
-- aspect as far as it is capable of doing so.
-- Examples:
-- - pure shunt signals can not display a "main" aspect
-- and have no effect on train moves, so they will only ever
-- honor the shunt.free field for their aspect.
-- - the german Hl system can only signal speeds of 40, 60
-- and 100 km/h, a speed of 80km/h should then be signalled
-- as 60 km/h instead.
-- In turn, it is not guaranteed that the aspect will fulfill the
-- criteria put down in supported_aspects.
-- If set_aspect is present, supported_aspects should also be declared.
-- The aspect passed in here can always be queried using the
-- advtrains.interlocking.signal_get_supposed_aspect(pos) function.
-- It is always DANGER when the signal is not used as route signal.
-- For static signals, this function should be completely omitted
-- If this function is omitted, it won't be possible to use
-- route setting on this signal.
end,
supported_aspects = {
-- A table which tells which different types of aspects this signal
-- is able to display. It is used to construct the "aspect editing"
-- formspec for route programming (and others) It should always be
-- present alongside with set_aspect. If this is not specified but
-- set_aspect is, the user will be allowed to select any aspect.
-- Any of the fields marked with <boolean/nil> support 3 types of values:
nil: if this signal can switch between free/blocked
false: always shows "blocked", unchangable
true: always shows "free", unchangable
-- Any of the "speed" fields should contain a list of possible values
-- to be set as restriction. If omitted, the value of the described
-- field is always assumed to be false (no information)
-- A speed of 0 means that the signal can show a "blocked" aspect
-- (which is probably the case for most signals)
-- If the signal can signal "no information" on one of the fields
-- (thus false is an acceptable value), include false in the list
-- If your signal can only display a single speed (may it be -1),
-- always enclose that single value into a list. (such as {-1})
main = {<speed1>, ..., <speedn>} or nil,
dst = {<speed1>, ..., <speedn>} or nil,
shunt = <boolean/nil>,
call_on = <boolean/nil>,
dead_end = <boolean/nil>,
w_speed = {<speed1>, ..., <speedn>} or nil,
},
Example for supported_aspects:
supported_aspects = {
main = {0, 6, -1}, -- can show either "Section blocked", "Proceed at speed 6" or "Proceed at maximum speed"
dst = {0, false}, -- can show only if next signal shows "blocked", no other information.
shunt = false, -- shunting by this signal is never allowed.
call_on = false,
dead_end = false,
w_speed = nil,
-- none of the information can be shown by the signal
},
get_aspect = function(pos, node)
-- This function gets called by the train safety system. It
should return the aspect that this signal actually displays,
not preferably the input of set_aspect.
-- For regular, full-featured light signals, they will probably
honor all entries in the original aspect, however, e.g.
simple shunt signals always return main=false regardless of
the set_aspect input because they can not signal "Halt" to
train moves.
-- advtrains.interlocking.DANGER contains a default "all-danger" aspect.
-- If your signal does not cover certain sub-tables of the aspect,
the following reasonable defaults are automatically assumed:
main = false (unchanged)
dst = false (unchanged)
shunt = false (shunting not allowed)
info = {} (no further information)
end,
}
on_rightclick = advtrains.interlocking.signal_rc_handler
can_dig = advtrains.interlocking.signal_can_dig
after_dig_node = advtrains.interlocking.signal_after_dig
(If you need to specify custom can_dig or after_dig_node callbacks,
please call those functions anyway!)
Important note: If your signal should support external ways to set its
aspect (e.g. via mesecons), there are some things that need to be considered:
- advtrains.interlocking.signal_get_supposed_aspect(pos) won't respect this
- Whenever you change the signal aspect, and that aspect change
did not happen through a call to
advtrains.interlocking.signal_set_aspect(pos, asp), you are
*required* to call this function:
advtrains.interlocking.signal_on_aspect_changed(pos)
in order to notify trains about the aspect change.
This function will query get_aspect to retrieve the new aspect.
]]--
local DANGER = {
main = 0,
shunt = false,

View File

@ -1,3 +1,6 @@
--- Signal aspect accessors
-- @module advtrains.interlocking
local A = advtrains.interlocking.aspects
local D = advtrains.distant
local I = advtrains.interlocking
@ -29,6 +32,9 @@ local get_aspect
local supposed_aspects = {}
--- Replace the signal aspect cache.
-- @function load_supposed_aspects
-- @param db The new database.
function I.load_supposed_aspects(tbl)
if tbl then
supposed_aspects = tbl
@ -38,23 +44,42 @@ function I.load_supposed_aspects(tbl)
end
end
--- Retrieve the signal aspect cache.
-- @function save_supposed_aspects
-- @return The current database in use.
function I.save_supposed_aspects()
return supposed_aspects
end
--- Read the aspect of a signal strictly from cache.
-- @param pos The position of the signal.
-- @return[1] The aspect of the signal (if present in cache).
-- @return[2] The nil constant (otherwise).
local function get_supposed_aspect(pos)
return supposed_aspects[pts(pos)]
end
--- Update the signal aspect information in cache.
-- @param pos The position of the signal.
-- @param asp The new signal aspect
local function set_supposed_aspect(pos, asp)
supposed_aspects[pts(pos)] = asp
end
--- Get the definition of a node.
-- @param pos The position of the node.
-- @return[1] The definition of the node (if present).
-- @return[2] An empty table (otherwise).
local function get_ndef(pos)
local node = N.get_node(pos)
return minetest.registered_nodes[node.name] or {}
end
--- Get the aspects supported by a signal.
-- @function signal_get_supported_aspects
-- @param pos The position of the signal.
-- @return[1] The table of supported aspects (if present).
-- @return[2] The nil constant (otherwise).
local function get_supported_aspects(pos)
local ndef = get_ndef(pos)
if ndef.advtrains and ndef.advtrains.supported_aspects then
@ -63,6 +88,11 @@ local function get_supported_aspects(pos)
return nil
end
--- Adjust a new signal aspect to fit a signal.
-- @param pos The position of the signal.
-- @param asp The new signal aspect.
-- @return The adjusted signal aspect.
-- @return The information to pass to the `advtrains.set_aspect` field in the node definitions.
local function adjust_aspect(pos, asp)
asp = table.copy(I.signal_convert_aspect_if_necessary(asp))
setmetatable(asp, signal_aspect_metatable)
@ -103,6 +133,12 @@ local function adjust_aspect(pos, asp)
return asp, asp
end
--- Get the aspect of a signal without accessing the cache.
-- For most cases, `get_aspect` should be used instead.
-- @function signal_get_real_aspect
-- @param pos The position of the signal.
-- @return[1] The signal aspect adjusted using `adjust_aspect` (if present).
-- @return[2] The nil constant (otherwise).
local function get_real_aspect(pos)
local ndef = get_ndef(pos)
if ndef.advtrains and ndef.advtrains.get_aspect then
@ -116,6 +152,11 @@ local function get_real_aspect(pos)
return nil
end
--- Get the aspect of a signal.
-- @function signal_get_aspect
-- @param pos The position of the signal.
-- @return[1] The aspect of the signal (if present).
-- @return[2] The nil constant (otherwise).
get_aspect = function(pos)
local asp = get_supposed_aspect(pos)
if not asp then
@ -125,6 +166,11 @@ get_aspect = function(pos)
return asp
end
--- Set the aspect of a signal.
-- @function signal_set_aspect
-- @param pos The position of the signal.
-- @param asp The new signal aspect.
-- @param[opt=false] skipdst Whether to skip updating distant signals.
local function set_aspect(pos, asp, skipdst)
local node = N.get_node(pos)
local ndef = minetest.registered_nodes[node.name]
@ -141,10 +187,16 @@ local function set_aspect(pos, asp, skipdst)
end
end
--- Remove a signal from cache.
-- @function signal_clear_aspect
-- @param pos The position of the signal.
local function clear_aspect(pos)
set_supposed_aspect(pos, nil)
end
--- Readjust the aspect of a signal.
-- @function signal_readjust_aspect
-- @param pos The position of the signal.
local function readjust_aspect(pos)
set_aspect(pos, get_aspect(pos))
end

View File

@ -1,5 +1,11 @@
--- Signal aspect handling.
-- @module advtrains.interlocking.aspects
local type2defs = {}
--- Register a type 2 signal group.
-- @function register_type2
-- @param def The definition table.
local function register_type2(def)
local t = {type = 2}
local name = def.name
@ -42,19 +48,21 @@ local function register_type2(def)
type2defs[name] = t
end
--- Get the definition of a type 2 signal group.
-- @function get_type2_definition
-- @param name The name of the signal group.
-- @return[1] The definition for the signal group (if present).
-- @return[2] The nil constant (otherwise).
local function get_type2_definition(name)
return type2defs[name]
end
local function get_type2_danger(group)
local def = type2defs[group]
if not def then
return nil
end
local main = def.main
return main[#main]
end
--- Get the name of the distant aspect before the current aspect.
-- @function get_type2_dst
-- @param group The name of the group.
-- @param name The name of the current aspect.
-- @return[1] The name of the distant aspect (if present).
-- @return[2] The nil constant (otherwise).
local function get_type2_dst(group, name)
local def = type2defs[group]
if not def then
@ -67,6 +75,12 @@ local function get_type2_dst(group, name)
return def.main[math.max(1, aspidx-1)].name
end
--- Convert a type 2 signal aspect to a type 1 signal aspect.
-- @function type2_to_type1
-- @param suppasp The table of supported aspects for the signal.
-- @param asp The name of the signal aspect.
-- @return[1] The type 1 signal aspect table (if present).
-- @return[2] The nil constant (otherwise).
local function type2_to_type1(suppasp, asp)
local name = suppasp.group
local shift = suppasp.dst_shift
@ -111,6 +125,13 @@ local function type2_to_type1(suppasp, asp)
return t
end
--- Convert a type 1 signal aspect table to a type 2 signal aspect.
-- @function type1_to_type2main
-- @param asp The type 1 signal aspect table
-- @param group The signal aspect group
-- @param[opt=0] shift The shift for the signal aspect.
-- @return[1] The name of the signal aspect (if present).
-- @return[2] The nil constant (otherwise).
local function type1_to_type2main(asp, group, shift)
local def = type2defs[group]
if not def then
@ -130,6 +151,11 @@ local function type1_to_type2main(asp, group, shift)
return t_main[math.max(1, idx-(shift or 0))].name
end
--- Compare two signal aspect tables.
-- @function equalp
-- @param asp1 The first signal aspect table.
-- @param asp2 The second signal aspect table.
-- @return Whether the two signal aspect tables give the same (type 1 aspect) information.
local function equalp(asp1, asp2)
if asp1 == asp2 then -- same reference
return true
@ -146,6 +172,11 @@ local function equalp(asp1, asp2)
return true
end
--- Compare two signal aspect tables.
-- @function not_equalp
-- @param asp1 The first signal aspect table.
-- @param asp2 The second signal aspect table.
-- @return The negation of `equalp``(asp1, asp2)`.
local function not_equalp(asp1, asp2)
return not equalp(asp1, asp2)
end