metrale_scheduler/
driver.rs

1// SPDX-License-Identifier: MIT OR Apache-2.0
2
3//! 2026-09-25: The scheduler loop. A [`Core`] plans a tick, executes its lanes and
4//! ends it; [`run`] only sequences those calls and never touches a device.
5//!
6//! Owner: scheduler.
7//! Invariants: none beyond the types.
8
9/// 2026-09-25: What one tick will do, decided by [`Core::plan`].
10pub struct TickPlan<L> {
11    /// 2026-09-25: The lanes to execute, in order.
12    pub lanes: Vec<L>,
13    /// 2026-09-25: Leave the loop without running `lanes`; [`run`] then calls
14    /// [`Core::finish`].
15    pub shutdown: bool,
16}
17
18/// 2026-09-25: What a lane says about the rest of its tick.
19#[derive(Debug, Clone, Copy, PartialEq, Eq)]
20pub enum LaneVerdict {
21    /// 2026-09-25: Run the remaining lanes and end the tick.
22    Proceed,
23    /// 2026-09-25: Skip the remaining lanes and [`Core::end_tick`]; the next tick
24    /// starts.
25    SkipRest,
26}
27
28pub trait Core {
29    type Lane;
30    /// 2026-09-25: Decide the tick's lanes, or that the loop ends.
31    fn plan(&mut self) -> TickPlan<Self::Lane>;
32    /// 2026-09-25: Run one lane; its verdict decides whether the tick goes on.
33    fn execute_lane(&mut self, lane: Self::Lane) -> LaneVerdict;
34    /// 2026-09-25: The work after the lanes; skipped when a lane returns `SkipRest`.
35    fn end_tick(&mut self);
36    /// 2026-09-25: Called once, after a plan with `shutdown` set.
37    fn finish(self);
38}
39
40pub fn run<C: Core>(mut core: C) {
41    loop {
42        let plan = core.plan();
43        if plan.shutdown {
44            break;
45        }
46        let mut skip_rest = false;
47        for lane in plan.lanes {
48            if core.execute_lane(lane) == LaneVerdict::SkipRest {
49                skip_rest = true;
50                break;
51            }
52        }
53        if skip_rest {
54            continue;
55        }
56        core.end_tick();
57    }
58    core.finish();
59}
60
61#[cfg(test)]
62mod tests {
63    use super::*;
64
65    /// 2026-09-25: A core that records the driver's call order and scripts its verdicts.
66    struct Scripted {
67        ticks: Vec<(Vec<LaneVerdict>, bool)>,
68        log: Vec<String>,
69        finished: std::rc::Rc<std::cell::Cell<bool>>,
70    }
71
72    impl Core for Scripted {
73        type Lane = LaneVerdict;
74        fn plan(&mut self) -> TickPlan<LaneVerdict> {
75            let (lanes, shutdown) = self.ticks.remove(0);
76            self.log
77                .push(format!("plan{}", if shutdown { " shutdown" } else { "" }));
78            TickPlan { lanes, shutdown }
79        }
80        fn execute_lane(&mut self, lane: LaneVerdict) -> LaneVerdict {
81            self.log.push(format!("lane {lane:?}"));
82            lane
83        }
84        fn end_tick(&mut self) {
85            self.log.push("end_tick".into());
86        }
87        fn finish(self) {
88            self.finished.set(true);
89            assert_eq!(
90                self.log,
91                [
92                    "plan",
93                    "lane Proceed",
94                    "lane Proceed",
95                    "end_tick",
96                    "plan",
97                    "lane Proceed",
98                    "lane SkipRest",
99                    "plan",
100                    "end_tick",
101                    "plan shutdown",
102                ]
103            );
104        }
105    }
106
107    #[test]
108    fn the_driver_runs_lanes_in_order_skips_the_tick_end_on_an_idle_lane_and_finishes_once() {
109        use LaneVerdict::*;
110        let finished = std::rc::Rc::new(std::cell::Cell::new(false));
111        run(Scripted {
112            ticks: vec![
113                (vec![Proceed, Proceed], false),
114                // 2026-09-25: SkipRest drops the third lane and the end of the tick.
115                (vec![Proceed, SkipRest, Proceed], false),
116                (vec![], false),
117                (vec![Proceed], true),
118            ],
119            log: Vec::new(),
120            finished: finished.clone(),
121        });
122        assert!(finished.get());
123    }
124}