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| {
55 let min_a = min_distance_to(a.1, &occupied);
56 let min_b = min_distance_to(b.1, &occupied);
57 min_a.total_cmp(&min_b)
58 });
59
60 if let Some(&(stop_eid, stop_pos)) = best {
61 if (stop_pos - elev_pos).abs() > 1e-6 {
62 out.push((elev_eid, stop_eid));
63 }
64 occupied.push(stop_pos);
65 }
66 }
67 }
68}
69
70pub struct ReturnToLobby {
75 pub home_stop_index: usize,
78}
79
80impl ReturnToLobby {
81 #[must_use]
83 pub const fn new() -> Self {
84 Self { home_stop_index: 0 }
85 }
86
87 #[must_use]
89 pub const fn with_home(index: usize) -> Self {
90 Self {
91 home_stop_index: index,
92 }
93 }
94}
95
96impl Default for ReturnToLobby {
97 fn default() -> Self {
98 Self::new()
99 }
100}
101
102impl RepositionStrategy for ReturnToLobby {
103 fn reposition(
104 &mut self,
105 idle_elevators: &[(EntityId, f64)],
106 stop_positions: &[(EntityId, f64)],
107 _group: &ElevatorGroup,
108 _world: &World,
109 out: &mut Vec<(EntityId, EntityId)>,
110 ) {
111 let Some(&(home_eid, home_pos)) = stop_positions.get(self.home_stop_index) else {
112 return;
113 };
114
115 out.extend(
116 idle_elevators
117 .iter()
118 .filter(|(_, pos)| (*pos - home_pos).abs() > 1e-6)
119 .map(|&(eid, _)| (eid, home_eid)),
120 );
121 }
122}
123
124pub struct DemandWeighted;
130
131impl RepositionStrategy for DemandWeighted {
132 fn reposition(
133 &mut self,
134 idle_elevators: &[(EntityId, f64)],
135 stop_positions: &[(EntityId, f64)],
136 group: &ElevatorGroup,
137 world: &World,
138 out: &mut Vec<(EntityId, EntityId)>,
139 ) {
140 if idle_elevators.is_empty() || stop_positions.is_empty() {
141 return;
142 }
143
144 let tags = world.resource::<MetricTags>();
145 let mut scored_stops: Vec<(EntityId, f64, f64)> = stop_positions
146 .iter()
147 .map(|&(stop_eid, stop_pos)| {
148 let demand = tags
149 .and_then(|t| {
150 t.tags_for(stop_eid)
151 .iter()
152 .filter_map(|tag| t.metric(tag).map(TaggedMetric::total_delivered))
153 .max()
154 })
155 .unwrap_or(0) as f64;
156 (stop_eid, stop_pos, demand + 1.0)
157 })
158 .collect();
159
160 scored_stops.sort_by(|a, b| b.2.total_cmp(&a.2));
161
162 let mut occupied: Vec<f64> = group
163 .elevator_entities()
164 .iter()
165 .filter_map(|&eid| {
166 if idle_elevators.iter().any(|(ie, _)| *ie == eid) {
167 return None;
168 }
169 world.position(eid).map(|p| p.value)
170 })
171 .collect();
172
173 let mut assigned_elevators: Vec<EntityId> = Vec::new();
174
175 for (stop_eid, stop_pos, _demand) in &scored_stops {
176 if min_distance_to(*stop_pos, &occupied) < 1e-6 {
177 continue;
178 }
179
180 let closest = idle_elevators
181 .iter()
182 .filter(|(eid, _)| !assigned_elevators.contains(eid))
183 .min_by(|a, b| {
184 let da = (a.1 - stop_pos).abs();
185 let db = (b.1 - stop_pos).abs();
186 da.total_cmp(&db)
187 });
188
189 if let Some(&(elev_eid, elev_pos)) = closest
190 && (elev_pos - stop_pos).abs() > 1e-6
191 {
192 out.push((elev_eid, *stop_eid));
193 assigned_elevators.push(elev_eid);
194 occupied.push(*stop_pos);
195 }
196
197 if assigned_elevators.len() == idle_elevators.len() {
198 break;
199 }
200 }
201 }
202}
203
204pub struct NearestIdle;
209
210impl RepositionStrategy for NearestIdle {
211 fn reposition(
212 &mut self,
213 _idle_elevators: &[(EntityId, f64)],
214 _stop_positions: &[(EntityId, f64)],
215 _group: &ElevatorGroup,
216 _world: &World,
217 _out: &mut Vec<(EntityId, EntityId)>,
218 ) {
219 }
220}
221
222fn min_distance_to(pos: f64, others: &[f64]) -> f64 {
224 if others.is_empty() {
225 return f64::INFINITY;
226 }
227 others
228 .iter()
229 .map(|&o| (pos - o).abs())
230 .fold(f64::INFINITY, f64::min)
231}