Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Entry/exit actions on composite states? #43

Closed
Ulenspiegel opened this issue Jun 2, 2016 · 7 comments
Closed

Entry/exit actions on composite states? #43

Ulenspiegel opened this issue Jun 2, 2016 · 7 comments

Comments

@Ulenspiegel
Copy link

Is there a way to assign entry/exit actions to composite states? Maybe I'm not looking hard enough, but the following code can't be compiled:

#include <boost/msm-lite.hpp>

namespace msm = boost::msm::lite;

using msm::state;
using msm::event;
using msm::sm;
using msm::make_transition_table;

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

state<class LeafState1_1> ls1_1;
state<class LeafState1_2> ls1_2;

struct SubState1 {
  SubState1() {}
  auto configure() const noexcept {
    return make_transition_table(
        *ls1_1 + event<e1> = ls1_2,
        ls1_2 + event<e2> = ls1_1
    );
  }
};

state<class LeafState2_1> ls2_1;
state<class LeafState2_2> ls2_2;

struct SubState2 {
  SubState2() {}
  auto configure() const noexcept {
    return make_transition_table(
        *ls2_1 + event<e1> = ls2_2,
        ls2_2 + event<e2> = ls2_1
    );
  }
};

state<sm<SubState1>> ss1;
state<sm<SubState2>> ss2;

struct TopState {
  TopState() {}
  auto configure() const noexcept {
    using namespace msm;
    return make_transition_table(
        *ss1 + event<e3> = ss2,
        ss2 + event<e4> = ss1,
        ss1 + on_entry / [] { /* ... */ }  // Compilation error.
    );
  }
};

int main(int argc, char** argv) {
  SubState1 ss1_;
  sm<SubState1> ssm1_{ss1_};
  SubState2 ss2_;
  sm<SubState2> ssm2_{ss2_};
  TopState ts_;
  sm<TopState> m{ts_, ssm1_, ssm2_};

  m.process_event(e1{});
  m.process_event(e2{});

  m.process_event(e3{});

  m.process_event(e1{});
  m.process_event(e2{});

  m.process_event(e4{});
  return 0;
}

Is it implemented? As I understand it, transition between composite states should start with exit action of current leaf state, traverse upwards to enclosing state of transition while successively calling exit actions of composite states as they're being left, and then, similar way, successive call entry actions of state hierarchy it enters down to the final destination leaf/leaves. (In addition, I think leaf state's on_entry/on_exit aren't called when transitioning to/from composite state, but that's separate concern.)

P.S.: I'd gladly assign the label 'question' to the issue, but it appears I can't assign labels.

@krzysztof-jusiak
Copy link
Collaborator

Thanks @Ulenspiegel for reporting this issue. Your investigation is right, it was a bug, which should be fixed now by krzysztof-jusiak/msm-lite@8bb2bc9. The problem was that update_current_state call was ambigious as both overload,s for different types of states and different types of states when one was a state machine too, were equally good. Anyway, it should be fixed now.
I will wait for your approval with closing this issue.

I will also verify your second concern with on_entry/on_exit not being called when transitioning to/from composite state.

@Ulenspiegel
Copy link
Author

Yup, compiles and runs just fine now. The issue doesn't seem to be completely fixed, though: it appears that composite state's on_entry/on_exit are not invoked any state below it in the transition has entry/exit handler:

Code with on_entry/on_exit defined on ls* leaf states.

#include <boost/msm-lite.hpp>
#include <cstdio>

namespace msm = boost::msm::lite;

using msm::state;
using msm::event;
using msm::sm;
using msm::make_transition_table;

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

state<class LeafState1_1> ls1_1;
state<class LeafState1_2> ls1_2;

struct SubState1 {
  SubState1() {}
  auto configure() const noexcept {
    using namespace msm;
    using msm::on_exit;
    return make_transition_table(
        *ls1_1 + event<e1> = ls1_2,
        ls1_2 + event<e2> = ls1_1,

        ls1_1 + on_entry / [] { printf("Entering ls1_1\n"); },
        ls1_1 + on_exit / [] { printf("Leaving ls1_1\n"); },
        ls1_2 + on_entry / [] { printf("Entering ls1_2\n"); },
        ls1_2 + on_exit / [] { printf("Leaving ls1_2\n"); }
    );
  }
};

