pytransitions / transitions

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

Parallel transitions #508

Closed Crandelius closed 3 years ago

Crandelius commented 3 years ago

Suppose I have two states: states=['A', 'B'] and we have two transitions: transitions = [['go1', 'source': 'A', 'dest': 'B'], ['go2', 'source': 'A', 'dest': 'B']] but I want to reach destination B only if both transitions were made independent of the order of execution.

I was thinking of creating an intermediate state that could be reached. But it would force to create four transitions.

Is there a simpler way to define a conditional to reach state B only if both transitions were made independent of the order of execution?

In other words, state B must be reached, if two triggers were raised

pkonnov commented 3 years ago

@Crandelius conditions ?

Crandelius commented 3 years ago

@pkonnov conditions will not allow transition to execute. I want to execute the transition, but I don't want to reach the destination until both transitions will be executed

aleneum commented 3 years ago

I am with @pkonnov here, I don't see an easy (especially an easy to comprehend) solution to execute a transition that conceptually consists of two events. The solution with an intermediate state and four transitions is the cleanest in my opinion. But if it should be handled differently you could use condition callbacks:

from transitions import Machine

class Model:

    def __init__(self):
        self.go1_triggered = False
        self.go2_triggered = False

    def go1_check(self):
        self.go1_triggered = True
        return self.go1_triggered and self.go2_triggered

    def go2_check(self):
        self.go2_triggered = True
        return self.go1_triggered and self.go2_triggered

transitions = [
    dict(trigger='go1', source='A', dest='B', conditions='go1_check'),
    dict(trigger='go2', source='A', dest='B', conditions='go2_check'),
]

model = Model()
machine = Machine(model, states=['A', 'B'], transitions=transitions, initial='A')
model.go2()
assert model.is_A()
model.go1()
assert model.is_B()

Since this is a question about how to use transitions, you could post it on Stack Overflow. 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.

Crandelius commented 3 years ago

Thank you for your answers. I did not implement it using transition conditionals since I already have the conditionals implemented and functions that allow to determine which transitions can be made. Finally I implemented a state conditional, which verifies after each transition (after event) if these conditions are met and modifies the state if necessary.

aleneum commented 3 years ago

Alright,

btw. if you want a transition to happen anyway, you could add either a reflexive transition (which will exit/enter the current state) or an internal transition (which will not exit/enter the state):

   dict(trigger='go1', source='A', dest='B', conditions='go1_check'),
   dict(trigger='go1', source='A', dest='='),  # this acts like an 'else' statement; this is a reflexive transition
   #  dict(trigger='go1', source='A', dest=None),  # this is an internal transition

I am closing this since this a) seems to be resolved and b) might be more concerned with how transitions should be used. Feel free to comment anyway. I will reopen the issue if a bug or feature request arises.