Skip to main content

rustsim_transit/
vehicle.rs

1//! Transit vehicles: service instances riding a route.
2
3use crate::route::RouteId;
4use crate::stop::StopId;
5use crate::PassengerId;
6
7/// Stable identifier for a [`TransitVehicle`].
8pub type VehicleId = u64;
9
10/// Operating state of a transit vehicle.
11#[derive(Debug, Clone, Copy, PartialEq, Eq)]
12pub enum VehiclePhase {
13    /// Depot, not yet dispatched.
14    Idle,
15    /// En route between two stops.
16    InTransit,
17    /// Dwelling at a stop.
18    Dwelling,
19    /// Completed its route for the day.
20    Finished,
21}
22
23/// A service instance (one bus, tram, metro unit, …).
24#[derive(Debug, Clone)]
25pub struct TransitVehicle {
26    /// Unique identifier.
27    pub id: VehicleId,
28    /// Which route this vehicle is assigned to.
29    pub route: RouteId,
30    /// Current stop index along the route (0 == first stop).
31    pub current_stop: usize,
32    /// Current operating phase.
33    pub phase: VehiclePhase,
34    /// Maximum seated + standing passengers.
35    pub capacity: u32,
36    /// On-board passengers.
37    pub passengers: Vec<PassengerId>,
38    /// Seconds remaining until the next phase transition.
39    pub time_remaining: f64,
40}
41
42impl TransitVehicle {
43    /// Create an idle vehicle ready for dispatch.
44    pub fn idle(id: VehicleId, route: RouteId, capacity: u32) -> Self {
45        Self {
46            id,
47            route,
48            current_stop: 0,
49            phase: VehiclePhase::Idle,
50            capacity,
51            passengers: Vec::new(),
52            time_remaining: 0.0,
53        }
54    }
55
56    /// Is there room for one more passenger?
57    pub fn has_room(&self) -> bool {
58        (self.passengers.len() as u32) < self.capacity
59    }
60
61    /// Number of free seats/standing room.
62    pub fn free_capacity(&self) -> u32 {
63        self.capacity.saturating_sub(self.passengers.len() as u32)
64    }
65
66    /// Add a passenger. Returns `false` if the vehicle was already full.
67    pub fn add_passenger(&mut self, id: PassengerId) -> bool {
68        if self.has_room() {
69            self.passengers.push(id);
70            true
71        } else {
72            false
73        }
74    }
75
76    /// Remove and return every passenger whose destination is `stop`.
77    /// `destinations` is a slice where `destinations[i]` is the alighting
78    /// stop of `self.passengers[i]`.
79    pub fn alight_at(&mut self, stop: StopId, destinations: &[StopId]) -> Vec<PassengerId> {
80        assert_eq!(self.passengers.len(), destinations.len());
81        let mut out = Vec::new();
82        let mut kept_dests = Vec::new();
83        let mut kept_pax = Vec::new();
84        for (p, d) in self.passengers.iter().zip(destinations.iter()) {
85            if *d == stop {
86                out.push(*p);
87            } else {
88                kept_pax.push(*p);
89                kept_dests.push(*d);
90            }
91        }
92        self.passengers = kept_pax;
93        // NOTE: The caller owns the `destinations` vector; this function
94        // does not mutate it, so the caller should drop alighters from
95        // its own mirror in the same order we did.
96        let _ = kept_dests;
97        out
98    }
99}
100
101#[cfg(test)]
102mod tests {
103    use super::*;
104
105    #[test]
106    fn capacity_tracking() {
107        let mut v = TransitVehicle::idle(1, 42, 3);
108        assert!(v.add_passenger(10));
109        assert!(v.add_passenger(11));
110        assert!(v.add_passenger(12));
111        assert!(!v.add_passenger(13));
112        assert_eq!(v.free_capacity(), 0);
113    }
114
115    #[test]
116    fn alight_removes_targeted_passengers() {
117        let mut v = TransitVehicle::idle(1, 42, 10);
118        v.add_passenger(10);
119        v.add_passenger(11);
120        v.add_passenger(12);
121        let dests = vec![5, 7, 5];
122        let out = v.alight_at(5, &dests);
123        assert_eq!(out, vec![10, 12]);
124        assert_eq!(v.passengers, vec![11]);
125    }
126}