sim/mechanics/queue.rs
1use std::collections::{BTreeSet, HashMap, VecDeque};
2
3use serde::{Deserialize, Serialize};
4
5use abstutil::FixedMap;
6use geom::{Distance, Time};
7use map_model::{Map, Position, Traversable};
8
9use crate::mechanics::car::{Car, CarState};
10use crate::{CarID, VehicleType, FOLLOWING_DISTANCE};
11
12/// A Queue of vehicles on a single lane or turn. This is where
13/// https://a-b-street.github.io/docs/tech/trafficsim/discrete_event.html#exact-positions is
14/// implemented.
15///
16/// Some helpful pieces of terminology:
17///
18/// - a "laggy head" is a vehicle whose front is now past the end of this queue, but whose back may
19/// still be partially in the queue. The position of the first car in the queue is still bounded
20/// by the laggy head's back.
21/// - a "static blockage" is due to a vehicle exiting a driveway and immediately cutting across a
22/// few lanes. The "static" part means it occupies a fixed interval of distance in the queue. When
23/// the vehicle is finished exiting the driveway, this blockage is removed.
24/// - a "dynamic blockage" is due to a vehicle changing lanes in the middle of the queue. The exact
25/// position of the blockage in this queue is unknown (it depends on the target queue). The
26/// blockage just occupies the length of the vehicle and keeps following whatever's in front of
27/// it.
28/// - "active cars" are the main members of the queue -- everything except for laggy heads and
29/// blockages.
30#[derive(Serialize, Deserialize, Clone, Debug)]
31pub(crate) struct Queue {
32 pub id: Traversable,
33 members: VecDeque<Queued>,
34 /// This car's back is still partly in this queue.
35 pub laggy_head: Option<CarID>,
36
37 /// How long the lane or turn physically is.
38 pub geom_len: Distance,
39 /// When a car's turn is accepted, reserve the vehicle length + FOLLOWING_DISTANCE for the
40 /// target lane. When the car completely leaves (stops being the laggy_head), free up that
41 /// space. To prevent blocking the box for possibly scary amounts of time, allocate some of
42 /// this length first. This is unused for turns themselves. This value can exceed geom_len
43 /// (for the edge case of ONE long car on a short queue).
44 pub reserved_length: Distance,
45}
46
47/// A member of a `Queue`.
48#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)]
49pub enum Queued {
50 /// A regular vehicle trying to move forwards
51 Vehicle(CarID),
52 /// Something occupying a fixed interval of distance on the queue
53 StaticBlockage {
54 /// This vehicle is exiting a driveway and cutting across a few lanes
55 cause: CarID,
56 front: Distance,
57 back: Distance,
58 },
59 /// This follows whatever's in front of it
60 DynamicBlockage {
61 /// This vehicle is in the middle of changing lanes
62 cause: CarID,
63 vehicle_len: Distance,
64 },
65}
66
67/// The exact position of something in a `Queue` at some time
68#[derive(Clone, Debug)]
69pub struct QueueEntry {
70 pub member: Queued,
71 pub front: Distance,
72 /// Not including FOLLOWING_DISTANCE
73 pub back: Distance,
74}
75
76impl Queue {
77 pub fn new(id: Traversable, map: &Map) -> Queue {
78 Queue {
79 id,
80 members: VecDeque::new(),
81 laggy_head: None,
82 geom_len: id.get_polyline(map).length(),
83 reserved_length: Distance::ZERO,
84 }
85 }
86
87 /// Get the front of the last car in the queue.
88 pub fn get_last_car_position(
89 &self,
90 now: Time,
91 cars: &FixedMap<CarID, Car>,
92 queues: &HashMap<Traversable, Queue>,
93 ) -> Option<(CarID, Distance)> {
94 self.inner_get_last_car_position(now, cars, queues, &mut BTreeSet::new(), None)
95 }
96
97 /// Return the exact position of each member of the queue. The farthest along (greatest distance) is first.
98 pub fn get_car_positions(
99 &self,
100 now: Time,
101 cars: &FixedMap<CarID, Car>,
102 queues: &HashMap<Traversable, Queue>,
103 ) -> Vec<QueueEntry> {
104 let mut all_cars = vec![];
105 self.inner_get_last_car_position(
106 now,
107 cars,
108 queues,
109 &mut BTreeSet::new(),
110 Some(&mut all_cars),
111 );
112 all_cars
113 }
114
115 /// Returns the front of the last car in the queue, only if the last member is an active car.
116 fn inner_get_last_car_position(
117 &self,
118 now: Time,
119 cars: &FixedMap<CarID, Car>,
120 queues: &HashMap<Traversable, Queue>,
121 recursed_queues: &mut BTreeSet<Traversable>,
122 mut intermediate_results: Option<&mut Vec<QueueEntry>>,
123 ) -> Option<(CarID, Distance)> {
124 if self.members.is_empty() {
125 return None;
126 }
127
128 // TODO Consider simplifying this loop's structure. Calculate the bound here before
129 // starting the loop, handling the laggy head case.
130 let mut previous: Option<QueueEntry> = None;
131 for queued in self.members.iter().cloned() {
132 let bound = match previous {
133 Some(entry) => entry.back - FOLLOWING_DISTANCE,
134 None => match self.laggy_head {
135 Some(id) => {
136 // The simple but broken version:
137 //self.geom_len - cars[&id].vehicle.length - FOLLOWING_DISTANCE
138
139 // The expensive case. We need to figure out exactly where the laggy head
140 // is on their queue.
141 let leader = &cars[&id];
142
143 // But don't create a cycle!
144 let recurse_to = leader.router.head();
145 if recursed_queues.contains(&recurse_to) {
146 // See the picture in
147 // https://github.com/a-b-street/abstreet/issues/30. We have two
148 // extremes to break the cycle.
149 //
150 // 1) Hope that the last person in this queue isn't bounded by the
151 // agent in front of them yet. geom_len
152 // 2) Assume the leader has advanced minimally into the next lane.
153 // geom_len - laggy head's length - FOLLOWING_DISTANCE.
154 //
155 // For now, optimistically assume 1. If we're wrong, consequences could
156 // be queue spillover (we're too optimistic about the number of
157 // vehicles that can fit on a lane) or cars jumping positions slightly
158 // while the cycle occurs.
159 self.geom_len
160 } else {
161 recursed_queues.insert(recurse_to);
162
163 let (head, head_dist) = queues[&leader.router.head()]
164 .inner_get_last_car_position(
165 now,
166 cars,
167 queues,
168 recursed_queues,
169 None,
170 )
171 .unwrap();
172 assert_eq!(head, id);
173
174 let mut dist_away_from_this_queue = head_dist;
175 for on in &leader.last_steps {
176 if *on == self.id {
177 break;
178 }
179 dist_away_from_this_queue += queues[on].geom_len;
180 }
181 // They might actually be out of the way, but laggy_head hasn't been
182 // updated yet.
183 if dist_away_from_this_queue
184 < leader.vehicle.length + FOLLOWING_DISTANCE
185 {
186 self.geom_len
187 - (cars[&id].vehicle.length - dist_away_from_this_queue)
188 - FOLLOWING_DISTANCE
189 } else {
190 self.geom_len
191 }
192 }
193 }
194 None => self.geom_len,
195 },
196 };
197
198 // There's spillover and a car shouldn't have been able to enter yet.
199 if bound < Distance::ZERO {
200 if let Some(intermediate_results) = intermediate_results {
201 dump_cars(intermediate_results, cars, self.id, now);
202 }
203 panic!(
204 "Queue has spillover on {} at {} -- can't draw {:?}, bound is {}. Laggy head is \
205 {:?}. This is usually a geometry bug; check for duplicate roads going \
206 between the same intersections.",
207 self.id, now, queued, bound, self.laggy_head
208 );
209 }
210
211 let entry = match queued {
212 Queued::Vehicle(id) => {
213 let car = &cars[&id];
214 let front = match car.state {
215 CarState::Queued { .. } => {
216 if car.router.last_step() {
217 car.router.get_end_dist().min(bound)
218 } else {
219 bound
220 }
221 }
222 CarState::WaitingToAdvance { .. } => {
223 if bound != self.geom_len {
224 if let Some(intermediate_results) = intermediate_results {
225 dump_cars(intermediate_results, cars, self.id, now);
226 }
227 panic!("{} is waiting to advance on {}, but the current bound is {}, not geom_len {}. How can anything be in front of them?", id, self.id, bound, self.geom_len);
228 }
229 self.geom_len
230 }
231 CarState::Crossing {
232 ref time_int,
233 ref dist_int,
234 ..
235 } => {
236 // TODO Why percent_clamp_end? We process car updates in any order, so we might
237 // calculate this before moving this car from Crossing to another state.
238 dist_int.lerp(time_int.percent_clamp_end(now)).min(bound)
239 }
240 CarState::ChangingLanes {
241 ref new_time,
242 ref new_dist,
243 ..
244 } => {
245 // Same as the Crossing logic
246 new_dist.lerp(new_time.percent_clamp_end(now)).min(bound)
247 }
248 CarState::Unparking { front, .. } => front,
249 CarState::Parking(front, _, _) => front,
250 CarState::IdlingAtStop(front, _) => front,
251 };
252 QueueEntry {
253 member: queued,
254 front,
255 back: front - car.vehicle.length,
256 }
257 }
258 Queued::StaticBlockage { front, back, .. } => QueueEntry {
259 member: queued,
260 front,
261 back,
262 },
263 Queued::DynamicBlockage { vehicle_len, .. } => QueueEntry {
264 member: queued,
265 // This is a reasonable guess, because a vehicle only starts changing lanes if
266 // there's something slower in front of it. So we assume that slow vehicle
267 // continues to exist for the 1 second that lane-changing takes. If for some
268 // reason that slower leader vanishes, this bound could jump up, which just
269 // causes anything following the lane-changing vehicle to be able to go a
270 // little faster.
271 front: bound,
272 back: bound - vehicle_len,
273 },
274 };
275
276 if let Some(ref mut intermediate_results) = intermediate_results {
277 intermediate_results.push(entry.clone());
278 }
279 previous = Some(entry);
280 }
281 // Enable to detect possible bugs, but save time otherwise
282 if false {
283 if let Some(intermediate_results) = intermediate_results {
284 validate_positions(intermediate_results, cars, now, self.id)
285 }
286 }
287
288 let previous = previous?;
289 match previous.member {
290 Queued::Vehicle(car) => Some((car, previous.front)),
291 Queued::StaticBlockage { .. } => None,
292 Queued::DynamicBlockage { .. } => None,
293 }
294 }
295
296 /// If the specified car can appear in the queue, return the position in the queue to do so.
297 pub fn get_idx_to_insert_car(
298 &self,
299 start_dist: Distance,
300 vehicle_len: Distance,
301 now: Time,
302 cars: &FixedMap<CarID, Car>,
303 queues: &HashMap<Traversable, Queue>,
304 ) -> Option<usize> {
305 if self.laggy_head.is_none() && self.members.is_empty() {
306 return Some(0);
307 }
308
309 let dists = self.get_car_positions(now, cars, queues);
310 // TODO Binary search
311 let idx = match dists.iter().position(|entry| start_dist >= entry.front) {
312 Some(i) => i,
313 None => dists.len(),
314 };
315
316 // Nope, there's not actually room at the front right now.
317 // (This is overly conservative; we could figure out exactly where the laggy head is and
318 // maybe allow it.)
319 if idx == 0 {
320 if let Some(c) = self.laggy_head {
321 // We don't know exactly where the laggy head is. So assume the worst case; that
322 // they've just barely started the turn, and we have to use the same
323 // too-close-to-leader math.
324 //
325 // TODO We can be more precise! We already call get_car_positions, and that
326 // calculates exactly where the laggy head is. We just need to plumb that bound
327 // back here.
328 if self.geom_len - cars[&c].vehicle.length - FOLLOWING_DISTANCE < start_dist {
329 return None;
330 }
331 }
332 }
333
334 // Are we too close to the leader?
335 if idx != 0 && dists[idx - 1].back - FOLLOWING_DISTANCE < start_dist {
336 return None;
337 }
338 // Or the follower?
339 if idx != dists.len() && start_dist - vehicle_len - FOLLOWING_DISTANCE < dists[idx].front {
340 return None;
341 }
342
343 Some(idx)
344 }
345
346 /// Record that a car has entered a queue at a position. This must match get_idx_to_insert_car
347 /// -- the same index and immediately after passing that query.
348 pub fn insert_car_at_idx(&mut self, idx: usize, car: &Car) {
349 self.members.insert(idx, Queued::Vehicle(car.vehicle.id));
350 self.reserved_length += car.vehicle.length + FOLLOWING_DISTANCE;
351 }
352
353 /// Record that a car has entered a queue at the end. It's assumed that try_to_reserve_entry
354 /// has already happened.
355 pub fn push_car_onto_end(&mut self, car: CarID) {
356 self.members.push_back(Queued::Vehicle(car));
357 }
358
359 /// Change the first car in the queue to the laggy head, indicating that it's front has left
360 /// the queue, but its back is still there. Return that car.
361 pub fn move_first_car_to_laggy_head(&mut self) -> CarID {
362 assert!(self.laggy_head.is_none());
363 let car = match self.members.pop_front() {
364 Some(Queued::Vehicle(c)) => c,
365 x => {
366 panic!(
367 "First member of {} is {:?}, not an active vehicle",
368 self.id, x
369 );
370 }
371 };
372 self.laggy_head = Some(car);
373 car
374 }
375
376 /// If true, there's room and the car must actually start the turn (because the space is
377 /// reserved).
378 pub fn try_to_reserve_entry(&mut self, car: &Car, force_entry: bool) -> bool {
379 // If self.reserved_length >= self.geom_len, then the lane is already full. Normally we
380 // won't allow more cars to start a turn towards it, but if force_entry is true, then we'll
381 // allow it.
382
383 // Sometimes a car + FOLLOWING_DISTANCE might be longer than the geom_len entirely. In that
384 // case, it just means the car won't totally fit on the queue at once, which is fine.
385 // Reserve the normal amount of space; the next car trying to enter will get rejected.
386 // Also allow this don't-block-the-box prevention to be disabled.
387 if self.room_for_car(car) || force_entry {
388 self.reserved_length += car.vehicle.length + FOLLOWING_DISTANCE;
389 return true;
390 }
391 false
392 }
393
394 /// True if the reserved length exceeds the physical length. This means a vehicle is headed
395 /// towards the queue already and is expected to not fit entirely inside.
396 pub fn is_overflowing(&self) -> bool {
397 self.reserved_length >= self.geom_len
398 }
399
400 /// Can a car start a turn for this queue?
401 pub fn room_for_car(&self, car: &Car) -> bool {
402 self.reserved_length == Distance::ZERO
403 || self.reserved_length + car.vehicle.length + FOLLOWING_DISTANCE < self.geom_len
404 }
405
406 /// Once a car has fully exited a queue, free up the space it was reserving.
407 pub fn free_reserved_space(&mut self, car: &Car) {
408 self.reserved_length -= car.vehicle.length + FOLLOWING_DISTANCE;
409 assert!(
410 self.reserved_length >= Distance::ZERO,
411 "invalid reserved length: {:?}, car: {:?}",
412 self.reserved_length,
413 car
414 );
415 }
416
417 /// Return a penalty for entering this queue, as opposed to some adjacent ones. Used for
418 /// lane-changing. (number of vehicles, is there a bike here)
419 pub fn target_lane_penalty(&self) -> (usize, usize) {
420 let mut num_vehicles = self.members.len();
421 if self.laggy_head.is_some() {
422 num_vehicles += 1;
423 }
424
425 let bike_cost = if self
426 .members
427 .iter()
428 .any(|x| matches!(x, Queued::Vehicle(c) if c.vehicle_type == VehicleType::Bike))
429 || self
430 .laggy_head
431 .map(|c| c.vehicle_type == VehicleType::Bike)
432 .unwrap_or(false)
433 {
434 1
435 } else {
436 0
437 };
438
439 (num_vehicles, bike_cost)
440 }
441
442 /// Find the vehicle in front of the specified input. None if the specified vehicle isn't
443 /// ACTIVE (not a blockage) in the queue at all, or they're the front (with or without a laggy
444 /// head).
445 pub fn get_leader(&self, id: CarID) -> Option<CarID> {
446 let mut leader = None;
447 for queued in &self.members {
448 match queued {
449 Queued::Vehicle(car) => {
450 if *car == id {
451 return leader;
452 }
453 leader = Some(*car);
454 }
455 Queued::StaticBlockage { .. } | Queued::DynamicBlockage { .. } => {
456 leader = None;
457 }
458 }
459 }
460 None
461 }
462
463 /// Record that a car is blocking a static portion of the queue (from front to back). Must use
464 /// the index from can_block_from_driveway.
465 pub fn add_static_blockage(
466 &mut self,
467 cause: CarID,
468 front: Distance,
469 back: Distance,
470 idx: usize,
471 ) {
472 assert!(front > back);
473 assert!(back >= FOLLOWING_DISTANCE);
474 let vehicle_len = front - back;
475 self.members
476 .insert(idx, Queued::StaticBlockage { cause, front, back });
477 self.reserved_length += vehicle_len + FOLLOWING_DISTANCE;
478 }
479
480 /// Record that a car is no longer blocking a static portion of the queue.
481 pub fn clear_static_blockage(&mut self, caused_by: CarID, idx: usize) {
482 let blockage = self.members.remove(idx).unwrap();
483 match blockage {
484 Queued::StaticBlockage { front, back, cause } => {
485 assert_eq!(caused_by, cause);
486 let vehicle_len = front - back;
487 self.reserved_length -= vehicle_len + FOLLOWING_DISTANCE;
488 }
489 _ => unreachable!(),
490 }
491 }
492
493 /// Record that a car is starting to change lanes away from this queue.
494 pub fn replace_car_with_dynamic_blockage(&mut self, car: &Car, idx: usize) {
495 self.remove_car_from_idx(car.vehicle.id, idx);
496 self.members.insert(
497 idx,
498 Queued::DynamicBlockage {
499 cause: car.vehicle.id,
500 vehicle_len: car.vehicle.length,
501 },
502 );
503 // We don't need to touch reserved_length -- it's still vehicle_len + FOLLOWING_DISTANCE
504 }
505
506 /// Record that a car is no longer blocking a dynamic portion of the queue.
507 pub fn clear_dynamic_blockage(&mut self, caused_by: CarID, idx: usize) {
508 let blockage = self.members.remove(idx).unwrap();
509 match blockage {
510 Queued::DynamicBlockage { cause, vehicle_len } => {
511 assert_eq!(caused_by, cause);
512 self.reserved_length -= vehicle_len + FOLLOWING_DISTANCE;
513 }
514 _ => unreachable!(),
515 }
516 }
517
518 /// True if a static blockage can be inserted into the queue without anything already there
519 /// intersecting it. Returns the index if so. The position represents the front of the
520 /// blockage.
521 pub fn can_block_from_driveway(
522 &self,
523 pos: &Position,
524 vehicle_len: Distance,
525 now: Time,
526 cars: &FixedMap<CarID, Car>,
527 queues: &HashMap<Traversable, Queue>,
528 ) -> Option<usize> {
529 self.get_idx_to_insert_car(pos.dist_along(), vehicle_len, now, cars, queues)
530 }
531
532 /// Get all cars in the queue, not including the laggy head or blockages.
533 ///
534 /// TODO Do NOT use this for calculating indices or getting the leader/follower. Might be safer
535 /// to just hide this and only expose number of active cars, first, and last.
536 pub fn get_active_cars(&self) -> Vec<CarID> {
537 self.members
538 .iter()
539 .filter_map(|x| match x {
540 Queued::Vehicle(c) => Some(*c),
541 Queued::StaticBlockage { .. } => None,
542 Queued::DynamicBlockage { .. } => None,
543 })
544 .collect()
545 }
546
547 /// Remove a car from a position. Need to separately do free_reserved_space.
548 pub fn remove_car_from_idx(&mut self, car: CarID, idx: usize) {
549 assert_eq!(self.members.remove(idx), Some(Queued::Vehicle(car)));
550 }
551
552 /// If a car thinks it's reached the end of the queue, double check. Blockages or laggy heads
553 /// might be in the way.
554 pub fn is_car_at_front(&self, car: CarID) -> bool {
555 self.laggy_head.is_none() && self.members.get(0) == Some(&Queued::Vehicle(car))
556 }
557}
558
559fn validate_positions(
560 dists: &[QueueEntry],
561 cars: &FixedMap<CarID, Car>,
562 now: Time,
563 id: Traversable,
564) {
565 for pair in dists.windows(2) {
566 if pair[0].back - FOLLOWING_DISTANCE < pair[1].front {
567 dump_cars(dists, cars, id, now);
568 panic!(
569 "get_car_positions wound up with bad positioning: {} then {}\n{:?}",
570 pair[0].front, pair[1].front, dists
571 );
572 }
573 }
574}
575
576fn dump_cars(dists: &[QueueEntry], cars: &FixedMap<CarID, Car>, id: Traversable, now: Time) {
577 println!("\nOn {} at {}...", id, now);
578 for entry in dists {
579 println!("- {:?} @ {}..{}", entry.member, entry.front, entry.back);
580 match entry.member {
581 Queued::Vehicle(id) => match cars[&id].state {
582 CarState::Crossing {
583 ref time_int,
584 ref dist_int,
585 ..
586 } => {
587 println!(
588 " Going {} .. {} during {} .. {}",
589 dist_int.start, dist_int.end, time_int.start, time_int.end
590 );
591 }
592 CarState::ChangingLanes {
593 ref new_time,
594 ref new_dist,
595 ..
596 } => {
597 println!(
598 " Going {} .. {} during {} .. {}, also in the middle of lane-changing",
599 new_dist.start, new_dist.end, new_time.start, new_time.end
600 );
601 }
602 CarState::Queued { .. } => {
603 println!(" Queued currently");
604 }
605 CarState::WaitingToAdvance { .. } => {
606 println!(" WaitingToAdvance currently");
607 }
608 CarState::Unparking { ref time_int, .. } => {
609 println!(" Unparking during {} .. {}", time_int.start, time_int.end);
610 }
611 CarState::Parking(_, _, ref time_int) => {
612 println!(" Parking during {} .. {}", time_int.start, time_int.end);
613 }
614 CarState::IdlingAtStop(_, ref time_int) => {
615 println!(" Idling during {} .. {}", time_int.start, time_int.end);
616 }
617 },
618 Queued::StaticBlockage { cause, .. } => {
619 println!(" Static blockage by {}", cause);
620 }
621 Queued::DynamicBlockage { cause, vehicle_len } => {
622 println!(" Dynamic blockage of length {} by {}", vehicle_len, cause);
623 }
624 }
625 }
626 println!();
627}