state<class LeafState2_1> ls2_1;
state<class LeafState2_2> ls2_2;

struct SubState2 {
  SubState2() {}
  auto configure() const noexcept {
    using namespace msm;
    using msm::on_exit;
    return make_transition_table(
        *ls2_1 + event<e1> = ls2_2,
        ls2_2 + event<e2> = ls2_1,

        ls2_1 + on_entry / [] { printf("Entering ls2_1\n"); },
        ls2_1 + on_exit / [] { printf("Leaving ls2_1\n"); },
        ls2_2 + on_entry / [] { printf("Entering ls2_2\n"); },
        ls2_2 + on_exit / [] { printf("Leaving ls2_2\n"); }
    );
  }
};

state<sm<SubState1>> ss1;
state<sm<SubState2>> ss2;

struct TopState {
  TopState() {}
  auto configure() const noexcept {
    using namespace msm;
    using msm::on_exit;
    return make_transition_table(
        *ss1 + event<e3> = ss2,
        ss2 + event<e4> = ss1,

        ss1 + on_entry / [] { printf("Entering ss1\n"); },
        ss1 + on_exit / [] { printf("Leaving ss1\n"); },
        ss2 + on_entry / [] { printf("Entering ss2\n"); },
        ss2 + on_exit / [] { printf("Leaving ss2\n"); }
    );
  }
};

int main(int argc, char** argv) {
  SubState1 ss1_;
  sm<SubState1> ssm1_{ss1_};
  SubState2 ss2_;
  sm<SubState2> ssm2_{ss2_};
  TopState ts_;
  sm<TopState> m{ts_, ssm1_, ssm2_};
  m.process_event(e3{});
  m.process_event(e4{});
  return 0;
}

outputs

Leaving ls1_1
Entering ls2_1
Leaving ls2_1
Entering ls1_1

instead of proper

Leaving ls1_1
Leaving ss1
Entering ss2
Entering ls2_1
Leaving ls2_1
Leaving ss2
Entering ss1
Entering ls1_1

And if leaf states' entry/exit handlers are removed, they're executed on composite states. It appears that on_{entry|exit} is implemented just like any regular event, and if it's caught by nested state, it's not propagated upwards. In this case it doesn't give proper entry/exit action order when switching between composites.

@krzysztof-jusiak
Copy link
Collaborator

Thanks @Ulenspiegel, you are right, on_entry/on_exit wasn't handled properly when combined with sub state machine. It should be fixed by krzysztof-jusiak/msm-lite@e4f29ee. Right now your example should print

Leaving ls1_1
Leaving ss1
Entering ls2_1
Entering ss2
Leaving ls2_1
Leaving ss2
Entering ls1_1
Entering ss1

The order of entering sub state machine is reveresed though.

Entering ls2_1
Entering ss2

I'm not sure whether it should be like that or the way you have specified it. I tried to look for it in the UML spec, but I couldn't find an definite answer?

@Ulenspiegel
Copy link
Author

That's just fantastic; thanks! I think the fix already covers most use cases, provided one's careful about ordering.

As for order of entry/exit actions, I'm looking at UML state machine article in wiki, last paragraph in "Entry and exit actions" chapter:

...order is analogous to the order in which class constructors are invoked. Construction of a class always starts at the very root of the class hierarchy and follows through all inheritance levels down to the class being instantiated. The execution of exit actions, which corresponds to destructor invocation, proceeds in the exact reverse order (bottom-up).

In UML 2.5 spec, I think it's implied in chapter 14.2.3.9.6 "Transition execution sequence" — states are exited from bottom up towards lowest common ancestor, and then downwards to the target state, as on the illustration. I think the part "...execution of Behaviors follows the rules of State entry (or composite State entry) described earlier" implies that external transitions trigger entry/exit events the usual way, when state is exited/entered (14.2.3.4.3). Take a closer look and double check, just in case I misread that section.

