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, RankContext, pair_is_useful};
20
21/// Elevator dispatch using the LOOK algorithm. See module docs.
22#[derive(serde::Serialize, serde::Deserialize)]
23pub struct LookDispatch {
24    /// Per-elevator sweep direction. Persisted across dispatch passes
25    /// (reversed once a sweep exhausts demand ahead) and round-tripped
26    /// through [`DispatchStrategy::snapshot_config`] so a restored sim
27    /// continues the current sweep instead of defaulting to `Up` for
28    /// every car.
29    direction: HashMap<EntityId, SweepDirection>,
30    /// Per-elevator accept mode for the current dispatch pass.
31    /// Overwritten in full by `prepare_car` every pass, so no round-
32    /// trip is needed.
33    #[serde(skip)]
34    mode: HashMap<EntityId, SweepMode>,
35}
36
37impl LookDispatch {
38    /// Create a new `LookDispatch` with no initial direction state.
39    #[must_use]
40    pub fn new() -> Self {
41        Self {
42            direction: HashMap::new(),
43            mode: HashMap::new(),
44        }
45    }
46
47    /// Sweep direction for `car`, defaulting to `Up` for first-time callers.
48    fn direction_for(&self, car: EntityId) -> SweepDirection {
49        self.direction
50            .get(&car)
51            .copied()
52            .unwrap_or(SweepDirection::Up)
53    }
54
55    /// Accept mode for `car` in the current pass, defaulting to `Strict`.
56    fn mode_for(&self, car: EntityId) -> SweepMode {
57        self.mode.get(&car).copied().unwrap_or(SweepMode::Strict)
58    }
59}
60
61impl Default for LookDispatch {
62    fn default() -> Self {
63        Self::new()
64    }
65}
66
67impl DispatchStrategy for LookDispatch {
68    fn prepare_car(
69        &mut self,
70        car: EntityId,
71        car_position: f64,
72        group: &ElevatorGroup,
73        manifest: &DispatchManifest,
74        world: &World,
75    ) {
76        let current = self.direction_for(car);
77        if sweep::strict_demand_ahead(current, car_position, group, manifest, world) {
78            self.mode.insert(car, SweepMode::Strict);
79        } else {
80            self.direction.insert(car, current.reversed());
81            self.mode.insert(car, SweepMode::Lenient);
82        }
83    }
84
85    fn rank(&mut self, ctx: &RankContext<'_>) -> Option<f64> {
86        // Same guard as SCAN: deny un-servable pairs so an over-capacity
87        // waiting rider at the car's own stop can't pull the car into a
88        // cost-0 self-assignment during the Lenient reversal tick.
89        if !pair_is_useful(ctx, false) {
90            return None;
91        }
92        sweep::rank(
93            self.mode_for(ctx.car),
94            self.direction_for(ctx.car),
95            ctx.car_position,
96            ctx.stop_position,
97        )
98    }
99
100    fn notify_removed(&mut self, elevator: EntityId) {
101        self.direction.remove(&elevator);
102        self.mode.remove(&elevator);
103    }
104
105    fn builtin_id(&self) -> Option<super::BuiltinStrategy> {
106        Some(super::BuiltinStrategy::Look)
107    }
108
109    fn snapshot_config(&self) -> Option<String> {
110        ron::to_string(self).ok()
111    }
112
113    fn restore_config(&mut self, serialized: &str) -> Result<(), String> {
114        let restored: Self = ron::from_str(serialized).map_err(|e| e.to_string())?;
115        *self = restored;
116        Ok(())
117    }
118}