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

Support on_entry and on_exit for states #66

Merged
merged 6 commits into from
Jun 30, 2024

Conversation

MartinBroers
Copy link

Issue #60 describes what I needed. I may have sprinkled a few Debug statements here and there which may not have been required, but I am open to suggestions to remove them.

I am opening this MR since @pleepleus asked to have a go at it. I don't want him to start from scratch.

Copy link
Collaborator

@ryan-summers ryan-summers left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Some general feedback is below. I don't know the state of this or if the intent is to merge it as-is or if it's just inteded to be a starting point.

If this is just a reference implementation for someone else to pick up, can we mark it as a draft? Otherwise, I'm not sure if I should be reviewing or not.

@@ -356,6 +370,10 @@ pub fn generate_code(sm: &ParsedStateMachine) -> proc_macro2::TokenStream {
.zip(out_states.iter().zip(guard_action_parameters.iter().zip(guard_action_ref_parameters.iter()))),
)
.map(|(guard, (action, (out_state, (g_a_param, g_a_ref_param))))| {
let out_state_string = &out_state.to_string()[0..out_state.to_string().find('(').unwrap_or_else(|| out_state.to_string().len())];
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think a split is more appropriate and canonical here

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed. I think it is now more readable than I had it first, thanks.

#entry_list

#[allow(missing_docs)]
fn transition_callback(&self, new_state: &Option<#states_type_name>) {}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why a transition callback when we already have an on_entry and on_exit for all states? Is this basically just intended to be a default function?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, understandable. Idea was a generic callback to capture the transition, I use it for bookkeeping in other parts of my application, synchronizing states to my backend. Could your on_entry_[stateA,stateB] and on_exit[stateA,stateB] for this as well, but I'd be duplicating the code for all transitions. So I thought a generic one is better.
Will hide this behind a feature flag as well.

self.state = Some(#states_type_name::#out_state);
}
} else {
quote! {
self.state = Some(#states_type_name::#out_state);
self.context_mut().#exit_ident();
self.context_mut().#entry_ident();
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't want to force everyone to implement these if they're not needed. Can we guard these calls based on their existence in the FSM definition?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, I have seen other functions to be behind these flags, will update this accordingly (and will put this MR in draft while I haven't done that).

When I have this flag and the other comments fixed, I think it is good to go.

@MartinBroers MartinBroers marked this pull request as draft November 14, 2023 09:37
@MartinBroers
Copy link
Author

Some general feedback is below. I don't know the state of this or if the intent is to merge it as-is or if it's just inteded to be a starting point.

If this is just a reference implementation for someone else to pick up, can we mark it as a draft? Otherwise, I'm not sure if I should be reviewing or not.

Yes, wasn't totally clear on that. Will work on this to make it final and ping you when I'm happy. Also, open for edits by others if they have time before I do.

@MartinBroers MartinBroers marked this pull request as ready for review November 24, 2023 14:16
@MartinBroers
Copy link
Author

I have added two new features, generate_entry_exit_states and generate_transition_callback. Code will not be generated when these flags are not present. Please let me know what you think of these additions. I am a bit unsure on the naming of the whole thing, but naming is important.

@@ -19,6 +19,9 @@ pub fn generate_code(sm: &ParsedStateMachine) -> proc_macro2::TokenStream {
let state_machine_context_type_name =
format_ident!("{sm_name}StateMachineContext", span = sm_name_span);

let generate_entry_exit_states = sm.generate_entry_exit_states;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd much rather we generate the calls by the existence of the exit/entry function as specified in the state machine itself.

Like, maybe:

statemachine! {
     *Init < enter_init > exit_init + transition / guard_transition => Next,
      Next < enter_next > exit_next + transition / guard_transition2 => Init,
}

What do you think?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Then, if the enter_* and exit_* functions don't exist in the UML, we don't generate the respective calls.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Or the notation you specified at the end of #60 (comment) would work as well. If we're following Boost SML, that would be preferable.

@dgehriger
Copy link

@ryan-summers : do you know what the state of this PR is? Has it gone stale?

@ryan-summers
Copy link
Collaborator

ryan-summers commented Mar 6, 2024

I haven't seen any response from the author to my review, so I would assume it's been abandoned, but maybe these comments may grab their attention as well :)

@MartinBroers
Copy link
Author

MartinBroers commented Mar 7, 2024

@dgehriger I had it on my list to fix, but got other priorities to work on first. I spent some time to rework the comments, but I failed in properly understanding the macros. Then I got other things on my plate and this got pushed down on my todo list.

So, to re-open the issue for myself and start thinking about it again, I have the token parser to parse < and >, but I am failing to get those functions into the generated result. Its been a while since I've looked at it though.

So, in transition.rs I now have

if input.parse::<Token![<]>().is_ok() 

which generates an entry for StateTransitions. Now, I need to get those fields back in the result, if they are not None, then generate a function for them. My code is very much in progress, but I'll push it anyway, and I am very open to remarks about how to get this issue done with.

@andygikling
Copy link

andygikling commented Mar 25, 2024

Bump. This appears to be one of the more feature-rich state machines in rust that anyone uses. on_enter and on_exit are table stakes for a state machine library. Can we get this feature in here please?

@ryan-summers
Copy link
Collaborator

ryan-summers commented Mar 25, 2024

I have nothing against the implementation @andygikling, but it is not yet done to my knowledge.

This, like many open-sourced projects, is maintained primarily through volunteer time. I'm happy to review any proposed changes you may have for the repo, but I don't have significant amounts of time to implement new features unless I need them for specific projects that I'm working on.

@andygikling
Copy link

Ok thanks I'll take a closer look and see what's missing!

@MartinBroers
Copy link
Author

So, I've finally gotten around to update this PR. I implemented the notation with > and < to indicate entry- and exit functions.

@ryan-summers pinging for review.

@ryan-summers
Copy link
Collaborator

ryan-summers commented Jun 21, 2024

I see that I initially proposed it, but the > and < notation is extremely confusing to me.

Would it make more sense to provide generic on_enter_<snake-case statename> and on_exit_<snake-case statename> for all states and provide them in the trait definition? Then, if the user wants to implement the entry/exit functions, they just have to provide a custom implementation.

This keeps the notation entirely out of the DSL and the compiler can optimize out the empty enter/exit calls if the user doesn't actually need them.

It also should make the necessary code changes here pretty simple.

@MartinBroers
Copy link
Author

@ryan-summers Thanks for the feedback. That was my original thought as well. When I read your comment I tried the flag and in rebasing the flag didn't work. So I updated it now and now both ways should work. I kinda like your suggestion with the '<' and '>' as well, so I left them in for now. Updated README and added an example.

@ryan-summers
Copy link
Collaborator

ryan-summers commented Jun 26, 2024

@ryan-summers Thanks for the feedback. That was my original thought as well. When I read your comment I tried the flag and in rebasing the flag didn't work. So I updated it now and now both ways should work.

I was actually advocating for removing the flag entirely and having the on_entry and on_exit functions defined by-default for all users. This means the DSL doesn't actually change at all and this change isn't breaking. The trait would provide the default implementation, so for example:

enum States {
    Start,
}

trait StateMachineContext {
    fn on_entry_start() {
        // Default, empty implementation that the user can override in their context definition.
    }
    fn on_exit_start() {
    }


}

@MartinBroers
Copy link
Author

MartinBroers commented Jun 27, 2024

I have removed the flag and let all on_entry and on_exit functions be autogenerated from now. I think it is pretty neat now.

Thanks for your time. I will have a look at the rebase error and the pipeline failures.

Should I also add my example to the tests suite?

Martin Broers added 4 commits June 27, 2024 21:40
This patch adds functions which are executed when the statemachine
transitions from a state A to a state B. First on_exit_state_a is called
(which is defined as an empty trait implementation which can be
overridden) followed by a call to on_entry_state_b. The on_entry and
on_exit functions can be implemented in the Statemachine Context trait
implementation or ignored altogether, since the standard implementation
is empty.

Signed-off-by: Martin Broers <[email protected]>
This patch adds a callback for succesfull transitions. This function can
be used to implement a logging service of some sorts for the state
machine.

Signed-off-by: Martin Broers <[email protected]>
By using `>` and `<` we can now define entry functions and exit
functions on states. These should be defined once and will apply to all
transitions interacting with either entering or exiting the state.

Signed-off-by: Martin Broers <[email protected]>
Also the parse will not parse ">" and "<" states anymore, since all
on_entry and on_exit functions will be autogenerated.

Signed-off-by: Martin Broers <[email protected]>
@ryan-summers
Copy link
Collaborator

@MartinBroers I removed the transition_callback function for now because I wanted to separate it from the on_entry and on_exit change. I'm not opposed to the idea, but I think it needs more refinement/discussion first, and the other callbacks are ready for now. Feel free to open up a secondary PR for the transition callback changes if you'd still like them!

I think I'd go for the route where it's defined as empty-by-default and the user doesn't need to specify any flags etc.

@ryan-summers ryan-summers merged commit 2d44416 into korken89:master Jun 30, 2024
7 checks passed
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

Successfully merging this pull request may close these issues.

4 participants