I think it's also the least surprise way — logically, nested state inherits behaviour of composite state, so in a way, nested state has "is-a" relation to its enclosing state. So, entry action of composite state must enforce invariants that should always hold true when any nested state is entered.

@krzysztof-jusiak
Copy link
Collaborator

Code with on_entry/on_exit defined on ls* leaf states.

#include <cstdio>
#include <boost/msm-lite.hpp>

namespace msm = boost::msm::lite;

using msm::state;
using msm::event;
using msm::on_entry;
using msm::on_exit;
using msm::sm;
using msm::make_transition_table;

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

auto ls1_1 =  state<class LeafState1_1> ;
auto ls1_2 = state<class LeafState1_2> ;

struct SubState1 {
  auto operator()() const noexcept {
    return make_transition_table(
        *ls1_1 + event<e1> = ls1_2,
        ls1_2 + event<e2> = ls1_1,
        ls1_1 + on_entry / [] { printf("Entering ls1_1\n"); },
        ls1_1 + on_exit / [] { printf("Leaving ls1_1\n"); },
        ls1_2 + on_entry / [] { printf("Entering ls1_2\n"); },
        ls1_2 + on_exit / [] { printf("Leaving ls1_2\n"); }
    );
  }
};

auto ls2_1 = state<class LeafState2_1> ;
auto ls2_2 = state<class LeafState2_2> ;

struct SubState2 {
  auto operator()() const noexcept {
    return make_transition_table(
        *ls2_1 + event<e1> = ls2_2,
        ls2_2 + event<e2> = ls2_1,

        ls2_1 + on_entry / [] { printf("Entering ls2_1\n"); },
        ls2_1 + on_exit / [] { printf("Leaving ls2_1\n"); },
        ls2_2 + on_entry / [] { printf("Entering ls2_2\n"); },
        ls2_2 + on_exit / [] { printf("Leaving ls2_2\n"); }
    );
  }
};

auto ss1 = state<sm<SubState1>> ;
auto ss2 = state<sm<SubState2>>;

struct TopState {
  auto operator()() const noexcept {
    using namespace msm;
    return make_transition_table(
        *ss1 + event<e3> = ss2,
        ss2 + event<e4> = ss1,
        ss1 + on_entry / [] { printf("Entering ss1\n"); },
        ss1 + on_exit / [] { printf("Leaving ss1\n"); },
        ss2 + on_entry / [] { printf("Entering ss2\n"); },
        ss2 + on_exit / [] { printf("Leaving ss2\n"); }
    );
  }
};

int main(int argc, char** argv) {
  sm<TopState> m;

  m.process_event(e1{});
  m.process_event(e2{});

  m.process_event(e3{});

  m.process_event(e1{});
  m.process_event(e2{});

  m.process_event(e4{});
  return 0;
}

is printing...

Entering ls1_1
Entering ss1
Leaving ls1_1
Entering ls1_2
Leaving ls1_2
Entering ls1_1
Leaving ls1_1
Leaving ss1
Entering ls2_1
Entering ss2
Leaving ls2_1
Entering ls2_2
Leaving ls2_2
Entering ls2_1
Leaving ls2_1
Leaving ss2
Entering ls1_1
Entering ss1

Closing as #50 is fixed already.

@Ulenspiegel
Copy link
Author

Hm-m... Is it right, though? I think ordering of entry is still broken - leaf states ("Entering ls1_1" on the line before last, for instance) are activated before their parent states ("Entering ss1" on the last line).

@krzysztof-jusiak
Copy link
Collaborator

Thanks @Ulenspiegel for pointing that out. It should be fixed by krzysztof-jusiak/msm-lite@4800b8a.

order of entry/exit actions:

  • on_entry -> top sm, sub sm
  • on_exit -> sub sm, top sm
Entering ss1
Entering ls1_1
Leaving ls1_1
Entering ls1_2
Leaving ls1_2
Entering ls1_1
Leaving ls1_1
Leaving ss1
Entering ss2
Entering ls2_1
Leaving ls2_1
Entering ls2_2
Leaving ls2_2
Entering ls2_1
Leaving ls2_1
Leaving ss2
Entering ss1
Entering ls1_1

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants