panda3d/panda/src/event/pythonTask.cxx
2008-10-10 21:30:11 +00:00

579 lines
18 KiB
C++

// Filename: pythonTask.cxx
// Created by: drose (16Sep08)
//
////////////////////////////////////////////////////////////////////
//
// PANDA 3D SOFTWARE
// Copyright (c) Carnegie Mellon University. All rights reserved.
//
// All use of this software is subject to the terms of the revised BSD
// license. You should have received a copy of this license along
// with this source code in a file named "LICENSE."
//
////////////////////////////////////////////////////////////////////
#include "pythonTask.h"
#include "pnotify.h"
#ifdef HAVE_PYTHON
#include "py_panda.h"
TypeHandle PythonTask::_type_handle;
#ifndef CPPPARSER
IMPORT_THIS struct Dtool_PyTypedObject Dtool_PythonTask;
#endif
////////////////////////////////////////////////////////////////////
// Function: PythonTask::Constructor
// Access: Published
// Description:
////////////////////////////////////////////////////////////////////
PythonTask::
PythonTask(PyObject *function, const string &name) :
AsyncTask(name)
{
_function = NULL;
_args = NULL;
_upon_death = NULL;
_owner = NULL;
_generator = NULL;
set_function(function);
set_args(Py_None, true);
set_upon_death(Py_None);
set_owner(Py_None);
_dict = PyDict_New();
#ifndef SIMPLE_THREADS
// Ensure that the Python threading system is initialized and ready
// to go.
PyEval_InitThreads();
#endif
}
////////////////////////////////////////////////////////////////////
// Function: PythonTask::Destructor
// Access: Published, Virtual
// Description:
////////////////////////////////////////////////////////////////////
PythonTask::
~PythonTask() {
Py_DECREF(_function);
Py_DECREF(_args);
Py_DECREF(_dict);
Py_XDECREF(_generator);
}
////////////////////////////////////////////////////////////////////
// Function: PythonTask::set_function
// Access: Published
// Description: Replaces the function that is called when the task
// runs. The parameter should be a Python callable
// object.
////////////////////////////////////////////////////////////////////
void PythonTask::
set_function(PyObject *function) {
Py_XDECREF(_function);
_function = function;
Py_INCREF(_function);
if (_function != Py_None && !PyCallable_Check(_function)) {
nassert_raise("Invalid function passed to PythonTask");
}
}
////////////////////////////////////////////////////////////////////
// Function: PythonTask::get_function
// Access: Published
// Description: Returns the function that is called when the task
// runs.
////////////////////////////////////////////////////////////////////
PyObject *PythonTask::
get_function() {
Py_INCREF(_function);
return _function;
}
////////////////////////////////////////////////////////////////////
// Function: PythonTask::set_args
// Access: Published
// Description: Replaces the argument list that is passed to the task
// function. The parameter should be a tuple or list of
// arguments, or None to indicate the empty list.
////////////////////////////////////////////////////////////////////
void PythonTask::
set_args(PyObject *args, bool append_task) {
Py_XDECREF(_args);
_args = NULL;
if (args == Py_None) {
// None means no arguments; create an empty tuple.
_args = PyTuple_New(0);
} else {
if (PySequence_Check(args)) {
_args = PySequence_Tuple(args);
}
}
if (_args == NULL) {
nassert_raise("Invalid args passed to PythonTask");
_args = PyTuple_New(0);
}
_append_task = append_task;
}
////////////////////////////////////////////////////////////////////
// Function: PythonTask::get_args
// Access: Published
// Description: Returns the argument list that is passed to the task
// function.
////////////////////////////////////////////////////////////////////
PyObject *PythonTask::
get_args() {
if (_append_task) {
// If we want to append the task, we have to create a new tuple
// with space for one more at the end. We have to do this
// dynamically each time, to avoid storing the task itself in its
// own arguments list, and thereby creating a cyclical reference.
int num_args = PyTuple_GET_SIZE(_args);
PyObject *with_task = PyTuple_New(num_args + 1);
for (int i = 0; i < num_args; ++i) {
PyObject *item = PyTuple_GET_ITEM(_args, i);
Py_INCREF(item);
PyTuple_SET_ITEM(with_task, i, item);
}
this->ref();
PyObject *self =
DTool_CreatePyInstanceTyped(this, Dtool_PythonTask,
true, false, get_type_index());
PyTuple_SET_ITEM(with_task, num_args, self);
return with_task;
} else {
Py_INCREF(_args);
return _args;
}
}
////////////////////////////////////////////////////////////////////
// Function: PythonTask::set_upon_death
// Access: Published
// Description: Replaces the function that is called when the task
// finishes. The parameter should be a Python callable
// object.
////////////////////////////////////////////////////////////////////
void PythonTask::
set_upon_death(PyObject *upon_death) {
Py_XDECREF(_upon_death);
_upon_death = upon_death;
Py_INCREF(_upon_death);
if (_upon_death != Py_None && !PyCallable_Check(_upon_death)) {
nassert_raise("Invalid upon_death function passed to PythonTask");
}
}
////////////////////////////////////////////////////////////////////
// Function: PythonTask::get_upon_death
// Access: Published
// Description: Returns the function that is called when the task
// finishes.
////////////////////////////////////////////////////////////////////
PyObject *PythonTask::
get_upon_death() {
Py_INCREF(_upon_death);
return _upon_death;
}
////////////////////////////////////////////////////////////////////
// Function: PythonTask::set_owner
// Access: Published
// Description: Specifies a Python object that serves as the "owner"
// for the task. This owner object must have two
// methods: _addTask() and _clearTask(), which will be
// called with one parameter, the task object.
//
// owner._addTask() is called when the task is added
// into the active task list, and owner._clearTask() is
// called when it is removed.
////////////////////////////////////////////////////////////////////
void PythonTask::
set_owner(PyObject *owner) {
if (_owner != NULL && _owner != Py_None && _state != S_inactive) {
upon_death(false);
}
Py_XDECREF(_owner);
_owner = owner;
Py_INCREF(_owner);
if (_owner != Py_None && _state != S_inactive) {
upon_birth();
}
}
////////////////////////////////////////////////////////////////////
// Function: PythonTask::get_owner
// Access: Published
// Description: Returns the "owner" object. See set_owner().
////////////////////////////////////////////////////////////////////
PyObject *PythonTask::
get_owner() {
Py_INCREF(_owner);
return _owner;
}
////////////////////////////////////////////////////////////////////
// Function: PythonTask::__setattr__
// Access: Published
// Description: Maps from an expression like "task.attr_name = v".
// This is customized here so we can support some
// traditional task interfaces that supported directly
// assigning certain values. We also support adding
// arbitrary data to the Task object.
////////////////////////////////////////////////////////////////////
int PythonTask::
__setattr__(const string &attr_name, PyObject *v) {
if (task_cat.is_debug()) {
PyObject *str = PyObject_Repr(v);
task_cat.debug()
<< *this << ": task." << attr_name << " = "
<< PyString_AsString(str) << "\n";
Py_DECREF(str);
}
if (attr_name == "delayTime") {
if (v == Py_None) {
clear_delay();
} else {
double delay = PyFloat_AsDouble(v);
if (!PyErr_Occurred()) {
set_delay(delay);
}
}
} else if (attr_name == "name") {
char *name = PyString_AsString(v);
if (name != (char *)NULL) {
set_name(name);
}
} else if (attr_name == "id" || attr_name == "time" ||
attr_name == "frame" || attr_name == "wakeTime") {
nassert_raise("Cannot set constant value");
return true;
} else {
return PyDict_SetItemString(_dict, attr_name.c_str(), v);
}
return 0;
}
////////////////////////////////////////////////////////////////////
// Function: PythonTask::__setattr__
// Access: Published
// Description: Maps from an expression like "del task.attr_name".
// This is customized here so we can support some
// traditional task interfaces that supported directly
// assigning certain values. We also support adding
// arbitrary data to the Task object.
////////////////////////////////////////////////////////////////////
int PythonTask::
__setattr__(const string &attr_name) {
return PyDict_DelItemString(_dict, attr_name.c_str());
}
////////////////////////////////////////////////////////////////////
// Function: PythonTask::__getattr__
// Access: Published
// Description: Maps from an expression like "task.attr_name".
// This is customized here so we can support some
// traditional task interfaces that supported directly
// querying certain values. We also support adding
// arbitrary data to the Task object.
////////////////////////////////////////////////////////////////////
PyObject *PythonTask::
__getattr__(const string &attr_name) const {
if (attr_name == "time") {
return PyFloat_FromDouble(get_elapsed_time());
} else if (attr_name == "name") {
return PyString_FromString(get_name().c_str());
} else if (attr_name == "wakeTime") {
if (get_state() != S_sleeping) {
Py_RETURN_NONE;
}
return PyFloat_FromDouble(get_wake_time());
} else if (attr_name == "delayTime") {
if (!has_delay()) {
Py_RETURN_NONE;
}
return PyFloat_FromDouble(get_delay());
} else if (attr_name == "frame") {
return PyInt_FromLong(get_elapsed_frames());
} else if (attr_name == "id") {
return PyInt_FromLong(_task_id);
} else {
return PyMapping_GetItemString(_dict, (char *)attr_name.c_str());
}
}
////////////////////////////////////////////////////////////////////
// Function: PythonTask::is_runnable
// Access: Protected, Virtual
// Description: Override this function to return true if the task can
// be successfully executed, false if it cannot. Mainly
// intended as a sanity check when attempting to add the
// task to a task manager.
//
// This function is called with the lock held.
////////////////////////////////////////////////////////////////////
bool PythonTask::
is_runnable() {
return _function != Py_None;
}
////////////////////////////////////////////////////////////////////
// Function: PythonTask::do_task
// Access: Protected, Virtual
// Description: Override this function to do something useful for the
// task.
//
// This function is called with the lock *not* held.
////////////////////////////////////////////////////////////////////
AsyncTask::DoneStatus PythonTask::
do_task() {
#if defined(HAVE_THREADS) && !defined(SIMPLE_THREADS)
// Use PyGILState to protect this asynchronous call.
PyGILState_STATE gstate;
gstate = PyGILState_Ensure();
#endif
DoneStatus result = do_python_task();
#if defined(HAVE_THREADS) && !defined(SIMPLE_THREADS)
PyGILState_Release(gstate);
#endif
return result;
}
////////////////////////////////////////////////////////////////////
// Function: PythonTask::do_python_task
// Access: Protected
// Description: The Python calls that implement do_task(). This
// function is separate so we can acquire the Python
// interpretor lock while it runs.
////////////////////////////////////////////////////////////////////
AsyncTask::DoneStatus PythonTask::
do_python_task() {
PyObject *result = NULL;
if (_generator == (PyObject *)NULL) {
// We are calling the function directly.
PyObject *args = get_args();
result =
Thread::get_current_thread()->call_python_func(_function, args);
Py_DECREF(args);
if (result != (PyObject *)NULL && PyGen_Check(result)) {
// The function has yielded a generator. We will call into that
// henceforth, instead of calling the function from the top
// again.
if (task_cat.is_debug()) {
PyObject *str = PyObject_Repr(_function);
task_cat.debug()
<< PyString_AsString(str) << " in " << *this
<< " yielded a generator.\n";
Py_DECREF(str);
}
_generator = result;
result = NULL;
}
}
if (_generator != (PyObject *)NULL) {
// We are calling a generator.
PyObject *func = PyObject_GetAttrString(_generator, "next");
nassertr(func != (PyObject *)NULL, DS_interrupt);
result = PyObject_CallObject(func, NULL);
Py_DECREF(func);
if (result == (PyObject *)NULL && PyErr_Occurred() &&
PyErr_ExceptionMatches(PyExc_StopIteration)) {
// "Catch" StopIteration and treat it like DS_done.
PyErr_Clear();
Py_DECREF(_generator);
_generator = NULL;
return DS_done;
}
}
if (result == (PyObject *)NULL) {
task_cat.error()
<< "Exception occurred in " << *this << "\n";
return DS_interrupt;
}
if (result == Py_None) {
Py_DECREF(result);
return DS_done;
}
if (PyInt_Check(result)) {
int retval = PyInt_AS_LONG(result);
switch (retval) {
case DS_again:
Py_XDECREF(_generator);
_generator = NULL;
// Fall through.
case DS_done:
case DS_cont:
case DS_pickup:
case DS_exit:
case DS_pause:
// Legitimate value.
Py_DECREF(result);
return (DoneStatus)retval;
case -1:
// Legacy value.
Py_DECREF(result);
return DS_done;
default:
// Unexpected value.
break;
}
}
PyObject *str = PyObject_Repr(result);
ostringstream strm;
strm
<< *this << " returned " << PyString_AsString(str);
Py_DECREF(str);
Py_DECREF(result);
string message = strm.str();
nassert_raise(message);
return DS_interrupt;
}
////////////////////////////////////////////////////////////////////
// Function: PythonTask::upon_birth
// Access: Protected, Virtual
// Description: Override this function to do something useful when the
// task has been added to the active queue.
//
// This function is called with the lock *not* held.
////////////////////////////////////////////////////////////////////
void PythonTask::
upon_birth() {
AsyncTask::upon_birth();
if (_owner != Py_None) {
#if defined(HAVE_THREADS) && !defined(SIMPLE_THREADS)
// Use PyGILState to protect this asynchronous call.
PyGILState_STATE gstate;
gstate = PyGILState_Ensure();
#endif
call_owner_method("_addTask");
#if defined(HAVE_THREADS) && !defined(SIMPLE_THREADS)
PyGILState_Release(gstate);
#endif
}
}
////////////////////////////////////////////////////////////////////
// Function: PythonTask::upon_death
// Access: Protected, Virtual
// Description: Override this function to do something useful when the
// task has been removed from the active queue. The
// parameter clean_exit is true if the task has been
// removed because it exited normally (returning
// DS_done), or false if it was removed for some other
// reason (e.g. AsyncTaskManager::remove()).
//
// The normal behavior is to throw the done_event only
// if clean_exit is true.
//
// This function is called with the lock *not* held.
////////////////////////////////////////////////////////////////////
void PythonTask::
upon_death(bool clean_exit) {
AsyncTask::upon_death(clean_exit);
if (_owner != Py_None && _upon_death != Py_None) {
#if defined(HAVE_THREADS) && !defined(SIMPLE_THREADS)
// Use PyGILState to protect this asynchronous call.
PyGILState_STATE gstate;
gstate = PyGILState_Ensure();
#endif
call_owner_method("_clearTask");
call_function(_upon_death);
#if defined(HAVE_THREADS) && !defined(SIMPLE_THREADS)
PyGILState_Release(gstate);
#endif
}
}
////////////////////////////////////////////////////////////////////
// Function: PythonTask::call_owner_method
// Access: Protected
// Description: Calls the indicated method name on the given object,
// if defined, passing in the task object as the only
// parameter.
////////////////////////////////////////////////////////////////////
void PythonTask::
call_owner_method(const char *method_name) {
if (_owner != Py_None) {
PyObject *func = PyObject_GetAttrString(_owner, (char *)method_name);
if (func == (PyObject *)NULL) {
PyObject *str = PyObject_Repr(_owner);
task_cat.error()
<< "Owner object " << PyString_AsString(str) << " added to "
<< *this << " has no method " << method_name << "().\n";
Py_DECREF(str);
} else {
call_function(func);
Py_DECREF(func);
}
}
}
////////////////////////////////////////////////////////////////////
// Function: PythonTask::call_function
// Access: Protected
// Description: Calls the indicated Python function, passing in the
// task object as the only parameter.
////////////////////////////////////////////////////////////////////
void PythonTask::
call_function(PyObject *function) {
if (function != Py_None) {
this->ref();
PyObject *self =
DTool_CreatePyInstanceTyped(this, Dtool_PythonTask,
true, false, get_type_index());
PyObject *args = PyTuple_Pack(1, self);
Py_DECREF(self);
PyObject *result = PyObject_CallObject(function, args);
Py_XDECREF(result);
Py_DECREF(args);
}
}
#endif // HAVE_PYTHON