Panda3D
 All Classes Functions Variables Enumerations
pythonTask.cxx
1 // Filename: pythonTask.cxx
2 // Created by: drose (16Sep08)
3 //
4 ////////////////////////////////////////////////////////////////////
5 //
6 // PANDA 3D SOFTWARE
7 // Copyright (c) Carnegie Mellon University. All rights reserved.
8 //
9 // All use of this software is subject to the terms of the revised BSD
10 // license. You should have received a copy of this license along
11 // with this source code in a file named "LICENSE."
12 //
13 ////////////////////////////////////////////////////////////////////
14 
15 #include "pythonTask.h"
16 #include "pnotify.h"
17 #include "config_event.h"
18 
19 #ifdef HAVE_PYTHON
20 #include "py_panda.h"
21 
22 TypeHandle PythonTask::_type_handle;
23 
24 Configure(config_pythonTask);
25 ConfigureFn(config_pythonTask) {
26  PythonTask::init_type();
27 }
28 
29 #ifndef CPPPARSER
30 extern struct Dtool_PyTypedObject Dtool_TypedReferenceCount;
31 #endif
32 
33 ////////////////////////////////////////////////////////////////////
34 // Function: PythonTask::Constructor
35 // Access: Published
36 // Description:
37 ////////////////////////////////////////////////////////////////////
38 PythonTask::
39 PythonTask(PyObject *function, const string &name) :
40  AsyncTask(name)
41 {
42  _function = NULL;
43  _args = NULL;
44  _upon_death = NULL;
45  _owner = NULL;
46  _registered_to_owner = false;
47  _generator = NULL;
48 
49  set_function(function);
50  set_args(Py_None, true);
51  set_upon_death(Py_None);
52  set_owner(Py_None);
53 
54  __dict__ = PyDict_New();
55 
56 #ifndef SIMPLE_THREADS
57  // Ensure that the Python threading system is initialized and ready to go.
58 #ifdef WITH_THREAD // This symbol defined within Python.h
59  PyEval_InitThreads();
60 #endif
61 #endif
62 }
63 
64 ////////////////////////////////////////////////////////////////////
65 // Function: PythonTask::Destructor
66 // Access: Published, Virtual
67 // Description:
68 ////////////////////////////////////////////////////////////////////
69 PythonTask::
70 ~PythonTask() {
71  Py_DECREF(_function);
72  Py_DECREF(_args);
73  Py_DECREF(__dict__);
74  Py_XDECREF(_generator);
75  Py_XDECREF(_owner);
76  Py_XDECREF(_upon_death);
77 }
78 
79 ////////////////////////////////////////////////////////////////////
80 // Function: PythonTask::set_function
81 // Access: Published
82 // Description: Replaces the function that is called when the task
83 // runs. The parameter should be a Python callable
84 // object.
85 ////////////////////////////////////////////////////////////////////
86 void PythonTask::
87 set_function(PyObject *function) {
88  Py_XDECREF(_function);
89 
90  _function = function;
91  Py_INCREF(_function);
92  if (_function != Py_None && !PyCallable_Check(_function)) {
93  nassert_raise("Invalid function passed to PythonTask");
94  }
95 }
96 
97 ////////////////////////////////////////////////////////////////////
98 // Function: PythonTask::get_function
99 // Access: Published
100 // Description: Returns the function that is called when the task
101 // runs.
102 ////////////////////////////////////////////////////////////////////
103 PyObject *PythonTask::
104 get_function() {
105  Py_INCREF(_function);
106  return _function;
107 }
108 
109 ////////////////////////////////////////////////////////////////////
110 // Function: PythonTask::set_args
111 // Access: Published
112 // Description: Replaces the argument list that is passed to the task
113 // function. The parameter should be a tuple or list of
114 // arguments, or None to indicate the empty list.
115 ////////////////////////////////////////////////////////////////////
116 void PythonTask::
117 set_args(PyObject *args, bool append_task) {
118  Py_XDECREF(_args);
119  _args = NULL;
120 
121  if (args == Py_None) {
122  // None means no arguments; create an empty tuple.
123  _args = PyTuple_New(0);
124  } else {
125  if (PySequence_Check(args)) {
126  _args = PySequence_Tuple(args);
127  }
128  }
129 
130  if (_args == NULL) {
131  nassert_raise("Invalid args passed to PythonTask");
132  _args = PyTuple_New(0);
133  }
134 
135  _append_task = append_task;
136 }
137 
138 ////////////////////////////////////////////////////////////////////
139 // Function: PythonTask::get_args
140 // Access: Published
141 // Description: Returns the argument list that is passed to the task
142 // function.
143 ////////////////////////////////////////////////////////////////////
144 PyObject *PythonTask::
145 get_args() {
146  if (_append_task) {
147  // If we want to append the task, we have to create a new tuple
148  // with space for one more at the end. We have to do this
149  // dynamically each time, to avoid storing the task itself in its
150  // own arguments list, and thereby creating a cyclical reference.
151 
152  int num_args = PyTuple_GET_SIZE(_args);
153  PyObject *with_task = PyTuple_New(num_args + 1);
154  for (int i = 0; i < num_args; ++i) {
155  PyObject *item = PyTuple_GET_ITEM(_args, i);
156  Py_INCREF(item);
157  PyTuple_SET_ITEM(with_task, i, item);
158  }
159 
160  this->ref();
161  PyObject *self =
162  DTool_CreatePyInstanceTyped(this, Dtool_TypedReferenceCount,
163  true, false, get_type_index());
164  PyTuple_SET_ITEM(with_task, num_args, self);
165  return with_task;
166 
167  } else {
168  Py_INCREF(_args);
169  return _args;
170  }
171 }
172 
173 ////////////////////////////////////////////////////////////////////
174 // Function: PythonTask::set_upon_death
175 // Access: Published
176 // Description: Replaces the function that is called when the task
177 // finishes. The parameter should be a Python callable
178 // object.
179 ////////////////////////////////////////////////////////////////////
180 void PythonTask::
181 set_upon_death(PyObject *upon_death) {
182  Py_XDECREF(_upon_death);
183 
184  _upon_death = upon_death;
185  Py_INCREF(_upon_death);
186  if (_upon_death != Py_None && !PyCallable_Check(_upon_death)) {
187  nassert_raise("Invalid upon_death function passed to PythonTask");
188  }
189 }
190 
191 ////////////////////////////////////////////////////////////////////
192 // Function: PythonTask::get_upon_death
193 // Access: Published
194 // Description: Returns the function that is called when the task
195 // finishes.
196 ////////////////////////////////////////////////////////////////////
197 PyObject *PythonTask::
198 get_upon_death() {
199  Py_INCREF(_upon_death);
200  return _upon_death;
201 }
202 
203 ////////////////////////////////////////////////////////////////////
204 // Function: PythonTask::set_owner
205 // Access: Published
206 // Description: Specifies a Python object that serves as the "owner"
207 // for the task. This owner object must have two
208 // methods: _addTask() and _clearTask(), which will be
209 // called with one parameter, the task object.
210 //
211 // owner._addTask() is called when the task is added
212 // into the active task list, and owner._clearTask() is
213 // called when it is removed.
214 ////////////////////////////////////////////////////////////////////
215 void PythonTask::
216 set_owner(PyObject *owner) {
217  if (_owner != NULL && _owner != Py_None && _state != S_inactive) {
218  unregister_from_owner();
219  }
220 
221  Py_XDECREF(_owner);
222  _owner = owner;
223  Py_INCREF(_owner);
224 
225  if (_owner != Py_None && _state != S_inactive) {
226  register_to_owner();
227  }
228 }
229 
230 ////////////////////////////////////////////////////////////////////
231 // Function: PythonTask::get_owner
232 // Access: Published
233 // Description: Returns the "owner" object. See set_owner().
234 ////////////////////////////////////////////////////////////////////
235 PyObject *PythonTask::
236 get_owner() {
237  Py_INCREF(_owner);
238  return _owner;
239 }
240 
241 ////////////////////////////////////////////////////////////////////
242 // Function: PythonTask::__setattr__
243 // Access: Published
244 // Description: Maps from an expression like "task.attr_name = v".
245 // This is customized here so we can support some
246 // traditional task interfaces that supported directly
247 // assigning certain values. We also support adding
248 // arbitrary data to the Task object.
249 ////////////////////////////////////////////////////////////////////
250 int PythonTask::
251 __setattr__(PyObject *self, PyObject *attr, PyObject *v) {
252  if (PyObject_GenericSetAttr(self, attr, v) == 0) {
253  return 0;
254  }
255 
256  if (!PyErr_ExceptionMatches(PyExc_AttributeError)) {
257  return -1;
258  }
259 
260  PyErr_Clear();
261 
262  if (task_cat.is_debug()) {
263  PyObject *str = PyObject_Repr(v);
264  task_cat.debug()
265  << *this << ": task."
266 #if PY_MAJOR_VERSION >= 3
267  << PyUnicode_AsUTF8(attr) << " = "
268  << PyUnicode_AsUTF8(str) << "\n";
269 #else
270  << PyString_AsString(attr) << " = "
271  << PyString_AsString(str) << "\n";
272 #endif
273  Py_DECREF(str);
274  }
275 
276  return PyDict_SetItem(__dict__, attr, v);
277 }
278 
279 ////////////////////////////////////////////////////////////////////
280 // Function: PythonTask::__delattr__
281 // Access: Published
282 // Description: Maps from an expression like "del task.attr_name".
283 // This is customized here so we can support some
284 // traditional task interfaces that supported directly
285 // assigning certain values. We also support adding
286 // arbitrary data to the Task object.
287 ////////////////////////////////////////////////////////////////////
288 int PythonTask::
289 __delattr__(PyObject *self, PyObject *attr) {
290  if (PyObject_GenericSetAttr(self, attr, NULL) == 0) {
291  return 0;
292  }
293 
294  if (!PyErr_ExceptionMatches(PyExc_AttributeError)) {
295  return -1;
296  }
297 
298  PyErr_Clear();
299 
300  if (PyDict_DelItem(__dict__, attr) == -1) {
301  // PyDict_DelItem does not raise an exception.
302 #if PY_MAJOR_VERSION < 3
303  PyErr_Format(PyExc_AttributeError,
304  "'PythonTask' object has no attribute '%.400s'",
305  PyString_AS_STRING(attr));
306 #else
307  PyErr_Format(PyExc_AttributeError,
308  "'PythonTask' object has no attribute '%U'",
309  attr);
310 #endif
311  return -1;
312  }
313 
314  return 0;
315 }
316 
317 ////////////////////////////////////////////////////////////////////
318 // Function: PythonTask::__getattr__
319 // Access: Published
320 // Description: Maps from an expression like "task.attr_name".
321 // This is customized here so we can support some
322 // traditional task interfaces that supported directly
323 // querying certain values. We also support adding
324 // arbitrary data to the Task object.
325 ////////////////////////////////////////////////////////////////////
326 PyObject *PythonTask::
327 __getattr__(PyObject *attr) const {
328  // Note that with the new Interrogate behavior, this method
329  // behaves more like the Python __getattr__ rather than being
330  // directly assigned to the tp_getattro slot (a la __getattribute__).
331  // So, we won't get here when the attribute has already been found
332  // via other methods.
333 
334  PyObject *item = PyDict_GetItem(__dict__, attr);
335 
336  if (item == NULL) {
337  // PyDict_GetItem does not raise an exception.
338 #if PY_MAJOR_VERSION < 3
339  PyErr_Format(PyExc_AttributeError,
340  "'PythonTask' object has no attribute '%.400s'",
341  PyString_AS_STRING(attr));
342 #else
343  PyErr_Format(PyExc_AttributeError,
344  "'PythonTask' object has no attribute '%U'",
345  attr);
346 #endif
347  return NULL;
348  }
349 
350  // PyDict_GetItem returns a borrowed reference.
351  Py_INCREF(item);
352  return item;
353 }
354 
355 ////////////////////////////////////////////////////////////////////
356 // Function: PythonTask::__traverse__
357 // Access: Published
358 // Description: Called by Python to implement cycle detection.
359 ////////////////////////////////////////////////////////////////////
360 int PythonTask::
361 __traverse__(visitproc visit, void *arg) {
362  Py_VISIT(_function);
363  Py_VISIT(_args);
364  Py_VISIT(_upon_death);
365  Py_VISIT(_owner);
366  Py_VISIT(__dict__);
367  Py_VISIT(_generator);
368  return 0;
369 }
370 
371 ////////////////////////////////////////////////////////////////////
372 // Function: PythonTask::__clear__
373 // Access: Published
374 // Description: Called by Python to implement cycle breaking.
375 ////////////////////////////////////////////////////////////////////
376 int PythonTask::
377 __clear__() {
378  Py_CLEAR(_function);
379  Py_CLEAR(_args);
380  Py_CLEAR(_upon_death);
381  Py_CLEAR(_owner);
382  Py_CLEAR(__dict__);
383  Py_CLEAR(_generator);
384  return 0;
385 }
386 
387 ////////////////////////////////////////////////////////////////////
388 // Function: PythonTask::is_runnable
389 // Access: Protected, Virtual
390 // Description: Override this function to return true if the task can
391 // be successfully executed, false if it cannot. Mainly
392 // intended as a sanity check when attempting to add the
393 // task to a task manager.
394 //
395 // This function is called with the lock held.
396 ////////////////////////////////////////////////////////////////////
397 bool PythonTask::
398 is_runnable() {
399  return _function != Py_None;
400 }
401 
402 ////////////////////////////////////////////////////////////////////
403 // Function: PythonTask::do_task
404 // Access: Protected, Virtual
405 // Description: Override this function to do something useful for the
406 // task.
407 //
408 // This function is called with the lock *not* held.
409 ////////////////////////////////////////////////////////////////////
410 AsyncTask::DoneStatus PythonTask::
411 do_task() {
412 #if defined(HAVE_THREADS) && !defined(SIMPLE_THREADS)
413  // Use PyGILState to protect this asynchronous call.
414  PyGILState_STATE gstate;
415  gstate = PyGILState_Ensure();
416 #endif
417 
418  DoneStatus result = do_python_task();
419 
420 #if defined(HAVE_THREADS) && !defined(SIMPLE_THREADS)
421  PyGILState_Release(gstate);
422 #endif
423 
424  return result;
425 }
426 
427 ////////////////////////////////////////////////////////////////////
428 // Function: PythonTask::do_python_task
429 // Access: Protected
430 // Description: The Python calls that implement do_task(). This
431 // function is separate so we can acquire the Python
432 // interpretor lock while it runs.
433 ////////////////////////////////////////////////////////////////////
434 AsyncTask::DoneStatus PythonTask::
435 do_python_task() {
436  PyObject *result = NULL;
437 
438  if (_generator == (PyObject *)NULL) {
439  // We are calling the function directly.
440  PyObject *args = get_args();
441  result =
442  Thread::get_current_thread()->call_python_func(_function, args);
443  Py_DECREF(args);
444 
445 #ifdef PyGen_Check
446  if (result != (PyObject *)NULL && PyGen_Check(result)) {
447  // The function has yielded a generator. We will call into that
448  // henceforth, instead of calling the function from the top
449  // again.
450  if (task_cat.is_debug()) {
451 #if PY_MAJOR_VERSION >= 3
452  PyObject *str = PyObject_ASCII(_function);
453  task_cat.debug()
454  << PyUnicode_AsUTF8(str) << " in " << *this
455  << " yielded a generator.\n";
456 #else
457  PyObject *str = PyObject_Repr(_function);
458  task_cat.debug()
459  << PyString_AsString(str) << " in " << *this
460  << " yielded a generator.\n";
461 #endif
462  Py_DECREF(str);
463  }
464  _generator = result;
465  result = NULL;
466  }
467 #endif
468  }
469 
470  if (_generator != (PyObject *)NULL) {
471  // We are calling a generator.
472  PyObject *func = PyObject_GetAttrString(_generator, "next");
473  nassertr(func != (PyObject *)NULL, DS_interrupt);
474 
475  result = PyObject_CallObject(func, NULL);
476  Py_DECREF(func);
477 
478  if (result == (PyObject *)NULL && PyErr_Occurred() &&
479  PyErr_ExceptionMatches(PyExc_StopIteration)) {
480  // "Catch" StopIteration and treat it like DS_done.
481  PyErr_Clear();
482  Py_DECREF(_generator);
483  _generator = NULL;
484  return DS_done;
485  }
486  }
487 
488  if (result == (PyObject *)NULL) {
489  if (PyErr_Occurred() && PyErr_ExceptionMatches(PyExc_SystemExit)) {
490  // Don't print an error message for SystemExit. Or rather, make
491  // it a debug message.
492  if (task_cat.is_debug()) {
493  task_cat.debug()
494  << "SystemExit occurred in " << *this << "\n";
495  }
496  } else {
497  task_cat.error()
498  << "Exception occurred in " << *this << "\n";
499  }
500  return DS_interrupt;
501  }
502 
503  if (result == Py_None) {
504  Py_DECREF(result);
505  return DS_done;
506  }
507 
508 #if PY_MAJOR_VERSION >= 3
509  if (PyLong_Check(result)) {
510  long retval = PyLong_AS_LONG(result);
511 #else
512  if (PyInt_Check(result)) {
513  long retval = PyInt_AS_LONG(result);
514 #endif
515 
516  switch (retval) {
517  case DS_again:
518  Py_XDECREF(_generator);
519  _generator = NULL;
520  // Fall through.
521 
522  case DS_done:
523  case DS_cont:
524  case DS_pickup:
525  case DS_exit:
526  case DS_pause:
527  // Legitimate value.
528  Py_DECREF(result);
529  return (DoneStatus) retval;
530 
531  case -1:
532  // Legacy value.
533  Py_DECREF(result);
534  return DS_done;
535 
536  default:
537  // Unexpected value.
538  break;
539  }
540  }
541 
542  ostringstream strm;
543 #if PY_MAJOR_VERSION >= 3
544  PyObject *str = PyObject_ASCII(result);
545  strm
546  << *this << " returned " << PyUnicode_AsUTF8(str);
547 #else
548  PyObject *str = PyObject_Repr(result);
549  strm
550  << *this << " returned " << PyString_AsString(str);
551 #endif
552  Py_DECREF(str);
553  Py_DECREF(result);
554  string message = strm.str();
555  nassert_raise(message);
556 
557  return DS_interrupt;
558 }
559 
560 ////////////////////////////////////////////////////////////////////
561 // Function: PythonTask::upon_birth
562 // Access: Protected, Virtual
563 // Description: Override this function to do something useful when the
564 // task has been added to the active queue.
565 //
566 // This function is called with the lock *not* held.
567 ////////////////////////////////////////////////////////////////////
568 void PythonTask::
569 upon_birth(AsyncTaskManager *manager) {
570  AsyncTask::upon_birth(manager);
571  register_to_owner();
572 }
573 
574 ////////////////////////////////////////////////////////////////////
575 // Function: PythonTask::upon_death
576 // Access: Protected, Virtual
577 // Description: Override this function to do something useful when the
578 // task has been removed from the active queue. The
579 // parameter clean_exit is true if the task has been
580 // removed because it exited normally (returning
581 // DS_done), or false if it was removed for some other
582 // reason (e.g. AsyncTaskManager::remove()). By the
583 // time this method is called, _manager has been
584 // cleared, so the parameter manager indicates the
585 // original AsyncTaskManager that owned this task.
586 //
587 // The normal behavior is to throw the done_event only
588 // if clean_exit is true.
589 //
590 // This function is called with the lock *not* held.
591 ////////////////////////////////////////////////////////////////////
592 void PythonTask::
593 upon_death(AsyncTaskManager *manager, bool clean_exit) {
594  AsyncTask::upon_death(manager, clean_exit);
595 
596  if (_upon_death != Py_None) {
597 #if defined(HAVE_THREADS) && !defined(SIMPLE_THREADS)
598  // Use PyGILState to protect this asynchronous call.
599  PyGILState_STATE gstate;
600  gstate = PyGILState_Ensure();
601 #endif
602 
603  call_function(_upon_death);
604 
605 #if defined(HAVE_THREADS) && !defined(SIMPLE_THREADS)
606  PyGILState_Release(gstate);
607 #endif
608  }
609  unregister_from_owner();
610 }
611 
612 ////////////////////////////////////////////////////////////////////
613 // Function: PythonTask::register_to_owner
614 // Access: Private
615 // Description: Tells the owner we are now his task.
616 ////////////////////////////////////////////////////////////////////
617 void PythonTask::
618 register_to_owner() {
619  if (_owner != Py_None && !_registered_to_owner) {
620 #if defined(HAVE_THREADS) && !defined(SIMPLE_THREADS)
621  // Use PyGILState to protect this asynchronous call.
622  PyGILState_STATE gstate;
623  gstate = PyGILState_Ensure();
624 #endif
625 
626  _registered_to_owner = true;
627  call_owner_method("_addTask");
628 
629 #if defined(HAVE_THREADS) && !defined(SIMPLE_THREADS)
630  PyGILState_Release(gstate);
631 #endif
632  }
633 }
634 
635 ////////////////////////////////////////////////////////////////////
636 // Function: PythonTask::unregister_from_owner
637 // Access: Private
638 // Description: Tells the owner we are no longer his task.
639 ////////////////////////////////////////////////////////////////////
640 void PythonTask::
641 unregister_from_owner() {
642  // make sure every call to _clearTask corresponds to a call to _addTask
643  if (_owner != Py_None && _registered_to_owner) {
644 #if defined(HAVE_THREADS) && !defined(SIMPLE_THREADS)
645  // Use PyGILState to protect this asynchronous call.
646  PyGILState_STATE gstate;
647  gstate = PyGILState_Ensure();
648 #endif
649 
650  _registered_to_owner = false;
651  call_owner_method("_clearTask");
652 
653 #if defined(HAVE_THREADS) && !defined(SIMPLE_THREADS)
654  PyGILState_Release(gstate);
655 #endif
656  }
657 }
658 
659 ////////////////////////////////////////////////////////////////////
660 // Function: PythonTask::call_owner_method
661 // Access: Private
662 // Description: Calls the indicated method name on the given object,
663 // if defined, passing in the task object as the only
664 // parameter.
665 ////////////////////////////////////////////////////////////////////
666 void PythonTask::
667 call_owner_method(const char *method_name) {
668  if (_owner != Py_None) {
669  PyObject *func = PyObject_GetAttrString(_owner, (char *)method_name);
670  if (func == (PyObject *)NULL) {
671 #if PY_MAJOR_VERSION >= 3
672  PyObject *str = PyObject_ASCII(_owner);
673  task_cat.error()
674  << "Owner object " << PyUnicode_AsUTF8(str) << " added to "
675  << *this << " has no method " << method_name << "().\n";
676 #else
677  PyObject *str = PyObject_Repr(_owner);
678  task_cat.error()
679  << "Owner object " << PyString_AsString(str) << " added to "
680  << *this << " has no method " << method_name << "().\n";
681 #endif
682  Py_DECREF(str);
683 
684  } else {
685  call_function(func);
686  Py_DECREF(func);
687  }
688  }
689 }
690 
691 ////////////////////////////////////////////////////////////////////
692 // Function: PythonTask::call_function
693 // Access: Private
694 // Description: Calls the indicated Python function, passing in the
695 // task object as the only parameter.
696 ////////////////////////////////////////////////////////////////////
697 void PythonTask::
698 call_function(PyObject *function) {
699  if (function != Py_None) {
700  this->ref();
701  PyObject *self =
702  DTool_CreatePyInstanceTyped(this, Dtool_TypedReferenceCount,
703  true, false, get_type_index());
704  PyObject *args = Py_BuildValue("(O)", self);
705  Py_DECREF(self);
706 
707  PyObject *result = PyObject_CallObject(function, args);
708  Py_XDECREF(result);
709  Py_DECREF(args);
710  }
711 }
712 
713 #endif // HAVE_PYTHON
A class to manage a loose queue of isolated tasks, which can be performed either synchronously (in th...
static Thread * get_current_thread()
Returns a pointer to the currently-executing Thread object.
Definition: thread.I:145
This class represents a concrete task performed by an AsyncManager.
Definition: asyncTask.h:43
TypeHandle is the identifier used to differentiate C++ class types.
Definition: typeHandle.h:85