Pyro Advanced Module: FSM using Python Generators
In the previous section we saw a Brain architecture for simulating a state machine. However, state machines only allow you to go to one state or another. There really isn't the idea of a subroutine. However, we did introduce the ideas of push/pop in the previous section which allows control to pass back to the state from a substate. But, control always begins again at the beginning of the state.
Sometimes we might like to do something like the following:
if A:
self.gosub("step1")
self.gosub("step2")
self.gosub("step3")
else:
self.gosub("step4")
self.gosub("step5")
This idea of sequential gosubs would be very difficult using the push/pop machinery from the previous section. In fact, there is no clear way of doing these short of creating a different path through a FSM for each possible sequence of states.
The problem is that push/pop didn't really transfer control, but merely pushed or popped values off a stack, so when the step method exited, the next state would be picked appropriately.
To implement the above combination of function and state, we need a bit more power in our language. We could just call functions directly, but that would require us to take care of FSM details in our code directly. That is, we would though out the FSM abstractions. However, Python now includes ideas from more sophisticated languages. One idea that we can take advantage here is the idea of a "generator". Using the "yield" command in Python automatically turns a normal function into a generator.
Consider this simple example:
def generator():
yield 1
yield 2
yield 3
g = generator()
for j in g:
print j
Here, the function generator that returns a value (1) with the yield statement. The statement "for j in g:" then asks for the instance g to give it a sequence of results. When g is asked for another value, g() continues on from where is last left off, and returns the second yield. When there are no more yields, then j is done.
We can use this same technique to build a control system. The following code sketches this idea out.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 |
"""
An advance FSM Brain architecture for allowing a true GOSUB which
saves the state of the State, and returns to continue where it left
off. Takes advantage of the new "yield" command in Python.
All states are generators. Control is handled by using "yield". Yield
passes back:
"continue" - let others have a turn, and continue
"goto" - continue processing in another state
"gosub" - go do another state as a function
"quit" - system will stop
TODO: allow system to be in multiple states simultaneously.
"""
import random, time
def process(control, level=0):
""" This function processes the states. """
command = "goto"
state = None
arg = None
while command not in ["return", "quit"]:
print "at level", level
for stepResults in control: # this is where the call is
if type(stepResults) == type(""):
command, state, arg = stepResults, None, None
else:
command, state, arg = stepResults
# -----------------------------
if command == "goto" or command == "quit":
break
elif command == "gosub":
command = process(state(*arg), level+1) # recursive
if command == "quit":
break
elif command == "return":
break
time.sleep(.001)
print "Ending level", level
if command == "goto":
control = state(*arg)
return command
def State1(*args):
""" Sample state """
print "Computing in State1"
for i in range(100):
yield "continue"
print
r = random.random()
if r < .33:
print " pre-goto..."
yield "goto", State2, (11,)
print " ERROR post-goto..." # never gets here
elif r < .66:
print " pre-gosub..."
yield "gosub", State2, (12,)
print " post-gosub..."
print "end loop..."
yield "return" # only do this when done, otherwise will loop
def State2(*args):
print "Computing in State2"
for i in range(100):
yield "continue"
print
r = random.random()
if r < .33:
print " pre-goto..."
yield "goto", State1, (11,)
print " ERROR post-goto..." # never gets here
elif r < .66:
print " pre-gosub..."
yield "gosub", State1, (12,)
print " post-gosub..."
print "end loop..."
yield "return" # only do this when done, otherwise will loop
# Get the process running:
retval = None
while retval != "quit":
retval = process(State1()) |
Pyro Modules Table of Contents
-
Pyro - Back to Pyro main page
-
Beyond Legos - NSF grant that pays for Pyro
Modules
Additional Resources
Reference: PyroSiteNotes
