passing args to tasks

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.

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


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.

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.

Thanks so much!

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

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?

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.

task = Task(self.myCallback)
taskMgr.add(task, extraArgs = [task, myArg1, myArg2])

David

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.

Also fascinating that the thread was revived exactly one year to the day after it was first created. Coincidence?

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)

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

Thanks! It still doesn’t work completely though. :confused:
I got an error but manged to figure it out: The callback needed to be like this: (damage was my extra arg)

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?

Do it like this:

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

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:

TypeError: 'module' object is not callable

When I do use the arguments, it works. :exclamation:
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. :slight_smile: Thanks for explaining anyway.

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:

from direct.task import Task

instead of this:

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.

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

Yep, you’re right again but it still doesn’t work. Now using Task.cont produces an error.

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

globalClock.getFrameTime()-time

instead of task.time

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:

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:

from direct.task.Task import cont as Task_cont

and then return Task_cont.

David

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