Skip to main content

ready_active_safe/
runtime.rs

1//! A small reference runtime.
2//!
3//! The core [`Machine`] trait is pure and does not own mode.
4//! Real systems need a loop that:
5//!
6//! - stores the current mode
7//! - feeds external events into the machine
8//! - applies mode changes (optionally checking a [`Policy`])
9//! - dispatches emitted commands to the outside world
10//!
11//! This module provides a minimal, synchronous helper for that loop.
12//! It does **not** provide an event queue, scheduler, or async runtime.
13
14use alloc::vec::Vec;
15
16use crate::{LifecycleError, Machine, Policy};
17
18/// Owns the current mode and feeds events into a [`Machine`].
19///
20/// `Runner` is intentionally small. It is a convenience layer around the core
21/// contracts, not a framework.
22#[derive(Debug)]
23pub struct Runner<'a, M, E = core::convert::Infallible>
24where
25    M: Machine<E>,
26{
27    machine: &'a M,
28    mode: M::Mode,
29}
30
31impl<'a, M, E> Runner<'a, M, E>
32where
33    M: Machine<E>,
34{
35    /// Creates a new runner starting at `machine.initial_mode()`.
36    #[must_use]
37    pub fn new(machine: &'a M) -> Self {
38        Self {
39            mode: machine.initial_mode(),
40            machine,
41        }
42    }
43
44    /// Creates a runner with an explicit initial mode.
45    #[must_use]
46    pub const fn with_mode(machine: &'a M, mode: M::Mode) -> Self {
47        Self { machine, mode }
48    }
49
50    /// Returns the current mode.
51    #[must_use]
52    pub const fn mode(&self) -> &M::Mode {
53        &self.mode
54    }
55
56    /// Feeds an event into the machine, applies any mode change, and returns emitted commands.
57    ///
58    /// This does not enforce a [`Policy`]. Use [`Runner::feed_checked`]
59    /// when you need explicit transition approval.
60    #[must_use]
61    pub fn feed(&mut self, event: &M::Event) -> Vec<M::Command> {
62        let decision = self.machine.decide(&self.mode, event);
63        let (change, commands) = decision.into_parts();
64
65        if let Some(change) = change {
66            self.mode = change.target;
67        }
68
69        commands
70    }
71
72    /// Like [`Runner::feed`], but checks `policy` before applying a mode change.
73    ///
74    /// # Errors
75    ///
76    /// Returns [`LifecycleError::TransitionDenied`] if the policy rejects the transition.
77    pub fn feed_checked<P>(
78        &mut self,
79        event: &M::Event,
80        policy: &P,
81    ) -> Result<Vec<M::Command>, LifecycleError<M::Mode>>
82    where
83        P: Policy<M::Mode>,
84        M::Mode: Clone,
85    {
86        let decision = self.machine.decide(&self.mode, event);
87        let (change, commands) = decision.into_parts();
88
89        match change {
90            Some(change) => {
91                let target = change.target;
92
93                match policy.check(&self.mode, &target) {
94                    Ok(()) => {
95                        self.mode = target;
96                        Ok(commands)
97                    }
98                    Err(reason) => Err(LifecycleError::TransitionDenied {
99                        from: self.mode.clone(),
100                        to: target,
101                        reason,
102                    }),
103                }
104            }
105            None => Ok(commands),
106        }
107    }
108
109    /// Feeds an event and dispatches each command to `dispatch`.
110    ///
111    /// This is a convenience helper for the common “execute commands immediately”
112    /// pattern. Commands are dispatched in the order they were emitted.
113    pub fn feed_and_dispatch<F>(&mut self, event: &M::Event, mut dispatch: F)
114    where
115        F: FnMut(M::Command),
116    {
117        for command in self.feed(event) {
118            dispatch(command);
119        }
120    }
121
122    /// Like [`Runner::feed_and_dispatch`], but checks `policy` before applying a mode change.
123    ///
124    /// # Errors
125    ///
126    /// Returns [`LifecycleError::TransitionDenied`] if the policy rejects the transition.
127    pub fn feed_checked_and_dispatch<P, F>(
128        &mut self,
129        event: &M::Event,
130        policy: &P,
131        mut dispatch: F,
132    ) -> Result<(), LifecycleError<M::Mode>>
133    where
134        P: Policy<M::Mode>,
135        M::Mode: Clone,
136        F: FnMut(M::Command),
137    {
138        for command in self.feed_checked(event, policy)? {
139            dispatch(command);
140        }
141        Ok(())
142    }
143}