From b89e163c2d57f25cb61374a6b0f648ad95e6c8ca Mon Sep 17 00:00:00 2001 From: gabewillen Date: Mon, 23 Feb 2026 17:15:12 -0600 Subject: [PATCH 1/2] Fix issue #171 wildcard event double invocation --- include/boost/sml.hpp | 2 ++ test/ft/CMakeLists.txt | 3 +++ test/ft/issue_171.cpp | 44 ++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 49 insertions(+) create mode 100644 test/ft/issue_171.cpp diff --git a/include/boost/sml.hpp b/include/boost/sml.hpp index 7065e66e..ddb0e191 100644 --- a/include/boost/sml.hpp +++ b/include/boost/sml.hpp @@ -1477,6 +1477,8 @@ struct get_event_mapping_impl_helper, TMappings> template struct get_event_mapping_impl_helper, TMappings> : decltype(get_event_mapping_impl>((TMappings *)0)) {}; +template +struct get_event_mapping_impl_helper : decltype(get_event_mapping_impl((TMappings *)0)) {}; template using get_event_mapping_t = get_event_mapping_impl_helper; } // namespace back diff --git a/test/ft/CMakeLists.txt b/test/ft/CMakeLists.txt index 6c1750b9..feaf4d43 100644 --- a/test/ft/CMakeLists.txt +++ b/test/ft/CMakeLists.txt @@ -48,6 +48,9 @@ add_test(test_fwd test_fwd) add_executable(test_history history.cpp) add_test(test_history test_history) +add_executable(test_issue_171 issue_171.cpp) +add_test(test_issue_171 test_issue_171) + add_executable(test_issue_253 issue_253.cpp) add_test(test_issue_253 test_issue_253) diff --git a/test/ft/issue_171.cpp b/test/ft/issue_171.cpp new file mode 100644 index 00000000..52abe7f9 --- /dev/null +++ b/test/ft/issue_171.cpp @@ -0,0 +1,44 @@ +// +// Copyright (c) 2016-2020 Kris Jusiak (kris at jusiak dot net) +// +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) +// +#include + +namespace sml = boost::sml; + +test issue_171_event_any_is_not_fired_twice = [] { + struct e1 {}; + + struct issue_171_counters { + int init_calls = 0; + int wildcard_calls = 0; + }; + + struct issue_171_transitions { + auto operator()() const noexcept { + using namespace sml; + const auto issue_171_idle = sml::state; + const auto issue_171_s1 = sml::state; + const auto issue_171_s2 = sml::state; + + // clang-format off + return make_transition_table( + *issue_171_idle / [](issue_171_counters& counters) { ++counters.init_calls; } = issue_171_s1 + , issue_171_s1 + event<_> / [](issue_171_counters& counters) { ++counters.wildcard_calls; } + , issue_171_s2 + event / [] {} + ); + // clang-format on + } + }; + + issue_171_counters counters{}; + sml::sm sm{counters}; + + sm.process_event(e1{}); + + expect(1 == counters.init_calls); + expect(1 == counters.wildcard_calls); +}; From c7527fb8427f890b9b636bc475511922730f7c36 Mon Sep 17 00:00:00 2001 From: gabewillen Date: Tue, 24 Feb 2026 15:36:20 -0600 Subject: [PATCH 2/2] Add completion post-event transitions --- include/boost/sml.hpp | 48 +++++++++++++----- test/ft/actions_process_n_defer.cpp | 76 ++++++++++++++++++++++++++++- test/ft/transitions.cpp | 24 +++++++++ 3 files changed, 136 insertions(+), 12 deletions(-) diff --git a/include/boost/sml.hpp b/include/boost/sml.hpp index ddb0e191..0472b367 100644 --- a/include/boost/sml.hpp +++ b/include/boost/sml.hpp @@ -1058,6 +1058,10 @@ struct internal_event { struct anonymous : internal_event { constexpr static auto c_str() { return "anonymous"; } }; +template +struct completion : internal_event { + constexpr static auto c_str() { return "completion"; } +}; template struct on_entry : internal_event, entry_exit { constexpr static auto c_str() { return "on_entry"; } @@ -1477,6 +1481,9 @@ struct get_event_mapping_impl_helper, TMappings> template struct get_event_mapping_impl_helper, TMappings> : decltype(get_event_mapping_impl>((TMappings *)0)) {}; +template +struct get_event_mapping_impl_helper, TMappings> + : decltype(get_event_mapping_impl>((TMappings *)0)) {}; template struct get_event_mapping_impl_helper : decltype(get_event_mapping_impl((TMappings *)0)) {}; template @@ -1822,18 +1829,41 @@ struct sm_impl : aux::conditional_t>(event, deps, subs); bool queued_handled = true; do { do { - while (process_internal_events(anonymous{}, deps, subs)) { - } changed = (old != current_state_[0]); old = current_state_[0]; } while (process_defer_events(deps, subs, changed, aux::type_wrapper>{}, events_t{})); } while (process_queued_events(deps, subs, queued_handled, aux::type_wrapper>{}, events_t{})); return handled && queued_handled; } + template + constexpr bool process_completion_event(TDeps &deps, TSubs &subs, aux::true_type) { + return process_internal_events(completion{}, deps, subs); + } + template + constexpr bool process_completion_event(TDeps &, TSubs &, aux::false_type) { + return false; + } + template + constexpr bool process_post_event_step(TDeps &deps, TSubs &subs) { + return process_completion_event( + deps, subs, typename aux::is_base_of, events_ids_t>::type{}) || + process_internal_events(anonymous{}, deps, subs); + } + template + constexpr void process_post_events(TDeps &deps, TSubs &subs) { + while (process_post_event_step(deps, subs)) { + } + } + template + constexpr bool process_event_and_post_events(const TEvent &event, TDeps &deps, TSubs &subs) { + const bool handled = process_internal_events(event, deps, subs); + process_post_events(deps, subs); + return handled; + } constexpr void initialize(const aux::type_list<> &) {} template constexpr void initialize(const aux::type_list &) { @@ -2007,7 +2037,7 @@ struct sm_impl : aux::conditional_t constexpr bool process_event_no_defer(TDeps &deps, TSubs &subs, const void *data) { const auto &event = *static_cast(data); - bool handled = process_internal_events(event, deps, subs); + bool handled = process_event_and_post_events>(event, deps, subs); if (handled && defer_again_) { ++defer_it_; } else { @@ -2048,13 +2078,7 @@ struct sm_impl : aux::conditional_t constexpr bool process_event_no_queue(TDeps &deps, TSubs &subs, const void *data) { const auto &event = *static_cast(data); - policies::log_process_event(aux::type_wrapper{}, deps, event); -#if BOOST_SML_DISABLE_EXCEPTIONS - return process_event_impl>(event, deps, subs, states_t{}, - aux::make_index_sequence{}); -#else - return process_event_except_impl>(event, deps, subs, has_exceptions{}); -#endif + return process_event_and_post_events>(event, deps, subs); } template bool process_queued_events(TDeps &deps, TSubs &subs, bool &queued_handled, const aux::type_wrapper &, const aux::type_list &) { @@ -3204,6 +3228,8 @@ template front::event> unexpected_event __BOOST_SML_VT_INIT; template front::event> exception __BOOST_SML_VT_INIT; +template +using completion = back::completion; using anonymous = back::anonymous; using initial = back::initial; #if !(defined(_MSC_VER) && !defined(__clang__)) diff --git a/test/ft/actions_process_n_defer.cpp b/test/ft/actions_process_n_defer.cpp index 53cf62f5..f79fdb46 100644 --- a/test/ft/actions_process_n_defer.cpp +++ b/test/ft/actions_process_n_defer.cpp @@ -118,6 +118,80 @@ test process_n_defer_again = [] { expect(calls == "|s3_entry|e1|e1|e1"); }; +test process_queue_runs_completion_for_popped_event_type = [] { + struct trigger {}; + struct queued1 {}; + struct queued2 {}; + struct q0 {}; + struct q1 {}; + struct q2 {}; + struct q3 {}; + struct done {}; + struct wrong {}; + + struct c { + auto operator()() const { + using namespace sml; + auto q0_state = state; + auto q1_state = state; + auto q2_state = state; + auto q3_state = state; + auto done_state = state; + auto wrong_state = state; + // clang-format off + return make_transition_table( + *q0_state + event / (process(queued1{}), process(queued2{})) = q1_state + , q1_state + event = q2_state + , q2_state + event> = q3_state + , q2_state + event = wrong_state + , q3_state + event = done_state + ); + // clang-format on + } + }; + + sml::sm> sm{}; + expect(sm.process_event(trigger{})); + expect(sm.is(sml::state)); + expect(!sm.is(sml::state)); +}; + +test defer_queue_runs_completion_for_popped_event_type = [] { + struct deferred {}; + struct release {}; + struct d0 {}; + struct d1 {}; + struct d2 {}; + struct done {}; + struct wrong {}; + + struct c { + auto operator()() const { + using namespace sml; + auto d0_state = state; + auto d1_state = state; + auto d2_state = state; + auto done_state = state; + auto wrong_state = state; + // clang-format off + return make_transition_table( + *d0_state + event / defer + , d0_state + event = d1_state + , d1_state + event = d2_state + , d2_state + event> = done_state + , d2_state + event> = wrong_state + ); + // clang-format on + } + }; + + sml::sm, sml::defer_queue> sm{}; + expect(sm.process_event(deferred{})); + expect(sm.process_event(release{})); + expect(sm.is(sml::state)); + expect(!sm.is(sml::state)); +}; + template using MinimalStaticDeque10 = MinimalStaticDeque; @@ -147,4 +221,4 @@ test mix_process_n_defer_at_init_static_queue = [] { sml::sm, sml::defer_queue> sm{}; expect(sm.is(sml::X)); -}; \ No newline at end of file +}; diff --git a/test/ft/transitions.cpp b/test/ft/transitions.cpp index 47c30dcf..b8996ed8 100644 --- a/test/ft/transitions.cpp +++ b/test/ft/transitions.cpp @@ -88,6 +88,30 @@ test anonymous_transition = [] { expect(static_cast(sm).a_called); }; +test completion_transition_runs_before_anonymous = [] { + struct c { + auto operator()() noexcept { + using namespace sml; + // clang-format off + return make_transition_table( + *idle + event = s1 + ,s1 + event> / [this] { calls += "completion|"; } = s2 + ,s1 / [this] { calls += "anonymous_s1|"; } = s3 + ,s2 / [this] { calls += "anonymous_s2|"; } = s4 + ); + // clang-format on + } + + std::string calls{}; + }; + + sml::sm sm{}; + expect(sm.process_event(e1{})); + expect(sm.is(s4)); + expect(!sm.is(s3)); + expect(static_cast(sm).calls == "completion|anonymous_s2|"); +}; + test subsequent_anonymous_transitions = [] { struct c { auto operator()() noexcept {