diff options
Diffstat (limited to 'advtrains_interlocking/routesetting.lua')
-rw-r--r-- | advtrains_interlocking/routesetting.lua | 342 |
1 files changed, 342 insertions, 0 deletions
diff --git a/advtrains_interlocking/routesetting.lua b/advtrains_interlocking/routesetting.lua new file mode 100644 index 0000000..67efaea --- /dev/null +++ b/advtrains_interlocking/routesetting.lua @@ -0,0 +1,342 @@ +-- Setting and clearing routes + +-- TODO duplicate +local lntrans = { "A", "B" } +local function sigd_to_string(sigd) + return minetest.pos_to_string(sigd.p).." / "..lntrans[sigd.s] +end + +local ildb = advtrains.interlocking.db +local ilrs = {} + +local sigd_equal = advtrains.interlocking.sigd_equal + +-- table containing locked points +-- also manual locks (maintenance a.s.o.) are recorded here +-- [pts] = { +-- [n] = { [by = <ts_id>], rsn = <human-readable text>, [origin = <sigd>] } +-- } +ilrs.rte_locks = {} +ilrs.rte_callbacks = { + ts = {}, + lck = {} +} + + +-- main route setting. First checks if everything can be set as designated, +-- then (if "try" is not set) actually sets it +-- returns: +-- true - route can be/was successfully set +-- false, message, cbts, cblk - something went wrong, what is contained in the message. +-- cbts: the ts id of the conflicting ts, cblk: the pts of the conflicting component +function ilrs.set_route(signal, route, try) + if not try then + local tsuc, trsn, cbts, cblk = ilrs.set_route(signal, route, true) + if not tsuc then + return false, trsn, cbts, cblk + end + end + + + -- we start at the tc designated by signal + local c_sigd = signal + local first = true + local i = 1 + local rtename = route.name + local signalname = ildb.get_tcbs(signal).signal_name + local c_tcbs, c_ts_id, c_ts, c_rseg, c_lckp + while c_sigd and i<=#route do + c_tcbs = ildb.get_tcbs(c_sigd) + if not c_tcbs then + if not try then atwarn("Did not find TCBS",c_sigd,"while setting route",rtename,"of",signal) end + return false, "No TCB found at "..sigd_to_string(c_sigd)..". Please reconfigure route!" + end + c_ts_id = c_tcbs.ts_id + if not c_ts_id then + if not try then atwarn("Encountered End-Of-Interlocking while setting route",rtename,"of",signal) end + return false, "No track section adjacent to "..sigd_to_string(c_sigd)..". Please reconfigure route!" + end + c_ts = ildb.get_ts(c_ts_id) + c_rseg = route[i] + c_lckp = {} + + if c_ts.route then + if not try then atwarn("Encountered ts lock during a real run of routesetting routine, at ts=",c_ts_id,"while setting route",rtename,"of",signal) end + return false, "Section '"..c_ts.name.."' already has route set from "..sigd_to_string(c_ts.route.origin)..":\n"..c_ts.route.rsn, c_ts_id, nil + end + if c_ts.trains and #c_ts.trains>0 then + if not try then atwarn("Encountered ts occupied during a real run of routesetting routine, at ts=",c_ts_id,"while setting route",rtename,"of",signal) end + return false, "Section '"..c_ts.name.."' is occupied!", c_ts_id, nil + end + + for pts, state in pairs(c_rseg.locks) do + local confl = ilrs.has_route_lock(pts, state) + + local pos = minetest.string_to_pos(pts) + if advtrains.is_passive(pos) then + local cstate = advtrains.getstate(pos) + if cstate ~= state then + local confl = ilrs.has_route_lock(pts) + if confl then + if not try then atwarn("Encountered route lock while a real run of routesetting routine, at position",pts,"while setting route",rtename,"of",signal) end + return false, "Lock conflict at "..pts..", Held locked by:\n"..confl, nil, pts + elseif not try then + advtrains.setstate(pos, state) + end + end + if not try then + ilrs.add_route_lock(pts, c_ts_id, "Route '"..rtename.."' from signal '"..signalname.."'", signal) + c_lckp[#c_lckp+1] = pts + end + else + if not try then atwarn("Encountered route lock misconfiguration (no passive component) while a real run of routesetting routine, at position",pts,"while setting route",rtename,"of",signal) end + return false, "No passive component at "..pts..". Please reconfigure route!" + end + end + -- reserve ts and write locks + if not try then + local nvar = c_rseg.next + if not route[i+1] then + -- We shouldn't use the "next" value of the final route segment, because this can lead to accidental route-cancelling of already set routes from another signal. + nvar = nil + end + c_ts.route = { + origin = signal, + entry = c_sigd, + rsn = "Route '"..rtename.."' from signal '"..signalname.."', segment #"..i, + first = first, + } + c_ts.route_post = { + locks = c_lckp, + next = nvar, + } + if c_tcbs.signal then + c_tcbs.route_committed = true + c_tcbs.aspect = route.aspect or advtrains.interlocking.GENERIC_FREE + c_tcbs.route_origin = signal + advtrains.interlocking.update_signal_aspect(c_tcbs) + end + end + -- advance + first = nil + c_sigd = c_rseg.next + i = i + 1 + end + + return true +end + +-- Checks whether there is a route lock that prohibits setting the component +-- to the wanted state. returns string with reasons on conflict +function ilrs.has_route_lock(pts) + -- look this up + local e = ilrs.rte_locks[pts] + if not e then return nil + elseif #e==0 then + ilrs.rte_locks[pts] = nil + return nil + end + local txts = {} + for _, ent in ipairs(e) do + txts[#txts+1] = ent.rsn + end + return table.concat(txts, "\n") +end + +-- adds route lock for position +function ilrs.add_route_lock(pts, ts, rsn, origin) + ilrs.free_route_locks_indiv(pts, ts, true) + local elm = {by=ts, rsn=rsn, origin=origin} + if not ilrs.rte_locks[pts] then + ilrs.rte_locks[pts] = { elm } + else + table.insert(ilrs.rte_locks[pts], elm) + end +end + +-- adds route lock for position +function ilrs.add_manual_route_lock(pts, rsn) + local elm = {rsn=rsn} + if not ilrs.rte_locks[pts] then + ilrs.rte_locks[pts] = { elm } + else + table.insert(ilrs.rte_locks[pts], elm) + end +end + +-- frees route locking for all points (components) that were set by this ts +function ilrs.free_route_locks(ts, lcks, nocallbacks) + for _,pts in pairs(lcks) do + ilrs.free_route_locks_indiv(pts, ts, nocallbacks) + end +end + +function ilrs.free_route_locks_indiv(pts, ts, nocallbacks) + local e = ilrs.rte_locks[pts] + if not e then return nil + elseif #e==0 then + ilrs.rte_locks[pts] = nil + return nil + end + local i = 1 + while i <= #e do + if e[i].by == ts then + --atdebug("free_route_locks_indiv",pts,"clearing entry",e[i].by,e[i].rsn) + table.remove(e,i) + else + i = i + 1 + end + end + -- This must be delayed, because this code is executed in-between a train step + -- TODO use luaautomation timers? + if not nocallbacks then + minetest.after(0, ilrs.update_waiting, "lck", pts) + minetest.after(0.5, advtrains.set_fallback_state, minetest.string_to_pos(pts)) + end +end +-- frees all route locks, even manual ones set with the tool, at a specific position +function ilrs.remove_route_locks(pts, nocallbacks) + ilrs.rte_locks[pts] = nil + -- This must be delayed, because this code is executed in-between a train step + -- TODO use luaautomation timers? + if not nocallbacks then + minetest.after(0, ilrs.update_waiting, "lck", pts) + end +end + + +-- starting from the designated sigd, clears all subsequent route and route_post +-- information from the track sections. +-- note that this does not clear the routesetting status from the entry signal, +-- only from the ts's +function ilrs.cancel_route_from(sigd) + -- we start at the tc designated by signal + local c_sigd = sigd + local c_tcbs, c_ts_id, c_ts, c_rseg, c_lckp + while c_sigd do + --atdebug("cancel_route_from: at sigd",c_sigd) + c_tcbs = ildb.get_tcbs(c_sigd) + if not c_tcbs then + atwarn("Failed to cancel route, no TCBS at",c_sigd) + return false + end + + --atdebug("cancelling",c_ts.route.rsn) + -- clear signal aspect and routesetting state + c_tcbs.route_committed = nil + c_tcbs.aspect = nil + c_tcbs.routeset = nil + c_tcbs.route_auto = nil + c_tcbs.route_origin = nil + + advtrains.interlocking.update_signal_aspect(c_tcbs) + + c_ts_id = c_tcbs.ts_id + if not c_tcbs then + atwarn("Failed to cancel route, end of interlocking at",c_sigd) + return false + end + c_ts = ildb.get_ts(c_ts_id) + + if not c_ts + or not c_ts.route + or not sigd_equal(c_ts.route.entry, c_sigd) then + --atdebug("cancel_route_from: abort (eoi/no route):") + return false + end + + c_ts.route = nil + + if c_ts.route_post then + advtrains.interlocking.route.free_route_locks(c_ts_id, c_ts.route_post.locks) + c_sigd = c_ts.route_post.next + else + c_sigd = nil + end + c_ts.route_post = nil + minetest.after(0, advtrains.interlocking.route.update_waiting, "ts", c_ts_id) + end + --atdebug("cancel_route_from: done (no final sigd)") + return true +end + +-- TCBS Routesetting helper: generic update function for +-- route setting +-- Call this function to set and cancel routes! +-- sigd, tcbs: self-explanatory +-- newrte: If a new route should be set, the route index of it (in tcbs.routes). nil otherwise +-- cancel: true in combination with newrte=nil causes cancellation of the current route. +function ilrs.update_route(sigd, tcbs, newrte, cancel) + --atdebug("Update_Route for",sigd,tcbs.signal_name) + local has_changed_aspect = false + if tcbs.route_origin and not sigd_equal(tcbs.route_origin, sigd) then + --atdebug("Signal not in control, held by",tcbs.signal_name) + return + end + if (newrte and tcbs.routeset and tcbs.routeset ~= newrte) or cancel then + if tcbs.route_committed then + --atdebug("Cancelling:",tcbs.routeset) + advtrains.interlocking.route.cancel_route_from(sigd) + end + tcbs.route_committed = nil + tcbs.aspect = nil + has_changed_aspect = true + tcbs.routeset = nil + tcbs.route_auto = nil + tcbs.route_rsn = nil + end + if newrte or tcbs.routeset then + if tcbs.route_committed then + return + end + if newrte then tcbs.routeset = newrte end + --atdebug("Setting:",tcbs.routeset) + local succ, rsn, cbts, cblk = ilrs.set_route(sigd, tcbs.routes[tcbs.routeset]) + if not succ then + tcbs.route_rsn = rsn + --atdebug("Routesetting failed:",rsn) + -- add cbts or cblk to callback table + if cbts then + --atdebug("cbts =",cbts) + if not ilrs.rte_callbacks.ts[cbts] then ilrs.rte_callbacks.ts[cbts]={} end + advtrains.insert_once(ilrs.rte_callbacks.ts[cbts], sigd, sigd_equal) + end + if cblk then + --atdebug("cblk =",cblk) + if not ilrs.rte_callbacks.lck[cblk] then ilrs.rte_callbacks.lck[cblk]={} end + advtrains.insert_once(ilrs.rte_callbacks.lck[cblk], sigd, sigd_equal) + end + else + --atdebug("Committed Route:",tcbs.routeset) + has_changed_aspect = true + end + end + if has_changed_aspect then + -- FIX: prevent an minetest.after() loop caused by update_signal_aspect dispatching path invalidation, which in turn calls ARS again + advtrains.interlocking.update_signal_aspect(tcbs) + end + advtrains.interlocking.update_player_forms(sigd) +end + +-- Try to re-set routes that conflicted with this point +-- sys can be one of "ts" and "lck" +-- key is then ts_id or pts respectively +function ilrs.update_waiting(sys, key) + --atdebug("update_waiting:",sys,".",key) + local t = ilrs.rte_callbacks[sys][key] + ilrs.rte_callbacks[sys][key] = nil + if t then + for _,sigd in ipairs(t) do + --atdebug("Updating", sigd) + -- While these are run, the table we cleared before may be populated again, which is in our interest. + -- (that's the reason we needed to copy it) + local tcbs = ildb.get_tcbs(sigd) + if tcbs then + ilrs.update_route(sigd, tcbs) + end + end + end +end + +advtrains.interlocking.route = ilrs + |