Skip to main content

telltale_machine/
kernel.rs

1//! Deterministic protocol-machine kernel API.
2//!
3//! This module provides the canonical instruction-step entrypoints for the
4//! cooperative protocol-machine state machine. Driver layers (native single-thread, wasm,
5//! and adapter backends) should call this API instead of re-implementing
6//! stepping logic.
7
8use crate::effect::EffectHandler;
9use crate::engine::{ProtocolMachineError, RunStatus, StepResult};
10use crate::scheduler::Scheduler;
11
12/// Runtime machine that can execute one kernel scheduler round.
13pub trait KernelMachine {
14    /// Execute one scheduler round in the machine's native state.
15    ///
16    /// # Errors
17    ///
18    /// Returns a `ProtocolMachineError` if a coroutine faults.
19    fn kernel_step_round(
20        &mut self,
21        handler: &dyn EffectHandler,
22        n: usize,
23    ) -> Result<StepResult, ProtocolMachineError>;
24}
25
26/// Canonical cooperative kernel entrypoints.
27#[derive(Debug, Default, Clone, Copy)]
28pub struct ProtocolMachineKernel;
29
30impl ProtocolMachineKernel {
31    /// Select a ready coroutine using kernel-owned scheduler policy semantics.
32    ///
33    /// `has_progress` models policy-relevant progress state, while
34    /// `is_eligible` is a runtime/driver eligibility gate (e.g. paused role,
35    /// lane/session wave constraints). Ineligible candidates are rescheduled.
36    pub fn select_ready_eligible<FProgress, FEligible>(
37        scheduler: &mut Scheduler,
38        has_progress: FProgress,
39        mut is_eligible: FEligible,
40    ) -> Option<usize>
41    where
42        FProgress: Fn(usize) -> bool + Copy,
43        FEligible: FnMut(usize) -> bool,
44    {
45        let attempts = scheduler.ready_count();
46        for _ in 0..attempts {
47            let id = scheduler.pick_runnable(has_progress)?;
48            if is_eligible(id) {
49                return Some(id);
50            }
51            scheduler.reschedule(id);
52        }
53        None
54    }
55
56    /// Execute one scheduler round through the canonical kernel path.
57    ///
58    /// # Errors
59    ///
60    /// Returns a `ProtocolMachineError` if a coroutine faults.
61    pub fn step_round<M: KernelMachine>(
62        machine: &mut M,
63        handler: &dyn EffectHandler,
64        n: usize,
65    ) -> Result<StepResult, ProtocolMachineError> {
66        machine.kernel_step_round(handler, n)
67    }
68
69    /// Run until completion/stuck or `max_rounds` is reached via the kernel.
70    ///
71    /// # Errors
72    ///
73    /// Returns a `ProtocolMachineError` if any coroutine faults.
74    pub fn run_concurrent<M: KernelMachine>(
75        machine: &mut M,
76        handler: &dyn EffectHandler,
77        max_rounds: usize,
78        concurrency: usize,
79    ) -> Result<RunStatus, ProtocolMachineError> {
80        for _ in 0..max_rounds {
81            match Self::step_round(machine, handler, concurrency)? {
82                StepResult::AllDone => return Ok(RunStatus::AllDone),
83                StepResult::Stuck => return Ok(RunStatus::Stuck),
84                StepResult::Continue => {}
85            }
86        }
87        Ok(RunStatus::MaxRoundsExceeded)
88    }
89
90    /// Run with single-lane cooperative scheduling via the kernel.
91    ///
92    /// # Errors
93    ///
94    /// Returns a `ProtocolMachineError` if any coroutine faults.
95    pub fn run<M: KernelMachine>(
96        machine: &mut M,
97        handler: &dyn EffectHandler,
98        max_steps: usize,
99    ) -> Result<RunStatus, ProtocolMachineError> {
100        Self::run_concurrent(machine, handler, max_steps, 1)
101    }
102}