Fix lots of things around new LZB

master
orwell96 2020-04-28 16:01:35 +02:00
parent 5c2534cc35
commit 8660794ef8
9 changed files with 184 additions and 100 deletions

View File

@ -182,9 +182,11 @@ minetest.register_node(nodename, {
^- called when a train leaves the rail
-- The following function is only in effect when interlocking is enabled:
on_train_approach = function(pos, train_id, train, index, lzbdata)
on_train_approach = function(pos, train_id, train, index, has_entered, lzbdata)
^- called when a train is approaching this position, called exactly once for every path recalculation (which can happen at any time)
^- This is called so that if the train would start braking now, it would come to halt about(wide approx) 5 nodes before the rail.
^- has_entered: when true, the train is already standing on this node with its front tip, and the enter callback has already been called.
Possibly, some actions need not to be taken in this case. Only set if it's the very first node the train is standing on.
^- lzbdata should be ignored and nothing should be assigned to it
}
})

View File

@ -27,8 +27,10 @@ attrans = minetest.get_translator ("advtrains")
--advtrains
DUMP_DEBUG_SAVE = false
GENERATE_ATRICIFIAL_LAG = false
local DUMP_DEBUG_SAVE = false
local GENERATE_ATRICIFIAL_LAG = false
local HOW_MANY_LAG = 1.0
--Constant for maximum connection value/division of the circle
AT_CMAX = 16
@ -563,19 +565,12 @@ minetest.register_globalstep(function(dtime_mt)
local dtime
if GENERATE_ATRICIFIAL_LAG then
dtime = 0.2
dtime = HOW_MANY_LAG
if os.clock()<t then
return
end
t = os.clock()+0.2
else
--limit dtime: if trains move too far in one step, automation may cause stuck and wrongly braking trains
dtime=dtime_mt
if dtime>0.2 then
atprint("Limiting dtime to 0.2!")
dtime=0.2
end
t = os.clock()+HOW_MANY_LAG
end
advtrains.mainloop_trainlogic(dtime)

View File

@ -18,13 +18,22 @@ train.lzb = {
-- Table of custom data filled in by approach callbacks
-- Whenever an approach callback inserts an LZB checkpoint with changed lzbdata,
-- all consecutive approach callbacks will see these passed as lzbdata table.
udata = arbitrary user data, no official way to retrieve (do not use)
}
trav_lzbdata = currently active lzbdata table at traverser index
}
each step, for every item in "oncoming", we need to determine the location to start braking (+ some safety margin)
and, if we passed this point for at least one of the items, initiate brake.
When speed has dropped below, say 3, decrease the margin to zero, so that trains actually stop at the signal IP.
The spd variable and travsht need to be updated on every aspect change. it's probably best to reset everything when any aspect changes
The LZB subsystem keeps track of "checkpoints" the train will pass in the future, and has two main tasks:
1. run approach callbacks, and run callbacks when passing LZB checkpoints
2. keep track of the permitted speed at checkpoints, and make sure that the train brakes accordingly
To perform 2, it populates the train.path_speed table which is handled along with the path subsystem.
This table is used in trainlogic.lua/train_step_b() and applied to the velocity calculations.
Note: in contrast to node enter callbacks, which are called when the train passes the .5 index mark, LZB callbacks are executed on passing the .0 index mark!
If an LZB checkpoint has speed 0, the train will still enter the node (the enter callback will be called), but will stop at the 0.9 index mark (for details, see SLOW_APPROACH in trainlogic.lua)
The start point for the LZB traverser (and thus the first node that will receive an approach callback) is floor(train.index) + 1. This means, once the LZB checkpoint callback has fired,
this path node will not receive any further approach callbacks for the same approach situation
]]
@ -61,6 +70,7 @@ local function resolve_latest_lzbdata(ckp, index)
return ckpi.lzbdata
end
end
return {}
end
local function look_ahead(id, train)
@ -75,9 +85,11 @@ local function look_ahead(id, train)
local lzb = train.lzb
local trav = lzb.trav_index
-- retrieve latest lzbdata
local lzbdata = lzb.trav_lzbdata
if lzbdata.off_track then
if not lzb.trav_lzbdata then
lzb.trav_lzbdata = resolve_latest_lzbdata(lzb.checkpoints, trav)
end
if lzb.trav_lzbdata.off_track then
--previous position was off track, do not scan any further
end
@ -85,8 +97,8 @@ local function look_ahead(id, train)
local pos = advtrains.path_get(train, trav)
-- check offtrack
if trav - 1 == train.path_trk_f then
lzbdata.off_track = true
advtrains.lzb_add_checkpoint(train, trav - 1, 0, nil, lzbdata)
lzb.trav_lzbdata.off_track = true
advtrains.lzb_add_checkpoint(train, trav - 1, 0, nil, lzb.trav_lzbdata)
else
-- run callbacks
-- Note: those callbacks are defined in trainlogic.lua for consistency with the other node callbacks
@ -101,6 +113,27 @@ local function look_ahead(id, train)
end
local function call_runover_callbacks(id, train)
if not train.lzb then return end
local i = 1
local idx = atfloor(train.index)
local ckp = train.lzb.checkpoints
while ckp[i] do
if ckp[i].index <= idx then
-- call callback
local it = ckp[i]
if it.callback then
it.callback(it.pos, id, train, it.index, it.speed, train.lzb.lzbdata)
end
-- note: lzbdata is always defined as look_ahead was called before
table.remove(ckp, i)
else
i = i + 1
end
end
end
-- Flood-fills train.path_speed, based on this checkpoint
local function apply_checkpoint_to_path(train, checkpoint)
if not checkpoint.speed then
@ -145,8 +178,7 @@ s = v0 * ------- + - * | ------- | = -----------
-- Removes all LZB checkpoints and restarts the traverser at the current train index
function advtrains.lzb_invalidate(train)
train.lzb = {
trav_index = atround(train.index),
trav_lzbdata = {},
trav_index = atfloor(train.index) + 1,
checkpoints = {},
}
end
@ -175,7 +207,8 @@ end
-- Add LZB control point
-- lzbdata: If you modify lzbdata in an approach callback, you MUST add a checkpoint AND pass the (modified) lzbdata into it.
-- If you DON'T modify lzbdata, you MUST pass nil as lzbdata. Always modify the lzbdata table in place, never overwrite it!
function advtrains.lzb_add_checkpoint(train, index, speed, callback, lzbdata)
-- udata: user-defined data, do not use externally
function advtrains.lzb_add_checkpoint(train, index, speed, callback, lzbdata, udata)
local lzb = train.lzb
local pos = advtrains.path_get(train, index)
local lzbdata_c = nil
@ -190,6 +223,7 @@ function advtrains.lzb_add_checkpoint(train, index, speed, callback, lzbdata)
speed = speed,
callback = callback,
lzbdata = lzbdata_c,
udata = udata,
}
table.insert(lzb.checkpoints, ckp)
@ -212,5 +246,5 @@ advtrains.te_register_on_update(function(id, train)
return
end
look_ahead(id, train)
--apply_control(id, train)
call_runover_callbacks(id, train)
end, true)

