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 out: &mut Vec<(EntityId, EntityId)>,
36 ) {
37 if idle_elevators.is_empty() || stop_positions.is_empty() {
38 return;
39 }
40
41 let mut occupied: Vec<f64> = group
43 .elevator_entities()
44 .iter()
45 .filter_map(|&eid| {
46 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 for &(elev_eid, elev_pos) in idle_elevators {
54 let best = stop_positions.iter().max_by(|a, b| {
62 let min_a = min_distance_to(a.1, &occupied);
63 let min_b = min_distance_to(b.1, &occupied);
64 min_a.total_cmp(&min_b).then_with(|| {
65 let dist_a = (a.1 - elev_pos).abs();
66 let dist_b = (b.1 - elev_pos).abs();
67 dist_b.total_cmp(&dist_a)
70 })
71 });
72
73 if let Some(&(stop_eid, stop_pos)) = best {
74 if (stop_pos - elev_pos).abs() > 1e-6 {
75 out.push((elev_eid, stop_eid));
76 }
77 occupied.push(stop_pos);
78 }
79 }
80 }
81}
82
83pub struct ReturnToLobby {
88 pub home_stop_index: usize,
91}
92
93impl ReturnToLobby {
94 #[must_use]
96 pub const fn new() -> Self {
97 Self { home_stop_index: 0 }
98 }
99
100 #[must_use]
102 pub const fn with_home(index: usize) -> Self {
103 Self {
104 home_stop_index: index,
105 }
106 }
107}
108
109impl Default for ReturnToLobby {
110 fn default() -> Self {
111 Self::new()
112 }
113}
114
115impl RepositionStrategy for ReturnToLobby {
116 fn reposition(
117 &mut self,
118 idle_elevators: &[(EntityId, f64)],
119 stop_positions: &[(EntityId, f64)],
120 _group: &ElevatorGroup,
121 _world: &World,
122 out: &mut Vec<(EntityId, EntityId)>,
123 ) {
124 let Some(&(home_eid, home_pos)) = stop_positions.get(self.home_stop_index) else {
125 return;
126 };
127
128 out.extend(
129 idle_elevators
130 .iter()
131 .filter(|(_, pos)| (*pos - home_pos).abs() > 1e-6)
132 .map(|&(eid, _)| (eid, home_eid)),
133 );
134 }
135}
136
137pub struct DemandWeighted;
143
144impl RepositionStrategy for DemandWeighted {
145 fn reposition(
146 &mut self,
147 idle_elevators: &[(EntityId, f64)],
148 stop_positions: &[(EntityId, f64)],
149 group: &ElevatorGroup,
150 world: &World,
151 out: &mut Vec<(EntityId, EntityId)>,
152 ) {
153 if idle_elevators.is_empty() || stop_positions.is_empty() {
154 return;
155 }
156
157 let tags = world.resource::<MetricTags>();
158 let mut scored_stops: Vec<(EntityId, f64, f64)> = stop_positions
159 .iter()
160 .map(|&(stop_eid, stop_pos)| {
161 let demand = tags
162 .and_then(|t| {
163 t.tags_for(stop_eid)
164 .iter()
165 .filter_map(|tag| t.metric(tag).map(TaggedMetric::total_delivered))
166 .max()
167 })
168 .unwrap_or(0) as f64;
169 (stop_eid, stop_pos, demand + 1.0)
170 })
171 .collect();
172
173 scored_stops.sort_by(|a, b| b.2.total_cmp(&a.2));
174
175 let mut occupied: Vec<f64> = group
176 .elevator_entities()
177 .iter()
178 .filter_map(|&eid| {
179 if idle_elevators.iter().any(|(ie, _)| *ie == eid) {
180 return None;
181 }
182 world.position(eid).map(|p| p.value)
183 })
184 .collect();
185
186 let mut assigned_elevators: Vec<EntityId> = Vec::new();
187
188 for (stop_eid, stop_pos, _demand) in &scored_stops {
189 if min_distance_to(*stop_pos, &occupied) < 1e-6 {
190 continue;
191 }
192
193 let closest = idle_elevators
194 .iter()
195 .filter(|(eid, _)| !assigned_elevators.contains(eid))
196 .min_by(|a, b| {
197 let da = (a.1 - stop_pos).abs();
198 let db = (b.1 - stop_pos).abs();
199 da.total_cmp(&db)
200 });
201
202 if let Some(&(elev_eid, elev_pos)) = closest
203 && (elev_pos - stop_pos).abs() > 1e-6
204 {
205 out.push((elev_eid, *stop_eid));
206 assigned_elevators.push(elev_eid);
207 occupied.push(*stop_pos);
208 }
209
210 if assigned_elevators.len() == idle_elevators.len() {
211 break;
212 }
213 }
214 }
215}
216
217pub struct NearestIdle;
222
223impl RepositionStrategy for NearestIdle {
224 fn reposition(
225 &mut self,
226 _idle_elevators: &[(EntityId, f64)],
227 _stop_positions: &[(EntityId, f64)],
228 _group: &ElevatorGroup,
229 _world: &World,
230 _out: &mut Vec<(EntityId, EntityId)>,
231 ) {
232 }
233}
234
235fn min_distance_to(pos: f64, others: &[f64]) -> f64 {
237 if others.is_empty() {
238 return f64::INFINITY;
239 }
240 others
241 .iter()
242 .map(|&o| (pos - o).abs())
243 .fold(f64::INFINITY, f64::min)
244}