Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Bevy Integration

The elevator-bevy crate is a Bevy 0.18 binary that wraps the core simulation with 2D rendering, a HUD, AI passengers, and keyboard controls. It serves as both a visual debugger for testing dispatch strategies and a reference implementation for integrating elevator-core into a game engine.

Running the Bevy app

With the default config:

cargo run

With a custom config:

cargo run -- assets/config/space_elevator.ron

The app reads a RON config file, creates a Simulation, and renders the building in a 2D view with elevator cars, rider dots, and a metrics HUD.

Plugin architecture

The integration is built around a single Bevy plugin:

pub struct ElevatorSimPlugin;

When you add this plugin to a Bevy app, it:

  1. Loads config from a RON file (CLI argument or assets/config/default.ron)
  2. Creates a Simulation and inserts it as the SimulationRes resource
  3. Inserts SimSpeed resource for controlling simulation speed
  4. Registers the EventWrapper message for bridging sim events to Bevy
  5. Adds systems for ticking the sim, rendering, AI passengers, input, and HUD

Key resources

SimulationRes

The core simulation is wrapped in a Bevy resource:

#[derive(Resource)]
pub struct SimulationRes {
    pub sim: Simulation,
}

Any Bevy system can access the simulation through Res<SimulationRes> (read) or ResMut<SimulationRes> (write).

SimSpeed

Controls how many simulation ticks run per Bevy frame:

#[derive(Resource)]
pub struct SimSpeed {
    pub multiplier: u32,
}
  • multiplier: 0 – simulation is paused
  • multiplier: 1 – one tick per frame (normal speed)
  • multiplier: 10 – ten ticks per frame (fast forward)

The built-in input system maps keyboard keys to speed changes.

EventWrapper

Core simulation events are bridged into Bevy’s message system:

#[derive(Message)]
pub struct EventWrapper(pub Event);

Bevy systems can read simulation events using MessageReader<EventWrapper>:

fn my_system(mut events: MessageReader<EventWrapper>) {
    for EventWrapper(event) in events.read() {
        match event {
            Event::RiderExited { rider, stop, tick, .. } => {
                // React to rider arrival in Bevy-land.
            }
            _ => {}
        }
    }
}

The tick system

The bridge between elevator-core and Bevy is a single system that runs each frame:

pub fn tick_simulation(
    mut sim: ResMut<SimulationRes>,
    speed: Res<SimSpeed>,
    mut events: MessageWriter<EventWrapper>,
) {
    for _ in 0..speed.multiplier {
        sim.sim.step();
    }
    for event in sim.sim.drain_events() {
        events.write(EventWrapper(event));
    }
}

It steps the simulation multiplier times, then drains all events and re-emits them as Bevy messages. This is the only point where the core simulation and Bevy synchronize.

Writing custom Bevy systems

To add your own gameplay systems that interact with the simulation, access SimulationRes:

use bevy::prelude::*;
use elevator_bevy::sim_bridge::SimulationRes;
use elevator_core::prelude::*;

fn print_metrics(sim: Res<SimulationRes>) {
    let m = sim.sim.metrics();
    if sim.sim.current_tick() % 3600 == 0 {
        println!(
            "Minute {}: delivered={} avg_wait={:.0}",
            sim.sim.current_tick() / 3600,
            m.total_delivered(),
            m.avg_wait_time(),
        );
    }
}

Register your system in the Bevy app after the plugin:

app.add_plugins(ElevatorSimPlugin)
    .add_systems(Update, print_metrics);

Module layout

The elevator-bevy crate is organized into focused modules:

ModuleResponsibility
plugin.rsElevatorSimPlugin – loads config, creates sim, registers everything
sim_bridge.rsSimulationRes, SimSpeed, EventWrapper, tick system
rendering.rs2D visualization of the building, elevators, and riders
ui.rsHUD overlay showing metrics and simulation state
camera.rsCamera setup sized to the building
input.rsKeyboard controls for speed adjustment
passenger_ai.rsTimer-based passenger spawning

When to use elevator-bevy vs. building your own

Use elevator-bevy if you want a quick visual test of your dispatch strategy or config. Run it, watch the elevators move, tweak parameters.

Build your own if you are making a game. The Bevy crate is intentionally simple – it is a reference, not a framework. Copy the patterns you need (the SimulationRes resource, the tick system, the event bridge) into your own Bevy app and build your game systems around them.

The core library does not depend on Bevy at all. You can use it with any Rust game engine, a TUI, a web frontend via WASM, or pure headless batch simulation.