View File

@ -56,6 +56,13 @@ local VLEVER_ROLL = 2
local VLEVER_HOLD = 3
local VLEVER_ACCEL = 4
-- How far in front of a whole index with LZB 0 restriction the train should come to a halt
-- value must be between 0 and 0.5, exclusively
local LZB_ZERO_APPROACH_DIST = 0.1
-- Speed the train temporarily approaches the stop point with
local LZB_ZERO_APPROACH_SPEED = 0.2
tp_player_tmr = 0
@ -256,7 +263,6 @@ function advtrains.train_ensure_init(id, train)
--assertdef(train, "tarvelocity", 0)
assertdef(train, "acceleration", 0)
assertdef(train, "id", id)
assertdef(train, "ctrl", {})
if not train.drives_on or not train.max_speed then
@ -350,11 +356,9 @@ function advtrains.train_step_b(id, train, dtime)
v_target_apply(v_targets, VLEVER_ROLL, 0)
end
--- 3a. this can be useful for debugs/warnings and is used for check_trainpartload ---
local t_info, train_pos=sid(id), advtrains.path_get(train, atfloor(train.index))
if train_pos then
t_info=t_info.." @"..minetest.pos_to_string(train_pos)
--atprint("train_pos:",train_pos)
-- interlocking speed restriction
if train.speed_restriction then
v_target_apply(v_targets, VLEVER_BRAKE, train.speed_restriction)
end
--apply off-track handling:
@ -374,7 +378,7 @@ function advtrains.train_step_b(id, train, dtime)
--interpret ATC command and apply auto-lever control when not actively controlled
local v0 = train.velocity
if train.ctrl.user then
if train.ctrl_user then
advtrains.atc.train_reset_command(train)
else
local braketar = train.atc_brake_target
@ -405,7 +409,6 @@ function advtrains.train_step_b(id, train, dtime)
train.atc_delay = nil
end
train.ctrl.atc = nil
if train.tarvelocity and train.tarvelocity>v0 then
v_target_apply(v_targets, VLEVER_ACCEL, train.tarvelocity)
end
@ -417,28 +420,47 @@ function advtrains.train_step_b(id, train, dtime)
v_target_apply(v_targets, VLEVER_EMERG, braketar)
end
else
v_target_apply(v_targets, VLEVER_ROLL, braketar)
v_target_apply(v_targets, VLEVER_ROLL, train.tarvelocity)
end
end
end
local userc = train.ctrl_user
if userc then
v_target_apply(v_targets, userc, userc==VLEVER_ACCEL and train.max_speed or 0)
end
--- 2b. look at v_target, determine the effective v_target and desired acceleration ---
local tv_target, tv_lever
for _,lever in ipairs({VLEVER_EMERG, VLEVER_BRAKE, VLEVER_ROLL, VLEVER_ACCEL}) do
if v_targets[lever] then
tv_target = v_targets[lever]
tv_lever = lever
break
if v_targets[VLEVER_ACCEL] then
if v_targets[VLEVER_ACCEL] > v0 then
tv_target = v_targets[VLEVER_ACCEL]
tv_lever = VLEVER_ACCEL
end
end
for _,lever in ipairs({VLEVER_ROLL, VLEVER_BRAKE, VLEVER_EMERG}) do
if v_targets[lever] then
if v_targets[lever] <= v0 then
if not tv_target then
tv_target = v_targets[lever]
else
tv_target = math.min(v_targets[lever], tv_target)
end
end
if v_targets[lever] < v0 then
tv_lever = lever
end
end
end
--train.debug = dump({tv_target,tv_lever})
--- 2c. If no tv_lever set, honor the user control ---
local a_lever = tv_lever
if not tv_lever then
a_lever = train.ctrl.user
if not a_lever then
-- default to holding current speed
a_lever = VLEVER_HOLD
end
-- default to holding current speed
a_lever = VLEVER_HOLD
end
train.lever = a_lever
@ -447,11 +469,15 @@ function advtrains.train_step_b(id, train, dtime)
-- Iterates over the path nodes we WOULD pass if we were continuing with the speed assumed by actual_lever
-- and determines the MINIMUM of path_speed in this range.
-- Then, determines acceleration so that we can reach this 'overridden' target speed in this step (but short-circuited)
local lzb_zeroappr_target_index
local new_index_v_base -- which v was assumed when curr_tv was calculated
local new_index_curr_tv -- pre-calculated new train index in lzb check
if not a_lever or a_lever > VLEVER_BRAKE then
-- only needs to run if we're not yet braking anyway
local tv_vdiff = advtrains.get_acceleration(train, tv_lever) * dtime
local dst_curr_v = (v0 + tv_vdiff) * dtime
local nindex_curr_v = advtrains.path_get_index_by_offset(train, train.index, dst_curr_v)
new_index_v_base = v0 + (advtrains.get_acceleration(train, tv_lever) * dtime)
local dst_curr_v = new_index_v_base * dtime
new_index_curr_tv = advtrains.path_get_index_by_offset(train, train.index, dst_curr_v)
local i = atfloor(train.index)
local lzb_target
local psp
@ -460,17 +486,44 @@ function advtrains.train_step_b(id, train, dtime)
if psp then
lzb_target = lzb_target and math.min(lzb_target, psp) or psp
end
if i > nindex_curr_v then
if i > new_index_curr_tv then
break
end
i = i + 1
end
local dv
--train.debug = "newindex calc "..new_index_curr_tv.." basev="..new_index_v_base.." lzbtarget="..(lzb_target or "nil")
if lzb_target and lzb_target <= v0 then
-- apply to tv_target after the actual calculation happened
a_lever = VLEVER_BRAKE
tv_target = tv_target and math.min(tv_target, lzb_target) or lzb_target
if tv_target and tv_target > lzb_target then
if lzb_target < LZB_ZERO_APPROACH_SPEED then
--atdebug("hit zeroappr lzb=",lzb_target, "tv=", tv_target)
--go forward with LZB_ZERO_APPROACH_SPEED if tv_target didn't tell us otherwise
tv_target = LZB_ZERO_APPROACH_SPEED
-- find the zero index we're approaching
local lzb_zeroappr_target_index = math.ceil(train.index)
while train.path_speed[lzb_zeroappr_target_index] and train.path_speed[lzb_zeroappr_target_index] > 0 do
lzb_zeroappr_target_index = lzb_zeroappr_target_index + 1
--atdebug("zeroappr advancing ",lzb_zeroappr_target_index)
end
-- it should now point to an index with path_speed==0. In case of weird things, points to some far away index, so doesn't matter
lzb_zeroappr_target_index = lzb_zeroappr_target_index - LZB_ZERO_APPROACH_DIST
--atdebug("zeroappr target idx ",lzb_zeroappr_target_index)
-- don't do anything when we are already at this index, and stop
if train.index >= lzb_zeroappr_target_index then
tv_target = 0
a_lever = VLEVER_BRAKE
lzb_zeroappr_target_index = nil
--atdebug("zeroappr cancelling train has passed idx=",train.index, "za_idx=",lzb_zeroappr_target_index)
end
else
tv_target = lzb_target
end
end
end
end
@ -480,11 +533,13 @@ function advtrains.train_step_b(id, train, dtime)
local v1
local tv_effective = false
if tv_target and (math.abs(dv) > math.abs(tv_target - v0)) then
--atdebug("hit tv_target ",tv_target,"with v=",v0, "dv=",dv)
v1 = tv_target
tv_effective = true
else
v1 = v0 +dv
end
--train.debug = "tv_target="..(tv_target or "nil").." v0="..v0.." v1="..v1
if v1 > train.max_speed then
v1 = train.max_speed
@ -493,22 +548,23 @@ function advtrains.train_step_b(id, train, dtime)
v1 = 0
end
train.acceleration = v1 - v0
train.acceleration = (v1 - v0) / dtime
train.velocity = v1
--- 4. move train ---
-- if we have calculated the new end index before, don't do that again
if not new_index_v_base or new_index_v_base ~= v1 then
local tv_vdiff = advtrains.get_acceleration(train, tv_lever) * dtime
local dst_curr_v = v1 * dtime
new_index_curr_tv = advtrains.path_get_index_by_offset(train, train.index, dst_curr_v)
end
local idx_floor = math.floor(train.index)
local pdist = (train.path_dist[idx_floor+1] - train.path_dist[idx_floor])
local distance = (train.velocity*dtime) / pdist
--debugging code
--local debutg = advtrains.print_concat_table({"v0=",v0,"v1=",v1,"a_lever",a_lever,"tv_target",tv_target,"tv_eff",tv_effective})
--train.debug = debutg
if advtrains.DFLAG and v1>0 then error("DFLAG") end
train.index=train.index+distance
-- if the zeroappr mechanism has hit, go no further than zeroappr index
if lzb_zeroappr_target_index and new_index_curr_tv > lzb_zeroappr_target_index then
--atdebug("zeroappr hitcond newidx_tv=",new_index_curr_tv, "za_idx=",lzb_zeroappr_target_index)
new_index_curr_tv = lzb_zeroappr_target_index
end
train.index = new_index_curr_tv
recalc_end_index(train)
@ -636,8 +692,9 @@ local callbacks_enter_node, run_callbacks_enter_node = mknodecallback("enter")
local callbacks_leave_node, run_callbacks_leave_node = mknodecallback("leave")
-- Node callback for approaching
-- Might be called multiple times, whenever path is recalculated
-- signature is function(pos, id, train, index, lzbdata)
-- Might be called multiple times, whenever path is recalculated. Also called for the first node the train is standing on, then has_entered is true.
-- signature is function(pos, id, train, index, has_entered, lzbdata)
-- has_entered: true if the "enter" callback has already been executed for this train in this location
-- lzbdata: arbitrary data (shared between all callbacks), deleted when LZB is restarted.
-- These callbacks are called in order of distance as train progresses along tracks, so lzbdata can be used to
-- keep track of a train's state once it passes this point
@ -693,14 +750,16 @@ end
function advtrains.tnc_call_approach_callback(pos, train_id, train, index, lzbdata)
--atdebug("tnc approach",pos,train_id, lzbdata)
local has_entered = atround(train.index) == index
local node = advtrains.ndb.get_node(pos) --this spares the check if node is nil, it has a name in any case
local mregnode=minetest.registered_nodes[node.name]
if mregnode and mregnode.advtrains and mregnode.advtrains.on_train_approach then
mregnode.advtrains.on_train_approach(pos, train_id, train, index, lzbdata)
mregnode.advtrains.on_train_approach(pos, train_id, train, index, has_entered, lzbdata)
end
-- call other registered callbacks
run_callbacks_approach_node(pos, train_id, train, index, lzbdata)
run_callbacks_approach_node(pos, train_id, train, index, has_entered, lzbdata)
end

