elevator_core/lib.rs
1//! # elevator-core
2//!
3//! Engine-agnostic, tick-based elevator simulation library for Rust.
4//!
5//! This crate provides the building blocks for modeling vertical transportation
6//! systems — from a 3-story office building to an orbital space elevator.
7//! Stops sit at arbitrary positions rather than uniform floors, and the
8//! simulation is driven by a deterministic 8-phase tick loop.
9//!
10//! ## Key capabilities
11//!
12//! - **Pluggable dispatch** — four built-in algorithms ([`dispatch::scan::ScanDispatch`],
13//! [`dispatch::look::LookDispatch`], [`dispatch::nearest_car::NearestCarDispatch`],
14//! [`dispatch::etd::EtdDispatch`]) plus the [`dispatch::DispatchStrategy`] trait
15//! for custom implementations.
16//! - **Trapezoidal motion profiles** — realistic acceleration, cruise, and
17//! deceleration computed per-tick.
18//! - **Extension components** — attach arbitrary `Serialize + DeserializeOwned`
19//! data to any entity via [`world::World::insert_ext`] without modifying the
20//! library.
21//! - **Lifecycle hooks** — inject logic before or after any of the eight
22//! simulation phases. See [`hooks::Phase`].
23//! - **Metrics and events** — query aggregate wait/ride times through
24//! [`metrics::Metrics`] and react to fine-grained tick events via
25//! [`events::Event`].
26//! - **Snapshot save/load** — capture and restore full simulation state with
27//! [`snapshot::WorldSnapshot`].
28//! - **Zero `unsafe` code** — enforced by `#![forbid(unsafe_code)]`.
29//!
30//! ## Quick start
31//!
32//! ```rust
33//! use elevator_core::prelude::*;
34//! use elevator_core::stop::StopConfig;
35//!
36//! let mut sim = SimulationBuilder::demo()
37//! .stops(vec![
38//! StopConfig { id: StopId(0), name: "Ground".into(), position: 0.0 },
39//! StopConfig { id: StopId(1), name: "Floor 2".into(), position: 4.0 },
40//! StopConfig { id: StopId(2), name: "Floor 3".into(), position: 8.0 },
41//! ])
42//! .build()
43//! .unwrap();
44//!
45//! sim.spawn_rider_by_stop_id(StopId(0), StopId(2), 75.0).unwrap();
46//!
47//! for _ in 0..1000 {
48//! sim.step();
49//! }
50//!
51//! assert!(sim.metrics().total_delivered() > 0);
52//! ```
53//!
54//! ## Crate layout
55//!
56//! | Module | Purpose |
57//! |--------|---------|
58//! | [`builder`] | Fluent [`SimulationBuilder`](builder::SimulationBuilder) API |
59//! | [`sim`] | Top-level [`Simulation`](sim::Simulation) runner and tick loop |
60//! | [`dispatch`] | Dispatch strategies and the [`DispatchStrategy`](dispatch::DispatchStrategy) trait |
61//! | [`world`] | ECS-style [`World`](world::World) with typed component storage |
62//! | [`components`] | Entity data types: [`Rider`](components::Rider), [`Elevator`](components::Elevator), [`Stop`](components::Stop), [`Line`](components::Line), [`Route`](components::Route), [`Patience`](components::Patience), [`Preferences`](components::Preferences), [`AccessControl`](components::AccessControl), [`DestinationQueue`](components::DestinationQueue), [`ServiceMode`](components::ServiceMode), [`Orientation`](components::Orientation), [`Position`](components::Position), [`Velocity`](components::Velocity), [`FloorPosition`](components::FloorPosition) |
63//! | [`config`] | RON-deserializable [`SimConfig`](config::SimConfig), [`GroupConfig`](config::GroupConfig), [`LineConfig`](config::LineConfig) |
64//! | [`events`] | [`Event`](events::Event) variants and the [`EventBus`](events::EventBus) |
65//! | [`metrics`] | Aggregate [`Metrics`](metrics::Metrics) (wait time, throughput, etc.) |
66//! | [`hooks`] | Lifecycle hook registration by [`Phase`](hooks::Phase) |
67//! | [`query`] | Entity query builder for filtering by component composition |
68//! | [`systems`] | Per-phase tick logic (dispatch, movement, doors, loading, ...) |
69//! | [`snapshot`] | [`WorldSnapshot`](snapshot::WorldSnapshot) save/restore with custom-strategy factory |
70//! | [`scenario`] | Deterministic scenario replay from recorded event streams |
71//! | [`topology`] | Lazy-rebuilt connectivity graph for cross-line routing |
72//! | [`traffic`] | [`TrafficSource`](traffic::TrafficSource) trait + `PoissonSource` (feature-gated) |
73//! | [`tagged_metrics`] | Per-tag metric accumulators for zone/line/priority breakdowns |
74//! | [`movement`] | Trapezoidal velocity-profile primitives ([`braking_distance`](movement::braking_distance), [`tick_movement`](movement::tick_movement)) |
75//! | [`door`] | Door finite-state machine ([`DoorState`](door::DoorState)) |
76//! | [`time`] | Tick-to-wall-clock conversion ([`TimeAdapter`](time::TimeAdapter)) |
77//! | `energy` | Simplified per-elevator energy modeling (gated behind the `energy` feature) |
78//! | [`stop`] | [`StopId`](stop::StopId) and [`StopConfig`](stop::StopConfig) |
79//! | [`entity`] | Opaque [`EntityId`](entity::EntityId) runtime identity |
80//! | [`ids`] | Config-level typed identifiers ([`GroupId`](ids::GroupId), etc.) |
81//! | [`error`] | [`SimError`](error::SimError), [`RejectionReason`](error::RejectionReason), [`RejectionContext`](error::RejectionContext) |
82//!
83//! ## Architecture overview
84//!
85//! ### 8-phase tick loop
86//!
87//! Each call to [`Simulation::step()`](sim::Simulation::step) runs these
88//! phases in order:
89//!
90//! 1. **`AdvanceTransient`** — transitions `Boarding→Riding`, `Exiting→Arrived`,
91//! teleports walkers.
92//! 2. **Dispatch** — builds a [`DispatchManifest`](dispatch::DispatchManifest)
93//! and calls each group's [`DispatchStrategy`](dispatch::DispatchStrategy).
94//! 3. **Reposition** — optional phase; moves idle elevators via
95//! [`RepositionStrategy`](dispatch::RepositionStrategy) for better coverage.
96//! 4. **`AdvanceQueue`** — reconciles each elevator's phase/target with the
97//! front of its [`DestinationQueue`](components::DestinationQueue), so
98//! imperative pushes from game code take effect before movement.
99//! 5. **Movement** — applies trapezoidal velocity profiles, detects stop arrivals
100//! and emits [`PassingFloor`](events::Event::PassingFloor) events.
101//! 6. **Doors** — ticks the [`DoorState`](door::DoorState) FSM per elevator.
102//! 7. **Loading** — boards/exits riders with capacity and preference checks.
103//! 8. **Metrics** — aggregates wait/ride times into [`Metrics`](metrics::Metrics)
104//! and per-tag accumulators.
105//!
106//! For full per-phase semantics (events emitted, edge cases, design rationale),
107//! see [`ARCHITECTURE.md`][arch] §3. This crate-level summary is the short
108//! form; `ARCHITECTURE.md` is canonical.
109//!
110//! [arch]: https://github.com/andymai/elevator-core/blob/main/crates/elevator-core/ARCHITECTURE.md
111//!
112//! ### Component relationships
113//!
114//! ```text
115//! Group ──contains──▶ Line ──has──▶ Elevator ──carries──▶ Rider
116//! │ │ │ │
117//! └── DispatchStrategy └── Position └── Route (optional)
118//! RepositionStrategy Velocity Patience
119//! │ DoorState Preferences
120//! └── Stop (served stops along the shaft)
121//! ```
122//!
123//! ### Rider lifecycle
124//!
125//! Riders progress through phases managed by the simulation:
126//!
127//! ```text
128//! Waiting → Boarding → Riding → Exiting → Arrived
129//! ↑ (1 tick) (1 tick) │
130//! │ ├── settle_rider() → Resident
131//! │ │ │
132//! │ └── despawn_rider() │
133//! │ │
134//! └──────── reroute_rider() ────────────────────────────────────────┘
135//!
136//! Waiting ──(patience exceeded)──→ Abandoned ──→ settle/despawn
137//! ```
138//!
139//! - **`Arrived`** / **`Abandoned`**: terminal states; consumer must explicitly
140//! settle or despawn the rider.
141//! - **`Resident`**: parked at a stop, invisible to dispatch and loading.
142//! Query with [`Simulation::residents_at()`](sim::Simulation::residents_at).
143//! - **Population queries**: O(1) via maintained reverse index —
144//! [`residents_at`](sim::Simulation::residents_at),
145//! [`waiting_at`](sim::Simulation::waiting_at),
146//! [`abandoned_at`](sim::Simulation::abandoned_at).
147//!
148//! ### Extension storage
149//!
150//! Games attach custom data to any entity without modifying the library:
151//!
152//! ```rust,ignore
153//! // Attach a VIP flag to a rider.
154//! world.insert_ext(rider_id, VipTag { priority: 1 }, "vip_tag");
155//!
156//! // Query it alongside built-in components.
157//! for (id, rider, vip) in world.query::<(EntityId, &Rider, &Ext<VipTag>)>().iter() {
158//! // ...
159//! }
160//! ```
161//!
162//! Extensions participate in snapshots via `serialize_extensions()` /
163//! `register_ext::<T>(name)` + `load_extensions()`.
164//!
165//! ### Snapshot lifecycle
166//!
167//! 1. Capture: `sim.snapshot()` → [`WorldSnapshot`](snapshot::WorldSnapshot)
168//! 2. Serialize: serde (RON, JSON, bincode, etc.)
169//! 3. Deserialize + restore: `snapshot.restore(factory)` → new `Simulation`
170//! 4. Re-register extensions: `world.register_ext::<T>(name)` per type
171//! 5. Load extension data: `sim.load_extensions()`
172//!
173//! ### Performance
174//!
175//! | Operation | Complexity |
176//! |-----------|-----------|
177//! | Entity iteration | O(n) via `SlotMap` secondary maps |
178//! | Stop-passing detection | O(log n) via `SortedStops` binary search |
179//! | Dispatch manifest build | O(riders) per group |
180//! | Population queries | O(1) via `RiderIndex` reverse index |
181//! | Topology graph queries | O(V+E) BFS, lazy rebuild |
182//!
183//! For narrative guides, tutorials, and architecture walkthroughs, see the
184//! [mdBook documentation](https://andymai.github.io/elevator-core/).
185
186#![forbid(unsafe_code)]
187#![deny(clippy::unwrap_used, clippy::expect_used, clippy::panic)]
188
189/// Entity-component data types for the simulation.
190pub mod components;
191/// Entity identity and allocation.
192pub mod entity;
193/// Simulation error types.
194pub mod error;
195/// Typed identifiers for groups and other sim concepts.
196pub mod ids;
197/// ECS-style query builder for iterating entities by component composition.
198pub mod query;
199/// Tick-loop system phases (dispatch, reposition, movement, doors, loading, metrics).
200pub mod systems;
201/// Central entity/component storage.
202pub mod world;
203
204/// Fluent builder for constructing a Simulation programmatically.
205pub mod builder;
206/// Building and elevator configuration (RON deserialization).
207pub mod config;
208/// Pluggable dispatch strategies (SCAN, LOOK, nearest-car, ETD).
209pub mod dispatch;
210/// Door finite-state machine.
211pub mod door;
212/// Simplified energy modeling for elevators.
213#[cfg(feature = "energy")]
214pub mod energy;
215/// Simulation event bus and event types.
216pub mod events;
217/// Lifecycle hooks for injecting logic before/after simulation phases.
218pub mod hooks;
219/// Aggregate simulation metrics.
220pub mod metrics;
221/// Trapezoidal velocity-profile movement math.
222///
223/// Exposes [`braking_distance`](movement::braking_distance) for consumers
224/// writing opportunistic dispatch strategies that need the kinematic answer
225/// without constructing a `Simulation`.
226pub mod movement;
227/// Phase-partitioned reverse index for rider population queries.
228mod rider_index;
229/// Scenario replay from recorded event streams.
230pub mod scenario;
231/// Top-level simulation runner.
232pub mod sim;
233/// World snapshot for save/load.
234pub mod snapshot;
235/// Stop configuration helpers.
236pub mod stop;
237/// Tag-based per-entity metrics.
238pub mod tagged_metrics;
239/// Tick-to-wall-clock time conversion.
240pub mod time;
241/// Topology graph for cross-line connectivity queries.
242pub mod topology;
243/// Traffic generation (arrival patterns).
244#[cfg(feature = "traffic")]
245pub mod traffic;
246
247/// Register multiple extension types for snapshot deserialization in one call.
248///
249/// Eliminates the manual `register_ext` ceremony after snapshot restore.
250///
251/// # Example
252///
253/// ```
254/// use elevator_core::prelude::*;
255/// use elevator_core::register_extensions;
256/// use serde::{Deserialize, Serialize};
257///
258/// #[derive(Clone, Debug, Serialize, Deserialize)]
259/// struct VipTag { level: u32 }
260///
261/// #[derive(Clone, Debug, Serialize, Deserialize)]
262/// struct Priority { rank: u8 }
263///
264/// let mut sim = SimulationBuilder::demo().build().unwrap();
265/// register_extensions!(sim.world_mut(), VipTag => "vip_tag", Priority => "priority");
266/// ```
267#[macro_export]
268macro_rules! register_extensions {
269 ($world:expr, $($ty:ty => $name:expr),+ $(,)?) => {
270 $( $world.register_ext::<$ty>($name); )+
271 };
272}
273
274/// Common imports for consumers of this library.
275///
276/// `use elevator_core::prelude::*;` pulls in the types you need for the vast
277/// majority of simulations — building a sim, stepping it, spawning riders,
278/// reading events and metrics, and writing custom dispatch strategies.
279///
280/// # Contents
281///
282/// - **Builder & simulation:** [`SimulationBuilder`](crate::builder::SimulationBuilder),
283/// [`Simulation`](crate::sim::Simulation),
284/// [`RiderBuilder`](crate::sim::RiderBuilder)
285/// - **Components:** [`Rider`](crate::components::Rider),
286/// [`RiderPhase`](crate::components::RiderPhase),
287/// [`Elevator`](crate::components::Elevator),
288/// [`ElevatorPhase`](crate::components::ElevatorPhase),
289/// [`Stop`](crate::components::Stop), [`Line`](crate::components::Line),
290/// [`Position`](crate::components::Position),
291/// [`Velocity`](crate::components::Velocity),
292/// [`FloorPosition`](crate::components::FloorPosition),
293/// [`Route`](crate::components::Route),
294/// [`Patience`](crate::components::Patience),
295/// [`Preferences`](crate::components::Preferences),
296/// [`AccessControl`](crate::components::AccessControl),
297/// [`Orientation`](crate::components::Orientation),
298/// [`ServiceMode`](crate::components::ServiceMode)
299/// - **Config:** [`SimConfig`](crate::config::SimConfig),
300/// [`GroupConfig`](crate::config::GroupConfig),
301/// [`LineConfig`](crate::config::LineConfig)
302/// - **Dispatch:** [`DispatchStrategy`](crate::dispatch::DispatchStrategy),
303/// [`RepositionStrategy`](crate::dispatch::RepositionStrategy), plus the
304/// built-in reposition strategies
305/// [`NearestIdle`](crate::dispatch::reposition::NearestIdle),
306/// [`ReturnToLobby`](crate::dispatch::reposition::ReturnToLobby),
307/// [`SpreadEvenly`](crate::dispatch::reposition::SpreadEvenly),
308/// [`DemandWeighted`](crate::dispatch::reposition::DemandWeighted)
309/// - **Identity:** [`EntityId`](crate::entity::EntityId),
310/// [`StopId`](crate::stop::StopId), [`GroupId`](crate::ids::GroupId)
311/// - **Errors & events:** [`SimError`](crate::error::SimError),
312/// [`RejectionReason`](crate::error::RejectionReason),
313/// [`RejectionContext`](crate::error::RejectionContext),
314/// [`Event`](crate::events::Event),
315/// [`EventBus`](crate::events::EventBus)
316/// - **Misc:** [`Metrics`](crate::metrics::Metrics),
317/// [`TimeAdapter`](crate::time::TimeAdapter)
318///
319/// # Not included (import explicitly)
320///
321/// - Concrete dispatch implementations: `dispatch::scan::ScanDispatch`,
322/// `dispatch::look::LookDispatch`, `dispatch::nearest_car::NearestCarDispatch`,
323/// `dispatch::etd::EtdDispatch`
324/// - `ElevatorConfig` and `StopConfig` from [`crate::config`]
325/// - Traffic generation types from [`crate::traffic`] (feature-gated)
326/// - Snapshot types from [`crate::snapshot`]
327/// - The [`World`](crate::world::World) type (accessed via `sim.world()`,
328/// but required as a parameter when implementing custom dispatch)
329pub mod prelude {
330 pub use crate::builder::SimulationBuilder;
331 pub use crate::components::{
332 AccessControl, DestinationQueue, Direction, Elevator, ElevatorPhase, FloorPosition, Line,
333 Orientation, Patience, Position, Preferences, Rider, RiderPhase, Route, ServiceMode, Stop,
334 Velocity,
335 };
336 pub use crate::config::{GroupConfig, LineConfig, SimConfig};
337 pub use crate::dispatch::reposition::{
338 DemandWeighted, NearestIdle, ReturnToLobby, SpreadEvenly,
339 };
340 pub use crate::dispatch::{DispatchStrategy, RepositionStrategy};
341 pub use crate::entity::EntityId;
342 pub use crate::error::{RejectionContext, RejectionReason, SimError};
343 pub use crate::events::{Event, EventBus, EventCategory};
344 pub use crate::ids::GroupId;
345 pub use crate::metrics::Metrics;
346 pub use crate::sim::{RiderBuilder, Simulation};
347 pub use crate::stop::StopId;
348 pub use crate::time::TimeAdapter;
349}
350
351#[cfg(test)]
352mod tests;