Add Track Circuit Breaks (TCBs), Database and Track Circuit Setup

Does not get saved yet.
This commit is contained in:
orwell96 2018-06-20 20:13:12 +02:00
parent 65675664e3
commit 5992618ee8
4 changed files with 377 additions and 0 deletions

View File

@ -334,4 +334,8 @@ function advtrains.random_id()
end
return idst
end
-- Shorthand for pos_to_string and round_vector_floor_y
function advtrains.roundfloorpts(pos)
return minetest.pos_to_string(advtrains.round_vector_floor_y(pos))
end

View File

@ -32,6 +32,9 @@ Route setting fails whenever any TC that we want to set ROUTE to is already set
Apart from this, we need to set turnouts
- Turnouts on the track are set held as ROUTE
- Turnouts that purpose as flank protection are set held as FLANK (NOTE: left as an idea for later, because it's not clear how to do this properly without an engineer)
Note: In SimSig, it is possible to set a route into an still occupied section on the victoria line sim. (at the depot exit at seven sisters), although
there are still segments set ahead of the first train passing, remaining from another route.
Because our system will be able to remember "requested routes" and set them automatically once ready, this is not necessary here.
== Call-On/Multiple Trains ==
It will be necessary to join and split trains using call-on routes. A call-on route may be set when:
- there are no ROUTE reservations
@ -50,7 +53,196 @@ CALL_ON_ALLOWED - Whether this TC being blocked (TRAIN or ROUTE) does not preven
== More notes ==
- It may not be possible to switch turnouts when their TC has any state entry
== Route releasing (TORR) ==
A train passing through a route happens as follows:
Route set from entry to exit signal
Train passes entry signal and enters first TC past the signal
-> Route from signal cleared (TCs remain locked)
-> ROUTE status of first TC past signal cleared
Train continues along the route.
Whenever train leaves a TC
-> Clearing any routes set from this TC outward recursively - see "Reversing problem"
Whenever train enters a TC
-> Clear route status from the just entered TC
== Reversing Problem ==
Encountered at the Royston simulation in SimSig. It is solved there by imposing a time limit on the set route. Call-on routes can somehow be set anyway.
Imagine this setup: (T=Train, R=Route, >=in_dir TCB)
O-| Royston P2 |-O
T->---|->RRR-|->RRR-|--
Train T enters from the left, the route is set to the right signal. But train is supposed to reverse here and stops this way:
O-| Royston P2 |-O
------|-TTTT-|->RRR-|--
The "Route" on the right is still set. Imposing a timeout here is a thing only professional engineers can determine, not an algorithm.
O-| Royston P2 |-O
<-T---|------|->RRR-|--
The train has left again, while route on the right is still set.
So, we have to clear the set route when the train has left the left TC.
This does not conflict with call-on routes, because both station tracks are set as "allow call-on"
Because none of the routes extends past any non-call-on sections, call-on route would be allowed here, even though the route
is locked in opposite direction at the time of routesetting.
Another case of this:
--TTT/--|->RRR--
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.
]]--
local TRAVERSER_LIMIT = 100
local ildb = {}
local track_circuit_breaks = {}
function ildb.load(data)
end
function ildb.save()
return {}
end
--
--[[
TCB data structure
{
[1] = { -- Variant: with adjacent TCs.
== Synchronized properties == Apply to the whole TC
adjacent = { <signal specifier>,... } -- Adjacent TCBs, forms a TC with these
conflict = { <signal specifier>,... } -- Conflicting TC's (chosen as a representative TCB member)
-- Used e.g. for crossing rails that do not have nodes in common (like it's currently done)
incomplete = <boolean> -- Set when the recursion counter hit during traverse. Probably needs to add
-- another tcb at some far-away place
route = {origin = <signal>, in_dir = <boolean>}
-- Set whenever a route has been set through this TC. It saves the origin tcb id and side
-- (=the origin signal). in_dir is set when the train will enter the TC from this side
== Unsynchronized properties == Apply only to this side of the TC
signal = <pos> -- optional: when set, routes can be set from this tcb/direction and signal
-- aspect will be set accordingly.
routetar = <signal> -- Route set from this signal. This is the entry that is cleared once
-- train has passed the signal. (which will set the aspect to "danger" again)
route_committed = <boolean> -- When setting/requesting a route, routetar will be set accordingly,
-- while the signal still displays danger and nothing is written to the TCs
-- As soon as the route can actually be set, all relevant TCs and turnouts are set and this field
-- is set true, clearing the signal
},
[2] = { -- Variant: end of track-circuited area (initial state of TC)
end_of_interlocking = true,
section_free = <boolean>, --this can be set by an exit node via mesecons or atlatc,
-- or from the tc formspec.
}
}
Signal specifier (a pair of TCB/Side):
{p = <pos>, s = <1/2>}
]]
--
function ildb.create_tcb(pos)
local new_tcb = {
[1] = {end_of_interlocking = true},
[2] = {end_of_interlocking = true},
}
local pts = advtrains.roundfloorpts(pos)
track_circuit_breaks[pts] = new_tcb
end
function ildb.get_tcb(pos)
local pts = advtrains.roundfloorpts(pos)
return track_circuit_breaks[pts]
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)
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
-- end of track
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
table.insert(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,"found tcb's:",found_tcbs)
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, hit_counter)
end
end
return counter_hit
end
local function sigd_equal(sigd, cmp)
return vector.equals(sigd.p, cmp.p) and sigd.s==cmp.s
end
-- Updates the neighbors of this TCB using the traverser function (see comments above)
-- returns true if the traverser hit the counter, which means that there could be another
-- TCB outside of the traversed range.
function ildb.update_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
error("update_tcb_neighbors but node is NOK: "..minetest.pos_to_string(pos))
end
local counter_hit = traverser(found_tcbs, pos, conns, connid, 0, hit_counter)
for idx, sigd in pairs(found_tcbs) do
local tcb = ildb.get_tcb(sigd.p)
local tcbs = tcb[sigd.s]
tcbs.end_of_interlocking = nil
tcbs.incomplete = counter_hit
tcbs.adjacent = {}
for idx2, other_sigd in pairs(found_tcbs) do
if idx~=idx2 then
ildb.add_adjacent(tcbs, sigd.p, sigd.s, other_sigd)
end
end
end
return hit_counter
end
-- Add the adjacency entry into the tcbs, but without duplicating it
-- and without adding a self-reference
function ildb.add_adjacent(tcbs, this_pos, this_connid, sigd)
if sigd_equal(sigd, {p=this_pos, s=this_connid}) then
return
end
tcbs.end_of_interlocking = nil
if not tcbs.adjacent then
tcbs.adjacent = {}
end
for idx, cmp in pairs(tcbs.adjacent) do
if sigd_equal(sigd, cmp) then
return
end
end
table.insert(tcbs.adjacent, sigd)
end
advtrains.interlocking.db = ildb

