Auto-Repair Track Sections/TCBs (automatically when adding/removing or triggered by interlocking tool)

This commit is contained in:
orwell96 2023-03-19 15:20:03 +01:00
parent f284d395d7
commit 6a5540878f
5 changed files with 528 additions and 399 deletions

View File

@ -342,9 +342,10 @@ function advtrains.get_matching_conn(conn, nconns)
return connlku[nconns][conn]
end
function advtrains.random_id()
function advtrains.random_id(lenp)
local idst=""
for i=0,5 do
local len = lenp or 6
for i=1,len do
idst=idst..(math.random(0,9))
end
return idst
@ -476,6 +477,14 @@ end
-- Metatable:
local trackiter_mt = {
-- Internal State:
-- branches: A list of {pos, connid, limit} for where to restart
-- pos: The *next* position that the track iterator will return
-- bconnid: The connid of the connection of the rail at pos that points backward
-- tconns: The connections of the rail at pos
-- limit: the current limit
-- visited: a key-boolean table of already visited rails
-- get whether there are still unprocessed branches
has_next_branch = function(self)
return #self.branches > 0
@ -491,13 +500,18 @@ local trackiter_mt = {
self.tconns = adj_conns
self.limit = br.limit - 1
self.visited[advtrains.encode_pos(br.pos)] = true
self.last_track_already_visited = false
return br.pos, br.connid
end,
-- get the next track along the current branch,
-- potentially adding branching tracks to the unprocessed branches list
-- returns status, track_pos, track_connid
-- status is true(ok), false(track has ended), nil(traversing limit has been reached) (when status~=true, track_pos and track_connid are nil)
-- returns track_pos, track_connid, track_backwards_connid
-- On error, returns nil, reason; reason is one of "track_end", "limit_hit", "already_visited"
next_track = function(self)
if self.last_track_already_visited then
-- see comment below
return nil, "already_visited"
end
local pos = self.pos
if not pos then
-- last run found track end. Return false
@ -507,12 +521,17 @@ local trackiter_mt = {
if self.limit <= 0 then
return nil, "limit_hit"
end
if self.visited[advtrains.encode_pos(pos)] then
-- node was already seen. do not continue
return nil, "already_visited"
end
-- select next conn (main conn to follow is the associated connection)
local old_bconnid = self.bconnid
local mconnid = advtrains.get_matching_conn(self.bconnid, #self.tconns)
if self.visited[advtrains.encode_pos(pos)] then
-- node was already seen
-- Due to special requirements for the track section updater, return this first already visited track once
-- but do not process any further rails on this branch
-- The next call will then throw already_visited error
self.last_track_already_visited = true
return pos, mconnid, old_bconnid
end
-- If there are more connections, add these to branches
for nconnid,_ in ipairs(self.tconns) do
if nconnid~=mconnid and nconnid~=self.bconnid then
@ -526,7 +545,16 @@ local trackiter_mt = {
self.tconns = adj_conns
self.limit = self.limit - 1
self.visited[advtrains.encode_pos(pos)] = true
return pos, mconnid
self.last_track_already_visited = false
return pos, mconnid, old_bconnid
end,
add_branch = function(self, pos, connid)
table.insert(self.branches, {pos = pos, connid = connid, limit=self.limit})
end,
is_visited = function(self, pos)
return self.visited[advtrains.encode_pos(pos)]
end,
}
@ -569,8 +597,8 @@ while ti:has_next_branch() do
repeat
<do something with the track>
if <track satisfies an abort condition> then break end --for example, when traversing should stop at TCBs this can check if there is a tcb here
ok, pos, connid = ti:next_track()
until not ok -- this stops the loop when either the track end is reached or the limit is hit
pos, connid = ti:next_track()
until not pos -- this stops the loop when either the track end is reached or the limit is hit
-- while loop continues with the next branch ( diverging branch of one of the switches/crossings) until no more are left
end

View File

@ -37,6 +37,22 @@ Another case of this:
The / here is a non-interlocked turnout (to a non-frequently used siding). For some reason, there is no exit node there,
so the route is set to the signal at the right end. The train is taking the exit to the siding and frees the TC, without ever
having touched the right TC.
== Terminology / Variable Names ==
"tcb" : A TCB table (as in track_circuit_breaks)
"tcbs" : One side of a tcb (that is tcb == {[1] = tcbs, [2] = tcbs})
"sigd" : A table of format {p=<position>, s=<side aka connid>} by which a "tcbs" is uniqely identified.
== Section Autorepair & Turnout Cache ==
As fundamental part of reworked route programming mechanism, Track Section objects become weak now. They are created and destroyed on demand.
ildb.repair_tcb automatically checks all nearby sections for issues and repairs them automatically.
Also the database now holds a cache of the turnouts in the section and their position for all possible driving paths.
Every time a repair operation takes place, and on every track edit operation, the affected sections need to have their cache updated.
]]--
local TRAVERSER_LIMIT = 1000
@ -59,7 +75,22 @@ advtrains.interlocking.npr_rails = {}
function ildb.load(data)
if not data then return end
if data.tcbs then
track_circuit_breaks = data.tcbs
if data.tcbpts_conversion_applied then
track_circuit_breaks = data.tcbs
else
-- Convert legacy pos_to_string tcbs to new advtrains.encode_pos position strings
for pts, tcb in pairs(data.tcbs) do
local pos = minetest.string_to_pos(pts)
if pos then
-- that was a pos_to_string
local epos = advtrains.encode_pos(pos)
track_circuit_breaks[epos] = tcb
else
-- keep entry, it is already new
track_circuit_breaks[pts] = tcb
end
end
end
end
if data.ts then
track_sections = data.ts
@ -121,6 +152,7 @@ function ildb.save()
rs_callbacks = advtrains.interlocking.route.rte_callbacks,
influence_points = influence_points,
npr_rails = advtrains.interlocking.npr_rails,
tcbpts_conversion_applied = true, -- remark that legacy pos conversion has taken place
}
end
@ -147,8 +179,6 @@ TCB data structure
-- This is the "B" side of the TCB
[2] = { -- Variant: end of track-circuited area (initial state of TC)
ts_id = nil, -- this is the indication for end_of_interlocking
section_free = <boolean>, --this can be set by an exit node via mesecons or atlatc,
-- or from the tc formspec.
}
}
@ -156,7 +186,11 @@ Track section
[id] = {
name = "Some human-readable name"
tc_breaks = { <signal specifier>,... } -- Bounding TC's (signal specifiers)
-- Can be direct ends (auto-detected), conflicting routes or TCBs that are too far away from each other
rs_cache = { [<x>-<y>] = { [<encoded pos>] = "state" } }
-- Saves the turnout states that need to be locked when a route is set from tcb#x to tcb#y
-- e.g. "1-2" = { "800080008000" = "st" }
-- Recalculated on every change via update_ts_cache
route = {
origin = <signal>, -- route origin
entry = <sigd>, -- supposed train entry point
@ -172,7 +206,9 @@ Track section
-- first says whether to clear the routesetting status from the origin signal.
-- locks contains the positions where locks are held by this ts.
-- 'route' is cleared when train enters the section, while 'route_post' cleared when train leaves section.
trains = {<id>, ...} -- Set whenever a train (or more) reside in this TC
-- Note: The same train ID may be contained in this mapping multiple times, when it has entered the section in two different places.
}
@ -185,24 +221,13 @@ signal_assignments = {
}
]]
-- Maximum scan length for track iterator
local TS_MAX_SCAN = 1000
--
function ildb.create_tcb(pos)
local new_tcb = {
[1] = {},
[2] = {},
}
local pts = advtrains.roundfloorpts(pos)
if not track_circuit_breaks[pts] then
track_circuit_breaks[pts] = new_tcb
return true
else
return false
end
end
-- basic functions
function ildb.get_tcb(pos)
local pts = advtrains.roundfloorpts(pos)
local pts = advtrains.encode_pos(pos)
return track_circuit_breaks[pts]
end
@ -212,99 +237,281 @@ function ildb.get_tcbs(sigd)
return tcb[sigd.s]
end
function ildb.create_ts(sigd)
local tcbs = ildb.get_tcbs(sigd)
local id = advtrains.random_id()
while track_sections[id] do
id = advtrains.random_id()
end
track_sections[id] = {
name = "Section "..id,
tc_breaks = { sigd }
}
tcbs.ts_id = id
end
function ildb.get_ts(id)
return track_sections[id]
end
-- various helper functions handling sigd's
local sigd_equal = advtrains.interlocking.sigd_equal
local function insert_sigd_nodouble(list, sigd)
for idx, cmp in pairs(list) do
if sigd_equal(sigd, cmp) then
return
end
end
table.insert(list, sigd)
-- retrieve full tables. Please use only read-only!
function ildb.get_all_tcb()
return track_circuit_breaks
end
function ildb.get_all_ts()
return track_sections
end
-- This function will actually handle the node that is in connid direction from the node at pos
-- so, this needs the conns of the node at pos, since these are already calculated
local function traverser(found_tcbs, pos, conns, connid, count, brk_when_found_n)
local adj_pos, adj_connid, conn_idx, nextrail_y, next_conns = advtrains.get_adjacent_rail(pos, conns, connid, advtrains.all_tracktypes)
if not adj_pos then
--atdebug("Traverser found end-of-track at",pos, connid)
return
end
-- look whether there is a TCB here
if #next_conns == 2 then --if not, don't even try!
local tcb = ildb.get_tcb(adj_pos)
if tcb then
-- done with this branch
--atdebug("Traverser found tcb at",adj_pos, adj_connid)
insert_sigd_nodouble(found_tcbs, {p=adj_pos, s=adj_connid})
return
end
end
-- recursion abort condition
if count > TRAVERSER_LIMIT then
--atdebug("Traverser hit counter at",adj_pos, adj_connid)
return true
end
-- continue traversing
local counter_hit = false
for nconnid, nconn in ipairs(next_conns) do
if adj_connid ~= nconnid then
counter_hit = counter_hit or traverser(found_tcbs, adj_pos, next_conns, nconnid, count + 1, brk_when_found_n)
if brk_when_found_n and #found_tcbs>=brk_when_found_n then
break
-- Checks the consistency of the track section at the given position
-- This method attempts to autorepair track sections if they are inconsistent
-- @param pos: the position to start from
-- Returns:
-- ts_id - the track section that was found
-- nil - No track section exists
function ildb.check_and_repair_ts_at_pos(pos)
atdebug("check_and_repair_ts_at_pos", pos)
-- STEP 1: Ensure that only one section is at this place
-- get all TCBs adjacent to this
local all_tcbs = ildb.get_all_tcbs_adjacent(pos, nil)
local first_ts = true
local ts_id
for _,sigd in ipairs(all_tcbs) do
ildb.tcbs_ensure_ts_ref_exists(sigd)
local tcbs_ts_id = sigd.tcbs.ts_id
if first_ts then
-- this one determines
ts_id = tcbs_ts_id
first_ts = false
else
-- these must be the same as the first
if ts_id ~= tcbs_ts_id then
-- inconsistency is found, repair it
atdebug("check_and_repair_ts_at_pos: Inconsistency is found!")
return ildb.repair_ts_merge_all(all_tcbs)
-- Step2 check is no longer necessary since we just created that new section
end
end
end
return counter_hit
-- only one found (it is either nil or a ts id)
atdebug("check_and_repair_ts_at_pos: TS consistent id=",ts_id,"")
if not ts_id then
return
-- All TCBs agreed that there is no section here.
end
local ts = ildb.get_ts(ts_id)
if not ts then
-- This branch may never be reached, because ildb.tcbs_ensure_ts_ref_exists(sigd) is already supposed to clear out missing sections
error("check_and_repair_ts_at_pos: Resolved to nonexisting section although ildb.tcbs_ensure_ts_ref_exists(sigd) was supposed to prevent this. Panic!")
end
ildb.purge_ts_tcb_refs(ts_id)
-- STEP 2: Ensure that all_tcbs is equal to the track section's TCB list. If there are extra TCBs then the section should be split
-- ildb.tcbs_ensure_ts_ref_exists(sigd) has already make sure that all tcbs are found in the ts's tc_breaks list
-- That means it is sufficient to compare the LENGTHS of both lists, if one is longer then it is inconsistent
if #ts.tc_breaks ~= #all_tcbs then
atdebug("check_and_repair_ts_at_pos: Partition is found!")
return ildb.repair_ts_merge_all(all_tcbs)
end
return ts_id
end
-- Helper function to prevent duplicates
local function insert_sigd_if_not_present(tab, sigd)
local found = false
for _, ssigd in ipairs(tab) do
if vector.equals(sigd.p, ssigd.p) and sigd.s==ssigd.s then
found = true
end
end
if not found then
table.insert(tab, sigd)
end
return not found
end
-- Starting from a position, search all TCBs that can be reached from this position.
-- In a non-faulty setup, all of these should have the same track section assigned.
-- This function does not trigger a repair.
-- @param inipos: the initial position
-- @param inidir: the initial direction, or nil to search in all directions
-- Returns: a list of sigd's describing the TCBs found (sigd's point inward):
-- {p=<pos>, s=<side>, tcbs=<ref to tcbs table>}
function ildb.get_all_tcbs_adjacent(inipos, inidir)
atdebug("get_all_tcbs_adjacent: inipos",inipos,"inidir",inidir,"")
local found_sigd = {}
local ti = advtrains.get_track_iterator(inipos, inidir, TS_MAX_SCAN, true)
local pos, connid, bconnid, tcb
while ti:has_next_branch() do
pos, connid = ti:next_branch()
--atdebug("get_all_tcbs_adjacent: BRANCH: ",pos, connid)
bconnid = nil
repeat
tcb = ildb.get_tcb(pos)
if tcb then
-- found a tcb
if not bconnid then
-- A branch start point cannot be a TCB, as that would imply that it was a turnout/crossing (illegal)
-- Only exception where this can happen is if the start point is a TCB, then we'd need to add the forward side of it to our list
if pos.x==inipos.x and pos.y==inipos.y and pos.z==inipos.z then
-- Note "connid" instead of "bconnid"
atdebug("get_all_tcbs_adjacent: Found Startpoint TCB: ",pos, connid, "ts=", tcb[connid].ts_id)
insert_sigd_if_not_present(found_sigd, {p=pos, s=connid, tcbs=tcb[connid]})
else
-- this may not happend
error("Found TCB at TrackIterator new branch which is not the start point, this is illegal! pos="..minetest.pos_to_string(pos))
end
else
-- add the sigd of this tcb and a reference to the tcb table in it
atdebug("get_all_tcbs_adjacent: Found TCB: ",pos, bconnid, "ts=", tcb[bconnid].ts_id)
insert_sigd_if_not_present(found_sigd, {p=pos, s=bconnid, tcbs=tcb[bconnid]})
break
end
end
pos, connid, bconnid = ti:next_track()
--atdebug("get_all_tcbs_adjacent: TRACK: ",pos, connid, bconnid)
until not pos
end
return found_sigd
end
-- Called by frontend functions when multiple tcbs's that logically belong to one section have been determined to have different sections
-- Parameter is the output of ildb.get_all_tcbs_adjacent(pos)
-- Returns the ID of the track section that results after the merge
function ildb.repair_ts_merge_all(all_tcbs, force_create)
atdebug("repair_ts_merge_all: Instructed to merge sections of following TCBs:")
-- The first loop does the following for each TCBS:
-- a) Store the TS ID in the set of TS to update
-- b) Set the TS ID to nil, so that the TCBS gets removed from the section
local ts_to_update = {}
local ts_name_repo = {}
local any_ts = false
for _,sigd in ipairs(all_tcbs) do
local ts_id = sigd.tcbs.ts_id
atdebug(sigd, "ts=", ts_id)
if ts_id then
local ts = track_sections[ts_id]
if ts then
any_ts = true
ts_to_update[ts_id] = true
-- if nonstandard name, store this
if ts.name and not string.match(ts.name, "^Section") then
ts_name_repo[#ts_name_repo+1] = ts.name
end
end
end
sigd.tcbs.ts_id = nil
end
if not any_ts and not force_create then
-- nothing to do at all, just no interlocking. Why were we even called
atdebug("repair_ts_merge_all: No track section present, will not create a new one")
return nil
end
-- Purge every TS in turn. TS's that are now empty will be deleted. TS's that still have TCBs will be kept
for ts_id, _ in pairs(ts_to_update) do
local remain_ts = ildb.purge_ts_tcb_refs(ts_id)
end
-- Create a new fresh track section with all the TCBs we have in our collection
local new_ts_id, new_ts = ildb.create_ts_from_tcb_list(all_tcbs)
return new_ts_id
end
-- For the specified TS, go through the list of TCBs and purge all TCBs that have no corresponding backreference in their TCBS table.
-- If the track section ends up empty, it is deleted in this process.
-- Should the track section still exist after the purge operation, it is returned.
function ildb.purge_ts_tcb_refs(ts_id)
local ts = track_sections[ts_id]
if not ts then
return nil
end
local has_changed = false
local i = 1
while ts.tc_breaks[i] do
-- get TCBS
local sigd = ts.tc_breaks[i]
local tcbs = ildb.get_tcbs(sigd)
if tcbs then
if tcbs.ts_id == ts_id then
-- this one is legit
i = i+1
else
-- this one is to be purged
atdebug("purge_ts_tcb_refs(",ts_id,"): purging",sigd,"(backreference = ",tcbs.ts_id,")")
table.remove(ts.tc_breaks, i)
has_changed = true
end
else
-- if not tcbs: was anyway an orphan, remove it
atdebug("purge_ts_tcb_refs(",ts_id,"): purging",sigd,"(referred nonexisting TCB)")
table.remove(ts.tc_breaks, i)
has_changed = true
end
end
if #ts.tc_breaks == 0 then
-- remove the section completely
atdebug("purge_ts_tcb_refs(",ts_id,"): after purging, the section is empty, is being deleted")
track_sections[ts_id] = nil
return nil
else
if has_changed then
-- needs to update route cache
ildb.update_ts_cache(ts_id)
end
return ts
end
end
-- For the specified TCBS, make sure that the track section referenced by it
-- (a) exists and
-- (b) has a backreference to this TCBS stored in its tc_breaks list
function ildb.tcbs_ensure_ts_ref_exists(sigd)
local tcbs = sigd.tcbs or ildb.get_tcbs(sigd)
if not tcbs or not tcbs.ts_id then return end
local ts = ildb.get_ts(tcbs.ts_id)
if not ts then
atdebug("tcbs_ensure_ts_ref_exists(",sigd,"): TS does not exist, setting to nil")
-- TS is deleted, clear own ts id
tcbs.ts_id = nil
return
end
local did_insert = insert_sigd_if_not_present(ts.tc_breaks, {p=sigd.p, s=sigd.s})
if did_insert then
atdebug("tcbs_ensure_ts_ref_exists(",sigd,"): TCBS was missing reference in TS",tcbs.ts_id)
ildb.update_ts_cache(ts_id)
end
end
function ildb.create_ts_from_tcb_list(sigd_list)
local id = advtrains.random_id(8)
while track_sections[id] do
id = advtrains.random_id(8)
end
atdebug("create_ts_from_tcb_list: sigd_list=",sigd_list, "new ID will be ",id)
local tcbr = {}
-- makes a copy of the sigd list, for use in repair mechanisms where sigd may contain a tcbs field which we dont want
for _, sigd in ipairs(sigd_list) do
table.insert(tcbr, {p=sigd.p, s=sigd.s})
local tcbs = sigd.tcbs or ildb.get_tcbs(sigd)
if tcbs.ts_id then
error("Trying to create TS with TCBS that is already assigned to other section")
end
tcbs.ts_id = id
end
local new_ts = {
tc_breaks = tcbr
}
track_sections[id] = new_ts
-- update the TCB markers
for _, sigd in ipairs(sigd_list) do
advtrains.interlocking.show_tcb_marker(sigd.p)
end
ildb.update_ts_cache(id)
return id, new_ts
end
-- Merges the TS with merge_id into root_id and then deletes merge_id
local function merge_ts(root_id, merge_id)
local rts = ildb.get_ts(root_id)
local mts = ildb.get_ts(merge_id)
if not mts then return end -- This may be the case when sync_tcb_neighbors
-- inserts the same id twice. do nothing.
if not ildb.may_modify_ts(rts) then return false end
if not ildb.may_modify_ts(mts) then return false end
-- cobble together the list of TCBs
for _, msigd in ipairs(mts.tc_breaks) do
local tcbs = ildb.get_tcbs(msigd)
if tcbs then
insert_sigd_nodouble(rts.tc_breaks, msigd)
tcbs.ts_id = root_id
end
advtrains.interlocking.show_tcb_marker(msigd.p)
-- Updates the turnout cache of the given track section
function ildb.update_ts_cache(ts_id)
local ts = ildb.get_ts(ts_id)
if not ts then
error("Update TS Cache called with nonexisting ts_id "..(ts_id or "nil"))
end
-- done
track_sections[merge_id] = nil
local rscache = {}
-- start on every of the TS's TCBs, walk the track forward and store locks along the way
-- TODO: Need change in handling of switches
atdebug("update_ts_cache",ts_id,"TODO: implement")
end
local lntrans = { "A", "B" }
@ -312,127 +519,65 @@ local function sigd_to_string(sigd)
return minetest.pos_to_string(sigd.p).." / "..lntrans[sigd.s]
end
-- Check for near TCBs and connect to their TS if they have one, and syncs their data.
function ildb.sync_tcb_neighbors(pos, connid)
local found_tcbs = { {p = pos, s = connid} }
local node_ok, conns, rhe = advtrains.get_rail_info_at(pos, advtrains.all_tracktypes)
if not node_ok then
atwarn("update_tcb_neighbors but node is NOK: "..minetest.pos_to_string(pos))
return
end
--atdebug("Traversing from ",pos, connid)
local counter_hit = traverser(found_tcbs, pos, conns, connid, 0)
local ts_id
local list_eoi = {}
local list_ok = {}
local list_mismatch = {}
local ts_to_merge = {}
for idx, sigd in pairs(found_tcbs) do
local tcbs = ildb.get_tcbs(sigd)
if not tcbs.ts_id then
--atdebug("Sync: put",sigd_to_string(sigd),"into list_eoi")
table.insert(list_eoi, sigd)
elseif not ts_id and tcbs.ts_id then
if not ildb.get_ts(tcbs.ts_id) then
atwarn("Track section database is inconsistent, there's no TS with ID=",tcbs.ts_id)
tcbs.ts_id = nil
table.insert(list_eoi, sigd)
else
--atdebug("Sync: put",sigd_to_string(sigd),"into list_ok")
ts_id = tcbs.ts_id
table.insert(list_ok, sigd)
end
elseif ts_id and tcbs.ts_id and tcbs.ts_id ~= ts_id then
atwarn("Track section database is inconsistent, sections share track!")
atwarn("Merging",tcbs.ts_id,"into",ts_id,".")
table.insert(list_mismatch, sigd)
table.insert(ts_to_merge, tcbs.ts_id)
end
end
if ts_id then
local ts = ildb.get_ts(ts_id)
for _, sigd in ipairs(list_eoi) do
local tcbs = ildb.get_tcbs(sigd)
tcbs.ts_id = ts_id
table.insert(ts.tc_breaks, sigd)
advtrains.interlocking.show_tcb_marker(sigd.p)
end
for _, mts in ipairs(ts_to_merge) do
merge_ts(ts_id, mts)
end
end
-- Create a new TCB at the position and update/repair the adjoining sections
function ildb.create_tcb_at(pos)
atdebug("create_tcb_at",pos)
local pts = advtrains.encode_pos(pos)
track_circuit_breaks[pts] = {[1] = {}, [2] = {}}
local all_tcbs_1 = ildb.get_all_tcbs_adjacent(pos, 1)
atdebug("TCBs on A side",all_tcbs_1)
local all_tcbs_2 = ildb.get_all_tcbs_adjacent(pos, 2)
atdebug("TCBs on B side",all_tcbs_2)
-- perform TS repair
ildb.repair_ts_merge_all(all_tcbs_1)
ildb.repair_ts_merge_all(all_tcbs_2)
end
function ildb.link_track_sections(merge_id, root_id)
if merge_id == root_id then
return
end
merge_ts(root_id, merge_id)
end
function ildb.remove_from_interlocking(sigd)
local tcbs = ildb.get_tcbs(sigd)
if not ildb.may_modify_tcbs(tcbs) then return false end
if tcbs.ts_id then
local tsid = tcbs.ts_id
local ts = ildb.get_ts(tsid)
if not ts then
tcbs.ts_id = nil
return true
end
-- remove entry from the list
local idx = 1
while idx <= #ts.tc_breaks do
local cmp = ts.tc_breaks[idx]
if sigd_equal(sigd, cmp) then
table.remove(ts.tc_breaks, idx)
else
idx = idx + 1
end
end
tcbs.ts_id = nil
--ildb.sync_tcb_neighbors(sigd.p, sigd.s)
if #ts.tc_breaks == 0 then
track_sections[tsid] = nil
end
end
advtrains.interlocking.show_tcb_marker(sigd.p)
if tcbs.signal then
return false
end
return true
end
function ildb.remove_tcb(pos)
local pts = advtrains.roundfloorpts(pos)
if not track_circuit_breaks[pts] then
return true --FIX: not an error, because tcb is already removed
end
for connid=1,2 do
if not ildb.remove_from_interlocking({p=pos, s=connid}) then
return false
end
end
-- Create a new TCB at the position and update/repair the now joined section
function ildb.remove_tcb_at(pos)
atdebug("remove_tcb_at",pos)
local pts = advtrains.encode_pos(pos)
local old_tcb = track_circuit_breaks[pts]
track_circuit_breaks[pts] = nil
-- purge the track sections adjacent
if old_tcb[1].ts_id then
ildb.purge_ts_tcb_refs(old_tcb[1].ts_id)
end
if old_tcb[2].ts_id then
ildb.purge_ts_tcb_refs(old_tcb[2].ts_id)
end
advtrains.interlocking.remove_tcb_marker(pos)
-- If needed, merge the track sections here
ildb.check_and_repair_ts_at_pos(pos)
return true
end
function ildb.dissolve_ts(ts_id)
local ts = ildb.get_ts(ts_id)
if not ildb.may_modify_ts(ts) then return false end
local tcbr = advtrains.merge_tables(ts.tc_breaks)
for _,sigd in ipairs(tcbr) do
ildb.remove_from_interlocking(sigd)
function ildb.create_ts_from_tcbs(sigd)
atdebug("create_ts_from_tcbs",sigd)
local all_tcbs = ildb.get_all_tcbs_adjacent(sigd.p, sigd.s)
ildb.repair_ts_merge_all(all_tcbs, true)
end
-- Remove the given track section, leaving its TCBs with no section assigned
function ildb.remove_ts(ts_id)
atdebug("remove_ts",ts_id)
local ts = track_sections[ts_id]
if not ts then
error("remove_ts: "..ts_id.." doesn't exist")
end
-- Note: ts gets removed in the moment of the removal of the last TCB.
return true
while ts.tc_breaks[i] do
-- get TCBS
local sigd = ts.tc_breaks[i]
local tcbs = ildb.get_tcbs(sigd)
if tcbs then
atdebug("cleared TCB",sigd)
tcbs.ts_id = nil
else
atdebug("orphan TCB",sigd)
end
i = i+1
end
track_sections[ts_id] = nil
end
-- Returns true if it is allowed to modify any property of a track section, such as
@ -457,38 +602,8 @@ function ildb.may_modify_tcbs(tcbs)
return true
end
-- Utilize the traverser to find the track section at the specified position
-- Returns:
-- ts_id, origin - the first found ts and the sigd of the found tcb
-- nil - there were no TCBs in TRAVERSER_MAX range of the position
-- false - the first found TCB stated End-Of-Interlocking, or track ends were reached
function ildb.get_ts_at_pos(pos)
local node_ok, conns, rhe = advtrains.get_rail_info_at(pos, advtrains.all_tracktypes)
if not node_ok then
error("get_ts_at_pos but node is NOK: "..minetest.pos_to_string(pos))
end
local limit_hit = false
local found_tcbs = {}
for connid, conn in ipairs(conns) do -- Note: a breadth-first-search would be better for performance
limit_hit = limit_hit or traverser(found_tcbs, pos, conns, connid, 0, 1)
if #found_tcbs >= 1 then
local tcbs = ildb.get_tcbs(found_tcbs[1])
local ts
if tcbs.ts_id then
return tcbs.ts_id, found_tcbs[1]
else
return false
end
end
end
if limit_hit then
-- there was at least one limit hit
return nil
else
-- all traverser ends were track ends
return false
end
end
-- Signals/IP --
-- returns the sigd the signal at pos belongs to, if this is known

View File

@ -88,12 +88,8 @@ minetest.register_node("advtrains_interlocking:tcb_node", {
local tcb = ildb.get_tcb(tcbpos)
if not tcb then return true end
for connid=1,2 do
if tcb[connid].ts_id or tcb[connid].signal then
minetest.chat_send_player(pname, "Can't remove TCB: Both sides must have no track section and no signal assigned!")
return false
end
if not ildb.may_modify_tcbs(tcb[connid]) then
minetest.chat_send_player(pname, "Can't remove TCB: Side "..connid.." forbids modification (shouldn't happen).")
if tcb[connid].signal then
minetest.chat_send_player(pname, "Can't remove TCB: Both sides must have no signal assigned!")
return false
end
end
@ -105,15 +101,7 @@ minetest.register_node("advtrains_interlocking:tcb_node", {
local tcbpts = oldmetadata.fields.tcb_pos
if tcbpts and tcbpts ~= "" then
local tcbpos = minetest.string_to_pos(tcbpts)
local success = ildb.remove_tcb(tcbpos)
if success and player then
minetest.chat_send_player(player:get_player_name(), "TCB has been removed.")
else
minetest.chat_send_player(player:get_player_name(), "Failed to remove TCB!")
minetest.set_node(pos, oldnode)
local meta = minetest.get_meta(pos)
meta:set_string("tcb_pos", minetest.pos_to_string(tcbpos))
end
ildb.remove_tcb_at(tcbpos)
end
end,
})
@ -167,15 +155,13 @@ minetest.register_on_punchnode(function(pos, node, player, pointed_thing)
if vector.distance(pos, tcbnpos)<=20 then
local node_ok, conns, rhe = advtrains.get_rail_info_at(pos, advtrains.all_tracktypes)
if node_ok and #conns == 2 then
local ok = ildb.create_tcb(pos)
if not ok then
minetest.chat_send_player(pname, "Configuring TCB: TCB already exists at this position! It has now been re-assigned.")
-- if there is already a tcb here, reassign it
if ildb.get_tcb(pos) then
minetest.chat_send_player(pname, "Configuring TCB: Already existed at this position, it is now linked to this TCB marker")
else
ildb.create_tcb_at(pos)
end
ildb.sync_tcb_neighbors(pos, 1)
ildb.sync_tcb_neighbors(pos, 2)
local meta = minetest.get_meta(tcbnpos)
meta:set_string("tcb_pos", minetest.pos_to_string(pos))
meta:set_string("infotext", "TCB assigned to "..minetest.pos_to_string(pos))
@ -234,22 +220,12 @@ local function mktcbformspec(tcbs, btnpref, offset, pname)
ts = ildb.get_ts(tcbs.ts_id)
end
if ts then
form = form.."label[0.5,"..offset..";Side "..btnpref..": "..minetest.formspec_escape(ts.name).."]"
form = form.."label[0.5,"..offset..";Side "..btnpref..": "..minetest.formspec_escape(ts.name or tcbs.ts_id).."]"
form = form.."button[0.5,"..(offset+0.5)..";5,1;"..btnpref.."_gotots;Show track section]"
if ildb.may_modify_tcbs(tcbs) then
-- Note: the security check to prohibit those actions is located in database.lua in the corresponding functions.
form = form.."button[0.5,"..(offset+1.5)..";2.5,1;"..btnpref.."_update;Update near TCBs]"
form = form.."button[3 ,"..(offset+1.5)..";2.5,1;"..btnpref.."_remove;Remove from section]"
end
else
tcbs.ts_id = nil
form = form.."label[0.5,"..offset..";Side "..btnpref..": ".."End of interlocking]"
form = form.."button[0.5,"..(offset+0.5)..";5,1;"..btnpref.."_makeil;Create Interlocked Track Section]"
--if tcbs.section_free then
--form = form.."button[0.5,"..(offset+1.5)..";5,1;"..btnpref.."_setlocked;Section is free]"
--else
--form = form.."button[0.5,"..(offset+1.5)..";5,1;"..btnpref.."_setfree;Section is blocked]"
--end
end
if tcbs.signal then
form = form.."button[0.5,"..(offset+2.5)..";5,1;"..btnpref.."_sigdia;Signalling]"
@ -297,11 +273,7 @@ minetest.register_on_player_receive_fields(function(player, formname, fields)
local tcb = ildb.get_tcb(pos)
if not tcb then return end
local f_gotots = {fields.A_gotots, fields.B_gotots}
local f_update = {fields.A_update, fields.B_update}
local f_remove = {fields.A_remove, fields.B_remove}
local f_makeil = {fields.A_makeil, fields.B_makeil}
local f_setlocked = {fields.A_setlocked, fields.B_setlocked}
local f_setfree = {fields.A_setfree, fields.B_setfree}
local f_asnsig = {fields.A_asnsig, fields.B_asnsig}
local f_sigdia = {fields.A_sigdia, fields.B_sigdia}
@ -312,29 +284,12 @@ minetest.register_on_player_receive_fields(function(player, formname, fields)
advtrains.interlocking.show_ts_form(tcbs.ts_id, pname)
return
end
if f_update[connid] then
ildb.sync_tcb_neighbors(pos, connid)
end
if f_remove[connid] then
ildb.remove_from_interlocking({p=pos, s=connid})
end
else
if f_makeil[connid] then
-- try sinc_tcb_neighbors first
ildb.sync_tcb_neighbors(pos, connid)
-- if that didn't work, create new section
if not tcbs.ts_id then
ildb.create_ts({p=pos, s=connid})
ildb.sync_tcb_neighbors(pos, connid)
ildb.create_ts_from_tcbs({p=pos, s=connid})
end
end
-- non-interlocked
if f_setfree[connid] then
tcbs.section_free = true
end
if f_setlocked[connid] then
tcbs.section_free = nil
end
end
if f_asnsig[connid] and not tcbs.signal then
minetest.chat_send_player(pname, "Configuring TCB: Please punch the signal to assign.")
@ -357,10 +312,7 @@ end)
-- TS Formspec
-- textlist selection temporary storage
local ts_pselidx = {}
function advtrains.interlocking.show_ts_form(ts_id, pname, sel_tcb)
function advtrains.interlocking.show_ts_form(ts_id, pname)
if not minetest.check_player_privs(pname, "interlocking") then
minetest.chat_send_player(pname, "Insufficient privileges to use this!")
return
@ -369,7 +321,7 @@ function advtrains.interlocking.show_ts_form(ts_id, pname, sel_tcb)
if not ts_id then return end
local form = "size[10,10]label[0.5,0.5;Track Section Detail - "..ts_id.."]"
form = form.."field[0.8,2;5.2,1;name;Section name;"..minetest.formspec_escape(ts.name).."]"
form = form.."field[0.8,2;5.2,1;name;Section name;"..minetest.formspec_escape(ts.name or "").."]"
form = form.."button[5.5,1.7;1,1;setname;Set]"
local hint
@ -382,26 +334,8 @@ function advtrains.interlocking.show_ts_form(ts_id, pname, sel_tcb)
form = form.."textlist[0.5,3;5,3;tcblist;"..table.concat(strtab, ",").."]"
if ildb.may_modify_ts(ts) then
if players_link_ts[pname] then
local other_id = players_link_ts[pname]
local other_ts = ildb.get_ts(other_id)
if other_ts then
if ildb.may_modify_ts(other_ts) then
form = form.."button[5.5,3;3.5,1;mklink;Join with "..minetest.formspec_escape(other_ts.name).."]"
form = form.."button[9 ,3;0.5,1;cancellink;X]"
end
end
else
form = form.."button[5.5,3;4,1;link;Join into other section]"
hint = 1
end
form = form.."button[5.5,4;4,1;dissolve;Dissolve Section]"
form = form.."button[5.5,4;4,1;remove;Remove Section]"
form = form.."tooltip[dissolve;This will remove the track section and set all its end points to End Of Interlocking]"
if sel_tcb then
form = form.."button[5.5,5;4,1;del_tcb;Unlink selected TCB]"
hint = 2
end
else
hint=3
end
@ -420,17 +354,12 @@ function advtrains.interlocking.show_ts_form(ts_id, pname, sel_tcb)
end
form = form.."button[5.5,7;4,1;reset;Reset section state]"
if hint == 1 then
form = form.."label[0.5,0.75;Use the 'Join' button to designate rail crosses and link not listed far-away TCBs]"
elseif hint == 2 then
form = form.."label[0.5,0.75;Unlinking a TCB will set it to non-interlocked mode.]"
elseif hint == 3 then
if hint == 3 then
form = form.."label[0.5,0.75;You cannot modify track sections when a route is set or a train is on the section.]"
--form = form.."label[0.5,1;Trying to unlink a TCB directly connected to this track will not work.]"
end
ts_pselidx[pname]=sel_tcb
minetest.show_formspec(pname, "at_il_tsconfig_"..ts_id, form)
end
@ -442,45 +371,15 @@ minetest.register_on_player_receive_fields(function(player, formname, fields)
return
end
-- independent of the formspec, clear this whenever some formspec event happens
local tpsi = ts_pselidx[pname]
ts_pselidx[pname] = nil
local ts_id = string.match(formname, "^at_il_tsconfig_(.+)$")
if ts_id and not fields.quit then
local ts = ildb.get_ts(ts_id)
if not ts then return end
local sel_tcb
if fields.tcblist then
local tev = minetest.explode_textlist_event(fields.tcblist)
sel_tcb = tev.index
ts_pselidx[pname] = sel_tcb
elseif tpsi then
sel_tcb = tpsi
end
if ildb.may_modify_ts(ts) then
if players_link_ts[pname] then
if fields.cancellink then
players_link_ts[pname] = nil
elseif fields.mklink then
ildb.link_track_sections(players_link_ts[pname], ts_id)
players_link_ts[pname] = nil
end
end
if fields.del_tcb and sel_tcb and sel_tcb > 0 and sel_tcb <= #ts.tc_breaks then
if not ildb.remove_from_interlocking(ts.tc_breaks[sel_tcb]) then
minetest.chat_send_player(pname, "Please unassign signal first!")
end
sel_tcb = nil
end
if fields.link then
players_link_ts[pname] = ts_id
end
if fields.dissolve then
ildb.dissolve_ts(ts_id)
if fields.remove then
ildb.remove_ts(ts_id)
minetest.close_formspec(pname, formname)
return
end
@ -489,7 +388,7 @@ minetest.register_on_player_receive_fields(function(player, formname, fields)
if fields.setname then
ts.name = fields.name
if ts.name == "" then
ts.name = "Section "..ts_id
ts.name = nil
end
end
@ -566,7 +465,7 @@ function advtrains.interlocking.show_tcb_marker(pos)
ts = ildb.get_ts(tcbs.ts_id)
end
if ts then
itex[connid] = ts.name
itex[connid] = ts.name or tcbs.ts_id or "???"
else
itex[connid] = "--EOI--"
end
@ -589,6 +488,47 @@ function advtrains.interlocking.show_tcb_marker(pos)
markerent[pts] = obj
end
function advtrains.interlocking.remove_tcb_marker(pos)
local pts = advtrains.roundfloorpts(pos)
if markerent[pts] then
markerent[pts]:remove()
end
markerent[pts] = nil
end
-- Spawns particles to highlight the clicked track section
-- TODO: Adapt behavior to not dumb-walk anymore
function advtrains.interlocking.highlight_track_section(pos)
local ti = advtrains.get_track_iterator(pos, nil, 100, true)
local pos, connid, bconnid, tcb
while ti:has_next_branch() do
pos, connid = ti:next_branch()
--atdebug("highlight_track_section: BRANCH: ",pos, connid)
bconnid = nil
repeat
-- spawn particles
minetest.add_particle({
pos = pos,
velocity = {x=0, y=0, z=0},
acceleration = {x=0, y=0, z=0},
expirationtime = 10,
size = 7,
vertical = true,
texture = "at_il_ts_highlight_particle.png",
glow = 6,
})
-- abort if TCB is found
tcb = ildb.get_tcb(pos)
if tcb then
advtrains.interlocking.show_tcb_marker(pos)
break
end
pos, connid, bconnid = ti:next_track()
--atdebug("highlight_track_section: TRACK: ",pos, connid, bconnid)
until not pos
end
end
-- Signalling formspec - set routes a.s.o
-- textlist selection temporary storage

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.0 KiB

View File

@ -3,14 +3,64 @@
local ilrs = advtrains.interlocking.route
local function node_right_click(pos, pname)
if advtrains.is_passive(pos) then
local form = "size[7,5]label[0.5,0.5;Route lock inspector]"
local pts = minetest.pos_to_string(pos)
local rtl = ilrs.has_route_lock(pts)
if rtl then
form = form.."label[0.5,1;Route locks currently put:\n"..rtl.."]"
form = form.."button_exit[0.5,3.5; 5,1;clear;Clear]"
else
form = form.."label[0.5,1;No route locks set]"
form = form.."button_exit[0.5,3.5; 5,1;emplace;Emplace manual lock]"
end
minetest.show_formspec(pname, "at_il_rtool_"..pts, form)
return
end
-- If not a turnout, check the track section and show a form
local node_ok, conns, rail_y=advtrains.get_rail_info_at(pos)
if not node_ok then
minetest.chat_send_player(pname, "Node is not a track!")
return
end
local ts_id = advtrains.interlocking.db.check_and_repair_ts_at_pos(pos)
if ts_id then
advtrains.interlocking.show_ts_form(ts_id, pname)
else
minetest.chat_send_player(pname, "No track section at this location!")
end
end
local function node_left_click(pos, pname)
local node_ok, conns, rail_y=advtrains.get_rail_info_at(pos)
if not node_ok then
minetest.chat_send_player(pname, "Node is not a track!")
return
end
local ts_id = advtrains.interlocking.db.check_and_repair_ts_at_pos(pos)
if ts_id then
advtrains.interlocking.highlight_track_section(pos)
else
minetest.chat_send_player(pname, "No track section at this location!")
end
end
minetest.register_craftitem("advtrains_interlocking:tool",{
description = "Interlocking tool\nright-click turnouts to inspect route locks",
description = "Interlocking tool\nPunch: Highlight track section\nPlace: check route locks/show track section info",
groups = {cracky=1}, -- key=name, value=rating; rating=1..3.
inventory_image = "at_il_tool.png",
wield_image = "at_il_tool.png",
stack_max = 1,
on_place = function(itemstack, placer, pointed_thing)
local pname = placer:get_player_name()
on_place = function(itemstack, player, pointed_thing)
local pname = player:get_player_name()
if not pname then
return
end
@ -20,27 +70,23 @@ minetest.register_craftitem("advtrains_interlocking:tool",{
end
if pointed_thing.type=="node" then
local pos=pointed_thing.under
if advtrains.is_passive(pos) then
local form = "size[7,5]label[0.5,0.5;Route lock inspector]"
local pts = minetest.pos_to_string(pos)
local rtl = ilrs.has_route_lock(pts)
if rtl then
form = form.."label[0.5,1;Route locks currently put:\n"..rtl.."]"
form = form.."button_exit[0.5,3.5; 5,1;clear;Clear]"
else
form = form.."label[0.5,1;No route locks set]"
form = form.."button_exit[0.5,3.5; 5,1;emplace;Emplace manual lock]"
end
minetest.show_formspec(pname, "at_il_rtool_"..pts, form)
else
minetest.chat_send_player(pname, "Cannot use this here.")
return
end
node_right_click(pos, pname)
end
end,
on_use = function(itemstack, player, pointed_thing)
local pname = player:get_player_name()
if not pname then
return
end
if not minetest.check_player_privs(pname, {interlocking=true}) then
minetest.chat_send_player(pname, "Insufficient privileges to use this!")
return
end
if pointed_thing.type=="node" then
local pos=pointed_thing.under
node_left_click(pos, pname)
end
end
})
minetest.register_on_player_receive_fields(function(player, formname, fields)