///|
pub fn validate_route_transition(
from : RouteState,
to : RouteState,
) -> Result[Unit, String] {
let allowed = match (from, to) {
(RouteInactive, RouteRequested) => true
(RouteRequested, SwitchesThrowing) => true
(RouteRequested, RouteInactive) => true
(SwitchesThrowing, RouteLocked) => true
(SwitchesThrowing, RouteFault) => true
(SwitchesThrowing, RouteInactive) => true
(RouteLocked, ApproachLocked) => true
(RouteLocked, TrainInRoute) => true
(RouteLocked, RouteCancelling) => true
(RouteLocked, RouteFault) => true
(ApproachLocked, TrainInRoute) => true
(ApproachLocked, RouteCancelling) => true
(ApproachLocked, RouteFault) => true
(TrainInRoute, SectionalUnlocking) => true
(TrainInRoute, RouteFault) => true
(SectionalUnlocking, TrainInRoute) => true
(SectionalUnlocking, RouteInactive) => true
(SectionalUnlocking, RouteFault) => true
(RouteCancelling, RouteInactive) => true
(RouteCancelling, RouteFault) => true
(RouteFault, RouteCancelling) => true
(RouteFault, RouteInactive) => true
_ => false
}
if allowed {
Ok(())
} else {
Err(
"illegal route transition: " + from.to_string() + " -> " + to.to_string(),
)
}
}
///|
pub(all) struct RouteLifecycleAudit {
route_id : RouteId
mut is_valid : Bool
mut transition_count : Int
errors : Array[String]
} derive(Debug)
///|
fn RouteLifecycleAudit::new(route_id : RouteId) -> RouteLifecycleAudit {
{ route_id, is_valid: true, transition_count: 0, errors: [] }
}
///|
fn RouteLifecycleAudit::add_error(
self : RouteLifecycleAudit,
message : String,
) -> Unit {
self.errors.push(message)
self.is_valid = false
}
///|
fn ActiveRouteState::has_occupied_section(self : ActiveRouteState) -> Bool {
let mut index = 0
while index < 256 {
if self.occupied_sections[index] {
return true
}
index = index + 1
}
false
}
///|
fn ActiveRouteState::has_invalid_release_history(
self : ActiveRouteState,
) -> Bool {
for track_id in self.rule.track_sections {
if self.was_unlocked(track_id) && !self.was_occupied(track_id) {
return true
}
}
false
}
///|
fn ActiveRouteState::lifecycle_snapshot_is_safe(
self : ActiveRouteState,
) -> Bool {
if self.occupancy_order_violation || self.has_invalid_release_history() {
return false
}
match self.state {
RouteLocked => !self.has_occupied_section()
TrainInRoute | SectionalUnlocking => self.has_occupied_section()
_ => true
}
}
///|
pub fn InterlockingTable::validate_active_route_lifecycle(
self : InterlockingTable,
station : @topology.Station,
route_id : RouteId,
) -> RouteLifecycleAudit {
let audit = RouteLifecycleAudit::new(route_id)
match self.get_active_route(route_id) {
None => audit.add_error("route is not present in active table")
Some(active_route) => {
audit.transition_count = 1
if !active_route.lifecycle_snapshot_is_safe() {
audit.add_error("active route state does not match occupancy history")
}
if active_route.state == RouteFault {
match station.graph.get_signal(active_route.rule.entry_signal) {
Some(signal) =>
if signal.current_aspect != @topology.Red {
audit.add_error("faulted route entry signal is not red")
}
None => audit.add_error("faulted route entry signal is missing")
}
}
}
}
audit
}