View File

@ -986,10 +986,10 @@ function wagon:show_bordcom(pname)
-- Interlocking functionality: If the interlocking module is loaded, you can set the signal aspect
-- from inside the train
if advtrains.interlocking and train.lzb and #train.lzb.oncoming > 0 then
if advtrains.interlocking and train.lzb and #train.lzb.checkpoints > 0 then
local i=1
while train.lzb.oncoming[i] do
local oci = train.lzb.oncoming[i] --TODO repair this
while train.lzb.checkpoints[i] do
local oci = train.lzb.checkpoints[i]
if oci.udata and oci.udata.signal_pos then
if advtrains.interlocking.db.get_sigd_for_signal(oci.udata.signal_pos) then
form = form .. "button[4.5,8;5,1;ilrs;Remote Routesetting]"

View File

@ -19,32 +19,34 @@ local function get_over_function(speed, shunt)
if speed == 0 and minetest.settings:get_bool("at_il_force_lzb_halt") then
atwarn(id,"overrun LZB 0 restriction (red signal) ",pos)
-- Set train 1 index backward. Hope this does not lead to bugs...
train.index = index - 0.5
train.velocity = 0
train.ctrl.lzb = 0
minetest.after(0, advtrains.invalidate_path, id)
--train.index = index - 0.5
train.speed_restriction = 0
else
train.speed_restriction = speed
train.is_shunt = shunt
end
--atdebug("train drove over IP: speed=",speed,"shunt=",shunt)
end
end
advtrains.tnc_register_on_approach(function(pos, id, train, index, lzbdata)
advtrains.tnc_register_on_approach(function(pos, id, train, index, has_entered, lzbdata)
--atdebug(id,"IL ApprC",pos,index,lzbdata)
--train.debug = advtrains.print_concat_table({train.is_shunt,"|",index,"|",lzbdata})
local pts = advtrains.roundfloorpts(pos)
local cn = train.path_cn[index]
local travsht = lzbdata.travsht
local travsht = lzbdata.il_shunt
local travspd = lzbdata.il_speed
if travsht==nil then
travsht = train.is_shunt
-- lzbdata has reset
travspd = train.speed_restriction
travsht = train.is_shunt or false
end
local travspd = lzbdata.travspd
local travwspd = lzbdata.travwspd
-- check for signal
local asp, spos = il.db.get_ip_signal_asp(pts, cn)
@ -66,7 +68,7 @@ advtrains.tnc_register_on_approach(function(pos, id, train, index, lzbdata)
--atdebug("trav: ",pos, cn, asp, spos, "travsht=", lzb.travsht)
local lspd
if asp then
--atdebug(id,"IL Signal",spos,asp)
--atdebug(id,"IL Signal",spos, asp, lzbdata, "trainstate", train.speed_restriction, train.is_shunt)
local nspd = 0
--interpreting aspect and determining speed to proceed
if travsht then
@ -95,26 +97,15 @@ advtrains.tnc_register_on_approach(function(pos, id, train, index, lzbdata)
end
end
local nwspd = asp.info.w_speed
if nwspd then
if nwspd == -1 then
travwspd = nil
else
travwspd = nwspd
end
end
--atdebug("ns,wns,ts,wts", nspd, nwspd, travspd, travwspd)
--atdebug("ns,ts", nspd, travspd)
lspd = travspd
if travwspd and (not lspd or lspd>travwspd) then
lspd = travwspd
end
local udata = {signal_pos = spos}
local callback = get_over_function(lspd, travsht)
lzbdata.travsht = travsht
lzbdata.travspd = travspd
lzbdata.travwspd = travwspd
advtrains.lzb_add_checkpoint(train, index, lspd, callback, lzbdata)
lzbdata.il_shunt = travsht
lzbdata.il_speed = travspd
--atdebug("new lzbdata",lzbdata)
advtrains.lzb_add_checkpoint(train, index, lspd, callback, lzbdata, udata)
end
end)

View File

@ -169,7 +169,8 @@ local adefunc = function(def, preset, suffix, rotation)
show_stoprailform(pos, player)
end,
advtrains = {
on_train_approach = function(pos,train_id, train, index)
on_train_approach = function(pos,train_id, train, index, has_entered)
if has_entered then return end -- do not stop again!
if train.path_cn[index] == 1 then
local pe = advtrains.encode_pos(pos)
local stdata = advtrains.lines.stops[pe]
@ -180,7 +181,7 @@ local adefunc = function(def, preset, suffix, rotation)
stdata.ars = {default=true}
end
if stdata.ars and (stdata.ars.default or advtrains.interlocking.ars_check_rule_match(stdata.ars, train) ) then
advtrains.lzb_add_checkpoint(train, index, 2, nil)
advtrains.lzb_add_checkpoint(train, index, 0, nil)
local stn = advtrains.lines.stations[stdata.stn]
local stnname = stn and stn.name or "Unknown Station"
train.text_inside = "Next Stop:\n"..stnname

View File

@ -247,8 +247,10 @@ set_shunt(), unset_shunt()
-- This additional function is available when advtrains_interlocking is enabled: --
atc_set_disable_ars(true)
atc_set_disable_ars(boolean)
Disables (true) or enables (false) the use of ARS for this train. The train will not trigger ARS (automatic route setting) on signals then.
Note: If you want to disable ARS from an approach callback, the call to atc_set_disable_ars(true) must happen during the approach callback,
and may not be deferred to an interrupt(). Else the train might trigger an ARS before the interrupt fires.
# Approach callbacks
The LuaATC interface provides a way to hook into the approach callback system, which is for example used in the TSR rails (provided by advtrains_interlocking) or the station tracks (provided by advtrains_lines). However, for compatibility reasons, this behavior needs to be explicitly enabled.

View File

@ -199,7 +199,7 @@ advtrains.register_tracks("default", {
--do async. Event is fired in train steps
atlatc.interrupt.add(0, pos, {type="train", train=true, id=train_id})
end,
on_train_approach = function(pos, train_id, train, index, lzbdata)
on_train_approach = function(pos, train_id, train, index, has_entered, lzbdata)
-- Insert an event only if the rail indicated that it supports approach callbacks
local ph=minetest.pos_to_string(pos)
local railtbl = atlatc.active.nodes[ph]
@ -208,7 +208,7 @@ advtrains.register_tracks("default", {
if railtbl and railtbl.data and railtbl.data.__approach_callback_mode then
local acm = railtbl.data.__approach_callback_mode
if acm==2 or (acm==1 and train.path_cn[index] == 1) then
local evtdata = {type="approach", approach=true, id=train_id}
local evtdata = {type="approach", approach=true, id=train_id, has_entered = has_entered}
-- This event is *required* to run synchronously, because it might set the ars_disable flag on the train and add LZB checkpoints,
-- although this is generally discouraged because this happens right in a train step
-- At this moment, I am not aware whether this may cause side effects, and I must encourage users not to do expensive calculations here.