Skip to main content

sml/
utility.rs

1//! Allocation-free utilities for runtime dispatch and groups of state machines.
2
3use crate::{Machine, Terminated};
4
5/// Result of hierarchical child-first event routing.
6#[derive(Clone, Copy, Debug, PartialEq, Eq)]
7pub enum HierarchicalDispatch {
8    /// The active child handled the event and remains active.
9    Child,
10    /// The child handled the event and reached its terminal state.
11    ChildTerminated,
12    /// The parent handled the event.
13    Parent,
14    /// Neither active machine handled the event.
15    Unhandled,
16}
17
18/// Parent and child state machines with child-first event routing and retained
19/// shallow history.
20///
21/// Deactivating a child does not reset it. Reactivating it therefore resumes
22/// its previous state; `reset_child` explicitly replaces that history.
23pub struct Hierarchical<P, C> {
24    parent: P,
25    child: C,
26    child_active: bool,
27}
28
29impl<P, C> Hierarchical<P, C> {
30    /// Creates a hierarchy with an inactive child.
31    pub const fn new(parent: P, child: C) -> Self {
32        Self {
33            parent,
34            child,
35            child_active: false,
36        }
37    }
38
39    /// Creates a hierarchy whose child starts active.
40    pub const fn new_active(parent: P, child: C) -> Self {
41        Self {
42            parent,
43            child,
44            child_active: true,
45        }
46    }
47
48    /// Activates the child, preserving its previous state.
49    pub fn activate_child(&mut self) {
50        self.child_active = true;
51    }
52
53    /// Deactivates the child while preserving shallow history.
54    pub fn deactivate_child(&mut self) {
55        self.child_active = false;
56    }
57
58    /// Replaces the child and activates its new initial state.
59    pub fn reset_child(&mut self, child: C) {
60        self.child = child;
61        self.child_active = true;
62    }
63
64    /// Returns true when the child is active.
65    pub const fn child_is_active(&self) -> bool {
66        self.child_active
67    }
68
69    /// Returns the parent machine.
70    pub fn parent(&self) -> &P {
71        &self.parent
72    }
73
74    /// Returns the parent machine mutably.
75    pub fn parent_mut(&mut self) -> &mut P {
76        &mut self.parent
77    }
78
79    /// Returns the child machine.
80    pub fn child(&self) -> &C {
81        &self.child
82    }
83
84    /// Returns the child machine mutably.
85    pub fn child_mut(&mut self) -> &mut C {
86        &mut self.child
87    }
88
89    /// Routes an event to the active child first and bubbles unhandled events
90    /// to the parent.
91    pub fn process_event<E, FC, FP>(
92        &mut self,
93        event: E,
94        mut child_dispatch: FC,
95        mut parent_dispatch: FP,
96    ) -> HierarchicalDispatch
97    where
98        E: Clone,
99        C: Terminated,
100        FC: FnMut(&mut C, E) -> bool,
101        FP: FnMut(&mut P, E) -> bool,
102    {
103        if self.child_active && child_dispatch(&mut self.child, event.clone()) {
104            return if self.child.is_terminated() {
105                HierarchicalDispatch::ChildTerminated
106            } else {
107                HierarchicalDispatch::Child
108            };
109        }
110
111        if parent_dispatch(&mut self.parent, event) {
112            HierarchicalDispatch::Parent
113        } else {
114            HierarchicalDispatch::Unhandled
115        }
116    }
117
118    /// Routes an event like `process_event` and propagates child termination
119    /// to a parent completion callback.
120    ///
121    /// When the callback handles completion, the child is deactivated while
122    /// retaining its state as shallow history.
123    pub fn process_event_with_completion<E, FC, FP, FT>(
124        &mut self,
125        event: E,
126        mut child_dispatch: FC,
127        mut parent_dispatch: FP,
128        mut child_completion: FT,
129    ) -> HierarchicalDispatch
130    where
131        E: Clone,
132        C: Terminated,
133        FC: FnMut(&mut C, E) -> bool,
134        FP: FnMut(&mut P, E) -> bool,
135        FT: FnMut(&mut P) -> bool,
136    {
137        if self.child_active && child_dispatch(&mut self.child, event.clone()) {
138            if self.child.is_terminated() {
139                if child_completion(&mut self.parent) {
140                    self.child_active = false;
141                    return HierarchicalDispatch::Parent;
142                }
143                return HierarchicalDispatch::ChildTerminated;
144            }
145            return HierarchicalDispatch::Child;
146        }
147
148        if parent_dispatch(&mut self.parent, event) {
149            HierarchicalDispatch::Parent
150        } else {
151            HierarchicalDispatch::Unhandled
152        }
153    }
154}
155
156/// Error returned when a bounded event queue has no remaining capacity.
157#[derive(Clone, Copy, Debug, PartialEq, Eq)]
158pub struct QueueFull;
159
160/// Allocation-free FIFO used for deferred and explicitly processed events.
161pub struct EventQueue<E, const N: usize> {
162    events: [Option<E>; N],
163    head: usize,
164    len: usize,
165}
166
167impl<E, const N: usize> EventQueue<E, N> {
168    /// Creates an empty queue.
169    pub const fn new() -> Self {
170        Self {
171            events: [const { None }; N],
172            head: 0,
173            len: 0,
174        }
175    }
176
177    /// Returns the number of queued events.
178    pub const fn len(&self) -> usize {
179        self.len
180    }
181
182    /// Returns true when no events are queued.
183    pub const fn is_empty(&self) -> bool {
184        self.len == 0
185    }
186
187    /// Defers an event until events already in the queue have been processed.
188    pub fn defer(&mut self, event: E) -> Result<(), QueueFull> {
189        if self.len == N || N == 0 {
190            return Err(QueueFull);
191        }
192        let tail = (self.head + self.len) % N;
193        self.events[tail] = Some(event);
194        self.len += 1;
195        Ok(())
196    }
197
198    /// Schedules an event ahead of currently deferred events.
199    pub fn process(&mut self, event: E) -> Result<(), QueueFull> {
200        if self.len == N || N == 0 {
201            return Err(QueueFull);
202        }
203        self.head = (self.head + N - 1) % N;
204        self.events[self.head] = Some(event);
205        self.len += 1;
206        Ok(())
207    }
208
209    /// Removes the next event.
210    pub fn pop(&mut self) -> Option<E> {
211        if self.len == 0 {
212            return None;
213        }
214        let event = self.events[self.head].take();
215        self.head = (self.head + 1) % N;
216        self.len -= 1;
217        event
218    }
219
220    /// Drops all queued events.
221    pub fn clear(&mut self) {
222        while self.pop().is_some() {}
223    }
224}
225
226impl<E, const N: usize> Default for EventQueue<E, N> {
227    fn default() -> Self {
228        Self::new()
229    }
230}
231
232/// Result reported by a queued dispatch callback.
233#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
234pub struct DispatchStatus {
235    /// The event matched a transition or handler.
236    pub handled: bool,
237    /// Processing changed the active state.
238    pub transitioned: bool,
239}
240
241/// Aggregate result of an external dispatch and the queue work it triggered.
242#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
243pub struct DispatchSummary {
244    /// Total number of events offered to the machine.
245    pub dispatched: usize,
246    /// Number reported as handled.
247    pub handled: usize,
248    /// Number that changed active state.
249    pub transitioned: usize,
250}
251
252impl DispatchSummary {
253    fn record(&mut self, status: DispatchStatus) {
254        self.dispatched += 1;
255        self.handled += usize::from(status.handled);
256        self.transitioned += usize::from(status.transitioned);
257    }
258}
259
260/// Separate bounded queues for deferred events and events requested by actions.
261pub struct EventQueues<E, const DEFERRED: usize, const PROCESSED: usize> {
262    deferred: EventQueue<E, DEFERRED>,
263    processed: EventQueue<E, PROCESSED>,
264}
265
266impl<E, const DEFERRED: usize, const PROCESSED: usize> EventQueues<E, DEFERRED, PROCESSED> {
267    /// Creates empty queues.
268    pub const fn new() -> Self {
269        Self {
270            deferred: EventQueue::new(),
271            processed: EventQueue::new(),
272        }
273    }
274
275    /// Defers an event until a dispatch changes state.
276    pub fn defer(&mut self, event: E) -> Result<(), QueueFull> {
277        self.deferred.defer(event)
278    }
279
280    /// Schedules an event for immediate processing after the current action.
281    pub fn process(&mut self, event: E) -> Result<(), QueueFull> {
282        self.processed.defer(event)
283    }
284
285    /// Returns the number of deferred events.
286    pub const fn deferred_len(&self) -> usize {
287        self.deferred.len()
288    }
289
290    /// Returns the number of immediately processed events.
291    pub const fn processed_len(&self) -> usize {
292        self.processed.len()
293    }
294
295    /// Dispatches an external event, drains action-requested events, and gives
296    /// each previously deferred event one retry after a state change.
297    pub fn dispatch<M, F>(&mut self, machine: &mut M, event: E, mut dispatch: F) -> DispatchSummary
298    where
299        F: FnMut(&mut M, &mut Self, E) -> DispatchStatus,
300    {
301        let mut summary = DispatchSummary::default();
302        let initial = dispatch(machine, self, event);
303        summary.record(initial);
304        self.drain_processed(machine, &mut dispatch, &mut summary);
305
306        if summary.transitioned > 0 {
307            let retries = self.deferred.len();
308            for _ in 0..retries {
309                if let Some(event) = self.deferred.pop() {
310                    let status = dispatch(machine, self, event);
311                    summary.record(status);
312                    self.drain_processed(machine, &mut dispatch, &mut summary);
313                }
314            }
315        }
316
317        summary
318    }
319
320    fn drain_processed<M, F>(
321        &mut self,
322        machine: &mut M,
323        dispatch: &mut F,
324        summary: &mut DispatchSummary,
325    ) where
326        F: FnMut(&mut M, &mut Self, E) -> DispatchStatus,
327    {
328        while let Some(event) = self.processed.pop() {
329            summary.record(dispatch(machine, self, event));
330        }
331    }
332}
333
334impl<E, const DEFERRED: usize, const PROCESSED: usize> Default
335    for EventQueues<E, DEFERRED, PROCESSED>
336{
337    fn default() -> Self {
338        Self::new()
339    }
340}
341
342/// Routes a runtime event ID to one of a contiguous set of typed handlers.
343///
344/// Each handler is responsible for translating the raw event into the
345/// generated event enum expected by its state machine.
346pub struct DispatchTable<'a, M, Raw, R> {
347    machine: &'a mut M,
348    first_id: usize,
349    handlers: &'a [fn(&mut M, &Raw) -> R],
350}
351
352impl<'a, M, Raw, R> DispatchTable<'a, M, Raw, R> {
353    /// Creates a table whose first handler corresponds to `first_id`.
354    pub const fn new(
355        machine: &'a mut M,
356        first_id: usize,
357        handlers: &'a [fn(&mut M, &Raw) -> R],
358    ) -> Self {
359        Self {
360            machine,
361            first_id,
362            handlers,
363        }
364    }
365
366    /// Dispatches `raw` through the handler associated with `id`.
367    ///
368    /// Returns `None` when the ID is outside the table's contiguous range.
369    #[inline]
370    pub fn dispatch(&mut self, raw: &Raw, id: usize) -> Option<R> {
371        let index = id.checked_sub(self.first_id)?;
372        let handler = *self.handlers.get(index)?;
373        Some(handler(self.machine, raw))
374    }
375
376    /// Returns a shared reference to the underlying machine.
377    pub fn machine(&self) -> &M {
378        self.machine
379    }
380
381    /// Returns a mutable reference to the underlying machine.
382    pub fn machine_mut(&mut self) -> &mut M {
383        self.machine
384    }
385}
386
387/// Associates an event with the index of the machine that should receive it.
388#[derive(Clone, Debug, PartialEq, Eq)]
389pub struct IndexedEvent<E> {
390    /// Index in the pool's storage.
391    pub index: usize,
392    /// Event to dispatch.
393    pub event: E,
394}
395
396/// Creates an indexed event for heterogeneous batch dispatch.
397pub const fn with_id<E>(index: usize, event: E) -> IndexedEvent<E> {
398    IndexedEvent { index, event }
399}
400
401/// A storage-generic pool of state machines.
402///
403/// Arrays, mutable slices, and allocation-backed containers can all be used as
404/// storage. Dispatch is supplied as a closure because each generated sml.rs
405/// machine has its own concrete event and error types.
406pub struct SmPool<S> {
407    storage: S,
408}
409
410/// A set of simultaneously active state-machine regions.
411///
412/// Each event is offered to every region, matching orthogonal-region broadcast
413/// semantics. Storage remains caller-selected and allocation-free.
414pub struct OrthogonalRegions<S> {
415    regions: S,
416}
417
418impl<S> OrthogonalRegions<S> {
419    /// Wraps the active region machines.
420    pub const fn new(regions: S) -> Self {
421        Self { regions }
422    }
423
424    /// Returns the region storage.
425    pub fn regions(&self) -> &S {
426        &self.regions
427    }
428
429    /// Returns the region storage mutably.
430    pub fn regions_mut(&mut self) -> &mut S {
431        &mut self.regions
432    }
433
434    /// Broadcasts an event to every region and returns the number that handled
435    /// it successfully.
436    pub fn process_event<M, E>(&mut self, event: E) -> usize
437    where
438        S: AsMut<[M]>,
439        M: Machine<E>,
440        E: Clone,
441    {
442        self.regions.as_mut().iter_mut().fold(0, |handled, region| {
443            handled + usize::from(Machine::process_event(region, event.clone()))
444        })
445    }
446}
447
448impl<S> SmPool<S> {
449    /// Wraps caller-provided storage.
450    pub const fn new(storage: S) -> Self {
451        Self { storage }
452    }
453
454    /// Returns the underlying storage.
455    pub fn storage(&self) -> &S {
456        &self.storage
457    }
458
459    /// Returns the underlying storage mutably.
460    pub fn storage_mut(&mut self) -> &mut S {
461        &mut self.storage
462    }
463
464    /// Replaces every machine using a caller-provided initializer.
465    pub fn reset<M, F>(&mut self, mut initialize: F)
466    where
467        S: AsMut<[M]>,
468        F: FnMut(usize) -> M,
469    {
470        for (index, machine) in self.storage.as_mut().iter_mut().enumerate() {
471            *machine = initialize(index);
472        }
473    }
474
475    /// Dispatches one event to one machine.
476    #[inline]
477    pub fn process_indexed<M, E, R, F>(&mut self, index: usize, event: E, dispatch: F) -> Option<R>
478    where
479        S: AsMut<[M]>,
480        F: FnOnce(&mut M, E) -> R,
481    {
482        self.storage
483            .as_mut()
484            .get_mut(index)
485            .map(|machine| dispatch(machine, event))
486    }
487
488    /// Dispatches the same clonable event to a batch of machine indices.
489    ///
490    /// Returns the number of valid indices that were processed.
491    pub fn process_indexed_batch<M, E, I, F>(
492        &mut self,
493        indices: I,
494        event: E,
495        mut dispatch: F,
496    ) -> usize
497    where
498        S: AsMut<[M]>,
499        E: Clone,
500        I: IntoIterator<Item = usize>,
501        F: FnMut(&mut M, E),
502    {
503        let machines = self.storage.as_mut();
504        let mut handled = 0;
505        for index in indices {
506            if let Some(machine) = machines.get_mut(index) {
507                dispatch(machine, event.clone());
508                handled += 1;
509            }
510        }
511        handled
512    }
513
514    /// Dispatches a batch of indexed events.
515    ///
516    /// Returns the number of valid indices that were processed.
517    pub fn process_event_batch<M, E, I, F>(&mut self, events: I, mut dispatch: F) -> usize
518    where
519        S: AsMut<[M]>,
520        I: IntoIterator<Item = IndexedEvent<E>>,
521        F: FnMut(&mut M, E),
522    {
523        let machines = self.storage.as_mut();
524        let mut handled = 0;
525        for IndexedEvent { index, event } in events {
526            if let Some(machine) = machines.get_mut(index) {
527                dispatch(machine, event);
528                handled += 1;
529            }
530        }
531        handled
532    }
533}