### Running Headless Trace Example Source: https://github.com/andymai/elevator-core/blob/main/docs/src/headless-non-bevy.md This command demonstrates how to run the headless trace example, specifying a configuration file, the number of ticks to simulate, and an output file for the trace data. ```bash cargo run --example headless_trace -- \ --config assets/config/default.ron \ --ticks 2000 \ --output /tmp/trace.ndjson ``` -------------------------------- ### Running the Basic Example with Cargo Source: https://github.com/andymai/elevator-core/blob/main/README.md Command to execute the basic simulation example using Cargo. Ensure the `elevator-core` crate is available. ```sh cargo run --example basic -p elevator-core ``` -------------------------------- ### Complete Elevator Simulation Program Source: https://github.com/andymai/elevator-core/blob/main/docs/src/quick-start.md This snippet demonstrates a full simulation setup. It initializes a building with stops and an elevator, spawns a rider, and then steps through the simulation, printing events until the rider exits. Use this as a starting point for understanding the core simulation loop and event handling. ```rust use elevator_core::prelude::*; use elevator_core::config::ElevatorConfig; use elevator_core::stop::StopId; fn main() -> Result<(), SimError> { let mut sim = SimulationBuilder::new() .stop(StopId(0), "Lobby", 0.0) .stop(StopId(1), "Floor 2", 4.0) .stop(StopId(2), "Floor 3", 8.0) .elevator(ElevatorConfig { starting_stop: StopId(0), ..Default::default() }) .building_name("Tutorial Tower") .build()?; let rider_id = sim.spawn_rider(StopId(0), StopId(2), 75.0)?; let mut arrived = false; while !arrived { sim.step(); for event in sim.drain_events() { match event { Event::RiderBoarded { rider, elevator, tick, .. } => { println!("Tick {tick}: rider {rider:?} boarded elevator {elevator:?}"); } Event::ElevatorArrived { elevator, at_stop, tick } => { println!("Tick {tick}: elevator {elevator:?} arrived at {at_stop:?}"); } Event::RiderExited { rider, stop, tick, .. } => { println!("Tick {tick}: rider {rider:?} exited at {stop:?}"); if rider == rider_id.entity() { arrived = true; } } _ => {} } } } println!("\n--- Summary ---"); println!("Total ticks: {}", sim.current_tick()); println!("{}", sim.metrics()); Ok(()) } ``` -------------------------------- ### Build a Basic Simulation Source: https://github.com/andymai/elevator-core/blob/main/docs/src/quick-start.md Set up a 3-stop building with one elevator using SimulationBuilder. Configure elevator starting position and building name. ```rust use elevator_core::prelude::*; use elevator_core::config::ElevatorConfig; use elevator_core::stop::StopId; fn main() -> Result<(), SimError> { let mut sim = SimulationBuilder::new() .stop(StopId(0), "Lobby", 0.0) .stop(StopId(1), "Floor 2", 4.0) .stop(StopId(2), "Floor 3", 8.0) .elevator(ElevatorConfig { starting_stop: StopId(0), ..Default::default() }) .building_name("Tutorial Tower") .build()?; Ok(()) } ``` -------------------------------- ### Complete Manual Mode Example Source: https://github.com/andymai/elevator-core/blob/main/docs/src/manual-inspection-modes.md This example demonstrates putting an elevator into Manual mode, commanding it to ascend, and then executing an emergency stop. It includes simulation stepping and checks for the car's final position and velocity. ```rust use elevator_core::prelude::* use elevator_core::components::ServiceMode; fn main() { let mut sim = SimulationBuilder::demo().build().unwrap(); let elev = ElevatorId::from(sim.world().iter_elevators().next().unwrap().0); sim.set_service_mode(elev.entity(), ServiceMode::Manual).unwrap(); // Command full ascent. sim.set_target_velocity(elev, 2.0).unwrap(); for t in 0..180 { // Halfway through, slam the emergency brake. if t == 90 { sim.emergency_stop(elev).unwrap(); } sim.step(); let pos = sim.world().position(elev.entity()).unwrap().value(); let vel = sim.velocity(elev.entity()).unwrap(); if t > 90 && vel.abs() < 1e-6 { println!("Car stopped at {pos:.2}m after {t} ticks."); break; } } } ``` -------------------------------- ### Manual Door Commands Example Source: https://github.com/andymai/elevator-core/blob/main/docs/src/door-control.md Example of using manual door commands to hold doors for a late arrival and then force them shut. ```rust // A friend is running for the elevator -- hold the doors. sim.hold_door(elev, 60).unwrap(); // Friend made it aboard. Force the doors closed early. sim.close_door(elev).unwrap(); ``` -------------------------------- ### Run Bevy App with Default Config Source: https://github.com/andymai/elevator-core/blob/main/docs/src/bevy-integration.md Execute the Bevy application using the default configuration. This command starts the simulation with its default settings. ```bash cargo run ``` -------------------------------- ### Quick Start: Poisson Arrivals with Office Day Schedule Source: https://github.com/andymai/elevator-core/blob/main/docs/src/traffic-generation.md Demonstrates setting up a simulation with a Poisson traffic source and an office-day schedule. Ensure stops are sorted by position for correct lobby identification. ```rust use elevator_core::prelude::*; use elevator_core::config::ElevatorConfig; use elevator_core::traffic::{PoissonSource, TrafficPattern, TrafficSchedule, TrafficSource}; # fn main() -> Result<(), SimError> { let mut sim = SimulationBuilder::new() .stop(StopId(0), "Ground", 0.0) .stop(StopId(1), "Top", 10.0) .elevator(ElevatorConfig::default()) .build()?; let stops: Vec = sim.stop_lookup_iter().map(|(id, _)| *id).collect(); // Poisson arrivals with an office-day schedule. let mut source = PoissonSource::new( stops, TrafficSchedule::office_day(3600), // 3600 ticks per hour 120, // mean inter-arrival: 120 ticks (60.0, 90.0), // weight range: 60-90kg ); for _ in 0..10_000 { let tick = sim.current_tick(); for req in source.generate(tick) { let _ = sim.spawn_rider(req.origin, req.destination, req.weight); } sim.step(); } # Ok(()) # } ``` -------------------------------- ### Consuming from C# Source: https://github.com/andymai/elevator-core/blob/main/crates/elevator-ffi/README.md Provides an example of how to consume the elevator FFI from C# using `DllImport`. ```APIDOC ## Consuming from C# ```csharp using System.Runtime.InteropServices; public static class Ev { [DllImport("elevator_ffi")] public static extern uint ev_abi_version(); [DllImport("elevator_ffi")] public static extern IntPtr ev_sim_create(string path); [DllImport("elevator_ffi")] public static extern void ev_sim_destroy(IntPtr sim); [DllImport("elevator_ffi")] public static extern int ev_sim_step(IntPtr sim); // ... see include/elevator_ffi.h for the full surface } ``` For Unity, drop the compiled `libelevator_ffi.so` / `.dylib` / `.dll` into `Assets/Plugins/` and call through `DllImport("elevator_ffi")` as above. ``` -------------------------------- ### Basic Simulation Setup and Event Handling in Rust Source: https://github.com/andymai/elevator-core/blob/main/README.md Demonstrates how to set up a basic elevator simulation, spawn riders, and process events like rider delivery or abandonment. Requires `elevator_core::prelude::*` and `elevator_core::config::ElevatorConfig`. ```rust use elevator_core::prelude::*; use elevator_core::config::ElevatorConfig; fn main() -> Result<(), SimError> { let mut sim = SimulationBuilder::new() .stop(StopId(0), "Lobby", 0.0) .stop(StopId(1), "Floor 2", 4.0) .stop(StopId(2), "Floor 3", 8.0) .elevator(ElevatorConfig { starting_stop: StopId(0), ..Default::default() }) .build()?; sim.spawn_rider(StopId(0), StopId(2), 75.0)?; for _ in 0..1000 { sim.step(); for event in sim.drain_events() { match event { Event::RiderExited { rider, tick, .. } => { println!("Tick {tick}: rider {rider:?} delivered!"); return Ok(()); } Event::RiderAbandoned { rider, stop, tick, .. } => { eprintln!("Tick {tick}: rider {rider:?} abandoned at {stop:?}"); return Ok(()); } _ => {} } } } eprintln!("timed out after 1000 ticks"); Ok(()) } ``` -------------------------------- ### RON Configuration File Example Source: https://github.com/andymai/elevator-core/blob/main/docs/src/configuration.md Define simulation parameters in a RON file for data-driven workflows. This format is human-readable and maps directly to Rust structs. ```ron 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), ), ) ``` -------------------------------- ### Manage World Resources Source: https://github.com/andymai/elevator-core/blob/main/docs/src/extensions.md Use `insert_resource()`, `resource()`, and `resource_mut()` to manage global, non-entity-specific data. This example inserts a `u32` resource, reads it, and then mutates it. ```rust use elevator_core::prelude::*; use elevator_core::__doctest_prelude::*; fn run(sim: &mut Simulation) { // Insert a resource sim.world_mut().insert_resource(42u32); // Read it if let Some(value) = sim.world().resource::() { println!("The answer is {}", value); } // Mutate it if let Some(value) = sim.world_mut().resource_mut::() { *value += 1; } } ``` -------------------------------- ### Build and Run Local Development Server Source: https://github.com/andymai/elevator-core/blob/main/playground/README.md Build the wasm-pack output and start the Vite development server for local development. Rust changes require a fresh build. ```sh wasm-pack build ../crates/elevator-wasm --target web --out-dir ../../playground/public/pkg pnpm install pnpm dev ``` -------------------------------- ### Implement Custom Dispatch Strategy with Snapshot Support Source: https://github.com/andymai/elevator-core/blob/main/docs/src/custom-dispatch.md This example demonstrates how to implement a custom dispatch strategy (`PriorityDispatch`) that supports snapshot serialization. It overrides `builtin_id`, `snapshot_config`, and `restore_config` to allow the strategy's configuration to be saved and loaded with the simulation state. The `run` function shows how to build a simulation with a custom strategy and how to restore a simulation from a snapshot using a factory function to map strategy names back to instances. ```rust use elevator_core::prelude::*; use elevator_core::config::ElevatorConfig; use elevator_core::dispatch::{BuiltinStrategy, DispatchStrategy, RankContext}; use elevator_core::snapshot::WorldSnapshot; use serde::{Deserialize, Serialize}; const PRIORITY_NAME: &str = "priority"; #[derive(Default, Serialize, Deserialize)] struct PriorityDispatch { urgency_boost: f64, } impl DispatchStrategy for PriorityDispatch { // Real implementations score against `ctx`; see `BusyStopNearest` above. fn rank(&mut self, _ctx: &RankContext<'_>) -> Option { Some(0.0) } fn builtin_id(&self) -> Option { // Identify the strategy to the snapshot layer. Keep this name // stable across releases -- changing it breaks old saves. Some(BuiltinStrategy::Custom(PRIORITY_NAME.into())) } fn snapshot_config(&self) -> Option { ron::to_string(self).ok() } fn restore_config(&mut self, serialized: &str) -> Result<(), String> { let restored: Self = ron::from_str(serialized).map_err(|e| e.to_string())?; *self = restored; Ok(()) } } fn run(snapshot: WorldSnapshot) -> Result<(), SimError> { // `Simulation::new` and the builder consult `builtin_id` so you can // drop the strategy in without a second id argument -- the snapshot // records `BuiltinStrategy::Custom("priority")` automatically. let _sim = SimulationBuilder::new() .stop(StopId(0), "Ground", 0.0) .stop(StopId(1), "Top", 10.0) .elevator(ElevatorConfig::default()) .dispatch(PriorityDispatch::default()) .build()?; // When restoring, the factory maps names back to strategy instances. // `restore_config` replays `urgency_boost` onto the new instance. let sim = snapshot.restore(Some(&|name: &str| -> Option> { match name { PRIORITY_NAME => Some(Box::new(PriorityDispatch::default())), // Return `None` for unknown names -- the restore records a // `SnapshotDanglingReference` event and falls back to // `ScanDispatch` rather than panicking. _ => None, } }))?; let _ = sim; Ok(()) } ``` -------------------------------- ### Compact Metrics Display Source: https://github.com/andymai/elevator-core/blob/main/docs/src/events-metrics.md Utilize the `Display` implementation for `Metrics` to get a concise, one-line summary suitable for logs or HUDs. ```rust println!("{{}}", sim.metrics()); ``` -------------------------------- ### Build Elevator FFI Library Source: https://github.com/andymai/elevator-core/blob/main/examples/unity-demo/README.md Compiles the elevator-ffi library in release mode and copies the native library to the Unity project's plugins directory. Ensure you have the Rust toolchain installed. ```bash bash examples/unity-demo/build.sh ``` -------------------------------- ### Build Elevator GDExtension Source: https://github.com/andymai/elevator-core/blob/main/examples/godot-demo/README.md Compiles the elevator-gdext library in release mode and copies the native library to the bin directory. Ensure you have Rust 1.88+ installed. ```bash bash examples/godot-demo/build.sh ``` -------------------------------- ### Build WASM and Run Playground Source: https://github.com/andymai/elevator-core/blob/main/CLAUDE.md Scripts to build the WebAssembly module and start the development server for the playground. The dev server auto-builds WASM if the output package is missing and watches Rust sources. ```bash scripts/build-wasm.sh # build wasm + generate TS bindings ``` ```bash cd playground && pnpm dev # auto-builds wasm if pkg/ missing, watches Rust sources ``` -------------------------------- ### Read and Mutate Extensions Source: https://github.com/andymai/elevator-core/blob/main/docs/src/extensions.md Access extension data using `world.ext()` for a clone, `world.ext_ref()` for a borrow, or `world.ext_mut()` for a mutable reference. This example demonstrates reading and then mutating the `priority_class` of a `GuestPriority` extension. ```rust use serde::{Serialize, Deserialize}; use elevator_core::prelude::*; use elevator_core::__doctest_prelude::*; #[derive(Debug, Clone, Serialize, Deserialize)] struct GuestPriority { priority_class: u8, suite_floor: u32 } fn run(sim: &mut Simulation, rider_id: EntityId) { // Read (cloned) if let Some(guest) = sim.world().ext::(rider_id) { println!("priority class: {}", guest.priority_class); } // Mutate -- promote the guest's class. if let Some(guest) = sim.world_mut().ext_mut::(rider_id) { guest.priority_class = guest.priority_class.saturating_add(1); } } ``` -------------------------------- ### Query Entities with Extensions Source: https://github.com/andymai/elevator-core/blob/main/docs/src/extensions.md Iterate over entities that have a specific extension using `world.query()` with `Ext` for read-only access, or `world.query_ext_mut()` for mutable access. This example shows iterating and printing priority classes, then promoting all guests. ```rust use serde::{Serialize, Deserialize}; use elevator_core::prelude::*; use elevator_core::__doctest_prelude::*; use elevator_core::query::Ext; #[derive(Debug, Clone, Serialize, Deserialize)] struct GuestPriority { priority_class: u8, suite_floor: u32 } fn run(world: &mut World) { // Read-only iteration (cloned via Ext) for (id, guest) in world.query::<(EntityId, &Ext)>().iter() { println!("{:?} is priority class {}", id, guest.priority_class); } // Mutable access -- promote everyone by one class (with saturation). world.query_ext_mut::().for_each_mut(|_id, guest| { guest.priority_class = guest.priority_class.saturating_add(1); }); } ``` -------------------------------- ### Building the FFI Library Source: https://github.com/andymai/elevator-core/blob/main/crates/elevator-ffi/README.md Instructions on how to build the `elevator-ffi` crate, including the commands to generate the release artifacts. ```APIDOC ## Building ```bash cargo build -p elevator-ffi --release # artefacts in target/release/: # libelevator_ffi.so / .dylib / .dll (cdylib — for P/Invoke) # libelevator_ffi.a / elevator_ffi.lib (staticlib — for embedded linking) ``` The header is regenerated on every build via `build.rs` and checked in. ``` -------------------------------- ### Query Idle Elevator Count Source: https://github.com/andymai/elevator-core/blob/main/docs/src/events-metrics.md Get the number of elevators that are currently idle and not disabled. This is useful for understanding elevator availability. ```rust let idle = sim.idle_elevator_count(); let loading = sim.elevators_in_phase(ElevatorPhase::Loading); println!("{{idle}} idle, {{loading}} loading"); ``` -------------------------------- ### Running Bevy Demo with Different Configurations Source: https://github.com/andymai/elevator-core/blob/main/README.md Commands to run the Bevy frontend demo. The first command uses the default 5-stop building configuration, while the second uses a configuration for a 1,000 km orbital tether. ```sh cargo run ``` ```sh cargo run -- assets/config/space_elevator.ron ``` -------------------------------- ### Run a Specific Benchmark Source: https://github.com/andymai/elevator-core/blob/main/docs/src/testing.md Execute a single criterion benchmark, for example, `sim_bench`, using Cargo. This is useful for focusing performance analysis on a particular area. ```bash cargo bench -p elevator-core --bench sim_bench ``` -------------------------------- ### Check ABI Version Source: https://github.com/andymai/elevator-core/blob/main/crates/elevator-ffi/README.md Always check the ABI version at startup to ensure compatibility. The returned version must match EV_ABI_VERSION from the C header. ```c uint32_t v = ev_abi_version(); // must equal EV_ABI_VERSION from the header ``` -------------------------------- ### Configure and Use Built-in Dispatch Strategies Source: https://context7.com/andymai/elevator-core/llms.txt Demonstrates configuring different dispatch strategies like Scan, Look, ETD, and NearestCar for single or multi-elevator systems. Strategies can be set during simulation building or swapped at runtime. ```rust use elevator_core::prelude::*; use elevator_core::config::ElevatorConfig; use elevator_core::ids::GroupId; use elevator_core::dispatch::{ BuiltinStrategy, scan::ScanDispatch, look::LookDispatch, nearest_car::NearestCarDispatch, etd::EtdDispatch, destination::DestinationDispatch, rsr::RsrDispatch, }; fn main() -> Result<(), SimError> { // Single-elevator building → ScanDispatch (fair, simple) let _scan = SimulationBuilder::new() .stop(StopId(0), "G", 0.0).stop(StopId(1), "T", 10.0) .elevator(ElevatorConfig::default()) .dispatch(ScanDispatch::new()) .build()?; // Bursty traffic → LookDispatch (reverses at last request, not at shaft extremes) let _look = SimulationBuilder::new() .stop(StopId(0), "G", 0.0).stop(StopId(1), "T", 10.0) .elevator(ElevatorConfig::default()) .dispatch(LookDispatch::new()) .build()?; // Multi-car group → EtdDispatch with tuning let etd = EtdDispatch::new() .with_delay_weight(1.5) // favor existing riders over new calls .with_age_linear_weight(1.0); // fairness term to prevent starvation let mut sim = SimulationBuilder::new() .stop(StopId(0), "G", 0.0).stop(StopId(1), "T", 10.0) .elevator(ElevatorConfig { id: 0, starting_stop: StopId(0), ..Default::default() }) .elevator(ElevatorConfig { id: 1, starting_stop: StopId(1), ..Default::default() }) .dispatch(etd) .build()?; // Swap strategy at runtime sim.set_dispatch( GroupId(0), Box::new(NearestCarDispatch::new()), BuiltinStrategy::NearestCar, ); // Per-group strategies in a multi-bank building let _multi = SimulationBuilder::new() .stop(StopId(0), "G", 0.0).stop(StopId(1), "Sky", 50.0).stop(StopId(2), "Top", 100.0) .elevator(ElevatorConfig { id: 0, starting_stop: StopId(0), ..Default::default() }) .elevator(ElevatorConfig { id: 1, starting_stop: StopId(2), ..Default::default() }) .dispatch_for_group(GroupId(0), ScanDispatch::new()) .dispatch_for_group(GroupId(1), EtdDispatch::new()) .build()?; Ok(()) } ``` ```rust use elevator_core::prelude::*; use elevator_core::config::ElevatorConfig; fn main() -> Result<(), SimError> { let mut sim = SimulationBuilder::new() .stop(StopId(0), "Ground", 0.0) .stop(StopId(1), "Mid", 5.0) .stop(StopId(2), "Top", 10.0) .elevator(ElevatorConfig::default()) .build()?; let elev = ElevatorId::from(sim.world().elevator_ids()[0]); let stop_top = sim.stop_entity(StopId(2)).unwrap(); let stop_mid = sim.stop_entity(StopId(1)).unwrap(); // Enqueue stops sim.push_destination(elev, stop_top).unwrap(); // → queue: [Top] sim.push_destination(elev, stop_mid).unwrap(); // → queue: [Top, Mid] sim.push_destination_front(elev, stop_mid).unwrap(); // → queue: [Mid, Top] // Inspect queue let queue = sim.destination_queue(elev).unwrap(); println!("Queue depth: {}", queue.len()); // Cancel pending stops (elevator finishes current leg, then idles) sim.clear_destinations(elev).unwrap(); // Abort current motion immediately (brakes along decel profile → nearest stop) sim.abort_movement(elev).unwrap(); for _ in 0..3000 { sim.step(); } println!("Tick: {}", sim.current_tick()); Ok(()) } ``` -------------------------------- ### C# Interop Declarations Source: https://github.com/andymai/elevator-core/blob/main/crates/elevator-ffi/README.md Import necessary functions from the `elevator_ffi` library into C# using `DllImport`. This setup is required for consuming the FFI from C# or Unity. ```csharp using System.Runtime.InteropServices; public static class Ev { [DllImport("elevator_ffi")] public static extern uint ev_abi_version(); [DllImport("elevator_ffi")] public static extern IntPtr ev_sim_create(string path); [DllImport("elevator_ffi")] public static extern void ev_sim_destroy(IntPtr sim); [DllImport("elevator_ffi")] public static extern int ev_sim_step(IntPtr sim); // ... see include/elevator_ffi.h for the full surface } ``` -------------------------------- ### Poisson Arrivals: Basic Configuration Source: https://github.com/andymai/elevator-core/blob/main/docs/src/traffic-generation.md Sets up a `PoissonSource` with a constant uniform traffic pattern, specifying the mean arrival interval and weight range. ```rust use elevator_core::traffic::{PoissonSource, TrafficSchedule, TrafficPattern}; use elevator_core::stop::StopId; let stops = vec![StopId(0), StopId(1), StopId(2)]; let source = PoissonSource::new( stops, TrafficSchedule::constant(TrafficPattern::Uniform), 60, // mean arrival every 60 ticks (50.0, 100.0), // weight range (min, max) in kg ); ``` -------------------------------- ### DispatchStrategy Trait Definition Source: https://github.com/andymai/elevator-core/blob/main/docs/src/custom-dispatch.md Defines the core trait for custom dispatch strategies, including hooks for pre-dispatch, per-car setup, ranking, fallback, and removal notifications. ```rust pub trait DispatchStrategy: Send + Sync { /// Pre-pass hook with mutable world access. Used by sticky strategies /// (e.g. destination dispatch) to commit rider -> car assignments. fn pre_dispatch( &mut self, _group: &ElevatorGroup, _manifest: &DispatchManifest, _world: &mut World, ) { /* default: no-op */ } /// Per-car setup called once before any `rank` calls for this car. /// Strategies with per-car state (sweep direction, queue pointers) /// refresh it here so `rank` is order-independent over stops. fn prepare_car( &mut self, _car: EntityId, _car_position: f64, _group: &ElevatorGroup, _manifest: &DispatchManifest, _world: &World, ) { /* default: no-op */ } /// Score sending `car` to `stop`. Lower is better. `None` marks /// the pair unavailable (capacity limits, wrong-direction, sticky). /// Must return a finite, non-negative value when `Some`. fn rank(&mut self, ctx: &RankContext<'_>) -> Option; /// Decide what a car should do when the assignment phase couldn't /// give it a stop (no demand or all candidate ranks were `None`). fn fallback( &mut self, _car: EntityId, _car_position: f64, _group: &ElevatorGroup, _manifest: &DispatchManifest, _world: World, ) -> DispatchDecision { DispatchDecision::Idle } /// Clean up per-elevator state when a car leaves the group. /// Strategies with internal `HashMap` state must /// remove the entry here -- otherwise the map grows unbounded. fn notify_removed(&mut self, _elevator: EntityId) { /* default: no-op */ } } ``` -------------------------------- ### Building the FFI Library Source: https://github.com/andymai/elevator-core/blob/main/crates/elevator-ffi/README.md Build the `elevator-ffi` crate using Cargo. The release build produces `cdylib` for P/Invoke and `staticlib` for embedded linking. ```bash cargo build -p elevator-ffi --release # artefacts in target/release/: # libelevator_ffi.so / .dylib / .dll (cdylib — for P/Invoke) # libelevator_ffi.a / elevator_ffi.lib (staticlib — for embedded linking) ``` -------------------------------- ### RON Configuration for Traffic Schedule Source: https://github.com/andymai/elevator-core/blob/main/docs/src/traffic-generation.md Example of configuring `TrafficSchedule` using RON format. This allows for defining complex, time-varying traffic patterns in external configuration files. ```ron // traffic_config.ron TrafficSchedule( segments: [ (0..3600, UpPeak), (3600..7200, Uniform), (7200..10800, Lunchtime), ], fallback: Uniform, ) ``` -------------------------------- ### Reading Simulation Events in Bevy Source: https://github.com/andymai/elevator-core/blob/main/docs/src/bevy-integration.md Example of a Bevy system reading simulation events bridged via `EventWrapper`. It demonstrates matching on specific event types like `RiderExited`. ```rust use bevy::prelude::* use elevator_core::events::Event; #[derive(Message, Clone)] pub struct EventWrapper(pub Event); fn my_system(mut events: MessageReader) { for EventWrapper(event) in events.read() { match event { Event::RiderExited { rider, stop, tick, .. } => { // React to rider arrival in Bevy-land. } _ => {} } } } ``` -------------------------------- ### Poisson Arrivals: Fluent Configuration with Schedule Override Source: https://github.com/andymai/elevator-core/blob/main/docs/src/traffic-generation.md Shows how to configure a `PoissonSource` using a fluent API, allowing for schedule overrides. ```rust # use elevator_core::traffic::{PoissonSource, TrafficSchedule, TrafficPattern}; # use elevator_core::stop::StopId; ``` -------------------------------- ### Control Elevator Doors Manually Source: https://context7.com/andymai/elevator-core/llms.txt Use these methods to override automatic door behavior. Commands are queued and processed at the start of each Doors phase tick. Timing can be adjusted at runtime. ```rust use elevator_core::prelude::*; use elevator_core::config::ElevatorConfig; fn main() -> Result<(), SimError> { let mut sim = SimulationBuilder::new() .stop(StopId(0), "Ground", 0.0) .stop(StopId(1), "Top", 10.0) .elevator(ElevatorConfig { door_open_ticks: 30, door_transition_ticks: 10, ..Default::default() }) .build()?; let elev = ElevatorId::from(sim.world().elevator_ids()[0]); // Hold doors open for 60 extra ticks (e.g., late arrival running for the lift) sim.hold_door(elev, 60).unwrap(); // Cancel the hold (friend boarded — close up) sim.cancel_door_hold(elev).unwrap(); // Force early close sim.close_door(elev).unwrap(); // Reopen closed doors sim.open_door(elev).unwrap(); // Change door timing at runtime (takes effect on next door cycle) sim.set_door_open_ticks(elev, 45).unwrap(); sim.set_door_transition_ticks(elev, 8).unwrap(); // Observe door events for _ in 0..1000 { sim.step(); for event in sim.drain_events() { match event { Event::DoorOpened { elevator, tick } => println!("[{tick}] {elevator:?} opened"), Event::DoorClosed { elevator, tick } => println!("[{tick}] {elevator:?} closed"), Event::DoorCommandQueued { elevator, command, tick } => println!("[{tick}] {elevator:?} command queued: {command:?}"), Event::DoorCommandApplied { elevator, command, tick } => println!("[{tick}] {elevator:?} command applied: {command:?}"), _ => {} } } } Ok(()) } ``` -------------------------------- ### Simulation Step Execution Flow Source: https://github.com/andymai/elevator-core/blob/main/docs/src/simulation-loop.md This diagram illustrates the 8 phases of a single simulation tick, starting with `sim.step()` and ending with `advance_tick()` which flushes events and increments the tick counter. ```mermaid flowchart TD STEP["sim.step()"] --> P1 P1["Phase 1: Advance Transient"] --> P2["Phase 2: Dispatch"] P2 --> P3["Phase 3: Reposition"] P3 --> P4["Phase 4: Advance Queue"] P4 --> P5["Phase 5: Movement"] P5 --> P6["Phase 6: Doors"] P6 --> P7["Phase 7: Loading"] P7 --> P8["Phase 8: Metrics"] P8 --> ADV["advance_tick()\nflush events, tick += 1"] ``` -------------------------------- ### Interpolate Elevator Position Source: https://github.com/andymai/elevator-core/blob/main/docs/src/movement-physics.md Calculates an elevator's position at a specific point within a simulation tick for smooth rendering. Alpha 0.0 is the start of the tick, and 1.0 is the end. ```rust # use elevator_core::prelude::*; # use elevator_core::__doctest_prelude::*; # let sim: Simulation = todo!(); # let elev: EntityId = todo!(); // alpha = 0.0 is the position at tick start, 1.0 at tick end. let p_start = sim.position_at(elev, 0.0).unwrap(); let p_mid = sim.position_at(elev, 0.5).unwrap(); let p_end = sim.position_at(elev, 1.0).unwrap(); ``` -------------------------------- ### Run Bevy App with Custom Config Source: https://github.com/andymai/elevator-core/blob/main/docs/src/bevy-integration.md Execute the Bevy application with a custom RON configuration file. Specify the path to your configuration file after the command. ```bash cargo run -- assets/config/space_elevator.ron ``` -------------------------------- ### Accessing Simulation Metrics in Bevy System Source: https://github.com/andymai/elevator-core/blob/main/docs/src/bevy-integration.md Example of a custom Bevy system accessing the `SimulationRes` to retrieve and print simulation metrics. It checks the current tick to log metrics periodically. ```rust use bevy::prelude::* use elevator_bevy::sim_bridge::SimulationRes; use elevator_core::prelude::* fn print_metrics(sim: Res) { 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(), ); } } ``` -------------------------------- ### Registering Custom Bevy System Source: https://github.com/andymai/elevator-core/blob/main/docs/src/bevy-integration.md Demonstrates how to register a custom Bevy system, such as `print_metrics`, after adding the `ElevatorSimPlugin` to the Bevy app. ```rust use bevy::prelude::* use elevator_bevy::sim_bridge::SimulationRes; pub struct ElevatorSimPlugin; impl Plugin for ElevatorSimPlugin { fn build(&self, _: &mut App) {} } fn print_metrics(_sim: Res) {} let mut app = App::new(); app.add_plugins(ElevatorSimPlugin) .add_systems(Update, print_metrics); ``` -------------------------------- ### Import All Dispatch Strategies Source: https://github.com/andymai/elevator-core/blob/main/docs/src/dispatch-strategies.md Import all available built-in dispatch strategies from their respective modules. ```rust use elevator_core::dispatch::scan::ScanDispatch; use elevator_core::dispatch::look::LookDispatch; use elevator_core::dispatch::nearest_car::NearestCarDispatch; use elevator_core::dispatch::etd::EtdDispatch; use elevator_core::dispatch::destination::DestinationDispatch; use elevator_core::dispatch::rsr::RsrDispatch; ``` -------------------------------- ### Loading and Building Simulation from RON Config Source: https://github.com/andymai/elevator-core/blob/main/docs/src/configuration.md Load a RON configuration string, deserialize it into `SimConfig`, and then build the simulation using `SimulationBuilder::from_config`. This allows overriding config values with builder methods. ```rust use elevator_core::prelude::*; use elevator_core::config::SimConfig; fn main() -> Result<(), Box> { let ron_str = std::fs::read_to_string("assets/config/default.ron")?; let config: SimConfig = ron::from_str(&ron_str)?; let sim = SimulationBuilder::from_config(config).build()?; println!("Loaded simulation with {} tick rate", sim.dt().recip() as u32); Ok(()) } ``` -------------------------------- ### Check Binding Coverage Source: https://github.com/andymai/elevator-core/blob/main/CLAUDE.md CI script to verify that all public methods on `impl Simulation` are correctly enumerated in `bindings.toml` for each consumer crate. Fails the build if entries are missing or stale. ```bash scripts/check-bindings.sh ``` -------------------------------- ### Import Elevator Core Prelude Source: https://github.com/andymai/elevator-core/blob/main/docs/src/quick-start.md Import the main prelude for common elevator-core functionalities, including simulation, building, and event types. ```rust use elevator_core::prelude::* ``` -------------------------------- ### Buffer Pattern for Variable-Length Output Source: https://github.com/andymai/elevator-core/blob/main/docs/src/using-the-bindings.md Use a two-step process for FFI exports returning lists: first, probe with capacity 0 to learn the required size, then allocate and read the data into the buffer. ```c // Step 1 — probe with capacity 0 to learn the required size. uint32_t needed = 0; EvStatus s = ev_sim_shortest_route(sim, from, to, NULL, 0, &needed); if (s == EvStatusNotFound) { // No route exists. return; } // `needed` is now the slot count. // Step 2 — allocate, read, use. uint64_t* stops = malloc(needed * sizeof(uint64_t)); uint32_t written = 0; s = ev_sim_shortest_route(sim, from, to, stops, needed, &written); assert(s == EvStatusOk && written == needed); for (uint32_t i = 0; i < written; i++) { /* ... handle stops[i] ... */ } free(stops); ``` -------------------------------- ### Running elevator-tui Debugger Source: https://github.com/andymai/elevator-core/blob/main/README.md Commands to run the terminal UI debugger for elevator-core. The first command starts an interactive session with default configuration. The second command runs a headless smoke test until tick 5000. ```sh cargo run -p elevator-tui -- assets/config/default.ron ``` ```sh cargo run -p elevator-tui -- assets/config/default.ron --headless --until 5000 ``` -------------------------------- ### Manage State with `notify_removed` Source: https://github.com/andymai/elevator-core/blob/main/docs/src/custom-dispatch.md Implement `notify_removed` to clean up per-elevator state when an elevator is removed or reassigned across groups. This prevents state maps from growing indefinitely. The example shows how to remove an elevator's cooldown entry. ```rust use std::collections::HashMap; #[derive(Default)] struct PriorityDispatch { /// Per-elevator cooldown -- once this elevator served a priority stop, /// suppress priority preference for N ticks so non-priority riders /// aren't starved. cooldown_ticks: HashMap, } impl DispatchStrategy for PriorityDispatch { fn rank(/* ... */) -> Option { /* ... */ } fn notify_removed(&mut self, elevator: EntityId) { // CRITICAL: keeps the map from growing unbounded under churn. self.cooldown_ticks.remove(&elevator); } } ``` -------------------------------- ### Configure Elevator with ElevatorConfig Source: https://github.com/andymai/elevator-core/blob/main/docs/src/elevators.md Use ElevatorConfig to set initial parameters like ID, name, speed, acceleration, capacity, and door timings. Ensure all physics parameters are positive; invalid values will cause a SimError at build time. Use `..Default::default()` to fill in unspecified fields with their default values. ```rust elevator(ElevatorConfig { id: 0, name: "Express A".into(), max_speed: 5.0.into(), acceleration: 2.0.into(), deceleration: 3.0.into(), weight_capacity: 1200.0.into(), starting_stop: StopId(0), door_open_ticks: 60, door_transition_ticks: 15, ..Default::default() }) ``` -------------------------------- ### Build Simulation Programmatically Source: https://context7.com/andymai/elevator-core/llms.txt Constructs a simulation instance by defining stops, elevators, tick rate, and dispatch strategy. Spawns a rider and steps through the simulation, printing events. ```rust use elevator_core::prelude::*; use elevator_core::config::ElevatorConfig; use elevator_core::dispatch::etd::EtdDispatch; fn main() -> Result<(), SimError> { let mut sim = SimulationBuilder::new() .stop(StopId(0), "Lobby", 0.0) .stop(StopId(1), "Floor 2", 4.0) .stop(StopId(2), "Floor 3", 8.0) .stop(StopId(3), "Roof", 12.0) .elevator(ElevatorConfig { id: 0, name: "Car A".into(), max_speed: 2.0.into(), acceleration: 1.5.into(), deceleration: 2.0.into(), weight_capacity: 800.0.into(), starting_stop: StopId(0), door_open_ticks: 60, door_transition_ticks: 15, ..Default::default() }) .elevator(ElevatorConfig { id: 1, name: "Car B".into(), starting_stop: StopId(3), ..Default::default() }) .ticks_per_second(60.0) .dispatch(EtdDispatch::new()) .building_name("Demo Tower") .build()?; sim.spawn_rider(StopId(0), StopId(3), 72.0)?; for _ in 0..2000 { sim.step(); for event in sim.drain_events() { if let Event::RiderExited { rider, stop, tick, .. } = event { println!("Tick {tick}: {rider:?} arrived at {stop:?}"); } } } Ok(()) } ``` -------------------------------- ### Consume Simulation Events with drain_events Source: https://context7.com/andymai/elevator-core/llms.txt Use `sim.drain_events()` to retrieve all events emitted since the last drain. This is crucial for long simulations; drain at least once per tick. The example shows how to match and print various elevator and rider events. ```rust use elevator_core::prelude::*; use elevator_core::config::ElevatorConfig; fn main() -> Result<(), SimError> { let mut sim = SimulationBuilder::new() .stop(StopId(0), "Ground", 0.0) .stop(StopId(1), "Top", 10.0) .elevator(ElevatorConfig::default()) .build()?; sim.spawn_rider(StopId(0), StopId(1), 75.0)?; for _ in 0..3000 { sim.step(); for event in sim.drain_events() { match event { // Elevator events Event::ElevatorAssigned { elevator, stop, tick } => println!("[{tick}] {elevator:?} assigned to {stop:?}"), Event::DoorOpened { elevator, tick } => println!("[{tick}] {elevator:?} doors opened"), Event::DoorClosed { elevator, tick } => println!("[{tick}] {elevator:?} doors closed"), // Rider events Event::RiderSpawned { rider, origin, destination, tick, .. } => println!("[{tick}] {rider:?} spawned {origin:?}->{destination:?}"), Event::RiderBoarded { rider, elevator, tick, .. } => println!("[{tick}] {rider:?} boarded {elevator:?}"), Event::RiderExited { rider, stop, tick, .. } => println!("[{tick}] {rider:?} exited at {stop:?}"), Event::RiderAbandoned{ rider, stop, tick, .. } => println!("[{tick}] {rider:?} abandoned at {stop:?}"), Event::RiderRejected { rider, reason, context, tick, .. } => { println!("[{tick}] {rider:?} rejected: {reason:?}"); if let Some(ctx) = context { println!(" Detail: {ctx}"); } } _ => {} } } } Ok(()) } ``` -------------------------------- ### Implement Per-Car State with `prepare_car` Source: https://github.com/andymai/elevator-core/blob/main/docs/src/custom-dispatch.md Use the `prepare_car` hook to refresh per-car state before `rank` calls. This ensures that ranking decisions are consistent within a pass, regardless of iteration order. The example shows how to determine a car's sweep direction based on demand. ```rust use std::collections::HashMap; use elevator_core::dispatch::{ DispatchManifest, DispatchStrategy, ElevatorGroup, RankContext, }; use elevator_core::entity::EntityId; use elevator_core::world::World; struct DirectionalDispatch { /// Tracks the preferred sweep direction per car. sweep_up: HashMap, } impl DispatchStrategy for DirectionalDispatch { fn prepare_car( &mut self, car: EntityId, car_position: f64, group: &ElevatorGroup, manifest: &DispatchManifest, world: &World, ) { // Decide sweep direction based on where demand is heaviest // relative to this car. This runs once per car, before any // rank() calls for that car. let demand_above = group.stop_entities().iter().filter(|&&s| { manifest.waiting_count_at(s) > 0 && world.stop_position(s).map_or(false, |p| p > car_position) }).count(); let demand_below = group.stop_entities().iter().filter(|&&s| { manifest.waiting_count_at(s) > 0 && world.stop_position(s).map_or(false, |p| p < car_position) }).count(); self.sweep_up.insert(car, demand_above >= demand_below); } fn rank(&mut self, ctx: &RankContext<'_>) -> Option { let going_up = self.sweep_up.get(&ctx.car).copied().unwrap_or(true); let is_ahead = if going_up { ctx.stop_position >= ctx.car_position } else { ctx.stop_position <= ctx.car_position }; if is_ahead { Some((ctx.car_position - ctx.stop_position).abs()) } else { // Penalize stops behind the sweep direction. Some((ctx.car_position - ctx.stop_position).abs() + 1000.0) } } fn notify_removed(&mut self, elevator: EntityId) { self.sweep_up.remove(&elevator); } } ``` -------------------------------- ### Run Elevator Core Benchmarks Source: https://github.com/andymai/elevator-core/blob/main/docs/src/performance.md Execute the benchmark suite for the elevator core crate using Cargo. Results are saved to target/criterion/ with HTML reports. ```bash cargo bench -p elevator-core ```