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

initialState() does not work beyond machine load #21

Open
panghy opened this issue May 9, 2024 · 9 comments
Open

initialState() does not work beyond machine load #21

panghy opened this issue May 9, 2024 · 9 comments

Comments

@panghy
Copy link
Contributor

panghy commented May 9, 2024

panghy@d7a2438

Even though the generated visualization of the FSM suggests that entering a parent state will automatically also activate its child state (prescribed by initialState<S>()). In reality, the automated activation of child initialState() only works on initial load of the FSM.

Perhaps there could have been a different way to indicate that subsequent entry of the parent state should also trigger transition to a child state (perhaps instead of initialState<S>(), one could imagine declaring activateChild<S>(), with the bonus of coregions automatically activating multiple children on entry, without the need for onFork()).

@panghy
Copy link
Contributor Author

panghy commented May 9, 2024

@bsutton thoughts on whether this might break stuff? It will cause transitions that are not explicitly defined (i.e. E -> S -> S_C_1...S_C_n) with all the necessity of calling onEnter for S_C_1 to S_C_n.

@panghy
Copy link
Contributor Author

panghy commented May 9, 2024

The workaround, today, is to do something like:

..state<ParentState>((b) => b
  ..on<_InternalPropagate, ChildState>()
  ..onEnter(
      (fromState, event) async => _fsm.applyEvent(_InternalPropagate()))

@bsutton
Copy link
Collaborator

bsutton commented May 9, 2024

So the doco states: (for co-regions).

If you use an 'on' transition or an 'onFork' that doesn't explicitly target every sub state then for all other substates their 'initialState' is entered.

So if 'initialState' isn't being called for each child (that isn't explicitly targeted in the on or onFork) then I would consider that a bug.

I agree that there is a risk that this could break existing code. Perhaps the path forward is to fix the bug and release it as 4.x with appropriate warnings of the behavior change.

initialState<S>(), one could imagine declaring activateChild<S>(), with the bonus of coregions automatically activating multiple children on entry, without the need for onFork()).

Not certain I understand what you are saying here but...

I'm not certain changing 'initialState' to 'activateChild' provides any great benefit but I'm open to a discussion and better terminology.

The current bug and name 'initialState' does leave a user wondering if this is a one off event or each time a co-region is entered. Maybe 'defaultState' would be a better term. The state a region is in before it has been explicitly set to a state.
I'm not convinced 'defaultState' is any more concise/descriptive.

with the bonus of coregions automatically activating multiple children on entry

This is already meant to happen and I thought it was what we are were talking about with respect to the initialState not being called, so this sentence is confusing me.

@panghy
Copy link
Contributor Author

panghy commented May 9, 2024

So if 'initialState' isn't being called for each child (that isn't explicitly targeted in the on or onFork) then I would consider that a bug.

It is a bug then. initialState is only used when the FSM is initialized (via _loadStateOfMind() in state_machine.dart). You can enter a parent state afterwards, and the first child state (or the one explicitly given by initialState) is not activated.

Not certain I understand what you are saying here but...
I'm not certain changing 'initialState' to 'activateChild' provides any great benefit but I'm open to a discussion and better terminology.

My point is if initalState only applies on FSM load, we can add a new method called activateChild() (or defaultState() doesn't matter) to maintain backward compatibility:

  • initialState remains how it is today, it only influences the initial state of the FSM on load
  • activateChild() or defaultState() would mean that whenever the state is entered, a child state would also be entered. If it's not used, the current behavior applies.

Of course, if we conclude that initialState not applying beyond FSM load is a bug, we could make a new version and make it clear that existing FSMs might break.

This is already meant to happen and I thought it was what we are were talking about with respect to the initialState not being called, so this sentence is confusing me.

I was mainly talking about non-coregions in the ticket, but I was thinking that this should also apply to coregions. Those would not have intialState (since that's singular) but rather that when you enter a coregion state (with on(), without explicitly using onFork() with a subset of child states), it will also activate all child states. I am actually not sure if this is true today. In my example, I only tested a simple FSM where going to a parent state does not activate any children.

@bsutton
Copy link
Collaborator

bsutton commented May 10, 2024

I was mainly talking about non-coregions in the ticket

so I'm a little confused as initialState only applies on startup and for co-regions.

To be clear you can't directly enter a 'parent' state as these are called abstract states. You can only ever directly enter a leaf state. The path to that leaf state then makes it explicit which parent states to activate - initialState is not needed. Only when a co-region exists is initialState needed.

For co-regions, if i enter a leaf state that is within a co-region then I also enter co-states in the co-region. As I've not explicitly stated which of these is the target ,we then should use initialState (or the state that was added to the region first) to work out what the state is of those other co-states in the co-region.

So are we talking about the same thing?

@panghy
Copy link
Contributor Author

panghy commented May 10, 2024

To be clear you can't directly enter a 'parent' state as these are called abstract states. You can only ever directly enter a leaf state. The path to that leaf state then makes it explicit which parent states to activate - initialState is not needed. Only when a co-region exists is initialState needed.

So I am pretty sure this is possible (entering an abstract state). When you do, none of the child states (even with initialState() set on the abstract state) would be active.

I wrote panghy@d7a2438 to show that.

For co-regions, if i enter a leaf state that is within a co-region then I also enter co-states in the co-region. As I've not explicitly stated which of these is the target ,we then should use initialState (or the state that was added to the region first) to work out what the state is of those other co-states in the co-region.

Yeah this is the "bonus" I mentioned initially, but I was looking originally to make initialState() work on abstract states (after initial FSM load).

@bsutton
Copy link
Collaborator

bsutton commented May 11, 2024 via email

@panghy
Copy link
Contributor Author

panghy commented May 13, 2024

I made the fix to disallow transitioning into an abstract state: #22

This breaks existing tests (nested_test.dart): Purgatory is an abstract state (has a child state called Matrix) but Alive onDeath goes to Purgatory which should not be allowed.

Similar test failures can be observed in cleaning_air_test.dart and registration_test.dart.

The unit tests show that it is valid to enter an abstract state. We can of course change that behavior and fix the tests. This issue was filed to see whether we can keep the behavior today as-is but let initialState() dictate that the first state of an abstract state or one specified via initialState<>() would also be activated when an abstract state is entered (this will fix abstract states being "transition-able towards" and not completely disallow existing state machines -- of course the behavior of them would change as child states would then be activated).

@bsutton
Copy link
Collaborator

bsutton commented May 14, 2024

So if you are happy to make the changes then I'm not going to object to formally allowing transitions to abstract states - particular given the existing code allows it.

Need to look through the existing exceptions because I believe there are parts that prohibit it.

We just need to update the documentation and add a few more unit tests.

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