Vizard 7 » Reference » Flow control » Tasks » Task basics
7.3

Task basics

With viztask, you can pause the execution of a function while waiting for some condition, like a few seconds to pass, without blocking the Vizard main loop or spawning an operating system thread.  

viztask versus viz.director

The viztask module and viz.director functions have similar functionality; however, there are important differences.  Calling viz.director actually creates a new thread, while scheduling a task does not.  This means that calling a long blocking operation in a task will block the Vizard update loop as well and the frame rate will go to zero.  However, in most cases, tasks should be used because they are much more CPU/memory efficient and are non-preemptive so synchronization locks are not necessary.  

Yield statements

Any function that is passed to the scheduler must have a yield statement within it.  This line tells the program to stop until a condition has been met. There are a number of predefined conditions that tasks can wait for. Check out the yield conditions in the Tasks command table for a list. In the following example, <viztask>.addAction adds the fading action to the ball and waits for that action to be completed before moving to the next line in the scheduled function.

import viztask
ball = viz.add('soccerball.ive')
def fadeAndAppear():
    yield viztask.addAction( ball, vizact.fadeTo(0,time=2) )
    print('done fading' )
    yield viztask.addAction( ball, vizact.fadeTo(1,time=2) )
    print('done appearing')
viztask.schedule( fadeAndAppear() )

Running a task repeatedly

To run a task repeatedly, just use standard loop statement (while or for). If you want to stop a looping task, kill it with <viztask:Task>.kill.

import viztask
ball = viz.add('soccerball.ive')
def bigAndSmall():
    while True:
        yield viztask.waitTime( 1 )
        ball.setScale( 2,2,2 )
        yield viztask.waitTime( 1 )
        ball.setScale( 1,1,1 )
myTask = viztask.schedule( bigAndSmall() )
vizact.onkeydown( ' ', myTask.kill )

Using event data in a task

Many viztask yield statements return a viz.Data object with data about the event or condition that occurred (e.g. <viztask>.waitMouseDown returns information about the mouse button that was pressed and the time it was pressed.) The <viztask>.waitEvent command will wait for any standard or custom event and return data about that event. Check out the Event reference for standard events or the Custom events section for more on making your own kind of event.

 

Here's an example with <viztask>.waitKeyDown that waits for a keypress and then uses the returned key to proceed.

import viztask
text = viz.addText(' ', viz.SCREEN)
text.setPosition(.5,.5)
def showLetter():
    while True:
        
        dataObject = yield viztask.waitKeyDown(keys=None)
        #Change the text with the returned data.
        text.message( dataObject.key )
        yield viztask.addAction( text, vizact.fadeTo(0,begin=1,time=1) )
viztask.schedule( showLetter() )

Custom conditions

If you want to create a custom condition for your yield statement, write a viztask.Condition class. The update function within this class will generally be called once per frame. When it returns True, the scheduler will continue. In the following example, the up-arrow key moves the ball upwards until it reaches one meter, and then repositions it at zero. Here, waitHigh is the custom condition. It returns True when the object reaches the specified height. The scheduled function then proceeds.

import viztask
ball = viz.add( 'white_ball.wrl' )
vizact.onkeydown( viz.KEY_UP, ball.setPosition, [0,.2,0], viz.REL_PARENT )

def limitHeight():
    myCondition = waitHigh(1, ball)
    while True:
        yield myCondition
        print('too high')
        ball.setPosition([0,0,0])
       
class waitHigh( viztask.Condition ):
    def __init__( self, height_limit, node ):
        self.__limit = height_limit
        self.__node = node
    def reset( self ):
        #Reset from the current height of the ball.
        self.__currentLimit = self.__node.getPosition()[1] + self.__limit
    def update( self ):
        #This function will run about every frame, returning True or False
        #to the yield statement.
        return self.__currentLimit <= self.__node.getPosition()[1]

viztask.schedule( limitHeight() )

Using sub tasks

You can embed sub tasks within a scheduled task. For example, doTrial is scheduled within executeExperiment. The parent task waits for the sub task to finish before it continues.

def executeExperiment():
    for trialNumber in range(3):
        yield viztask.waitKeyDown(' ') #hold here until spacebar pressed
        yield doTrial() #wait for doTrial to finish
        print('Trial Done: ', trialNumber)
    print('done with experiment')

def doTrial():
    viz.clearcolor(0, 1, 0)
    yield viztask.waitFrame(30) #wait for 30 frames
    viz.clearcolor(1, 0, 0)
    yield viztask.waitTime(2) #wait for 2 seconds
    viz.clearcolor(0, 0, 0)

viztask.schedule(executeExperiment())

Returning values

When called inside a task function, the viztask.returnValue command will set a return value and stop the task. The return value is available to the parent task that yielded the sub-task.

 

Task functions are implemented using Python generators, which don't allow return values. This command works around this limitation by raising a special exception that stores the return value. The viztask module will handle this exception and return the value to the parent task.

Note: Since this command raises an exception to return the value, you should not call this command within a try/except block

def waitForIt():
    d = yield viztask.waitKeyDown(None)
    viztask.returnValue('Key: {0}'.format(d.key))
    print('this line will not be reached')
    
def main():
    while True:

        it = yield waitForIt()
        print(it)

viztask.schedule(main())

Signalling within a task

Signal objects are useful for communicating between tasks.  Tasks can either wait for, or send, signals. In the following example, one ball waits for the signal to change color while the other ball sends out the signal between actions.

import viztask
ball1 = viz.add('white_ball.wrl')
ball2 = viz.add('white_ball.wrl')
#Create a signal.
changeColorSignal = viztask.Signal()

def colorBall():
    #This task will wait for the signalVi.
    while True:
        yield changeColorSignal.wait()
        ball2.color( viz.RED )
        yield changeColorSignal.wait()
        ball2.color( viz.BLUE )

def moveBall():
    #This task will repeated send the signal.
    while True:
        yield viztask.addAction( ball1, vizact.moveTo([0,0,0],speed=1) )
        changeColorSignal.send()
        yield viztask.addAction( ball1, vizact.moveTo([-1,0,0],speed=1) )
        changeColorSignal.send()

viztask.schedule( colorBall() )
viztask.schedule( moveBall() )

See also

In this section:

Tasks command table

Other sections:

Tutorial: Creating a task

Tutorial:Action Objects

Action basics

Director basics

Event Basics