Skip to main content

rustsim_transit/
boarding.rs

1//! Boarding queues and FIFO service discipline at transit stops.
2
3use std::collections::VecDeque;
4
5use crate::policy::{BoardingPolicy, FifoBoardingPolicy};
6use crate::stop::StopId;
7use crate::vehicle::TransitVehicle;
8use crate::PassengerId;
9
10/// A waiting passenger with an intended alighting stop.
11#[derive(Debug, Clone, Copy, PartialEq)]
12pub struct Waiter {
13    /// Passenger identifier.
14    pub passenger: PassengerId,
15    /// Stop at which this passenger intends to alight.
16    pub destination: StopId,
17    /// Simulation time at which the passenger arrived at the stop.
18    pub arrived_at: f64,
19}
20
21/// FIFO boarding queue for a single stop.
22#[derive(Debug, Clone, Default)]
23pub struct Boarding {
24    /// Queued passengers.
25    queue: VecDeque<Waiter>,
26}
27
28/// Outcome of a single boarding attempt.
29#[derive(Debug, Clone, PartialEq)]
30pub struct BoardingResult {
31    /// Number of passengers that boarded this vehicle at this stop.
32    pub boarded: u32,
33    /// Number of passengers that stayed at the stop (because the vehicle
34    /// was full or served an incompatible destination).
35    pub passed_over: u32,
36}
37
38impl Boarding {
39    /// New empty queue.
40    pub fn new() -> Self {
41        Self::default()
42    }
43
44    /// Enqueue a waiter.
45    pub fn enqueue(&mut self, waiter: Waiter) {
46        self.queue.push_back(waiter);
47    }
48
49    /// Number of waiters.
50    pub fn len(&self) -> usize {
51        self.queue.len()
52    }
53
54    /// True if no passengers are waiting.
55    pub fn is_empty(&self) -> bool {
56        self.queue.is_empty()
57    }
58
59    /// Board a vehicle from the front of the queue. Only waiters whose
60    /// `destination` is in `served_stops` board; others are left at the
61    /// front of the queue (they will wait for a different service).
62    ///
63    /// Returns a summary of how many boarded vs passed over.
64    pub fn board_vehicle(
65        &mut self,
66        vehicle: &mut TransitVehicle,
67        served_stops: &[StopId],
68        passenger_destinations: &mut Vec<StopId>,
69    ) -> BoardingResult {
70        self.board_vehicle_with_policy(
71            vehicle,
72            served_stops,
73            passenger_destinations,
74            &FifoBoardingPolicy::default(),
75        )
76    }
77
78    /// Board a vehicle using an explicit boarding policy.
79    pub fn board_vehicle_with_policy<P: BoardingPolicy>(
80        &mut self,
81        vehicle: &mut TransitVehicle,
82        served_stops: &[StopId],
83        passenger_destinations: &mut Vec<StopId>,
84        policy: &P,
85    ) -> BoardingResult {
86        let mut boarded: u32 = 0;
87        let mut passed: u32 = 0;
88
89        // Walk the queue once. Boardable waiters are popped; non-serviced
90        // waiters are rotated to the back so the next vehicle can pick them
91        // up. The rotation is bounded by the initial queue size.
92        let initial = self.queue.len();
93        for _ in 0..initial {
94            if !vehicle.has_room() {
95                break;
96            }
97            if policy
98                .max_boardings()
99                .is_some_and(|max_boardings| boarded >= max_boardings)
100            {
101                break;
102            }
103            let Some(w) = self.queue.pop_front() else {
104                break;
105            };
106            if policy.may_board(&w, vehicle, served_stops) {
107                vehicle.passengers.push(w.passenger);
108                passenger_destinations.push(w.destination);
109                boarded += 1;
110            } else {
111                self.queue.push_back(w);
112                passed += 1;
113            }
114        }
115
116        BoardingResult {
117            boarded,
118            passed_over: passed,
119        }
120    }
121}
122
123#[cfg(test)]
124mod tests {
125    use super::*;
126
127    #[test]
128    fn boarding_matches_served_destinations() {
129        let mut q = Boarding::new();
130        q.enqueue(Waiter {
131            passenger: 10,
132            destination: 3,
133            arrived_at: 0.0,
134        });
135        q.enqueue(Waiter {
136            passenger: 11,
137            destination: 99, // unserved
138            arrived_at: 0.0,
139        });
140        q.enqueue(Waiter {
141            passenger: 12,
142            destination: 4,
143            arrived_at: 0.0,
144        });
145
146        let mut v = TransitVehicle::idle(1, 1, 10);
147        let mut dests = Vec::new();
148        let r = q.board_vehicle(&mut v, &[3, 4, 5], &mut dests);
149
150        assert_eq!(r.boarded, 2);
151        assert_eq!(r.passed_over, 1);
152        assert_eq!(v.passengers, vec![10, 12]);
153        assert_eq!(dests, vec![3, 4]);
154        assert_eq!(q.len(), 1);
155    }
156
157    #[test]
158    fn full_vehicle_stops_boarding() {
159        let mut q = Boarding::new();
160        for i in 0..5 {
161            q.enqueue(Waiter {
162                passenger: i,
163                destination: 3,
164                arrived_at: 0.0,
165            });
166        }
167        let mut v = TransitVehicle::idle(1, 1, 2);
168        let mut dests = Vec::new();
169        let r = q.board_vehicle(&mut v, &[3], &mut dests);
170        assert_eq!(r.boarded, 2);
171        assert_eq!(q.len(), 3);
172    }
173}