View File

@ -6,5 +6,6 @@ advtrains.interlocking = {}
local modpath = minetest.get_modpath(minetest.get_current_modname()) .. DIR_DELIM
dofile(modpath.."database.lua")
dofile(modpath.."tcb.lua")
dofile(modpath.."signal_api.lua")
dofile(modpath.."demosignals.lua")

View File

@ -0,0 +1,180 @@
-- Track Circuit Breaks - Player interaction
local players_assign_tcb = {}
local players_addfar_tcb = {}
local lntrans = { "A", "B" }
local function sigd_to_string(sigd)
return minetest.pos_to_string(sigd.p).." / "..lntrans[sigd.s]
end
minetest.register_node("advtrains_interlocking:tcb_node", {
drawtype = "mesh",
paramtype="light",
paramtype2="facedir",
walkable = true,
selection_box = {
type = "fixed",
fixed = {-1/4, -1/2, -1/4, 1/4, 1/2, 1/4},
},
mesh = "at_il_tcb_node.obj",
tiles = {"at_il_tcb_node.png"},
description="Track Circuit Break",
sunlight_propagates=true,
groups = {
cracky=3,
not_blocking_trains=1,
--save_in_at_nodedb=2,
},
after_place_node = function(pos, node, player)
local meta = minetest.get_meta(pos)
meta:set_string("infotext", "Unconfigured Track Circuit Break, right-click to assign.")
end,
on_rightclick = function(pos, node, player)
local meta = minetest.get_meta(pos)
local tcbpts = meta:get_string("tcb_pos")
local pname = player:get_player_name()
if tcbpts ~= "" then
local tcbpos = minetest.string_to_pos(tcbpts)
advtrains.interlocking.show_tcb_form(tcbpos, pname)
else
--unconfigured
--TODO security
minetest.chat_send_player(pname, "Configuring TCB: Please punch the rail you want to assign this TCB to.")
players_assign_tcb[pname] = pos
end
end,
on_punch = function(pos, node, player)
local meta = minetest.get_meta(pos)
atwarn("Would show tcb marker.")
-- TODO TCB-Marker anzeigen
end,
})
minetest.register_on_punchnode(function(pos, node, player, pointed_thing)
local pname = player:get_player_name()
local tcbnpos = players_assign_tcb[pname]
if tcbnpos then
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
advtrains.interlocking.db.create_tcb(pos)
advtrains.interlocking.db.update_tcb_neighbors(pos, 1)
advtrains.interlocking.db.update_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))
minetest.chat_send_player(pname, "Configuring TCB: Successfully configured TCB")
else
minetest.chat_send_player(pname, "Configuring TCB: This is not a normal two-connection rail! Aborted.")
end
else
minetest.chat_send_player(pname, "Configuring TCB: Node is too far away. Aborted.")
end
players_assign_tcb[pname] = nil
end
end)
local function mkformspec(tcbs, btnpref, offset, pname)
local form = "label[0.5,"..offset..";Side "..btnpref..": "..(tcbs.end_of_interlocking and "End of interlocking" or "Track Circuit").."]"
if tcbs.end_of_interlocking then
form = form.."button[0.5,"..(offset+1)..";3,1;"..btnpref.."_clearadj;Activate Interlocking]"
if tcbs.section_free then
form = form.."button[4.5,"..(offset+1)..";3,1;"..btnpref.."_setlocked;Section is free]"
else
form = form.."button[4.5,"..(offset+1)..";3,1;"..btnpref.."_setfree;Section is blocked]"
end
else
local strtab = {}
for idx, sigd in ipairs(tcbs.adjacent) do
strtab[idx] = minetest.formspec_escape(sigd_to_string(sigd))
end
form = form.."textlist[0.5,"..(offset+1)..";5,3;"..btnpref.."_adjlist;"..table.concat(strtab, ",").."]"
if players_addfar_tcb[pname] then
local sigd = players_addfar_tcb[pname]
form = form.."button[5.5,"..(offset+2)..";2.5,1;"..btnpref.."_addadj;Link TCB to "..sigd_to_string(sigd).."]"
form = form.."button[8,"..(offset+2)..";0.5,1;"..btnpref.."_canceladdadj;X]"
else
form = form.."button[5.5,"..(offset+2)..";3,1;"..btnpref.."_addadj;Add far TCB]"
end
form = form.."button[5.5,"..(offset+1)..";3,1;"..btnpref.."_clearadj;Clear&Update]"
form = form.."button[5.5,"..(offset+3)..";3,1;"..btnpref.."_mknonint;Make non-interlocked]"
if tcbs.incomplete then
form = form.."label[0.5,"..(offset+0.5)..";Warning: You possibly need to add TCBs manually!]"
end
end
return form
end
function advtrains.interlocking.show_tcb_form(pos, pname)
local tcb = advtrains.interlocking.db.get_tcb(pos)
if not tcb then return end
local form = "size[10,10] label[0.5,0.5;Track Circuit Break Configuration]"
form = form .. mkformspec(tcb[1], "A", 1, pname)
form = form .. mkformspec(tcb[2], "B", 6, pname)
minetest.show_formspec(pname, "at_il_tcbconfig_"..minetest.pos_to_string(pos), form)
end
minetest.register_on_player_receive_fields(function(player, formname, fields)
local pname = player:get_player_name()
local pts = string.match(formname, "^at_il_tcbconfig_(.+)$")
local pos
if pts then
pos = minetest.string_to_pos(pts)
end
if pos and not fields.quit then
local tcb = advtrains.interlocking.db.get_tcb(pos)
if not tcb then return end
local f_clearadj = {fields.A_clearadj, fields.B_clearadj}
local f_addadj = {fields.A_addadj, fields.B_addadj}
local f_canceladdadj = {fields.A_canceladdadj, fields.B_canceladdadj}
local f_setlocked = {fields.A_setlocked, fields.B_setlocked}
local f_setfree = {fields.A_setfree, fields.B_setfree}
local f_mknonint = {fields.A_mknonint, fields.B_mknonint}
for connid=1,2 do
if f_clearadj[connid] then
advtrains.interlocking.db.update_tcb_neighbors(pos, connid)
end
if f_mknonint[connid] then
--TODO: remove this from the other tcb's
tcb[connid].end_of_interlocking = true
end
if f_addadj[connid] then
if players_addfar_tcb[pname] then
local sigd = players_addfar_tcb[pname]
advtrains.interlocking.db.add_adjacent(tcb[connid], pos, connid, sigd)
players_addfar_tcb[pname] = nil
else
players_addfar_tcb[pname] = {p = pos, s = connid}
end
end
if f_canceladdadj[connid] then
players_addfar_tcb[pname] = nil
end
if f_setfree[connid] then
tcb[connid].section_free = true
end
if f_setlocked[connid] then
tcb[connid].section_free = nil
end
end
advtrains.interlocking.show_tcb_form(pos, pname)
end
end)