passing args to tasks

Return to Scripting Issues

passing args to tasks

Postby seemoor » Thu Nov 20, 2003 3:30 am

Can I pass parameters to a task when adding it to the taskMgr?

I want to add this task many times during the world, but each time with one different parameter, and sometimes with more than one instance of the task at a time, so I don't want to use global variables.
seemoor
 

Postby JasonPratt » Thu Nov 20, 2003 8:22 pm

The following code demonstrates passing parameters to both tasks and event handlers:

Code: Select all
from ShowBaseGlobal import *

class World( DirectObject.DirectObject ):
   def __init__( self ):
      base.mouseInterfaceNode.setPos( 0, 20, 0 )

      teapot = loader.loadModel( "teapot" )
      teapot.reparentTo( render )

      self.lightAttrib = LightAttrib.makeAllOff()
      self.ambientLight = AmbientLight( "ambientLight" )
      self.ambientLight.setColor( Vec4( .3, .3, .3, 1 ) )
      self.lightAttrib = self.lightAttrib.addLight( self.ambientLight )
      self.directionalLight = DirectionalLight( "directionalLight" )
      self.directionalLight.setColor( Vec4( .7, .7, .7, 1 ) )
      self.directionalLight.setDirection( Vec3( 1, 1, -2 ) )
      self.lightAttrib = self.lightAttrib.addLight( self.directionalLight )
      render.node().setAttrib( self.lightAttrib )

      self.accept( "arrow_left", self.turnObject, [teapot, -40, 0, 0] )
      self.accept( "arrow_right", self.turnObject, [teapot,  40, 0, 0] )
      self.accept( "arrow_up", self.turnObject, [teapot,  0, -40, 0] )
      self.accept( "arrow_down", self.turnObject, [teapot,  0, 40, 0] )

   def turnObject( self, object, h, p, r ):
      taskMgr.add( self.turnObjectTask, "turnObjectTask" + `globalClock.getLongTime()`, extraArgs=[object, h, p, r] )

   def turnObjectTask( task, object, h, p, r ):
      dt = globalClock.getDt()
      object.setHpr( object.getH() + h*dt, object.getP() + p*dt, object.getR() + r*dt )
      return Task.cont

world = World()

run()


Note that this isn't a particularly efficient way of getting the demonstrated behavior, since a new task is created for each key press, but I felt this was closer to what you're trying to do, since you want to launch multiple tasks simultaneously.

The extraArgs parameter is new, so you will have to download the latest installer before running the above code.

Also of note is that the task function requires you to omit the usually obligatory "self" argument. I think this is a flaw in the implementation.
JasonPratt
 
Posts: 47
Joined: Mon Nov 17, 2003 7:32 pm
Location: Pittsburgh, PA

Postby JonParise » Fri Nov 21, 2003 2:15 pm

JasonPratt wrote:Also of note is that the task function requires you to omit the usually obligatory "self" argument. I think this is a flaw in the implementation.

