Closed Blindfreddy closed 2 years ago
Hello @Blindfreddy,
thank you for the suggestion. Currently, I would say that this use case can be covered with internal transitions.
transitions
distinguished between reflexive and internal transitions. Reflexive transitions leave the state and reenter it again. As far as I know, this is the common behavior for 'self transitions' across statechart/FSM libraries.
Wildcards will treat transitions from/to the same state as reflexive transitions.
Since transitions are evaluated in the order they were added, you can make use of internal transitions (with dest=None
) to prevent an actual state change:
transitions = [
{'trigger': 'swon', 'source': 'ON', 'dest': None},
{'trigger': 'swon', 'source': '*', 'dest': 'ON'},
{'trigger': 'swoff', 'source': 'OFF', 'dest': None},
{'trigger': 'swoff', 'source': '*', 'dest': 'OFF'},
]
If you dont want to explicitly model internal transitions you could make a custom machine inherit from Machine
and always add an internal transitions with the same event name.
class MyMachine(Machine):
def add_transition(self, trigger, source, dest, conditions=None,
unless=None, before=None, after=None, prepare=None, **kwargs):
if source == "*":
super().add_transition(trigger, dest, None, conditions, unless, before, after, prepare, **kwargs)
super().add_transition(trigger, source, dest, conditions, unless, before, after, prepare, **kwargs)
MyMachine.add_transition
could be a bit smarter by omitting the reflexive transition that is automatically added by just forwarding the wildcard.
Thanks for this suggestion, I tend to agree that the UC can be covered by internal transitions as suggested above. I will try it out in my application to confirm. When I first started using transitions they didn't exist, yet.
Separately, I'd still suggest to make this behavior an option/configuration/feature of the library, because I still find it unintuitive behavior, at least in some circumstances. On the other hand, it may well be the desired/required behavior in some circumstances, e.g the mentioned StateCharts. That indicates a configurable behavior, to me. To that end a new Machine extension (aka mixins in your documentation) which implements above extension of add_transition
might be an idea. Perhaps named 'StickyStatemachine' or similar, where the word 'sticky' indicates that reflexive transitions don't exit/re-enter when source and dest states are identical.
Thank you for your feedback, @Blindfreddy. I get your point but right now I am not convinced that another parameter is a good idea. For newcomers, the amount of parameters is already quite daunting as far as I can tell and this is why I try to follow standards when possible. According to the Wikipedia passage about internal transitions the difference between self/reflexive transitions and internal transitions is the fact that reflexive transitions will exit and re-enter the source and target state:
In the absence of entry and exit actions, internal transitions would be identical to self-transitions (transitions in which the target state is the same as the source state). In fact, in a classical Mealy machine, actions are associated exclusively with state transitions, so the only way to execute actions without changing state is through a self-transition [...]. However, in the presence of entry and exit actions, as in UML statecharts, a self-transition involves the execution of exit and entry actions and therefore it is distinctively different from an internal transition.
As of now, I would conclude that transitions
already offers the means to design and configure self-transitions and internal transitions and thus state transitions either with exit/enter callbacks or without them.
This is of course no decision meant to last forever. I am open to further discussion and should the community favor another option to make reflexive transitions act as internal transitions I am willing to implement and maintain this. For now I will close this issue. But again, feel free to comment anyway.
My two cents: I think this feature would be especially useful for hierarchical state machines. I use super states to abstract out some transitions from multiple sub states, but I don't necessary want that trigger to cause reflexive transitions. For example:
from transitions.extensions import HierarchicalMachine
states = [
{'name': 'a', 'children': ['b', 'c', 'd'], 'initial': 'b'}
]
transitions = [
['button_pressed', 'a', 'd']
]
machine = HierarchicalMachine(states=states, transitions=transitions, initial='a')
This allows entry and exit actions to contain operations that we only want to do on state change. This is important when any kind of physical operation is involved or when state entry involves kicking off a separate task.
I also use triggers to handle commands from users or separate services. This feature would additionally make it trivial to handle duplicate commands. For example: a user spams a button, multiple services send the same trigger, or a service wants to send a trigger without knowledge of the current state.
For now, I will try to implement this in an HSM subclass and reply if I find a good way. Manually adding the internal transitions is possible but scales quite poorly.
You have an idea for a feature that would make
transitions
more helpful or easier to use? Great! We are looking forward to your suggestion.Is your feature request related to a problem? Please describe.
Currently, when a machine is in a given state and a transition results in the same state, the machine exits the current state and enters it, ie. on_exit and on_enter callbacks are called, despite the machine already being in the target state.
I find this highly unintuitive and had to write code to check the current state and omit calling a transition that would result in the same state. A workaround would be to exclude the current state from allowed source states, but this would result in an exception being triggered, so masking / compensation would still be required.
An example is a light switch. When one presses the on-switch of a light that is already on, it doesn't first turn off and then on again. It just stays on.
Describe the solution you'd like
I would like it to be configurable so that 'on_exit' and 'on_enter' events (and possible others) are NOT fired when a transition results in the same state. Perhaps a boolean 'reflexive_trigger_state_events'=<True/False>. If True, the current behavior is kept, if False the abovementioned events don't fire.
Additional context
A simple example shows the problem - note a the end that the state ON is kept but exited and then re-entered.
Output:
Note how a switch which is already in the ON state exits from state ON and re-enters state ON when switched on again.