elevator-core
A tick-based elevator simulation engine for Rust. Model anything from a 3-story office building to an orbital space elevator. Pluggable dispatch strategies, realistic trapezoidal motion profiles, and an extension system let you build exactly the simulation you need.
Table of Contents
- Getting Started
- Examples
- Architecture
- Dispatch Strategies
- Configuration
- Bevy Integration
- Feature Flags
- License
Getting Started
Add elevator-core to your project:
From there, the typical workflow is:
- Configure stops -- define the building layout with named stops at arbitrary positions.
- Build the simulation --
SimulationBuildervalidates the config and returns a ready-to-runSimulation. - Spawn riders -- place riders at origin stops with a destination and weight.
- Step the loop -- each call to
sim.step()advances one tick through all six phases. - Read metrics -- query aggregate wait times, ride times, and throughput at any point.
Examples
Basic
Create a simulation, spawn a rider, and run until delivery.
use *;
let mut sim = new
.stop
.stop
.stop
.build
.unwrap;
// Spawn a 75 kg rider going from Ground to Floor 3.
sim.spawn_rider_by_stop_id.unwrap;
// Run for 1000 ticks.
for _ in 0..1000
println!;
Custom Dispatch
Swap in a different dispatch algorithm and react to simulation events.
use *;
use EtdDispatch;
let mut sim = new
.stop
.stop
.stop
.dispatch
.build
.unwrap;
sim.spawn_rider_by_stop_id.unwrap;
for _ in 0..2000
Extensions and Hooks
Attach custom data to entities and inject logic into the tick loop.
use *;
use ;
let mut sim = new
.stop
.stop
.
.after
.build
.unwrap;
// Spawn a rider and tag them as VIP.
let rider_id = sim.spawn_rider_by_stop_id.unwrap;
sim.world_mut.insert_ext;
// Later, read back the extension data.
if let Some = sim.world.
Architecture
Each call to sim.step() executes six phases:
┌───────────────┐ ┌──────────┐ ┌──────────┐
│ Advance │──▶│ Dispatch │──▶│ Movement │
│ Transient │ │ │ │ │
└───────────────┘ └──────────┘ └──────────┘
│
┌───────────────┐ ┌──────────┐ ┌──────────┐
│ Metrics │◀──│ Loading │◀──│ Doors │
│ │ │ │ │ │
└───────────────┘ └──────────┘ └──────────┘
| Phase | Description |
|---|---|
| Advance Transient | Promotes one-tick states forward (Boarding to Riding, Exiting to Arrived). |
| Dispatch | Assigns idle elevators to stops via the pluggable DispatchStrategy. |
| Movement | Updates elevator position and velocity using trapezoidal acceleration profiles. |
| Doors | Ticks door open/close finite-state machines at each stop. |
| Loading | Boards waiting riders onto elevators with open doors and exits riders at their destination. |
| Metrics | Aggregates wait times, ride times, and throughput from the current tick's events. |
Internally, elevator-core uses an ECS-style struct-of-arrays World with typed
component accessors, per-entity extension storage for game-specific data, and a
query builder for filtering and iterating entities by component composition.
Lifecycle hooks let you inject custom logic before or after any phase.
Dispatch Strategies
Four built-in strategies ship with the crate. All implement the DispatchStrategy trait.
| Strategy | Type | Description |
|---|---|---|
| SCAN | ScanDispatch |
Classic elevator algorithm. Sweeps end-to-end before reversing direction. |
| LOOK | LookDispatch |
Like SCAN, but reverses at the last pending request instead of the shaft end. |
| Nearest Car | NearestCarDispatch |
Assigns each hall call to the closest idle elevator. Coordinates across multi-elevator groups to avoid duplicate responses. |
| ETD | EtdDispatch |
Industry-standard Estimated Time to Destination. Evaluates every elevator for each call and minimizes total cost. |
To implement a custom strategy, implement the DispatchStrategy trait and pass
it to the builder:
use *;
let sim = new
.dispatch_for_group
.build
.unwrap;
Configuration
Simulations can be configured programmatically via SimulationBuilder or loaded
from RON files. The workspace includes example configs in assets/config/.
SimConfig(
building: BuildingConfig(
name: "Demo Tower",
stops: [
StopConfig(id: StopId(0), name: "Ground", position: 0.0),
StopConfig(id: StopId(1), name: "Floor 2", position: 4.0),
StopConfig(id: StopId(2), name: "Floor 3", position: 7.5),
StopConfig(id: StopId(3), name: "Floor 4", position: 11.0),
StopConfig(id: StopId(4), name: "Roof", position: 15.0),
],
),
elevators: [
ElevatorConfig(
id: 0,
name: "Main",
max_speed: 2.0,
acceleration: 1.5,
deceleration: 2.0,
weight_capacity: 800.0,
starting_stop: StopId(0),
door_open_ticks: 60,
door_transition_ticks: 15,
),
],
simulation: SimulationParams(
ticks_per_second: 60.0,
),
passenger_spawning: PassengerSpawnConfig(
mean_interval_ticks: 120,
weight_range: (50.0, 100.0),
),
)
To load a RON config at runtime:
use *;
let config: SimConfig = from_str?;
let sim = from_config.build?;
Bevy Integration
The elevator-bevy crate wraps the core simulation as a
Bevy 0.18 plugin. ElevatorSimPlugin reads a RON config file, constructs a
Simulation, and inserts it as a Bevy Resource. It bridges simulation events
into the Bevy message system, renders the building and riders with 2D meshes,
and supports configurable simulation speed via keyboard input.
Feature Flags
| Flag | Default | Description |
|---|---|---|
traffic |
yes | Enables traffic pattern generation (adds rand dependency) |
License
Licensed under either of Apache License, Version 2.0 or MIT license at your option.