elevator_core/dispatch/
scan.rs1use std::collections::HashMap;
4
5use smallvec::SmallVec;
6
7use crate::entity::EntityId;
8use crate::world::World;
9
10use super::{DispatchDecision, DispatchManifest, DispatchStrategy, ElevatorGroup};
11
12const EPSILON: f64 = 1e-9;
14
15#[derive(Debug, Clone, Copy, PartialEq, Eq)]
17#[non_exhaustive]
18pub(crate) enum ScanDirection {
19 Up,
21 Down,
23}
24
25pub struct ScanDispatch {
29 direction: HashMap<EntityId, ScanDirection>,
31}
32
33impl ScanDispatch {
34 #[must_use]
36 pub fn new() -> Self {
37 Self {
38 direction: HashMap::new(),
39 }
40 }
41}
42
43impl Default for ScanDispatch {
44 fn default() -> Self {
45 Self::new()
46 }
47}
48
49impl DispatchStrategy for ScanDispatch {
50 fn decide(
51 &mut self,
52 elevator: EntityId,
53 elevator_position: f64,
54 group: &ElevatorGroup,
55 manifest: &DispatchManifest,
56 world: &World,
57 ) -> DispatchDecision {
58 let direction = self
59 .direction
60 .get(&elevator)
61 .copied()
62 .unwrap_or(ScanDirection::Up);
63
64 let mut interesting: SmallVec<[(EntityId, f64); 32]> = SmallVec::new();
66
67 for &stop_eid in group.stop_entities() {
68 if manifest.has_demand(stop_eid)
69 && let Some(pos) = world.stop_position(stop_eid)
70 {
71 interesting.push((stop_eid, pos));
72 }
73 }
74
75 if interesting.is_empty() {
76 return DispatchDecision::Idle;
77 }
78
79 let pos = elevator_position;
80
81 let (ahead, behind): (SmallVec<[_; 32]>, SmallVec<[_; 32]>) = match direction {
83 ScanDirection::Up => interesting.iter().partition(|(_, p)| *p > pos + EPSILON),
84 ScanDirection::Down => interesting.iter().partition(|(_, p)| *p < pos - EPSILON),
85 };
86
87 if !ahead.is_empty() {
88 let nearest = match direction {
89 ScanDirection::Up => ahead
90 .iter()
91 .min_by(|a: &&&(EntityId, f64), b: &&&(EntityId, f64)| a.1.total_cmp(&b.1)),
92 ScanDirection::Down => ahead
93 .iter()
94 .max_by(|a: &&&(EntityId, f64), b: &&&(EntityId, f64)| a.1.total_cmp(&b.1)),
95 };
96 if let Some(stop) = nearest {
98 return DispatchDecision::GoToStop(stop.0);
99 }
100 }
101
102 let new_dir = match direction {
104 ScanDirection::Up => ScanDirection::Down,
105 ScanDirection::Down => ScanDirection::Up,
106 };
107 self.direction.insert(elevator, new_dir);
108
109 if behind.is_empty() {
110 return interesting
112 .first()
113 .map_or(DispatchDecision::Idle, |(sid, _)| {
114 DispatchDecision::GoToStop(*sid)
115 });
116 }
117
118 let nearest = match new_dir {
119 ScanDirection::Up => behind
120 .iter()
121 .min_by(|a: &&&(EntityId, f64), b: &&&(EntityId, f64)| a.1.total_cmp(&b.1)),
122 ScanDirection::Down => behind
123 .iter()
124 .max_by(|a: &&&(EntityId, f64), b: &&&(EntityId, f64)| a.1.total_cmp(&b.1)),
125 };
126
127 nearest.map_or(DispatchDecision::Idle, |stop| {
129 DispatchDecision::GoToStop(stop.0)
130 })
131 }
132
133 fn notify_removed(&mut self, elevator: EntityId) {
134 self.direction.remove(&elevator);
135 }
136}