Panda3D Manual: Tasks
<<prev | top | next>> |
Tasks are special functions that are called once, each frame, while your application executes. They are similar in concept to threads, but in Panda, tasks are not generally separate threads; instead, all tasks are run cooperatively, one at a time, within the main thread. This design simplifies game programming considerably by removing the requirement to protect critical sections of code from mutual access. (But see Task Chains in the next section if you really want to use threading.) When you start Panda3D by importing DirectStart, a handful of tasks are created by default, but you are free to add as many additional tasks as you like. ## The Task Function A task is defined with a function or class method; this function is the main entry point for the task and will be called once per frame while the task is running. By default, the function receives one parameter, which is the task object; the task object carries information about the task itself, such as the amount of time that the task has been running. Your task function should return when it has finished processing for the frame. Because all tasks are run in the same thread, you must not spend too much time processing any one task function; the entire application will be locked up until the function returns. The task function may return either `Task.cont` to indicate that the task should be called again next frame, or `Task.done` to indicate that it should not be called again. If it returns None (which is to say, it does not return anything), then the default behavior is to stop. You can check how long your task has been running by checking `task.time` in your task function. You can also check how many times the task function has been run by using `task.frame`. The below example imports the Task module and shows a function used as a task.
    from direct.task import Task

    #This task runs for two seconds, then prints done
    def exampleTask(task):
      if task.time < 2.0:
        return Task.cont
      print 'Done'
      return Task.done
## Task Return Values The value returned from a task affects how the task manager handles that task going forward.
Variable | Purpose
--- | ---
`Task.done` | Specifies that a task is finished and removes it from the task manager.
`Task.cont` | Perform the task again next frame.
`Task.again` | Perform the task again, using the same delay as initially specified.
`None` | Same as done.
## The Do-Later Task A useful special kind of task is the do-later: this is similar to a task, but rather than being called every frame it will be called only once, after a certain amount of time (in seconds) has elapsed. You can, of course, implement a do-later task with a regular task that simply does nothing until a certain amount of time has elapsed (as in the above example), but using a do-later is a much more efficient way to achieve the same thing, especially if you will have many such tasks waiting around.
    taskMgr.doMethodLater(delayTime, myFunction, 'Task Name')
In this example myFunction must accept a task variable. If you wish to use a function that does not accept a task variable:
    taskMgr.doMethodLater(delayTime, myFunction, 'Task Name', extraArgs = [variables])
Note: if you wish to call a function which takes no variables simply pass extraArgs = [] Do-Later task's can be repeated from the task function by returning `task.again`. You can also change the delay of the Do-Later task by changing `task.delayTime`, but changing this will not have any affect on the task's actual delay time until the next time it gets added to the do-later list, for instance by returning Task.again.
    # This task increments itself so that the delay between task executions
    # gradually increases over time. If you do not change task.delayTime
    # the task will simply repeat itself every 2 seconds
    def myFunction(task):
        print "Delay:",task.delayTime
        print "Frame:",task.frame
        task.delayTime += 1
        return task.again

    myTask = taskMgr.doMethodLater(2, myFunction, 'tickTask')
If you wish to change the delayTime outside of the task function itself, and have it make an immediate affect, you can remove and re-add the task by hand, for instance:
    taskMgr.remove(task)
    task.delayTime += 1
    taskMgr.add(task)
Although there is a public member task.wakeTime which stores the time at which the task should wake up, you should not attempt to modify this. Doing so may appear to work in some simple cases, but will actually invalidate the Task Manager's internal priority queue, potentially causing other tasks to wake up later or sooner than they are supposed to. (In Panda version 1.6 and later, changing this value is specifically disallowed.) ## The Task Object The `task` object is passed into all Task Functions. There are several variables accessible in the func object, these are:
Variable | Returns
--- | ---
`task.time` | A float that indicates how long this task function has been running since the first execution of the function. The timer is running even when the task function is not being executed.
`task.frame` | An integer that counts the number of elapsed frames since this function was added. Count may start from 0 or 1.
`task.id` | An integer that gives the unique id assigned to this task by the Task Manager.
`task.name` | The task name assigned to the task function.
## The Task Manager All tasks are handled through the global Task Manager object, called `taskMgr` in Panda3D. The Task Manager keeps a list of all currently-running tasks. To add your task function to the task list, call `taskMgr.add()` with your function and an arbitrary name for the task. `taskMgr.add()` returns a Task which can be used to remove the task later on.
    taskMgr.add(exampleTask, 'MyTaskName')
You can add extra arguments to the call through the extraArgs parameter. When you add extraArgs, the task parameter is no longer sent to your function by default. If you still want it, make sure to set appendTask to true. `appendTask=True` makes the task the last argument sent to the function.
    taskMgr.add(exampleTask, 'MyTaskName', extraArgs=[a,b,c], appendTask=True)
Although normally each task is given a unique name, you may also create multiple different tasks with the same name. This can be conventient for removing many task functions at the same time. Each task remains independent of the others, even if they have the same name; this means that a task function returning `Task.done` will not affect any other task functions.
    taskMgr.add(taskFunc,'Existing TaskName')
To remove the task and stop it from executing, call taskMgr.remove(). You can pass in either the name of the task, or the task object (which was returned by `taskMgr.add()`, above).
    taskMgr.remove('MyTaskName')
You may add a cleanup function to the task function with the uponDeath parameter. Similar to task functions, the uponDeath function has a task object as a parameter. The cleanup function is called whenever the task finishes, for instance by return Task.done, or when it is explicitly removed via taskMgr.remove().
    taskMgr.add(exampleTask,'TaskName',uponDeath=cleanupFunc)
To print the list of tasks currently running, simply print out `taskMgr`. Among your own tasks, you may see the following system tasks listed:
dataloop | Processes the keyboard and mouse inputs
tkloop | Processes Tk GUI events
eventManager | Processes events generated by C++ code, such as collision events
igloop | Draws the scene
There also is graphical interface for managing tasks. This is very useful for having a look at the tasks while your application is running.
    taskMgr.popupControls()
## Task timing To see the specific timing information for each task when you print taskMgr, add the following line to your Config.prc file
    task-timer-verbose #t
(see The Configuration File for config syntax) ## Examples uponDeath
    taskAccumulator = 0

    def cleanUp(task):
      print "Task func has accumulated",taskAccumulator
      #Reset the accumulator
      taskAccumulator = 0

    #A task that runs forever
    def taskFunc(task):
      taskAccumulator += 1
      return Task.cont

    def taskStop(task):
      taskMgr.remove('Accumulator')

    #Add the taskFunc function with an uponDeath argument
    taskMgr.add(taskFunc,'Accumulator',uponDeath=cleanUp)
    #Stops the task 2 seconds later
    taskMgr.doMethodLater(2, taskStop, 'Task Stop')
<<prev | top | next>> |