nodo_runtime/
state_machine.rs

1// Copyright 2023 David Weikersdorfer
2
3use core::fmt::{Debug, Formatter};
4use eyre::{Report, Result};
5use nodo::{
6    codelet::{Lifecycle, Transition},
7    core::DefaultStatus,
8};
9
10/// Possible states of codelets
11#[derive(Clone, Copy, PartialEq, Debug)]
12pub enum State {
13    /// Codelet is not started. The codelet can be started with the start transition
14    Inactive,
15
16    /// Codelet is started. The codelet can be stepped, paused or stopped.
17    Started,
18
19    /// Codelet is paused. Operation can be resumed with the resume transition. It is also possible
20    /// to stop the codelet.
21    Paused,
22
23    /// Codelet is in an error state
24    Error,
25}
26
27impl State {
28    /// The next state after a successful state transition
29    pub fn transition(self, request: Transition) -> Option<State> {
30        match (self, request) {
31            (State::Started, Transition::Stop) | (State::Paused, Transition::Stop) => {
32                Some(State::Inactive)
33            }
34            (State::Inactive, Transition::Start)
35            | (State::Started, Transition::Step)
36            | (State::Paused, Transition::Resume) => Some(State::Started),
37            (State::Started, Transition::Pause) => Some(State::Paused),
38            (_, _) => None,
39        }
40    }
41}
42
43/// State machine which oversees correct codelet state transitions
44pub struct StateMachine<C> {
45    inner: C,
46    state: State,
47}
48
49#[derive(thiserror::Error, Debug)]
50pub enum TransitionError {
51    /// Transition is not valid in the current state
52    #[error("invalid transition {0:?} -> {1:?}")]
53    InvalidTransition(State, Transition),
54
55    /// Codelet transition function returned failure
56    #[error("execution failed [{0:?}]: {1:?}")]
57    ExecutionFailure(Transition, Report),
58}
59
60impl<C> StateMachine<C> {
61    pub fn new(inner: C) -> Self {
62        Self {
63            inner,
64            state: State::Inactive,
65        }
66    }
67
68    pub fn inner(&self) -> &C {
69        &self.inner
70    }
71
72    pub fn inner_mut(&mut self) -> &mut C {
73        &mut self.inner
74    }
75
76    pub fn state(&self) -> State {
77        self.state
78    }
79
80    pub fn is_valid_request(&self, request: Transition) -> bool {
81        self.state.transition(request).is_some()
82    }
83
84    pub fn transition(&mut self, transition: Transition) -> Result<DefaultStatus, TransitionError>
85    where
86        C: Lifecycle,
87    {
88        if let Some(next_state) = self.state.transition(transition) {
89            match self.inner.cycle(transition) {
90                Ok(kind) => {
91                    self.state = next_state;
92                    return Ok(kind);
93                }
94                Err(err) => {
95                    self.state = State::Error;
96                    return Err(TransitionError::ExecutionFailure(transition, err));
97                }
98            }
99        } else {
100            Err(TransitionError::InvalidTransition(self.state, transition))
101        }
102    }
103}
104
105impl<C> Debug for StateMachine<C> {
106    fn fmt(&self, fmt: &mut Formatter<'_>) -> Result<(), std::fmt::Error> {
107        fmt.debug_struct("StateMachine")
108            .field("inner", &"()")
109            .field("state", &self.state)
110            .finish()
111    }
112}
113
114#[cfg(test)]
115mod tests {
116    use crate::State;
117    use nodo::codelet::*;
118
119    #[test]
120    fn state_transition() {
121        assert_eq!(
122            State::Inactive.transition(Transition::Start),
123            Some(State::Started)
124        );
125        assert_eq!(
126            State::Started.transition(Transition::Step),
127            Some(State::Started)
128        );
129        assert_eq!(
130            State::Started.transition(Transition::Stop),
131            Some(State::Inactive)
132        );
133    }
134}