Skip to main content

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 7-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 seven
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::new()
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`] | Data types: [`Elevator`](components::Elevator), [`Rider`](components::Rider), [`Stop`](components::Stop), etc. |
63//! | [`events`] | [`Event`](events::Event) variants and the [`EventBus`](events::EventBus) |
64//! | [`metrics`] | Aggregate [`Metrics`](metrics::Metrics) (wait time, throughput, etc.) |
65//! | [`config`] | RON-deserializable [`SimConfig`](config::SimConfig) |
66//! | [`hooks`] | Lifecycle hook registration by [`Phase`](hooks::Phase) |
67//! | [`query`] | Entity query builder for filtering by component composition |
68//!
69//! ## Architecture overview
70//!
71//! ### 7-phase tick loop
72//!
73//! Each call to [`Simulation::step()`](sim::Simulation::step) runs these
74//! phases in order:
75//!
76//! 1. **`AdvanceTransient`** — transitions `Boarding→Riding`, `Exiting→Arrived`,
77//!    teleports walkers.
78//! 2. **Dispatch** — builds a [`DispatchManifest`](dispatch::DispatchManifest)
79//!    and calls each group's [`DispatchStrategy`](dispatch::DispatchStrategy).
80//! 3. **Reposition** — optional phase; moves idle elevators via
81//!    [`RepositionStrategy`](dispatch::RepositionStrategy) for better coverage.
82//! 4. **Movement** — applies trapezoidal velocity profiles, detects stop arrivals
83//!    and emits [`PassingFloor`](events::Event::PassingFloor) events.
84//! 5. **Doors** — ticks the [`DoorState`](door::DoorState) FSM per elevator.
85//! 6. **Loading** — boards/exits riders with capacity and preference checks.
86//! 7. **Metrics** — aggregates wait/ride times into [`Metrics`](metrics::Metrics)
87//!    and per-tag accumulators.
88//!
89//! ### Component relationships
90//!
91//! ```text
92//! Group ──contains──▶ Line ──has──▶ Elevator ──carries──▶ Rider
93//!   │                  │              │                      │
94//!   └── DispatchStrategy              └── Position           └── Route (optional)
95//!        RepositionStrategy               Velocity               Patience
96//!                      │                  DoorState               Preferences
97//!                      └── Stop (served stops along the shaft)
98//! ```
99//!
100//! ### Rider lifecycle
101//!
102//! Riders progress through phases managed by the simulation:
103//!
104//! ```text
105//! Waiting → Boarding → Riding → Exiting → Arrived
106//!    ↑         (1 tick)           (1 tick)     │
107//!    │                                         ├── settle_rider() → Resident
108//!    │                                         │                       │
109//!    │                                         └── despawn_rider()     │
110//!    │                                                                 │
111//!    └──────── reroute_rider() ────────────────────────────────────────┘
112//!
113//! Waiting ──(patience exceeded)──→ Abandoned ──→ settle/despawn
114//! ```
115//!
116//! - **`Arrived`** / **`Abandoned`**: terminal states; consumer must explicitly
117//!   settle or despawn the rider.
118//! - **`Resident`**: parked at a stop, invisible to dispatch and loading.
119//!   Query with [`Simulation::residents_at()`](sim::Simulation::residents_at).
120//! - **Population queries**: O(1) via maintained reverse index —
121//!   [`residents_at`](sim::Simulation::residents_at),
122//!   [`waiting_at`](sim::Simulation::waiting_at),
123//!   [`abandoned_at`](sim::Simulation::abandoned_at).
124//!
125//! ### Extension storage
126//!
127//! Games attach custom data to any entity without modifying the library:
128//!
129//! ```rust,ignore
130//! // Attach a VIP flag to a rider.
131//! world.insert_ext(rider_id, VipTag { priority: 1 }, "vip_tag");
132//!
133//! // Query it alongside built-in components.
134//! for (id, rider, vip) in world.query::<(EntityId, &Rider, &Ext<VipTag>)>().iter() {
135//!     // ...
136//! }
137//! ```
138//!
139//! Extensions participate in snapshots via `serialize_extensions()` /
140//! `register_ext::<T>(name)` + `load_extensions()`.
141//!
142//! ### Snapshot lifecycle
143//!
144//! 1. Capture: `sim.snapshot()` → [`WorldSnapshot`](snapshot::WorldSnapshot)
145//! 2. Serialize: serde (RON, JSON, bincode, etc.)
146//! 3. Deserialize + restore: `snapshot.restore(factory)` → new `Simulation`
147//! 4. Re-register extensions: `world.register_ext::<T>(name)` per type
148//! 5. Load extension data: `sim.load_extensions()`
149//!
150//! ### Performance
151//!
152//! | Operation | Complexity |
153//! |-----------|-----------|
154//! | Entity iteration | O(n) via `SlotMap` secondary maps |
155//! | Stop-passing detection | O(log n) via `SortedStops` binary search |
156//! | Dispatch manifest build | O(riders) per group |
157//! | Population queries | O(1) via `RiderIndex` reverse index |
158//! | Topology graph queries | O(V+E) BFS, lazy rebuild |
159//!
160//! For narrative guides, tutorials, and architecture walkthroughs, see the
161//! [mdBook documentation](https://andymai.github.io/elevator-core/).
162
163#![forbid(unsafe_code)]
164#![deny(clippy::unwrap_used, clippy::expect_used, clippy::panic)]
165
166/// Entity-component data types for the simulation.
167pub mod components;
168/// Entity identity and allocation.
169pub mod entity;
170/// Simulation error types.
171pub mod error;
172/// Typed identifiers for groups and other sim concepts.
173pub mod ids;
174/// ECS-style query builder for iterating entities by component composition.
175pub mod query;
176/// Tick-loop system phases (dispatch, reposition, movement, doors, loading, metrics).
177pub mod systems;
178/// Central entity/component storage.
179pub mod world;
180
181/// Fluent builder for constructing a Simulation programmatically.
182pub mod builder;
183/// Building and elevator configuration (RON deserialization).
184pub mod config;
185/// Pluggable dispatch strategies (SCAN, LOOK, nearest-car, ETD).
186pub mod dispatch;
187/// Door finite-state machine.
188pub mod door;
189/// Simplified energy modeling for elevators.
190#[cfg(feature = "energy")]
191pub mod energy;
192/// Simulation event bus and event types.
193pub mod events;
194/// Lifecycle hooks for injecting logic before/after simulation phases.
195pub mod hooks;
196/// Aggregate simulation metrics.
197pub mod metrics;
198/// Trapezoidal velocity-profile movement math.
199pub(crate) mod movement;
200/// Phase-partitioned reverse index for rider population queries.
201mod rider_index;
202/// Scenario replay from recorded event streams.
203pub mod scenario;
204/// Top-level simulation runner.
205pub mod sim;
206/// World snapshot for save/load.
207pub mod snapshot;
208/// Stop configuration helpers.
209pub mod stop;
210/// Tag-based per-entity metrics.
211pub mod tagged_metrics;
212/// Tick-to-wall-clock time conversion.
213pub mod time;
214/// Topology graph for cross-line connectivity queries.
215pub mod topology;
216/// Traffic generation (arrival patterns).
217#[cfg(feature = "traffic")]
218pub mod traffic;
219
220/// Register multiple extension types for snapshot deserialization in one call.
221///
222/// Eliminates the manual `register_ext` ceremony after snapshot restore.
223///
224/// # Example
225///
226/// ```ignore
227/// register_extensions!(sim.world_mut(), VipTag => "vip_tag", Priority => "priority");
228/// ```
229#[macro_export]
230macro_rules! register_extensions {
231    ($world:expr, $($ty:ty => $name:expr),+ $(,)?) => {
232        $( $world.register_ext::<$ty>($name); )+
233    };
234}
235
236/// Common imports for consumers of this library.
237///
238/// `use elevator_core::prelude::*;` pulls in the types you need for the vast
239/// majority of simulations — building a sim, stepping it, spawning riders,
240/// reading events and metrics, and writing custom dispatch strategies.
241///
242/// # Contents
243///
244/// - **Builder & simulation:** [`SimulationBuilder`], [`Simulation`],
245///   [`RiderBuilder`]
246/// - **Components:** [`Rider`], [`RiderPhase`], [`Elevator`], [`ElevatorPhase`],
247///   [`Stop`], [`Line`], [`Position`], [`Velocity`], [`FloorPosition`],
248///   [`Route`], [`Patience`], [`Preferences`], [`AccessControl`],
249///   [`Orientation`], [`ServiceMode`]
250/// - **Config:** [`SimConfig`], [`GroupConfig`], [`LineConfig`]
251/// - **Dispatch:** [`DispatchStrategy`], [`RepositionStrategy`], plus the
252///   built-in reposition strategies [`NearestIdle`], [`ReturnToLobby`],
253///   [`SpreadEvenly`], [`DemandWeighted`]
254/// - **Identity:** [`EntityId`], [`StopId`], [`GroupId`]
255/// - **Errors & events:** [`SimError`], [`RejectionReason`],
256///   [`RejectionContext`], [`Event`], [`EventBus`]
257/// - **Misc:** [`Metrics`], [`TimeAdapter`]
258///
259/// # Not included (import explicitly)
260///
261/// - Concrete dispatch implementations: `dispatch::scan::ScanDispatch`,
262///   `dispatch::look::LookDispatch`, `dispatch::nearest_car::NearestCarDispatch`,
263///   `dispatch::etd::EtdDispatch`
264/// - `ElevatorConfig` and `StopConfig` from [`crate::config`]
265/// - Traffic generation types from [`crate::traffic`] (feature-gated)
266/// - Snapshot types from [`crate::snapshot`]
267/// - The [`World`](crate::world::World) type (accessed via `sim.world()`,
268///   but required as a parameter when implementing custom dispatch)
269pub mod prelude {
270    pub use crate::builder::SimulationBuilder;
271    pub use crate::components::{
272        AccessControl, Elevator, ElevatorPhase, FloorPosition, Line, Orientation, Patience,
273        Position, Preferences, Rider, RiderPhase, Route, ServiceMode, Stop, Velocity,
274    };
275    pub use crate::config::{GroupConfig, LineConfig, SimConfig};
276    pub use crate::dispatch::reposition::{
277        DemandWeighted, NearestIdle, ReturnToLobby, SpreadEvenly,
278    };
279    pub use crate::dispatch::{DispatchStrategy, RepositionStrategy};
280    pub use crate::entity::EntityId;
281    pub use crate::error::{RejectionContext, RejectionReason, SimError};
282    pub use crate::events::{Event, EventBus};
283    pub use crate::ids::GroupId;
284    pub use crate::metrics::Metrics;
285    pub use crate::sim::{RiderBuilder, Simulation};
286    pub use crate::stop::StopId;
287    pub use crate::time::TimeAdapter;
288}
289
290#[cfg(test)]
291mod tests;