elevator_core/sim/runtime.rs
1//! Runtime elevator upgrades (speed, accel, decel, capacity, door timings).
2//!
3//! Part of the [`super::Simulation`] API surface; extracted from the
4//! monolithic `sim.rs` for readability. See the parent module for the
5//! overarching essential-API summary.
6
7use crate::components::{Accel, Speed, Weight};
8use crate::entity::ElevatorId;
9use crate::error::SimError;
10
11impl super::Simulation {
12 // ── Runtime elevator upgrades ────────────────────────────────────
13 //
14 // Games that want to mutate elevator parameters at runtime (e.g.
15 // an RPG speed-upgrade purchase, a scripted capacity boost) go
16 // through these setters rather than poking `Elevator` directly via
17 // `world_mut()`. Each setter validates its input, updates the
18 // underlying component, and emits an [`Event::ElevatorUpgraded`]
19 // so game code can react without polling.
20 //
21 // ### Semantics
22 //
23 // - `max_speed`, `acceleration`, `deceleration`: applied on the next
24 // movement integration step. The car's **current velocity is
25 // preserved** — there is no instantaneous jerk. If `max_speed`
26 // is lowered below the current velocity, the movement integrator
27 // clamps velocity to the new cap on the next tick.
28 // - `weight_capacity`: applied immediately. If the new capacity is
29 // below `current_load` the car ends up temporarily overweight —
30 // no riders are ejected, but the next boarding pass will reject
31 // any rider that would push the load further over the new cap.
32 // - `door_transition_ticks`, `door_open_ticks`: applied on the
33 // **next** door cycle. An in-progress door transition keeps its
34 // original timing, so setters never cause visual glitches.
35
36 /// Set the maximum travel speed for an elevator at runtime.
37 ///
38 /// The new value applies on the next movement integration step;
39 /// the car's current velocity is preserved (see the
40 /// [runtime upgrades section](crate#runtime-upgrades) of the crate
41 /// docs). If the new cap is below the current velocity, the movement
42 /// system clamps velocity down on the next tick.
43 ///
44 /// # Errors
45 ///
46 /// - [`SimError::NotAnElevator`] if `elevator` is not an elevator entity.
47 /// - [`SimError::InvalidConfig`] if `speed` is not a positive finite number.
48 ///
49 /// # Example
50 ///
51 /// ```
52 /// use elevator_core::prelude::*;
53 ///
54 /// let mut sim = SimulationBuilder::demo().build().unwrap();
55 /// let elev = ElevatorId::from(sim.world().iter_elevators().next().unwrap().0);
56 /// sim.set_max_speed(elev, 4.0).unwrap();
57 /// assert_eq!(sim.world().elevator(elev.entity()).unwrap().max_speed().value(), 4.0);
58 /// ```
59 pub fn set_max_speed(&mut self, elevator: ElevatorId, speed: f64) -> Result<(), SimError> {
60 let elevator = elevator.entity();
61 Self::validate_positive_finite_f64(speed, "elevators.max_speed")?;
62 let old = self.require_elevator(elevator)?.max_speed.value();
63 let speed = Speed::from(speed);
64 if let Some(car) = self.world.elevator_mut(elevator) {
65 car.max_speed = speed;
66 }
67 self.emit_upgrade(
68 elevator,
69 crate::events::UpgradeField::MaxSpeed,
70 crate::events::UpgradeValue::float(old),
71 crate::events::UpgradeValue::float(speed.value()),
72 );
73 Ok(())
74 }
75
76 /// Set the acceleration rate for an elevator at runtime.
77 ///
78 /// See [`set_max_speed`](Self::set_max_speed) for the general
79 /// velocity-preservation rules that apply to kinematic setters.
80 ///
81 /// # Errors
82 ///
83 /// - [`SimError::NotAnElevator`] if `elevator` is not an elevator entity.
84 /// - [`SimError::InvalidConfig`] if `accel` is not a positive finite number.
85 ///
86 /// # Example
87 ///
88 /// ```
89 /// use elevator_core::prelude::*;
90 ///
91 /// let mut sim = SimulationBuilder::demo().build().unwrap();
92 /// let elev = ElevatorId::from(sim.world().iter_elevators().next().unwrap().0);
93 /// sim.set_acceleration(elev, 3.0).unwrap();
94 /// assert_eq!(sim.world().elevator(elev.entity()).unwrap().acceleration().value(), 3.0);
95 /// ```
96 pub fn set_acceleration(&mut self, elevator: ElevatorId, accel: f64) -> Result<(), SimError> {
97 let elevator = elevator.entity();
98 Self::validate_positive_finite_f64(accel, "elevators.acceleration")?;
99 let old = self.require_elevator(elevator)?.acceleration.value();
100 let accel = Accel::from(accel);
101 if let Some(car) = self.world.elevator_mut(elevator) {
102 car.acceleration = accel;
103 }
104 self.emit_upgrade(
105 elevator,
106 crate::events::UpgradeField::Acceleration,
107 crate::events::UpgradeValue::float(old),
108 crate::events::UpgradeValue::float(accel.value()),
109 );
110 Ok(())
111 }
112
113 /// Set the deceleration rate for an elevator at runtime.
114 ///
115 /// See [`set_max_speed`](Self::set_max_speed) for the general
116 /// velocity-preservation rules that apply to kinematic setters.
117 ///
118 /// # Errors
119 ///
120 /// - [`SimError::NotAnElevator`] if `elevator` is not an elevator entity.
121 /// - [`SimError::InvalidConfig`] if `decel` is not a positive finite number.
122 ///
123 /// # Example
124 ///
125 /// ```
126 /// use elevator_core::prelude::*;
127 ///
128 /// let mut sim = SimulationBuilder::demo().build().unwrap();
129 /// let elev = ElevatorId::from(sim.world().iter_elevators().next().unwrap().0);
130 /// sim.set_deceleration(elev, 3.5).unwrap();
131 /// assert_eq!(sim.world().elevator(elev.entity()).unwrap().deceleration().value(), 3.5);
132 /// ```
133 pub fn set_deceleration(&mut self, elevator: ElevatorId, decel: f64) -> Result<(), SimError> {
134 let elevator = elevator.entity();
135 Self::validate_positive_finite_f64(decel, "elevators.deceleration")?;
136 let old = self.require_elevator(elevator)?.deceleration.value();
137 let decel = Accel::from(decel);
138 if let Some(car) = self.world.elevator_mut(elevator) {
139 car.deceleration = decel;
140 }
141 self.emit_upgrade(
142 elevator,
143 crate::events::UpgradeField::Deceleration,
144 crate::events::UpgradeValue::float(old),
145 crate::events::UpgradeValue::float(decel.value()),
146 );
147 Ok(())
148 }
149
150 /// Set the weight capacity for an elevator at runtime.
151 ///
152 /// Applied immediately. If the new capacity is below the car's
153 /// current load the car is temporarily overweight; no riders are
154 /// ejected, but subsequent boarding attempts that would push load
155 /// further over the cap will be rejected as
156 /// [`RejectionReason::OverCapacity`](crate::error::RejectionReason::OverCapacity).
157 ///
158 /// # Errors
159 ///
160 /// - [`SimError::NotAnElevator`] if `elevator` is not an elevator entity.
161 /// - [`SimError::InvalidConfig`] if `capacity` is not a positive finite number.
162 ///
163 /// # Example
164 ///
165 /// ```
166 /// use elevator_core::prelude::*;
167 ///
168 /// let mut sim = SimulationBuilder::demo().build().unwrap();
169 /// let elev = ElevatorId::from(sim.world().iter_elevators().next().unwrap().0);
170 /// sim.set_weight_capacity(elev, 1200.0).unwrap();
171 /// assert_eq!(sim.world().elevator(elev.entity()).unwrap().weight_capacity().value(), 1200.0);
172 /// ```
173 pub fn set_weight_capacity(
174 &mut self,
175 elevator: ElevatorId,
176 capacity: f64,
177 ) -> Result<(), SimError> {
178 let elevator = elevator.entity();
179 Self::validate_positive_finite_f64(capacity, "elevators.weight_capacity")?;
180 let old = self.require_elevator(elevator)?.weight_capacity.value();
181 let capacity = Weight::from(capacity);
182 if let Some(car) = self.world.elevator_mut(elevator) {
183 car.weight_capacity = capacity;
184 }
185 self.emit_upgrade(
186 elevator,
187 crate::events::UpgradeField::WeightCapacity,
188 crate::events::UpgradeValue::float(old),
189 crate::events::UpgradeValue::float(capacity.value()),
190 );
191 Ok(())
192 }
193
194 /// Set the door open/close transition duration for an elevator.
195 ///
196 /// Applied on the **next** door cycle — an in-progress transition
197 /// keeps its original timing to avoid visual glitches.
198 ///
199 /// # Errors
200 ///
201 /// - [`SimError::NotAnElevator`] if `elevator` is not an elevator entity.
202 /// - [`SimError::InvalidConfig`] if `ticks` is zero.
203 ///
204 /// # Example
205 ///
206 /// ```
207 /// use elevator_core::prelude::*;
208 ///
209 /// let mut sim = SimulationBuilder::demo().build().unwrap();
210 /// let elev = ElevatorId::from(sim.world().iter_elevators().next().unwrap().0);
211 /// sim.set_door_transition_ticks(elev, 3).unwrap();
212 /// assert_eq!(sim.world().elevator(elev.entity()).unwrap().door_transition_ticks(), 3);
213 /// ```
214 pub fn set_door_transition_ticks(
215 &mut self,
216 elevator: ElevatorId,
217 ticks: u32,
218 ) -> Result<(), SimError> {
219 let elevator = elevator.entity();
220 Self::validate_nonzero_u32(ticks, "elevators.door_transition_ticks")?;
221 let old = self.require_elevator(elevator)?.door_transition_ticks;
222 if let Some(car) = self.world.elevator_mut(elevator) {
223 car.door_transition_ticks = ticks;
224 }
225 self.emit_upgrade(
226 elevator,
227 crate::events::UpgradeField::DoorTransitionTicks,
228 crate::events::UpgradeValue::ticks(old),
229 crate::events::UpgradeValue::ticks(ticks),
230 );
231 Ok(())
232 }
233
234 /// Set how long doors hold fully open for an elevator.
235 ///
236 /// Applied on the **next** door cycle — a door that is currently
237 /// holding open will complete its original dwell before the new
238 /// value takes effect.
239 ///
240 /// # Errors
241 ///
242 /// - [`SimError::NotAnElevator`] if `elevator` is not an elevator entity.
243 /// - [`SimError::InvalidConfig`] if `ticks` is zero.
244 ///
245 /// # Example
246 ///
247 /// ```
248 /// use elevator_core::prelude::*;
249 ///
250 /// let mut sim = SimulationBuilder::demo().build().unwrap();
251 /// let elev = ElevatorId::from(sim.world().iter_elevators().next().unwrap().0);
252 /// sim.set_door_open_ticks(elev, 20).unwrap();
253 /// assert_eq!(sim.world().elevator(elev.entity()).unwrap().door_open_ticks(), 20);
254 /// ```
255 pub fn set_door_open_ticks(
256 &mut self,
257 elevator: ElevatorId,
258 ticks: u32,
259 ) -> Result<(), SimError> {
260 let elevator = elevator.entity();
261 Self::validate_nonzero_u32(ticks, "elevators.door_open_ticks")?;
262 let old = self.require_elevator(elevator)?.door_open_ticks;
263 if let Some(car) = self.world.elevator_mut(elevator) {
264 car.door_open_ticks = ticks;
265 }
266 self.emit_upgrade(
267 elevator,
268 crate::events::UpgradeField::DoorOpenTicks,
269 crate::events::UpgradeValue::ticks(old),
270 crate::events::UpgradeValue::ticks(ticks),
271 );
272 Ok(())
273 }
274}