use crate::components::RiderPhase;
use crate::config::{
BuildingConfig, ElevatorConfig, PassengerSpawnConfig, SimConfig, SimulationParams,
};
use crate::dispatch::scan::ScanDispatch;
use crate::error::SimError;
use crate::events::{Event, RouteInvalidReason};
use crate::sim::Simulation;
use crate::stop::{StopConfig, StopId};
fn three_stop_config() -> SimConfig {
SimConfig {
building: BuildingConfig {
name: "Reroute".into(),
stops: vec![
StopConfig {
id: StopId(0),
name: "A".into(),
position: 0.0,
},
StopConfig {
id: StopId(1),
name: "B".into(),
position: 10.0,
},
StopConfig {
id: StopId(2),
name: "C".into(),
position: 20.0,
},
],
lines: None,
groups: None,
},
elevators: vec![ElevatorConfig {
id: 0,
name: "E0".into(),
max_speed: 5.0,
acceleration: 3.0,
deceleration: 3.0,
weight_capacity: 800.0,
starting_stop: StopId(0),
door_open_ticks: 5,
door_transition_ticks: 3,
restricted_stops: Vec::new(),
#[cfg(feature = "energy")]
energy_profile: None,
service_mode: None,
inspection_speed_factor: 0.25,
}],
simulation: SimulationParams {
ticks_per_second: 60.0,
},
passenger_spawning: PassengerSpawnConfig {
mean_interval_ticks: 120,
weight_range: (50.0, 100.0),
},
}
}
#[test]
fn reroute_changes_rider_destination() {
let config = three_stop_config();
let mut sim = Simulation::new(&config, ScanDispatch::new()).unwrap();
let rider = sim
.spawn_rider_by_stop_id(StopId(0), StopId(2), 70.0)
.unwrap();
let stop1 = sim.stop_entity(StopId(1)).unwrap();
sim.reroute(rider, stop1).unwrap();
let route = sim.world().route(rider).unwrap();
assert_eq!(route.current_destination(), Some(stop1));
}
#[test]
fn disable_stop_reroutes_affected_riders() {
let config = three_stop_config();
let mut sim = Simulation::new(&config, ScanDispatch::new()).unwrap();
let rider = sim
.spawn_rider_by_stop_id(StopId(0), StopId(1), 70.0)
.unwrap();
let stop1 = sim.stop_entity(StopId(1)).unwrap();
sim.disable(stop1).unwrap();
sim.drain_events();
let route = sim.world().route(rider).unwrap();
let dest = route.current_destination().unwrap();
let stop2 = sim.stop_entity(StopId(2)).unwrap();
assert_eq!(dest, stop2);
}
#[test]
fn disable_stop_emits_route_invalidated_event() {
let config = three_stop_config();
let mut sim = Simulation::new(&config, ScanDispatch::new()).unwrap();
sim.spawn_rider_by_stop_id(StopId(0), StopId(1), 70.0)
.unwrap();
let stop1 = sim.stop_entity(StopId(1)).unwrap();
sim.disable(stop1).unwrap();
let events = sim.drain_events();
let invalidated: Vec<_> = events
.iter()
.filter(|e| matches!(e, Event::RouteInvalidated { .. }))
.collect();
assert_eq!(invalidated.len(), 1);
if let Event::RouteInvalidated { reason, .. } = invalidated[0] {
assert_eq!(*reason, RouteInvalidReason::StopDisabled);
}
}
#[test]
fn disable_only_stop_causes_abandonment() {
let config = SimConfig {
building: BuildingConfig {
name: "Two".into(),
stops: vec![
StopConfig {
id: StopId(0),
name: "A".into(),
position: 0.0,
},
StopConfig {
id: StopId(1),
name: "B".into(),
position: 10.0,
},
],
lines: None,
groups: None,
},
elevators: vec![ElevatorConfig {
id: 0,
name: "E0".into(),
max_speed: 5.0,
acceleration: 3.0,
deceleration: 3.0,
weight_capacity: 800.0,
starting_stop: StopId(0),
door_open_ticks: 5,
door_transition_ticks: 3,
restricted_stops: Vec::new(),
#[cfg(feature = "energy")]
energy_profile: None,
service_mode: None,
inspection_speed_factor: 0.25,
}],
simulation: SimulationParams {
ticks_per_second: 60.0,
},
passenger_spawning: PassengerSpawnConfig {
mean_interval_ticks: 120,
weight_range: (50.0, 100.0),
},
};
let mut sim = Simulation::new(&config, ScanDispatch::new()).unwrap();
let rider = sim
.spawn_rider_by_stop_id(StopId(0), StopId(1), 70.0)
.unwrap();
let stop1 = sim.stop_entity(StopId(1)).unwrap();
sim.disable(stop1).unwrap();
let r = sim.world().rider(rider).unwrap();
assert_eq!(r.phase, RiderPhase::Abandoned);
let events = sim.drain_events();
let invalidated_count = events
.iter()
.filter(|e| {
matches!(
e,
Event::RouteInvalidated {
reason: RouteInvalidReason::NoAlternative,
..
}
)
})
.count();
assert_eq!(invalidated_count, 1);
}
#[test]
fn set_rider_route_replaces_route() {
use crate::components::{Route, RouteLeg, TransportMode};
use crate::ids::GroupId;
let config = three_stop_config();
let mut sim = Simulation::new(&config, ScanDispatch::new()).unwrap();
let rider = sim
.spawn_rider_by_stop_id(StopId(0), StopId(2), 70.0)
.unwrap();
let stop0 = sim.stop_entity(StopId(0)).unwrap();
let stop1 = sim.stop_entity(StopId(1)).unwrap();
let stop2 = sim.stop_entity(StopId(2)).unwrap();
let route = Route {
legs: vec![
RouteLeg {
from: stop0,
to: stop1,
via: TransportMode::Group(GroupId(0)),
},
RouteLeg {
from: stop1,
to: stop2,
via: TransportMode::Group(GroupId(0)),
},
],
current_leg: 0,
};
sim.set_rider_route(rider, route).unwrap();
let r = sim.world().route(rider).unwrap();
assert_eq!(r.legs.len(), 2);
assert_eq!(r.current_destination(), Some(stop1));
}
#[test]
fn reroute_rejects_non_waiting_rider() {
let config = three_stop_config();
let mut sim = Simulation::new(&config, ScanDispatch::new()).unwrap();
let rider = sim
.spawn_rider_by_stop_id(StopId(0), StopId(2), 70.0)
.unwrap();
for _ in 0..500 {
sim.step();
let phase = sim.world().rider(rider).unwrap().phase;
if matches!(phase, RiderPhase::Riding(_) | RiderPhase::Arrived) {
break;
}
}
let stop1 = sim.stop_entity(StopId(1)).unwrap();
let result = sim.reroute(rider, stop1);
let phase = sim.world().rider(rider).unwrap().phase;
if phase != RiderPhase::Waiting {
assert!(result.is_err());
assert!(matches!(result.unwrap_err(), SimError::InvalidState { .. }));
}
}
#[test]
fn reroute_nonexistent_rider_returns_error() {
let config = three_stop_config();
let mut sim = Simulation::new(&config, ScanDispatch::new()).unwrap();
let stop1 = sim.stop_entity(StopId(1)).unwrap();
let result = sim.reroute(stop1, stop1);
assert!(result.is_err());
}