korken89 / smlang-rs

A State Machine Language DSL procedual macro for Rust
Apache License 2.0
203 stars 28 forks source link

Support on_entry and on_exit for states #60

Closed MartinBroers closed 5 months ago

MartinBroers commented 1 year ago

When I am implementing a state machine which represents a traffic light, I want to have repetitive actions when I enter, for example, state Green. I could solve that by using an action, but that action could be the same for multiple transitions. Let's say I have the following definition:

statemachine! {
    transitions: {
         *Red + Next / to_orangered = OrangeRed,
         OrangeRed + TimerEvent / to_green = Green,
         Green + Next / to_orange = Orange,
         Orange + TimerEvent / to_red = Red
     }
}

So, this works. Every transition allows me to have the correct lights on- and off. However, this results in code repetition I don't want. So, when I am entering the Green state, I want to enable the Green light. When I am exiting the Orange state, I want to disable the orange light.

So, if we could define something like

statemachine! {
    states: {
         Red, OrangeRed, Orange, Green
    }
    transitions: {
         *Red + Next / to_orange = OrangeRed,
         OrangeRed + TimerEvent / to_green = Green,
         Green + Next / to_orange = Orange,
         Orange + TimerEvent / to_red = Red
     }
}

And then


impl StateMachineContext for Context {
    // Guard1 has access to the data from Event1
    fn on_enter_red(&mut self) ->  {
        self.enable_red().unwrap() ; // Function CANNOT fail; that should be prevented by the guard...
    }

    fn on_exit_red(&mut self) ->  {
        self.disable_red().unwrap() ; // Function CANNOT fail; that should be prevented by the guard...
    }
    ...

Although I am really unsure about the notation. Now that I read boost-sml: https://boost-ext.github.io/sml/examples.html#states, this is what they do:


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

Then you'd get something like

statemachine! {
    transitions: {
         *Red + Next / to_orange = OrangeRed,
         Red + sml::on_entry / [] { /* inline function */ }
         Red + sml::on_exit / [] { self.disable_red(); }
         OrangeRed + TimerEvent / to_green = Green,
         Green + Next / to_orange = Orange,
         Orange + TimerEvent / to_red = Red
     }
}

Let me know your thoughts. Very curious about the notation how you guys think about it. I also want to help to implement it.

andresv commented 1 year ago

In the past I have used SMs with on_entry and on_exit so this is definitely something that I miss here.

MartinBroers commented 1 year ago

I have a working branch in https://github.com/MartinBroers/smlang-rs/tree/add-on-entry-on-exit, so if anyone wants to have a look at it already and have some early feedback, be most welcome :-)

pleepleus commented 1 year ago

@MartinBroers, what is the state of this? I may take a crack at it.

MartinBroers commented 1 year ago

I haven't worked on it since I published my own branch, which is working for me, but I think there's a few things to improve. I'll open an MR here so you have a base to start from and I do accept comments, remarks, improvement points and patches to get this in.

ryan-summers commented 5 months ago

Implemented in https://github.com/korken89/smlang-rs/pull/66