moku 0.3.0

A library for creating hierarchical state machines (HSM)
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
# moku
[![Rust](https://github.com/harrisonmg/moku/workflows/Rust/badge.svg)](https://github.com/harrisonmg/moku/actions)
[![Latest version](https://img.shields.io/crates/v/moku.svg)](https://crates.io/crates/moku)
[![Documentation](https://docs.rs/moku/badge.svg)](https://docs.rs/moku)
![License](https://img.shields.io/crates/l/moku.svg)

Moku is a Rust library for creating hierarchical state machines.

While it's also useful for creating flat state machines, nested states are a first-class feature.

Though it may be _impure_ to store data inside of states, in practice it is often useful for states to hold some data (e.g. file handles, perf counters, etc) and to allow substates and external code to access that data. This is also a core feature of moku.

## Features
- Autogeneration of boilerplate, including
    * A full state list
    * A state tree diagram
    * A state machine type
    * A state machine builder type
- Mutable access to active states from both outside and within the state machine
- Proc macros that emit useful compiler errors
- No dynamic memory allocation
- Minimal stack memory usage
- Logging of state machine actions through the Rust `log` API
- `no_std` support

## Shortcomings
Because moku generates a tree of sum types to represent the state machine, states must be `Sized` and do not support generic parameters (though generics can be used behind type aliases).

## What is a hierarchical state machine?
A hierarchical state machine (HSM) is a type of finite state machine where states can be nested inside of other states. Common functionalities between substates, such as state entry and exit actions, can be grouped by implementing them for the superstate. Beyond the convenient programming implications of HSMs, they often provide a more logical way of modeling systems.

A classic HSM example is blinky, a state machine that - when enabled - blinks some LED on and off:
```text
┌─Enabled─────────────────┐    ┌─Disabled───┐
│                         ├───►│            │
│       ┌─LedOn───┐       │    │            │
│    ┌─►│         ├──┐    │◄───┤            │
│    │  └─────────┘  │    │    └────────────┘
│    │               │    │
│    │  ┌─LedOff──┐  │    │
│    └──┤         │◄─┘    │
│       └─────────┘       │
│                         │
└─────────────────────────┘
```

Blinky has two superstates: `Enabled` and `Disabled`. When in the `Enabled` state, it cycles between two substates, `LedOn` and `LedOff`, which results in a blinking LED.

## Usage
The simplest possible moku state machine can be defined as follows:
```rust
// The `state_machine` attribute indicates to moku that the `blinky` module contains
// state definitions and an empty module for it to generate the state machine within.
#[moku::state_machine]
mod blinky {
    use moku::*;

    // The `machine_module` attribute marks the module moku will use for autogeneration.
    #[machine_module]
    mod machine {}

    // Moku has generated `BlinkyState`, an enum of all states.
    use machine::BlinkyState;

    // Every moku state machine must have a single `TopState`, which acts as a
    // superstate to all other states. The state machine never leaves this state.
    struct Top;

    // The top state is indicated by implementing the `TopState` trait for a struct.
    impl TopState<BlinkyState> for Top {}
}
```

Moku will generate the following public items inside of the `machine` module:
- The enum `BlinkyState` that implements [StateEnum]
- The struct `BlinkyMachine` that implements [StateMachine] and [StateRef] for every state
- The struct `BlinkyMachineBuilder` that implements [StateMachineBuilder]
- The `const` `&str` `BLINKY_STATE_CHART`

The `Blinky` name that prepends each of these items defaults to the name of the parent module in `UpperCamel` case, but can be manually specified as an argument to the [state_machine] attribute.

Let's add some more states inside of the `blinky` module:
```rust
# // NOTE: The lines prefixed with `#` below should be hidden with rustdoc.
# //       Visit https://docs.rs/moku instead for your viewing pleasure.
# #[moku::state_machine]
# mod blinky {
#     use moku::*;
#     #[machine_module]
#     mod machine {}
#     use machine::BlinkyState;
#     struct Top;
#     impl TopState<BlinkyState> for Top {}
    // ...

    struct Disabled;

    // Every `State` must use the `superstate` attribute to indicate what state
    // it is a substate of.
    #[superstate(Top)]
    impl State<BlinkyState> for Disabled {}

    struct Enabled;

    #[superstate(Top)]
    impl State<BlinkyState> for Enabled {}

    struct LedOn;

    #[superstate(Enabled)]
    impl State<BlinkyState> for LedOn {}

    struct LedOff;

    #[superstate(Enabled)]
    impl State<BlinkyState> for LedOff {}

    // ...
# }
```

At this point, `BLINKY_STATE_CHART` will look like:
```txt
Top
├─ Disabled
└─ Enabled
   ├─ LedOn
   └─ LedOff
```

and `BlinkyState` will look like:
```rust
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum BlinkyState {
    Top,
    Disabled,
    Enabled,
    LedOn,
    LedOff,
}
```

Let's add some functionality to our states:
```rust
# // NOTE: The lines prefixed with `#` below should be hidden with rustdoc.
# //       Visit https://docs.rs/moku instead for your viewing pleasure.
# #[moku::state_machine]
# mod blinky {
#     use moku::*;
#     #[machine_module]
#     mod machine {}
#     use machine::BlinkyState;
    // ...

    struct Top {
        // Store the blink time setting in the top state; we may want to configure it on a
        // per-instance basis.
        //
        // Because the `TopState` is always active, this value will persist for the lifetime
        // of the state machine.
        blink_time: std::time::Duration,
    }

    impl TopState<BlinkyState> for Top {
        // By implementing the `init` method, we can define the initial transition taken
        // after transitioning into a state.
        //
        // Like most other methods in the `TopState` and `State` traits, the return value
        // indicates a state to transition to, where `None` indicates no transition.
        fn init(&mut self) -> Option<BlinkyState> {
            // When we transition into the `Top` state (or initialize the state machine),
            // transition into the `Enabled` state.
            Some(BlinkyState::Enabled)
        }
    }

    // ...
#     struct Disabled;
#     #[superstate(Top)]
#     impl State<BlinkyState> for Disabled {}
#     struct Enabled;

    #[superstate(Top)]
    impl State<BlinkyState> for Enabled {
        fn init(
            &mut self,
            _superstates: &mut Self::Superstates<'_>,
        ) -> Option<BlinkyState> {
            // When we transition into the `Enabled` state, transition into the `LedOn` state.
            Some(BlinkyState::LedOn)
        }
    }

    struct LedOn {
        entry_time: std::time::Instant,
    }

    #[superstate(Enabled)]
    impl State<BlinkyState> for LedOn {
        // The `enter` method acts as a constructor for the state when it becomes active.
        // States do not persist when they are inactive.
        //
        // If unimplemented, moku will autogenerate this method for states with no fields.
        //
        // The `StateEntry` return type also allows for a transition away instead of
        // entering the state - for instance towards a fault state if some aspect of
        // state construction fails.
        fn enter(
            _superstates: &mut Self::Superstates<'_>,
        ) -> StateEntry<Self, BlinkyState> {
            // dummy code to turn the LED on
            // led_gpio.set_high()

            StateEntry::State(Self {
                entry_time: std::time::Instant::now(),
            })
        }

        // Moku automatically defines the `Superstates` associated type for each state.
        // This type will contain a mutable reference to each active superstate.
        fn update(
            &mut self,
            superstates: &mut Self::Superstates<'_>,
        ) -> Option<BlinkyState> {
            // We can use `superstates` to access the `blink_time` field of the `Top` state.
            if self.entry_time.elapsed() >= superstates.top.blink_time {
                // If we've met or exceeded the blink time, transition to the `LedOff` state.
                Some(BlinkyState::LedOff)
            } else {
                // Otherwise, don't transition away from this state.
                None
            }
        }
    }

    struct LedOff {
        entry_time: std::time::Instant,
    }

    #[superstate(Enabled)]
    impl State<BlinkyState> for LedOff {
        fn enter(
            _superstates: &mut Self::Superstates<'_>,
        ) -> StateEntry<Self, BlinkyState> {
            // dummy code to turn the LED off
            // led_gpio.set_low()

            StateEntry::State(Self {
                entry_time: std::time::Instant::now(),
            })
        }

        fn update(
            &mut self,
            superstates: &mut Self::Superstates<'_>,
        ) -> Option<BlinkyState> {
            if self.entry_time.elapsed() >= superstates.top.blink_time {
                // If we've met or exceeded the blink time, transition to the `LedOn` state.
                Some(BlinkyState::LedOn)
            } else {
                // Otherwise, don't transition away from this state.
                None
            }
        }
    }

    // ...
# }
```

Finally, let's use our state machine!
```rust
# // NOTE: The lines prefixed with `#` below should be hidden with rustdoc.
# //       Visit https://docs.rs/moku instead for your viewing pleasure.
# #[moku::state_machine]
# mod blinky {
#     use moku::*;
#     #[machine_module]
#     pub mod machine {}
#     use machine::BlinkyState;
#    pub struct Top { pub blink_time: std::time::Duration }
#    impl TopState<BlinkyState> for Top {
#        fn init(&mut self) -> Option<BlinkyState> {
#            Some(BlinkyState::Enabled)
#        }
#    }
#     struct Disabled;
#     #[superstate(Top)]
#     impl State<BlinkyState> for Disabled {}
#     struct Enabled;
#     #[superstate(Top)]
#     impl State<BlinkyState> for Enabled {
#         fn init(
#             &mut self,
#             _superstates: &mut Self::Superstates<'_>,
#         ) -> Option<BlinkyState> {
#             Some(BlinkyState::LedOn)
#         }
#     }
#     struct LedOn { entry_time: std::time::Instant }
#     #[superstate(Enabled)]
#     impl State<BlinkyState> for LedOn {
#         fn enter(
#             _superstates: &mut Self::Superstates<'_>,
#         ) -> StateEntry<Self, BlinkyState> {
#             StateEntry::State(Self {
#                 entry_time: std::time::Instant::now(),
#             })
#         }
#         fn update(
#             &mut self,
#             superstates: &mut Self::Superstates<'_>,
#         ) -> Option<BlinkyState> {
#             if self.entry_time.elapsed() >= superstates.top.blink_time {
#                 Some(BlinkyState::LedOff)
#             } else {
#                 None
#             }
#         }
#     }
#     pub struct LedOff { pub entry_time: std::time::Instant }
#     #[superstate(Enabled)]
#     impl State<BlinkyState> for LedOff {
#         fn enter(
#             _superstates: &mut Self::Superstates<'_>,
#         ) -> StateEntry<Self, BlinkyState> {
#             StateEntry::State(Self {
#                 entry_time: std::time::Instant::now(),
#             })
#         }
#         fn update(
#             &mut self,
#             superstates: &mut Self::Superstates<'_>,
#         ) -> Option<BlinkyState> {
#             if self.entry_time.elapsed() >= superstates.top.blink_time {
#                 Some(BlinkyState::LedOn)
#             } else {
#                 None
#             }
#         }
#     }
# }
// ...

use moku::{StateMachine, StateMachineBuilder};
use blinky::{machine::{BlinkyMachineBuilder, BlinkyState}, Top};

let top_state = Top {
    blink_time: std::time::Duration::ZERO,
};

// The builder type let's us make a new state machine from a top state.
// The state machine is initialized upon building.
let mut machine = BlinkyMachineBuilder::new(top_state).build();

// log output:
// ----------
// Blinky: Initial transition to Enabled
// │Entering Enabled
// │Initial transition to LedOn
// │Entering LedOn
// └Transition complete

// `state_matches(...)` will match with any active state or superstate.
assert!(machine.state_matches(BlinkyState::Enabled));
assert!(machine.state_matches(BlinkyState::LedOn));

// `state()` returns the exact current state.
assert!(matches!(machine.state(), BlinkyState::LedOn));

// `update()` calls each state's `update()` method, starting from the deepest state.
machine.update();

// log output:
// ----------
// Blinky: Updating
// │Updating LedOn
// │Transitioning from LedOn to LedOff
// ││Exiting LedOn
// ││Entering LedOff
// │└Transition complete
// │Updating Enabled
// │Updating Top
// └Update complete

// `top_down_update()` calls each state's `top_down_update()` method,
// starting from the `TopState`.
machine.top_down_update();

// log output:
// ----------
// Blinky: Top-down updating
// │Top-down updating Top
// │Top-down updating Enabled
// │Top-down updating LedOff
// └Top-down update complete

// We have access to the `TopState` at all times.
dbg!(machine.top_ref().blink_time);
machine.top_mut().blink_time = std::time::Duration::from_secs(1);

// We can access currently active states through the `StateRef` trait.
use moku::StateRef;

let led_off: &blinky::LedOff = machine.state_ref().unwrap();
dbg!(led_off.entry_time);

let mut led_off: &mut blinky::LedOff = machine.state_mut().unwrap();
led_off.entry_time = std::time::Instant::now();

// We can manually induce transitions.
machine.transition(BlinkyState::Disabled);

// log output:
// ----------
// Blinky: Transitioning from LedOff to Disabled
// │Exiting LedOff
// │Exiting Enabled
// │Entering Disabled
// └Transition complete

```
If a transition occurs during an update or top-down update, the update will continue from the nearest common ancestor between the previous state and the new state. See [StateMachine::update] and [StateMachine::top_down_update] for more details.

An interactive example of blinky can be found in the [`examples/`](https://github.com/harrisonmg/moku/tree/main/examples) directory. Try it out with:
```text
cargo run --example blinky
```

## Examples
Along with an interactive example of the blinky machine described above, the following examples are included in the [`examples/`](https://github.com/harrisonmg/moku/tree/main/examples) directory.

### Event handling
It's common to implement state machines alongside an event type, where each active state handles events as they are generated. Often state machine transitions are defined via a centralized table of states and events. Moku focuses on the autogeneration of state machine boilerplate, leaving event queues and handling for users to implement at their discretion.

### Test Mocks
Sometimes it's necessary to mock away interfaces for testing purposes. Moku does not support states with generic parameters, but conditional compilation (among other approaches) can be used to substitute in test mocks when needed.

## Warning
Moku exposes the [internal] module, the contents of which are intended to be used only by the code that is generated by moku. This, in addition to the methods defined in the [TopState] and [State] traits, are not intended to be called by users.

## Macro expansion
Should you wish to view the fully expanded code generated by moku, the [cargo-expand](https://crates.io/crates/cargo-expand) crate may prove useful.