elevator_core/dispatch/
look.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 Direction {
19 Up,
21 Down,
23}
24
25pub struct LookDispatch {
33 direction: HashMap<EntityId, Direction>,
35}
36
37impl LookDispatch {
38 #[must_use]
40 pub fn new() -> Self {
41 Self {
42 direction: HashMap::new(),
43 }
44 }
45}
46
47impl Default for LookDispatch {
48 fn default() -> Self {
49 Self::new()
50 }
51}
52
53impl DispatchStrategy for LookDispatch {
54 fn decide(
55 &mut self,
56 elevator: EntityId,
57 elevator_position: f64,
58 group: &ElevatorGroup,
59 manifest: &DispatchManifest,
60 world: &World,
61 ) -> DispatchDecision {
62 let direction = self
63 .direction
64 .get(&elevator)
65 .copied()
66 .unwrap_or(Direction::Up);
67
68 let mut interesting: SmallVec<[(EntityId, f64); 32]> = SmallVec::new();
70
71 for &stop_eid in group.stop_entities() {
72 if manifest.has_demand(stop_eid)
73 && let Some(pos) = world.stop_position(stop_eid)
74 {
75 interesting.push((stop_eid, pos));
76 }
77 }
78
79 if interesting.is_empty() {
80 return DispatchDecision::Idle;
81 }
82
83 let pos = elevator_position;
84
85 let (ahead, behind): (SmallVec<[_; 32]>, SmallVec<[_; 32]>) = match direction {
87 Direction::Up => interesting.iter().partition(|(_, p)| *p > pos + EPSILON),
88 Direction::Down => interesting.iter().partition(|(_, p)| *p < pos - EPSILON),
89 };
90
91 if !ahead.is_empty() {
92 let nearest = match direction {
94 Direction::Up => ahead
95 .iter()
96 .min_by(|a: &&&(EntityId, f64), b: &&&(EntityId, f64)| a.1.total_cmp(&b.1)),
97 Direction::Down => ahead
98 .iter()
99 .max_by(|a: &&&(EntityId, f64), b: &&&(EntityId, f64)| a.1.total_cmp(&b.1)),
100 };
101 if let Some(stop) = nearest {
103 return DispatchDecision::GoToStop(stop.0);
104 }
105 }
106
107 let new_dir = match direction {
109 Direction::Up => Direction::Down,
110 Direction::Down => Direction::Up,
111 };
112 self.direction.insert(elevator, new_dir);
113
114 if behind.is_empty() {
115 return interesting
117 .first()
118 .map_or(DispatchDecision::Idle, |(sid, _)| {
119 DispatchDecision::GoToStop(*sid)
120 });
121 }
122
123 let nearest = match new_dir {
125 Direction::Up => behind
126 .iter()
127 .min_by(|a: &&&(EntityId, f64), b: &&&(EntityId, f64)| a.1.total_cmp(&b.1)),
128 Direction::Down => behind
129 .iter()
130 .max_by(|a: &&&(EntityId, f64), b: &&&(EntityId, f64)| a.1.total_cmp(&b.1)),
131 };
132
133 nearest.map_or(DispatchDecision::Idle, |stop| {
135 DispatchDecision::GoToStop(stop.0)
136 })
137 }
138
139 fn notify_removed(&mut self, elevator: EntityId) {
140 self.direction.remove(&elevator);
141 }
142}