[Python-Dev] generator/microthread syntax

Phillip J. Eby pje at telecommunity.com
Tue Nov 18 19:42:55 EST 2003


At 04:12 PM 11/18/03 -0800, Wade Brainerd wrote:
>Hello, I'm working on a game engine using Python as the scripting language 
>and have a question about generators.
>I'm using what I guess are called 'microthreads' as my basic script 
>building block, and I'd like to know if there is some kind of syntax that 
>could make them clearer, either something in Python already or something 
>that could be added.
>
>Here's an example script that illustrates the problem.
>
>from jthe import *
>
>def darlene_ai(self):
>    while True:
>        for x in wait_until_near(player.po.w,self.po.w): yield None
>
>        begin_cutscene(self)
>
>        for x in wait_face_each_other(player.po,self.po): yield None
>
>        if not player.inventory.has_key("papers"):
>            for x in say("Hi, I'm Darlene!  I found these papers,\ndid you 
> lose them?"): yield None
>        else:
>            for x in say("Hey, I'm new to this town, wanna go out 
> sometime?"): yield None
>
>        end_cutscene(self)
>
>        if not player.inventory.has_key("papers"):
>            spawn(give_item("papers"))
>
>        for x in wait(2.5): yield None
>
>Now in our in-house script language the above code would look very 
>similar, only without the
>
>for x in <call>: yield None
>
>constructs.  Instead, subroutines with a wait_ prefix execute yield 
>statements which are automatically propogated up the call stack all the 
>way to the thread manager.
>Is there anything to be done about this in Python?  I can see it 
>implemented three ways:

Since you don't seem to be using the values yielded, how about doing this 
instead:

while True:
     yield wait_until_near(...)
     begin_cutscene(self)
     yield wait_face_each_other(player.po,self.po)
     ...

All you need to do is change your microthread scheduler so that when a 
microthread yields a generator-iterator, you push the current microthread 
onto a stack, and replace it with the yielded generator.  Whenever a 
generator raises StopIteration, you pop the stack it's associated with and 
resume that generator.

This will produce the desired behavior without any language changes.  Your 
scheduler might look like:


class Scheduler:

     def __init__(self):
         self.threads = []

     def spawn(self,thread):
         stack = [thread]
         threads.append(stack)

     def __iter__(self):

         while True:

             for thread in self.threads:

                 current = thread[-1]

                 try:
                     step = current.next()
                 except StopIteration:
                     # Current generator is finished, remove it
                     # and give the next thread a chance
                     thread.pop()
                     if not thread:
                         self.threads.remove(thread)
                     yield None
                     continue

                 try:
                     # Is the yielded result iterable?
                     new = iter(step)
                 except TypeError:
                     # No, skip it
                     yield None
                     continue

                 # Yes, push it on the thread's call stack
                 thread.append(new)


So, to use this, you would do, e.g:

scheduler = Scheduler()
runOnce = iter(scheduler).next

scheduler.spawn( whatever.darlene_ai() )

while True:
     runOnce()
     # do between-quanta activities


All this is untested, so use at your own risk.




More information about the Python-Dev mailing list