///|
pub(all) struct RouteLockResult {
is_success : Bool
route_id : RouteId
message : String
} derive(Debug)
///|
fn InterlockingTable::rollback_route_resources(
_self : InterlockingTable,
station : @topology.Station,
rule : InterlockingRule,
acquired_turnouts : Array[@topology.TurnoutId],
acquired_tracks : Array[@topology.TrackId],
signal_locked : Bool,
) -> Unit {
let _ = station.set_signal_aspect(rule.entry_signal, Red)
if signal_locked {
let _ = station.unlock_signal(rule.entry_signal)
}
for turnout_id in acquired_turnouts {
let _ = station.unlock_turnout(turnout_id)
}
for track_id in acquired_tracks {
let _ = station.set_track_state(track_id, Clear, "")
}
}
///|
fn InterlockingTable::reset_fast_scratch(self : InterlockingTable) -> Unit {
self.acquired_turnout_count = 0
self.acquired_track_count = 0
}
///|
fn InterlockingTable::rollback_route_resources_fast(
self : InterlockingTable,
station : @topology.Station,
rule : InterlockingRule,
signal_locked : Bool,
) -> Unit {
let _ = station.set_signal_aspect(rule.entry_signal, Red)
if signal_locked {
let _ = station.unlock_signal(rule.entry_signal)
}
let mut turnout_index = 0
while turnout_index < self.acquired_turnout_count {
let _ = station.unlock_turnout(self.acquired_turnout_scratch[turnout_index])
turnout_index = turnout_index + 1
}
let mut track_index = 0
while track_index < self.acquired_track_count {
let _ = station.set_track_state(
self.acquired_track_scratch[track_index],
Clear,
"",
)
track_index = track_index + 1
}
self.reset_fast_scratch()
}
///|
pub fn InterlockingTable::request_and_lock_route_fast(
self : InterlockingTable,
station : @topology.Station,
route_id : RouteId,
) -> Bool {
match self.get_rule(route_id) {
None => false
Some(rule) => {
if self.is_route_reserved(route_id) {
return false
}
match station.graph.get_signal(rule.entry_signal) {
None => return false
Some(signal) => if signal.is_locked { return false }
}
for hostile_id in rule.hostile_route_ids {
if self.is_route_reserved(hostile_id) {
return false
}
}
for track_id in rule.track_sections {
match station.graph.get_track(track_id) {
None => return false
Some(track) =>
if track.state == Occupied ||
track.state == Locked ||
track.state == Fault {
return false
}
}
}
self.reset_fast_scratch()
let mut signal_locked = false
for switch_requirement in rule.switches {
if self.acquired_turnout_count >= 256 {
self.rollback_route_resources_fast(station, rule, signal_locked)
return false
}
match
station.set_turnout_position(
switch_requirement.turnout_id,
switch_requirement.required_position,
) {
Err(_) => {
self.rollback_route_resources_fast(station, rule, signal_locked)
return false
}
Ok(_) => ()
}
match station.lock_turnout(switch_requirement.turnout_id) {
Err(_) => {
self.rollback_route_resources_fast(station, rule, signal_locked)
return false
}
Ok(_) => {
self.acquired_turnout_scratch[self.acquired_turnout_count] = switch_requirement.turnout_id
self.acquired_turnout_count = self.acquired_turnout_count + 1
}
}
}
for flank in rule.flank_switches {
if self.acquired_turnout_count >= 256 {
self.rollback_route_resources_fast(station, rule, signal_locked)
return false
}
match
station.set_turnout_position(
flank.turnout_id,
flank.required_position,
) {
Err(_) => {
self.rollback_route_resources_fast(station, rule, signal_locked)
return false
}
Ok(_) => ()
}
match station.lock_turnout(flank.turnout_id) {
Err(_) => {
self.rollback_route_resources_fast(station, rule, signal_locked)
return false
}
Ok(_) => {
self.acquired_turnout_scratch[self.acquired_turnout_count] = flank.turnout_id
self.acquired_turnout_count = self.acquired_turnout_count + 1
}
}
}
for track_id in rule.track_sections {
if self.acquired_track_count >= 256 {
self.rollback_route_resources_fast(station, rule, signal_locked)
return false
}
match station.set_track_state(track_id, Locked, "") {
Err(_) => {
self.rollback_route_resources_fast(station, rule, signal_locked)
return false
}
Ok(_) => {
self.acquired_track_scratch[self.acquired_track_count] = track_id
self.acquired_track_count = self.acquired_track_count + 1
}
}
}
self.activate_route(route_id, rule)
let safety_summary = self.scan_safety_summary(station)
if !safety_summary.can_clear_signal {
self.deactivate_route(route_id)
self.rollback_route_resources_fast(station, rule, signal_locked)
return false
}
match station.set_signal_aspect(rule.entry_signal, rule.target_aspect) {
Err(_) => {
self.deactivate_route(route_id)
self.rollback_route_resources_fast(station, rule, signal_locked)
return false
}
Ok(_) => ()
}
match station.lock_signal(rule.entry_signal) {
Err(_) => {
self.deactivate_route(route_id)
self.rollback_route_resources_fast(station, rule, signal_locked)
return false
}
Ok(_) => signal_locked = true
}
self.reset_fast_scratch()
true
}
}
}
///|
pub fn InterlockingTable::request_and_lock_route(
self : InterlockingTable,
station : @topology.Station,
route_id : RouteId,
) -> RouteLockResult {
match self.get_rule(route_id) {
None =>
{
is_success: false,
route_id,
message: "Route rule not found: " + route_id.id.to_string(),
}
Some(rule) => {
// 1. Check if route is already active
if self.is_route_reserved(route_id) {
return {
is_success: false,
route_id,
message: "Route " + rule.name + " is already active!",
}
}
match station.graph.get_signal(rule.entry_signal) {
None =>
return {
is_success: false,
route_id,
message: "Entry signal is missing!",
}
Some(signal) =>
if signal.is_locked {
return {
is_success: false,
route_id,
message: "Entry signal is already locked!",
}
}
}
let acquired_turnouts : Array[@topology.TurnoutId] = []
let acquired_tracks : Array[@topology.TrackId] = []
let mut signal_locked = false
// 2. Check hostile route conflicts
for h_id in rule.hostile_route_ids {
if self.is_route_reserved(h_id) {
return {
is_success: false,
route_id,
message: "Hostile route " +
h_id.id.to_string() +
" is active! Route " +
rule.name +
" rejected.",
}
}
}
// 3. Verify all track sections are clear
for tr_id in rule.track_sections {
match station.graph.get_track(tr_id) {
None =>
return {
is_success: false,
route_id,
message: "Track segment " + tr_id.id.to_string() + " missing!",
}
Some(tr) => {
if tr.state == Occupied {
return {
is_success: false,
route_id,
message: "Track " + tr.name + " is OCCUPIED!",
}
}
if tr.state == Fault {
return {
is_success: false,
route_id,
message: "Track " + tr.name + " is FAULTED!",
}
}
if tr.state == Locked {
return {
is_success: false,
route_id,
message: "Track " + tr.name + " is LOCKED by another route!",
}
}
}
}
}
// 4. Throw switches to target positions and lock them
for sw in rule.switches {
let throw_res = station.set_turnout_position(
sw.turnout_id,
sw.required_position,
)
match throw_res {
Err(err) => {
self.rollback_route_resources(
station, rule, acquired_turnouts, acquired_tracks, signal_locked,
)
return {
is_success: false,
route_id,
message: "Failed throwing switch: " + err,
}
}
Ok(_) => ()
}
let lock_res = station.lock_turnout(sw.turnout_id)
match lock_res {
Err(err) => {
self.rollback_route_resources(
station, rule, acquired_turnouts, acquired_tracks, signal_locked,
)
return {
is_success: false,
route_id,
message: "Failed locking switch: " + err,
}
}
Ok(_) => acquired_turnouts.push(sw.turnout_id)
}
}
// 5. Throw and lock flank protection switches
for flank in rule.flank_switches {
let flank_throw = station.set_turnout_position(
flank.turnout_id,
flank.required_position,
)
match flank_throw {
Err(err) => {
self.rollback_route_resources(
station, rule, acquired_turnouts, acquired_tracks, signal_locked,
)
return {
is_success: false,
route_id,
message: "Failed throwing flank protection: " + err,
}
}
Ok(_) => ()
}
let flank_lock = station.lock_turnout(flank.turnout_id)
match flank_lock {
Err(err) => {
self.rollback_route_resources(
station, rule, acquired_turnouts, acquired_tracks, signal_locked,
)
return {
is_success: false,
route_id,
message: "Failed locking flank protection: " + err,
}
}
Ok(_) => acquired_turnouts.push(flank.turnout_id)
}
}
// 6. Lock track sections
for tr_id in rule.track_sections {
match station.set_track_state(tr_id, Locked, "") {
Err(err) => {
self.rollback_route_resources(
station, rule, acquired_turnouts, acquired_tracks, signal_locked,
)
return {
is_success: false,
route_id,
message: "Failed locking track: " + err,
}
}
Ok(_) => acquired_tracks.push(tr_id)
}
}
// 7. Register the tentative active route and run the fixed-field safety
// scan before any proceed aspect is allowed to be displayed.
self.activate_route(route_id, rule)
let safety_summary = self.scan_safety_summary(station)
if !safety_summary.can_clear_signal {
self.deactivate_route(route_id)
self.rollback_route_resources(
station, rule, acquired_turnouts, acquired_tracks, signal_locked,
)
return {
is_success: false,
route_id,
message: "Safety audit rejected route before signal clearing",
}
}
// 8. Open entry signal
let sig_res = station.set_signal_aspect(
rule.entry_signal,
rule.target_aspect,
)
match sig_res {
Err(err) => {
self.deactivate_route(route_id)
self.rollback_route_resources(
station, rule, acquired_turnouts, acquired_tracks, signal_locked,
)
return {
is_success: false,
route_id,
message: "Failed setting entry signal: " + err,
}
}
Ok(_) => ()
}
match station.lock_signal(rule.entry_signal) {
Err(err) => {
self.deactivate_route(route_id)
self.rollback_route_resources(
station, rule, acquired_turnouts, acquired_tracks, signal_locked,
)
return {
is_success: false,
route_id,
message: "Failed locking entry signal: " + err,
}
}
Ok(_) => signal_locked = true
}
{
is_success: true,
route_id,
message: "Route " + rule.name + " successfully locked and signal open!",
}
}
}
}
///|
pub fn InterlockingTable::cancel_route(
self : InterlockingTable,
station : @topology.Station,
route_id : RouteId,
) -> Result[Unit, String] {
match self.get_active_route(route_id) {
None => Err("Route is not active!")
Some(active_route) => {
let rule = active_route.rule
// Set signal to red
let _ = station.set_signal_aspect(rule.entry_signal, Red)
let _ = station.unlock_signal(rule.entry_signal)
// Unlock switches
for sw in rule.switches {
let _ = station.unlock_turnout(sw.turnout_id)
}
for flank in rule.flank_switches {
let _ = station.unlock_turnout(flank.turnout_id)
}
// Unlock tracks
for tr_id in rule.track_sections {
let _ = station.set_track_state(tr_id, Clear, "")
}
active_route.state = RouteInactive
self.deactivate_route(route_id)
Ok(())
}
}
}