use smallvec::SmallVec;
use crate::components::{ElevatorPhase, Route};
use crate::entity::EntityId;
use crate::world::World;
use super::{DispatchDecision, DispatchManifest, DispatchStrategy, ElevatorGroup};
pub struct EtdDispatch {
pub wait_weight: f64,
pub delay_weight: f64,
pub door_weight: f64,
}
impl EtdDispatch {
#[must_use]
pub const fn new() -> Self {
Self {
wait_weight: 1.0,
delay_weight: 1.0,
door_weight: 0.5,
}
}
#[must_use]
pub const fn with_delay_weight(delay_weight: f64) -> Self {
Self {
wait_weight: 1.0,
delay_weight,
door_weight: 0.5,
}
}
#[must_use]
pub const fn with_weights(wait_weight: f64, delay_weight: f64, door_weight: f64) -> Self {
Self {
wait_weight,
delay_weight,
door_weight,
}
}
}
impl Default for EtdDispatch {
fn default() -> Self {
Self::new()
}
}
impl DispatchStrategy for EtdDispatch {
fn decide(
&mut self,
_elevator: EntityId,
_elevator_position: f64,
_group: &ElevatorGroup,
_manifest: &DispatchManifest,
_world: &World,
) -> DispatchDecision {
DispatchDecision::Idle
}
fn decide_all(
&mut self,
elevators: &[(EntityId, f64)],
group: &ElevatorGroup,
manifest: &DispatchManifest,
world: &World,
) -> Vec<(EntityId, DispatchDecision)> {
let pending_stops: SmallVec<[(EntityId, f64); 16]> = group
.stop_entities()
.iter()
.filter_map(|&stop_eid| {
if manifest.has_demand(stop_eid) {
world.stop_position(stop_eid).map(|pos| (stop_eid, pos))
} else {
None
}
})
.collect();
if pending_stops.is_empty() {
return elevators
.iter()
.map(|(eid, _)| (*eid, DispatchDecision::Idle))
.collect();
}
let mut results: Vec<(EntityId, DispatchDecision)> = Vec::new();
let mut assigned_elevators: SmallVec<[EntityId; 16]> = SmallVec::new();
for (stop_eid, stop_pos) in &pending_stops {
let mut best_elevator: Option<EntityId> = None;
let mut best_cost = f64::INFINITY;
for &(elev_eid, elev_pos) in elevators {
if assigned_elevators.contains(&elev_eid) {
continue;
}
let cost = self.compute_cost(
elev_eid,
elev_pos,
*stop_pos,
&pending_stops,
manifest,
world,
);
if cost < best_cost {
best_cost = cost;
best_elevator = Some(elev_eid);
}
}
if let Some(elev_eid) = best_elevator {
results.push((elev_eid, DispatchDecision::GoToStop(*stop_eid)));
assigned_elevators.push(elev_eid);
}
}
for (eid, _) in elevators {
if !assigned_elevators.contains(eid) {
results.push((*eid, DispatchDecision::Idle));
}
}
results
}
}
impl EtdDispatch {
fn compute_cost(
&self,
elev_eid: EntityId,
elev_pos: f64,
target_pos: f64,
pending_stops: &[(EntityId, f64)],
_manifest: &DispatchManifest,
world: &World,
) -> f64 {
let Some(car) = world.elevator(elev_eid) else {
return f64::INFINITY;
};
let distance = (elev_pos - target_pos).abs();
let travel_time = if car.max_speed > 0.0 {
distance / car.max_speed
} else {
return f64::INFINITY;
};
let door_overhead_per_stop = f64::from(car.door_transition_ticks * 2 + car.door_open_ticks);
let (lo, hi) = if elev_pos < target_pos {
(elev_pos, target_pos)
} else {
(target_pos, elev_pos)
};
let intervening_stops = pending_stops
.iter()
.filter(|(_, pos)| *pos > lo + 1e-9 && *pos < hi - 1e-9)
.count() as f64;
let door_cost = intervening_stops * door_overhead_per_stop;
let mut existing_rider_delay = 0.0_f64;
for &rider_eid in car.riders() {
if let Some(dest) = world.route(rider_eid).and_then(Route::current_destination)
&& let Some(dest_pos) = world.stop_position(dest)
{
let direct_dist = (elev_pos - dest_pos).abs();
let detour_dist = (elev_pos - target_pos).abs() + (target_pos - dest_pos).abs();
let extra = (detour_dist - direct_dist).max(0.0);
if car.max_speed > 0.0 {
existing_rider_delay += extra / car.max_speed;
}
}
}
let direction_bonus = match car.phase.moving_target() {
Some(current_target) => world.stop_position(current_target).map_or(0.0, |ctp| {
let moving_up = ctp > elev_pos;
let target_is_ahead = if moving_up {
target_pos > elev_pos && target_pos <= ctp
} else {
target_pos < elev_pos && target_pos >= ctp
};
if target_is_ahead {
-travel_time * 0.5
} else {
0.0
}
}),
None if car.phase == ElevatorPhase::Idle => -travel_time * 0.3, _ => 0.0,
};
self.wait_weight.mul_add(
travel_time,
self.delay_weight.mul_add(
existing_rider_delay,
self.door_weight.mul_add(door_cost, direction_bonus),
),
)
}
}