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}