[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