use crate::components::{ElevatorPhase, RiderPhase};
use crate::door::{DoorCommand, DoorState, DoorTransition};
use crate::entity::EntityId;
use crate::events::{Event, EventBus};
use crate::world::World;
use super::PhaseContext;
pub fn run(
world: &mut World,
events: &mut EventBus,
ctx: &PhaseContext,
elevator_ids: &[crate::entity::EntityId],
) {
let mut just_opened: Vec<(EntityId, EntityId, bool, bool)> = Vec::new();
for &eid in elevator_ids {
if world.is_disabled(eid) {
continue;
}
let is_inspection = world
.service_mode(eid)
.is_some_and(|m| *m == crate::components::ServiceMode::Inspection);
process_door_commands(world, events, ctx, eid);
let Some(car) = world.elevator_mut(eid) else {
continue;
};
if car.door.is_closed() && car.phase != ElevatorPhase::DoorOpening {
continue;
}
if is_inspection && matches!(car.door, DoorState::Open { .. }) {
continue;
}
let transition = car.door.tick();
match transition {
DoorTransition::FinishedOpening => {
car.phase = ElevatorPhase::Loading;
let (up, down) = (car.going_up, car.going_down);
let at_stop = car.target_stop;
events.emit(Event::DoorOpened {
elevator: eid,
tick: ctx.tick,
});
if let Some(stop) = at_stop {
just_opened.push((eid, stop, up, down));
}
}
DoorTransition::FinishedOpen => {
car.phase = ElevatorPhase::DoorClosing;
}
DoorTransition::FinishedClosing => {
car.phase = ElevatorPhase::Stopped;
car.target_stop = None;
events.emit(Event::DoorClosed {
elevator: eid,
tick: ctx.tick,
});
}
DoorTransition::None => {}
}
}
for (car, stop, going_up, going_down) in just_opened {
clear_matching_hall_calls(world, events, car, stop, going_up, going_down, ctx.tick);
}
}
fn clear_matching_hall_calls(
world: &mut World,
events: &mut EventBus,
car: EntityId,
stop: EntityId,
going_up: bool,
going_down: bool,
tick: u64,
) {
use crate::components::CallDirection;
if going_up && world.hall_call(stop, CallDirection::Up).is_some() {
world.remove_hall_call(stop, CallDirection::Up);
events.emit(Event::HallCallCleared {
stop,
direction: CallDirection::Up,
car,
tick,
});
}
if going_down && world.hall_call(stop, CallDirection::Down).is_some() {
world.remove_hall_call(stop, CallDirection::Down);
events.emit(Event::HallCallCleared {
stop,
direction: CallDirection::Down,
car,
tick,
});
}
}
fn process_door_commands(
world: &mut World,
events: &mut EventBus,
ctx: &PhaseContext,
eid: EntityId,
) {
let Some(car) = world.elevator_mut(eid) else {
return;
};
if car.door_command_queue.is_empty() {
return;
}
let queue = std::mem::take(&mut car.door_command_queue);
let mut remaining: Vec<DoorCommand> = Vec::new();
for cmd in queue {
if try_apply_command(world, eid, cmd) {
events.emit(Event::DoorCommandApplied {
elevator: eid,
command: cmd,
tick: ctx.tick,
});
} else {
remaining.push(cmd);
}
}
if let Some(car) = world.elevator_mut(eid) {
car.door_command_queue = remaining;
}
}
fn try_apply_command(world: &mut World, eid: EntityId, cmd: DoorCommand) -> bool {
let Some(car) = world.elevator(eid) else {
return true;
};
let phase = car.phase;
match cmd {
DoorCommand::Open => apply_open(world, eid, phase),
DoorCommand::Close => apply_close(world, eid, phase),
DoorCommand::HoldOpen { ticks } => apply_hold(world, eid, phase, ticks),
DoorCommand::CancelHold => apply_cancel_hold(world, eid, phase),
}
}
fn apply_open(world: &mut World, eid: EntityId, phase: ElevatorPhase) -> bool {
match phase {
ElevatorPhase::DoorOpening | ElevatorPhase::Loading => true,
ElevatorPhase::Stopped | ElevatorPhase::Idle => {
let pos = world.position(eid).map_or(0.0, |p| p.value);
if world.find_stop_at_position(pos).is_none() {
return false;
}
if let Some(car) = world.elevator_mut(eid) {
car.phase = ElevatorPhase::DoorOpening;
car.door = DoorState::request_open(car.door_transition_ticks, car.door_open_ticks);
}
true
}
ElevatorPhase::DoorClosing => {
if let Some(car) = world.elevator_mut(eid) {
car.phase = ElevatorPhase::DoorOpening;
car.door = DoorState::request_open(car.door_transition_ticks, car.door_open_ticks);
}
true
}
ElevatorPhase::MovingToStop(_) | ElevatorPhase::Repositioning(_) => false,
}
}
fn apply_close(world: &mut World, eid: EntityId, phase: ElevatorPhase) -> bool {
match phase {
ElevatorPhase::Loading => {
if has_rider_traversing(world, eid) {
return false;
}
if let Some(car) = world.elevator_mut(eid) {
car.phase = ElevatorPhase::DoorClosing;
car.door = DoorState::Closing {
ticks_remaining: car.door_transition_ticks,
};
}
true
}
ElevatorPhase::DoorOpening => false,
ElevatorPhase::DoorClosing
| ElevatorPhase::Stopped
| ElevatorPhase::Idle
| ElevatorPhase::MovingToStop(_)
| ElevatorPhase::Repositioning(_) => true,
}
}
fn apply_hold(world: &mut World, eid: EntityId, phase: ElevatorPhase, ticks: u32) -> bool {
match phase {
ElevatorPhase::Loading => {
if let Some(car) = world.elevator_mut(eid)
&& let DoorState::Open {
ticks_remaining, ..
} = &mut car.door
{
*ticks_remaining = ticks_remaining.saturating_add(ticks);
}
true
}
ElevatorPhase::DoorOpening => false,
ElevatorPhase::DoorClosing
| ElevatorPhase::Stopped
| ElevatorPhase::Idle
| ElevatorPhase::MovingToStop(_)
| ElevatorPhase::Repositioning(_) => true,
}
}
fn apply_cancel_hold(world: &mut World, eid: EntityId, phase: ElevatorPhase) -> bool {
if matches!(phase, ElevatorPhase::Loading)
&& let Some(car) = world.elevator_mut(eid)
{
let base = car.door_open_ticks;
if let DoorState::Open {
ticks_remaining, ..
} = &mut car.door
&& *ticks_remaining > base
{
*ticks_remaining = base;
}
}
true
}
fn has_rider_traversing(world: &World, eid: EntityId) -> bool {
world.iter_riders().any(|(_, r)| {
matches!(
r.phase,
RiderPhase::Boarding(e) | RiderPhase::Exiting(e) if e == eid
)
})
}