Skip to main content

elevator_core/
hooks.rs

1//! Lifecycle hooks for injecting custom logic before/after simulation phases.
2//!
3//! # Example
4//!
5//! ```rust
6//! use elevator_core::prelude::*;
7//! use elevator_core::hooks::Phase;
8//!
9//! let mut sim = SimulationBuilder::demo()
10//!     .before(Phase::Dispatch, |world| {
11//!         // Inspect world state before dispatch runs
12//!         let idle_count = world.iter_idle_elevators().count();
13//!         let _ = idle_count; // use it
14//!     })
15//!     .build()
16//!     .unwrap();
17//!
18//! sim.step(); // hooks fire during each step
19//! ```
20
21use crate::ids::GroupId;
22use crate::world::World;
23use std::collections::HashMap;
24
25/// Simulation phase identifier for hook registration.
26///
27/// Each variant corresponds to one phase in the tick loop. Hooks registered
28/// for a phase run immediately before or after that phase executes.
29#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
30#[non_exhaustive]
31pub enum Phase {
32    /// Advance transient rider states (Boarding→Riding, Exiting→Arrived).
33    AdvanceTransient,
34    /// Assign idle elevators to stops via dispatch strategy.
35    Dispatch,
36    /// Update elevator position and velocity.
37    Movement,
38    /// Tick door finite-state machines.
39    Doors,
40    /// Board and exit riders.
41    Loading,
42    /// Reposition idle elevators for better coverage.
43    Reposition,
44    /// Reconcile elevator phase with its `DestinationQueue` front.
45    AdvanceQueue,
46    /// Aggregate metrics from tick events.
47    Metrics,
48}
49
50/// A boxed closure that receives mutable world access during a phase hook.
51pub(crate) type PhaseHook = Box<dyn Fn(&mut World) + Send + Sync>;
52
53/// Storage for before/after hooks keyed by phase.
54#[derive(Default)]
55pub(crate) struct PhaseHooks {
56    /// Hooks to run before each phase.
57    before: HashMap<Phase, Vec<PhaseHook>>,
58    /// Hooks to run after each phase.
59    after: HashMap<Phase, Vec<PhaseHook>>,
60    /// Hooks to run before a phase for a specific group.
61    before_group: HashMap<(Phase, GroupId), Vec<PhaseHook>>,
62    /// Hooks to run after a phase for a specific group.
63    after_group: HashMap<(Phase, GroupId), Vec<PhaseHook>>,
64}
65
66impl PhaseHooks {
67    /// Run all before-hooks for the given phase.
68    pub(crate) fn run_before(&self, phase: Phase, world: &mut World) {
69        if let Some(hooks) = self.before.get(&phase) {
70            for hook in hooks {
71                hook(world);
72            }
73        }
74    }
75
76    /// Run all after-hooks for the given phase.
77    pub(crate) fn run_after(&self, phase: Phase, world: &mut World) {
78        if let Some(hooks) = self.after.get(&phase) {
79            for hook in hooks {
80                hook(world);
81            }
82        }
83    }
84
85    /// Register a hook to run before a phase.
86    pub(crate) fn add_before(&mut self, phase: Phase, hook: PhaseHook) {
87        self.before.entry(phase).or_default().push(hook);
88    }
89
90    /// Register a hook to run after a phase.
91    pub(crate) fn add_after(&mut self, phase: Phase, hook: PhaseHook) {
92        self.after.entry(phase).or_default().push(hook);
93    }
94
95    /// Run all before-hooks for the given phase and group.
96    pub(crate) fn run_before_group(&self, phase: Phase, group: GroupId, world: &mut World) {
97        if let Some(hooks) = self.before_group.get(&(phase, group)) {
98            for hook in hooks {
99                hook(world);
100            }
101        }
102    }
103
104    /// Run all after-hooks for the given phase and group.
105    pub(crate) fn run_after_group(&self, phase: Phase, group: GroupId, world: &mut World) {
106        if let Some(hooks) = self.after_group.get(&(phase, group)) {
107            for hook in hooks {
108                hook(world);
109            }
110        }
111    }
112
113    /// Register a hook to run before a phase for a specific group.
114    pub(crate) fn add_before_group(&mut self, phase: Phase, group: GroupId, hook: PhaseHook) {
115        self.before_group
116            .entry((phase, group))
117            .or_default()
118            .push(hook);
119    }
120
121    /// Register a hook to run after a phase for a specific group.
122    pub(crate) fn add_after_group(&mut self, phase: Phase, group: GroupId, hook: PhaseHook) {
123        self.after_group
124            .entry((phase, group))
125            .or_default()
126            .push(hook);
127    }
128}