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