boost-ext / sml

C++14 State Machine library
https://boost-ext.github.io/sml
Boost Software License 1.0
1.14k stars 177 forks source link

Is there a simple way to say: transition from each state for specific event ? #328

Open lumpidu opened 4 years ago

lumpidu commented 4 years ago

How can I use a simple way to say: transition always to a specific state with a specific event, without having to specify this event for each state of the state machine ?

In my example, I am trying to transition to the "aborted"_s state from everywhere, if an event onAbort is sent.

I could rearrange the state machine to be hierarchic, but I want to avoid that.

So I have tried the following:

#include <boost/sml.hpp>
#include <cassert>
#include <iostream>

namespace sml = boost::sml;

namespace {
struct e1 {};
struct e2 {};
struct onAbort {};

struct states {
  auto operator()() const noexcept {
    using namespace sml;
    return make_transition_table(
      state<_> + event<onAbort> = "aborted"_s
      , "aborted"_s + sml::on_entry<_> / [] { std::cout << "aborted on entry" << std::endl; }
      , *"s1"_s + event<e1> = "s2"_s
      , "s1"_s + sml::on_exit<_> / [] { std::cout << "s1 on exit" << std::endl; }
      , "s2"_s + sml::on_entry<_> / [] { std::cout << "s2 on entry" << std::endl; }
      , "s2"_s + sml::on_exit<_> / [] { std::cout << "s2 on exit" << std::endl; }
      , "s2"_s + event<e2> = X
    );
  }
};
}  // namespace

int main() {
  sml::sm<states> sm;
  sm.process_event(e1{});
  sm.process_event(onAbort{});
  sm.process_event(e2{});
}

My expectation:

s1 on exit
s2 on entry
s2 on exit
aborted on entry

But the outcome is:

s1 on exit
s2 on entry
s2 on exit
Sulter commented 4 years ago

@lumpidu I don't think this is a thing in UML, so it's not a thing (and will never be?) in sml. You probably want to be using composite state: https://boost-experimental.github.io/sml/examples.html#composite

UML example of leaving any state in "Configuring" on EvConfig by using composite state:

image

GuiCodron commented 4 years ago

Like @lumpidu I think that composite state is the solution to your problem. In your case, you sm would become:

#include <boost/sml.hpp>
#include <cassert>
#include <iostream>

namespace sml = boost::sml;

namespace {
struct e1 {};
struct e2 {};
struct onAbort {};

struct states {
  auto operator()() const noexcept {
    using namespace sml;
    return make_transition_table(
      *"s1"_s + event<e1> = "s2"_s
      , "s1"_s + sml::on_exit<_> / [] { std::cout << "s1 on exit" << std::endl; }
      , "s2"_s + sml::on_entry<_> / [] { std::cout << "s2 on entry" << std::endl; }
      , "s2"_s + sml::on_exit<_> / [] { std::cout << "s2 on exit" << std::endl; }
      , "s2"_s + event<e2> = X
    );
  }
};
struct error_mgt {

  auto operator()() const noexcept {
    using namespace sml;
    return make_transition_table(
      *state<states> + event<onAbort> = "aborted"_s
      , "aborted"_s + sml::on_entry<_> / [] { std::cout << "aborted on entry" << std::endl; }
    );
  }
};
}  // namespace

int main() {
  sml::sm<error_mgt> sm;
  sm.process_event(e1{});
  sm.process_event(onAbort{});
  sm.process_event(e2{});
}