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