use crate::components::{ElevatorPhase, RiderPhase, Route};
use crate::dispatch::{
DispatchDecision, DispatchManifest, DispatchStrategy, ElevatorGroup, RiderInfo,
};
use crate::entity::EntityId;
use crate::events::{Event, EventBus};
use crate::ids::GroupId;
use crate::rider_index::RiderIndex;
use crate::world::World;
use std::collections::BTreeMap;
use super::PhaseContext;
pub fn run(
world: &mut World,
events: &mut EventBus,
ctx: &PhaseContext,
groups: &[ElevatorGroup],
dispatchers: &mut BTreeMap<GroupId, Box<dyn DispatchStrategy>>,
rider_index: &RiderIndex,
) {
for group in groups {
let manifest = build_manifest(world, group, ctx.tick, rider_index);
let idle_elevators: Vec<(EntityId, f64)> = group
.elevator_entities()
.iter()
.filter_map(|eid| {
if world.is_disabled(*eid) {
return None;
}
if world
.service_mode(*eid)
.is_some_and(|m| *m == crate::components::ServiceMode::Independent)
{
return None;
}
let car = world.elevator(*eid)?;
if matches!(car.phase, ElevatorPhase::Idle | ElevatorPhase::Stopped) {
let pos = world.position(*eid)?.value;
Some((*eid, pos))
} else {
None
}
})
.collect();
if idle_elevators.is_empty() {
continue;
}
let Some(dispatch) = dispatchers.get_mut(&group.id()) else {
continue;
};
let decisions = dispatch.decide_all(&idle_elevators, group, &manifest, world);
for (eid, decision) in decisions {
match decision {
DispatchDecision::GoToStop(stop_eid) => {
let pos = world.position(eid).map_or(0.0, |p| p.value);
let current_stop = world.find_stop_at_position(pos);
events.emit(Event::ElevatorAssigned {
elevator: eid,
stop: stop_eid,
tick: ctx.tick,
});
let target_pos = world.stop_position(stop_eid).unwrap_or(pos);
let (new_up, new_down) = if target_pos > pos {
(true, false)
} else if target_pos < pos {
(false, true)
} else {
(true, true)
};
update_indicators(world, events, eid, new_up, new_down, ctx.tick);
if current_stop == Some(stop_eid) {
events.emit(Event::ElevatorArrived {
elevator: eid,
at_stop: stop_eid,
tick: ctx.tick,
});
if let Some(car) = world.elevator_mut(eid) {
car.phase = ElevatorPhase::DoorOpening;
car.door = crate::door::DoorState::request_open(
car.door_transition_ticks,
car.door_open_ticks,
);
}
continue;
}
if let Some(car) = world.elevator_mut(eid) {
car.phase = ElevatorPhase::MovingToStop(stop_eid);
car.target_stop = Some(stop_eid);
car.repositioning = false;
}
if let Some(from) = current_stop {
events.emit(Event::ElevatorDeparted {
elevator: eid,
from_stop: from,
tick: ctx.tick,
});
}
}
DispatchDecision::Idle => {
let was_idle = world
.elevator(eid)
.is_some_and(|car| car.phase == ElevatorPhase::Idle);
if let Some(car) = world.elevator_mut(eid) {
car.phase = ElevatorPhase::Idle;
}
update_indicators(world, events, eid, true, true, ctx.tick);
if !was_idle {
let at_stop = world
.position(eid)
.and_then(|p| world.find_stop_at_position(p.value));
events.emit(Event::ElevatorIdle {
elevator: eid,
at_stop,
tick: ctx.tick,
});
}
}
}
}
}
}
fn update_indicators(
world: &mut World,
events: &mut EventBus,
eid: EntityId,
new_up: bool,
new_down: bool,
tick: u64,
) {
let Some(car) = world.elevator_mut(eid) else {
return;
};
if car.going_up == new_up && car.going_down == new_down {
return;
}
car.going_up = new_up;
car.going_down = new_down;
events.emit(Event::DirectionIndicatorChanged {
elevator: eid,
going_up: new_up,
going_down: new_down,
tick,
});
}
fn build_manifest(
world: &World,
group: &ElevatorGroup,
tick: u64,
rider_index: &RiderIndex,
) -> DispatchManifest {
let mut manifest = DispatchManifest::default();
for (rid, rider) in world.iter_riders() {
if world.is_disabled(rid) {
continue;
}
if rider.phase != RiderPhase::Waiting {
continue;
}
if let Some(stop) = rider.current_stop
&& group.stop_entities().contains(&stop)
{
let destination = world.route(rid).and_then(Route::current_destination);
let wait_ticks = tick.saturating_sub(rider.spawn_tick);
manifest
.waiting_at_stop
.entry(stop)
.or_default()
.push(RiderInfo {
id: rid,
destination,
weight: rider.weight,
wait_ticks,
});
}
}
for &elev_eid in group.elevator_entities() {
if let Some(car) = world.elevator(elev_eid) {
for &rider_eid in car.riders() {
let destination = world.route(rider_eid).and_then(Route::current_destination);
if let Some(dest) = destination {
let rider = world.rider(rider_eid);
let weight = rider.map_or(0.0, |r| r.weight);
manifest
.riding_to_stop
.entry(dest)
.or_default()
.push(RiderInfo {
id: rider_eid,
destination: Some(dest),
weight,
wait_ticks: 0,
});
}
}
}
}
for &stop in group.stop_entities() {
let count = rider_index.resident_count_at(stop);
if count > 0 {
manifest.resident_count_at_stop.insert(stop, count);
}
}
manifest
}