Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
125 changes: 125 additions & 0 deletions doc/faq.md
Original file line number Diff line number Diff line change
Expand Up @@ -82,3 +82,128 @@ neither STL nor Boost are required. It is part of the `boost-ext` project; it is

Both. Transition actions run on transitions (Mealy), while `on_entry<...>` /
`on_exit<...>` provide per-state actions (Moore).

### Can a state hold data?

Yes. A state is a plain type, take it by reference in a guard or action to read or
change it. `example/data.cpp` keeps an `id` in the `Connected` and `Interrupted`
states and copies it between them:

```cpp
const auto set = [](const auto& event, Connected& state) { state.id = event.id; };
const auto update = [](Connected& src_state, Interrupted& dst_state) { dst_state.id = src_state.id; };
```

### Is a state machine thread safe? Can I call `process_event` from an action?

Not by default. Pass the `thread_safe` policy with a lock type. With
`std::recursive_mutex`, `process_event` can be called from inside an action:

```cpp
sml::sm<example, sml::thread_safe<std::recursive_mutex>> sm;
```

### How do I handle any event (wildcard)?

Use both `event<_>` and `unexpected_event<_>`. `"s"_s + event<_> / action` matches every event type that appears somewhere in the table, `"s"_s + unexpected_event<_> / action` the types that do not. A specific `event<e>` row on the same state has priority over the wildcard.

### Why doesn't `unexpected_event<_>` catch an event that is in the table?

`unexpected_event<_>` matches only event types that appear nowhere in the table. A table event with no transition in the current state is dropped and `process_event` returns `false`. To react to it add `unexpected_event<that_event>`, or check the returned `bool`.

### How do I handle an event in every state, with a default action?

Use `state<_>` as the source. It applies in every state with no transition of its own for the event, so a specific row such as `"s2"_s + event<e> / specific` wins there and the default does not run:

```cpp
"s2"_s + event<e> / specific,
state<_> + event<e> / default_action
```

### Why is my `"name"_e` event not handled?

`"name"_e` is the event token for tables. To send it, create an instance with parentheses: `sm.process_event("name"_e())`. Without them it compiles, nothing matches and `process_event` returns `false`.

### How do I check the state of a sub-state-machine?

Name the sub machine's state type in the first template argument of `is`:

```cpp
sm.is<decltype(sml::state<sub>)>(sml::state<sub_state>);
```

`sm.is(sml::state<sub>)` only says the parent is inside `sub`. For "any of these states" (what MSM calls a flag) combine `is` calls:

```cpp
template <class SM, class... S>
bool is_any(const SM& sm, S... s) { return (sm.is(s) || ...); }
```

### How do I enter a composite state in a specific sub-state?

There is no direct entry: `state<Composite::Sub2>` is treated as a state of the parent and skips the composite's `on_entry`. Transition to `state<Composite>` and give the composite an initial dispatch state with guarded anonymous transitions to the sub-states, driven by a dependency the parent sets first.

### How do I reuse a base transition table in several machines?

`make_transition_table` takes transitions, not other tables. Return the shared transitions in a `std::tuple` and unpack them with `std::apply` (C++17):

```cpp
struct base { static constexpr auto transitions() { return std::make_tuple(*"a"_s + event<e> = "b"_s); } };
struct derived { auto operator()() const { return std::apply([](auto... t) {
return sml::make_transition_table(t..., "b"_s + event<f> = "c"_s); }, base::transitions()); } };
```

This is reuse, a base transition cannot be overridden.

### Can I build the transition table conditionally or in pieces?

Not at runtime and not by concatenating tables, the table is a type. Select a whole table with `if constexpr` on a template parameter, or use guards for runtime conditions. For the same state and event, transitions are tried in written order and the first one whose guard passes runs.

### Can I ask whether an event would be handled without processing it?

Not directly, there is no `can_process_event`. Processing a copy (`auto probe = sm; probe.process_event(e)`) answers it including guards, but actions run and dependencies passed by reference are shared with the copy. Use it only when actions have no outside side effects.

### How do I ignore events while an action is in progress?

Make the in-progress period a state with one way out: `idle + event<start> / begin_work = busy` and `busy + event<work_done> = idle`. Events with no transition in `busy` are dropped. Send `work_done` when the work completes, or queue it from the action with `process(work_done{})` and a `process_queue` policy.

### How do I process a new event built from the incoming one?

Use an action taking `back::process<E>` (needs a `process_queue` policy):

```cpp
event<raw> / [](const raw& e, back::process<converted> process) { process(converted{e.value * 2}); }
```

### How do errors reach the caller of `process_event`?

`process_event` returns only `bool`. With no `exception<>` transition in the table, exceptions propagate to the caller. With any `exception<>` transition, unmatched exceptions are swallowed. To report one, store it in a dependency:

```cpp
"s"_s + exception<_> / [](std::exception_ptr& e) { e = std::current_exception(); } = "idle"_s
```

Without exceptions, report an error from a sub-state by firing an event from the action with `process(request_failed{})` (needs a `process_queue` policy). Events the sub machine does not handle fall through to the parent.

### How do I read my context in a logger? Why is the initial substate not logged?

A logger is a dependency like any other: give it a reference to your context and pass both to the machine, `sml::sm<top, sml::logger<my_logger>> sm{logger, ctx}`. `log_state_change` gets only name holders (`c_str()`), not state objects, and runs on transitions only, before the destination's `on_entry`. The initial state of a sub machine is entered without a transition, log it from an `on_entry<_>` action on that state.

### How do I see the current state in a debugger?

The sm object holds template internals, not names. Track the name with a logger policy and watch the variable:

```cpp
template <class SM, class TSrc, class TDst>
void log_state_change(const TSrc&, const TDst& dst) { current_state_name = dst.c_str(); }
```

The logger must define all four `log_*` members and be passed as an lvalue to the `sm` constructor.

### Why does `wrap(&Class::method)` fail with "cannot bind non-const lvalue reference"?

A member-function pointer makes `Class` a dependency of the machine and dependencies are held by reference. Pass an lvalue of `Class` to the `sm` constructor, a temporary will not bind.

### Why can't I pass a temporary dependency to the `sm` constructor?

Dependencies are held by reference, so `sm{logger{}}` does not compile when an action or the logger takes `logger&`. Pass an lvalue that outlives the machine, or define `BOOST_SML_CREATE_DEFAULT_CONSTRUCTIBLE_DEPS` to make the machine keep its own copy of a default-constructible dependency.
2 changes: 1 addition & 1 deletion doc/tutorial.md
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ auto state = "idle"_s;

However, please notice that above solution is a non-standard extension for Clang/GCC.

`SML` states cannot have data as data is injected directly into guards/actions instead.
`SML` states are plain types, so a state can hold data. Guards and actions take the state by reference to read or change it, see the [Data](examples.md#data) example. Other data is injected directly into guards/actions.

A state machine might be a State itself.

Expand Down
Loading