boost-ext / sml

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

Problems with reentering a terminated substate. #440

Open lukashausperger opened 3 years ago

lukashausperger commented 3 years ago

Hey there!

We encountered the following issue:

Expected Behavior

Having a look at the following code snippet, we expect that the SubSubState is at Leave3 after trying to enter it for the second time.

Actual Behavior

When we try to reenter SubSubState in SubState for the second time (after leaving it), it stucks at the termination state X.

Steps to Reproduce the Problem

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

namespace sml = boost::sml;

namespace {
struct event_1 {};
struct event_2 {};
struct event_3 {};
struct event_4 {};

struct Leave1 {};
struct Leave2 {};
struct Leave3 {};

struct SubSubState {
  auto operator()() const noexcept {
    using namespace sml;
    return make_transition_table(*state<Leave3> + event<event_4> / process(event_3{}) = X);
  }
};

struct SubState {
  auto operator()() const noexcept {
    using namespace sml;
    return make_transition_table(*state<SubSubState> + event<event_3> / process(event_2{}) = X);
  }
};

struct StateMachine {
  auto operator()() const noexcept {
    using namespace sml;
    return make_transition_table(*state<Leave1> + event<event_1> = state<SubState>,
                                 state<SubState> + event<event_2> = state<Leave1>);
  }
};
}  // namespace

int main(int argv, char** argc) {
  sml::sm<StateMachine, sml::defer_queue<std::deque>, sml::process_queue<std::queue>> stm;
  using namespace sml;
  stm.process_event(event_1{});
  assert(stm.is<decltype(state<SubState>)>(state<SubSubState>));
  assert(stm.is<decltype(state<SubSubState>)>(state<Leave3>));
  stm.process_event(event_4{});
  stm.process_event(event_1{});
  assert(stm.is<decltype(state<SubState>)>(state<SubSubState>));
  assert(stm.is<decltype(state<SubSubState>)>(state<Leave3>));
}

This will output:

int main(int, char**): Assertion `stm.is<decltype(state<SubSubState>)>(state<Leave3>)' failed.

So the last assertion fails.

Specifications

Version: master OS: Ubuntu 18.04 Compiler: gcc-10, g++-10

Acutally we found the following workaround which solves this issue: When we change struct SubState{...} to

struct SubState {
  auto operator()() const noexcept {
    using namespace sml;
    return make_transition_table(*state<Leave2> + sml::on_entry<_> / process(event_5{}),
                                 state<Leave2> + event<event_5> = state<SubSubState>,
                                 state<SubSubState> + event<event_3> / process(event_2{}) = X);
  }
};

the last assertion does not fail anymore and SubSubState is a Leave_3 which is the desired behavior.

The question is why isn't it working without this workaround.

Thanks for your support.

SchmadenSchmuki commented 5 months ago

The same here (using the same workaround). Took me some additional time to understand it, maybe you already know this but my explanation is:

I think it's a bug or at least undesireable because this way SubState behaves differently between the first and the following times it's entered.

Until now I only used SML and didn't look into the source code, so I have no idea how difficult it would be to fix this. In my opinion fixing it would be an improvement because to me this was unexpected behaviour and took some time to figure out. Also eliminating the workaround would simplify my state machines.