mod construction;
mod lifecycle;
mod topology;
use crate::components::{
AccessControl, FloorPosition, Orientation, Patience, Preferences, Rider, RiderPhase, Route,
Velocity,
};
use crate::dispatch::{BuiltinReposition, DispatchStrategy, ElevatorGroup, RepositionStrategy};
use crate::entity::EntityId;
use crate::error::SimError;
use crate::events::{Event, EventBus};
use crate::hooks::{Phase, PhaseHooks};
use crate::ids::GroupId;
use crate::metrics::Metrics;
use crate::rider_index::RiderIndex;
use crate::stop::StopId;
use crate::systems::PhaseContext;
use crate::time::TimeAdapter;
use crate::topology::TopologyGraph;
use crate::world::World;
use std::collections::{BTreeMap, HashMap, HashSet};
use std::fmt;
use std::sync::Mutex;
use std::time::Duration;
#[derive(Debug, Clone)]
pub struct ElevatorParams {
pub max_speed: f64,
pub acceleration: f64,
pub deceleration: f64,
pub weight_capacity: f64,
pub door_transition_ticks: u32,
pub door_open_ticks: u32,
pub restricted_stops: HashSet<EntityId>,
pub inspection_speed_factor: f64,
}
impl Default for ElevatorParams {
fn default() -> Self {
Self {
max_speed: 2.0,
acceleration: 1.5,
deceleration: 2.0,
weight_capacity: 800.0,
door_transition_ticks: 5,
door_open_ticks: 10,
restricted_stops: HashSet::new(),
inspection_speed_factor: 0.25,
}
}
}
#[derive(Debug, Clone)]
pub struct LineParams {
pub name: String,
pub group: GroupId,
pub orientation: Orientation,
pub min_position: f64,
pub max_position: f64,
pub position: Option<FloorPosition>,
pub max_cars: Option<usize>,
}
impl LineParams {
pub fn new(name: impl Into<String>, group: GroupId) -> Self {
Self {
name: name.into(),
group,
orientation: Orientation::default(),
min_position: 0.0,
max_position: 0.0,
position: None,
max_cars: None,
}
}
}
pub struct RiderBuilder<'a> {
sim: &'a mut Simulation,
origin: EntityId,
destination: EntityId,
weight: f64,
group: Option<GroupId>,
route: Option<Route>,
patience: Option<u64>,
preferences: Option<Preferences>,
access_control: Option<AccessControl>,
}
impl RiderBuilder<'_> {
#[must_use]
pub const fn weight(mut self, weight: f64) -> Self {
self.weight = weight;
self
}
#[must_use]
pub const fn group(mut self, group: GroupId) -> Self {
self.group = Some(group);
self
}
#[must_use]
pub fn route(mut self, route: Route) -> Self {
self.route = Some(route);
self
}
#[must_use]
pub const fn patience(mut self, max_wait_ticks: u64) -> Self {
self.patience = Some(max_wait_ticks);
self
}
#[must_use]
pub const fn preferences(mut self, prefs: Preferences) -> Self {
self.preferences = Some(prefs);
self
}
#[must_use]
pub fn access_control(mut self, ac: AccessControl) -> Self {
self.access_control = Some(ac);
self
}
pub fn spawn(self) -> Result<EntityId, SimError> {
let route = if let Some(route) = self.route {
route
} else if let Some(group) = self.group {
if !self.sim.groups.iter().any(|g| g.id() == group) {
return Err(SimError::GroupNotFound(group));
}
Route::direct(self.origin, self.destination, group)
} else {
let matching: Vec<GroupId> = self
.sim
.groups
.iter()
.filter(|g| {
g.stop_entities().contains(&self.origin)
&& g.stop_entities().contains(&self.destination)
})
.map(ElevatorGroup::id)
.collect();
match matching.len() {
0 => {
let origin_groups: Vec<GroupId> = self
.sim
.groups
.iter()
.filter(|g| g.stop_entities().contains(&self.origin))
.map(ElevatorGroup::id)
.collect();
let destination_groups: Vec<GroupId> = self
.sim
.groups
.iter()
.filter(|g| g.stop_entities().contains(&self.destination))
.map(ElevatorGroup::id)
.collect();
return Err(SimError::NoRoute {
origin: self.origin,
destination: self.destination,
origin_groups,
destination_groups,
});
}
1 => Route::direct(self.origin, self.destination, matching[0]),
_ => {
return Err(SimError::AmbiguousRoute {
origin: self.origin,
destination: self.destination,
groups: matching,
});
}
}
};
let eid = self
.sim
.spawn_rider_inner(self.origin, self.destination, self.weight, route);
if let Some(max_wait) = self.patience {
self.sim.world.set_patience(
eid,
Patience {
max_wait_ticks: max_wait,
waited_ticks: 0,
},
);
}
if let Some(prefs) = self.preferences {
self.sim.world.set_preferences(eid, prefs);
}
if let Some(ac) = self.access_control {
self.sim.world.set_access_control(eid, ac);
}
Ok(eid)
}
}
pub struct Simulation {
world: World,
events: EventBus,
pending_output: Vec<Event>,
tick: u64,
dt: f64,
groups: Vec<ElevatorGroup>,
stop_lookup: HashMap<StopId, EntityId>,
dispatchers: BTreeMap<GroupId, Box<dyn DispatchStrategy>>,
strategy_ids: BTreeMap<GroupId, crate::dispatch::BuiltinStrategy>,
repositioners: BTreeMap<GroupId, Box<dyn RepositionStrategy>>,
reposition_ids: BTreeMap<GroupId, BuiltinReposition>,
metrics: Metrics,
time: TimeAdapter,
hooks: PhaseHooks,
elevator_ids_buf: Vec<EntityId>,
topo_graph: Mutex<TopologyGraph>,
rider_index: RiderIndex,
}
impl Simulation {
#[must_use]
pub const fn world(&self) -> &World {
&self.world
}
pub const fn world_mut(&mut self) -> &mut World {
&mut self.world
}
#[must_use]
pub const fn current_tick(&self) -> u64 {
self.tick
}
#[must_use]
pub const fn dt(&self) -> f64 {
self.dt
}
#[must_use]
pub fn position_at(&self, id: EntityId, alpha: f64) -> Option<f64> {
let current = self.world.position(id)?.value;
let alpha = if alpha.is_nan() {
0.0
} else {
alpha.clamp(0.0, 1.0)
};
let prev = self.world.prev_position(id).map_or(current, |p| p.value);
Some((current - prev).mul_add(alpha, prev))
}
#[must_use]
pub fn velocity(&self, id: EntityId) -> Option<f64> {
self.world.velocity(id).map(Velocity::value)
}
#[must_use]
pub const fn metrics(&self) -> &Metrics {
&self.metrics
}
#[must_use]
pub const fn time(&self) -> &TimeAdapter {
&self.time
}
#[must_use]
pub fn groups(&self) -> &[ElevatorGroup] {
&self.groups
}
#[must_use]
pub fn stop_entity(&self, id: StopId) -> Option<EntityId> {
self.stop_lookup.get(&id).copied()
}
#[must_use]
pub fn strategy_id(&self, group: GroupId) -> Option<&crate::dispatch::BuiltinStrategy> {
self.strategy_ids.get(&group)
}
pub fn stop_lookup_iter(&self) -> impl Iterator<Item = (&StopId, &EntityId)> {
self.stop_lookup.iter()
}
#[must_use]
pub fn pending_events(&self) -> &[Event] {
&self.pending_output
}
#[must_use]
pub fn destination_queue(&self, elev: EntityId) -> Option<&[EntityId]> {
self.world
.destination_queue(elev)
.map(crate::components::DestinationQueue::queue)
}
pub fn push_destination(&mut self, elev: EntityId, stop: EntityId) -> Result<(), SimError> {
self.validate_push_targets(elev, stop)?;
let appended = self
.world
.destination_queue_mut(elev)
.is_some_and(|q| q.push_back(stop));
if appended {
self.events.emit(Event::DestinationQueued {
elevator: elev,
stop,
tick: self.tick,
});
}
Ok(())
}
pub fn push_destination_front(
&mut self,
elev: EntityId,
stop: EntityId,
) -> Result<(), SimError> {
self.validate_push_targets(elev, stop)?;
let inserted = self
.world
.destination_queue_mut(elev)
.is_some_and(|q| q.push_front(stop));
if inserted {
self.events.emit(Event::DestinationQueued {
elevator: elev,
stop,
tick: self.tick,
});
}
Ok(())
}
pub fn clear_destinations(&mut self, elev: EntityId) -> Result<(), SimError> {
if self.world.elevator(elev).is_none() {
return Err(SimError::InvalidState {
entity: elev,
reason: "not an elevator".into(),
});
}
if let Some(q) = self.world.destination_queue_mut(elev) {
q.clear();
}
Ok(())
}
fn validate_push_targets(&self, elev: EntityId, stop: EntityId) -> Result<(), SimError> {
if self.world.elevator(elev).is_none() {
return Err(SimError::InvalidState {
entity: elev,
reason: "not an elevator".into(),
});
}
if self.world.stop(stop).is_none() {
return Err(SimError::InvalidState {
entity: stop,
reason: "not a stop".into(),
});
}
Ok(())
}
#[must_use]
pub fn eta(&self, elev: EntityId, stop: EntityId) -> Option<Duration> {
let elevator = self.world.elevator(elev)?;
self.world.stop(stop)?;
let svc = self.world.service_mode(elev).copied().unwrap_or_default();
if svc.is_dispatch_excluded() {
return None;
}
let mut route: Vec<EntityId> = Vec::new();
if let Some(t) = elevator.phase().moving_target() {
route.push(t);
}
if let Some(q) = self.world.destination_queue(elev) {
for &s in q.queue() {
if route.last() != Some(&s) {
route.push(s);
}
}
}
if !route.contains(&stop) {
return None;
}
let max_speed = elevator.max_speed();
let accel = elevator.acceleration();
let decel = elevator.deceleration();
let door_cycle_ticks =
u64::from(elevator.door_transition_ticks()) * 2 + u64::from(elevator.door_open_ticks());
let door_cycle_secs = (door_cycle_ticks as f64) * self.dt;
let mut total = match elevator.door() {
crate::door::DoorState::Opening {
ticks_remaining,
open_duration,
close_duration,
} => f64::from(*ticks_remaining + *open_duration + *close_duration) * self.dt,
crate::door::DoorState::Open {
ticks_remaining,
close_duration,
} => f64::from(*ticks_remaining + *close_duration) * self.dt,
crate::door::DoorState::Closing { ticks_remaining } => {
f64::from(*ticks_remaining) * self.dt
}
crate::door::DoorState::Closed => 0.0,
};
let in_door_cycle = !matches!(elevator.door(), crate::door::DoorState::Closed);
let mut pos = self.world.position(elev)?.value;
let vel_signed = self.world.velocity(elev).map_or(0.0, Velocity::value);
for (idx, &s) in route.iter().enumerate() {
let Some(s_pos) = self.world.stop_position(s) else {
return None;
};
let dist = (s_pos - pos).abs();
let v0 = if idx == 0 && !in_door_cycle && vel_signed.abs() > f64::EPSILON {
let dir = (s_pos - pos).signum();
if dir * vel_signed > 0.0 {
vel_signed.abs()
} else {
0.0
}
} else {
0.0
};
total += crate::eta::travel_time(dist, v0, max_speed, accel, decel);
if s == stop {
return Some(Duration::from_secs_f64(total.max(0.0)));
}
total += door_cycle_secs;
pos = s_pos;
}
None
}
#[must_use]
pub fn best_eta(
&self,
stop: EntityId,
direction: crate::components::Direction,
) -> Option<(EntityId, Duration)> {
use crate::components::Direction;
self.world
.iter_elevators()
.filter_map(|(eid, _, elev)| {
let car_dir = elev.direction();
let direction_ok = match direction {
Direction::Either => true,
requested => car_dir == Direction::Either || car_dir == requested,
};
if !direction_ok {
return None;
}
self.eta(eid, stop).map(|d| (eid, d))
})
.min_by_key(|(_, d)| *d)
}
pub fn set_max_speed(&mut self, elevator: EntityId, speed: f64) -> Result<(), SimError> {
Self::validate_positive_finite_f64(speed, "elevators.max_speed")?;
let old = self.require_elevator(elevator)?.max_speed;
if let Some(car) = self.world.elevator_mut(elevator) {
car.max_speed = speed;
}
self.emit_upgrade(
elevator,
crate::events::UpgradeField::MaxSpeed,
crate::events::UpgradeValue::float(old),
crate::events::UpgradeValue::float(speed),
);
Ok(())
}
pub fn set_acceleration(&mut self, elevator: EntityId, accel: f64) -> Result<(), SimError> {
Self::validate_positive_finite_f64(accel, "elevators.acceleration")?;
let old = self.require_elevator(elevator)?.acceleration;
if let Some(car) = self.world.elevator_mut(elevator) {
car.acceleration = accel;
}
self.emit_upgrade(
elevator,
crate::events::UpgradeField::Acceleration,
crate::events::UpgradeValue::float(old),
crate::events::UpgradeValue::float(accel),
);
Ok(())
}
pub fn set_deceleration(&mut self, elevator: EntityId, decel: f64) -> Result<(), SimError> {
Self::validate_positive_finite_f64(decel, "elevators.deceleration")?;
let old = self.require_elevator(elevator)?.deceleration;
if let Some(car) = self.world.elevator_mut(elevator) {
car.deceleration = decel;
}
self.emit_upgrade(
elevator,
crate::events::UpgradeField::Deceleration,
crate::events::UpgradeValue::float(old),
crate::events::UpgradeValue::float(decel),
);
Ok(())
}
pub fn set_weight_capacity(
&mut self,
elevator: EntityId,
capacity: f64,
) -> Result<(), SimError> {
Self::validate_positive_finite_f64(capacity, "elevators.weight_capacity")?;
let old = self.require_elevator(elevator)?.weight_capacity;
if let Some(car) = self.world.elevator_mut(elevator) {
car.weight_capacity = capacity;
}
self.emit_upgrade(
elevator,
crate::events::UpgradeField::WeightCapacity,
crate::events::UpgradeValue::float(old),
crate::events::UpgradeValue::float(capacity),
);
Ok(())
}
pub fn set_door_transition_ticks(
&mut self,
elevator: EntityId,
ticks: u32,
) -> Result<(), SimError> {
Self::validate_nonzero_u32(ticks, "elevators.door_transition_ticks")?;
let old = self.require_elevator(elevator)?.door_transition_ticks;
if let Some(car) = self.world.elevator_mut(elevator) {
car.door_transition_ticks = ticks;
}
self.emit_upgrade(
elevator,
crate::events::UpgradeField::DoorTransitionTicks,
crate::events::UpgradeValue::ticks(old),
crate::events::UpgradeValue::ticks(ticks),
);
Ok(())
}
pub fn set_door_open_ticks(&mut self, elevator: EntityId, ticks: u32) -> Result<(), SimError> {
Self::validate_nonzero_u32(ticks, "elevators.door_open_ticks")?;
let old = self.require_elevator(elevator)?.door_open_ticks;
if let Some(car) = self.world.elevator_mut(elevator) {
car.door_open_ticks = ticks;
}
self.emit_upgrade(
elevator,
crate::events::UpgradeField::DoorOpenTicks,
crate::events::UpgradeValue::ticks(old),
crate::events::UpgradeValue::ticks(ticks),
);
Ok(())
}
pub fn request_door_open(&mut self, elevator: EntityId) -> Result<(), SimError> {
self.require_enabled_elevator(elevator)?;
self.enqueue_door_command(elevator, crate::door::DoorCommand::Open);
Ok(())
}
pub fn request_door_close(&mut self, elevator: EntityId) -> Result<(), SimError> {
self.require_enabled_elevator(elevator)?;
self.enqueue_door_command(elevator, crate::door::DoorCommand::Close);
Ok(())
}
pub fn hold_door_open(&mut self, elevator: EntityId, ticks: u32) -> Result<(), SimError> {
Self::validate_nonzero_u32(ticks, "hold_door_open.ticks")?;
self.require_enabled_elevator(elevator)?;
self.enqueue_door_command(elevator, crate::door::DoorCommand::HoldOpen { ticks });
Ok(())
}
pub fn cancel_door_hold(&mut self, elevator: EntityId) -> Result<(), SimError> {
self.require_enabled_elevator(elevator)?;
self.enqueue_door_command(elevator, crate::door::DoorCommand::CancelHold);
Ok(())
}
pub fn set_target_velocity(
&mut self,
elevator: EntityId,
velocity: f64,
) -> Result<(), SimError> {
self.require_enabled_elevator(elevator)?;
self.require_manual_mode(elevator)?;
if !velocity.is_finite() {
return Err(SimError::InvalidConfig {
field: "target_velocity",
reason: format!("must be finite, got {velocity}"),
});
}
let max = self
.world
.elevator(elevator)
.map_or(f64::INFINITY, |c| c.max_speed);
let clamped = velocity.clamp(-max, max);
if let Some(car) = self.world.elevator_mut(elevator) {
car.manual_target_velocity = Some(clamped);
}
self.events.emit(Event::ManualVelocityCommanded {
elevator,
target_velocity: Some(ordered_float::OrderedFloat(clamped)),
tick: self.tick,
});
Ok(())
}
pub fn emergency_stop(&mut self, elevator: EntityId) -> Result<(), SimError> {
self.require_enabled_elevator(elevator)?;
self.require_manual_mode(elevator)?;
if let Some(car) = self.world.elevator_mut(elevator) {
car.manual_target_velocity = Some(0.0);
}
self.events.emit(Event::ManualVelocityCommanded {
elevator,
target_velocity: None,
tick: self.tick,
});
Ok(())
}
fn require_manual_mode(&self, elevator: EntityId) -> Result<(), SimError> {
let is_manual = self
.world
.service_mode(elevator)
.is_some_and(|m| *m == crate::components::ServiceMode::Manual);
if !is_manual {
return Err(SimError::InvalidState {
entity: elevator,
reason: "elevator is not in ServiceMode::Manual".into(),
});
}
Ok(())
}
fn enqueue_door_command(&mut self, elevator: EntityId, command: crate::door::DoorCommand) {
if let Some(car) = self.world.elevator_mut(elevator) {
let q = &mut car.door_command_queue;
let collapse = matches!(
command,
crate::door::DoorCommand::Open
| crate::door::DoorCommand::Close
| crate::door::DoorCommand::CancelHold
) && q.last().copied() == Some(command);
if !collapse {
q.push(command);
if q.len() > crate::components::DOOR_COMMAND_QUEUE_CAP {
q.remove(0);
}
}
}
self.events.emit(Event::DoorCommandQueued {
elevator,
command,
tick: self.tick,
});
}
fn require_enabled_elevator(&self, elevator: EntityId) -> Result<(), SimError> {
if self.world.elevator(elevator).is_none() {
return Err(SimError::InvalidState {
entity: elevator,
reason: "not an elevator".into(),
});
}
if self.world.is_disabled(elevator) {
return Err(SimError::InvalidState {
entity: elevator,
reason: "elevator is disabled".into(),
});
}
Ok(())
}
fn require_elevator(
&self,
elevator: EntityId,
) -> Result<&crate::components::Elevator, SimError> {
self.world
.elevator(elevator)
.ok_or_else(|| SimError::InvalidState {
entity: elevator,
reason: "not an elevator".into(),
})
}
fn validate_positive_finite_f64(value: f64, field: &'static str) -> Result<(), SimError> {
if !value.is_finite() {
return Err(SimError::InvalidConfig {
field,
reason: format!("must be finite, got {value}"),
});
}
if value <= 0.0 {
return Err(SimError::InvalidConfig {
field,
reason: format!("must be positive, got {value}"),
});
}
Ok(())
}
fn validate_nonzero_u32(value: u32, field: &'static str) -> Result<(), SimError> {
if value == 0 {
return Err(SimError::InvalidConfig {
field,
reason: "must be > 0".into(),
});
}
Ok(())
}
fn emit_upgrade(
&mut self,
elevator: EntityId,
field: crate::events::UpgradeField,
old: crate::events::UpgradeValue,
new: crate::events::UpgradeValue,
) {
self.events.emit(Event::ElevatorUpgraded {
elevator,
field,
old,
new,
tick: self.tick,
});
}
pub fn tag_entity(&mut self, id: EntityId, tag: impl Into<String>) {
if let Some(tags) = self
.world
.resource_mut::<crate::tagged_metrics::MetricTags>()
{
tags.tag(id, tag);
}
}
pub fn untag_entity(&mut self, id: EntityId, tag: &str) {
if let Some(tags) = self
.world
.resource_mut::<crate::tagged_metrics::MetricTags>()
{
tags.untag(id, tag);
}
}
#[must_use]
pub fn metrics_for_tag(&self, tag: &str) -> Option<&crate::tagged_metrics::TaggedMetric> {
self.world
.resource::<crate::tagged_metrics::MetricTags>()
.and_then(|tags| tags.metric(tag))
}
pub fn all_tags(&self) -> Vec<&str> {
self.world
.resource::<crate::tagged_metrics::MetricTags>()
.map_or_else(Vec::new, |tags| tags.all_tags().collect())
}
pub const fn build_rider(
&mut self,
origin: EntityId,
destination: EntityId,
) -> RiderBuilder<'_> {
RiderBuilder {
sim: self,
origin,
destination,
weight: 75.0,
group: None,
route: None,
patience: None,
preferences: None,
access_control: None,
}
}
pub fn build_rider_by_stop_id(
&mut self,
origin: StopId,
destination: StopId,
) -> Result<RiderBuilder<'_>, SimError> {
let origin_eid = self
.stop_lookup
.get(&origin)
.copied()
.ok_or(SimError::StopNotFound(origin))?;
let dest_eid = self
.stop_lookup
.get(&destination)
.copied()
.ok_or(SimError::StopNotFound(destination))?;
Ok(RiderBuilder {
sim: self,
origin: origin_eid,
destination: dest_eid,
weight: 75.0,
group: None,
route: None,
patience: None,
preferences: None,
access_control: None,
})
}
pub fn spawn_rider(
&mut self,
origin: EntityId,
destination: EntityId,
weight: f64,
) -> Result<EntityId, SimError> {
let matching: Vec<GroupId> = self
.groups
.iter()
.filter(|g| {
g.stop_entities().contains(&origin) && g.stop_entities().contains(&destination)
})
.map(ElevatorGroup::id)
.collect();
let group = match matching.len() {
0 => {
let origin_groups: Vec<GroupId> = self
.groups
.iter()
.filter(|g| g.stop_entities().contains(&origin))
.map(ElevatorGroup::id)
.collect();
let destination_groups: Vec<GroupId> = self
.groups
.iter()
.filter(|g| g.stop_entities().contains(&destination))
.map(ElevatorGroup::id)
.collect();
return Err(SimError::NoRoute {
origin,
destination,
origin_groups,
destination_groups,
});
}
1 => matching[0],
_ => {
return Err(SimError::AmbiguousRoute {
origin,
destination,
groups: matching,
});
}
};
let route = Route::direct(origin, destination, group);
Ok(self.spawn_rider_inner(origin, destination, weight, route))
}
pub fn spawn_rider_with_route(
&mut self,
origin: EntityId,
destination: EntityId,
weight: f64,
route: Route,
) -> Result<EntityId, SimError> {
if self.world.stop(origin).is_none() {
return Err(SimError::EntityNotFound(origin));
}
if let Some(leg) = route.current()
&& leg.from != origin
{
return Err(SimError::InvalidState {
entity: origin,
reason: format!(
"origin {origin:?} does not match route first leg from {:?}",
leg.from
),
});
}
Ok(self.spawn_rider_inner(origin, destination, weight, route))
}
fn spawn_rider_inner(
&mut self,
origin: EntityId,
destination: EntityId,
weight: f64,
route: Route,
) -> EntityId {
let eid = self.world.spawn();
self.world.set_rider(
eid,
Rider {
weight,
phase: RiderPhase::Waiting,
current_stop: Some(origin),
spawn_tick: self.tick,
board_tick: None,
},
);
self.world.set_route(eid, route);
self.rider_index.insert_waiting(origin, eid);
self.events.emit(Event::RiderSpawned {
rider: eid,
origin,
destination,
tick: self.tick,
});
let stop_tag = self
.world
.stop(origin)
.map(|s| format!("stop:{}", s.name()));
if let Some(tags_res) = self
.world
.resource_mut::<crate::tagged_metrics::MetricTags>()
{
let origin_tags: Vec<String> = tags_res.tags_for(origin).to_vec();
for tag in origin_tags {
tags_res.tag(eid, tag);
}
if let Some(tag) = stop_tag {
tags_res.tag(eid, tag);
}
}
eid
}
pub fn spawn_rider_by_stop_id(
&mut self,
origin: StopId,
destination: StopId,
weight: f64,
) -> Result<EntityId, SimError> {
let origin_eid = self
.stop_lookup
.get(&origin)
.copied()
.ok_or(SimError::StopNotFound(origin))?;
let dest_eid = self
.stop_lookup
.get(&destination)
.copied()
.ok_or(SimError::StopNotFound(destination))?;
self.spawn_rider(origin_eid, dest_eid, weight)
}
pub fn spawn_rider_in_group(
&mut self,
origin: EntityId,
destination: EntityId,
weight: f64,
group: GroupId,
) -> Result<EntityId, SimError> {
if !self.groups.iter().any(|g| g.id() == group) {
return Err(SimError::GroupNotFound(group));
}
let route = Route::direct(origin, destination, group);
Ok(self.spawn_rider_inner(origin, destination, weight, route))
}
pub fn spawn_rider_in_group_by_stop_id(
&mut self,
origin: StopId,
destination: StopId,
weight: f64,
group: GroupId,
) -> Result<EntityId, SimError> {
let origin_eid = self
.stop_lookup
.get(&origin)
.copied()
.ok_or(SimError::StopNotFound(origin))?;
let dest_eid = self
.stop_lookup
.get(&destination)
.copied()
.ok_or(SimError::StopNotFound(destination))?;
self.spawn_rider_in_group(origin_eid, dest_eid, weight, group)
}
pub fn drain_events(&mut self) -> Vec<Event> {
self.pending_output.extend(self.events.drain());
std::mem::take(&mut self.pending_output)
}
pub fn drain_events_where(&mut self, predicate: impl Fn(&Event) -> bool) -> Vec<Event> {
self.pending_output.extend(self.events.drain());
let mut matched = Vec::new();
let mut remaining = Vec::new();
for event in std::mem::take(&mut self.pending_output) {
if predicate(&event) {
matched.push(event);
} else {
remaining.push(event);
}
}
self.pending_output = remaining;
matched
}
#[must_use]
pub fn dispatchers(&self) -> &BTreeMap<GroupId, Box<dyn DispatchStrategy>> {
&self.dispatchers
}
pub fn dispatchers_mut(&mut self) -> &mut BTreeMap<GroupId, Box<dyn DispatchStrategy>> {
&mut self.dispatchers
}
pub const fn events_mut(&mut self) -> &mut EventBus {
&mut self.events
}
pub const fn metrics_mut(&mut self) -> &mut Metrics {
&mut self.metrics
}
#[must_use]
pub const fn phase_context(&self) -> PhaseContext {
PhaseContext {
tick: self.tick,
dt: self.dt,
}
}
pub fn run_advance_transient(&mut self) {
self.hooks
.run_before(Phase::AdvanceTransient, &mut self.world);
for group in &self.groups {
self.hooks
.run_before_group(Phase::AdvanceTransient, group.id(), &mut self.world);
}
let ctx = self.phase_context();
crate::systems::advance_transient::run(
&mut self.world,
&mut self.events,
&ctx,
&mut self.rider_index,
);
for group in &self.groups {
self.hooks
.run_after_group(Phase::AdvanceTransient, group.id(), &mut self.world);
}
self.hooks
.run_after(Phase::AdvanceTransient, &mut self.world);
}
pub fn run_dispatch(&mut self) {
self.hooks.run_before(Phase::Dispatch, &mut self.world);
for group in &self.groups {
self.hooks
.run_before_group(Phase::Dispatch, group.id(), &mut self.world);
}
let ctx = self.phase_context();
crate::systems::dispatch::run(
&mut self.world,
&mut self.events,
&ctx,
&self.groups,
&mut self.dispatchers,
&self.rider_index,
);
for group in &self.groups {
self.hooks
.run_after_group(Phase::Dispatch, group.id(), &mut self.world);
}
self.hooks.run_after(Phase::Dispatch, &mut self.world);
}
pub fn run_movement(&mut self) {
self.hooks.run_before(Phase::Movement, &mut self.world);
for group in &self.groups {
self.hooks
.run_before_group(Phase::Movement, group.id(), &mut self.world);
}
let ctx = self.phase_context();
self.world.elevator_ids_into(&mut self.elevator_ids_buf);
crate::systems::movement::run(
&mut self.world,
&mut self.events,
&ctx,
&self.elevator_ids_buf,
&mut self.metrics,
);
for group in &self.groups {
self.hooks
.run_after_group(Phase::Movement, group.id(), &mut self.world);
}
self.hooks.run_after(Phase::Movement, &mut self.world);
}
pub fn run_doors(&mut self) {
self.hooks.run_before(Phase::Doors, &mut self.world);
for group in &self.groups {
self.hooks
.run_before_group(Phase::Doors, group.id(), &mut self.world);
}
let ctx = self.phase_context();
self.world.elevator_ids_into(&mut self.elevator_ids_buf);
crate::systems::doors::run(
&mut self.world,
&mut self.events,
&ctx,
&self.elevator_ids_buf,
);
for group in &self.groups {
self.hooks
.run_after_group(Phase::Doors, group.id(), &mut self.world);
}
self.hooks.run_after(Phase::Doors, &mut self.world);
}
pub fn run_loading(&mut self) {
self.hooks.run_before(Phase::Loading, &mut self.world);
for group in &self.groups {
self.hooks
.run_before_group(Phase::Loading, group.id(), &mut self.world);
}
let ctx = self.phase_context();
self.world.elevator_ids_into(&mut self.elevator_ids_buf);
crate::systems::loading::run(
&mut self.world,
&mut self.events,
&ctx,
&self.elevator_ids_buf,
&mut self.rider_index,
);
for group in &self.groups {
self.hooks
.run_after_group(Phase::Loading, group.id(), &mut self.world);
}
self.hooks.run_after(Phase::Loading, &mut self.world);
}
pub fn run_advance_queue(&mut self) {
self.hooks.run_before(Phase::AdvanceQueue, &mut self.world);
for group in &self.groups {
self.hooks
.run_before_group(Phase::AdvanceQueue, group.id(), &mut self.world);
}
let ctx = self.phase_context();
self.world.elevator_ids_into(&mut self.elevator_ids_buf);
crate::systems::advance_queue::run(
&mut self.world,
&mut self.events,
&ctx,
&self.elevator_ids_buf,
);
for group in &self.groups {
self.hooks
.run_after_group(Phase::AdvanceQueue, group.id(), &mut self.world);
}
self.hooks.run_after(Phase::AdvanceQueue, &mut self.world);
}
pub fn run_reposition(&mut self) {
if self.repositioners.is_empty() {
return;
}
self.hooks.run_before(Phase::Reposition, &mut self.world);
for group in &self.groups {
if self.repositioners.contains_key(&group.id()) {
self.hooks
.run_before_group(Phase::Reposition, group.id(), &mut self.world);
}
}
let ctx = self.phase_context();
crate::systems::reposition::run(
&mut self.world,
&mut self.events,
&ctx,
&self.groups,
&mut self.repositioners,
);
for group in &self.groups {
if self.repositioners.contains_key(&group.id()) {
self.hooks
.run_after_group(Phase::Reposition, group.id(), &mut self.world);
}
}
self.hooks.run_after(Phase::Reposition, &mut self.world);
}
#[cfg(feature = "energy")]
fn run_energy(&mut self) {
let ctx = self.phase_context();
self.world.elevator_ids_into(&mut self.elevator_ids_buf);
crate::systems::energy::run(
&mut self.world,
&mut self.events,
&ctx,
&self.elevator_ids_buf,
);
}
pub fn run_metrics(&mut self) {
self.hooks.run_before(Phase::Metrics, &mut self.world);
for group in &self.groups {
self.hooks
.run_before_group(Phase::Metrics, group.id(), &mut self.world);
}
let ctx = self.phase_context();
crate::systems::metrics::run(
&mut self.world,
&self.events,
&mut self.metrics,
&ctx,
&self.groups,
);
for group in &self.groups {
self.hooks
.run_after_group(Phase::Metrics, group.id(), &mut self.world);
}
self.hooks.run_after(Phase::Metrics, &mut self.world);
}
pub fn advance_tick(&mut self) {
self.pending_output.extend(self.events.drain());
self.tick += 1;
}
pub fn step(&mut self) {
self.world.snapshot_prev_positions();
self.run_advance_transient();
self.run_dispatch();
self.run_reposition();
self.run_advance_queue();
self.run_movement();
self.run_doors();
self.run_loading();
#[cfg(feature = "energy")]
self.run_energy();
self.run_metrics();
self.advance_tick();
}
}
impl fmt::Debug for Simulation {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("Simulation")
.field("tick", &self.tick)
.field("dt", &self.dt)
.field("groups", &self.groups.len())
.field("entities", &self.world.entity_count())
.finish_non_exhaustive()
}
}