Add Track Circuit Breaks (TCBs), Database and Track Circuit Setup
Does not get saved yet.
This commit is contained in:
parent
65675664e3
commit
5992618ee8
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -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")
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
||||
|
||||
|
||||
|
Loading…
Reference in New Issue