Panda3D
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 #ifndef NDEBUG
218  if (owner != Py_None) {
219  PyObject *add = PyObject_GetAttrString(owner, "_addTask");
220  PyObject *clear = PyObject_GetAttrString(owner, "_clearTask");
221 
222  if (add == NULL || !PyCallable_Check(add) ||
223  clear == NULL || !PyCallable_Check(clear)) {
224  Dtool_Raise_TypeError("owner object should have _addTask and _clearTask methods");
225  return;
226  }
227  }
228 #endif
229 
230  if (_owner != NULL && _owner != Py_None && _state != S_inactive) {
231  unregister_from_owner();
232  }
233 
234  Py_XDECREF(_owner);
235  _owner = owner;
236  Py_INCREF(_owner);
237 
238  if (_owner != Py_None && _state != S_inactive) {
239  register_to_owner();
240  }
241 }
242 
243 ////////////////////////////////////////////////////////////////////
244 // Function: PythonTask::get_owner
245 // Access: Published
246 // Description: Returns the "owner" object. See set_owner().
247 ////////////////////////////////////////////////////////////////////
248 PyObject *PythonTask::
249 get_owner() {
250  Py_INCREF(_owner);
251  return _owner;
252 }
253 
254 ////////////////////////////////////////////////////////////////////
255 // Function: PythonTask::__setattr__
256 // Access: Published
257 // Description: Maps from an expression like "task.attr_name = v".
258 // This is customized here so we can support some
259 // traditional task interfaces that supported directly
260 // assigning certain values. We also support adding
261 // arbitrary data to the Task object.
262 ////////////////////////////////////////////////////////////////////
263 int PythonTask::
264 __setattr__(PyObject *self, PyObject *attr, PyObject *v) {
265  if (PyObject_GenericSetAttr(self, attr, v) == 0) {
266  return 0;
267  }
268 
269  if (!PyErr_ExceptionMatches(PyExc_AttributeError)) {
270  return -1;
271  }
272 
273  PyErr_Clear();
274 
275  if (task_cat.is_debug()) {
276  PyObject *str = PyObject_Repr(v);
277  task_cat.debug()
278  << *this << ": task."
279 #if PY_MAJOR_VERSION >= 3
280  << PyUnicode_AsUTF8(attr) << " = "
281  << PyUnicode_AsUTF8(str) << "\n";
282 #else
283  << PyString_AsString(attr) << " = "
284  << PyString_AsString(str) << "\n";
285 #endif
286  Py_DECREF(str);
287  }
288 
289  return PyDict_SetItem(__dict__, attr, v);
290 }
291 
292 ////////////////////////////////////////////////////////////////////
293 // Function: PythonTask::__delattr__
294 // Access: Published
295 // Description: Maps from an expression like "del task.attr_name".
296 // This is customized here so we can support some
297 // traditional task interfaces that supported directly
298 // assigning certain values. We also support adding
299 // arbitrary data to the Task object.
300 ////////////////////////////////////////////////////////////////////
301 int PythonTask::
302 __delattr__(PyObject *self, PyObject *attr) {
303  if (PyObject_GenericSetAttr(self, attr, NULL) == 0) {
304  return 0;
305  }
306 
307  if (!PyErr_ExceptionMatches(PyExc_AttributeError)) {
308  return -1;
309  }
310 
311  PyErr_Clear();
312 
313  if (PyDict_DelItem(__dict__, attr) == -1) {
314  // PyDict_DelItem does not raise an exception.
315 #if PY_MAJOR_VERSION < 3
316  PyErr_Format(PyExc_AttributeError,
317  "'PythonTask' object has no attribute '%.400s'",
318  PyString_AS_STRING(attr));
319 #else
320  PyErr_Format(PyExc_AttributeError,
321  "'PythonTask' object has no attribute '%U'",
322  attr);
323 #endif
324  return -1;
325  }
326 
327  return 0;
328 }
329 
330 ////////////////////////////////////////////////////////////////////
331 // Function: PythonTask::__getattr__
332 // Access: Published
333 // Description: Maps from an expression like "task.attr_name".
334 // This is customized here so we can support some
335 // traditional task interfaces that supported directly
336 // querying certain values. We also support adding
337 // arbitrary data to the Task object.
338 ////////////////////////////////////////////////////////////////////
339 PyObject *PythonTask::
340 __getattr__(PyObject *attr) const {
341  // Note that with the new Interrogate behavior, this method
342  // behaves more like the Python __getattr__ rather than being
343  // directly assigned to the tp_getattro slot (a la __getattribute__).
344  // So, we won't get here when the attribute has already been found
345  // via other methods.
346 
347  PyObject *item = PyDict_GetItem(__dict__, attr);
348 
349  if (item == NULL) {
350  // PyDict_GetItem does not raise an exception.
351 #if PY_MAJOR_VERSION < 3
352  PyErr_Format(PyExc_AttributeError,
353  "'PythonTask' object has no attribute '%.400s'",
354  PyString_AS_STRING(attr));
355 #else
356  PyErr_Format(PyExc_AttributeError,
357  "'PythonTask' object has no attribute '%U'",
358  attr);
359 #endif
360  return NULL;
361  }
362 
363  // PyDict_GetItem returns a borrowed reference.
364  Py_INCREF(item);
365  return item;
366 }
367 
368 ////////////////////////////////////////////////////////////////////
369 // Function: PythonTask::__traverse__
370 // Access: Published
371 // Description: Called by Python to implement cycle detection.
372 ////////////////////////////////////////////////////////////////////
373 int PythonTask::
374 __traverse__(visitproc visit, void *arg) {
375 /*
376  Py_VISIT(_function);
377  Py_VISIT(_args);
378  Py_VISIT(_upon_death);
379  Py_VISIT(_owner);
380  Py_VISIT(__dict__);
381  Py_VISIT(_generator);
382 */
383  return 0;
384 }
385 
386 ////////////////////////////////////////////////////////////////////
387 // Function: PythonTask::__clear__
388 // Access: Published
389 // Description: Called by Python to implement cycle breaking.
390 ////////////////////////////////////////////////////////////////////
391 int PythonTask::
392 __clear__() {
393 /*
394  Py_CLEAR(_function);
395  Py_CLEAR(_args);
396  Py_CLEAR(_upon_death);
397  Py_CLEAR(_owner);
398  Py_CLEAR(__dict__);
399  Py_CLEAR(_generator);
400 */
401  return 0;
402 }
403 
404 ////////////////////////////////////////////////////////////////////
405 // Function: PythonTask::is_runnable
406 // Access: Protected, Virtual
407 // Description: Override this function to return true if the task can
408 // be successfully executed, false if it cannot. Mainly
409 // intended as a sanity check when attempting to add the
410 // task to a task manager.
411 //
412 // This function is called with the lock held.
413 ////////////////////////////////////////////////////////////////////
414 bool PythonTask::
415 is_runnable() {
416  return _function != Py_None;
417 }
418 
419 ////////////////////////////////////////////////////////////////////
420 // Function: PythonTask::do_task
421 // Access: Protected, Virtual
422 // Description: Override this function to do something useful for the
423 // task.
424 //
425 // This function is called with the lock *not* held.
426 ////////////////////////////////////////////////////////////////////
427 AsyncTask::DoneStatus PythonTask::
428 do_task() {
429 #if defined(HAVE_THREADS) && !defined(SIMPLE_THREADS)
430  // Use PyGILState to protect this asynchronous call.
431  PyGILState_STATE gstate;
432  gstate = PyGILState_Ensure();
433 #endif
434 
435  DoneStatus result = do_python_task();
436 
437 #if defined(HAVE_THREADS) && !defined(SIMPLE_THREADS)
438  PyGILState_Release(gstate);
439 #endif
440 
441  return result;
442 }
443 
444 ////////////////////////////////////////////////////////////////////
445 // Function: PythonTask::do_python_task
446 // Access: Protected
447 // Description: The Python calls that implement do_task(). This
448 // function is separate so we can acquire the Python
449 // interpretor lock while it runs.
450 ////////////////////////////////////////////////////////////////////
451 AsyncTask::DoneStatus PythonTask::
452 do_python_task() {
453  PyObject *result = NULL;
454 
455  if (_generator == (PyObject *)NULL) {
456  // We are calling the function directly.
457  PyObject *args = get_args();
458  result =
459  Thread::get_current_thread()->call_python_func(_function, args);
460  Py_DECREF(args);
461 
462 #ifdef PyGen_Check
463  if (result != (PyObject *)NULL && PyGen_Check(result)) {
464  // The function has yielded a generator. We will call into that
465  // henceforth, instead of calling the function from the top
466  // again.
467  if (task_cat.is_debug()) {
468 #if PY_MAJOR_VERSION >= 3
469  PyObject *str = PyObject_ASCII(_function);
470  task_cat.debug()
471  << PyUnicode_AsUTF8(str) << " in " << *this
472  << " yielded a generator.\n";
473 #else
474  PyObject *str = PyObject_Repr(_function);
475  task_cat.debug()
476  << PyString_AsString(str) << " in " << *this
477  << " yielded a generator.\n";
478 #endif
479  Py_DECREF(str);
480  }
481  _generator = result;
482  result = NULL;
483  }
484 #endif
485  }
486 
487  if (_generator != (PyObject *)NULL) {
488  // We are calling a generator.
489  PyObject *func = PyObject_GetAttrString(_generator, "next");
490  nassertr(func != (PyObject *)NULL, DS_interrupt);
491 
492  result = PyObject_CallObject(func, NULL);
493  Py_DECREF(func);
494 
495  if (result == (PyObject *)NULL && PyErr_Occurred() &&
496  PyErr_ExceptionMatches(PyExc_StopIteration)) {
497  // "Catch" StopIteration and treat it like DS_done.
498  PyErr_Clear();
499  Py_DECREF(_generator);
500  _generator = NULL;
501  return DS_done;
502  }
503  }
504 
505  if (result == (PyObject *)NULL) {
506  if (PyErr_Occurred() && PyErr_ExceptionMatches(PyExc_SystemExit)) {
507  // Don't print an error message for SystemExit. Or rather, make
508  // it a debug message.
509  if (task_cat.is_debug()) {
510  task_cat.debug()
511  << "SystemExit occurred in " << *this << "\n";
512  }
513  } else {
514  task_cat.error()
515  << "Exception occurred in " << *this << "\n";
516  }
517  return DS_interrupt;
518  }
519 
520  if (result == Py_None) {
521  Py_DECREF(result);
522  return DS_done;
523  }
524 
525 #if PY_MAJOR_VERSION >= 3
526  if (PyLong_Check(result)) {
527  long retval = PyLong_AS_LONG(result);
528 #else
529  if (PyInt_Check(result)) {
530  long retval = PyInt_AS_LONG(result);
531 #endif
532 
533  switch (retval) {
534  case DS_again:
535  Py_XDECREF(_generator);
536  _generator = NULL;
537  // Fall through.
538 
539  case DS_done:
540  case DS_cont:
541  case DS_pickup:
542  case DS_exit:
543  case DS_pause:
544  // Legitimate value.
545  Py_DECREF(result);
546  return (DoneStatus) retval;
547 
548  case -1:
549  // Legacy value.
550  Py_DECREF(result);
551  return DS_done;
552 
553  default:
554  // Unexpected value.
555  break;
556  }
557  }
558 
559  ostringstream strm;
560 #if PY_MAJOR_VERSION >= 3
561  PyObject *str = PyObject_ASCII(result);
562  if (str == NULL) {
563  str = PyUnicode_FromString("<repr error>");
564  }
565  strm
566  << *this << " returned " << PyUnicode_AsUTF8(str);
567 #else
568  PyObject *str = PyObject_Repr(result);
569  if (str == NULL) {
570  str = PyString_FromString("<repr error>");
571  }
572  strm
573  << *this << " returned " << PyString_AsString(str);
574 #endif
575  Py_DECREF(str);
576  Py_DECREF(result);
577  string message = strm.str();
578  nassert_raise(message);
579 
580  return DS_interrupt;
581 }
582 
583 ////////////////////////////////////////////////////////////////////
584 // Function: PythonTask::upon_birth
585 // Access: Protected, Virtual
586 // Description: Override this function to do something useful when the
587 // task has been added to the active queue.
588 //
589 // This function is called with the lock *not* held.
590 ////////////////////////////////////////////////////////////////////
591 void PythonTask::
592 upon_birth(AsyncTaskManager *manager) {
593  AsyncTask::upon_birth(manager);
594  register_to_owner();
595 }
596 
597 ////////////////////////////////////////////////////////////////////
598 // Function: PythonTask::upon_death
599 // Access: Protected, Virtual
600 // Description: Override this function to do something useful when the
601 // task has been removed from the active queue. The
602 // parameter clean_exit is true if the task has been
603 // removed because it exited normally (returning
604 // DS_done), or false if it was removed for some other
605 // reason (e.g. AsyncTaskManager::remove()). By the
606 // time this method is called, _manager has been
607 // cleared, so the parameter manager indicates the
608 // original AsyncTaskManager that owned this task.
609 //
610 // The normal behavior is to throw the done_event only
611 // if clean_exit is true.
612 //
613 // This function is called with the lock *not* held.
614 ////////////////////////////////////////////////////////////////////
615 void PythonTask::
616 upon_death(AsyncTaskManager *manager, bool clean_exit) {
617  AsyncTask::upon_death(manager, clean_exit);
618 
619  if (_upon_death != Py_None) {
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  call_function(_upon_death);
627 
628 #if defined(HAVE_THREADS) && !defined(SIMPLE_THREADS)
629  PyGILState_Release(gstate);
630 #endif
631  }
632  unregister_from_owner();
633 }
634 
635 ////////////////////////////////////////////////////////////////////
636 // Function: PythonTask::register_to_owner
637 // Access: Private
638 // Description: Tells the owner we are now his task.
639 ////////////////////////////////////////////////////////////////////
640 void PythonTask::
641 register_to_owner() {
642  if (_owner != Py_None && !_registered_to_owner) {
643 #if defined(HAVE_THREADS) && !defined(SIMPLE_THREADS)
644  // Use PyGILState to protect this asynchronous call.
645  PyGILState_STATE gstate;
646  gstate = PyGILState_Ensure();
647 #endif
648 
649  _registered_to_owner = true;
650  call_owner_method("_addTask");
651 
652 #if defined(HAVE_THREADS) && !defined(SIMPLE_THREADS)
653  PyGILState_Release(gstate);
654 #endif
655  }
656 }
657 
658 ////////////////////////////////////////////////////////////////////
659 // Function: PythonTask::unregister_from_owner
660 // Access: Private
661 // Description: Tells the owner we are no longer his task.
662 ////////////////////////////////////////////////////////////////////
663 void PythonTask::
664 unregister_from_owner() {
665  // make sure every call to _clearTask corresponds to a call to _addTask
666  if (_owner != Py_None && _registered_to_owner) {
667 #if defined(HAVE_THREADS) && !defined(SIMPLE_THREADS)
668  // Use PyGILState to protect this asynchronous call.
669  PyGILState_STATE gstate;
670  gstate = PyGILState_Ensure();
671 #endif
672 
673  _registered_to_owner = false;
674  call_owner_method("_clearTask");
675 
676 #if defined(HAVE_THREADS) && !defined(SIMPLE_THREADS)
677  PyGILState_Release(gstate);
678 #endif
679  }
680 }
681 
682 ////////////////////////////////////////////////////////////////////
683 // Function: PythonTask::call_owner_method
684 // Access: Private
685 // Description: Calls the indicated method name on the given object,
686 // if defined, passing in the task object as the only
687 // parameter.
688 ////////////////////////////////////////////////////////////////////
689 void PythonTask::
690 call_owner_method(const char *method_name) {
691  if (_owner != Py_None) {
692  PyObject *func = PyObject_GetAttrString(_owner, (char *)method_name);
693  if (func == (PyObject *)NULL) {
694  task_cat.error()
695  << "Owner object added to " << *this << " has no method "
696  << method_name << "().\n";
697 
698  } else {
699  call_function(func);
700  Py_DECREF(func);
701  }
702  }
703 }
704 
705 ////////////////////////////////////////////////////////////////////
706 // Function: PythonTask::call_function
707 // Access: Private
708 // Description: Calls the indicated Python function, passing in the
709 // task object as the only parameter.
710 ////////////////////////////////////////////////////////////////////
711 void PythonTask::
712 call_function(PyObject *function) {
713  if (function != Py_None) {
714  this->ref();
715  PyObject *self =
716  DTool_CreatePyInstanceTyped(this, Dtool_TypedReferenceCount,
717  true, false, get_type_index());
718  PyObject *args = Py_BuildValue("(O)", self);
719  Py_DECREF(self);
720 
721  PyObject *result = PyObject_CallObject(function, args);
722  Py_XDECREF(result);
723  Py_DECREF(args);
724  }
725 }
726 
727 #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
void ref() const
Explicitly increments the reference count.
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
int get_type_index() const
Returns the internal index number associated with this object&#39;s TypeHandle, a unique number for each ...
Definition: typedObject.I:52