use crate::components::{ElevatorPhase, RiderPhase, Route, TransportMode};
use crate::dispatch::{
self, 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;
#[allow(clippy::too_many_lines)]
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);
if let Some(dispatch) = dispatchers.get_mut(&group.id()) {
dispatch.pre_dispatch(group, &manifest, world);
}
let pinned_pairs: Vec<(EntityId, EntityId)> = world
.iter_hall_calls()
.filter(|c| c.pinned)
.filter_map(|c| {
c.assigned_car.and_then(|car| {
if group.stop_entities().contains(&c.stop)
&& group.elevator_entities().contains(&car)
{
Some((car, c.stop))
} else {
None
}
})
})
.collect();
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.is_dispatch_excluded())
{
return None;
}
if pinned_pairs.iter().any(|(car, _)| car == eid) {
return None;
}
let car = world.elevator(*eid)?;
let eligible = matches!(car.phase, ElevatorPhase::Idle | ElevatorPhase::Stopped)
|| (matches!(car.phase, ElevatorPhase::MovingToStop(_))
&& car.riders.is_empty()
&& !car.repositioning);
if eligible {
let pos = world.position(*eid)?.value;
Some((*eid, pos))
} else {
None
}
})
.collect();
for (car_eid, stop_eid) in pinned_pairs.iter().copied() {
let eligible = world.elevator(car_eid).is_some_and(|c| {
matches!(c.phase, ElevatorPhase::Idle | ElevatorPhase::Stopped)
|| (matches!(c.phase, ElevatorPhase::MovingToStop(_)) && c.riders.is_empty())
});
if eligible {
commit_go_to_stop(world, events, ctx, car_eid, stop_eid);
}
}
if idle_elevators.is_empty() {
continue;
}
let Some(dispatch) = dispatchers.get_mut(&group.id()) else {
continue;
};
let result = dispatch::assign(dispatch.as_mut(), &idle_elevators, group, &manifest, world);
for (eid, decision) in result.decisions {
match decision {
DispatchDecision::GoToStop(stop_eid) => {
commit_go_to_stop(world, events, ctx, eid, stop_eid);
record_hall_assignment(world, stop_eid, eid);
}
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 commit_go_to_stop(
world: &mut World,
events: &mut EventBus,
ctx: &PhaseContext,
eid: EntityId,
stop_eid: EntityId,
) {
if world
.elevator(eid)
.is_some_and(|car| car.restricted_stops().contains(&stop_eid))
{
return;
}
if let Some(car) = world.elevator(eid)
&& car.phase == ElevatorPhase::MovingToStop(stop_eid)
{
return;
}
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) {
if let Some(q) = world.destination_queue_mut(eid)
&& q.front() == Some(stop_eid)
{
q.pop_front();
}
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.target_stop = Some(stop_eid);
car.door = crate::door::DoorState::request_open(
car.door_transition_ticks,
car.door_open_ticks,
);
}
return;
}
if let Some(q) = world.destination_queue_mut(eid)
&& !q.contains(&stop_eid)
&& q.push_back(stop_eid)
{
events.emit(Event::DestinationQueued {
elevator: eid,
stop: stop_eid,
tick: ctx.tick,
});
}
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,
});
}
}
fn record_hall_assignment(world: &mut World, stop: EntityId, car: EntityId) {
use crate::components::CallDirection;
let Some(car_pos) = world.position(car).map(|p| p.value) else {
return;
};
let Some(stop_pos) = world.stop_position(stop) else {
return;
};
let direction = if stop_pos > car_pos {
CallDirection::Up
} else if stop_pos < car_pos {
CallDirection::Down
} else {
if world.hall_call(stop, CallDirection::Up).is_some() {
CallDirection::Up
} else {
CallDirection::Down
}
};
if let Some(call) = world.hall_call_mut(stop, direction)
&& !call.pinned
{
call.assigned_car = Some(car);
}
}
pub 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)
{
if let Some(route) = world.route(rid)
&& let Some(leg) = route.current()
{
match leg.via {
TransportMode::Group(g) => {
if g != group.id() {
continue;
}
}
TransportMode::Line(l) => {
if !group.lines().iter().any(|line| line.entity() == l) {
continue;
}
}
TransportMode::Walk => continue,
}
}
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(crate::components::Weight::ZERO, |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);
}
}
for &stop in group.stop_entities() {
if let Some(stop_calls) = world.stop_calls(stop) {
let calls: Vec<_> = stop_calls
.iter()
.filter(|c| c.is_acknowledged())
.cloned()
.collect();
if !calls.is_empty() {
manifest.hall_calls_at_stop.insert(stop, calls);
}
}
}
for &car in group.elevator_entities() {
let calls: Vec<_> = world
.car_calls(car)
.iter()
.filter(|c| c.is_acknowledged())
.cloned()
.collect();
if !calls.is_empty() {
manifest.car_calls_by_car.insert(car, calls);
}
}
manifest
}