Skip to main content

elevator_core/dispatch/
look.rs

1//! LOOK dispatch algorithm — reverses at the last request, not the shaft end.
2//!
3//! Introduced in Merten, A. G. (1970), "Some Quantitative Techniques for
4//! File Organization" (Univ. Wisconsin tech report) as an improvement on
5//! SCAN that avoids unnecessary travel past the furthest pending request.
6//!
7//! Within this library SCAN and LOOK share identical dispatch semantics:
8//! both prefer demanded stops in the current sweep direction and reverse
9//! only when nothing remains ahead. The historical distinction — whether
10//! the car drives to the physical shaft end between sweeps — applies to
11//! the motion layer, not dispatch.
12
13use std::collections::HashMap;
14
15use crate::entity::EntityId;
16use crate::world::World;
17
18use super::sweep::{self, SweepDirection, SweepMode};
19use super::{DispatchManifest, DispatchStrategy, ElevatorGroup};
20
21/// Elevator dispatch using the LOOK algorithm. See module docs.
22pub struct LookDispatch {
23    /// Per-elevator sweep direction.
24    direction: HashMap<EntityId, SweepDirection>,
25    /// Per-elevator accept mode for the current dispatch pass.
26    mode: HashMap<EntityId, SweepMode>,
27}
28
29impl LookDispatch {
30    /// Create a new `LookDispatch` with no initial direction state.
31    #[must_use]
32    pub fn new() -> Self {
33        Self {
34            direction: HashMap::new(),
35            mode: HashMap::new(),
36        }
37    }
38
39    /// Sweep direction for `car`, defaulting to `Up` for first-time callers.
40    fn direction_for(&self, car: EntityId) -> SweepDirection {
41        self.direction
42            .get(&car)
43            .copied()
44            .unwrap_or(SweepDirection::Up)
45    }
46
47    /// Accept mode for `car` in the current pass, defaulting to `Strict`.
48    fn mode_for(&self, car: EntityId) -> SweepMode {
49        self.mode.get(&car).copied().unwrap_or(SweepMode::Strict)
50    }
51}
52
53impl Default for LookDispatch {
54    fn default() -> Self {
55        Self::new()
56    }
57}
58
59impl DispatchStrategy for LookDispatch {
60    fn prepare_car(
61        &mut self,
62        car: EntityId,
63        car_position: f64,
64        group: &ElevatorGroup,
65        manifest: &DispatchManifest,
66        world: &World,
67    ) {
68        let current = self.direction_for(car);
69        if sweep::strict_demand_ahead(current, car_position, group, manifest, world) {
70            self.mode.insert(car, SweepMode::Strict);
71        } else {
72            self.direction.insert(car, current.reversed());
73            self.mode.insert(car, SweepMode::Lenient);
74        }
75    }
76
77    fn rank(
78        &mut self,
79        car: EntityId,
80        car_position: f64,
81        _stop: EntityId,
82        stop_position: f64,
83        _group: &ElevatorGroup,
84        _manifest: &DispatchManifest,
85        _world: &World,
86    ) -> Option<f64> {
87        sweep::rank(
88            self.mode_for(car),
89            self.direction_for(car),
90            car_position,
91            stop_position,
92        )
93    }
94
95    fn notify_removed(&mut self, elevator: EntityId) {
96        self.direction.remove(&elevator);
97        self.mode.remove(&elevator);
98    }
99}