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

How to achieve shallow history behavior? #588

Open aplotskin opened 1 year ago

aplotskin commented 1 year ago

Expected Behavior

The docs show support for UML Shallow History as (✓) and UML Deep history as (~) [not sure what ~ is intended to indicate vs -].

Actual Behavior

Implementation with the (H) init tag on a state behaves like deep history, not shallow. Is there a way to induce shallow history behavior?

Steps to Reproduce the Problem

I have expanded the history example below:

//
// 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 <boost/sml.hpp>
#include <cassert>
#include <iostream>

namespace sml = boost::sml;

namespace {

// subsub does not have a history init, just a normal default init
struct subsub {
  auto operator()() const noexcept {
    using namespace sml;
    // clang-format off
    return make_transition_table(

      "s1"_s <= *"idle"_s + "e1"_e / [] { std::cout << "subsub:e1" << std::endl; },

    // verbose entries/exits
      "idle"_s + on_entry<_>     / []{std::cout << "entering subsub/idle" << std::endl;},
      "idle"_s + sml::on_exit<_> / []{std::cout << "exiting subsub/idle" << std::endl;},
      "s1"_s + on_entry<_>       / []{std::cout << "entering subsub/s1" << std::endl;},
      "s1"_s + sml::on_exit<_>   / []{std::cout << "exiting subsub/s1" << std::endl;}

    );
    // clang-format on
  }
};

struct sub {
  auto operator()() const noexcept {
    using namespace sml;
    // clang-format off
    return make_transition_table(

      "s1"_s        <= "idle"_s(H) + "e1"_e  / [] { std::cout << "sub:e1" << std::endl; },
      state<subsub> <= "s1"_s      + "e2"_e  / [] { std::cout << "sub:e2" << std::endl; },

    // verbose entries/exits
      "idle"_s + on_entry<_>          / []{std::cout << "entering sub/idle" << std::endl;},
      "idle"_s + sml::on_exit<_>      / []{std::cout << "exiting sub/idle" << std::endl;},
      "s1"_s + on_entry<_>            / []{std::cout << "entering sub/s1" << std::endl;},
      "s1"_s + sml::on_exit<_>        / []{std::cout << "exiting sub/s1" << std::endl;},  
      state<subsub> + on_entry<_>     / []{std::cout << "entering sub/subsub" << std::endl;},
      state<subsub> + sml::on_exit<_> / []{std::cout << "exiting sub/subsub" << std::endl;}
    );
    // clang-format on
  }
};

struct history {
  auto operator()() const noexcept {
    using namespace sml;
    // clang-format off
    return make_transition_table(
      state<sub> <= *"idle"_s  + "e1"_e / [] { std::cout << "history:e1" << std::endl; }
    , "s1"_s     <= state<sub> + "e3"_e / [] { std::cout << "history:e3" << std::endl; }
    , state<sub> <= "s1"_s     + "e4"_e / [] { std::cout << "history:e4 enter sub again" << std::endl; },

    // verbose entries/exits
    "idle"_s + on_entry<_>       / []{ std::cout << "history: entering idle>" << std::endl;},
    "idle"_s + sml::on_exit<_>   / []{ std::cout << "history: exiting idle" << std::endl;},
    "s1"_s + on_entry<_>         / []{ std::cout << "history: entering s1>" << std::endl;},
    "s1"_s + sml::on_exit<_>     / []{ std::cout << "history: exiting s1" << std::endl;},
    state<sub> + on_entry<_>     / []{ std::cout << "history: entering state<sub>" << std::endl;},
    state<sub> + sml::on_exit<_> / []{ std::cout << "history: exiting state<sub>" << std::endl;}
    );
    // clang-format on
  }
};
}  // namespace

int main() {
  sml::sm<history> sm;
  using namespace sml;
  std::cout << std::endl << ">>>>>>  " << "e1 processing" << std::endl; 
  sm.process_event("e1"_e());  // enter sub

  std::cout << std::endl << ">>>>>>  " << "e1 processing again" << std::endl; 
  sm.process_event("e1"_e());  // in sub

  std::cout << std::endl << ">>>>>>  " << "e3 processing" << std::endl;
  sm.process_event("e3"_e());  // exit sub

  std::cout << std::endl << ">>>>>>  " << "e4 processing" << std::endl;
  sm.process_event("e4"_e());  // enter sub again

  std::cout << std::endl << ">>>>>>  " << "e2 processing (activate subsub)" << std::endl;
  sm.process_event("e2"_e());  // in sub again now with subsub

  std::cout << std::endl << ">>>>>>  " << "e1 processing again (progress subsub from default)" << std::endl; 
  sm.process_event("e1"_e());  // move subsub

  std::cout << std::endl << ">>>>>>  " << "e3 processing" << std::endl;
  sm.process_event("e3"_e());  // exit sub

  std::cout << std::endl << ">>>>>>  " << "e4 processing (shallow history should now have subsub in idle)" << std::endl;
  sm.process_event("e4"_e());  // enter sub again

}

Output:

history: entering idle>

>>>>>>  e1 processing
history: exiting idle
history:e1
history: entering state<sub>
entering sub/idle

>>>>>>  e1 processing again
exiting sub/idle
sub:e1
entering sub/s1

>>>>>>  e3 processing
exiting sub/s1
history: exiting state<sub>
history:e3
history: entering s1>

>>>>>>  e4 processing
history: exiting s1
history:e4 enter sub again
history: entering state<sub>
entering sub/s1

>>>>>>  e2 processing (activate subsub)
exiting sub/s1
sub:e2
entering sub/subsub
entering subsub/idle

>>>>>>  e1 processing again (progress subsub from default)
exiting subsub/idle
subsub:e1
entering subsub/s1

>>>>>>  e3 processing
exiting subsub/s1
exiting sub/subsub
history: exiting state<sub>
history:e3
history: entering s1>

>>>>>>  e4 processing (shallow history should see subsub in idle)
history: exiting s1
history:e4 enter sub again
history: entering state<sub>
entering sub/subsub
entering subsub/s1
Alex0vSky commented 11 months ago

I think ~ means approximately.

(1) In mathematics, the tilde (~) stands for equivalence; for example, a ~ b means "a is equivalent to b" (not equal, but comparable). It also stands for approximation. Officially written as two tildes, one over the other, the single tilde has become acceptable; for example, ~100 means "approximately 100."