transitions
transitions copied to clipboard
Does this project have any automatic traversal of states?
I was looking to find if this project had a feature similar to the automatic transitions but going through the intermediary states instead of warping to the final state
for example:
A -> B -> C -> D
So from A, would call something alike traverse_to_D() and receive either True or False if the target state was reached sucessfully, instead of throwing an error saying we can't access D from A, because we can if we manually reach each state using the triggers
Since the state machine ends up like a graph with complex conditions, it could benefit from having a pathfinding to simplify the proccess of traversal
Looking at the source code for core.py, does what you are asking not already exist with the loop=True
parameter to .add_ordered_transitions()
?
e.g.
In [1]:
from transitions.extensions import GraphMachine
class ABCD:
pass
states = list(ABCD.__name__)
abcd_machine = GraphMachine(model=ABCD(),
states=states,
use_pygraphviz=False, # pygraphviz does not render in Jupyter
initial = states[0])
abcd_machine.add_ordered_transitions(states, loop=False)
abcd_machine.get_graph()
Out[1]:
Though after reviewing the source I then found feature documentation is rather buried in the README.md
When it's linear its alright, but once lets say, B gets a bifurcation of transitions where C(1) is unavailable due to some conditions but it has C(2) available and both ends at D, we could benefit from automagically calling traverse_to_D()
Similar to A* pathfinding but simplified, where the costs are the conditions, if there is a path, we can traverse to the target even if the graph is complex.
There are many use cases, I was automating a few UI tests and was missing a feature to do these automated changes of states so I could focus only on defining valid paths instead of manually switching between many of them.
I use this run function that i call every x seconds:
def run(self):
a = self.get_triggers(self.state)
triggers = [x for x in a if not x.startswith("to_")]
for trig in triggers:
if self.trigger(trig):
break
and this as the init of the statemachine:
super().__init__(
self,
states=self.states,
initial=self.initial,
transitions=self.trans,
auto_transitions=True,
ignore_invalid_triggers=True,
)
The auto triggers is not needed for your case, but the ignore_invalid_triggers here is crucial, because otherwise you'll be bombarded with errors. This run function just checks all conditional transitions for the current state (but skips the auto_transitions) and when the first one succeeds it breaks
@jorritsmit You gave me a few ideas,
using this simple state machine described below..
class SM:
def __init__(self):
self.states = [
State(name="dummy", on_enter=['echo']),
State(name="idle", on_enter=['echo']),
State(name="jumping", on_enter=['echo']),
State(name="walking", on_enter=['echo']),
State(name="running", on_enter=['echo'])
]
self.transitions = [
{'trigger': 'idle', 'source': 'dummy', 'dest': 'idle'},
['jumping', 'idle', 'jumping'],
['walking', 'idle', 'walking'],
['running', 'walking', 'running']
]
self.machine = Machine(self, states=self.states, transitions=self.transitions, initial='dummy')
def echo(self):
print("Echo: "+self.state)
time.sleep(3)
if we wanted to find a valid path from the dummy state to any other state, we could use something like this
def traverse(current_state, target_state):
looked_states = []
path = []
def search(current_state=current_state, target_state=target_state):
#Prevent loops
looked_states.append(current_state)
#Get the list of transitions we can travel to
a = sm.machine.get_triggers(current_state)
triggers = [x for x in a if not x.startswith("to_")]
#TODO: Filter the list of transitions by condidions
#Return true if we're in the target state
if current_state == target_state:
print("**Reached target state**")
path.append(current_state)
return True
#Otherwise check the rest of the list recursively
print(current_state+" -> "+str(triggers))
for next_state in triggers:
if not next_state in looked_states:
print("Looking: "+next_state)
if search(next_state, target_state):
path.append(current_state)
return True
return False
search()
path.reverse()
return path[1::]
which would return a valid path
The big let down is that by using get_triggers the state name must be exactly the same as the trigger, and i think this isn't very cool
The big let down is that by using get_triggers the state name must be exactly the same as the trigger, and i think this isn't very cool
you could combine get_triggers
with get_transitions
. The traversal part of search
could look like this:
#Otherwise check the rest of the list recursively
print(current_state+" -> "+str(triggers))
for event in triggers:
for trans in sm.machine.get_transitions(trigger=event, source=current_state):
if trans.dest not in looked_states:
print("Looking: " + trans.dest)
if search(trans.dest, target_state):
path.append(current_state)
return True
return False
transitions
0.9.0 also supports model.may_<trigger>
to check whether a transition can be executed based on the transition's conditions. But this would not suffice for your use case because sm.may_walking()
would take the current state of sm
into account.
Since there hasn't been feedback for 14 days I will close this issue for now. Feel free to comment nevertheless if this issues has not been solved for you. If necessary, I will reopen the issue again.