generator functions in base.accept

Generator Functions in ‘base.accept’

Hello,

The ‘next’ and ‘send’ methods of generator objects, or the ‘next’ function with a generator object as an extraArg in Python 2.6, can be a target for the ‘accept’ method of a DirectObject. Generators provide a nice middle ground between a class instance and a function; they maintain a state but do not require a class definition; good in such cases as a lambda is just barely too limited. Generators have a ‘send’ method which allows arguments to passed be to the generator, which are returned from the ‘yield’ statement, for additional functionality such as ‘restart’ or ‘continue’.

'''
This program demonstrates the 'next' or 'send' methods of
generators as parameters to the 'accept' method.

'P' and 'Y' start a card at the top of the screen which
spirals down.  The 'Y' spiral continues if the key is let
up; the 'P' starts over; due to the presence of parameters
through the 'send' method on return from the 'yield', combined
with a flag in the parameter.
'''

import direct.directbase.DirectStart
from panda3d.core import *
from math import *
import random as ran

def downward_spiral( continueOnNone ):
    cm= CardMaker( 'cm' )
    while 1:
        print 'new'
        c= cm.generate( )
        obj= render.attachNewNode( c ) # add object to scene
        z, theta= 0, ran.uniform( 0, 2* pi )
        z_vel= ran.uniform( 0.01, 0.02 )
        theta_vel= ran.uniform( 0.1, 0.2 )
        for _ in xrange( 200 ): # just a downward spiral
            x, y= cos( theta ), sin( theta )
            obj.setPos( x, y, z )
            instruction= yield # function exits and re-enters here
            if not continueOnNone and instruction is None:
                break
            z= z- z_vel
            theta+= theta_vel
        obj.removeNode( ) # then start over

spiral1= downward_spiral( False )
spiral2= downward_spiral( True ) # parameter controls 'send' behavior
base.accept( 'p', spiral1.next ) # form 1 (restart spiral)
base.accept( 'p-repeat', spiral1.send, extraArgs= [ 'continue' ] ) # form 1
base.accept( 'y-repeat', next, extraArgs= [ spiral2 ] ) # form 2
base.cam.setPos( 0, -5, 0 )
run( )

Remember that if the generator exits, it will throw an exception. We might want to get the ‘accept’ method called when the generator starts, to enable it to remove it when it ends; or enter an empty yield loop. Since a generator is created before its creator has a reference to it, we can’t pass itself in as a parameter. However, we could establish an initialization protocol to pass it in via ‘send’, or as a post-facto attribute of an auxiliary parameter. Or we could wrap the function in a second generator that cancels the accept once the first throws an exception.

A confusing, but interesting apprach. Thanks for sharing.