elevator_core/components/destination_queue.rs
1//! Per-elevator destination queue (FIFO of target stop `EntityId`s).
2//!
3//! Games can push stops to the back or front of the queue, or clear it
4//! entirely, without writing a custom `DispatchStrategy`. This is the
5//! imperative-dispatch escape hatch for scripted scenarios.
6//!
7//! The built-in dispatch also writes to the queue (via
8//! [`Simulation::push_destination`](crate::sim::Simulation::push_destination)),
9//! so the queue is always in sync with the elevator's current target.
10
11use crate::entity::EntityId;
12use serde::{Deserialize, Serialize};
13
14/// FIFO queue of target stop `EntityId`s for a single elevator.
15///
16/// Adjacent duplicates are collapsed on push:
17///
18/// - `push_back` is a no-op if the *last* entry already equals the new stop.
19/// - `push_front` is a no-op if the *first* entry already equals the new stop.
20///
21/// Games interact with the queue via
22/// [`Simulation::push_destination`](crate::sim::Simulation::push_destination),
23/// [`Simulation::push_destination_front`](crate::sim::Simulation::push_destination_front),
24/// and [`Simulation::clear_destinations`](crate::sim::Simulation::clear_destinations).
25#[derive(Debug, Clone, Default, Serialize, Deserialize)]
26pub struct DestinationQueue {
27 /// Ordered FIFO of target stop `EntityId`s.
28 queue: Vec<EntityId>,
29}
30
31impl DestinationQueue {
32 /// Create an empty queue.
33 #[must_use]
34 pub const fn new() -> Self {
35 Self { queue: Vec::new() }
36 }
37
38 /// Read-only view of the current queue in FIFO order.
39 #[must_use]
40 pub fn queue(&self) -> &[EntityId] {
41 &self.queue
42 }
43
44 /// `true` if the queue contains no entries.
45 #[must_use]
46 pub const fn is_empty(&self) -> bool {
47 self.queue.is_empty()
48 }
49
50 /// Number of entries in the queue.
51 #[must_use]
52 pub const fn len(&self) -> usize {
53 self.queue.len()
54 }
55
56 /// The stop at the front of the queue (next destination).
57 #[must_use]
58 pub fn front(&self) -> Option<EntityId> {
59 self.queue.first().copied()
60 }
61
62 /// Push a stop onto the back of the queue.
63 ///
64 /// Returns `true` if the stop was actually appended. Returns `false` if
65 /// the queue is non-empty and its last entry already equals `stop`
66 /// (adjacent-duplicate dedup).
67 pub(crate) fn push_back(&mut self, stop: EntityId) -> bool {
68 if self.queue.last() == Some(&stop) {
69 return false;
70 }
71 self.queue.push(stop);
72 true
73 }
74
75 /// Insert a stop at the front of the queue (jump to this destination next).
76 ///
77 /// Returns `true` if the stop was actually inserted. Returns `false` if
78 /// the queue is non-empty and its first entry already equals `stop`.
79 pub(crate) fn push_front(&mut self, stop: EntityId) -> bool {
80 if self.queue.first() == Some(&stop) {
81 return false;
82 }
83 self.queue.insert(0, stop);
84 true
85 }
86
87 /// Drain all entries.
88 pub(crate) fn clear(&mut self) {
89 self.queue.clear();
90 }
91
92 /// Replace the queue contents with `stops` (order preserved).
93 ///
94 /// Used by direction-aware dispatch strategies that rebuild the
95 /// queue as a two-run monotone sequence.
96 pub(crate) fn replace(&mut self, stops: Vec<EntityId>) {
97 self.queue = stops;
98 }
99
100 /// Retain only entries that satisfy `predicate`.
101 ///
102 /// Used by `remove_stop` to scrub references to a despawned stop.
103 pub(crate) fn retain(&mut self, mut predicate: impl FnMut(EntityId) -> bool) {
104 self.queue.retain(|&eid| predicate(eid));
105 }
106
107 /// `true` if the queue contains `stop` anywhere.
108 #[must_use]
109 pub fn contains(&self, stop: &EntityId) -> bool {
110 self.queue.contains(stop)
111 }
112
113 /// Remove and return the front entry.
114 pub(crate) fn pop_front(&mut self) -> Option<EntityId> {
115 (!self.queue.is_empty()).then(|| self.queue.remove(0))
116 }
117}