use crate::components::ElevatorPhase;
use crate::dispatch::reposition::RepositionCooldowns;
use crate::dispatch::{ElevatorGroup, RepositionStrategy};
use crate::entity::EntityId;
use crate::events::{Event, EventBus};
use crate::ids::GroupId;
use crate::world::World;
use std::collections::BTreeMap;
use super::PhaseContext;
pub fn run(
world: &mut World,
events: &mut EventBus,
ctx: &PhaseContext,
groups: &[ElevatorGroup],
repositioners: &mut BTreeMap<GroupId, Box<dyn RepositionStrategy>>,
decisions: &mut Vec<(EntityId, EntityId)>,
) {
let cooldowns_snapshot: Option<RepositionCooldowns> =
world.resource::<RepositionCooldowns>().cloned();
for group in groups {
let Some(strategy) = repositioners.get_mut(&group.id()) else {
continue;
};
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 let Some(cd) = &cooldowns_snapshot
&& cd.is_cooling_down(eid, ctx.tick)
{
return None;
}
let car = world.elevator(eid)?;
if car.phase == ElevatorPhase::Idle {
let pos = world.position(eid)?.value;
Some((eid, pos))
} else {
None
}
})
.collect();
if idle_elevators.is_empty() {
continue;
}
let stop_positions: Vec<(EntityId, f64)> = group
.stop_entities()
.iter()
.filter_map(|&sid| world.stop_position(sid).map(|p| (sid, p)))
.collect();
if stop_positions.is_empty() {
continue;
}
decisions.clear();
let strategy_pool =
apply_home_stop_overrides(world, &idle_elevators, &stop_positions, decisions);
strategy.reposition(&strategy_pool, &stop_positions, group, world, decisions);
for &(elev_eid, target_stop) in decisions.iter() {
if let Some(car) = world.elevator_mut(elev_eid) {
car.phase = ElevatorPhase::Repositioning(target_stop);
car.target_stop = Some(target_stop);
car.repositioning = true;
}
let elev_pos = world.position(elev_eid).map(|p| p.value);
if let Some(pos) = elev_pos {
let target_pos = world.stop_position(target_stop).unwrap_or(pos);
let (new_up, new_down) = if target_pos > pos {
(true, false)
} else if target_pos < pos {
(false, true)
} else {
(true, true)
};
super::dispatch::update_indicators(
world, events, elev_eid, new_up, new_down, ctx.tick,
);
}
events.emit(Event::ElevatorRepositioning {
elevator: elev_eid,
to_stop: target_stop,
tick: ctx.tick,
});
if let Some(pos) = elev_pos {
let serves = crate::dispatch::elevator_line_serves(world, groups, elev_eid);
let from = serves.map_or_else(
|| world.find_stop_at_position(pos),
|s| world.find_stop_at_position_in(pos, s),
);
if let Some(from) = from {
events.emit(Event::ElevatorDeparted {
elevator: elev_eid,
from_stop: from,
tick: ctx.tick,
});
}
}
}
}
}
fn apply_home_stop_overrides(
world: &World,
idle_elevators: &[(EntityId, f64)],
stop_positions: &[(EntityId, f64)],
decisions: &mut Vec<(EntityId, EntityId)>,
) -> Vec<(EntityId, f64)> {
let mut strategy_pool: Vec<(EntityId, f64)> = Vec::with_capacity(idle_elevators.len());
for &(elev_eid, elev_pos) in idle_elevators {
let pinned: Option<(EntityId, f64)> = world
.elevator(elev_eid)
.and_then(crate::components::Elevator::home_stop)
.and_then(|home_eid| stop_positions.iter().find(|(s, _)| *s == home_eid).copied());
match pinned {
Some((home_eid, home_pos)) => {
if (elev_pos - home_pos).abs() > 1e-6 {
decisions.push((elev_eid, home_eid));
}
}
None => strategy_pool.push((elev_eid, elev_pos)),
}
}
strategy_pool
}