Agreed. It looks like the "fix" would require explicitly passing 'self' to the taskMgr.add() method, though, because the callback function is no longer a bound method (i.e. it's lost its "I'm a member of this class" attribution) when it's invoked.
JonParise
 
Posts: 3
Joined: Wed Nov 19, 2003 3:08 am
Location: San Mateo, CA

Postby seemoor » Sat Nov 22, 2003 6:40 pm

Thanks so much!
seemoor
 

Postby drwr » Sat Nov 20, 2004 6:13 pm

Not to resurrect an old thread unnecessarily, but I wanted to clarify a point: the "self" argument is in fact still required in the task callback method. It's actually the "task" argument which is no longer required when you use the extraArgs parameter.

The reasoning is that if you are explicitly specifying the arguments to pass to your task callback function, you don't need to have the "task" argument automatically passed to your function, which no one liked anyway. The only reason we had the task argument in the first place is to provide a place to hang task-specific data, before we added the extraArgs option.

In your example, the first parameter to turnObjectTask should have been called "self", not "task". This first parameter will be filled in with the World object, not the Task object.

Note that you can also use the PythonUtil.Functor class to bind parameters to a function callback anywhere a function callback is expected, even if there is no extraArgs parameter provided.

David
drwr
 
Posts: 11425
Joined: Fri Feb 13, 2004 12:42 pm
Location: Glendale, CA

in that case..

Postby saeru » Sat Nov 20, 2004 6:55 pm

where is task.time in all this?

it's unclear where it came from anyway since it's not listed as a member of the task class but when you use extraArgs what's the easiest way to get access to the current time of the task?
saeru
 
Posts: 24
Joined: Sat Nov 20, 2004 5:45 pm
Location: pittsburgh

Postby drwr » Sat Nov 20, 2004 7:22 pm

If you need to access task.time, or other members of the Task structure, then you should explicitly include the Task structure as part of the extraArgs list.
Code: Select all
task = Task(self.myCallback)
taskMgr.add(task, extraArgs = [task, myArg1, myArg2])

David
drwr
 
Posts: 11425
Joined: Fri Feb 13, 2004 12:42 pm
Location: Glendale, CA

Postby JasonPratt » Wed Nov 24, 2004 4:47 pm

Fascinating. I'm embarrased that I never noticed what type of object was actually being passed in as the first parameter and just made a blind assumption.
Last edited by JasonPratt on Wed Nov 24, 2004 4:49 pm, edited 1 time in total.
JasonPratt
 
Posts: 47
Joined: Mon Nov 17, 2003 7:32 pm
Location: Pittsburgh, PA

Postby JasonPratt » Wed Nov 24, 2004 4:49 pm

Also fascinating that the thread was revived exactly one year to the day after it was first created. Coincidence?
JasonPratt
 
Posts: 47
Joined: Mon Nov 17, 2003 7:32 pm
Location: Pittsburgh, PA

Postby alexrudd » Fri Sep 29, 2006 6:11 pm

Code: Select all
task = Task(self.myCallback)


Ok, I found this topic after much frustration, I've tried, but I don't understand what a callback is. All I want to do is be able to pass one extra argument to a function (as well as being able to use task.time)

If there is another way to run a task for a certain period of time without using task.time that would work too.


(Brought from the dead again - sorry)
alexrudd
 
Posts: 23
Joined: Fri Sep 29, 2006 6:08 pm

Postby drwr » Sat Sep 30, 2006 9:07 am

A callback is just a Python function. So put the name of your function in where the example above says "myCallback". This is the same thing you would have passed as the first parameter to taskMgr.add() if you weren't creating the Task object explicitly.

David
drwr
 
Posts: 11425
Joined: Fri Feb 13, 2004 12:42 pm
Location: Glendale, CA

Postby alexrudd » Sat Sep 30, 2006 7:26 pm

Thanks! It still doesn't work completely though. :?
I got an error but manged to figure it out: The callback needed to be like this: (damage was my extra arg)
task = Task(self.float_dmg(self, damage))
and it worked, until I tried to use task.time (AttributeError: World instance has no attribute 'time'
)

I know World doesn't have time, but wasn't that the point of using Task() and the callback?
alexrudd
 
Posts: 23
Joined: Fri Sep 29, 2006 6:08 pm

Postby drwr » Sat Sep 30, 2006 9:03 pm

Do it like this:
Code: Select all
task = Task(self.float_dmg)
taskMgr.add(task, extraArgs = [task, damage])

Two points:
(1) You don't pass self.float_dmg(task, damage) to the Task constructor. Doing that calls self.float_dmg on the spot, and (if the function were to return without error) would just pass the return value of that call into the Task constructor. Instead, you want to pass the function itself to the Task constructor, which means just the function name, no parentheses. The Task will store the function pointer and call it later.
(2) The first argument is task, not self. Remember, self is your World object. So if you pass self as the first argument to your float_dmg function, then your World object takes the place of the first parameter, which you have called "task" in your float_dmg function--confusing you, because it is not the Task, it is a World object. So when you then reference task.time, you're really referencing World.time, which doesn't exist.

The reason you create the Task object independently is so you have a task object to pass as the first argument.

David
drwr
 
Posts: 11425
Joined: Fri Feb 13, 2004 12:42 pm
Location: Glendale, CA

Postby alexrudd » Sat Sep 30, 2006 9:49 pm

drwr wrote:Do it like this:
Code: Select all
task = Task(self.float_dmg)
taskMgr.add(task, extraArgs = [task, damage])

Two points:
(1) You don't pass self.float_dmg(task, damage) to the Task constructor. Doing that calls self.float_dmg on the spot, and (if the function were to return without error) would just pass the return value of that call into the Task constructor. Instead, you want to pass the function itself to the Task constructor, which means just the function name, no parentheses. The Task will store the function pointer and call it later.
That's what I thought made sense, since I had made the same mistake previously and discovered it. However, when I tried using Task() without passing the arguments, it gives me this error:
Code: Select all
TypeError: 'module' object is not callable

When I do use the arguments, it works. :!:
However. I think it does call the task, because I get an error about world.time not existing. I changed the first argument (when defining the task/function) to task, and got the same error. (Was self automatically being passed because it was self.float_damage?) At this point, I gave up and eventually found a workaround that replaced task.time. :) Thanks for explaining anyway.
alexrudd
 
