use std::collections::HashMap;
use smallvec::SmallVec;
use crate::entity::EntityId;
use crate::world::World;
use super::{DispatchDecision, DispatchManifest, DispatchStrategy, ElevatorGroup};
const EPSILON: f64 = 1e-9;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[non_exhaustive]
pub(crate) enum Direction {
Up,
Down,
}
pub struct LookDispatch {
direction: HashMap<EntityId, Direction>,
}
impl LookDispatch {
#[must_use]
pub fn new() -> Self {
Self {
direction: HashMap::new(),
}
}
}
impl Default for LookDispatch {
fn default() -> Self {
Self::new()
}
}
impl DispatchStrategy for LookDispatch {
fn decide(
&mut self,
elevator: EntityId,
elevator_position: f64,
group: &ElevatorGroup,
manifest: &DispatchManifest,
world: &World,
) -> DispatchDecision {
let direction = self
.direction
.get(&elevator)
.copied()
.unwrap_or(Direction::Up);
let mut interesting: SmallVec<[(EntityId, f64); 32]> = SmallVec::new();
for &stop_eid in group.stop_entities() {
if manifest.has_demand(stop_eid)
&& let Some(pos) = world.stop_position(stop_eid)
{
interesting.push((stop_eid, pos));
}
}
if interesting.is_empty() {
return DispatchDecision::Idle;
}
let pos = elevator_position;
let (ahead, behind): (SmallVec<[_; 32]>, SmallVec<[_; 32]>) = match direction {
Direction::Up => interesting.iter().partition(|(_, p)| *p > pos + EPSILON),
Direction::Down => interesting.iter().partition(|(_, p)| *p < pos - EPSILON),
};
if !ahead.is_empty() {
let nearest = match direction {
Direction::Up => ahead
.iter()
.min_by(|a: &&&(EntityId, f64), b: &&&(EntityId, f64)| a.1.total_cmp(&b.1)),
Direction::Down => ahead
.iter()
.max_by(|a: &&&(EntityId, f64), b: &&&(EntityId, f64)| a.1.total_cmp(&b.1)),
};
if let Some(stop) = nearest {
return DispatchDecision::GoToStop(stop.0);
}
}
let new_dir = match direction {
Direction::Up => Direction::Down,
Direction::Down => Direction::Up,
};
self.direction.insert(elevator, new_dir);
if behind.is_empty() {
return interesting
.first()
.map_or(DispatchDecision::Idle, |(sid, _)| {
DispatchDecision::GoToStop(*sid)
});
}
let nearest = match new_dir {
Direction::Up => behind
.iter()
.min_by(|a: &&&(EntityId, f64), b: &&&(EntityId, f64)| a.1.total_cmp(&b.1)),
Direction::Down => behind
.iter()
.max_by(|a: &&&(EntityId, f64), b: &&&(EntityId, f64)| a.1.total_cmp(&b.1)),
};
nearest.map_or(DispatchDecision::Idle, |stop| {
DispatchDecision::GoToStop(stop.0)
})
}
fn notify_removed(&mut self, elevator: EntityId) {
self.direction.remove(&elevator);
}
}