elevator_core/dispatch/
reposition.rs1use crate::entity::EntityId;
16use crate::tagged_metrics::{MetricTags, TaggedMetric};
17use crate::world::World;
18
19use super::{ElevatorGroup, RepositionStrategy};
20
21pub struct SpreadEvenly;
27
28impl RepositionStrategy for SpreadEvenly {
29 fn reposition(
30 &mut self,
31 idle_elevators: &[(EntityId, f64)],
32 stop_positions: &[(EntityId, f64)],
33 group: &ElevatorGroup,
34 world: &World,
35 ) -> Vec<(EntityId, EntityId)> {
36 if idle_elevators.is_empty() || stop_positions.is_empty() {
37 return Vec::new();
38 }
39
40 let mut occupied: Vec<f64> = group
42 .elevator_entities()
43 .iter()
44 .filter_map(|&eid| {
45 if idle_elevators.iter().any(|(ie, _)| *ie == eid) {
47 return None;
48 }
49 world.position(eid).map(|p| p.value)
50 })
51 .collect();
52
53 let mut results = Vec::new();
54
55 for &(elev_eid, elev_pos) in idle_elevators {
56 let best = stop_positions.iter().max_by(|a, b| {
58 let min_a = min_distance_to(a.1, &occupied);
59 let min_b = min_distance_to(b.1, &occupied);
60 min_a.total_cmp(&min_b)
61 });
62
63 if let Some(&(stop_eid, stop_pos)) = best {
64 if (stop_pos - elev_pos).abs() > 1e-6 {
65 results.push((elev_eid, stop_eid));
66 }
67 occupied.push(stop_pos);
68 }
69 }
70
71 results
72 }
73}
74
75pub struct ReturnToLobby {
80 pub home_stop_index: usize,
83}
84
85impl ReturnToLobby {
86 #[must_use]
88 pub const fn new() -> Self {
89 Self { home_stop_index: 0 }
90 }
91
92 #[must_use]
94 pub const fn with_home(index: usize) -> Self {
95 Self {
96 home_stop_index: index,
97 }
98 }
99}
100
101impl Default for ReturnToLobby {
102 fn default() -> Self {
103 Self::new()
104 }
105}
106
107impl RepositionStrategy for ReturnToLobby {
108 fn reposition(
109 &mut self,
110 idle_elevators: &[(EntityId, f64)],
111 stop_positions: &[(EntityId, f64)],
112 _group: &ElevatorGroup,
113 _world: &World,
114 ) -> Vec<(EntityId, EntityId)> {
115 let Some(&(home_eid, home_pos)) = stop_positions.get(self.home_stop_index) else {
116 return Vec::new();
117 };
118
119 idle_elevators
120 .iter()
121 .filter(|(_, pos)| (*pos - home_pos).abs() > 1e-6)
122 .map(|&(eid, _)| (eid, home_eid))
123 .collect()
124 }
125}
126
127pub struct DemandWeighted;
133
134impl RepositionStrategy for DemandWeighted {
135 fn reposition(
136 &mut self,
137 idle_elevators: &[(EntityId, f64)],
138 stop_positions: &[(EntityId, f64)],
139 group: &ElevatorGroup,
140 world: &World,
141 ) -> Vec<(EntityId, EntityId)> {
142 if idle_elevators.is_empty() || stop_positions.is_empty() {
143 return Vec::new();
144 }
145
146 let tags = world.resource::<MetricTags>();
148 let mut scored_stops: Vec<(EntityId, f64, f64)> = stop_positions
149 .iter()
150 .map(|&(stop_eid, stop_pos)| {
151 let demand = tags
152 .and_then(|t| {
153 t.tags_for(stop_eid)
154 .iter()
155 .filter_map(|tag| t.metric(tag).map(TaggedMetric::total_delivered))
156 .max()
157 })
158 .unwrap_or(0) as f64;
159 (stop_eid, stop_pos, demand + 1.0) })
161 .collect();
162
163 scored_stops.sort_by(|a, b| b.2.total_cmp(&a.2));
165
166 let mut occupied: Vec<f64> = group
168 .elevator_entities()
169 .iter()
170 .filter_map(|&eid| {
171 if idle_elevators.iter().any(|(ie, _)| *ie == eid) {
172 return None;
173 }
174 world.position(eid).map(|p| p.value)
175 })
176 .collect();
177
178 let mut results = Vec::new();
179 let mut assigned_elevators: Vec<EntityId> = Vec::new();
180
181 for (stop_eid, stop_pos, _demand) in &scored_stops {
182 if min_distance_to(*stop_pos, &occupied) < 1e-6 {
184 continue;
185 }
186
187 let closest = idle_elevators
189 .iter()
190 .filter(|(eid, _)| !assigned_elevators.contains(eid))
191 .min_by(|a, b| {
192 let da = (a.1 - stop_pos).abs();
193 let db = (b.1 - stop_pos).abs();
194 da.total_cmp(&db)
195 });
196
197 if let Some(&(elev_eid, elev_pos)) = closest
198 && (elev_pos - stop_pos).abs() > 1e-6
199 {
200 results.push((elev_eid, *stop_eid));
201 assigned_elevators.push(elev_eid);
202 occupied.push(*stop_pos);
203 }
204
205 if assigned_elevators.len() == idle_elevators.len() {
206 break;
207 }
208 }
209
210 results
211 }
212}
213
214pub struct NearestIdle;
219
220impl RepositionStrategy for NearestIdle {
221 fn reposition(
222 &mut self,
223 _idle_elevators: &[(EntityId, f64)],
224 _stop_positions: &[(EntityId, f64)],
225 _group: &ElevatorGroup,
226 _world: &World,
227 ) -> Vec<(EntityId, EntityId)> {
228 Vec::new()
229 }
230}
231
232fn min_distance_to(pos: f64, others: &[f64]) -> f64 {
234 if others.is_empty() {
235 return f64::INFINITY;
236 }
237 others
238 .iter()
239 .map(|&o| (pos - o).abs())
240 .fold(f64::INFINITY, f64::min)
241}