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** — five built-in algorithms ([`dispatch::scan::ScanDispatch`],
13//! [`dispatch::look::LookDispatch`], [`dispatch::nearest_car::NearestCarDispatch`],
14//! [`dispatch::etd::EtdDispatch`], [`dispatch::destination::DestinationDispatch`])
15//! plus the [`dispatch::DispatchStrategy`] trait 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(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//! ## Stability
57//!
58//! Every module below is classified as **stable**, **experimental**, or
59//! **internal**. Stable items break only in planned major versions
60//! with a deprecation cycle; experimental items may change in any
61//! minor version; internal items are not part of the supported surface.
62//! See [`STABILITY.md`](https://github.com/andymai/elevator-core/blob/main/STABILITY.md)
63//! for the full policy and cadence commitment.
64//!
65//! | Module | Purpose | Stability |
66//! |--------|---------|-----------|
67//! | [`builder`] | Fluent [`SimulationBuilder`](builder::SimulationBuilder) API | stable |
68//! | [`sim`] | Top-level [`Simulation`](sim::Simulation) runner and tick loop | stable (selected methods — see [`STABILITY.md`](https://github.com/andymai/elevator-core/blob/main/STABILITY.md)) |
69//! | [`dispatch`] | Dispatch strategies and the [`DispatchStrategy`](dispatch::DispatchStrategy) trait | stable |
70//! | [`world`] | ECS-style [`World`](world::World) with typed component storage | experimental |
71//! | [`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), [`SpatialPosition`](components::SpatialPosition) | stable (units + core), experimental (rest) |
72//! | [`config`] | RON-deserializable [`SimConfig`](config::SimConfig), [`GroupConfig`](config::GroupConfig), [`LineConfig`](config::LineConfig) | experimental |
73//! | [`events`] | [`Event`](events::Event) variants and the [`EventBus`](events::EventBus) | stable ([`Event`](events::Event)) |
74//! | [`metrics`] | Aggregate [`Metrics`](metrics::Metrics) (wait time, throughput, etc.) | stable |
75//! | [`hooks`] | Lifecycle hook registration by [`Phase`](hooks::Phase) | experimental |
76//! | [`query`] | Entity query builder for filtering by component composition | experimental |
77//! | [`systems`] | Per-phase tick logic (dispatch, movement, doors, loading, ...) | internal |
78//! | [`snapshot`] | [`WorldSnapshot`](snapshot::WorldSnapshot) save/restore with custom-strategy factory | stable |
79//! | [`scenario`] | Deterministic scenario replay from recorded event streams | experimental |
80//! | [`topology`] | Lazy-rebuilt connectivity graph for cross-line routing | experimental |
81//! | [`traffic`] | [`TrafficSource`](traffic::TrafficSource) trait + `PoissonSource` (feature-gated) | experimental |
82//! | [`tagged_metrics`] | Per-tag metric accumulators for zone/line/priority breakdowns | experimental |
83//! | [`movement`] | Trapezoidal velocity-profile primitives ([`braking_distance`](movement::braking_distance), [`tick_movement`](movement::tick_movement)) | experimental |
84//! | [`door`] | Door finite-state machine ([`DoorState`](door::DoorState)) | internal |
85//! | [`eta`] | Per-stop arrival-time estimation used by dispatch | internal |
86//! | [`time`] | Tick-to-wall-clock conversion ([`TimeAdapter`](time::TimeAdapter)) | experimental |
87//! | `energy` | Simplified per-elevator energy modeling (gated behind the `energy` feature) | experimental |
88//! | [`stop`] | [`StopId`](stop::StopId) and [`StopConfig`](stop::StopConfig) | stable |
89//! | [`entity`] | Opaque [`EntityId`](entity::EntityId) runtime identity | stable |
90//! | [`ids`] | Config-level typed identifiers ([`GroupId`](ids::GroupId), etc.) | experimental |
91//! | [`error`] | [`SimError`](error::SimError), [`RejectionReason`](error::RejectionReason), [`RejectionContext`](error::RejectionContext) | stable ([`SimError`](error::SimError)) |
92//!
93//! ## Architecture overview
94//!
95//! ### 8-phase tick loop
96//!
97//! Each call to [`Simulation::step()`](sim::Simulation::step) runs these
98//! phases in order:
99//!
100//! 1. **`AdvanceTransient`** — transitions `Boarding→Riding`, `Exiting→Arrived`,
101//! teleports walkers.
102//! 2. **Dispatch** — builds a [`DispatchManifest`](dispatch::DispatchManifest)
103//! and calls each group's [`DispatchStrategy`](dispatch::DispatchStrategy).
104//! 3. **Reposition** — optional phase; moves idle elevators via
105//! [`RepositionStrategy`](dispatch::RepositionStrategy) for better coverage.
106//! 4. **`AdvanceQueue`** — reconciles each elevator's phase/target with the
107//! front of its [`DestinationQueue`](components::DestinationQueue), so
108//! imperative pushes from game code take effect before movement.
109//! 5. **Movement** — applies trapezoidal velocity profiles, detects stop arrivals
110//! and emits [`PassingFloor`](events::Event::PassingFloor) events.
111//! 6. **Doors** — ticks the [`DoorState`](door::DoorState) FSM per elevator.
112//! 7. **Loading** — boards/exits riders with capacity and preference checks.
113//! 8. **Metrics** — aggregates wait/ride times into [`Metrics`](metrics::Metrics)
114//! and per-tag accumulators.
115//!
116//! For full per-phase semantics (events emitted, edge cases, design rationale),
117//! see the [guide](https://andymai.github.io/elevator-core/simulation-loop.html).
118//!
119//! ### Component relationships
120//!
121//! ```text
122//! Group ──contains──▶ Line ──has──▶ Elevator ──carries──▶ Rider
123//! │ │ │ │
124//! └── DispatchStrategy └── Position └── Route (optional)
125//! RepositionStrategy Velocity Patience
126//! │ DoorState Preferences
127//! └── Stop (served stops along the shaft)
128//! ```
129//!
130//! ### Rider lifecycle
131//!
132//! Riders progress through phases managed by the simulation:
133//!
134//! ```text
135//! Waiting → Boarding → Riding → Exiting → Arrived
136//! ↑ (1 tick) (1 tick) │
137//! │ ├── settle_rider() → Resident
138//! │ │ │
139//! │ └── despawn_rider() │
140//! │ │
141//! └──────── reroute_rider() ────────────────────────────────────────┘
142//!
143//! Waiting ──(patience exceeded)──→ Abandoned ──→ settle/despawn
144//! ```
145//!
146//! - **`Arrived`** / **`Abandoned`**: terminal states; consumer must explicitly
147//! settle or despawn the rider.
148//! - **`Resident`**: parked at a stop, invisible to dispatch and loading.
149//! Query with [`Simulation::residents_at()`](sim::Simulation::residents_at).
150//! - **Population queries**: O(1) via maintained reverse index —
151//! [`residents_at`](sim::Simulation::residents_at),
152//! [`waiting_at`](sim::Simulation::waiting_at),
153//! [`abandoned_at`](sim::Simulation::abandoned_at).
154//!
155//! ### Extension storage
156//!
157//! Games attach custom data to any entity without modifying the library:
158//!
159//! ```rust,no_run
160//! # use elevator_core::prelude::*;
161//! # use elevator_core::query::Ext;
162//! # use serde::{Serialize, Deserialize};
163//! # #[derive(Debug, Clone, Serialize, Deserialize)]
164//! # struct VipTag { priority: u32 }
165//! # fn run(world: &mut World, rider_id: EntityId) {
166//! // Attach a VIP flag to a rider.
167//! world.insert_ext(rider_id, VipTag { priority: 1 }, ExtKey::from_type_name());
168//!
169//! // Query it alongside built-in components.
170//! for (id, rider, vip) in world.query::<(EntityId, &Rider, &Ext<VipTag>)>().iter() {
171//! // ...
172//! }
173//! # }
174//! ```
175//!
176//! Extensions participate in snapshots via `serialize_extensions()` /
177//! `register_ext::<T>(key)` + `load_extensions()`.
178//!
179//! ### Snapshot lifecycle
180//!
181//! 1. Capture: `sim.snapshot()` → [`WorldSnapshot`](snapshot::WorldSnapshot)
182//! 2. Serialize: serde (RON, JSON, bincode, etc.)
183//! 3. Deserialize + restore: `snapshot.restore(factory)` → new `Simulation`
184//! 4. Re-register extensions: `world.register_ext::<T>(key)` per type
185//! 5. Load extension data: `sim.load_extensions()`
186//!
187//! For the common case (save-to-disk, load-from-disk), skip the format choice
188//! and use [`Simulation::snapshot_bytes`](sim::Simulation::snapshot_bytes) /
189//! [`Simulation::restore_bytes`](sim::Simulation::restore_bytes). The byte
190//! blob is postcard-encoded and carries a magic prefix plus the crate version:
191//! restoring bytes from a different `elevator-core` version returns
192//! [`SimError::SnapshotVersion`](error::SimError::SnapshotVersion) instead of
193//! silently producing a garbled sim. Determinism is bit-exact across builds
194//! of the same crate version, which makes snapshots viable as rollback-netcode
195//! checkpoints or deterministic replay fixtures.
196//!
197//! ### Performance
198//!
199//! | Operation | Complexity |
200//! |-----------|-----------|
201//! | Entity iteration | O(n) via `SlotMap` secondary maps |
202//! | Stop-passing detection | O(log n) via `SortedStops` binary search |
203//! | Dispatch manifest build | O(riders) per group |
204//! | Population queries | O(1) via `RiderIndex` reverse index |
205//! | Topology graph queries | O(V+E) BFS, lazy rebuild |
206//!
207//! ## Runtime upgrades
208//!
209//! Elevator kinematic and door-timing parameters can be mutated at runtime
210//! via the `Simulation::set_*` setters — handy for RPG-style upgrade systems
211//! or scripted events that boost speed, capacity, or door behavior mid-game.
212//!
213//! Each setter validates its input, mutates the underlying component, and
214//! emits an [`Event::ElevatorUpgraded`](events::Event::ElevatorUpgraded) so
215//! game code can react (score popups, SFX, UI). Velocity is preserved when
216//! kinematic parameters change — the integrator picks up the new values on
217//! the next tick without jerk. Door-timing changes apply to the next door
218//! cycle and never retroactively retime an in-progress transition.
219//!
220//! See [`examples/runtime_upgrades.rs`][rte] for an end-to-end demonstration
221//! that doubles a car's `max_speed` mid-run and prints the throughput delta.
222//!
223//! [rte]: https://github.com/andymai/elevator-core/blob/main/crates/elevator-core/examples/runtime_upgrades.rs
224//!
225//! ## Door control
226//!
227//! Games that want to drive elevator doors directly — e.g. the player
228//! pressing "open" or "close" on a cab panel in a first-person game, or an
229//! RPG where the player *is* the elevator — use the manual door-control
230//! API on [`Simulation`](sim::Simulation):
231//!
232//! - [`open_door`](sim::Simulation::open_door)
233//! - [`close_door`](sim::Simulation::close_door)
234//! - [`hold_door`](sim::Simulation::hold_door) (cumulative)
235//! - [`cancel_door_hold`](sim::Simulation::cancel_door_hold)
236//!
237//! Each call is either applied immediately (if the car is in a matching
238//! door-FSM state) or queued on the elevator's
239//! [`door_command_queue`](components::Elevator::door_command_queue) and
240//! re-tried every tick until it can be applied. The only hard errors are
241//! [`NotAnElevator`](error::SimError::NotAnElevator) / [`ElevatorDisabled`](error::SimError::ElevatorDisabled) and (for `hold_door`) a
242//! zero-tick argument — the rest return `Ok(())` and let the engine pick
243//! the right moment. A [`DoorCommand`](door::DoorCommand) can be:
244//!
245//! - `Open` — reverses a closing door; no-op if already open or opening;
246//! queues while the car is moving.
247//! - `Close` — forces an early close from `Loading`. Waits one tick if a
248//! rider is mid-boarding/exiting (safe-close).
249//! - `HoldOpen { ticks }` — adds to the remaining open dwell; two calls
250//! of 30 ticks stack to 60. Queues if doors aren't open yet.
251//! - `CancelHold` — clamps any accumulated hold back to the base dwell.
252//!
253//! Every command emits
254//! [`Event::DoorCommandQueued`](events::Event::DoorCommandQueued) when
255//! submitted and
256//! [`Event::DoorCommandApplied`](events::Event::DoorCommandApplied) when
257//! it actually takes effect — useful for driving UI feedback (button
258//! flashes, SFX) without polling the elevator every tick.
259//!
260//! See [`examples/door_commands.rs`][dex] for a runnable demo.
261//!
262//! [dex]: https://github.com/andymai/elevator-core/blob/main/crates/elevator-core/examples/door_commands.rs
263//!
264//! ## Sub-tick position interpolation
265//!
266//! Games that render at a higher framerate than the simulation ticks (e.g.
267//! a 60 Hz sim driving a 144 Hz camera, or a first-person game where the
268//! player is parented to an elevator car) need a smooth position between
269//! ticks. [`Simulation::position_at`](sim::Simulation::position_at) lerps
270//! between the snapshot taken at the start of the current tick and the
271//! post-tick position, using an `alpha` accumulator clamped to `[0.0, 1.0]`:
272//!
273//! ```text
274//! // typical fixed-timestep render loop
275//! accumulator += frame_dt;
276//! while accumulator >= sim.dt() {
277//! sim.step();
278//! accumulator -= sim.dt();
279//! }
280//! let alpha = accumulator / sim.dt();
281//! let y = sim.position_at(car, alpha).unwrap();
282//! ```
283//!
284//! The previous-position snapshot is refreshed automatically at the start
285//! of every [`step`](sim::Simulation::step). [`Simulation::velocity`](sim::Simulation::velocity) is
286//! a convenience that returns the raw `f64` along the shaft axis (signed:
287//! +up, -down) for camera tilt, motion blur, or cabin-sway effects.
288//!
289//! See [`examples/fp_player_rider.rs`][fpe] for a runnable demo.
290//!
291//! [fpe]: https://github.com/andymai/elevator-core/blob/main/crates/elevator-core/examples/fp_player_rider.rs
292//!
293//! ## Manual-drive mode
294//!
295//! Games where the player *is* the elevator — driving the car with a
296//! velocity stick, slamming an emergency brake, stopping between floors
297//! — use [`ServiceMode::Manual`](components::ServiceMode::Manual). Manual
298//! elevators are skipped by the automatic dispatch and repositioning
299//! phases; the consumer drives movement via:
300//!
301//! - [`Simulation::set_target_velocity`](sim::Simulation::set_target_velocity) — signed target speed (+up, -down), clamped to the car's `max_speed`.
302//! - [`Simulation::emergency_stop`](sim::Simulation::emergency_stop) — commands an immediate deceleration to zero.
303//!
304//! Physics still apply: velocity ramps toward the target using the car's
305//! `acceleration` / `deceleration` caps, and positions update at the same
306//! rate as normal elevators. Manual elevators can come to rest at any
307//! position — they are not required to align with a configured stop.
308//! Door behaviour is governed by the [manual door-control API](#door-control);
309//! nothing opens or closes automatically. Leaving `Manual` clears any
310//! pending velocity command.
311//!
312//! Each command emits
313//! [`Event::ManualVelocityCommanded`](events::Event::ManualVelocityCommanded)
314//! with the clamped target, or `None` for an emergency stop.
315//!
316//! See [`examples/manual_driver.rs`][mde] for a runnable demo.
317//!
318//! [mde]: https://github.com/andymai/elevator-core/blob/main/crates/elevator-core/examples/manual_driver.rs
319//!
320//! ## ETA queries
321//!
322//! Hall-call dispatch UIs, scheduling overlays, and "press to call" panels
323//! all need the same answer: *how long until this car shows up?* Two methods
324//! on [`Simulation`](sim::Simulation) compute it from the elevator's queued
325//! destinations, current kinematic state, and configured door dwell:
326//!
327//! - [`Simulation::eta`](sim::Simulation::eta) — seconds until a specific
328//! elevator reaches a specific stop, or an [`EtaError`](error::EtaError) if the
329//! stop isn't queued, the car is disabled, or the service mode excludes it.
330//! - [`Simulation::best_eta`](sim::Simulation::best_eta) — winner across all
331//! eligible elevators, optionally filtered by indicator-lamp direction
332//! ("which up-going car arrives first?").
333//!
334//! Both walk the queue in service order, summing closed-form trapezoidal
335//! travel time per leg plus the configured door cycle at every intermediate
336//! stop. The closed-form solver lives in [`eta::travel_time`] and tracks the
337//! per-tick integrator in [`movement::tick_movement`] to within a tick or
338//! two — close enough for UI countdowns; not a substitute for actually
339//! simulating to compare two dispatch policies.
340//!
341//! For narrative guides, tutorials, and architecture walkthroughs, see the
342//! [mdBook documentation](https://andymai.github.io/elevator-core/).
343
344#![forbid(unsafe_code)]
345#![deny(clippy::unwrap_used, clippy::expect_used, clippy::panic)]
346
347#[cfg(doctest)]
348mod doctests;
349
350/// Entity-component data types for the simulation.
351pub mod components;
352/// Entity identity and allocation.
353pub mod entity;
354/// Simulation error types.
355pub mod error;
356/// Typed identifiers for groups and other sim concepts.
357pub mod ids;
358/// ECS-style query builder for iterating entities by component composition.
359pub mod query;
360/// Tick-loop system phases (dispatch, reposition, movement, doors, loading, metrics).
361pub mod systems;
362/// Central entity/component storage.
363pub mod world;
364
365/// Rolling per-stop arrival log, the signal source for rate-driven
366/// dispatch and repositioning.
367pub mod arrival_log;
368/// Fluent builder for constructing a Simulation programmatically.
369pub mod builder;
370/// Building and elevator configuration (RON deserialization).
371pub mod config;
372/// Pluggable dispatch strategies (SCAN, LOOK, nearest-car, ETD).
373pub mod dispatch;
374/// Door finite-state machine.
375pub mod door;
376/// Simplified energy modeling for elevators.
377#[cfg(feature = "energy")]
378pub mod energy;
379/// ETA estimation for queued elevators (closed-form trapezoidal travel time).
380pub mod eta;
381/// Simulation event bus and event types.
382pub mod events;
383/// Lifecycle hooks for injecting logic before/after simulation phases.
384pub mod hooks;
385/// Aggregate simulation metrics.
386pub mod metrics;
387/// Trapezoidal velocity-profile movement math.
388///
389/// Exposes [`braking_distance`](movement::braking_distance) for consumers
390/// writing opportunistic dispatch strategies that need the kinematic answer
391/// without constructing a `Simulation`.
392pub mod movement;
393/// Phase-partitioned reverse index for rider population queries.
394mod rider_index;
395/// Scenario replay from recorded event streams.
396pub mod scenario;
397/// Top-level simulation runner.
398pub mod sim;
399/// World snapshot for save/load.
400pub mod snapshot;
401/// Stop configuration helpers.
402pub mod stop;
403/// Tag-based per-entity metrics.
404pub mod tagged_metrics;
405/// Tick-to-wall-clock time conversion.
406pub mod time;
407/// Topology graph for cross-line connectivity queries.
408pub mod topology;
409/// Traffic generation (arrival patterns).
410#[cfg(feature = "traffic")]
411pub mod traffic;
412/// Traffic-mode detector (up-peak / idle / inter-floor).
413pub mod traffic_detector;
414
415/// Register multiple extension types for snapshot deserialization in one call.
416///
417/// Eliminates the manual `register_ext` ceremony after snapshot restore.
418///
419/// # Examples
420///
421/// Name-less form (uses `type_name` automatically):
422///
423/// ```
424/// use elevator_core::prelude::*;
425/// use elevator_core::register_extensions;
426/// use serde::{Deserialize, Serialize};
427///
428/// #[derive(Clone, Debug, Serialize, Deserialize)]
429/// struct VipTag { level: u32 }
430///
431/// #[derive(Clone, Debug, Serialize, Deserialize)]
432/// struct Priority { rank: u8 }
433///
434/// let mut sim = SimulationBuilder::demo().build().unwrap();
435/// register_extensions!(sim.world_mut(), VipTag, Priority);
436/// ```
437///
438/// Named form (explicit storage name per type):
439///
440/// ```
441/// use elevator_core::prelude::*;
442/// use elevator_core::register_extensions;
443/// use serde::{Deserialize, Serialize};
444///
445/// #[derive(Clone, Debug, Serialize, Deserialize)]
446/// struct VipTag { level: u32 }
447///
448/// let mut sim = SimulationBuilder::demo().build().unwrap();
449/// register_extensions!(sim.world_mut(), VipTag => "vip_tag");
450/// ```
451#[macro_export]
452macro_rules! register_extensions {
453 ($world:expr, $($ty:ty),+ $(,)?) => {
454 $( $world.register_ext::<$ty>($crate::world::ExtKey::<$ty>::from_type_name()); )+
455 };
456 ($world:expr, $($ty:ty => $name:expr),+ $(,)?) => {
457 $( $world.register_ext::<$ty>($crate::world::ExtKey::<$ty>::new($name)); )+
458 };
459}
460
461/// Common imports for consumers of this library.
462///
463/// `use elevator_core::prelude::*;` pulls in the types you need for the vast
464/// majority of simulations — building a sim, stepping it, spawning riders,
465/// reading events and metrics, and writing custom dispatch strategies.
466///
467/// # Contents
468///
469/// - **Builder & simulation:** [`SimulationBuilder`](crate::builder::SimulationBuilder),
470/// [`Simulation`](crate::sim::Simulation),
471/// [`RiderBuilder`](crate::sim::RiderBuilder)
472/// - **Components:** [`Rider`](crate::components::Rider),
473/// [`RiderPhase`](crate::components::RiderPhase),
474/// [`RiderPhaseKind`](crate::components::RiderPhaseKind),
475/// [`Elevator`](crate::components::Elevator),
476/// [`ElevatorPhase`](crate::components::ElevatorPhase),
477/// [`Stop`](crate::components::Stop), [`Line`](crate::components::Line),
478/// [`Position`](crate::components::Position),
479/// [`Velocity`](crate::components::Velocity),
480/// [`SpatialPosition`](crate::components::SpatialPosition),
481/// [`Route`](crate::components::Route),
482/// [`Patience`](crate::components::Patience),
483/// [`Preferences`](crate::components::Preferences),
484/// [`AccessControl`](crate::components::AccessControl),
485/// [`DestinationQueue`](crate::components::DestinationQueue),
486/// [`Direction`](crate::components::Direction),
487/// [`Orientation`](crate::components::Orientation),
488/// [`ServiceMode`](crate::components::ServiceMode)
489/// - **Config:** [`SimConfig`](crate::config::SimConfig),
490/// [`ElevatorConfig`](crate::config::ElevatorConfig),
491/// [`GroupConfig`](crate::config::GroupConfig),
492/// [`LineConfig`](crate::config::LineConfig),
493/// [`StopConfig`](crate::stop::StopConfig)
494/// - **Dispatch:** [`DispatchStrategy`](crate::dispatch::DispatchStrategy),
495/// [`RepositionStrategy`](crate::dispatch::RepositionStrategy),
496/// [`DispatchDecision`](crate::dispatch::DispatchDecision),
497/// [`DispatchManifest`](crate::dispatch::DispatchManifest),
498/// [`ElevatorGroup`](crate::dispatch::ElevatorGroup),
499/// [`AssignedCar`](crate::dispatch::AssignedCar),
500/// [`RankContext`](crate::dispatch::RankContext),
501/// [`DestinationDispatch`](crate::dispatch::DestinationDispatch),
502/// [`ScanDispatch`](crate::dispatch::ScanDispatch),
503/// [`LookDispatch`](crate::dispatch::LookDispatch),
504/// [`NearestCarDispatch`](crate::dispatch::NearestCarDispatch),
505/// [`EtdDispatch`](crate::dispatch::EtdDispatch),
506/// plus the built-in reposition strategies
507/// [`NearestIdle`](crate::dispatch::reposition::NearestIdle),
508/// [`ReturnToLobby`](crate::dispatch::reposition::ReturnToLobby),
509/// [`SpreadEvenly`](crate::dispatch::reposition::SpreadEvenly),
510/// [`DemandWeighted`](crate::dispatch::reposition::DemandWeighted),
511/// [`PredictiveParking`](crate::dispatch::reposition::PredictiveParking)
512/// - **Identity:** [`EntityId`](crate::entity::EntityId),
513/// [`StopId`](crate::stop::StopId), [`StopRef`](crate::stop::StopRef),
514/// [`GroupId`](crate::ids::GroupId)
515/// - **Errors & events:** [`SimError`](crate::error::SimError),
516/// [`EtaError`](crate::error::EtaError),
517/// [`RejectionReason`](crate::error::RejectionReason),
518/// [`RejectionContext`](crate::error::RejectionContext),
519/// [`Event`](crate::events::Event),
520/// [`EventBus`](crate::events::EventBus),
521/// [`EventCategory`](crate::events::EventCategory)
522/// - **World & misc:** [`World`](crate::world::World),
523/// [`Metrics`](crate::metrics::Metrics),
524/// [`TimeAdapter`](crate::time::TimeAdapter),
525/// [`ExtKey`](crate::world::ExtKey)
526///
527/// # Not included (import explicitly)
528///
529/// - Traffic generation types from [`crate::traffic`] (feature-gated)
530/// - Snapshot types from [`crate::snapshot`]
531pub mod prelude {
532 pub use crate::builder::SimulationBuilder;
533 pub use crate::components::{
534 Accel, AccessControl, DestinationQueue, Direction, Elevator, ElevatorPhase, Line,
535 Orientation, Patience, Position, Preferences, Rider, RiderPhase, RiderPhaseKind, Route,
536 ServiceMode, SpatialPosition, Speed, Stop, UnitError, Velocity, Weight,
537 };
538 pub use crate::config::{ElevatorConfig, GroupConfig, LineConfig, SimConfig};
539 pub use crate::dispatch::reposition::{
540 DemandWeighted, NearestIdle, PredictiveParking, ReturnToLobby, SpreadEvenly,
541 };
542 pub use crate::dispatch::{
543 AssignedCar, DestinationDispatch, DispatchDecision, DispatchManifest, DispatchStrategy,
544 ElevatorGroup, EtdDispatch, LookDispatch, NearestCarDispatch, RankContext,
545 RepositionStrategy, ScanDispatch,
546 };
547 pub use crate::entity::{ElevatorId, EntityId, RiderId};
548 pub use crate::error::{EtaError, RejectionContext, RejectionReason, SimError};
549 pub use crate::events::{Event, EventBus, EventCategory};
550 pub use crate::ids::GroupId;
551 pub use crate::metrics::Metrics;
552 pub use crate::sim::{RiderBuilder, Simulation};
553 pub use crate::stop::{StopConfig, StopId, StopRef};
554 pub use crate::time::TimeAdapter;
555 pub use crate::world::{ExtKey, World};
556}
557
558#[cfg(test)]
559mod tests;