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

Jumping out of sub-machine not working correctly #400

Open gischthefish opened 3 years ago

gischthefish commented 3 years ago

The example of issue #283 (Jump out from substate) only works when using front::actions::process, but not with sml::back::process. #302 (Process events to/from sub/sibling machines) should have solved the problem when exiting sub-machines.

front::actions::process doesn't help, I need to be able to choose the event getting fired.

Following the example of #283 using sml::back::process (not working):

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

namespace sml = boost::sml;

struct e1 {};
struct e2 {};

auto process_e2 = [](sml::back::process<e2> processEvent) {
    std::cout << "in sub" << std::endl;
    processEvent( e2{} );
};

struct sub {
    auto operator()() {
        using namespace sml;

        return make_transition_table(
                *"start"_s + event<e1> / process_e2
        );
    }
};

struct top {
    auto operator()() {
        using namespace sml;

        return make_transition_table(
                *state<sub> + event<e2> / [] { std::cout << "jumped out from sub-state" << std::endl; } = X
        );
    }
};

int main() {
    sml::sm<top, sml::process_queue<std::queue>> sm{};
    sm.process_event(e1{});
    assert(sm.is(sml::X));
}

See the following compile error: error.txt

Furthermore issue #121 (Submachine terminate event should be actioned in outer state) seems not to be fixed yet. #302 (Process events to/from sub/sibling machines) should have fixed #121. But the event that terminates the sub-machine is not being propagated to the parent sm.

The following test should work:

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

namespace sml = boost::sml;

namespace {
struct e1 {};
struct e2 {};
struct e3 {};
struct e4 {};

struct sub {
    auto operator()() const noexcept {
        using namespace sml;

        return make_transition_table(
                *"idle"_s   + event<e3>         / [] { std::cout << "in sub sm" << std::endl; }     = "s1"_s,
                "s1"_s      + event<e4>         / [] { std::cout << "finish sub sm" << std::endl; } = X
      );
  }
};

struct composite {
    auto operator()() const noexcept {
        using namespace sml;

        return make_transition_table(
                *"idle"_s   + event<e1>                                                                 = "s1"_s,
                "s1"_s      + event<e2>         / [] { std::cout << "enter sub sm" << std::endl; }      = state<sub>,
                state<sub>  + event<e4>         / [] { std::cout << "exit sub sm" << std::endl; }       = X
        );
    }
};

}  // namespace

int main() {
    sml::sm<composite> sm;

    using namespace sml;
    assert(sm.is("idle"_s));
    assert(sm.is<decltype(state<sub>)>("idle"_s));

    sm.process_event(e1{});
    assert(sm.is("s1"_s));
    assert(sm.is<decltype(state<sub>)>("idle"_s));

    sm.process_event(e2{});  // enter sub sm
    assert(sm.is(state<sub>));
    assert(sm.is<decltype(state<sub>)>("idle"_s));

    sm.process_event(e3{});  // in sub sm
    assert(sm.is(state<sub>));
    assert(sm.is<decltype(state<sub>)>("s1"_s));

    sm.process_event(e4{});  // finish sub sm and propagate "exit event" to parent sm to finish parent too.
    assert(sm.is(X));
    assert(sm.is<decltype(state<sub>)>(X));
}

Propagating the event that terminated the sub-machine to the parent is a very essential feature, without that sub-machines are quite useless as the parent does not know what the sub-machine was doing, i.e. the sub-machine needs some means to tell the parent the result of the "computation".

Specifications

juehallier commented 2 years ago

I also have stumbled around this point for some hours, but indeed you can send events from the inner sm to an outer sm. Please see a working example in the test code in the lambda function "test process_event_sent_from_substate" here. Important fact: you'll have to add the policy sml::process_queue when you initialize the state machine.