diff --git a/CMakeLists.txt b/CMakeLists.txt index a4d535d3..af666767 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -206,7 +206,7 @@ configure_package_config_file( write_basic_package_version_file( ${CMAKE_CURRENT_BINARY_DIR}/smlConfigVersion.cmake - VERSION 1.1.13 + VERSION 1.2.0 COMPATIBILITY SameMajorVersion ) diff --git a/README.md b/README.md index 649328c7..e1c0818d 100644 --- a/README.md +++ b/README.md @@ -287,6 +287,7 @@ send: 42 * [sm [state machine]](https://boost-ext.github.io/sml/user_guide.html#sm-state-machine) * [policies [state machine]](https://boost-ext.github.io/sml/user_guide.html#policies-state-machine) * [testing::sm [testing]](https://boost-ext.github.io/sml/user_guide.html#testingsm-testing) + * [make_action [utility]](https://boost-ext.github.io/sml/user_guide.html#make_action-utility) * [make_dispatch_table [utility]](https://boost-ext.github.io/sml/user_guide.html#make_dispatch_table-utility) * [Examples](https://boost-ext.github.io/sml/examples.html) * [Hello World](https://boost-ext.github.io/sml/examples.html#hello-world) @@ -312,7 +313,11 @@ send: 42 * [SDL2 Integration](https://boost-ext.github.io/sml/examples.html#sdl2-integration) * [Plant UML Integration](https://boost-ext.github.io/sml/examples.html#plant-uml-integration) * [FAQ](https://boost-ext.github.io/sml/faq.html) + * [Limitations](https://boost-ext.github.io/sml/faq.html#limitations) + * [`on_entry<_>` across translation units](https://boost-ext.github.io/sml/faq.html#on_entry_-across-translation-units) + * [`operator,` with two raw member-function-pointer actions](https://boost-ext.github.io/sml/faq.html#operator-with-two-raw-member-function-pointer-actions) * [CHANGELOG](https://boost-ext.github.io/sml/CHANGELOG.html) + * [[1.2.0] - 2026-06-01](https://boost-ext.github.io/sml/CHANGELOG.html#120-2026-06-01) * [[1.1.13] - 2025-12-01](https://boost-ext.github.io/sml/CHANGELOG.html#1113-2025-12-01) * [[1.1.12] - 2025-04-02](https://boost-ext.github.io/sml/CHANGELOG.html#1112-2025-04-02) * [[1.1.11] - 2024-03-09](https://boost-ext.github.io/sml/CHANGELOG.html#1111-2024-03-09) diff --git a/doc/CHANGELOG.md b/doc/CHANGELOG.md index 99402046..8f2ba373 100644 --- a/doc/CHANGELOG.md +++ b/doc/CHANGELOG.md @@ -1,3 +1,27 @@ +## [1.2.0] - 2026-06-01 +- **Additions** + - `sm::flush_queue()` — drain pending queued events from async handlers (#456) + - `sml::clear_defer` action — discard deferred events from a transition (#643) + - `sml::deps` policy — explicit pool dependencies for generic-lambda actions/guards (#437) + - `sml::make_action(f)` — wrap a template/generic/constrained callable so its dependency and event types are deduced from the explicit `` list (#629) + +- **Behavior changes** + - Empty-SM min-size trick is now OFF by default on GCC/Clang (it was UB at `-O2`); + opt back in with `BOOST_SML_CFG_ENABLE_MIN_SIZE`. `sizeof(sm)` for an empty SM may change (#249) + - `any`/`_` wildcard no longer fires when a composable sub-SM has terminated (#622) + - Events raised via `process(E{})` are now storable in the process queue (#580) + - Guard taking `const State&` now sees live pool state instead of a stale copy (#530) + - Sub-SM defer queue is cleared when its composite state is re-entered (#253) + +- **Bug Fixes** + - Template-depth, `-Wshadow`, `-Wextra-semi`, UBSan, final-class, null-deref, + double-pop, reserved-identifier and related fixes — see full list: + - https://github.com/boost-ext/sml/compare/v1.1.13...v1.2.0 + +- **Documented limitations** + - `on_entry<_>` multi-TU linker limitation and dispatch priority (#565) + - `operator,` with two raw member-function-pointer actions; `wrap()` workaround (#389) + ## [1.1.13] - 2025-12-01 - Changes - https://github.com/boost-ext/sml/compare/v1.1.12...v1.1.13 diff --git a/doc/examples.md b/doc/examples.md index 2ecf3e34..e0848538 100644 --- a/doc/examples.md +++ b/doc/examples.md @@ -25,66 +25,88 @@ ###Hello World ![CPP](https://raw.githubusercontent.com/boost-ext/sml/master/example/hello_world.cpp) +[▶ Run on Compiler Explorer](https://godbolt.org/z/dq85hW4ab) ###Events ![CPP](https://raw.githubusercontent.com/boost-ext/sml/master/example/events.cpp) +[▶ Run on Compiler Explorer](https://godbolt.org/z/s816cG5j8) ###States ![CPP](https://raw.githubusercontent.com/boost-ext/sml/master/example/states.cpp) +[▶ Run on Compiler Explorer](https://godbolt.org/z/c9asGjYTr) ###Actions Guards ![CPP](https://raw.githubusercontent.com/boost-ext/sml/master/example/actions_guards.cpp) +[▶ Run on Compiler Explorer](https://godbolt.org/z/nehrdofPx) ###Transitions ![CPP](https://raw.githubusercontent.com/boost-ext/sml/master/example/transitions.cpp) +[▶ Run on Compiler Explorer](https://godbolt.org/z/qr8qs78Ts) ###Defer/Process ![CPP](https://raw.githubusercontent.com/boost-ext/sml/master/example/defer_and_process.cpp) +[▶ Run on Compiler Explorer](https://godbolt.org/z/T7YaW61en) ###Orthogonal Regions ![CPP](https://raw.githubusercontent.com/boost-ext/sml/master/example/orthogonal_regions.cpp) +[▶ Run on Compiler Explorer](https://godbolt.org/z/vqGbsrqM3) ###Composite ![CPP](https://raw.githubusercontent.com/boost-ext/sml/master/example/composite.cpp) +[▶ Run on Compiler Explorer](https://godbolt.org/z/5sYb9eGbe) ###History ![CPP](https://raw.githubusercontent.com/boost-ext/sml/master/example/history.cpp) +[▶ Run on Compiler Explorer](https://godbolt.org/z/Pvz6Y7vd4) ###Error handling ![CPP](https://raw.githubusercontent.com/boost-ext/sml/master/example/error_handling.cpp) +[▶ Run on Compiler Explorer](https://godbolt.org/z/cGd7TnM1G) ###Logging ![CPP](https://raw.githubusercontent.com/boost-ext/sml/master/example/logging.cpp) +[▶ Run on Compiler Explorer](https://godbolt.org/z/Tec7jz36r) ###Nested ![CPP](https://raw.githubusercontent.com/boost-ext/sml/master/example/nested.cpp) +[▶ Run on Compiler Explorer](https://godbolt.org/z/xd9PoEPdn) ###Testing ![CPP](https://raw.githubusercontent.com/boost-ext/sml/master/example/testing.cpp) +[▶ Run on Compiler Explorer](https://godbolt.org/z/oc73h5sY8) ###Runtime Dispatcher ![CPP](https://raw.githubusercontent.com/boost-ext/sml/master/example/dispatch_table.cpp) +[▶ Run on Compiler Explorer](https://godbolt.org/z/j5bMnobY9) ###eUML Emulation ![CPP](https://raw.githubusercontent.com/boost-ext/sml/master/example/euml_emulation.cpp) +[▶ Run on Compiler Explorer](https://godbolt.org/z/n93Y5Mner) ###Dependencies ![CPP](https://raw.githubusercontent.com/boost-ext/sml/master/example/dependencies.cpp) +[▶ Run on Compiler Explorer](https://godbolt.org/z/afxWz4jq3) ###Data ![CPP](https://raw.githubusercontent.com/boost-ext/sml/master/example/data.cpp) +[▶ Run on Compiler Explorer](https://godbolt.org/z/P4vn1dK9e) ###In-Place ![CPP](https://raw.githubusercontent.com/boost-ext/sml/master/example/in_place.cpp) +[▶ Run on Compiler Explorer](https://godbolt.org/z/r43fKbz6z) ###Dependency Injection ![CPP](https://raw.githubusercontent.com/boost-ext/sml/master/example/dependency_injection.cpp) +[▶ Run on Compiler Explorer](https://godbolt.org/z/1Pv6EaG7n) ###Arduino Integration ![CPP](https://raw.githubusercontent.com/boost-ext/sml/master/example/arduino.cpp) +[▶ Run on Compiler Explorer](https://godbolt.org/z/Kfba9GovG) ###SDL2 Integration ![CPP](https://raw.githubusercontent.com/boost-ext/sml/master/example/sdl2.cpp) +[▶ Run on Compiler Explorer](https://godbolt.org/z/8heY58eE6) ###Plant UML Integration ![CPP](https://raw.githubusercontent.com/boost-ext/sml/master/example/plant_uml.cpp) +[▶ Run on Compiler Explorer](https://godbolt.org/z/8eT6aWsc9) diff --git a/doc/faq.md b/doc/faq.md index e69de29b..c49ef59b 100644 --- a/doc/faq.md +++ b/doc/faq.md @@ -0,0 +1,31 @@ +# FAQ + +## Limitations + +### `on_entry<_>` across translation units + +When `on_entry<_>` (the wildcard entry handler) is defined in one translation unit +while the `initial`-event specialization is left undefined, the linker reports an +undefined reference. Make sure the entry handler is fully defined where it is used. + +Dispatch priority: a specific `on_entry` handler takes priority over the +`on_entry<_>` wildcard. The wildcard fires only when no specific handler matches the +event used to enter the state. + +```cpp +// on_entry fires when entering via e1 (specific handler wins) +// on_entry<_> fires when entering via any other event (no specific handler) +``` + +### `operator,` with two raw member-function-pointer actions + +Two raw member-function-pointer actions combined with the comma operator +(`&A::f, &A::g`) select the built-in comma operator, because both operands are +class types — so SML's action-sequencing is bypassed. Wrap the actions with +`wrap(...)` to restore sequencing: + +```cpp +*"s1"_s + event / (wrap(&A::f), wrap(&A::g)) = X +``` + +See `example/actions_guards.cpp` for a worked example. diff --git a/doc/index.md b/doc/index.md index 425d9a1e..28178acf 100644 --- a/doc/index.md +++ b/doc/index.md @@ -46,9 +46,13 @@ to avoid it `[Boost].SML` may suit you! ###Real Life examples? ![CPP(BTN)](Run_SDL2_Integration_Example|https://raw.githubusercontent.com/boost-ext/sml/master/example/sdl2.cpp) +[▶ Run on Compiler Explorer](https://godbolt.org/z/8heY58eE6) ![CPP(BTN)](Run_Plant_UML_Example|https://raw.githubusercontent.com/boost-ext/sml/master/example/plant_uml.cpp) +[▶ Run on Compiler Explorer](https://godbolt.org/z/8eT6aWsc9) ![CPP(BTN)](Run_Logging_Example|https://raw.githubusercontent.com/boost-ext/sml/master/example/logging.cpp) +[▶ Run on Compiler Explorer](https://godbolt.org/z/Tec7jz36r) ![CPP(BTN)](Run_Testing_Example|https://raw.githubusercontent.com/boost-ext/sml/master/example/testing.cpp) +[▶ Run on Compiler Explorer](https://godbolt.org/z/oc73h5sY8)   diff --git a/doc/overview.md b/doc/overview.md index 4cdd5f90..29dfbe8c 100644 --- a/doc/overview.md +++ b/doc/overview.md @@ -48,7 +48,17 @@ git clone https://github.com/boost-ext/sml && cd sml && make test ###Configuration | Macro | Description | | --------------------------------------------------------------|--------------------------------------------------------------| -| `BOOST_SML_VERSION` | Current version of [Boost].SML (ex. 1'0'0) | +| `BOOST_SML_VERSION` | Current version of [Boost].SML (ex. 1'1'13) | +| `BOOST_SML_CFG_ENABLE_MIN_SIZE` | Opt in to the empty-SM size trick (zero-size array); disabled by default, see note below | +| `BOOST_SML_CFG_DISABLE_MIN_SIZE` | Legacy name kept for backward compatibility; the min-size trick is now disabled by default | +| `BOOST_SML_DISABLE_EXCEPTIONS` | Build with exceptions disabled (e.g. with `-fno-exceptions`) | +| `BOOST_SML_CREATE_DEFAULT_CONSTRUCTIBLE_DEPS` | Allow default-constructing dependencies owned by the State Machine | + +> **Note (min-size):** Since the changes after `v1.1.13`, the empty-State-Machine +> size optimization is **off by default** on GCC/Clang because the zero-length-array +> trick triggered undefined behavior at `-O2` (UBSan violations). As a result, +> `sizeof(sm<...>)` for an empty State Machine may differ from earlier releases. +> Define `BOOST_SML_CFG_ENABLE_MIN_SIZE` to opt back in. ###Exception Safety @@ -83,15 +93,19 @@ sm.process_event(event{}); // thread safe call ***Not configurable*** ![CPP](https://raw.githubusercontent.com/boost-ext/sml/master/test/ft/errors/not_configurable.cpp) +[▶ See the compile error on Compiler Explorer](https://godbolt.org/z/zehzezMoe) ***Not callable*** ![CPP](https://raw.githubusercontent.com/boost-ext/sml/master/test/ft/errors/not_callable.cpp) +[▶ See the compile error on Compiler Explorer](https://godbolt.org/z/fb8K6q4WK) ***Not transitional*** ![CPP](https://raw.githubusercontent.com/boost-ext/sml/master/test/ft/errors/not_transitional.cpp) +[▶ See the compile error on Compiler Explorer](https://godbolt.org/z/3aWdcTanT) ***Not dispatchable*** ![CPP](https://raw.githubusercontent.com/boost-ext/sml/master/test/ft/errors/not_dispatchable.cpp) +[▶ See the compile error on Compiler Explorer](https://godbolt.org/z/984KoaP6x) [Boost.MSM-eUML]: http://www.boost.org/doc/libs/1_60_0/libs/msm/doc/HTML/ch03s04.html [Boost.MSM3-eUML2]: https://htmlpreview.github.io/?https://raw.githubusercontent.com/boostorg/msm/msm3/doc/HTML/ch03s05.html diff --git a/doc/scripts/gen_godbolt_links.sh b/doc/scripts/gen_godbolt_links.sh new file mode 100755 index 00000000..b5f6fad9 --- /dev/null +++ b/doc/scripts/gen_godbolt_links.sh @@ -0,0 +1,85 @@ +#!/usr/bin/env bash +# +# Regenerate the "Run on Compiler Explorer" links embedded in the docs. +# +# Boost.SML is a single-header library that is NOT registered as a Compiler +# Explorer library. Each godbolt.org/z/ link keeps the example source intact and +# pulls the header via Compiler Explorer's client-side "#include from URL" feature: +# #include +# CE fetches and pastes that header in the browser when the link is opened (it is +# NOT resolved by the compile API). dispatch_table.hpp itself does a relative +# `#include "boost/sml.hpp"`, which CE's fetcher cannot resolve, so for the two +# examples that use it (dispatch_table, sdl2) the small utility header is inlined +# with its own sml.hpp include rewritten to the URL form. +# +# The error/* examples intentionally fail to compile and get compile-only links +# that show the diagnostic. +# +# Requirements: bash, curl, jq. Run from the repository root. +# Usage: +# doc/scripts/gen_godbolt_links.sh # print "name url" for every example +# doc/scripts/gen_godbolt_links.sh --insert # also rewrite the CE links in doc/*.md +# +set -euo pipefail +cd "$(git rev-parse --show-toplevel)" + +GCC=g142 # Compiler Explorer GCC 14.2 +STD_DEFAULT=c++14 # SML's minimum standard +DT=include/boost/sml/utility/dispatch_table.hpp +URL='https://raw.githubusercontent.com/boost-ext/sml/master/include/boost/sml.hpp' + +assemble() { # -> thin url-include source on stdout + # Rewrite `#include ` to the URL form; for the dispatch_table.hpp + # include, inline that small header with its own sml.hpp include rewritten too. + awk -v url="$URL" -v dt="$DT" ' + /#include[ \t]*[<"]boost\/sml\.hpp[>"]/ { print "#include <" url ">"; next } + /#include[ \t]*[<"]boost\/sml\/utility\/dispatch_table\.hpp[>"]/ { + while ((getline l < dt) > 0) + if (l ~ /#include[ \t]*[<"]boost\/sml\.hpp[>"]/) print "#include <" url ">"; else print l + close(dt); next } + { print } + ' "$1" +} + +shorten() { # -> godbolt short url + local src="$1" std="$2" exec="$3" + jq -n --rawfile s "$src" --arg std "$std" --argjson ex "$exec" \ + '{sessions:[{id:1,language:"c++",source:$s, + compilers:[{id:"'"$GCC"'",options:("-O2 -std="+$std),libs:[], + filters:{execute:$ex,labels:true,directives:true,commentOnly:true,trim:true,demangle:true}}], + executors:(if $ex then [{compiler:{id:"'"$GCC"'",options:("-O2 -std="+$std),libs:[],overrides:[]}, + arguments:"",stdin:"",compilerOutputVisible:true,stdoutVisible:true,stderrVisible:true}] else [] end)}]}' \ + | curl -s -X POST 'https://godbolt.org/api/shortener' -H 'Content-Type: application/json' -d @- | jq -r '.url' +} + +MAP=$(mktemp) +process() { # + local ex="$1" exec="$2" name std tmp url + name=$(basename "$ex" .cpp) + std=$STD_DEFAULT; [ "$name" = arduino ] && std=c++20 # template lambdas + tmp=$(mktemp); assemble "$ex" > "$tmp" + url=$(shorten "$tmp" "$std" "$exec"); rm -f "$tmp" + echo "$name $url" | tee -a "$MAP" +} + +for f in example/*.cpp; do process "$f" true; done +for f in test/ft/errors/not_*.cpp; do process "$f" false; done + +if [ "${1:-}" = "--insert" ]; then + # strip previously-generated CE links, then re-insert after each embed. + # Only delete a CE link that immediately FOLLOWS an ![CPP] embed, so standalone + # links (e.g. the make_action example in the user guide) are preserved. + DOCS=(doc/examples.md doc/tutorial.md doc/user_guide.md doc/index.md doc/overview.md) + for d in "${DOCS[@]}"; do + awk '{ if (prev ~ /!\[CPP/ && $0 ~ /Compiler Explorer\]\(https:\/\/godbolt\.org/) { prev=$0; next } print; prev=$0 }' "$d" > "$d.tmp" && mv "$d.tmp" "$d" + done + while read -r name url; do + case "$name" in + not_*) label="▶ See the compile error on Compiler Explorer";; + *) label="▶ Run on Compiler Explorer";; + esac + for d in "${DOCS[@]}"; do sed -i "\#/${name}\.cpp)#a [$label]($url)" "$d"; done + done < "$MAP" + echo "docs updated." +fi +rm -f "$MAP" diff --git a/doc/themes/boost-modern/footer.html b/doc/themes/boost-modern/footer.html index 4d596232..9df4a3f7 100644 --- a/doc/themes/boost-modern/footer.html +++ b/doc/themes/boost-modern/footer.html @@ -39,5 +39,5 @@ {% endif %} - Built with MkDocs using a theme provided by Read the Docs | Powered by Wandbox + Built with MkDocs using a theme provided by Read the Docs | Powered by Wandbox diff --git a/doc/themes/boost-modern/js/cpp.js b/doc/themes/boost-modern/js/cpp.js index cb945a1d..c2007146 100644 --- a/doc/themes/boost-modern/js/cpp.js +++ b/doc/themes/boost-modern/js/cpp.js @@ -48,7 +48,7 @@ function compile_and_run(id) { document.getElementById("compile_and_run_" + id).firstChild.data = "Compiling..."; cpp_output[id].setValue("Compiling..."); var http = new XMLHttpRequest(); - http.open("POST", "http://melpon.org/wandbox/api/compile.json", true); + http.open("POST", "https://wandbox.org/api/compile.json", true); http.onreadystatechange = function(){ if (http.readyState == 4 && http.status == 200) { var output_json = JSON.parse(http.response); @@ -74,8 +74,11 @@ function compile_and_run(id) { , "codes" : [{ "file" : "boost/sml.hpp" , "code" : get_cpp_file("https://raw.githubusercontent.com/boost-ext/sml/master/include/boost/sml.hpp") + }, { + "file" : "boost/sml/utility/dispatch_table.hpp" + , "code" : get_cpp_file("https://raw.githubusercontent.com/boost-ext/sml/master/include/boost/sml/utility/dispatch_table.hpp") }] - , "options": "warning,cpp-pedantic-errors,optimize,boost-1.60,c++1y" + , "options": "warning,cpp-pedantic-errors,optimize,c++1y" , "compiler" : "clang-head" , "compiler-option-raw": "-I." + "\n" + "-fno-color-diagnostics" })); diff --git a/doc/tutorial.md b/doc/tutorial.md index 3ff4da4a..365cc67c 100644 --- a/doc/tutorial.md +++ b/doc/tutorial.md @@ -75,8 +75,11 @@ assert(string("idle") == "idle"_s.c_str()); ``` ![CPP(BTN)](Run_Events_Example|https://raw.githubusercontent.com/boost-ext/sml/master/example/events.cpp) +[▶ Run on Compiler Explorer](https://godbolt.org/z/s816cG5j8) ![CPP(BTN)](Run_States_Example|https://raw.githubusercontent.com/boost-ext/sml/master/example/states.cpp) +[▶ Run on Compiler Explorer](https://godbolt.org/z/c9asGjYTr) ![CPP(BTN)](Run_Composite_Example|https://raw.githubusercontent.com/boost-ext/sml/master/example/composite.cpp) +[▶ Run on Compiler Explorer](https://godbolt.org/z/5sYb9eGbe)   @@ -117,7 +120,18 @@ struct action4 { }; ``` +`SML` deduces the dependencies/event a guard or action wants from its `operator()`. +A *generic* lambda (`[](auto&){}`) or a concept-constrained one hides that, so wrap +it with [`make_action`](user_guide.md#make_action-utility) to name the types +explicitly: + +```cpp +struct Counter { int n = 0; }; +auto inc = sml::make_action([](auto& c) { c.n++; }); // generic lambda -> Counter& +``` + ![CPP(BTN)](Run_Actions_Guards_Example|https://raw.githubusercontent.com/boost-ext/sml/master/example/actions_guards.cpp) +[▶ Run on Compiler Explorer](https://godbolt.org/z/nehrdofPx)   @@ -188,7 +202,9 @@ make_transition_table( ``` ![CPP(BTN)](Run_Transition_Table_Example|https://raw.githubusercontent.com/boost-ext/sml/master/example/transitions.cpp) +[▶ Run on Compiler Explorer](https://godbolt.org/z/qr8qs78Ts) ![CPP(BTN)](Run_eUML_Emulation_Example|https://raw.githubusercontent.com/boost-ext/sml/master/example/euml_emulation.cpp) +[▶ Run on Compiler Explorer](https://godbolt.org/z/n93Y5Mner)   @@ -233,7 +249,9 @@ make_transition_table( ``` ![CPP(BTN)](Run_Orthogonal_Regions_Example|https://raw.githubusercontent.com/boost-ext/sml/master/example/orthogonal_regions.cpp) +[▶ Run on Compiler Explorer](https://godbolt.org/z/vqGbsrqM3) ![CPP(BTN)](Run_History_Example|https://raw.githubusercontent.com/boost-ext/sml/master/example/history.cpp) +[▶ Run on Compiler Explorer](https://godbolt.org/z/Pvz6Y7vd4)   @@ -297,7 +315,9 @@ sm.process_event(e1{}); ``` ![CPP(BTN)](Run_Hello_World_Example|https://raw.githubusercontent.com/boost-ext/sml/master/example/hello_world.cpp) +[▶ Run on Compiler Explorer](https://godbolt.org/z/dq85hW4ab) ![CPP(BTN)](Run_Dependency_Injection_Example|https://raw.githubusercontent.com/boost-ext/sml/master/example/dependency_injection.cpp) +[▶ Run on Compiler Explorer](https://godbolt.org/z/1Pv6EaG7n)   @@ -325,6 +345,31 @@ return make_transition_table( ); ``` +Events can also be deferred and re-evaluated later with `defer`, and a transition +can discard everything currently deferred with `clear_defer` (a no-op when nothing +is deferred). This is handy when leaving a composite/sub-state so that stale +deferred events do not survive re-entry. Both require a `defer_queue` policy. + +```cpp +using namespace sml; +return make_transition_table( + *"s1"_s + event / defer, // postpone my_event + state + event / clear_defer = "s1"_s // drop anything deferred on the way out +); +``` + +When events are queued asynchronously — e.g. an action stores a `back::process<>` +handle that fires after `process_event` has already returned — call `flush_queue()` +to drain whatever is still pending (it is a no-op when the queue is empty). This +requires a `process_queue` policy. + +```cpp +sml::sm> sm{...}; +sm.process_event(e1{}); // action stores a back::process handle +handle.push(e2{}); // async callback pushes e2 after process_event returned +sm.flush_queue(); // drain: e2 is processed now +``` + `SML` also provides a way to dispatch dynamically created events into the state machine. ```cpp @@ -340,8 +385,11 @@ dispatch_event(event, event.type); // will call sm.process(game_over{}); ``` ![CPP(BTN)](Run_Hello_World_Example|https://raw.githubusercontent.com/boost-ext/sml/master/example/hello_world.cpp) +[▶ Run on Compiler Explorer](https://godbolt.org/z/dq85hW4ab) ![CPP(BTN)](Run_Dispatch_Table_Example|https://raw.githubusercontent.com/boost-ext/sml/master/example/dispatch_table.cpp) +[▶ Run on Compiler Explorer](https://godbolt.org/z/j5bMnobY9) ![CPP(BTN)](Run_SDL2_Integration_Example|https://raw.githubusercontent.com/boost-ext/sml/master/example/sdl2.cpp) +[▶ Run on Compiler Explorer](https://godbolt.org/z/8heY58eE6)   @@ -409,6 +457,7 @@ make_transition_table( ``` ![CPP(BTN)](Run_Error_Handling_Example|https://raw.githubusercontent.com/boost-ext/sml/master/example/error_handling.cpp) +[▶ Run on Compiler Explorer](https://godbolt.org/z/cGd7TnM1G)   @@ -442,6 +491,7 @@ assert(sm.is(X)); ``` ![CPP(BTN)](Run_Testing_Example|https://raw.githubusercontent.com/boost-ext/sml/master/example/testing.cpp) +[▶ Run on Compiler Explorer](https://godbolt.org/z/oc73h5sY8)   @@ -483,7 +533,9 @@ sm.process_event(my_event{}); // will call logger appropriately ``` ![CPP(BTN)](Run_Logging_Example|https://raw.githubusercontent.com/boost-ext/sml/master/example/logging.cpp) +[▶ Run on Compiler Explorer](https://godbolt.org/z/Tec7jz36r) ![CPP(BTN)](Run_Plant_UML_Example|https://raw.githubusercontent.com/boost-ext/sml/master/example/plant_uml.cpp) +[▶ Run on Compiler Explorer](https://godbolt.org/z/8eT6aWsc9)   diff --git a/doc/user_guide.md b/doc/user_guide.md index 35a2cd58..ac754752 100644 --- a/doc/user_guide.md +++ b/doc/user_guide.md @@ -41,7 +41,8 @@ Requirements for transition. static_assert(transitional::value); } -![CPP(BTN)](Run_Transitional_Example|https://raw.githubusercontent.com/boost-ext/sml/master/example/errors/not_transitional.cpp) +![CPP(BTN)](Run_Transitional_Example|https://raw.githubusercontent.com/boost-ext/sml/master/test/ft/errors/not_transitional.cpp) +[▶ See the compile error on Compiler Explorer](https://godbolt.org/z/3aWdcTanT)   @@ -80,7 +81,8 @@ Requirements for the state machine. static_assert(configurable::value); -![CPP(BTN)](Run_Configurable_Example|https://raw.githubusercontent.com/boost-ext/sml/master/example/errors/not_configurable.cpp) +![CPP(BTN)](Run_Configurable_Example|https://raw.githubusercontent.com/boost-ext/sml/master/test/ft/errors/not_configurable.cpp) +[▶ See the compile error on Compiler Explorer](https://godbolt.org/z/zehzezMoe)   @@ -117,7 +119,8 @@ Requirements for action and guards. static_assert(callable::value); static_assert(callable::value); -![CPP(BTN)](Run_Callable_Example|https://raw.githubusercontent.com/boost-ext/sml/master/example/errors/not_callable.cpp) +![CPP(BTN)](Run_Callable_Example|https://raw.githubusercontent.com/boost-ext/sml/master/test/ft/errors/not_callable.cpp) +[▶ See the compile error on Compiler Explorer](https://godbolt.org/z/fb8K6q4WK)   @@ -163,8 +166,10 @@ Requirements for the dispatch table. static_assert(dispatchable::value); static_assert(dispatchable::value); -![CPP(BTN)](Run_Dispatchable_Example|https://raw.githubusercontent.com/boost-ext/sml/master/example/errors/not_dispatchable.cpp) +![CPP(BTN)](Run_Dispatchable_Example|https://raw.githubusercontent.com/boost-ext/sml/master/test/ft/errors/not_dispatchable.cpp) +[▶ See the compile error on Compiler Explorer](https://godbolt.org/z/984KoaP6x) ![CPP(BTN)](Run_SDL2_Integration_Example|https://raw.githubusercontent.com/boost-ext/sml/master/example/sdl2.cpp) +[▶ Run on Compiler Explorer](https://godbolt.org/z/8heY58eE6)   @@ -229,8 +234,11 @@ Represents a state machine state. auto terminate_state = X; ![CPP(BTN)](Run_States_Example|https://raw.githubusercontent.com/boost-ext/sml/master/example/states.cpp) +[▶ Run on Compiler Explorer](https://godbolt.org/z/c9asGjYTr) ![CPP(BTN)](Run_Composite_Example|https://raw.githubusercontent.com/boost-ext/sml/master/example/composite.cpp) +[▶ Run on Compiler Explorer](https://godbolt.org/z/5sYb9eGbe) ![CPP(BTN)](Run_Orthogonal_Regions_Example|https://raw.githubusercontent.com/boost-ext/sml/master/example/orthogonal_regions.cpp) +[▶ Run on Compiler Explorer](https://godbolt.org/z/vqGbsrqM3)   @@ -280,8 +288,10 @@ Represents a state machine event. auto my_int_event = event; -![CPP(BTN)](Run_Events_Example|https://raw.githubusercontent.com/boost-ext/sml/master/example/action_guards.cpp) +![CPP(BTN)](Run_Events_Example|https://raw.githubusercontent.com/boost-ext/sml/master/example/actions_guards.cpp) +[▶ Run on Compiler Explorer](https://godbolt.org/z/nehrdofPx) ![CPP(BTN)](Run_Error_Handling_Example|https://raw.githubusercontent.com/boost-ext/sml/master/example/error_handling.cpp) +[▶ Run on Compiler Explorer](https://godbolt.org/z/cGd7TnM1G)   @@ -328,6 +338,7 @@ Creates a transition table. }; ![CPP(BTN)](Run_Transition_Table_Example|https://raw.githubusercontent.com/boost-ext/sml/master/example/transitions.cpp) +[▶ Run on Compiler Explorer](https://godbolt.org/z/qr8qs78Ts)   @@ -362,6 +373,8 @@ Creates a State Machine. template // no requirements bool process_event(const TEvent&) + void flush_queue(); // drain events still pending in the process queue + template requires callable void visit_current_states(const TVisitor &) const noexcept(noexcept(visitor(state{}))); @@ -376,6 +389,7 @@ Creates a State Machine. | ---------- | ----------- | ----------- | ------- | | `TDeps...` | is_base_of dependencies | constructor | | | `process_event` | - | process event `TEvent` | returns true when handled, false otherwise | +| `flush_queue` | requires a `process_queue` / `defer_queue` policy | drain events still pending in the process queue (e.g. pushed from an async handler after `process_event` returned); no-op when empty | - | | `visit_current_states` | [callable](#callable-concept) | visit current states | - | | `is` | - | verify whether any of current states equals `TState` | true when any current state matches `TState`, false otherwise | | `is` | size of TStates... equals number of initial states | verify whether all current states match `TStates...` | true when all states match `TState...`, false otherwise | @@ -411,8 +425,11 @@ Creates a State Machine. sm.visit_current_states([](auto state) { std::cout << state.c_str() << std::endl; }); ![CPP(BTN)](Run_Hello_World_Example|https://raw.githubusercontent.com/boost-ext/sml/master/example/hello_world.cpp) +[▶ Run on Compiler Explorer](https://godbolt.org/z/dq85hW4ab) ![CPP(BTN)](Run_Dependency_Injection_Example|https://raw.githubusercontent.com/boost-ext/sml/master/example/dependency_injection.cpp) +[▶ Run on Compiler Explorer](https://godbolt.org/z/1Pv6EaG7n) ![CPP(BTN)](Run_eUML_Emulation_Example|https://raw.githubusercontent.com/boost-ext/sml/master/example/euml_emulation.cpp) +[▶ Run on Compiler Explorer](https://godbolt.org/z/n93Y5Mner)   @@ -432,11 +449,13 @@ Additional State Machine configurations. thread_safe logger + deps | Expression | Requirement | Description | Example | | ---------- | ----------- | ----------- | ------- | | `Lockable` | `lock/unlock` | Lockable type | `std::mutex`, `std::recursive_mutex` | | `Loggable` | `log_process_event/log_state_change/log_action/log_guard` | Loggable type | - | +| `deps` | - | force `Ts...` into the dependency pool even when no action/guard signature names them (needed when a dependency is only reached via a generic lambda using `sml::aux::get(deps)`) | `sml::sm> sm{dep};` | ***Example*** @@ -444,8 +463,10 @@ Additional State Machine configurations. sml::sm> sm; // logger policy sml::sm, sml::logger> sm; // thread safe and logger policy sml::sm, sml::thread_safe> sm; // thread safe and logger policy + sml::sm> sm{dep}; // explicit pool dependency for generic-lambda actions/guards ![CPP(BTN)](Run_Logging_Example|https://raw.githubusercontent.com/boost-ext/sml/master/example/logging.cpp) +[▶ Run on Compiler Explorer](https://godbolt.org/z/Tec7jz36r)   @@ -491,6 +512,57 @@ Creates a state machine with testing capabilities. sm.is(X); ![CPP(BTN)](Run_Testing_Example|https://raw.githubusercontent.com/boost-ext/sml/master/example/testing.cpp) +[▶ Run on Compiler Explorer](https://godbolt.org/z/oc73h5sY8) + +  + +--- + +###make_action [utility] + +***Header*** + + #include + +***Description*** + +Wraps a template/generic/constrained callable as an action or guard, naming its +dependency (and event) types explicitly via ``. SML normally deduces the +parameters a callable wants by inspecting its `operator()`; a generic lambda +(`[](auto&){}`) or a concept-constrained one hides that information, so the wrong +type may be deduced. `make_action(f)` supplies the concrete signature +`f` should be called with. + +***Synopsis*** + + template + constexpr auto make_action(F f) noexcept; + +***Semantics*** + + make_action(generic_callable); + make_action(generic_callable); + +***Example*** + + struct trigger {}; + struct Counter { int n = 0; }; + + auto table = [] { + using namespace sml; + return make_transition_table( + // generic lambda: name the dependency explicitly so the right type is passed + *"idle"_s + event / make_action([](auto& c) { c.n++; }) = X + ); + }; + +Also works for guards and for callables taking the event plus dependencies: + + *"idle"_s + event[make_action([](auto& c) { return c.n == 0; })] = X + *"idle"_s + event / make_action( + [](const auto& e, auto& c) { /* ... */ }) = X + +[▶ Run on Compiler Explorer](https://godbolt.org/z/Ge84qGM5z)   @@ -535,7 +607,9 @@ Creates a dispatch table to handle runtime events. dispatch_event(event, event.id); ![CPP(BTN)](Run_Dispatch_Table_Example|https://raw.githubusercontent.com/boost-ext/sml/master/example/dispatch_table.cpp) +[▶ Run on Compiler Explorer](https://godbolt.org/z/j5bMnobY9) ![CPP(BTN)](Run_SDL2_Integration_Example|https://raw.githubusercontent.com/boost-ext/sml/master/example/sdl2.cpp) +[▶ Run on Compiler Explorer](https://godbolt.org/z/8heY58eE6)   diff --git a/include/boost/sml.hpp b/include/boost/sml.hpp index 215a0723..bb8900de 100644 --- a/include/boost/sml.hpp +++ b/include/boost/sml.hpp @@ -12,12 +12,12 @@ #if defined(__ICCARM__) && __IAR_SYSTEMS_ICC__ < 8 #error "[Boost::ext].SML requires C++14 support (IAR C/C++ ARM 8.1+)" #endif -#define BOOST_SML_VERSION 1'1'13 +#define BOOST_SML_VERSION 1'2'0 #define BOOST_SML_NAMESPACE_BEGIN \ namespace boost { \ inline namespace ext { \ namespace sml { \ - inline namespace v1_1_13 { + inline namespace v1_2_0 { #define BOOST_SML_NAMESPACE_END \ } \ } \ @@ -989,16 +989,21 @@ auto get_type_name(const char *ptr, index_sequence) { } // namespace detail template const char *get_type_name() { +// NOTE: these offsets skip the function-signature prefix up to `T`. That prefix +// includes the inline namespace (e.g. v1_2_0) on compilers that print it +// (MSVC/GCC/IAR), so the offsets must be re-tuned whenever the namespace string +// length changes on a release. clang elides the inline namespace, so its offsets +// are length-independent. #if defined(_MSC_VER) && !defined(__clang__) - return detail::get_type_name(__FUNCSIG__, make_index_sequence{}); + return detail::get_type_name(__FUNCSIG__, make_index_sequence{}); #elif defined(__clang__) && (__clang_major__ >= 12) return detail::get_type_name(__PRETTY_FUNCTION__, make_index_sequence{}); #elif defined(__clang__) return detail::get_type_name(__PRETTY_FUNCTION__, make_index_sequence{}); #elif defined(__GNUC__) - return detail::get_type_name(__PRETTY_FUNCTION__, make_index_sequence{}); + return detail::get_type_name(__PRETTY_FUNCTION__, make_index_sequence{}); #elif defined(__ICCARM__) - return detail::get_type_name(__PRETTY_FUNCTION__, make_index_sequence{}); + return detail::get_type_name(__PRETTY_FUNCTION__, make_index_sequence{}); #endif } #if defined(__cpp_nontype_template_parameter_class) || \ @@ -1041,6 +1046,14 @@ template constexpr auto wrap(T callback) { return aux::zero_wrapper{callback}; } +// make_action(f) — adapt a callable whose parameter types SML cannot +// deduce (#629). SML normally reads the wanted dep/event types off a callable's +// operator(); a *generic* lambda ([](auto&){}) or a concept-constrained one +// (`[](HasFoo auto&){}`) exposes only a template, so function_traits has nothing +// concrete to read and picks the wrong type. action_wrap pins a fixed +// operator(TDeps...) in front of `f`, giving SML the explicit signature to +// inspect while still forwarding to the original callable. TDeps are listed in +// call order, e.g. make_action([](auto& e, auto& d){...}). template struct action_wrap { constexpr explicit action_wrap(F f) : f_(f) {} @@ -1748,6 +1761,12 @@ template struct logger : aux::pair> { using type = T; }; +// explicit_deps policy (#437) — force Ts into the dependency pool even +// when no action/guard *signature* names them. The pool is built from the dep +// types that appear in callable signatures; a dep reached only through a generic +// lambda via `aux::get(deps)` never appears in any signature, so it would +// be dropped from the pool and the static_cast to pool_type would fail to +// compile. sml::deps widens the pool to include these types explicitly. struct explicit_deps_policy__ {}; template struct explicit_deps : aux::pair> {}; @@ -2022,6 +2041,11 @@ struct sm_impl : aux::conditional_t>{}, events_t{})); return handled && queued_handled; } + // flush_queue — drain events left in the process queue (#456). process_event + // already drains its own queue before returning; this is for events pushed + // *after* it returned, e.g. from an async callback holding a back::process<> + // handle. Runs anonymous transitions to a fixpoint, then the queued events. + // No-op when the queue is empty; takes the thread_safe lock like process_event. template constexpr void flush_queue(TDeps &d, TSubs &subs) { const auto lock = thread_safety_.create_lock(); @@ -2323,6 +2347,8 @@ struct sm_impl : aux::conditional_t struct get_explicit_deps { using type = aux::type_list<>; @@ -2387,6 +2413,7 @@ class sm { constexpr bool process_event(const TEvent &event) { return aux::get>(sub_sms_).process_event(unexpected_event<_, TEvent>{event}, deps_, sub_sms_); } + // Public entry point: drain events queued after process_event returned (#456). constexpr void flush_queue() { aux::get>(sub_sms_).flush_queue(deps_, sub_sms_); } @@ -2814,6 +2841,10 @@ struct defer : action_base { } } }; +// clear_defer — the counterpart to `defer` (#643): drop every event currently +// held in the defer queue. Typical use is on the way out of a composite/sub-SM +// so events deferred during that scope do not resurface after re-entry. No-op +// when nothing is deferred; requires a defer_queue policy. struct clear_defer : action_base { template constexpr void operator()(const TEvent &, Tsm &sm, TDeps &, TSubs &) const { @@ -2834,6 +2865,7 @@ using defer_queue = back::policies::defer_queue; template