elevator_core/sim/
calls.rs1use crate::components::{CallDirection, CarCall, HallCall};
8use crate::dispatch::ElevatorGroup;
9use crate::entity::{ElevatorId, EntityId};
10use crate::error::{EtaError, SimError};
11use crate::events::Event;
12use crate::stop::StopRef;
13
14impl super::Simulation {
15 pub fn press_hall_button(
28 &mut self,
29 stop: impl Into<StopRef>,
30 direction: CallDirection,
31 ) -> Result<(), SimError> {
32 let stop = self.resolve_stop(stop.into())?;
33 if self.world.stop(stop).is_none() {
34 return Err(SimError::EntityNotFound(stop));
35 }
36 self.ensure_hall_call(stop, direction, None, None);
37 Ok(())
38 }
39
40 pub fn press_car_button(
46 &mut self,
47 car: ElevatorId,
48 floor: impl Into<StopRef>,
49 ) -> Result<(), SimError> {
50 let car = car.entity();
51 let floor = self.resolve_stop(floor.into())?;
52 if self.world.elevator(car).is_none() {
53 return Err(SimError::EntityNotFound(car));
54 }
55 if self.world.stop(floor).is_none() {
56 return Err(SimError::EntityNotFound(floor));
57 }
58 self.ensure_car_call(car, floor, None);
59 Ok(())
60 }
61
62 pub fn pin_assignment(
76 &mut self,
77 car: ElevatorId,
78 stop: EntityId,
79 direction: CallDirection,
80 ) -> Result<(), SimError> {
81 let car = car.entity();
82 let Some(elev) = self.world.elevator(car) else {
83 return Err(SimError::EntityNotFound(car));
84 };
85 let car_line = elev.line;
86 let line_serves_stop = self
92 .groups
93 .iter()
94 .flat_map(|g| g.lines().iter())
95 .find(|li| li.entity() == car_line)
96 .map(|li| li.serves().contains(&stop));
97 if line_serves_stop == Some(false) {
98 return Err(SimError::LineDoesNotServeStop {
99 line_or_car: car,
100 stop,
101 });
102 }
103 let Some(call) = self.world.hall_call_mut(stop, direction) else {
104 return Err(SimError::HallCallNotFound { stop, direction });
105 };
106 call.assigned_car = Some(car);
107 call.pinned = true;
108 Ok(())
109 }
110
111 pub fn unpin_assignment(&mut self, stop: EntityId, direction: CallDirection) {
114 if let Some(call) = self.world.hall_call_mut(stop, direction) {
115 call.pinned = false;
116 }
117 }
118
119 pub fn hall_calls(&self) -> impl Iterator<Item = &HallCall> {
124 self.world.iter_hall_calls()
125 }
126
127 #[must_use]
130 pub fn car_calls(&self, car: ElevatorId) -> &[CarCall] {
131 let car = car.entity();
132 self.world.car_calls(car)
133 }
134
135 #[must_use]
138 pub fn assigned_car(&self, stop: EntityId, direction: CallDirection) -> Option<EntityId> {
139 self.world
140 .hall_call(stop, direction)
141 .and_then(|c| c.assigned_car)
142 }
143
144 pub fn eta_for_call(&self, stop: EntityId, direction: CallDirection) -> Result<u64, EtaError> {
154 let call = self
155 .world
156 .hall_call(stop, direction)
157 .ok_or(EtaError::NotAStop(stop))?;
158 let car = call.assigned_car.ok_or(EtaError::NoCarAssigned(stop))?;
159 let car_pos = self
160 .world
161 .position(car)
162 .ok_or(EtaError::NotAnElevator(car))?
163 .value;
164 let stop_pos = self
165 .world
166 .stop_position(stop)
167 .ok_or(EtaError::StopVanished(stop))?;
168 let max_speed = self
169 .world
170 .elevator(car)
171 .ok_or(EtaError::NotAnElevator(car))?
172 .max_speed()
173 .value();
174 if max_speed <= 0.0 {
175 return Err(EtaError::NotAnElevator(car));
176 }
177 let distance = (car_pos - stop_pos).abs();
178 Ok((distance / max_speed).ceil() as u64)
181 }
182
183 pub(super) fn ensure_hall_call(
186 &mut self,
187 stop: EntityId,
188 direction: CallDirection,
189 rider: Option<EntityId>,
190 destination: Option<EntityId>,
191 ) {
192 let mut fresh_press = false;
193 if self.world.hall_call(stop, direction).is_none() {
194 let mut call = HallCall::new(stop, direction, self.tick);
195 call.destination = destination;
196 call.ack_latency_ticks = self.ack_latency_for_stop(stop);
197 if call.ack_latency_ticks == 0 {
198 call.acknowledged_at = Some(self.tick);
201 }
202 if let Some(rid) = rider {
203 call.pending_riders.push(rid);
204 }
205 self.world.set_hall_call(call);
206 fresh_press = true;
207 } else if let Some(existing) = self.world.hall_call_mut(stop, direction) {
208 if let Some(rid) = rider
209 && !existing.pending_riders.contains(&rid)
210 {
211 existing.pending_riders.push(rid);
212 }
213 if existing.destination.is_none() {
216 existing.destination = destination;
217 }
218 }
219 if fresh_press {
220 self.events.emit(Event::HallButtonPressed {
221 stop,
222 direction,
223 tick: self.tick,
224 });
225 if let Some(call) = self.world.hall_call(stop, direction)
227 && call.acknowledged_at == Some(self.tick)
228 {
229 self.events.emit(Event::HallCallAcknowledged {
230 stop,
231 direction,
232 tick: self.tick,
233 });
234 }
235 }
236 }
237
238 fn ack_latency_for(
241 &self,
242 entity: EntityId,
243 members: impl Fn(&ElevatorGroup) -> &[EntityId],
244 ) -> u32 {
245 self.groups
246 .iter()
247 .find(|g| members(g).contains(&entity))
248 .map_or(0, ElevatorGroup::ack_latency_ticks)
249 }
250
251 fn ack_latency_for_stop(&self, stop: EntityId) -> u32 {
253 self.ack_latency_for(stop, ElevatorGroup::stop_entities)
254 }
255
256 fn ack_latency_for_car(&self, car: EntityId) -> u32 {
258 self.ack_latency_for(car, ElevatorGroup::elevator_entities)
259 }
260
261 fn ensure_car_call(&mut self, car: EntityId, floor: EntityId, rider: Option<EntityId>) {
265 let press_tick = self.tick;
266 let ack_latency = self.ack_latency_for_car(car);
267 let Some(queue) = self.world.car_calls_mut(car) else {
268 debug_assert!(
269 false,
270 "ensure_car_call: car {car:?} has no car_calls component"
271 );
272 return;
273 };
274 let existing_idx = queue.iter().position(|c| c.floor == floor);
275 let fresh = existing_idx.is_none();
276 if let Some(idx) = existing_idx {
277 if let Some(rid) = rider
278 && !queue[idx].pending_riders.contains(&rid)
279 {
280 queue[idx].pending_riders.push(rid);
281 }
282 } else {
283 let mut call = CarCall::new(car, floor, press_tick);
284 call.ack_latency_ticks = ack_latency;
285 if ack_latency == 0 {
286 call.acknowledged_at = Some(press_tick);
287 }
288 if let Some(rid) = rider {
289 call.pending_riders.push(rid);
290 }
291 queue.push(call);
292 }
293 if fresh {
294 self.events.emit(Event::CarButtonPressed {
295 car,
296 floor,
297 rider,
298 tick: press_tick,
299 });
300 }
301 }
302}