Skip to main content

timed_fsm/
dispatch.rs

1use std::time::Duration;
2
3use crate::response::{Response, TimerCommand};
4
5/// A runtime that can set and kill timers.
6///
7/// The state machine produces [`TimerCommand`]s in its [`Response`].
8/// The runtime implements this trait to translate those commands into
9/// actual platform timer operations.
10///
11/// # Invariant: `Set` resets an existing timer
12///
13/// Implementations **must** ensure that calling [`set_timer`](Self::set_timer)
14/// with an ID that already has an active timer **replaces** that timer rather
15/// than creating a second concurrent timer for the same ID. The new timer
16/// starts counting from the moment `set_timer` is called.
17///
18/// # Platform examples
19///
20/// | Platform | `set_timer` | `kill_timer` |
21/// |----------|-------------|--------------|
22/// | Windows  | `SetTimer()` (reuses the same `nIDEvent`) | `KillTimer()` |
23/// | Linux    | `timerfd_settime()` (overwrites existing armed state) | `timerfd_settime(0)` |
24/// | macOS    | `CFRunLoopTimerSetNextFireDate()` (adjusts fire date) | invalidate the `CFRunLoopTimerRef` |
25/// | Test     | record to `Vec` | record to `Vec` |
26pub trait TimerRuntime {
27    /// The timer identifier type (must match the state machine's `TimerId`).
28    type TimerId;
29
30    /// Start or restart a timer.
31    ///
32    /// If a timer with the same ID is already active, it **must** be reset
33    /// to the new duration — only one timer per ID may be active at a time.
34    fn set_timer(&mut self, id: Self::TimerId, duration: Duration);
35
36    /// Stop an active timer. No-op if the timer is not active.
37    fn kill_timer(&mut self, id: Self::TimerId);
38}
39
40/// A runtime that can execute actions produced by the state machine.
41///
42/// # Order guarantee
43///
44/// The `actions` slice passed to [`execute`](Self::execute) is always in the
45/// same order as they appear in [`Response::actions`](crate::Response::actions).
46/// Implementations **must** process them in order (first to last) to preserve
47/// the intended sequence of output events (e.g., modifier-down before
48/// character-down in a key injection scenario).
49///
50/// # Platform examples
51///
52/// | Platform | Implementation |
53/// |----------|----------------|
54/// | Windows  | `SendInput()` for key injection |
55/// | Linux    | `uinput` write |
56/// | macOS    | `CGEventPost()` |
57/// | Test     | record to `Vec` |
58pub trait ActionExecutor {
59    /// The action type (must match the state machine's `Action`).
60    type Action;
61
62    /// Execute a batch of actions, in the order they appear in the slice.
63    fn execute(&mut self, actions: &[Self::Action]);
64}
65
66/// Dispatch a [`Response`] to a runtime.
67///
68/// This is the primary integration point between the state machine
69/// (which is pure and side-effect-free) and the runtime (which
70/// performs actual I/O).
71///
72/// # Processing order
73///
74/// 1. **Timer commands** — all [`TimerCommand`]s in `response.timers`
75///    are processed in order via [`TimerRuntime::set_timer`] /
76///    [`TimerRuntime::kill_timer`].
77/// 2. **Actions** — if `response.actions` is non-empty,
78///    [`ActionExecutor::execute`] is called once with the full slice.
79///
80/// Timer commands are applied before actions so that a newly started
81/// timer cannot fire before the actions from the same transition have
82/// been executed (in single-threaded runtimes). In multi-threaded
83/// runtimes, callers are responsible for any necessary synchronization.
84///
85/// Returns the `consumed` flag from the response.
86///
87/// # Example
88///
89/// ```
90/// use std::time::Duration;
91/// use timed_fsm::{Response, dispatch, TimerRuntime, ActionExecutor};
92///
93/// struct MockTimers(Vec<String>);
94/// impl TimerRuntime for MockTimers {
95///     type TimerId = u8;
96///     fn set_timer(&mut self, id: u8, dur: Duration) {
97///         self.0.push(format!("set({id}, {dur:?})"));
98///     }
99///     fn kill_timer(&mut self, id: u8) {
100///         self.0.push(format!("kill({id})"));
101///     }
102/// }
103///
104/// struct MockExecutor(Vec<i32>);
105/// impl ActionExecutor for MockExecutor {
106///     type Action = i32;
107///     fn execute(&mut self, actions: &[i32]) {
108///         self.0.extend_from_slice(actions);
109///     }
110/// }
111///
112/// let response = Response::emit_one(42)
113///     .with_timer(1, Duration::from_millis(100));
114///
115/// let mut timers = MockTimers(vec![]);
116/// let mut executor = MockExecutor(vec![]);
117/// let consumed = dispatch(&response, &mut timers, &mut executor);
118///
119/// assert!(consumed);
120/// assert_eq!(executor.0, vec![42]);
121/// assert_eq!(timers.0.len(), 1);
122/// ```
123pub fn dispatch<A, T: Copy + Eq + core::fmt::Debug>(
124    response: &Response<A, T>,
125    timers: &mut impl TimerRuntime<TimerId = T>,
126    executor: &mut impl ActionExecutor<Action = A>,
127) -> bool {
128    for cmd in &response.timers {
129        match *cmd {
130            TimerCommand::Set { id, duration } => timers.set_timer(id, duration),
131            TimerCommand::Kill { id } => timers.kill_timer(id),
132        }
133    }
134    if !response.actions.is_empty() {
135        executor.execute(&response.actions);
136    }
137    response.consumed
138}
139
140#[cfg(test)]
141mod tests {
142    use super::*;
143
144    struct RecordTimers(Vec<TimerCommand<u8>>);
145    impl TimerRuntime for RecordTimers {
146        type TimerId = u8;
147        fn set_timer(&mut self, id: u8, duration: Duration) {
148            self.0.push(TimerCommand::Set { id, duration });
149        }
150        fn kill_timer(&mut self, id: u8) {
151            self.0.push(TimerCommand::Kill { id });
152        }
153    }
154
155    struct RecordActions(Vec<&'static str>);
156    impl ActionExecutor for RecordActions {
157        type Action = &'static str;
158        fn execute(&mut self, actions: &[&'static str]) {
159            self.0.extend_from_slice(actions);
160        }
161    }
162
163    #[test]
164    fn dispatch_processes_timers_then_actions() {
165        let response = Response::emit(vec!["a", "b"])
166            .with_timer(1, Duration::from_millis(100))
167            .with_kill_timer(2);
168
169        let mut timers = RecordTimers(vec![]);
170        let mut executor = RecordActions(vec![]);
171        let consumed = dispatch(&response, &mut timers, &mut executor);
172
173        assert!(consumed);
174        assert_eq!(timers.0.len(), 2);
175        assert_eq!(executor.0, vec!["a", "b"]);
176    }
177
178    #[test]
179    fn dispatch_pass_through_returns_false() {
180        let response: Response<&str, u8> = Response::pass_through();
181        let mut timers = RecordTimers(vec![]);
182        let mut executor = RecordActions(vec![]);
183        let consumed = dispatch(&response, &mut timers, &mut executor);
184
185        assert!(!consumed);
186        assert!(timers.0.is_empty());
187        assert!(executor.0.is_empty());
188    }
189
190    #[test]
191    fn dispatch_consume_no_actions() {
192        let response: Response<&str, u8> =
193            Response::consume().with_timer(0, Duration::from_millis(50));
194        let mut timers = RecordTimers(vec![]);
195        let mut executor = RecordActions(vec![]);
196        let consumed = dispatch(&response, &mut timers, &mut executor);
197
198        assert!(consumed);
199        assert_eq!(timers.0.len(), 1);
200        assert!(executor.0.is_empty());
201    }
202}