aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorY. Wang <yw05@forksworld.de>2022-11-04 11:15:04 +0100
committerY. Wang <yw05@forksworld.de>2023-03-23 20:06:02 +0100
commitba98fa53780bf19266d7e5049fd7ec31eaad18bf (patch)
tree9cd93792726c3a68d591f399df52a64ec434b67e
parentd61c72002008280d70a7489f806d181250747c9a (diff)
Harden type 2 signal group API; add test for type 2 main signals
-rw-r--r--advtrains_interlocking/signal_aspects.lua17
-rw-r--r--advtrains_interlocking/spec/type2_spec.lua103
2 files changed, 115 insertions, 5 deletions
diff --git a/advtrains_interlocking/signal_aspects.lua b/advtrains_interlocking/signal_aspects.lua
index 37af7aa..65e970f 100644
--- a/advtrains_interlocking/signal_aspects.lua
+++ b/advtrains_interlocking/signal_aspects.lua
@@ -9,13 +9,15 @@ local type2defs = {}
local function register_type2(def)
local t = {type = 2}
local name = def.name
- if type2defs[name] then
- return error("Name " .. name .. " already used")
- elseif type(name) ~= "string" then
+ if type(name) ~= "string" then
return error("Name is not a string")
+ elseif type2defs[name] then
+ return error(string.format("Attempt to redefine type 2 signal aspect group %q, previously defined in %s", name, type2defs[name].defined))
end
t.name = name
+ t.defined = debug.getinfo(2, "S").short_src or "[?]"
+
local label = def.label or name
if type(label) ~= "string" then
return error("Label is not a string")
@@ -54,7 +56,12 @@ end
-- @return[1] The definition for the signal group (if present).
-- @return[2] The nil constant (otherwise).
local function get_type2_definition(name)
- return type2defs[name]
+ local t = type2defs[name]
+ if t then
+ return table.copy(t)
+ else
+ return nil
+ end
end
--- Get the name of the distant aspect before the current aspect.
@@ -151,7 +158,7 @@ local function type1_to_type2main(asp, group, shift)
return t_main[math.max(1, idx-(shift or 0))].name
end
---- Compare two signal aspect tables.
+--- Compare two type 1 signal aspect tables.
-- @function equalp
-- @param asp1 The first signal aspect table.
-- @param asp2 The second signal aspect table.
diff --git a/advtrains_interlocking/spec/type2_spec.lua b/advtrains_interlocking/spec/type2_spec.lua
new file mode 100644
index 0000000..514f6aa
--- /dev/null
+++ b/advtrains_interlocking/spec/type2_spec.lua
@@ -0,0 +1,103 @@
+require "mineunit"
+mineunit("core")
+
+_G.advtrains = {
+ interlocking = {
+ aspects = sourcefile("signal_aspects"),
+ },
+ ndb = {
+ get_node = minetest.get_node,
+ swap_node = minetest.swap_node,
+ }
+}
+
+fixture("advtrains_helpers")
+sourcefile("database")
+sourcefile("signal_api")
+sourcefile("distant")
+sourcefile("signal_aspect_accessors")
+
+local A = advtrains.interlocking.aspects
+local D = advtrains.distant
+local I = advtrains.interlocking
+local N = advtrains.ndb
+
+local type2def = {
+ name = "foo",
+ main = {
+ {name = "proceed", main = -1},
+ {name = "caution"},
+ {name = "danger", main = 0},
+ },
+}
+
+local asps = {}
+for _, v in pairs(type2def.main) do
+ minetest.register_node("advtrains_interlocking:" .. v.name, {
+ advtrains = {
+ supported_aspects = {
+ type = 2,
+ group = "foo",
+ },
+ get_aspect = function() return v.name end,
+ set_aspect = function(pos, _, name)
+ N.swap_node(pos, {name = "advtrains_interlocking:" .. name})
+ end,
+ }
+ })
+ asps[v.name] = {
+ main = v.main,
+ type2group = "foo",
+ type2name = v.name,
+ }
+end
+
+local origin = vector.new(0, 0, 0)
+local dstpos = vector.new(0, 0, 1)
+
+world.layout {
+ {origin, "advtrains_interlocking:danger"},
+ {dstpos, "advtrains_interlocking:proceed"},
+}
+
+describe("type 2 signal group registration", function()
+ it("should work", function()
+ A.register_type2(type2def)
+ assert(A.get_type2_definition("foo"))
+ end)
+ it("should only be allowed once for the same group", function()
+ assert.has.errors(function() A.register_type2(type2def) end)
+ end)
+ it("should handle nonexistant groups", function()
+ assert.is_nil(A.get_type2_definition("something_else"))
+ end)
+end)
+
+describe("signal aspect conversion", function()
+ it("should work for converting from type 1 to type 2", function()
+ assert.equal("danger", A.type1_to_type2main({main = 0}, "foo"))
+ assert.equal("caution", A.type1_to_type2main({main = 6}, "foo"))
+ assert.equal("proceed", A.type1_to_type2main({}, "foo"))
+ end)
+ -- Type 2 -> type 1 conversion is tested with signal aspect accessors
+end)
+
+describe("type 2 signals", function()
+ it("should support distant signaling", function()
+ assert.equal("caution", A.get_type2_dst("foo", 3))
+ assert.equal("proceed", A.get_type2_dst("foo", "caution"))
+ assert.equal("proceed", A.get_type2_dst("foo", "proceed"))
+ end)
+ it("should work with accessors", function()
+ assert.same(asps.danger, I.signal_get_aspect(origin))
+ local newasp = {type2group = "foo", type2name = "proceed", main = 6}
+ I.signal_set_aspect(origin, newasp)
+ assert.same(newasp, I.signal_get_aspect(origin))
+ end)
+ it("should work with distant signaling", function()
+ assert.same(asps.proceed, I.signal_get_aspect(dstpos))
+ local dstasp = {type2group = "foo", type2name = "proceed", dst = 6, main = -1}
+ D.assign(origin, dstpos)
+ assert.same(dstasp, I.signal_get_aspect(dstpos))
+ end)
+end)
">.il_sections then train.il_sections = {} end if not itkexist(train.il_sections, "ts_id", ts_id) then table.insert(train.il_sections, {ts_id = ts_id, origin = sigd}) end -- ts if not ts.trains then ts.trains = {} end if not itexist(ts.trains, tid) then table.insert(ts.trains, tid) end -- routes local tcbs = advtrains.interlocking.db.get_tcbs(sigd) -- route setting - clear route state if ts.route then --atdebug(tid,"enters",ts_id,"examining Routestate",ts.route) if not sigd_equal(ts.route.entry, sigd) then -- Train entered not from the route. Locate origin and cancel route! atwarn("Train",tid,"hit route",ts.route.rsn,"!") advtrains.interlocking.route.cancel_route_from(ts.route.origin) atwarn("Route was cancelled.") else -- train entered route regularily. Reset route and signal tcbs.route_committed = nil tcbs.route_comitted = nil -- TODO compatibility cleanup tcbs.aspect = nil tcbs.route_origin = nil advtrains.interlocking.update_signal_aspect(tcbs) if tcbs.signal and sigd_equal(ts.route.entry, ts.route.origin) then if tcbs.route_auto and tcbs.routeset then --atdebug("Resetting route (",ts.route.origin,")") advtrains.interlocking.route.update_route(ts.route.origin, tcbs) else tcbs.routeset = nil end end end ts.route = nil end if tcbs.signal then advtrains.interlocking.route.update_route(sigd, tcbs) end end local function freesection(tid, train, ts_id, ts) -- train if not train.il_sections then train.il_sections = {} end itkremove(train.il_sections, "ts_id", ts_id) -- ts if not ts.trains then ts.trains = {} end itremove(ts.trains, tid) if ts.route_post then advtrains.interlocking.route.free_route_locks(ts_id, ts.route_post.locks) if ts.route_post.next then --this does nothing when the train went the right way, because -- "route" info is already cleared. advtrains.interlocking.route.cancel_route_from(ts.route_post.next) end ts.route_post = nil end -- This must be delayed, because this code is executed in-between a train step -- TODO use luaautomation timers? minetest.after(0, advtrains.interlocking.route.update_waiting, "ts", ts_id) end -- This is regular operation -- The train is on a track and drives back and forth -- This sets the section for both directions, to be failsafe advtrains.tnc_register_on_enter(function(pos, id, train, index) local tcb = ildb.get_tcb(pos) if tcb then for connid=1,2 do local ts = tcb[connid].ts_id and ildb.get_ts(tcb[connid].ts_id) if ts then setsection(id, train, tcb[connid].ts_id, ts, {p=pos, s=connid}) end end end end) -- this time, of course, only clear the backside (cp connid) advtrains.tnc_register_on_leave(function(pos, id, train, index) local tcb = ildb.get_tcb(pos) if tcb and train.path_cp[index] then local connid = train.path_cp[index] local ts = tcb[connid].ts_id and ildb.get_ts(tcb[connid].ts_id) if ts then freesection(id, train, tcb[connid].ts_id, ts) end end end) -- those callbacks are needed to account for created and removed trains (also regarding coupling) advtrains.te_register_on_create(function(id, train) -- let's see what track sections we find here local index = atround(train.index) local pos = advtrains.path_get(train, index) local ts_id, origin = ildb.get_ts_at_pos(pos) if ts_id then local ts = ildb.get_ts(ts_id) if ts then setsection(id, train, ts_id, ts, origin) else atwarn("ILDB corruption: TCB",origin," has invalid TS reference") end -- Make train a shunt move train.is_shunt = true elseif ts_id==nil then atlog("Train",id,": Unable to determine whether to block a track section!") else --atdebug("Train",id,": Outside of interlocked area!") end end) advtrains.te_register_on_remove(function(id, train) if train.il_sections then for idx, item in ipairs(train.il_sections) do local ts = item.ts_id and ildb.get_ts(item.ts_id) if ts and ts.trains then itremove(ts.trains, id) end end train.il_sections = nil end end)