From 5c8962b39bd4f6871ec87a988ac43d7bfad04d2b Mon Sep 17 00:00:00 2001 From: "Y. Wang" Date: Mon, 11 Apr 2022 16:55:50 +0200 Subject: Implement basic route signaling with Japanese signals for demo --- advtrains_interlocking/signal_api.lua | 146 ++++------------------------------ 1 file changed, 17 insertions(+), 129 deletions(-) (limited to 'advtrains_interlocking/signal_api.lua') diff --git a/advtrains_interlocking/signal_api.lua b/advtrains_interlocking/signal_api.lua index 83fae4a..0a9e6ea 100644 --- a/advtrains_interlocking/signal_api.lua +++ b/advtrains_interlocking/signal_api.lua @@ -220,8 +220,17 @@ function advtrains.interlocking.signal_set_aspect(pos, asp) local node=advtrains.ndb.get_node(pos) local ndef=minetest.registered_nodes[node.name] if ndef and ndef.advtrains and ndef.advtrains.set_aspect then + local oldasp = advtrains.interlocking.signal_get_aspect(pos) or DANGER + local suppasp = advtrains.interlocking.signal_get_supported_aspects(pos) or {} + if suppasp.type == 2 then + asp = advtrains.interlocking.aspects.type1_to_type2main(asp, suppasp.group) + end ndef.advtrains.set_aspect(pos, node, asp) - advtrains.interlocking.signal_on_aspect_changed(pos) + local actualasp = advtrains.interlocking.signal_get_aspect(pos) or DANGER + local aspect_changed = advtrains.interlocking.aspects.not_equalp(oldasp, actualasp) + if aspect_changed then + advtrains.interlocking.signal_on_aspect_changed(pos) + end end end @@ -252,7 +261,7 @@ function advtrains.interlocking.signal_rc_handler(pos, node, player, itemstack, local function callback(pname, aspect) advtrains.interlocking.signal_set_aspect(pos, aspect) end - local isasp = ndef.advtrains.get_aspect(pos, node) + local isasp = advtrains.interlocking.signal_get_aspect(pos, node) advtrains.interlocking.show_signal_aspect_selector( pname, @@ -285,8 +294,13 @@ function advtrains.interlocking.signal_get_aspect(pos) local ndef=minetest.registered_nodes[node.name] if ndef and ndef.advtrains and ndef.advtrains.get_aspect then local asp = ndef.advtrains.get_aspect(pos, node) + local suppasp = advtrains.interlocking.signal_get_supported_aspects(pos) or {} + if suppasp.type == 2 then + asp = advtrains.interlocking.aspects.type2main_to_type1(suppasp.group, asp) + end if not asp then asp = DANGER end - return convert_aspect_if_necessary(asp) + asp = convert_aspect_if_necessary(asp) + return asp end return nil end @@ -411,129 +425,3 @@ minetest.register_on_punchnode(function(pos, node, player, pointed_thing) players_assign_ip[pname] = nil end end) - - ---== aspect selector ==-- - -local players_aspsel = {} - ---[[ -suppasp: "supported_aspects" table -purpose: form title string -callback: func(pname, aspect) called on form submit -isasp: aspect currently set -]] -function advtrains.interlocking.show_signal_aspect_selector(pname, p_suppasp, p_purpose, callback, isasp) - local suppasp = p_suppasp or { - main = {0, -1}, dst = {false}, shunt = false, info = {}, - } - local purpose = p_purpose or "" - - local form = "size[7,7]label[0.5,0.5;Select Signal Aspect:]" - form = form.."label[0.5,1;"..purpose.."]" - - form = form.."label[0.5,1.5;== Main Signal ==]" - local selid = 1 - local entries = {} - for idx, spv in ipairs(suppasp.main) do - local entry - if spv == 0 then - entry = "Halt" - elseif spv == -1 then - entry = "Continue at maximum speed" - elseif not spv then - entry = "Continue\\, speed limit unchanged (no info)" - else - entry = "Continue at speed of "..spv - end - -- hack: the crappy formspec system returns the label, not the index. save the index in it. - entries[idx] = idx.."| "..entry - if isasp and spv == (isasp.main or false) then - selid = idx - end - end - form = form.."dropdown[0.5,2;6;main;"..table.concat(entries, ",")..";"..selid.."]" - - - form = form.."label[0.5,3;== Shunting ==]" - if suppasp.shunt == nil then - local st = 1 - if isasp and isasp.shunt then st=2 end - form = form.."dropdown[0.5,3.5;6;shunt_free;---,allowed;"..st.."]" - end - - form = form.."label[0.5,4.5;== Distant Signal ==]" - local selid = 1 - local entries = {} - for idx, spv in ipairs(suppasp.dst) do - local entry - if spv == 0 then - entry = "Expect to stop at the next signal" - elseif spv == -1 then - entry = "Expect to pass the next signal at maximum speed" - elseif not spv then - entry = "No info" - else - entry = string.format("Expect to pass the next signal at speed of %d", spv) - end - entries[idx] = idx.."| "..entry - if isasp and spv == (isasp.dst or false) then - selid = idx - end - end - form = form.."dropdown[0.5,5;6;dst;"..table.concat(entries, ",")..";"..selid.."]" - - form = form.."button_exit[0.5,6;5,1;save;Save signal aspect]" - - local token = advtrains.random_id() - - minetest.show_formspec(pname, "at_il_sigaspdia_"..token, form) - - minetest.after(1, function() - players_aspsel[pname] = { - suppasp = suppasp, - callback = callback, - token = token, - } - end) -end - -local function usebool(sup, val, free) - if sup == nil then - return val==free - else - return sup - end -end - --- other side of hack: extract the index -local function ddindex(val) - return tonumber(string.match(val, "^(%d+)|")) -end - --- TODO use non-hacky way to parse outputs - -minetest.register_on_player_receive_fields(function(player, formname, fields) - local pname = player:get_player_name() - local psl = players_aspsel[pname] - if psl then - if formname == "at_il_sigaspdia_"..psl.token then - if fields.save then - local maini = ddindex(fields.main) - if not maini then return end - local dsti = ddindex(fields.dst) - if not dsti then return end - local asp = { - main = psl.suppasp.main[maini], - dst = psl.suppasp.dst[dsti], - shunt = usebool(psl.suppasp.shunt, fields.shunt_free, "allowed"), - info = {} - } - psl.callback(pname, asp) - end - else - players_aspsel[pname] = nil - end - end - -end) -- cgit v1.2.3 From 220563012d2aa2c753c791fa9faa38346f1355a5 Mon Sep 17 00:00:00 2001 From: "Y. Wang" Date: Fri, 6 May 2022 21:36:21 +0200 Subject: Record signal aspect to avoid excessive get_aspect calls; report testing coverage --- advtrains_interlocking/signal_api.lua | 38 ++++++++++++++++++++++++++++++++--- 1 file changed, 35 insertions(+), 3 deletions(-) (limited to 'advtrains_interlocking/signal_api.lua') diff --git a/advtrains_interlocking/signal_api.lua b/advtrains_interlocking/signal_api.lua index 0a9e6ea..a25e1f6 100644 --- a/advtrains_interlocking/signal_api.lua +++ b/advtrains_interlocking/signal_api.lua @@ -178,6 +178,8 @@ advtrains.interlocking.GENERIC_FREE = { dst = false, } +local supposed_aspects = {} + local function convert_aspect_if_necessary(asp) if type(asp.main) == "table" then local newasp = {} @@ -199,6 +201,24 @@ local function convert_aspect_if_necessary(asp) return asp end +function advtrains.interlocking.load_supposed_aspects(tbl) + if tbl then + supposed_aspects = tbl + end +end + +function advtrains.interlocking.save_supposed_aspects() + return supposed_aspects +end + +local function set_supposed_aspect(pos, asp) + supposed_aspects[advtrains.roundfloorpts(pos)] = asp +end + +local function get_supposed_aspect(pos) + return supposed_aspects[advtrains.roundfloorpts(pos)] +end + function advtrains.interlocking.update_signal_aspect(tcbs) if tcbs.signal then local asp = tcbs.aspect or DANGER @@ -213,6 +233,7 @@ end function advtrains.interlocking.signal_after_dig(pos) -- clear influence point advtrains.interlocking.db.clear_ip_by_signalpos(pos) + set_supposed_aspect(pos, nil) end function advtrains.interlocking.signal_set_aspect(pos, asp) @@ -222,12 +243,13 @@ function advtrains.interlocking.signal_set_aspect(pos, asp) if ndef and ndef.advtrains and ndef.advtrains.set_aspect then local oldasp = advtrains.interlocking.signal_get_aspect(pos) or DANGER local suppasp = advtrains.interlocking.signal_get_supported_aspects(pos) or {} + local newasp = asp if suppasp.type == 2 then asp = advtrains.interlocking.aspects.type1_to_type2main(asp, suppasp.group) end + set_supposed_aspect(pos, newasp) ndef.advtrains.set_aspect(pos, node, asp) - local actualasp = advtrains.interlocking.signal_get_aspect(pos) or DANGER - local aspect_changed = advtrains.interlocking.aspects.not_equalp(oldasp, actualasp) + local aspect_changed = advtrains.interlocking.aspects.not_equalp(oldasp, newasp) if aspect_changed then advtrains.interlocking.signal_on_aspect_changed(pos) end @@ -289,7 +311,7 @@ end -- Returns the actual aspect of the signal at position, as returned by the nodedef. -- returns nil when there's no signal at the position -function advtrains.interlocking.signal_get_aspect(pos) +function advtrains.interlocking.signal_get_real_aspect(pos) local node=advtrains.ndb.get_node(pos) local ndef=minetest.registered_nodes[node.name] if ndef and ndef.advtrains and ndef.advtrains.get_aspect then @@ -305,6 +327,16 @@ function advtrains.interlocking.signal_get_aspect(pos) return nil end +-- Returns the signal aspect as reported in the suppasp table. +function advtrains.interlocking.signal_get_aspect(pos) + local asp = get_supposed_aspect(pos) + if not asp then + asp = advtrains.interlocking.signal_get_real_aspect(pos) + set_supposed_aspect(pos) + end + return asp +end + -- Returns the "supported_aspects" of the signal at position, as returned by the nodedef. -- returns nil when there's no signal at the position function advtrains.interlocking.signal_get_supported_aspects(pos) -- cgit v1.2.3 From d1a0d8f2654d6ee64c1a43de7958b1eadfaff6b0 Mon Sep 17 00:00:00 2001 From: "Y. Wang" Date: Fri, 10 Jun 2022 22:21:54 +0200 Subject: Use tabs to switch between signaling and IP forms --- advtrains_interlocking/signal_api.lua | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) (limited to 'advtrains_interlocking/signal_api.lua') diff --git a/advtrains_interlocking/signal_api.lua b/advtrains_interlocking/signal_api.lua index a25e1f6..5b3baf8 100644 --- a/advtrains_interlocking/signal_api.lua +++ b/advtrains_interlocking/signal_api.lua @@ -272,7 +272,10 @@ function advtrains.interlocking.signal_rc_handler(pos, node, player, itemstack, advtrains.interlocking.show_ip_form(pos, pname) return end + advtrains.interlocking.show_signal_form(pos, node, pname) +end +function advtrains.interlocking.show_signal_form(pos, node, pname) local sigd = advtrains.interlocking.db.get_sigd_for_signal(pos) if sigd then advtrains.interlocking.show_signalling_form(sigd, pname) @@ -288,7 +291,7 @@ function advtrains.interlocking.signal_rc_handler(pos, node, player, itemstack, advtrains.interlocking.show_signal_aspect_selector( pname, ndef.advtrains.supported_aspects, - "Set aspect manually", callback, + pos, callback, isasp) else --static signal - only IP @@ -332,7 +335,7 @@ function advtrains.interlocking.signal_get_aspect(pos) local asp = get_supposed_aspect(pos) if not asp then asp = advtrains.interlocking.signal_get_real_aspect(pos) - set_supposed_aspect(pos) + set_supposed_aspect(pos, asp) end return asp end @@ -372,6 +375,7 @@ function advtrains.interlocking.show_ip_form(pos, pname, only_notset) return end local form = "size[7,5]label[0.5,0.5;Signal at "..minetest.pos_to_string(pos).."]" + form = form .. advtrains.interlocking.make_signal_formspec_tabheader(pname, pos, 7, 2) advtrains.interlocking.db.check_for_duplicate_ip(pos) local pts, connid = advtrains.interlocking.db.get_ip_by_signalpos(pos) if pts then @@ -394,6 +398,9 @@ end minetest.register_on_player_receive_fields(function(player, formname, fields) local pname = player:get_player_name() + if advtrains.interlocking.handle_signal_formspec_tabheader_fields(pname, fields) then + return true + end if not minetest.check_player_privs(pname, {train_operator=true, interlocking=true}) then return end -- cgit v1.2.3 From 98c37108762c6d7c9f1d691b84f49bfa65b81b28 Mon Sep 17 00:00:00 2001 From: "Y. Wang" Date: Sat, 11 Jun 2022 18:07:00 +0200 Subject: Implement primitive distant signaling --- advtrains_interlocking/signal_api.lua | 85 ++--------------------------------- 1 file changed, 3 insertions(+), 82 deletions(-) (limited to 'advtrains_interlocking/signal_api.lua') diff --git a/advtrains_interlocking/signal_api.lua b/advtrains_interlocking/signal_api.lua index 5b3baf8..1fd4e34 100644 --- a/advtrains_interlocking/signal_api.lua +++ b/advtrains_interlocking/signal_api.lua @@ -167,7 +167,6 @@ This function will query get_aspect to retrieve the new aspect. local DANGER = { main = 0, - dst = false, shunt = false, } advtrains.interlocking.DANGER = DANGER @@ -178,8 +177,6 @@ advtrains.interlocking.GENERIC_FREE = { dst = false, } -local supposed_aspects = {} - local function convert_aspect_if_necessary(asp) if type(asp.main) == "table" then local newasp = {} @@ -200,24 +197,7 @@ local function convert_aspect_if_necessary(asp) end return asp end - -function advtrains.interlocking.load_supposed_aspects(tbl) - if tbl then - supposed_aspects = tbl - end -end - -function advtrains.interlocking.save_supposed_aspects() - return supposed_aspects -end - -local function set_supposed_aspect(pos, asp) - supposed_aspects[advtrains.roundfloorpts(pos)] = asp -end - -local function get_supposed_aspect(pos) - return supposed_aspects[advtrains.roundfloorpts(pos)] -end +advtrains.interlocking.signal_convert_aspect_if_necessary = convert_aspect_if_necessary function advtrains.interlocking.update_signal_aspect(tcbs) if tcbs.signal then @@ -233,27 +213,8 @@ end function advtrains.interlocking.signal_after_dig(pos) -- clear influence point advtrains.interlocking.db.clear_ip_by_signalpos(pos) - set_supposed_aspect(pos, nil) -end - -function advtrains.interlocking.signal_set_aspect(pos, asp) - asp = convert_aspect_if_necessary(asp) - local node=advtrains.ndb.get_node(pos) - local ndef=minetest.registered_nodes[node.name] - if ndef and ndef.advtrains and ndef.advtrains.set_aspect then - local oldasp = advtrains.interlocking.signal_get_aspect(pos) or DANGER - local suppasp = advtrains.interlocking.signal_get_supported_aspects(pos) or {} - local newasp = asp - if suppasp.type == 2 then - asp = advtrains.interlocking.aspects.type1_to_type2main(asp, suppasp.group) - end - set_supposed_aspect(pos, newasp) - ndef.advtrains.set_aspect(pos, node, asp) - local aspect_changed = advtrains.interlocking.aspects.not_equalp(oldasp, newasp) - if aspect_changed then - advtrains.interlocking.signal_on_aspect_changed(pos) - end - end + advtrains.interlocking.signal_clear_aspect(pos) + advtrains.distant.unassign_all(pos, true) end -- should be called when aspect has changed on this signal. @@ -312,46 +273,6 @@ function advtrains.interlocking.signal_get_supposed_aspect(pos) return DANGER; end --- Returns the actual aspect of the signal at position, as returned by the nodedef. --- returns nil when there's no signal at the position -function advtrains.interlocking.signal_get_real_aspect(pos) - local node=advtrains.ndb.get_node(pos) - local ndef=minetest.registered_nodes[node.name] - if ndef and ndef.advtrains and ndef.advtrains.get_aspect then - local asp = ndef.advtrains.get_aspect(pos, node) - local suppasp = advtrains.interlocking.signal_get_supported_aspects(pos) or {} - if suppasp.type == 2 then - asp = advtrains.interlocking.aspects.type2main_to_type1(suppasp.group, asp) - end - if not asp then asp = DANGER end - asp = convert_aspect_if_necessary(asp) - return asp - end - return nil -end - --- Returns the signal aspect as reported in the suppasp table. -function advtrains.interlocking.signal_get_aspect(pos) - local asp = get_supposed_aspect(pos) - if not asp then - asp = advtrains.interlocking.signal_get_real_aspect(pos) - set_supposed_aspect(pos, asp) - end - return asp -end - --- Returns the "supported_aspects" of the signal at position, as returned by the nodedef. --- returns nil when there's no signal at the position -function advtrains.interlocking.signal_get_supported_aspects(pos) - local node=advtrains.ndb.get_node(pos) - local ndef=minetest.registered_nodes[node.name] - if ndef and ndef.advtrains and ndef.advtrains.supported_aspects then - local asp = ndef.advtrains.supported_aspects - return asp - end - return nil -end - local players_assign_ip = {} local function ipmarker(ipos, connid) -- cgit v1.2.3 From 875968f078ef89b13afae6d02a2dabe5ea7897cb Mon Sep 17 00:00:00 2001 From: "Y. Wang" Date: Sun, 3 Jul 2022 12:45:27 +0200 Subject: Unassign distant signals when canceling route --- advtrains_interlocking/signal_api.lua | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) (limited to 'advtrains_interlocking/signal_api.lua') diff --git a/advtrains_interlocking/signal_api.lua b/advtrains_interlocking/signal_api.lua index 1fd4e34..eec70f9 100644 --- a/advtrains_interlocking/signal_api.lua +++ b/advtrains_interlocking/signal_api.lua @@ -176,6 +176,11 @@ advtrains.interlocking.GENERIC_FREE = { shunt = false, dst = false, } +advtrains.interlocking.FULL_FREE = { + main = -1, + shunt = false, + proceed_as_main = true, +} local function convert_aspect_if_necessary(asp) if type(asp.main) == "table" then @@ -212,7 +217,7 @@ end function advtrains.interlocking.signal_after_dig(pos) -- clear influence point - advtrains.interlocking.db.clear_ip_by_signalpos(pos) + advtrains.interlocking.signal_clear_aspect(pos) advtrains.distant.unassign_all(pos, true) end -- cgit v1.2.3 From 4a3d442601a800e28a274026392461bd1a7cb127 Mon Sep 17 00:00:00 2001 From: "Y. Wang" Date: Sun, 3 Jul 2022 15:34:42 +0200 Subject: Reduce number of set_aspect calls --- advtrains_interlocking/signal_api.lua | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'advtrains_interlocking/signal_api.lua') diff --git a/advtrains_interlocking/signal_api.lua b/advtrains_interlocking/signal_api.lua index eec70f9..1c6ed27 100644 --- a/advtrains_interlocking/signal_api.lua +++ b/advtrains_interlocking/signal_api.lua @@ -204,10 +204,10 @@ local function convert_aspect_if_necessary(asp) end advtrains.interlocking.signal_convert_aspect_if_necessary = convert_aspect_if_necessary -function advtrains.interlocking.update_signal_aspect(tcbs) +function advtrains.interlocking.update_signal_aspect(tcbs, skipdst) if tcbs.signal then local asp = tcbs.aspect or DANGER - advtrains.interlocking.signal_set_aspect(tcbs.signal, asp) + advtrains.interlocking.signal_set_aspect(tcbs.signal, asp, skipdst) end end -- cgit v1.2.3 From a4abbf8824f893558861bff2893181ae02ed284d Mon Sep 17 00:00:00 2001 From: "Y. Wang" Date: Sat, 13 Aug 2022 16:16:17 +0200 Subject: Add hotfix for path invalidation --- advtrains_interlocking/signal_api.lua | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) (limited to 'advtrains_interlocking/signal_api.lua') diff --git a/advtrains_interlocking/signal_api.lua b/advtrains_interlocking/signal_api.lua index 1c6ed27..e615692 100644 --- a/advtrains_interlocking/signal_api.lua +++ b/advtrains_interlocking/signal_api.lua @@ -227,8 +227,10 @@ function advtrains.interlocking.signal_on_aspect_changed(pos) local ipts, iconn = advtrains.interlocking.db.get_ip_by_signalpos(pos) if not ipts then return end local ipos = minetest.string_to_pos(ipts) - - advtrains.invalidate_all_paths_ahead(ipos) + + -- FIXME: invalidate_all_paths_ahead does not appear to always work as expected + --advtrains.invalidate_all_paths_ahead(ipos) + minetest.after(0, advtrains.invalidate_all_paths, ipos) end function advtrains.interlocking.signal_rc_handler(pos, node, player, itemstack, pointed_thing) -- cgit v1.2.3 From 7c9fd9179dfcec06107ad1a66346418827bdc4eb Mon Sep 17 00:00:00 2001 From: "Y. Wang" Date: Mon, 31 Oct 2022 10:57:37 +0100 Subject: Add API documentation --- advtrains_interlocking/signal_api.lua | 165 ---------------------------------- 1 file changed, 165 deletions(-) (limited to 'advtrains_interlocking/signal_api.lua') diff --git a/advtrains_interlocking/signal_api.lua b/advtrains_interlocking/signal_api.lua index e615692..54202f0 100644 --- a/advtrains_interlocking/signal_api.lua +++ b/advtrains_interlocking/signal_api.lua @@ -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 = , - -- 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 = , - -- 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 = , - -- 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 = , - -- 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 = , -- Call-on route, expect train in track ahead (not implemented yet) - dead_end = , -- Route ends on a dead end (e.g. bumper) (not implemented yet) - - w_speed = , - -- "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 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 = {, ..., } or nil, - dst = {, ..., } or nil, - shunt = , - - call_on = , - dead_end = , - w_speed = {, ..., } 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, -- cgit v1.2.3 From 67efae9c9ad6f93aaa418d512c4ae1817eed6d24 Mon Sep 17 00:00:00 2001 From: "Y. Wang" Date: Sun, 18 Dec 2022 11:01:26 +0100 Subject: Adjust signal aspect formspecs to be of similar size --- advtrains_interlocking/signal_api.lua | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) (limited to 'advtrains_interlocking/signal_api.lua') diff --git a/advtrains_interlocking/signal_api.lua b/advtrains_interlocking/signal_api.lua index 54202f0..8bb92bf 100644 --- a/advtrains_interlocking/signal_api.lua +++ b/advtrains_interlocking/signal_api.lua @@ -137,14 +137,18 @@ function advtrains.interlocking.show_ip_form(pos, pname, only_notset) if not minetest.check_player_privs(pname, "interlocking") then return end - local form = "size[7,5]label[0.5,0.5;Signal at "..minetest.pos_to_string(pos).."]" - form = form .. advtrains.interlocking.make_signal_formspec_tabheader(pname, pos, 7, 2) + local form = "size[7,6.5]label[0.5,0.5;Signal at "..minetest.pos_to_string(pos).."]" + local node = advtrains.ndb.get_node(pos) + local ndef = minetest.registered_nodes[node.name] or {} + if ndef.advtrains and ndef.advtrains.set_aspect then + form = form .. advtrains.interlocking.make_signal_formspec_tabheader(pname, pos, 7, 2) + end advtrains.interlocking.db.check_for_duplicate_ip(pos) local pts, connid = advtrains.interlocking.db.get_ip_by_signalpos(pos) if pts then form = form.."label[0.5,1.5;Influence point is set at "..pts.."/"..connid.."]" - form = form.."button_exit[0.5,2.5; 5,1;set;Move]" - form = form.."button_exit[0.5,3.5; 5,1;clear;Clear]" + form = form.."button_exit[0.5,4.25; 6,1;set;Move]" + form = form.."button_exit[0.5,5.25; 6,1;clear;Clear]" local ipos = minetest.string_to_pos(pts) ipmarker(ipos, connid) else @@ -152,7 +156,7 @@ function advtrains.interlocking.show_ip_form(pos, pname, only_notset) form = form.."label[0.5,2.0;It is recommended to set an influence point.]" form = form.."label[0.5,2.5;This is the point where trains will obey the signal.]" - form = form.."button_exit[0.5,3.5; 5,1;set;Set]" + form = form.."button_exit[0.5,5.25; 6,1;set;Set]" end if not only_notset or not pts then minetest.show_formspec(pname, "at_il_ipassign_"..minetest.pos_to_string(pos), form) -- cgit v1.2.3 From 2d072cdc67ec3018eaba36b7c79c2b19e574a94d Mon Sep 17 00:00:00 2001 From: "Y. Wang" Date: Fri, 6 Jan 2023 14:45:03 +0100 Subject: Rework formspecs; add description to JP signal group --- advtrains_interlocking/signal_api.lua | 75 ++++++++++++++++++++--------------- 1 file changed, 44 insertions(+), 31 deletions(-) (limited to 'advtrains_interlocking/signal_api.lua') diff --git a/advtrains_interlocking/signal_api.lua b/advtrains_interlocking/signal_api.lua index 8bb92bf..cd408d7 100644 --- a/advtrains_interlocking/signal_api.lua +++ b/advtrains_interlocking/signal_api.lua @@ -1,5 +1,7 @@ -- Signal API implementation +local F = advtrains.formspec + local DANGER = { main = 0, shunt = false, @@ -131,57 +133,68 @@ local function ipmarker(ipos, connid) }) end --- shows small info form for signal IP state/assignment +function advtrains.interlocking.make_ip_formspec_component(pos, x, y, w) + advtrains.interlocking.db.check_for_duplicate_ip(pos) + local pts, connid = advtrains.interlocking.db.get_ip_by_signalpos(pos) + if pts then + return table.concat { + F.S_label(x, y, "Influence point is set at @1.", string.format("%s/%s", pts, connid)), + F.S_button_exit(x, y+0.5, w/2-0.125, "ip_set", "Modify"), + F.S_button_exit(x+w/2+0.125, y+0.5, w/2-0.125, "ip_clear", "Clear"), + }, pts, connid + else + return table.concat { + F.S_label(x, y, "Influence point is not set."), + F.S_button_exit(x, y+0.5, w, "ip_set", "Set influence point"), + } + end +end + +-- shows small info form for signal properties +-- This function is named show_ip_form because it was originally only intended +-- for assigning/changing the influence point. -- only_notset: show only if it is not set yet (used by signal tcb assignment) function advtrains.interlocking.show_ip_form(pos, pname, only_notset) if not minetest.check_player_privs(pname, "interlocking") then return end - local form = "size[7,6.5]label[0.5,0.5;Signal at "..minetest.pos_to_string(pos).."]" - local node = advtrains.ndb.get_node(pos) - local ndef = minetest.registered_nodes[node.name] or {} - if ndef.advtrains and ndef.advtrains.set_aspect then - form = form .. advtrains.interlocking.make_signal_formspec_tabheader(pname, pos, 7, 2) - end - advtrains.interlocking.db.check_for_duplicate_ip(pos) - local pts, connid = advtrains.interlocking.db.get_ip_by_signalpos(pos) + local ipform, pts, connid = advtrains.interlocking.make_ip_formspec_component(pos, 0.5, 0.5, 7) + local form = table.concat { + "formspec_version[4]", + "size[8,6.75]", + ipform, + advtrains.interlocking.make_dst_formspec_component(pos, 0.5, 2, 7, 4.25), + } if pts then - form = form.."label[0.5,1.5;Influence point is set at "..pts.."/"..connid.."]" - form = form.."button_exit[0.5,4.25; 6,1;set;Move]" - form = form.."button_exit[0.5,5.25; 6,1;clear;Clear]" local ipos = minetest.string_to_pos(pts) ipmarker(ipos, connid) - else - form = form.."label[0.5,1.5;Influence point is not set.]" - form = form.."label[0.5,2.0;It is recommended to set an influence point.]" - form = form.."label[0.5,2.5;This is the point where trains will obey the signal.]" - - form = form.."button_exit[0.5,5.25; 6,1;set;Set]" end if not only_notset or not pts then - minetest.show_formspec(pname, "at_il_ipassign_"..minetest.pos_to_string(pos), form) + minetest.show_formspec(pname, "at_il_propassign_"..minetest.pos_to_string(pos), form) end end -minetest.register_on_player_receive_fields(function(player, formname, fields) - local pname = player:get_player_name() - if advtrains.interlocking.handle_signal_formspec_tabheader_fields(pname, fields) then - return true - end - if not minetest.check_player_privs(pname, {train_operator=true, interlocking=true}) then +function advtrains.interlocking.handle_ip_formspec_fields(pname, pos, fields) + if not (pos and minetest.check_player_privs(pname, {train_operator=true, interlocking=true})) then return end - local pts = string.match(formname, "^at_il_ipassign_([^_]+)$") + if fields.ip_set then + advtrains.interlocking.signal_init_ip_assign(pos, pname) + elseif fields.ip_clear then + advtrains.interlocking.db.clear_ip_by_signalpos(pos) + end +end + +minetest.register_on_player_receive_fields(function(player, formname, fields) + local pname = player:get_player_name() + local pts = string.match(formname, "^at_il_propassign_([^_]+)$") local pos if pts then pos = minetest.string_to_pos(pts) end if pos then - if fields.set then - advtrains.interlocking.signal_init_ip_assign(pos, pname) - elseif fields.clear then - advtrains.interlocking.db.clear_ip_by_signalpos(pos) - end + advtrains.interlocking.handle_ip_formspec_fields(pname, pos, fields) + advtrains.interlocking.handle_dst_formspec_fields(pname, pos, fields) end end) -- cgit v1.2.3 From d443d8e07af89665a6bb3d87af91f43f08a6c47e Mon Sep 17 00:00:00 2001 From: "Y. Wang" Date: Fri, 6 Jan 2023 18:23:15 +0100 Subject: Distant signaling: avoid signal signs --- advtrains_interlocking/signal_api.lua | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) (limited to 'advtrains_interlocking/signal_api.lua') diff --git a/advtrains_interlocking/signal_api.lua b/advtrains_interlocking/signal_api.lua index cd408d7..1b4a21c 100644 --- a/advtrains_interlocking/signal_api.lua +++ b/advtrains_interlocking/signal_api.lua @@ -159,16 +159,20 @@ function advtrains.interlocking.show_ip_form(pos, pname, only_notset) return end local ipform, pts, connid = advtrains.interlocking.make_ip_formspec_component(pos, 0.5, 0.5, 7) - local form = table.concat { + local form = { "formspec_version[4]", - "size[8,6.75]", + "size[8,2.25]", ipform, - advtrains.interlocking.make_dst_formspec_component(pos, 0.5, 2, 7, 4.25), } if pts then local ipos = minetest.string_to_pos(pts) ipmarker(ipos, connid) end + if advtrains.distant.appropriate_signal(pos) then + form[#form+1] = advtrains.interlocking.make_dst_formspec_component(pos, 0.5, 2, 7, 4.25) + form[2] = "size[8,6.75]" + end + form = table.concat(form) if not only_notset or not pts then minetest.show_formspec(pname, "at_il_propassign_"..minetest.pos_to_string(pos), form) end -- cgit v1.2.3 From e25b1c744dad7daa561ee0b23b006bc616f88f23 Mon Sep 17 00:00:00 2001 From: "Y. Wang" Date: Sun, 26 Mar 2023 11:53:00 +0200 Subject: Cancel type 2 signals; introduce signal groups for all signals --- advtrains_interlocking/signal_api.lua | 28 ++++------------------------ 1 file changed, 4 insertions(+), 24 deletions(-) (limited to 'advtrains_interlocking/signal_api.lua') diff --git a/advtrains_interlocking/signal_api.lua b/advtrains_interlocking/signal_api.lua index 1b4a21c..ce8854a 100644 --- a/advtrains_interlocking/signal_api.lua +++ b/advtrains_interlocking/signal_api.lua @@ -19,27 +19,7 @@ advtrains.interlocking.FULL_FREE = { proceed_as_main = true, } -local function convert_aspect_if_necessary(asp) - if type(asp.main) == "table" then - local newasp = {} - if asp.main.free then - newasp.main = asp.main.speed - else - newasp.main = 0 - end - if asp.dst and asp.dst.free then - newasp.dst = asp.dst.speed - else - newasp.dst = 0 - end - newasp.proceed_as_main = asp.shunt.proceed_as_main - newasp.shunt = asp.shunt.free - -- Note: info table not transferred, it's not used right now - return newasp - end - return asp -end -advtrains.interlocking.signal_convert_aspect_if_necessary = convert_aspect_if_necessary +advtrains.interlocking.signal_convert_aspect_if_necessary = advtrains.interlocking.aspect function advtrains.interlocking.update_signal_aspect(tcbs, skipdst) if tcbs.signal then @@ -79,7 +59,7 @@ function advtrains.interlocking.signal_rc_handler(pos, node, player, itemstack, end advtrains.interlocking.show_signal_form(pos, node, pname) end - + function advtrains.interlocking.show_signal_form(pos, node, pname) local sigd = advtrains.interlocking.db.get_sigd_for_signal(pos) if sigd then @@ -92,7 +72,7 @@ function advtrains.interlocking.show_signal_form(pos, node, pname) advtrains.interlocking.signal_set_aspect(pos, aspect) end local isasp = advtrains.interlocking.signal_get_aspect(pos, node) - + advtrains.interlocking.show_signal_aspect_selector( pname, ndef.advtrains.supported_aspects, @@ -123,7 +103,7 @@ local function ipmarker(ipos, connid) local node_ok, conns, rhe = advtrains.get_rail_info_at(ipos, advtrains.all_tracktypes) if not node_ok then return end local yaw = advtrains.dir_to_angle(conns[connid].c) - + -- using tcbmarker here local obj = minetest.add_entity(vector.add(ipos, {x=0, y=0.2, z=0}), "advtrains_interlocking:tcbmarker") if not obj then return end -- cgit v1.2.3 From aa9033f9010b01e7ad28f2b8bb0c002f065ddc08 Mon Sep 17 00:00:00 2001 From: orwell Date: Sun, 28 Jan 2024 23:42:56 +0100 Subject: Implementation Plan --- advtrains_interlocking/signal_api.lua | 53 +++++++++++++++++++++++++++++++++++ 1 file changed, 53 insertions(+) (limited to 'advtrains_interlocking/signal_api.lua') diff --git a/advtrains_interlocking/signal_api.lua b/advtrains_interlocking/signal_api.lua index ce8854a..743e8e1 100644 --- a/advtrains_interlocking/signal_api.lua +++ b/advtrains_interlocking/signal_api.lua @@ -19,6 +19,59 @@ advtrains.interlocking.FULL_FREE = { proceed_as_main = true, } +--[[ +Implementation plan orwell 2024-01-28: +Most parts of ywang's implementation are fine, especially I like the formspecs. But I would like to change a few aspects (no pun intended) of this. +- Signal gets distant assigned via field in signal aspect table (instead of explicitly) +- Signal speed/shunt are no longer free-text but rather they need to be predefined in the node definition +To do this: Differentiation between: +== Aspect Group == +This is what a signal is assigned by either the route system or the user. +It is a string key which has an appropriate entry in the node definition (where it has a description assigned) +The signal mod defines a function to set a signal to the most appropriate aspect. This function gets +a) the aspect group name +b) the distant signal's aspect group name & aspect table +EVERY signal must define the special aspect group "halt". This must always be the most restrictive aspect possible. +The "halt" aspect group should ignore any distant info, in most cases it is called without them anyway. + +== Aspect == +One concrete combination of lights/shapes that a signal signal shows. Handling these is at the discretion of +the signal mod defining the signal, and they are typically combinations of main aspect and distant aspect +Example: +- A Ks signal has the aspect_group="proceed_12" set for a route +- The signal at the end of the route shows aspect_group="proceed_8", advtrains also passes on that this means {main=8, shunt=false} +- The ndef.advtrains.apply_aspect(pos, asp_group, dst_aspgrp, dst_aspinfo) determines that the signal should now show + blinking green with main indicator 12 and dst indicator 8, and sets the nodes accordingly. + This function can now return the Aspect Info table, which will be cached by advtrains until the aspect changes again + and will be used when a train approaches the signal. If nil is returned, then the aspect will be queried next time + by calling ndef.advtrains.get_aspect_info(pos) + +Note that once apply_aspect returns, there is no need for advtrains anymore to query the aspect info. +When the signal, for any reason, wants to change its aspect by itself *without* going through the signal API then +it should update the aspect info cache by calling advtrains.interlocking.signal.update_aspect_info(pos) + +== Aspect Info == +The actual signal aspect in the already-known format. This is what the trains use to determine halt/proceed and speed. In this, the dst field has to be resolved. +asp = { + main = 0 (halt) / -1 (max speed) / false (no info) / (speed limit) + shunt = true (shunt free) / false (shunt not free) + proceed_as_main = true (shunt move can proceed and become train move when main!=0) / false (no) + dst = (like main, informative character, not actually used) +} + +Node definition of signals: +- The signal needs some logic to figure out, for each combination of its own aspect group and the distant signal's aspect, what aspect info it can/will show. +ndef.advtrains = { + aspect_groups = { [name] = { description = "Proceed at full speed", } } + apply_aspect = function(pos, asp_group, dst_aspgrp, dst_aspinfo) + -- set the node to show the desired aspect + -- called by advtrains when this signal's aspect group or the distant signal's aspect changes + -- MAY return the aspect_info. If it returns nil then get_aspect_info will be queried at a later point. + get_aspect_info(pos) + -- Returns the aspect info table (main, shunt, dst etc.)W +} +]] + advtrains.interlocking.signal_convert_aspect_if_necessary = advtrains.interlocking.aspect function advtrains.interlocking.update_signal_aspect(tcbs, skipdst) -- cgit v1.2.3 From 2dab59f05572fe6cf73cde353446a5a501550b41 Mon Sep 17 00:00:00 2001 From: orwell Date: Tue, 6 Feb 2024 21:10:40 +0100 Subject: Start changing APIs and applying proof-of-concept to ks signals --- advtrains_interlocking/signal_api.lua | 105 ++++++++++++++++++++++------------ 1 file changed, 68 insertions(+), 37 deletions(-) (limited to 'advtrains_interlocking/signal_api.lua') diff --git a/advtrains_interlocking/signal_api.lua b/advtrains_interlocking/signal_api.lua index 743e8e1..c2cc08b 100644 --- a/advtrains_interlocking/signal_api.lua +++ b/advtrains_interlocking/signal_api.lua @@ -2,18 +2,19 @@ local F = advtrains.formspec -local DANGER = { - main = 0, - shunt = false, +local signal = {} + +signal.MASP_HALT = { + name = "halt" + halt = true, } -advtrains.interlocking.DANGER = DANGER -advtrains.interlocking.GENERIC_FREE = { - main = -1, +signal.ASPI_HALT = { + main = 0, shunt = false, - dst = false, } -advtrains.interlocking.FULL_FREE = { + +signal.ASPI_FREE = { main = -1, shunt = false, proceed_as_main = true, @@ -25,14 +26,12 @@ Most parts of ywang's implementation are fine, especially I like the formspecs. - Signal gets distant assigned via field in signal aspect table (instead of explicitly) - Signal speed/shunt are no longer free-text but rather they need to be predefined in the node definition To do this: Differentiation between: -== Aspect Group == +== Main Aspect == This is what a signal is assigned by either the route system or the user. It is a string key which has an appropriate entry in the node definition (where it has a description assigned) The signal mod defines a function to set a signal to the most appropriate aspect. This function gets -a) the aspect group name +a) the main aspect table (straight from node def) b) the distant signal's aspect group name & aspect table -EVERY signal must define the special aspect group "halt". This must always be the most restrictive aspect possible. -The "halt" aspect group should ignore any distant info, in most cases it is called without them anyway. == Aspect == One concrete combination of lights/shapes that a signal signal shows. Handling these is at the discretion of @@ -50,6 +49,11 @@ Note that once apply_aspect returns, there is no need for advtrains anymore to q When the signal, for any reason, wants to change its aspect by itself *without* going through the signal API then it should update the aspect info cache by calling advtrains.interlocking.signal.update_aspect_info(pos) +Note that the apply_aspect function MUST accept the following main aspect, even if it is not defined in the main_aspects table: +{ name = "halt", halt = true } +It should cause the signal to show its most restrictive aspect. Typically it is a halt aspect, but e.g. for distant-only +signals this would be "expect stop". + == Aspect Info == The actual signal aspect in the already-known format. This is what the trains use to determine halt/proceed and speed. In this, the dst field has to be resolved. asp = { @@ -62,7 +66,10 @@ asp = { Node definition of signals: - The signal needs some logic to figure out, for each combination of its own aspect group and the distant signal's aspect, what aspect info it can/will show. ndef.advtrains = { - aspect_groups = { [name] = { description = "Proceed at full speed", } } + main_aspects = { + { name = "proceed" description = "Proceed at full speed", } + { name = "proceed2" description = "Proceed at full speed", } + } -- The numerical order determines the layout of the list in the selection dialog. apply_aspect = function(pos, asp_group, dst_aspgrp, dst_aspinfo) -- set the node to show the desired aspect -- called by advtrains when this signal's aspect group or the distant signal's aspect changes @@ -72,28 +79,61 @@ ndef.advtrains = { } ]] -advtrains.interlocking.signal_convert_aspect_if_necessary = advtrains.interlocking.aspect +-- Set a signal's aspect. +-- Signal aspects should only be set through this function. It takes care of: +-- - Storing the main aspect and dst pos for this signal permanently (until next change) +-- - Assigning the distant signal for this signal +-- - Calling apply_aspect() in the signal's node definition to make the signal show the aspect +-- - Calling apply_aspect() again whenever the distant signal changes its aspect +-- - Notifying this signal's distant signals about changes to this signal (unless skip_dst_notify is specified) +function signal.set_aspect(pos, main_aspect, dst_pos, skip_dst_notify) + -- TODO +end + +-- Gets the stored main aspect and distant signal position for this signal +-- This information equals the information last passed to set_aspect +-- It does not take into consideration the actual speed signalling, please use +-- get_aspect_info() for this +-- returns: main_aspect, dst_pos +function signal.get_aspect(pos) + --TODO +end + +function signal.get_distant_signals_of(pos) + --TODO +end -function advtrains.interlocking.update_signal_aspect(tcbs, skipdst) +-- Called when either this signal has changed its main aspect +-- or when this distant signal's currently assigned main signal has changed its aspect +-- It retrieves the signal's main aspect and aspect info and calls apply_aspect of the node definition +-- to update the signal's appearance and aspect info +-- pts: The signal position to update as encoded_pos +function signal.reapply_aspect(pts, p_mainaspect) + --TODO +end + +-- Update this signal's aspect based on the set route +-- +function signal.update_route_aspect(tcbs, skip_dst_notify) if tcbs.signal then - local asp = tcbs.aspect or DANGER - advtrains.interlocking.signal_set_aspect(tcbs.signal, asp, skipdst) + local asp = tcbs.aspect or signal.MASP_HALT + signal.set_aspect(tcbs.signal, asp, skip_dst_notify) end end -function advtrains.interlocking.signal_can_dig(pos) +function signal.can_dig(pos) return not advtrains.interlocking.db.get_sigd_for_signal(pos) end function advtrains.interlocking.signal_after_dig(pos) -- clear influence point - advtrains.interlocking.signal_clear_aspect(pos) - advtrains.distant.unassign_all(pos, true) + advtrains.distant.unassign_all(pos, true) -- TODO end --- should be called when aspect has changed on this signal. -function advtrains.interlocking.signal_on_aspect_changed(pos) +-- Update waiting trains and distant signals about a changed signal aspect +function signal.notify_on_aspect_changed(pos, skip_dst_notify) + --TODO update distant? local ipts, iconn = advtrains.interlocking.db.get_ip_by_signalpos(pos) if not ipts then return end local ipos = minetest.string_to_pos(ipts) @@ -103,7 +143,7 @@ function advtrains.interlocking.signal_on_aspect_changed(pos) minetest.after(0, advtrains.invalidate_all_paths, ipos) end -function advtrains.interlocking.signal_rc_handler(pos, node, player, itemstack, pointed_thing) +function signal.on_rightclick(pos, node, player, itemstack, pointed_thing) local pname = player:get_player_name() local control = player:get_player_control() if control.aux1 then @@ -122,7 +162,7 @@ function advtrains.interlocking.show_signal_form(pos, node, pname) if ndef.advtrains and ndef.advtrains.set_aspect then -- permit to set aspect manually local function callback(pname, aspect) - advtrains.interlocking.signal_set_aspect(pos, aspect) + signal.set_aspect(pos, aspect) end local isasp = advtrains.interlocking.signal_get_aspect(pos, node) @@ -138,18 +178,6 @@ function advtrains.interlocking.show_signal_form(pos, node, pname) end end --- Returns the aspect the signal at pos is supposed to show -function advtrains.interlocking.signal_get_supposed_aspect(pos) - local sigd = advtrains.interlocking.db.get_sigd_for_signal(pos) - if sigd then - local tcbs = advtrains.interlocking.db.get_tcbs(sigd) - if tcbs.aspect then - return convert_aspect_if_necessary(tcbs.aspect) - end - end - return DANGER; -end - local players_assign_ip = {} local function ipmarker(ipos, connid) @@ -236,7 +264,7 @@ minetest.register_on_player_receive_fields(function(player, formname, fields) end) -- inits the signal IP assignment process -function advtrains.interlocking.signal_init_ip_assign(pos, pname) +function signal.init_ip_assign(pos, pname) if not minetest.check_player_privs(pname, "interlocking") then minetest.chat_send_player(pname, "Insufficient privileges to use this!") return @@ -281,3 +309,6 @@ minetest.register_on_punchnode(function(pos, node, player, pointed_thing) players_assign_ip[pname] = nil end end) + + +advtrains.interlocking.signal = signal -- cgit v1.2.3 From eb03b5f301c4244bbc79a101dd9f990b01503ab5 Mon Sep 17 00:00:00 2001 From: orwell Date: Fri, 5 Apr 2024 00:35:40 +0200 Subject: Continue with new-ks rework --- advtrains_interlocking/signal_api.lua | 274 ++++++++++++++++++++++++++++++---- 1 file changed, 243 insertions(+), 31 deletions(-) (limited to 'advtrains_interlocking/signal_api.lua') diff --git a/advtrains_interlocking/signal_api.lua b/advtrains_interlocking/signal_api.lua index c2cc08b..ce3b03f 100644 --- a/advtrains_interlocking/signal_api.lua +++ b/advtrains_interlocking/signal_api.lua @@ -5,7 +5,8 @@ local F = advtrains.formspec local signal = {} signal.MASP_HALT = { - name = "halt" + name = "halt", + description = "HALT", halt = true, } @@ -55,12 +56,12 @@ It should cause the signal to show its most restrictive aspect. Typically it is signals this would be "expect stop". == Aspect Info == -The actual signal aspect in the already-known format. This is what the trains use to determine halt/proceed and speed. In this, the dst field has to be resolved. +The actual signal aspect in the already-known format. This is what the trains use to determine halt/proceed and speed. asp = { main = 0 (halt) / -1 (max speed) / false (no info) / (speed limit) shunt = true (shunt free) / false (shunt not free) proceed_as_main = true (shunt move can proceed and become train move when main!=0) / false (no) - dst = (like main, informative character, not actually used) + dst = speed of the remote signal (like main, informative character, not actually used) } Node definition of signals: @@ -68,17 +69,84 @@ Node definition of signals: ndef.advtrains = { main_aspects = { { name = "proceed" description = "Proceed at full speed", } - { name = "proceed2" description = "Proceed at full speed", } - } -- The numerical order determines the layout of the list in the selection dialog. - apply_aspect = function(pos, asp_group, dst_aspgrp, dst_aspinfo) + { name = "reduced" description = "Proceed at reduced speed", } + } + -- This list is mainly for the selection dialog. Order of entries determines list order in the dropdown. + -- Some fields have special meaning: + -- name: A unique key to identify the main aspect. Only this key is saved, but APIs always receive the whole table + -- description: Text shown in UI dropdown + -- speed: a number. When present, a speed field is shown in the UI next to the dropdown (prefilled with the value). + -- When user selects a different speed there, this different speed replaces the value in the table whenever the main_aspect is applied. + -- Node can set any other fields at its discretion. They are not touched. + -- Note: On first call advtrains automatically inserts into the ndef.advtrains table a main_aspects_lookup hashtable + -- Note: Pure distant signals (that cannot show halt) should NOT have a main_aspects table + apply_aspect = function(pos, main_aspect, rem_aspect, rem_aspinfo) -- set the node to show the desired aspect - -- called by advtrains when this signal's aspect group or the distant signal's aspect changes + -- called by advtrains when this signal's aspect group or the remote signal's aspect changes -- MAY return the aspect_info. If it returns nil then get_aspect_info will be queried at a later point. - get_aspect_info(pos) - -- Returns the aspect info table (main, shunt, dst etc.)W + get_aspect_info(pos, main_aspect) + -- Returns the aspect info table (main, shunt, dst etc.) + distant_support = true or false + -- If true, signal is considered in distant signalling. If false or nil, rem_aspect and rem_aspinfo are never set. + route_role = one of "main", "shunt", "distant", "distant_repeater", "end" + -- Determines how the signal behaves when routes are set. Only in effect when signal is assigned to a TCB. + -- main: The signal is a possible endpoint for a train move route. Distant signals before it refer to it. + -- shunt: The signal is a possible endpoint for a shunt move route. Ignored for distant signals. + -- distant, distant_repeater: When route is set, signal is always assigned its first main aspect. The next signal with role="main" is set as the remote signal. (currently no further distinction) + -- end: like main, but signifies that it marks an end of track and trains cannot continue further. (currently no practical implications above main) } + +== Nomenclature == +The distant/main relation is named as follows: + V M +=====>====> +Main signal (main) always refers to the signal that is in focus right now (even if that is a distant-only signal) +From the standpoint of M, V is the distant (dst) signal. M does not need to concern itself with V's aspect but needs to notify V when it changes +From the standpoint of V, M is the remote (rem) signal. V needs to show an aspect that matches its remote signal M + +== Criteria for which signals are eligible for routes == + +All signals must define: +- get_aspect_info() + +Signals that can be assigned to a TCB must satisfy: +- apply_aspect() defined + +Signals that are possible start and end points for a route must satisfy: +- main_aspects defined (note, pure distant signals should therefore not define main_aspects) + ]] +-- Database +-- Signal Aspect store +-- Stores for each signal the main aspect and other info, like the assigned remote signal +-- [signal encodePos] = { main_aspect = "proceed", [speed = 12], [remote = encodedPos] } +signal.aspects = {} + +-- Distant signal notification. Records for each signal the distant signals that refer to it +-- Note: this mapping is weak. Needs always backreference check. +-- [signal encodePos] = { [distant signal encodePos] = true } +signal.distant_refs = {} + +function signal.load(data) + signal.aspects = data.aspects or {} + -- rebuild distant_refs after load + signal.distant_refs = {} + for main, aspt in pairs(signal.aspects) do + if aspt.remote then + if not signal.distant_refs[aspt.remote] then + signal.distant_refs[aspt.remote] = {} + end + signal.distant_refs[aspt.remote][main] = true + end + end +end + +function signal.save(data) + data.aspects = signal.aspects +end + + -- Set a signal's aspect. -- Signal aspects should only be set through this function. It takes care of: -- - Storing the main aspect and dst pos for this signal permanently (until next change) @@ -86,30 +154,184 @@ ndef.advtrains = { -- - Calling apply_aspect() in the signal's node definition to make the signal show the aspect -- - Calling apply_aspect() again whenever the distant signal changes its aspect -- - Notifying this signal's distant signals about changes to this signal (unless skip_dst_notify is specified) -function signal.set_aspect(pos, main_aspect, dst_pos, skip_dst_notify) - -- TODO +function signal.set_aspect(pos, main_asp_name, main_asp_speed, rem_pos, skip_dst_notify) + local main_pts = advtrains.encode_pos(pos) + local old_tbl = signal.aspects[main_pts] + local old_remote = old_tbl and old_tbl.remote + local new_remote = rem_pos and advtrains.encode_pos(rem_pos) + + -- if remote has changed, unregister from old remote + if old_remote and old_remote~=new_remote and signal.distant_refs[old_remote] then + signal.distant_refs[old_remote][main_pts] = nil + end + + signal.aspects[main_pts] = { main_aspect = main_asp_name, speed = main_asp_speed, remote = new_remote } + -- apply aspect on main signal, this also checks new_remote + signal.reapply_aspect(main_pts) + + -- notify my distants about this change (with limit 2) + if not skip_dst_notify then + signal.notify_distants_of(main_pts, 2) + end +end + +function signal.clear_aspect(pos, skip_dst_notify) + local main_pts = advtrains.encode_pos(pos) + local old_tbl = signal.aspects[main_pts] + local old_remote = old_tbl and old_tbl.remote + + -- unregister from old remote + if old_remote then + signal.distant_refs[old_remote][main_pts] = nil + end + + signal.aspects[main_pts] = nil + -- apply aspect on main signal, this also checks new_remote + signal.reapply_aspect(main_pts) + + -- notify my distants about this change (with limit 2) + if not skip_dst_notify then + signal.notify_distants_of(main_pts, 2) + end +end + +-- Notify distant signals of main_pts of a change in the aspect of this signal +-- +function signal.notify_distants_of(main_pts, limit) + if limit <= 0 then + return + end + local dstrefs = signal.distant_refs[main_pts] + if dstrefs then + for dst,_ in pairs(dstrefs) do + -- ensure that the backref is still valid + local dst_asp = signal.aspects[dst] + if dst_asp and dst_asp.remote == main_pts then + signal.reapply_aspect(dst) + signal.notify_distants_of(dst, limit - 1) + else + atwarn("Distant signal backref is not purged: main =",main_pts,", distant =",dst,", remote =",dst_asp.remote,"") + end + end + end +end + +function signal.notify_trains(pos) + local ipts, iconn = advtrains.interlocking.db.get_ip_by_signalpos(pos) + if not ipts then return end + local ipos = minetest.string_to_pos(ipts) + + -- FIXME: invalidate_all_paths_ahead does not appear to always work as expected + --advtrains.invalidate_all_paths_ahead(ipos) + minetest.after(0, advtrains.invalidate_all_paths, ipos) +end + +-- Update waiting trains and distant signals about a changed signal aspect +-- Must be called when a signal's aspect changes through some other means +-- and not via the signal mechanism +function signal.notify_on_aspect_changed(pos, skip_dst_notify) + signal.notify_trains(pos) + if not skip_dst_notify then + signal.notify_distants_of(advtrains.encode_pos(pos), 2) + end end -- Gets the stored main aspect and distant signal position for this signal -- This information equals the information last passed to set_aspect -- It does not take into consideration the actual speed signalling, please use -- get_aspect_info() for this +-- pos: the position of the signal -- returns: main_aspect, dst_pos function signal.get_aspect(pos) - --TODO + local aspt = signal.aspects[advtrains.encode_pos(pos)] + local ma,dp = signal.get_aspect_internal(pos, aspt) + return ma, advtrains.decode_pos(dp) +end + +local function cache_mainaspects(ndefat) + ndefat.main_aspects_lookup = { + -- always define halt aspect + halt = signal.MASP_HALT + } + for _,ma in ipairs(ndefat.main_aspects) then + ndefat.main_aspects_lookup[ma.name] = ma + end +end + +function signal.get_aspect_internal(pos, aspt) + if not aspt then + -- oh, no main aspect, nevermind + return nil, aspt.remote, nil + end + -- look aspect in nodedef + local node = advtrains.ndb.get_node_or_nil(pos) + local ndef = node and minetest.registered_nodes[node.name] + local ndefat = ndef and ndef.advtrains + -- only if signal defines main aspect and its set in aspt + if ndefat and ndefat.main_aspects and aspt.main_aspect then + if not ndefat.main_aspects_lookup then + cache_mainaspects(ndefat) + end + local masp = ndefat.main_aspects_lookup[aspt.name] + -- if speed, then apply speed + if masp.speed and aspt.speed then + masp = table.copy(masp) + masp.speed = aspt.speed + end + return masp, aspt.remote, ndef + end + -- invalid node or no main aspect, return nil for masp + return nil, aspt.remote, ndef end -function signal.get_distant_signals_of(pos) - --TODO +-- For the signal at pos, get the "aspect info" table. This contains the speed signalling information at this location +function signal.get_aspect_info(pos) + -- get aspect internal + local aspt = signal.aspects[advtrains.encode_pos(pos)] + local masp, remote, ndef = signal.get_aspect_internal(pos, aspt) + -- call into ndef + if ndef.advtrains and ndef.advtrains.get_aspect_info then + return ndef.advtrains.get_aspect_info(pos, masp) + end end + -- Called when either this signal has changed its main aspect -- or when this distant signal's currently assigned main signal has changed its aspect -- It retrieves the signal's main aspect and aspect info and calls apply_aspect of the node definition -- to update the signal's appearance and aspect info -- pts: The signal position to update as encoded_pos -function signal.reapply_aspect(pts, p_mainaspect) - --TODO +-- returns: the return value of the nodedef call which may be aspect_info +function signal.reapply_aspect(pts) + -- get aspt + local aspt = signal.aspects[pts] + if not aspt then + return -- oop, nothing to do + end + -- resolve mainaspect table by name + local pos = advtrains.decode_pos(pts) + -- note: masp may be nil, when aspt.main_aspect was nil. Valid case for distant-only signals + local masp, remote, ndef = signal.get_aspect_internal(pos, aspt) + -- if we have remote, resolve remote + local rem_masp, rem_aspi + if remote then + local rem_aspt = signal.aspects[remote] + if rem_aspt and rem_aspt.name then + local rem_pos = advtrains.decode_pos(remote) + rem_masp, _, rem_ndef = signal.get_aspect_internal(rem_pos, rem_aspt) + if rem_masp then + if rem_ndef.advtrains and rem_ndef.advtrains.get_aspect_info then + rem_aspi = rem_ndef.advtrains.get_aspect_info(pos, rem_masp) + end + end + end + end + -- call into ndef + if ndef.advtrains and ndef.advtrains.apply_aspect then + return ndef.advtrains.apply_aspect(pos, masp, rem_masp, rem_aspi) + end + -- notify trains + signal.notify_trains(pos) end -- Update this signal's aspect based on the set route @@ -121,26 +343,16 @@ function signal.update_route_aspect(tcbs, skip_dst_notify) end end + +---------------- + function signal.can_dig(pos) return not advtrains.interlocking.db.get_sigd_for_signal(pos) end function advtrains.interlocking.signal_after_dig(pos) - -- clear influence point - advtrains.interlocking.signal_clear_aspect(pos) - advtrains.distant.unassign_all(pos, true) -- TODO -end - --- Update waiting trains and distant signals about a changed signal aspect -function signal.notify_on_aspect_changed(pos, skip_dst_notify) - --TODO update distant? - local ipts, iconn = advtrains.interlocking.db.get_ip_by_signalpos(pos) - if not ipts then return end - local ipos = minetest.string_to_pos(ipts) - - -- FIXME: invalidate_all_paths_ahead does not appear to always work as expected - --advtrains.invalidate_all_paths_ahead(ipos) - minetest.after(0, advtrains.invalidate_all_paths, ipos) + -- TODO clear influence point + advtrains.interlocking.signal.clear_aspect(pos) end function signal.on_rightclick(pos, node, player, itemstack, pointed_thing) -- cgit v1.2.3 From 1f74697e85d456e97e201cdd9edef91a2df4fc14 Mon Sep 17 00:00:00 2001 From: orwell Date: Mon, 8 Apr 2024 21:46:43 +0200 Subject: Fully implement and test new aspect support/distant signalling on ks and japan signals --- advtrains_interlocking/signal_api.lua | 42 ++++++++++++++++++++++++----------- 1 file changed, 29 insertions(+), 13 deletions(-) (limited to 'advtrains_interlocking/signal_api.lua') diff --git a/advtrains_interlocking/signal_api.lua b/advtrains_interlocking/signal_api.lua index ce3b03f..d27a045 100644 --- a/advtrains_interlocking/signal_api.lua +++ b/advtrains_interlocking/signal_api.lua @@ -80,7 +80,7 @@ ndef.advtrains = { -- Node can set any other fields at its discretion. They are not touched. -- Note: On first call advtrains automatically inserts into the ndef.advtrains table a main_aspects_lookup hashtable -- Note: Pure distant signals (that cannot show halt) should NOT have a main_aspects table - apply_aspect = function(pos, main_aspect, rem_aspect, rem_aspinfo) + apply_aspect = function(pos, node, main_aspect, rem_aspect, rem_aspinfo) -- set the node to show the desired aspect -- called by advtrains when this signal's aspect group or the remote signal's aspect changes -- MAY return the aspect_info. If it returns nil then get_aspect_info will be queried at a later point. @@ -120,7 +120,7 @@ Signals that are possible start and end points for a route must satisfy: -- Database -- Signal Aspect store -- Stores for each signal the main aspect and other info, like the assigned remote signal --- [signal encodePos] = { main_aspect = "proceed", [speed = 12], [remote = encodedPos] } +-- [signal encodePos] = { name = "proceed", [speed = 12], [remote = encodedPos] } signal.aspects = {} -- Distant signal notification. Records for each signal the distant signals that refer to it @@ -162,10 +162,11 @@ function signal.set_aspect(pos, main_asp_name, main_asp_speed, rem_pos, skip_dst -- if remote has changed, unregister from old remote if old_remote and old_remote~=new_remote and signal.distant_refs[old_remote] then + atdebug("unregister old remote: ",old_remote,"from",main_pts) signal.distant_refs[old_remote][main_pts] = nil end - signal.aspects[main_pts] = { main_aspect = main_asp_name, speed = main_asp_speed, remote = new_remote } + signal.aspects[main_pts] = { name = main_asp_name, speed = main_asp_speed, remote = new_remote } -- apply aspect on main signal, this also checks new_remote signal.reapply_aspect(main_pts) @@ -198,10 +199,12 @@ end -- Notify distant signals of main_pts of a change in the aspect of this signal -- function signal.notify_distants_of(main_pts, limit) + atdebug("notify_distants_of",advtrains.decode_pos(main_pts),"limit",limit) if limit <= 0 then return end local dstrefs = signal.distant_refs[main_pts] + atdebug("dstrefs",dstrefs,"") if dstrefs then for dst,_ in pairs(dstrefs) do -- ensure that the backref is still valid @@ -253,7 +256,7 @@ local function cache_mainaspects(ndefat) -- always define halt aspect halt = signal.MASP_HALT } - for _,ma in ipairs(ndefat.main_aspects) then + for _,ma in ipairs(ndefat.main_aspects) do ndefat.main_aspects_lookup[ma.name] = ma end end @@ -263,32 +266,37 @@ function signal.get_aspect_internal(pos, aspt) -- oh, no main aspect, nevermind return nil, aspt.remote, nil end + atdebug("get_aspect_internal",pos,aspt) -- look aspect in nodedef local node = advtrains.ndb.get_node_or_nil(pos) local ndef = node and minetest.registered_nodes[node.name] local ndefat = ndef and ndef.advtrains -- only if signal defines main aspect and its set in aspt - if ndefat and ndefat.main_aspects and aspt.main_aspect then + if ndefat and ndefat.main_aspects and aspt.name then if not ndefat.main_aspects_lookup then cache_mainaspects(ndefat) end local masp = ndefat.main_aspects_lookup[aspt.name] + if not masp then + atwarn(pos,"invalid main aspect",aspt.name,"valid are",ndefat.main_aspects_lookup) + return nil, aspt.remote, node, ndef + end -- if speed, then apply speed if masp.speed and aspt.speed then masp = table.copy(masp) masp.speed = aspt.speed end - return masp, aspt.remote, ndef + return masp, aspt.remote, node, ndef end -- invalid node or no main aspect, return nil for masp - return nil, aspt.remote, ndef + return nil, aspt.remote, node, ndef end -- For the signal at pos, get the "aspect info" table. This contains the speed signalling information at this location function signal.get_aspect_info(pos) -- get aspect internal local aspt = signal.aspects[advtrains.encode_pos(pos)] - local masp, remote, ndef = signal.get_aspect_internal(pos, aspt) + local masp, remote, node, ndef = signal.get_aspect_internal(pos, aspt) -- call into ndef if ndef.advtrains and ndef.advtrains.get_aspect_info then return ndef.advtrains.get_aspect_info(pos, masp) @@ -305,30 +313,38 @@ end function signal.reapply_aspect(pts) -- get aspt local aspt = signal.aspects[pts] + atdebug("reapply_aspect",advtrains.decode_pos(pts),"aspt",aspt) if not aspt then return -- oop, nothing to do end -- resolve mainaspect table by name local pos = advtrains.decode_pos(pts) - -- note: masp may be nil, when aspt.main_aspect was nil. Valid case for distant-only signals - local masp, remote, ndef = signal.get_aspect_internal(pos, aspt) + -- note: masp may be nil, when aspt.name was nil. Valid case for distant-only signals + local masp, remote, node, ndef = signal.get_aspect_internal(pos, aspt) -- if we have remote, resolve remote local rem_masp, rem_aspi if remote then + -- register in remote signal as distant + if not signal.distant_refs[remote] then + signal.distant_refs[remote] = {} + end + signal.distant_refs[remote][pts] = true local rem_aspt = signal.aspects[remote] + atdebug("resolving remote",advtrains.decode_pos(remote),"aspt",rem_aspt) if rem_aspt and rem_aspt.name then local rem_pos = advtrains.decode_pos(remote) - rem_masp, _, rem_ndef = signal.get_aspect_internal(rem_pos, rem_aspt) + rem_masp, _, _, rem_ndef = signal.get_aspect_internal(rem_pos, rem_aspt) if rem_masp then if rem_ndef.advtrains and rem_ndef.advtrains.get_aspect_info then - rem_aspi = rem_ndef.advtrains.get_aspect_info(pos, rem_masp) + rem_aspi = rem_ndef.advtrains.get_aspect_info(rem_pos, rem_masp) end end end end -- call into ndef + atdebug("applying to",pos,": main_asp",masp,"rem_masp",rem_masp,"rem_aspi",rem_aspi) if ndef.advtrains and ndef.advtrains.apply_aspect then - return ndef.advtrains.apply_aspect(pos, masp, rem_masp, rem_aspi) + ndef.advtrains.apply_aspect(pos, node, masp, rem_masp, rem_aspi) end -- notify trains signal.notify_trains(pos) -- cgit v1.2.3