pytransitions / transitions

A lightweight, object-oriented finite state machine implementation in Python with many extensions
MIT License
5.68k stars 530 forks source link

How to add a nested substate which is parallel to the parent state? #502

Closed thedrow closed 3 years ago

thedrow commented 3 years ago

I have a started state and its substates are the state machines of the tasks the actor runs. I'd like to activate both started and one of the states of the task at the same time but it seems like there's no way to declare a parallel state using add_nested_substate. How do I do that?

aleneum commented 3 years ago

Hello @thedrow,

whether substates are entered in parallel depends in the state.initial value.

from transitions.extensions import HierarchicalMachine

state = HierarchicalMachine.state_cls(name='A')
child_1 =  HierarchicalMachine.state_cls(name='a')
child_2 =  HierarchicalMachine.state_cls(name='b')
state.add_substate([child_1, child_2])
machine = HierarchicalMachine(states=[state])
machine.to_A()
print(machine.state)  # >>> A
state.initial = ['a', 'b']
machine.to_A()
print(machine.state)  # >>> ['A_a', 'A_b']

In theorie, you could also set state.initial to a subset of child states. But that's not offically supported. It's probably best to either set ONE value for state.initial or state.initial = [s for s in state.states].

thedrow commented 3 years ago

The problem is that I'm adding that state dynamically so it is not initially parallel.

aleneum commented 3 years ago

In the example above, initial is set after the machine has been initialized and run. The property can be set whenever needed. The state, however, has to be reentered before any effect can be observed.

thedrow commented 3 years ago

How do I renter a state I just created? Do I need to call a trigger?

aleneum commented 3 years ago

Same as you would do with initially created states. Either you use auto transitions or you add a new transition specifically for that new state:

from transitions.extensions import HierarchicalMachine

class Model:

    def __init__(self):
        self.machine = HierarchicalMachine(self, states=['A', 'B'], initial='A')

    def on_enter_B(self):
        child_1 = HierarchicalMachine.state_cls(name='a')
        child_2 = HierarchicalMachine.state_cls(name='b')
        child_3 = HierarchicalMachine.state_cls(name='c')
        state = HierarchicalMachine.state_cls(name='C', initial=['a', 'b', 'c'])
        state.add_substate([child_1, child_2, child_3])
        self.machine.add_state(state)
        # option A: with auto_transitions
        # self.to_C()
        # option B: without auto_transitions
        # self.machine.add_transition(trigger='enter_C', source='*', dest='C')
        # self.enter_C()
        # option C: without auto_transitions and custom transitions
        # every Model is decorated with a 'to' method which create a (temporary) event on-the-fly
        self.to('C')

model = Model()
model.to_B()
assert model.state == ['C_a', 'C_b', 'C_c']

I'll close this issue since it's a question about how to use transitions. Feel free to comment anyway. I will reopen the issue should a feature request/bug emerge. For questions about how to use transitions please use Stack Overflow.

You already know this but I quote myself for other readers:

Your question gains higher visibility since most developers look for help there. The targeted community is larger; Some people will even help you to formulate a good question. People get 'rewarded' with 'reputation' to help you. You also gain reputation in case this questions pops up more frequently. It's a win-win situation.

If you tag your question with transitions and python as described in the README, Tal and me (and probably others) will get a notification as well.