Posts: 23
Joined: Fri Sep 29, 2006 6:08 pm

Postby drwr » Sat Sep 30, 2006 10:18 pm

Code: Select all
However, when I tried using Task() without passing the arguments, it gives me this error

That just indicates that you didn't import Task as a class, you imported it as a module. That is to say, you did something like this:
Code: Select all
from direct.task import Task

instead of this:
Code: Select all
from direct.task.Task import Task

Note that there is a module named direct.task.Task, and that inside that module is a class named Task. If you do the first import, then the symbol "Task" refers to the module (and the class is named "Task.Task"), and if you try to call the constructor on a module, you'll get the error you report. You have to do the second import to make the symbol "Task" refer to the class.
When I do use the arguments, it works.

Actually, I don't think it was working; you were just getting another, different error (World has no attribute time) before it got a chance to encounter the error with Task. You got this error first because putting the arguments there calls the function immediately, and therefore you discovered the error within your function before it had a chance to try to evaluate Task().

David
drwr
 
Posts: 11425
Joined: Fri Feb 13, 2004 12:42 pm
Location: Glendale, CA

Postby alexrudd » Sun Oct 01, 2006 12:56 pm

Yep, you're right again but it still doesn't work. Now using Task.cont produces an error.
AttributeError: class Task has no attribute 'cont'
I'm guessing that when you load the Task class and not the module, you don't have access to Task.cont any more, since it was part of the module. Is there a way to load both the module and the class? (perhaps with different names?)

If anyone else is running into the same trouble, you can pass globalClock.getFrameTime() as an argument (I called it time) to the task function and use
Code: Select all
globalClock.getFrameTime()-time
instead of task.time
alexrudd
 
Posts: 23
Joined: Fri Sep 29, 2006 6:08 pm

Postby drwr » Sun Oct 01, 2006 1:18 pm

I'm guessing that when you load the Task class and not the module, you don't have access to Task.cont any more, since it was part of the module. Is there a way to load both the module and the class? (perhaps with different names?)

Ah, right. Sorry about that; we have fixed this in the current version of Panda (by moving these symbols into the class), but it probably wasn't yet fixed in this way by the time 1.2.3 was released.

In the meantime, sure--this is standard Python stuff. You can either go back to importing Task as a module, and then reference the class as Task.Task (which is probably the simplest solution), or you can do an import statement like this:
Code: Select all
from direct.task.Task import Task, cont

And then you can reference Task as a class, and return cont (by itself, with no prefix). You can get fancier, too--you could do:
Code: Select all
from direct.task.Task import cont as Task_cont

and then return Task_cont.

David
drwr
 
Posts: 11425
Joined: Fri Feb 13, 2004 12:42 pm
Location: Glendale, CA

Postby Fixer » Sun Oct 01, 2006 1:32 pm

A bit off-topic, but I'd like to add a thought...

One of the fun things about Python is the way that you can bend the namespace around backwards when you need to. Few languages I've worked in give you this level of control over the way you can reference code components, allowing you to fairly arbitrarily hide and show parts of your program to other parts of your program. It can be confusing at times, but it's better than some alternatives. I've been involved in a situation before where I had to rename all the functions in a C++ program named "mouseDown" because someone at MIT decided a couple decades ago that if you intend to use their window manager, then you would have to accept that the string "mouseDown" always means the number '7'. Not fun! :wink:

As the Zen of Python says," Namespaces are one honking great idea -- let's do more of those!"

Take care,
Mark
Fixer
 
Posts: 190
Joined: Tue May 17, 2005 7:03 pm
Location: Pittsburgh, PA


Return to Scripting Issues

Who is online

Users browsing this forum: Bing [Bot], Google [Bot] and 0 guests