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 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(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), [`SpatialPosition`](components::SpatialPosition) |
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 }, ExtKey::from_type_name());
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>(key)` + `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>(key)` per type
171//! 5. Load extension data: `sim.load_extensions()`
172//!
173//! For the common case (save-to-disk, load-from-disk), skip the format choice
174//! and use [`Simulation::snapshot_bytes`](sim::Simulation::snapshot_bytes) /
175//! [`Simulation::restore_bytes`](sim::Simulation::restore_bytes). The byte
176//! blob is postcard-encoded and carries a magic prefix plus the crate version:
177//! restoring bytes from a different `elevator-core` version returns
178//! [`SimError::SnapshotVersion`](error::SimError::SnapshotVersion) instead of
179//! silently producing a garbled sim. Determinism is bit-exact across builds
180//! of the same crate version, which makes snapshots viable as rollback-netcode
181//! checkpoints or deterministic replay fixtures.
182//!
183//! ### Performance
184//!
185//! | Operation | Complexity |
186//! |-----------|-----------|
187//! | Entity iteration | O(n) via `SlotMap` secondary maps |
188//! | Stop-passing detection | O(log n) via `SortedStops` binary search |
189//! | Dispatch manifest build | O(riders) per group |
190//! | Population queries | O(1) via `RiderIndex` reverse index |
191//! | Topology graph queries | O(V+E) BFS, lazy rebuild |
192//!
193//! ## Runtime upgrades
194//!
195//! Elevator kinematic and door-timing parameters can be mutated at runtime
196//! via the `Simulation::set_*` setters — handy for RPG-style upgrade systems
197//! or scripted events that boost speed, capacity, or door behavior mid-game.
198//!
199//! Each setter validates its input, mutates the underlying component, and
200//! emits an [`Event::ElevatorUpgraded`](events::Event::ElevatorUpgraded) so
201//! game code can react (score popups, SFX, UI). Velocity is preserved when
202//! kinematic parameters change — the integrator picks up the new values on
203//! the next tick without jerk. Door-timing changes apply to the next door
204//! cycle and never retroactively retime an in-progress transition.
205//!
206//! See [`examples/runtime_upgrades.rs`][rte] for an end-to-end demonstration
207//! that doubles a car's `max_speed` mid-run and prints the throughput delta.
208//!
209//! [rte]: https://github.com/andymai/elevator-core/blob/main/crates/elevator-core/examples/runtime_upgrades.rs
210//!
211//! ## Door control
212//!
213//! Games that want to drive elevator doors directly — e.g. the player
214//! pressing "open" or "close" on a cab panel in a first-person game, or an
215//! RPG where the player *is* the elevator — use the manual door-control
216//! API on [`Simulation`](sim::Simulation):
217//!
218//! - [`open_door`](sim::Simulation::open_door)
219//! - [`close_door`](sim::Simulation::close_door)
220//! - [`hold_door`](sim::Simulation::hold_door) (cumulative)
221//! - [`cancel_door_hold`](sim::Simulation::cancel_door_hold)
222//!
223//! Each call is either applied immediately (if the car is in a matching
224//! door-FSM state) or queued on the elevator's
225//! [`door_command_queue`](components::Elevator::door_command_queue) and
226//! re-tried every tick until it can be applied. The only hard errors are
227//! [`NotAnElevator`](error::SimError::NotAnElevator) / [`ElevatorDisabled`](error::SimError::ElevatorDisabled) and (for `hold_door`) a
228//! zero-tick argument — the rest return `Ok(())` and let the engine pick
229//! the right moment. A [`DoorCommand`](door::DoorCommand) can be:
230//!
231//! - `Open` — reverses a closing door; no-op if already open or opening;
232//!   queues while the car is moving.
233//! - `Close` — forces an early close from `Loading`. Waits one tick if a
234//!   rider is mid-boarding/exiting (safe-close).
235//! - `HoldOpen { ticks }` — adds to the remaining open dwell; two calls
236//!   of 30 ticks stack to 60. Queues if doors aren't open yet.
237//! - `CancelHold` — clamps any accumulated hold back to the base dwell.
238//!
239//! Every command emits
240//! [`Event::DoorCommandQueued`](events::Event::DoorCommandQueued) when
241//! submitted and
242//! [`Event::DoorCommandApplied`](events::Event::DoorCommandApplied) when
243//! it actually takes effect — useful for driving UI feedback (button
244//! flashes, SFX) without polling the elevator every tick.
245//!
246//! See [`examples/door_commands.rs`][dex] for a runnable demo.
247//!
248//! [dex]: https://github.com/andymai/elevator-core/blob/main/crates/elevator-core/examples/door_commands.rs
249//!
250//! ## Sub-tick position interpolation
251//!
252//! Games that render at a higher framerate than the simulation ticks (e.g.
253//! a 60 Hz sim driving a 144 Hz camera, or a first-person game where the
254//! player is parented to an elevator car) need a smooth position between
255//! ticks. [`Simulation::position_at`](sim::Simulation::position_at) lerps
256//! between the snapshot taken at the start of the current tick and the
257//! post-tick position, using an `alpha` accumulator clamped to `[0.0, 1.0]`:
258//!
259//! ```text
260//! // typical fixed-timestep render loop
261//! accumulator += frame_dt;
262//! while accumulator >= sim.dt() {
263//!     sim.step();
264//!     accumulator -= sim.dt();
265//! }
266//! let alpha = accumulator / sim.dt();
267//! let y = sim.position_at(car, alpha).unwrap();
268//! ```
269//!
270//! The previous-position snapshot is refreshed automatically at the start
271//! of every [`step`](sim::Simulation::step). [`Simulation::velocity`] is
272//! a convenience that returns the raw `f64` along the shaft axis (signed:
273//! +up, -down) for camera tilt, motion blur, or cabin-sway effects.
274//!
275//! See [`examples/fp_player_rider.rs`][fpe] for a runnable demo.
276//!
277//! [fpe]: https://github.com/andymai/elevator-core/blob/main/crates/elevator-core/examples/fp_player_rider.rs
278//!
279//! ## Manual-drive mode
280//!
281//! Games where the player *is* the elevator — driving the car with a
282//! velocity stick, slamming an emergency brake, stopping between floors
283//! — use [`ServiceMode::Manual`](components::ServiceMode::Manual). Manual
284//! elevators are skipped by the automatic dispatch and repositioning
285//! phases; the consumer drives movement via:
286//!
287//! - [`Simulation::set_target_velocity`](sim::Simulation::set_target_velocity) — signed target speed (+up, -down), clamped to the car's `max_speed`.
288//! - [`Simulation::emergency_stop`](sim::Simulation::emergency_stop) — commands an immediate deceleration to zero.
289//!
290//! Physics still apply: velocity ramps toward the target using the car's
291//! `acceleration` / `deceleration` caps, and positions update at the same
292//! rate as normal elevators. Manual elevators can come to rest at any
293//! position — they are not required to align with a configured stop.
294//! Door behaviour is governed by the [manual door-control API](#door-control);
295//! nothing opens or closes automatically. Leaving `Manual` clears any
296//! pending velocity command.
297//!
298//! Each command emits
299//! [`Event::ManualVelocityCommanded`](events::Event::ManualVelocityCommanded)
300//! with the clamped target, or `None` for an emergency stop.
301//!
302//! See [`examples/manual_driver.rs`][mde] for a runnable demo.
303//!
304//! [mde]: https://github.com/andymai/elevator-core/blob/main/crates/elevator-core/examples/manual_driver.rs
305//!
306//! ## ETA queries
307//!
308//! Hall-call dispatch UIs, scheduling overlays, and "press to call" panels
309//! all need the same answer: *how long until this car shows up?* Two methods
310//! on [`Simulation`](sim::Simulation) compute it from the elevator's queued
311//! destinations, current kinematic state, and configured door dwell:
312//!
313//! - [`Simulation::eta`](sim::Simulation::eta) — seconds until a specific
314//!   elevator reaches a specific stop, or an [`EtaError`](error::EtaError) if the
315//!   stop isn't queued, the car is disabled, or the service mode excludes it.
316//! - [`Simulation::best_eta`](sim::Simulation::best_eta) — winner across all
317//!   eligible elevators, optionally filtered by indicator-lamp direction
318//!   ("which up-going car arrives first?").
319//!
320//! Both walk the queue in service order, summing closed-form trapezoidal
321//! travel time per leg plus the configured door cycle at every intermediate
322//! stop. The closed-form solver lives in [`eta::travel_time`] and tracks the
323//! per-tick integrator in [`movement::tick_movement`] to within a tick or
324//! two — close enough for UI countdowns; not a substitute for actually
325//! simulating to compare two dispatch policies.
326//!
327//! For narrative guides, tutorials, and architecture walkthroughs, see the
328//! [mdBook documentation](https://andymai.github.io/elevator-core/).
329
330#![forbid(unsafe_code)]
331#![deny(clippy::unwrap_used, clippy::expect_used, clippy::panic)]
332
333/// Entity-component data types for the simulation.
334pub mod components;
335/// Entity identity and allocation.
336pub mod entity;
337/// Simulation error types.
338pub mod error;
339/// Typed identifiers for groups and other sim concepts.
340pub mod ids;
341/// ECS-style query builder for iterating entities by component composition.
342pub mod query;
343/// Tick-loop system phases (dispatch, reposition, movement, doors, loading, metrics).
344pub mod systems;
345/// Central entity/component storage.
346pub mod world;
347
348/// Fluent builder for constructing a Simulation programmatically.
349pub mod builder;
350/// Building and elevator configuration (RON deserialization).
351pub mod config;
352/// Pluggable dispatch strategies (SCAN, LOOK, nearest-car, ETD).
353pub mod dispatch;
354/// Door finite-state machine.
355pub mod door;
356/// Simplified energy modeling for elevators.
357#[cfg(feature = "energy")]
358pub mod energy;
359/// ETA estimation for queued elevators (closed-form trapezoidal travel time).
360pub mod eta;
361/// Simulation event bus and event types.
362pub mod events;
363/// Lifecycle hooks for injecting logic before/after simulation phases.
364pub mod hooks;
365/// Aggregate simulation metrics.
366pub mod metrics;
367/// Trapezoidal velocity-profile movement math.
368///
369/// Exposes [`braking_distance`](movement::braking_distance) for consumers
370/// writing opportunistic dispatch strategies that need the kinematic answer
371/// without constructing a `Simulation`.
372pub mod movement;
373/// Phase-partitioned reverse index for rider population queries.
374mod rider_index;
375/// Scenario replay from recorded event streams.
376pub mod scenario;
377/// Top-level simulation runner.
378pub mod sim;
379/// World snapshot for save/load.
380pub mod snapshot;
381/// Stop configuration helpers.
382pub mod stop;
383/// Tag-based per-entity metrics.
384pub mod tagged_metrics;
385/// Tick-to-wall-clock time conversion.
386pub mod time;
387/// Topology graph for cross-line connectivity queries.
388pub mod topology;
389/// Traffic generation (arrival patterns).
390#[cfg(feature = "traffic")]
391pub mod traffic;
392
393/// Register multiple extension types for snapshot deserialization in one call.
394///
395/// Eliminates the manual `register_ext` ceremony after snapshot restore.
396///
397/// # Examples
398///
399/// Name-less form (uses `type_name` automatically):
400///
401/// ```
402/// use elevator_core::prelude::*;
403/// use elevator_core::register_extensions;
404/// use serde::{Deserialize, Serialize};
405///
406/// #[derive(Clone, Debug, Serialize, Deserialize)]
407/// struct VipTag { level: u32 }
408///
409/// #[derive(Clone, Debug, Serialize, Deserialize)]
410/// struct Priority { rank: u8 }
411///
412/// let mut sim = SimulationBuilder::demo().build().unwrap();
413/// register_extensions!(sim.world_mut(), VipTag, Priority);
414/// ```
415///
416/// Named form (explicit storage name per type):
417///
418/// ```
419/// use elevator_core::prelude::*;
420/// use elevator_core::register_extensions;
421/// use serde::{Deserialize, Serialize};
422///
423/// #[derive(Clone, Debug, Serialize, Deserialize)]
424/// struct VipTag { level: u32 }
425///
426/// let mut sim = SimulationBuilder::demo().build().unwrap();
427/// register_extensions!(sim.world_mut(), VipTag => "vip_tag");
428/// ```
429#[macro_export]
430macro_rules! register_extensions {
431    ($world:expr, $($ty:ty),+ $(,)?) => {
432        $( $world.register_ext::<$ty>($crate::world::ExtKey::<$ty>::from_type_name()); )+
433    };
434    ($world:expr, $($ty:ty => $name:expr),+ $(,)?) => {
435        $( $world.register_ext::<$ty>($crate::world::ExtKey::<$ty>::new($name)); )+
436    };
437}
438
439/// Common imports for consumers of this library.
440///
441/// `use elevator_core::prelude::*;` pulls in the types you need for the vast
442/// majority of simulations — building a sim, stepping it, spawning riders,
443/// reading events and metrics, and writing custom dispatch strategies.
444///
445/// # Contents
446///
447/// - **Builder & simulation:** [`SimulationBuilder`](crate::builder::SimulationBuilder),
448///   [`Simulation`](crate::sim::Simulation),
449///   [`RiderBuilder`](crate::sim::RiderBuilder)
450/// - **Components:** [`Rider`](crate::components::Rider),
451///   [`RiderPhase`](crate::components::RiderPhase),
452///   [`RiderPhaseKind`](crate::components::RiderPhaseKind),
453///   [`Elevator`](crate::components::Elevator),
454///   [`ElevatorPhase`](crate::components::ElevatorPhase),
455///   [`Stop`](crate::components::Stop), [`Line`](crate::components::Line),
456///   [`Position`](crate::components::Position),
457///   [`Velocity`](crate::components::Velocity),
458///   [`SpatialPosition`](crate::components::SpatialPosition),
459///   [`Route`](crate::components::Route),
460///   [`Patience`](crate::components::Patience),
461///   [`Preferences`](crate::components::Preferences),
462///   [`AccessControl`](crate::components::AccessControl),
463///   [`DestinationQueue`](crate::components::DestinationQueue),
464///   [`Direction`](crate::components::Direction),
465///   [`Orientation`](crate::components::Orientation),
466///   [`ServiceMode`](crate::components::ServiceMode)
467/// - **Config:** [`SimConfig`](crate::config::SimConfig),
468///   [`GroupConfig`](crate::config::GroupConfig),
469///   [`LineConfig`](crate::config::LineConfig)
470/// - **Dispatch:** [`DispatchStrategy`](crate::dispatch::DispatchStrategy),
471///   [`RepositionStrategy`](crate::dispatch::RepositionStrategy),
472///   [`AssignedCar`](crate::dispatch::AssignedCar),
473///   [`DestinationDispatch`](crate::dispatch::DestinationDispatch),
474///   plus the built-in reposition strategies
475///   [`NearestIdle`](crate::dispatch::reposition::NearestIdle),
476///   [`ReturnToLobby`](crate::dispatch::reposition::ReturnToLobby),
477///   [`SpreadEvenly`](crate::dispatch::reposition::SpreadEvenly),
478///   [`DemandWeighted`](crate::dispatch::reposition::DemandWeighted)
479/// - **Identity:** [`EntityId`](crate::entity::EntityId),
480///   [`StopId`](crate::stop::StopId), [`StopRef`](crate::stop::StopRef),
481///   [`GroupId`](crate::ids::GroupId)
482/// - **Errors & events:** [`SimError`](crate::error::SimError),
483///   [`EtaError`](crate::error::EtaError),
484///   [`RejectionReason`](crate::error::RejectionReason),
485///   [`RejectionContext`](crate::error::RejectionContext),
486///   [`Event`](crate::events::Event),
487///   [`EventBus`](crate::events::EventBus),
488///   [`EventCategory`](crate::events::EventCategory)
489/// - **Misc:** [`Metrics`](crate::metrics::Metrics),
490///   [`TimeAdapter`](crate::time::TimeAdapter),
491///   [`ExtKey`](crate::world::ExtKey)
492///
493/// # Not included (import explicitly)
494///
495/// - Concrete dispatch implementations: `dispatch::scan::ScanDispatch`,
496///   `dispatch::look::LookDispatch`, `dispatch::nearest_car::NearestCarDispatch`,
497///   `dispatch::etd::EtdDispatch`
498/// - `ElevatorConfig` from [`crate::config`] and `StopConfig` from [`crate::stop`]
499/// - Traffic generation types from [`crate::traffic`] (feature-gated)
500/// - Snapshot types from [`crate::snapshot`]
501/// - The [`World`](crate::world::World) type (accessed via `sim.world()`,
502///   but required as a parameter when implementing custom dispatch)
503pub mod prelude {
504    pub use crate::builder::SimulationBuilder;
505    pub use crate::components::{
506        AccessControl, DestinationQueue, Direction, Elevator, ElevatorPhase, Line, Orientation,
507        Patience, Position, Preferences, Rider, RiderPhase, RiderPhaseKind, Route, ServiceMode,
508        SpatialPosition, Stop, Velocity,
509    };
510    pub use crate::config::{GroupConfig, LineConfig, SimConfig};
511    pub use crate::dispatch::reposition::{
512        DemandWeighted, NearestIdle, ReturnToLobby, SpreadEvenly,
513    };
514    pub use crate::dispatch::{
515        AssignedCar, DestinationDispatch, DispatchStrategy, RepositionStrategy,
516    };
517    pub use crate::entity::EntityId;
518    pub use crate::error::{EtaError, RejectionContext, RejectionReason, SimError};
519    pub use crate::events::{Event, EventBus, EventCategory};
520    pub use crate::ids::GroupId;
521    pub use crate::metrics::Metrics;
522    pub use crate::sim::{RiderBuilder, Simulation};
523    pub use crate::stop::{StopId, StopRef};
524    pub use crate::time::TimeAdapter;
525    pub use crate::world::ExtKey;
526}
527
528#[cfg(test)]
529mod tests;