Skip to main content

elevator_core/
config.rs

1//! Building and elevator configuration (RON-deserializable).
2
3use crate::components::{Orientation, SpatialPosition};
4use crate::dispatch::{BuiltinReposition, BuiltinStrategy, HallCallMode};
5use crate::stop::{StopConfig, StopId};
6use serde::{Deserialize, Serialize};
7
8/// Top-level simulation configuration, loadable from RON.
9///
10/// Validated at construction time by [`Simulation::new()`](crate::sim::Simulation::new)
11/// or [`SimulationBuilder::build()`](crate::builder::SimulationBuilder::build).
12#[derive(Debug, Clone, Serialize, Deserialize)]
13pub struct SimConfig {
14    /// Building layout describing the stops (floors/stations) along the shaft.
15    pub building: BuildingConfig,
16    /// Elevator cars to install in the building.
17    ///
18    /// Legacy flat list — used when `building.lines` is `None`.
19    /// When explicit lines are provided, elevators live inside each
20    /// [`LineConfig`] instead.
21    #[serde(default)]
22    pub elevators: Vec<ElevatorConfig>,
23    /// Global simulation timing parameters.
24    pub simulation: SimulationParams,
25    /// Passenger spawning parameters used by the game layer.
26    ///
27    /// The core library does not consume these directly; they are stored here
28    /// for games and traffic generators that read the config.
29    pub passenger_spawning: PassengerSpawnConfig,
30}
31
32/// Building layout.
33#[derive(Debug, Clone, Serialize, Deserialize)]
34pub struct BuildingConfig {
35    /// Human-readable building name, displayed in UIs and logs.
36    pub name: String,
37    /// Ordered list of stops in the building.
38    ///
39    /// Must contain at least one stop. Each stop has a unique [`StopId`] and
40    /// an arbitrary position along the shaft axis. Positions need not be
41    /// uniformly spaced — this enables buildings, skyscrapers, and space
42    /// elevators with varying inter-stop distances.
43    pub stops: Vec<StopConfig>,
44    /// Lines (physical paths). If `None`, auto-inferred from the flat
45    /// elevator list on [`SimConfig`].
46    #[serde(default)]
47    pub lines: Option<Vec<LineConfig>>,
48    /// Dispatch groups. If `None`, auto-inferred (single group with all lines).
49    #[serde(default)]
50    pub groups: Option<Vec<GroupConfig>>,
51}
52
53/// Configuration for a single elevator car.
54#[derive(Debug, Clone, Serialize, Deserialize)]
55pub struct ElevatorConfig {
56    /// Numeric identifier for this elevator, unique within the config.
57    ///
58    /// Mapped to an [`EntityId`](crate::entity::EntityId) at construction
59    /// time; not used at runtime.
60    pub id: u32,
61    /// Human-readable elevator name, displayed in UIs and logs.
62    pub name: String,
63    /// Maximum travel speed in distance units per second.
64    ///
65    /// Must be positive. The trapezoidal velocity profile accelerates up to
66    /// this speed, cruises, then decelerates to stop at the target.
67    ///
68    /// Default (from `SimulationBuilder`): `2.0`.
69    pub max_speed: f64,
70    /// Acceleration rate in distance units per second squared.
71    ///
72    /// Must be positive. Controls how quickly the elevator reaches
73    /// `max_speed` from rest.
74    ///
75    /// Default (from `SimulationBuilder`): `1.5`.
76    pub acceleration: f64,
77    /// Deceleration rate in distance units per second squared.
78    ///
79    /// Must be positive. Controls how quickly the elevator slows to a stop
80    /// when approaching a target. May differ from `acceleration` for
81    /// asymmetric motion profiles.
82    ///
83    /// Default (from `SimulationBuilder`): `2.0`.
84    pub deceleration: f64,
85    /// Maximum total weight the elevator car can carry.
86    ///
87    /// Must be positive. Riders whose weight would exceed this limit are
88    /// rejected during the loading phase.
89    ///
90    /// Units: same as rider weight (typically kilograms).
91    /// Default (from `SimulationBuilder`): `800.0`.
92    pub weight_capacity: f64,
93    /// The [`StopId`] where this elevator starts at simulation init.
94    ///
95    /// Must reference an existing stop in the building config.
96    pub starting_stop: StopId,
97    /// How many ticks the doors remain fully open before closing.
98    ///
99    /// During this window, riders may board or exit. Longer values
100    /// increase loading opportunity but reduce throughput.
101    ///
102    /// Units: simulation ticks.
103    /// Default (from `SimulationBuilder`): `10`.
104    pub door_open_ticks: u32,
105    /// How many ticks a door open or close transition takes.
106    ///
107    /// Models the mechanical travel time of the door panels. No boarding
108    /// or exiting occurs during transitions.
109    ///
110    /// Units: simulation ticks.
111    /// Default (from `SimulationBuilder`): `5`.
112    pub door_transition_ticks: u32,
113    /// Stop IDs this elevator cannot serve (access restriction).
114    ///
115    /// Riders whose current destination is in this list are rejected
116    /// with [`RejectionReason::AccessDenied`](crate::error::RejectionReason::AccessDenied)
117    /// during the loading phase.
118    ///
119    /// Default: empty (no restrictions).
120    #[serde(default)]
121    pub restricted_stops: Vec<StopId>,
122    /// Energy profile for this elevator. If `None`, energy is not tracked.
123    ///
124    /// Requires the `energy` feature.
125    #[cfg(feature = "energy")]
126    #[serde(default)]
127    pub energy_profile: Option<crate::energy::EnergyProfile>,
128    /// Service mode at simulation start. Defaults to `Normal`.
129    #[serde(default)]
130    pub service_mode: Option<crate::components::ServiceMode>,
131    /// Speed multiplier for Inspection mode (0.0..1.0). Defaults to 0.25.
132    #[serde(default = "default_inspection_speed_factor")]
133    pub inspection_speed_factor: f64,
134}
135
136/// Default inspection speed factor (25% of normal speed).
137const fn default_inspection_speed_factor() -> f64 {
138    0.25
139}
140
141impl Default for ElevatorConfig {
142    /// Reasonable defaults matching the physics values the rest of
143    /// this struct's field docs advertise. Override any field with
144    /// struct-update syntax:
145    ///
146    /// ```
147    /// use elevator_core::config::ElevatorConfig;
148    /// use elevator_core::stop::StopId;
149    ///
150    /// let fast = ElevatorConfig {
151    ///     name: "Express".into(),
152    ///     max_speed: 6.0,
153    ///     starting_stop: StopId(0),
154    ///     ..Default::default()
155    /// };
156    /// # let _ = fast;
157    /// ```
158    ///
159    /// `starting_stop` defaults to `StopId(0)` — the conventional lobby
160    /// id. Override if your config uses a different bottom-stop id.
161    fn default() -> Self {
162        Self {
163            id: 0,
164            name: "Elevator 1".into(),
165            max_speed: 2.0,
166            acceleration: 1.5,
167            deceleration: 2.0,
168            weight_capacity: 800.0,
169            starting_stop: StopId(0),
170            door_open_ticks: 10,
171            door_transition_ticks: 5,
172            restricted_stops: Vec::new(),
173            #[cfg(feature = "energy")]
174            energy_profile: None,
175            service_mode: None,
176            inspection_speed_factor: default_inspection_speed_factor(),
177        }
178    }
179}
180
181/// Global simulation timing parameters.
182#[derive(Debug, Clone, Serialize, Deserialize)]
183pub struct SimulationParams {
184    /// Number of simulation ticks per real-time second.
185    ///
186    /// Must be positive. Determines the time delta per tick (`dt = 1.0 / ticks_per_second`).
187    /// Higher values yield finer-grained simulation at the cost of more
188    /// computation per wall-clock second.
189    ///
190    /// Default (from `SimulationBuilder`): `60.0`.
191    pub ticks_per_second: f64,
192}
193
194/// Passenger spawning parameters (used by the game layer).
195///
196/// The core simulation does not spawn passengers automatically; these values
197/// are advisory and consumed by game code or traffic generators.
198///
199/// This struct is always available regardless of feature flags. The built-in
200/// traffic generation that consumes it requires the `traffic` feature.
201#[derive(Debug, Clone, Serialize, Deserialize)]
202pub struct PassengerSpawnConfig {
203    /// Mean interval in ticks between passenger spawns.
204    ///
205    /// Used by traffic generators for Poisson-distributed arrivals.
206    ///
207    /// Units: simulation ticks.
208    /// Default (from `SimulationBuilder`): `120`.
209    pub mean_interval_ticks: u32,
210    /// `(min, max)` weight range for randomly spawned passengers.
211    ///
212    /// Weights are drawn uniformly from this range by traffic generators.
213    ///
214    /// Units: same as elevator `weight_capacity` (typically kilograms).
215    /// Default (from `SimulationBuilder`): `(50.0, 100.0)`.
216    pub weight_range: (f64, f64),
217}
218
219/// Configuration for a single line (physical path).
220///
221/// A line represents a shaft, tether, track, or other physical pathway
222/// that one or more elevator cars travel along. Lines belong to a
223/// [`GroupConfig`] for dispatch purposes.
224#[derive(Debug, Clone, Default, Serialize, Deserialize)]
225pub struct LineConfig {
226    /// Unique line identifier (within the config).
227    pub id: u32,
228    /// Human-readable name.
229    pub name: String,
230    /// Stops served by this line (references [`StopConfig::id`]).
231    pub serves: Vec<StopId>,
232    /// Elevators on this line.
233    pub elevators: Vec<ElevatorConfig>,
234    /// Physical orientation (defaults to Vertical).
235    #[serde(default)]
236    pub orientation: Orientation,
237    /// Optional floor-plan position.
238    #[serde(default)]
239    pub position: Option<SpatialPosition>,
240    /// Lowest reachable position (auto-computed from stops if `None`).
241    #[serde(default)]
242    pub min_position: Option<f64>,
243    /// Highest reachable position (auto-computed from stops if `None`).
244    #[serde(default)]
245    pub max_position: Option<f64>,
246    /// Max cars on this line (`None` = unlimited).
247    #[serde(default)]
248    pub max_cars: Option<usize>,
249}
250
251/// Configuration for an elevator dispatch group.
252///
253/// A group is the logical dispatch unit containing one or more lines.
254/// All elevators within the group share a single [`BuiltinStrategy`].
255///
256/// ## RON example — destination dispatch with controller latency
257///
258/// ```ron
259/// GroupConfig(
260///     id: 0,
261///     name: "Main",
262///     lines: [1],
263///     dispatch: Destination,
264///     hall_call_mode: Some(Destination),
265///     ack_latency_ticks: Some(15),
266/// )
267/// ```
268///
269/// `hall_call_mode` and `ack_latency_ticks` are optional; omitting them
270/// keeps the legacy behavior (Classic collective control, zero latency).
271#[derive(Debug, Clone, Serialize, Deserialize)]
272pub struct GroupConfig {
273    /// Unique group identifier.
274    pub id: u32,
275    /// Human-readable name.
276    pub name: String,
277    /// Line IDs belonging to this group (references [`LineConfig::id`]).
278    pub lines: Vec<u32>,
279    /// Dispatch strategy for this group.
280    pub dispatch: BuiltinStrategy,
281    /// Optional repositioning strategy for idle elevators.
282    ///
283    /// When `None`, idle elevators in this group stay where they stopped.
284    #[serde(default)]
285    pub reposition: Option<BuiltinReposition>,
286    /// How hall calls reveal rider destinations to dispatch.
287    ///
288    /// `None` defers to [`HallCallMode::default()`] (Classic collective
289    /// control). Set to `Some(HallCallMode::Destination)` to model a
290    /// DCS lobby-kiosk group, which is required to make
291    /// [`crate::dispatch::DestinationDispatch`] consult hall-call
292    /// destinations.
293    #[serde(default)]
294    pub hall_call_mode: Option<HallCallMode>,
295    /// Controller ack latency in ticks (button press → dispatch sees
296    /// the call). `None` means zero — dispatch sees presses immediately.
297    /// Realistic values at 60 Hz land around 5–30 ticks.
298    #[serde(default)]
299    pub ack_latency_ticks: Option<u32>,
300}