Panda3D
 All Classes Functions Variables Enumerations
pythonTask.cxx
00001 // Filename: pythonTask.cxx
00002 // Created by:  drose (16Sep08)
00003 //
00004 ////////////////////////////////////////////////////////////////////
00005 //
00006 // PANDA 3D SOFTWARE
00007 // Copyright (c) Carnegie Mellon University.  All rights reserved.
00008 //
00009 // All use of this software is subject to the terms of the revised BSD
00010 // license.  You should have received a copy of this license along
00011 // with this source code in a file named "LICENSE."
00012 //
00013 ////////////////////////////////////////////////////////////////////
00014 
00015 #include "pythonTask.h"
00016 #include "pnotify.h"
00017 
00018 #ifdef HAVE_PYTHON
00019 #include "py_panda.h"  
00020 
00021 TypeHandle PythonTask::_type_handle;
00022 
00023 #ifndef CPPPARSER
00024 IMPORT_THIS struct Dtool_PyTypedObject Dtool_TypedReferenceCount;
00025 #endif
00026 
00027 ////////////////////////////////////////////////////////////////////
00028 //     Function: PythonTask::Constructor
00029 //       Access: Published
00030 //  Description:
00031 ////////////////////////////////////////////////////////////////////
00032 PythonTask::
00033 PythonTask(PyObject *function, const string &name) :
00034   AsyncTask(name)
00035 {
00036   _function = NULL;
00037   _args = NULL;
00038   _upon_death = NULL;
00039   _owner = NULL;
00040   _registered_to_owner = false;
00041   _generator = NULL;
00042 
00043   set_function(function);
00044   set_args(Py_None, true);
00045   set_upon_death(Py_None);
00046   set_owner(Py_None);
00047 
00048   _dict = PyDict_New();
00049 
00050 #ifndef SIMPLE_THREADS
00051   // Ensure that the Python threading system is initialized and ready
00052   // to go.
00053 #ifdef WITH_THREAD  // This symbol defined within Python.h
00054   PyEval_InitThreads();
00055 #endif
00056 #endif
00057 }
00058 
00059 ////////////////////////////////////////////////////////////////////
00060 //     Function: PythonTask::Destructor
00061 //       Access: Published, Virtual
00062 //  Description:
00063 ////////////////////////////////////////////////////////////////////
00064 PythonTask::
00065 ~PythonTask() {
00066   Py_DECREF(_function);
00067   Py_DECREF(_args);
00068   Py_DECREF(_dict);
00069   Py_XDECREF(_generator);
00070   Py_XDECREF(_owner);
00071   Py_XDECREF(_upon_death);
00072 }
00073 
00074 ////////////////////////////////////////////////////////////////////
00075 //     Function: PythonTask::set_function
00076 //       Access: Published
00077 //  Description: Replaces the function that is called when the task
00078 //               runs.  The parameter should be a Python callable
00079 //               object.
00080 ////////////////////////////////////////////////////////////////////
00081 void PythonTask::
00082 set_function(PyObject *function) {
00083   Py_XDECREF(_function);
00084 
00085   _function = function;
00086   Py_INCREF(_function);
00087   if (_function != Py_None && !PyCallable_Check(_function)) {
00088     nassert_raise("Invalid function passed to PythonTask");
00089   }
00090 }
00091 
00092 ////////////////////////////////////////////////////////////////////
00093 //     Function: PythonTask::get_function
00094 //       Access: Published
00095 //  Description: Returns the function that is called when the task
00096 //               runs.
00097 ////////////////////////////////////////////////////////////////////
00098 PyObject *PythonTask::
00099 get_function() {
00100   Py_INCREF(_function);
00101   return _function;
00102 }
00103 
00104 ////////////////////////////////////////////////////////////////////
00105 //     Function: PythonTask::set_args
00106 //       Access: Published
00107 //  Description: Replaces the argument list that is passed to the task
00108 //               function.  The parameter should be a tuple or list of
00109 //               arguments, or None to indicate the empty list.
00110 ////////////////////////////////////////////////////////////////////
00111 void PythonTask::
00112 set_args(PyObject *args, bool append_task) {
00113   Py_XDECREF(_args);
00114   _args = NULL;
00115     
00116   if (args == Py_None) {
00117     // None means no arguments; create an empty tuple.
00118     _args = PyTuple_New(0);
00119   } else {
00120     if (PySequence_Check(args)) {
00121       _args = PySequence_Tuple(args);
00122     }
00123   }
00124 
00125   if (_args == NULL) {
00126     nassert_raise("Invalid args passed to PythonTask");
00127     _args = PyTuple_New(0);
00128   }
00129 
00130   _append_task = append_task;
00131 }
00132 
00133 ////////////////////////////////////////////////////////////////////
00134 //     Function: PythonTask::get_args
00135 //       Access: Published
00136 //  Description: Returns the argument list that is passed to the task
00137 //               function.
00138 ////////////////////////////////////////////////////////////////////
00139 PyObject *PythonTask::
00140 get_args() {
00141   if (_append_task) {
00142     // If we want to append the task, we have to create a new tuple
00143     // with space for one more at the end.  We have to do this
00144     // dynamically each time, to avoid storing the task itself in its
00145     // own arguments list, and thereby creating a cyclical reference.
00146 
00147     int num_args = PyTuple_GET_SIZE(_args);
00148     PyObject *with_task = PyTuple_New(num_args + 1);
00149     for (int i = 0; i < num_args; ++i) {
00150       PyObject *item = PyTuple_GET_ITEM(_args, i);
00151       Py_INCREF(item);
00152       PyTuple_SET_ITEM(with_task, i, item);
00153     }
00154 
00155     this->ref();
00156     PyObject *self = 
00157       DTool_CreatePyInstanceTyped(this, Dtool_TypedReferenceCount,
00158                                   true, false, get_type_index());
00159     PyTuple_SET_ITEM(with_task, num_args, self);
00160     return with_task;
00161 
00162   } else {
00163     Py_INCREF(_args);
00164     return _args;
00165   }
00166 }
00167 
00168 ////////////////////////////////////////////////////////////////////
00169 //     Function: PythonTask::set_upon_death
00170 //       Access: Published
00171 //  Description: Replaces the function that is called when the task
00172 //               finishes.  The parameter should be a Python callable
00173 //               object.
00174 ////////////////////////////////////////////////////////////////////
00175 void PythonTask::
00176 set_upon_death(PyObject *upon_death) {
00177   Py_XDECREF(_upon_death);
00178 
00179   _upon_death = upon_death;
00180   Py_INCREF(_upon_death);
00181   if (_upon_death != Py_None && !PyCallable_Check(_upon_death)) {
00182     nassert_raise("Invalid upon_death function passed to PythonTask");
00183   }
00184 }
00185 
00186 ////////////////////////////////////////////////////////////////////
00187 //     Function: PythonTask::get_upon_death
00188 //       Access: Published
00189 //  Description: Returns the function that is called when the task
00190 //               finishes.
00191 ////////////////////////////////////////////////////////////////////
00192 PyObject *PythonTask::
00193 get_upon_death() {
00194   Py_INCREF(_upon_death);
00195   return _upon_death;
00196 }
00197 
00198 ////////////////////////////////////////////////////////////////////
00199 //     Function: PythonTask::set_owner
00200 //       Access: Published
00201 //  Description: Specifies a Python object that serves as the "owner"
00202 //               for the task.  This owner object must have two
00203 //               methods: _addTask() and _clearTask(), which will be
00204 //               called with one parameter, the task object.
00205 //
00206 //               owner._addTask() is called when the task is added
00207 //               into the active task list, and owner._clearTask() is
00208 //               called when it is removed.
00209 ////////////////////////////////////////////////////////////////////
00210 void PythonTask::
00211 set_owner(PyObject *owner) {
00212   if (_owner != NULL && _owner != Py_None && _state != S_inactive) {
00213     unregister_from_owner();
00214   }
00215 
00216   Py_XDECREF(_owner);
00217   _owner = owner;
00218   Py_INCREF(_owner);
00219 
00220   if (_owner != Py_None && _state != S_inactive) {
00221     register_to_owner();
00222   }
00223 }
00224 
00225 ////////////////////////////////////////////////////////////////////
00226 //     Function: PythonTask::get_owner
00227 //       Access: Published
00228 //  Description: Returns the "owner" object.  See set_owner().
00229 ////////////////////////////////////////////////////////////////////
00230 PyObject *PythonTask::
00231 get_owner() {
00232   Py_INCREF(_owner);
00233   return _owner;
00234 }
00235 
00236 ////////////////////////////////////////////////////////////////////
00237 //     Function: PythonTask::__setattr__
00238 //       Access: Published
00239 //  Description: Maps from an expression like "task.attr_name = v".
00240 //               This is customized here so we can support some
00241 //               traditional task interfaces that supported directly
00242 //               assigning certain values.  We also support adding
00243 //               arbitrary data to the Task object.
00244 ////////////////////////////////////////////////////////////////////
00245 int PythonTask::
00246 __setattr__(const string &attr_name, PyObject *v) {
00247   if (task_cat.is_debug()) {
00248     PyObject *str = PyObject_Repr(v);
00249     task_cat.debug() 
00250       << *this << ": task." << attr_name << " = "
00251       << PyString_AsString(str) << "\n";
00252     Py_DECREF(str);
00253   }
00254 
00255   if (attr_name == "delayTime") {
00256     if (v == Py_None) {
00257       clear_delay();
00258     } else {
00259       double delay = PyFloat_AsDouble(v);
00260       if (!PyErr_Occurred()) {
00261         set_delay(delay);
00262       }
00263     }
00264 
00265   } else if (attr_name == "name") {
00266     char *name = PyString_AsString(v);
00267     if (name != (char *)NULL) {
00268       set_name(name);
00269     }
00270 
00271   } else if (attr_name == "id" || attr_name == "time" || 
00272              attr_name == "frame" || attr_name == "wakeTime") {
00273     nassert_raise("Cannot set constant value");
00274     return true;
00275 
00276   } else {
00277     return PyDict_SetItemString(_dict, attr_name.c_str(), v);
00278   }
00279 
00280   return 0;
00281 }
00282 
00283 ////////////////////////////////////////////////////////////////////
00284 //     Function: PythonTask::__setattr__
00285 //       Access: Published
00286 //  Description: Maps from an expression like "del task.attr_name".
00287 //               This is customized here so we can support some
00288 //               traditional task interfaces that supported directly
00289 //               assigning certain values.  We also support adding
00290 //               arbitrary data to the Task object.
00291 ////////////////////////////////////////////////////////////////////
00292 int PythonTask::
00293 __setattr__(const string &attr_name) {
00294   return PyDict_DelItemString(_dict, attr_name.c_str());
00295 }
00296 
00297 ////////////////////////////////////////////////////////////////////
00298 //     Function: PythonTask::__getattr__
00299 //       Access: Published
00300 //  Description: Maps from an expression like "task.attr_name".
00301 //               This is customized here so we can support some
00302 //               traditional task interfaces that supported directly
00303 //               querying certain values.  We also support adding
00304 //               arbitrary data to the Task object.
00305 ////////////////////////////////////////////////////////////////////
00306 PyObject *PythonTask::
00307 __getattr__(const string &attr_name) const {
00308   if (attr_name == "time") {
00309     return PyFloat_FromDouble(get_elapsed_time());
00310   } else if (attr_name == "name") {
00311     return PyString_FromString(get_name().c_str());
00312   } else if (attr_name == "wakeTime") {
00313     return PyFloat_FromDouble(get_wake_time());
00314   } else if (attr_name == "delayTime") {
00315     if (!has_delay()) {
00316       Py_RETURN_NONE;
00317     }
00318     return PyFloat_FromDouble(get_delay());
00319   } else if (attr_name == "frame") {
00320     return PyInt_FromLong(get_elapsed_frames());
00321   } else if (attr_name == "id") {
00322     return PyInt_FromLong(_task_id);
00323   } else {
00324     return PyMapping_GetItemString(_dict, (char *)attr_name.c_str());
00325   }
00326 }
00327 
00328 ////////////////////////////////////////////////////////////////////
00329 //     Function: PythonTask::is_runnable
00330 //       Access: Protected, Virtual
00331 //  Description: Override this function to return true if the task can
00332 //               be successfully executed, false if it cannot.  Mainly
00333 //               intended as a sanity check when attempting to add the
00334 //               task to a task manager.
00335 //
00336 //               This function is called with the lock held.
00337 ////////////////////////////////////////////////////////////////////
00338 bool PythonTask::
00339 is_runnable() {
00340   return _function != Py_None;
00341 }
00342 
00343 ////////////////////////////////////////////////////////////////////
00344 //     Function: PythonTask::do_task
00345 //       Access: Protected, Virtual
00346 //  Description: Override this function to do something useful for the
00347 //               task.
00348 //
00349 //               This function is called with the lock *not* held.
00350 ////////////////////////////////////////////////////////////////////
00351 AsyncTask::DoneStatus PythonTask::
00352 do_task() {
00353 #if defined(HAVE_THREADS) && !defined(SIMPLE_THREADS)
00354   // Use PyGILState to protect this asynchronous call.
00355   PyGILState_STATE gstate;
00356   gstate = PyGILState_Ensure();
00357 #endif
00358 
00359   DoneStatus result = do_python_task();
00360 
00361 #if defined(HAVE_THREADS) && !defined(SIMPLE_THREADS)
00362   PyGILState_Release(gstate);
00363 #endif
00364 
00365   return result;
00366 }
00367 
00368 ////////////////////////////////////////////////////////////////////
00369 //     Function: PythonTask::do_python_task
00370 //       Access: Protected
00371 //  Description: The Python calls that implement do_task().  This
00372 //               function is separate so we can acquire the Python
00373 //               interpretor lock while it runs.
00374 ////////////////////////////////////////////////////////////////////
00375 AsyncTask::DoneStatus PythonTask::
00376 do_python_task() {
00377   PyObject *result = NULL;
00378 
00379   if (_generator == (PyObject *)NULL) {
00380     // We are calling the function directly.
00381     PyObject *args = get_args();
00382     result = 
00383       Thread::get_current_thread()->call_python_func(_function, args);
00384     Py_DECREF(args);
00385 
00386 #ifdef PyGen_Check
00387     if (result != (PyObject *)NULL && PyGen_Check(result)) {
00388       // The function has yielded a generator.  We will call into that
00389       // henceforth, instead of calling the function from the top
00390       // again.
00391       if (task_cat.is_debug()) {
00392         PyObject *str = PyObject_Repr(_function);
00393         task_cat.debug() 
00394           << PyString_AsString(str) << " in " << *this
00395           << " yielded a generator.\n";
00396         Py_DECREF(str);
00397       }
00398       _generator = result;
00399       result = NULL;
00400     }
00401 #endif
00402   }
00403 
00404   if (_generator != (PyObject *)NULL) {
00405     // We are calling a generator.
00406     PyObject *func = PyObject_GetAttrString(_generator, "next");
00407     nassertr(func != (PyObject *)NULL, DS_interrupt);
00408 
00409     result = PyObject_CallObject(func, NULL);
00410     Py_DECREF(func);
00411 
00412     if (result == (PyObject *)NULL && PyErr_Occurred() &&
00413         PyErr_ExceptionMatches(PyExc_StopIteration)) {
00414       // "Catch" StopIteration and treat it like DS_done.
00415       PyErr_Clear();
00416       Py_DECREF(_generator);
00417       _generator = NULL;
00418       return DS_done;
00419     }
00420   }
00421 
00422   if (result == (PyObject *)NULL) {
00423     task_cat.error()
00424       << "Exception occurred in " << *this << "\n";
00425     return DS_interrupt;
00426   }
00427 
00428   if (result == Py_None) {
00429     Py_DECREF(result);
00430     return DS_done;
00431   }
00432 
00433   if (PyInt_Check(result)) {
00434     int retval = PyInt_AS_LONG(result);
00435     switch (retval) {
00436     case DS_again:
00437       Py_XDECREF(_generator);
00438       _generator = NULL;
00439       // Fall through.
00440 
00441     case DS_done:
00442     case DS_cont:
00443     case DS_pickup:
00444     case DS_exit:
00445     case DS_pause:
00446       // Legitimate value.
00447       Py_DECREF(result);
00448       return (DoneStatus)retval;
00449 
00450     case -1:
00451       // Legacy value.
00452       Py_DECREF(result);
00453       return DS_done;
00454 
00455     default:
00456       // Unexpected value.
00457       break;
00458     }
00459   }
00460 
00461   PyObject *str = PyObject_Repr(result);
00462   ostringstream strm;
00463   strm
00464     << *this << " returned " << PyString_AsString(str);
00465   Py_DECREF(str);
00466   Py_DECREF(result);
00467   string message = strm.str();
00468   nassert_raise(message);
00469 
00470   return DS_interrupt;
00471 }
00472 
00473 ////////////////////////////////////////////////////////////////////
00474 //     Function: PythonTask::upon_birth
00475 //       Access: Protected, Virtual
00476 //  Description: Override this function to do something useful when the
00477 //               task has been added to the active queue.
00478 //
00479 //               This function is called with the lock *not* held.
00480 ////////////////////////////////////////////////////////////////////
00481 void PythonTask::
00482 upon_birth(AsyncTaskManager *manager) {
00483   AsyncTask::upon_birth(manager);
00484   register_to_owner();
00485 }
00486 
00487 ////////////////////////////////////////////////////////////////////
00488 //     Function: PythonTask::upon_death
00489 //       Access: Protected, Virtual
00490 //  Description: Override this function to do something useful when the
00491 //               task has been removed from the active queue.  The
00492 //               parameter clean_exit is true if the task has been
00493 //               removed because it exited normally (returning
00494 //               DS_done), or false if it was removed for some other
00495 //               reason (e.g. AsyncTaskManager::remove()).  By the
00496 //               time this method is called, _manager has been
00497 //               cleared, so the parameter manager indicates the
00498 //               original AsyncTaskManager that owned this task.
00499 //
00500 //               The normal behavior is to throw the done_event only
00501 //               if clean_exit is true.
00502 //
00503 //               This function is called with the lock *not* held.
00504 ////////////////////////////////////////////////////////////////////
00505 void PythonTask::
00506 upon_death(AsyncTaskManager *manager, bool clean_exit) {
00507   AsyncTask::upon_death(manager, clean_exit);
00508 
00509   if (_upon_death != Py_None) {
00510 #if defined(HAVE_THREADS) && !defined(SIMPLE_THREADS)
00511     // Use PyGILState to protect this asynchronous call.
00512     PyGILState_STATE gstate;
00513     gstate = PyGILState_Ensure();
00514 #endif
00515     
00516     call_function(_upon_death);
00517     
00518 #if defined(HAVE_THREADS) && !defined(SIMPLE_THREADS)
00519     PyGILState_Release(gstate);
00520 #endif
00521   }
00522   unregister_from_owner();
00523 }
00524 
00525 ////////////////////////////////////////////////////////////////////
00526 //     Function: PythonTask::register_to_owner
00527 //       Access: Private
00528 //  Description: Tells the owner we are now his task.
00529 ////////////////////////////////////////////////////////////////////
00530 void PythonTask::
00531 register_to_owner() {
00532   if (_owner != Py_None && !_registered_to_owner) {
00533 #if defined(HAVE_THREADS) && !defined(SIMPLE_THREADS)
00534     // Use PyGILState to protect this asynchronous call.
00535     PyGILState_STATE gstate;
00536     gstate = PyGILState_Ensure();
00537 #endif
00538     
00539     _registered_to_owner = true;
00540     call_owner_method("_addTask");
00541     
00542 #if defined(HAVE_THREADS) && !defined(SIMPLE_THREADS)
00543     PyGILState_Release(gstate);
00544 #endif
00545   }
00546 }
00547 
00548 ////////////////////////////////////////////////////////////////////
00549 //     Function: PythonTask::unregister_from_owner
00550 //       Access: Private
00551 //  Description: Tells the owner we are no longer his task.
00552 ////////////////////////////////////////////////////////////////////
00553 void PythonTask::
00554 unregister_from_owner() {
00555   // make sure every call to _clearTask corresponds to a call to _addTask
00556   if (_owner != Py_None && _registered_to_owner) {
00557 #if defined(HAVE_THREADS) && !defined(SIMPLE_THREADS)
00558     // Use PyGILState to protect this asynchronous call.
00559     PyGILState_STATE gstate;
00560     gstate = PyGILState_Ensure();
00561 #endif
00562     
00563     _registered_to_owner = false;
00564     call_owner_method("_clearTask");
00565     
00566 #if defined(HAVE_THREADS) && !defined(SIMPLE_THREADS)
00567     PyGILState_Release(gstate);
00568 #endif
00569   }
00570 }
00571 
00572 ////////////////////////////////////////////////////////////////////
00573 //     Function: PythonTask::call_owner_method
00574 //       Access: Private
00575 //  Description: Calls the indicated method name on the given object,
00576 //               if defined, passing in the task object as the only
00577 //               parameter.
00578 ////////////////////////////////////////////////////////////////////
00579 void PythonTask::
00580 call_owner_method(const char *method_name) {
00581   if (_owner != Py_None) {
00582     PyObject *func = PyObject_GetAttrString(_owner, (char *)method_name);
00583     if (func == (PyObject *)NULL) {
00584       PyObject *str = PyObject_Repr(_owner);
00585       task_cat.error() 
00586         << "Owner object " << PyString_AsString(str) << " added to "
00587         << *this << " has no method " << method_name << "().\n";
00588       Py_DECREF(str);
00589 
00590     } else {
00591       call_function(func);
00592       Py_DECREF(func);
00593     }
00594   }
00595 }
00596 
00597 ////////////////////////////////////////////////////////////////////
00598 //     Function: PythonTask::call_function
00599 //       Access: Private
00600 //  Description: Calls the indicated Python function, passing in the
00601 //               task object as the only parameter.
00602 ////////////////////////////////////////////////////////////////////
00603 void PythonTask::
00604 call_function(PyObject *function) {
00605   if (function != Py_None) {
00606     this->ref();
00607     PyObject *self = 
00608       DTool_CreatePyInstanceTyped(this, Dtool_TypedReferenceCount,
00609                                   true, false, get_type_index());
00610     PyObject *args = Py_BuildValue("(O)", self);
00611     Py_DECREF(self);
00612     
00613     PyObject *result = PyObject_CallObject(function, args);
00614     Py_XDECREF(result);
00615     Py_DECREF(args);
00616   }
00617 }
00618 
00619 #endif  // HAVE_PYTHON
 All Classes Functions Variables Enumerations