Skip to main content

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}