Skip to main content

timed_fsm/
machine.rs

1use crate::response::Response;
2
3/// A timed finite state machine.
4///
5/// Unlike a classic FSM where transitions depend only on `(State, Event)`,
6/// a `TimedStateMachine` can express transitions that depend on the
7/// **absence** of events within a time window. This is achieved by
8/// including [`TimerCommand`](crate::response::TimerCommand)s in the
9/// [`Response`], which the runtime interprets to set or kill timers.
10///
11/// The state machine itself has **no side effects**. It never calls
12/// platform timer APIs directly. Instead, it returns a `Response`
13/// containing timer commands, and the runtime executes them.
14///
15/// # Two kinds of input
16///
17/// | Method | Meaning |
18/// |--------|---------|
19/// | [`on_event`](Self::on_event) | An external event arrived (key press, byte received, …) |
20/// | [`on_timeout`](Self::on_timeout) | A previously requested timer fired — the absence of events was detected |
21///
22/// Both methods return the same [`Response`] type, so the runtime can
23/// handle them uniformly via [`dispatch`](crate::dispatch::dispatch).
24///
25/// # Timer IDs
26///
27/// The [`TimerId`](Self::TimerId) type identifies timers within the
28/// state machine. Choose the type that fits your needs:
29///
30/// | `TimerId` | When to use |
31/// |-----------|-------------|
32/// | `()` | Exactly one timer — simplest case |
33/// | `enum Timer { … }` | Multiple named timers with distinct roles |
34/// | `u8` / `u32` | Indexed timers generated dynamically |
35///
36/// # Invariants
37///
38/// - `on_event` and `on_timeout` **must not produce side effects**.
39///   All effects are expressed through the returned [`Response`].
40/// - A `Response` with `consumed = false` means the event was not
41///   handled by this machine and should be forwarded to the next
42///   handler in the chain. The machine's internal state must remain
43///   unchanged in that case (as if `on_event` was never called).
44/// - Implementations should be deterministic: the same sequence of
45///   inputs always produces the same sequence of outputs.
46///
47/// # Example: single timer
48///
49/// ```
50/// use std::time::Duration;
51/// use timed_fsm::{TimedStateMachine, Response};
52///
53/// struct DebounceFilter {
54///     pending: Option<bool>,
55/// }
56///
57/// impl DebounceFilter {
58///     fn new() -> Self { Self { pending: None } }
59/// }
60///
61/// impl TimedStateMachine for DebounceFilter {
62///     type Event = bool;       // GPIO level: true = high, false = low
63///     type Action = bool;      // Confirmed level
64///     type TimerId = ();       // One debounce timer
65///
66///     fn on_event(&mut self, event: bool) -> Response<bool, ()> {
67///         self.pending = Some(event);
68///         Response::consume()
69///             .with_timer((), Duration::from_millis(20))
70///     }
71///
72///     fn on_timeout(&mut self, _: ()) -> Response<bool, ()> {
73///         match self.pending.take() {
74///             Some(level) => Response::emit_one(level),
75///             None => Response::pass_through(),
76///         }
77///     }
78/// }
79/// ```
80///
81/// # Example: multiple timers
82///
83/// When a state machine needs more than one concurrent timer, use an
84/// enum as `TimerId` so each timer has a descriptive name.
85///
86/// ```
87/// use std::time::Duration;
88/// use timed_fsm::{TimedStateMachine, Response};
89///
90/// #[derive(Clone, Copy, Debug, PartialEq, Eq)]
91/// enum Timer { Debounce, RepeatDelay, RepeatInterval }
92///
93/// struct KeyRepeater { key: Option<u8> }
94///
95/// impl KeyRepeater {
96///     fn new() -> Self { Self { key: None } }
97/// }
98///
99/// impl TimedStateMachine for KeyRepeater {
100///     type Event  = Option<u8>;  // Some(key) = pressed, None = released
101///     type Action = u8;
102///     type TimerId = Timer;
103///
104///     fn on_event(&mut self, event: Option<u8>) -> Response<u8, Timer> {
105///         match event {
106///             Some(key) => {
107///                 self.key = Some(key);
108///                 Response::consume()
109///                     .with_timer(Timer::Debounce,     Duration::from_millis(10))
110///                     .with_kill_timer(Timer::RepeatDelay)
111///                     .with_kill_timer(Timer::RepeatInterval)
112///             }
113///             None => {
114///                 self.key = None;
115///                 Response::consume()
116///                     .with_kill_timer(Timer::Debounce)
117///                     .with_kill_timer(Timer::RepeatDelay)
118///                     .with_kill_timer(Timer::RepeatInterval)
119///             }
120///         }
121///     }
122///
123///     fn on_timeout(&mut self, id: Timer) -> Response<u8, Timer> {
124///         match id {
125///             Timer::Debounce => match self.key {
126///                 Some(k) => Response::emit_one(k)
127///                     .with_timer(Timer::RepeatDelay, Duration::from_millis(500)),
128///                 None => Response::pass_through(),
129///             },
130///             Timer::RepeatDelay => match self.key {
131///                 Some(k) => Response::emit_one(k)
132///                     .with_timer(Timer::RepeatInterval, Duration::from_millis(50)),
133///                 None => Response::pass_through(),
134///             },
135///             Timer::RepeatInterval => match self.key {
136///                 Some(k) => Response::emit_one(k)
137///                     .with_timer(Timer::RepeatInterval, Duration::from_millis(50)),
138///                 None => Response::pass_through(),
139///             },
140///         }
141///     }
142/// }
143/// ```
144pub trait TimedStateMachine {
145    /// The event type fed into the state machine via [`on_event`](Self::on_event).
146    type Event;
147
148    /// The action type produced by transitions.
149    ///
150    /// Actions are collected in [`Response::actions`](crate::Response::actions)
151    /// and forwarded to [`ActionExecutor::execute`](crate::ActionExecutor::execute)
152    /// by the runtime.
153    type Action;
154
155    /// The timer identifier type.
156    ///
157    /// Use `()` if only one timer is needed.
158    /// Use an enum or integer for multiple concurrent timers.
159    ///
160    /// Must implement `Copy + Eq + Debug` so the runtime and assertion
161    /// helpers can compare and display timer IDs.
162    type TimerId: Copy + Eq + core::fmt::Debug;
163
164    /// Process an external event and return the transition result.
165    ///
166    /// The returned [`Response`] describes what actions to emit and
167    /// which timers to set or kill. The runtime then executes those
168    /// effects via [`dispatch`](crate::dispatch::dispatch).
169    fn on_event(&mut self, event: Self::Event) -> Response<Self::Action, Self::TimerId>;
170
171    /// Process a timer timeout and return the transition result.
172    ///
173    /// Called by the runtime when a timer previously requested via
174    /// [`TimerCommand::Set`](crate::response::TimerCommand::Set) fires.
175    /// The `timer_id` identifies which timer expired, allowing the state
176    /// machine to distinguish multiple concurrent timers.
177    fn on_timeout(&mut self, timer_id: Self::TimerId) -> Response<Self::Action, Self::TimerId>;
178}