diff --git a/source/gameengine/Expressions/CMakeLists.txt b/source/gameengine/Expressions/CMakeLists.txt index 6907f314503..48c10d75a17 100644 --- a/source/gameengine/Expressions/CMakeLists.txt +++ b/source/gameengine/Expressions/CMakeLists.txt @@ -55,6 +55,7 @@ set(SRC StringValue.cpp Value.cpp VectorValue.cpp + KX_PythonCallBack.cpp BoolValue.h ConstExpr.h @@ -77,6 +78,7 @@ set(SRC Value.h VectorValue.h VoidValue.h + KX_PythonCallBack.h ) blender_add_lib(ge_logic_expressions "${SRC}" "${INC}" "${INC_SYS}") diff --git a/source/gameengine/Expressions/KX_PythonCallBack.cpp b/source/gameengine/Expressions/KX_PythonCallBack.cpp new file mode 100644 index 00000000000..637441d3a9d --- /dev/null +++ b/source/gameengine/Expressions/KX_PythonCallBack.cpp @@ -0,0 +1,116 @@ +/* + * ***** BEGIN GPL LICENSE BLOCK ***** + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * Contributor(s): Porteries Tristan. + * + * ***** END GPL LICENSE BLOCK ***** + */ + +/** \file gameengine/Expressions/KX_PythonCallBack.cpp + * \ingroup expressions + */ + +#include "KX_PythonCallBack.h" +#include +#include + +/** Check if a python value is a function and have the correct number of arguments. + * \param value The python value to check. + * \param minargcount The minimum of arguments possible. + * \param maxargcount The maximum of arguments possible. + * \param r_argcount The number of argument of this function, this variable will be + * changed in the function. + */ +static PyObject *CheckPythonFunction(PyObject *value, unsigned int minargcount, unsigned int maxargcount, unsigned int &r_argcount) +{ + if (PyMethod_Check(value)) { + PyCodeObject *code = ((PyCodeObject *)PyFunction_GET_CODE(PyMethod_GET_FUNCTION(value))); + // *args support + r_argcount = (code->co_flags & CO_VARARGS) ? maxargcount : (code->co_argcount - 1); + } + else if (PyFunction_Check(value)) { + PyCodeObject *code = ((PyCodeObject *)PyFunction_GET_CODE(value)); + // *args support + r_argcount = (code->co_flags & CO_VARARGS) ? maxargcount : code->co_argcount; + } + else { // is not a methode or a function + PyErr_Format(PyExc_TypeError, "items must be functions or methodes, not %s", + Py_TYPE(value)->tp_name); + return NULL; + } + + if (r_argcount < minargcount || r_argcount > maxargcount) { + // wrong number of arguments + PyErr_Format(PyExc_TypeError, "methode or function (%s) has invalid number of arguments (%i) must be between %i and %i", + Py_TYPE(value)->tp_name, r_argcount, minargcount, maxargcount); + return NULL; + } + + return value; +} + +/** Create a python tuple to call a python function + * \param argcount The lenght of the tuple. + * \param arglist The fully list of python arguments [size >= argcount]. + */ +static PyObject *CreatePythonTuple(unsigned int argcount, PyObject **arglist) +{ + PyObject *tuple = PyTuple_New(argcount); + + for (unsigned int i = 0; i < argcount; ++i) { + PyObject *item = arglist[i]; + // increment reference and copy it in a new tuple + Py_INCREF(item); + PyTuple_SET_ITEM(tuple, i, item); + } + + return tuple; +} + +void RunPythonCallBackList(PyObject *functionlist, PyObject **arglist, unsigned int minargcount, unsigned int maxargcount) +{ + unsigned int size = PyList_Size(functionlist); + PyObject *argTuples[(maxargcount - minargcount) + 1] = {NULL}; + + for (unsigned int i = 0; i < size; ++i) { + unsigned int funcargcount = 0; + + PyObject *item = PyList_GET_ITEM(functionlist, i); + PyObject *func = CheckPythonFunction(item, minargcount, maxargcount, funcargcount); + if (!func) { // this item fails the check + PyErr_Print(); + PyErr_Clear(); + continue; + } + + // get correct argument tuple. + PyObject *tuple = argTuples[funcargcount - minargcount]; + if (!tuple) + argTuples[funcargcount - minargcount] = tuple = CreatePythonTuple(funcargcount, arglist); + + PyObject *ret = PyObject_Call(func, tuple, NULL); + if (!ret) { // if ret is NULL this seems that the function doesn't work ! + PyErr_Print(); + PyErr_Clear(); + } + else + Py_DECREF(ret); + } + + for (unsigned int i = 0; i <= (maxargcount - minargcount); ++i) + Py_XDECREF(argTuples[i]); +} diff --git a/source/gameengine/Expressions/KX_PythonCallBack.h b/source/gameengine/Expressions/KX_PythonCallBack.h new file mode 100644 index 00000000000..2ff6e305d67 --- /dev/null +++ b/source/gameengine/Expressions/KX_PythonCallBack.h @@ -0,0 +1,40 @@ +/* + * ***** BEGIN GPL LICENSE BLOCK ***** + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * Contributor(s): Porteries Tristan. + * + * ***** END GPL LICENSE BLOCK ***** + */ + +/** \file KX_PythonCallBack.h + * \ingroup expressions + */ + +#ifndef __KX_PYTHON_CALLBACK_H__ +#define __KX_PYTHON_CALLBACK_H__ + +#include "KX_Python.h" + +/** Execute each functions with at least one argument + * \param functionlist The python list which contains callbacks. + * \param arglist The first item in the tuple to execute callbacks (can be NULL for no arguments). + * \param minargcount The minimum of quantity of arguments possible. + * \param maxargcount The maximum of quantity of arguments possible. + */ +void RunPythonCallBackList(PyObject *functionlist, PyObject **arglist, unsigned int minargcount, unsigned int maxargcount); + +#endif // __KX_PYTHON_CALLBACK_H__ diff --git a/source/gameengine/Ketsji/KX_GameObject.cpp b/source/gameengine/Ketsji/KX_GameObject.cpp index 1536b31d1ba..e464883016e 100644 --- a/source/gameengine/Ketsji/KX_GameObject.cpp +++ b/source/gameengine/Ketsji/KX_GameObject.cpp @@ -69,6 +69,7 @@ #include "BL_ActionManager.h" #include "BL_Action.h" +#include "KX_PythonCallBack.h" #include "PyObjectPlus.h" /* python stuff */ #include "BLI_utildefines.h" #include "python_utildefines.h" @@ -1565,67 +1566,14 @@ void KX_GameObject::RegisterCollisionCallbacks() void KX_GameObject::RunCollisionCallbacks(KX_GameObject *collider, const MT_Vector3 &point, const MT_Vector3 &normal) { #ifdef WITH_PYTHON - Py_ssize_t len; - PyObject* collision_callbacks = m_collisionCallbacks; + if (!m_collisionCallbacks || PyList_GET_SIZE(m_collisionCallbacks) == 0) + return; - if (collision_callbacks && (len=PyList_GET_SIZE(collision_callbacks))) - { - // Argument tuples are created lazily, only when they are needed. - PyObject *args_3 = NULL; - PyObject *args_1 = NULL; // Only for compatibility with pre-2.74 callbacks that take 1 argument. + PyObject *args[] = {collider->GetProxy(), PyObjectFrom(point), PyObjectFrom(normal)}; + RunPythonCallBackList(m_collisionCallbacks, args, 1, ARRAY_SIZE(args)); - PyObject *func; - PyObject *ret; - int co_argcount; - - // Iterate the list and run the callbacks - for (Py_ssize_t pos=0; pos < len; pos++) - { - func = PyList_GET_ITEM(collision_callbacks, pos); - - // Get the number of arguments, supporting functions, methods and generic callables. - if (PyMethod_Check(func)) { - // Take away the 'self' argument for methods. - co_argcount = ((PyCodeObject *)PyFunction_GET_CODE(PyMethod_GET_FUNCTION(func)))->co_argcount - 1; - } else if (PyFunction_Check(func)) { - co_argcount = ((PyCodeObject *)PyFunction_GET_CODE(func))->co_argcount; - } else { - // We'll just assume the callable takes the correct number of arguments. - co_argcount = 3; - } - - // Check whether the function expects the colliding object only, - // or also the point and normal. - if (co_argcount <= 1) { - // One argument, or *args (which gives co_argcount == 0) - if (args_1 == NULL) { - args_1 = PyTuple_New(1); - PyTuple_SET_ITEMS(args_1, collider->GetProxy()); - } - ret = PyObject_Call(func, args_1, NULL); - } else { - // More than one argument, assume we can give point & normal. - if (args_3 == NULL) { - args_3 = PyTuple_New(3); - PyTuple_SET_ITEMS(args_3, - collider->GetProxy(), - PyObjectFrom(point), - PyObjectFrom(normal)); - } - ret = PyObject_Call(func, args_3, NULL); - } - - if (ret == NULL) { - PyErr_Print(); - PyErr_Clear(); - } - else { - Py_DECREF(ret); - } - } - - if (args_3) Py_DECREF(args_3); - if (args_1) Py_DECREF(args_1); + for (unsigned int i = 0; i < ARRAY_SIZE(args); ++i) { + Py_DECREF(args[i]); } #endif } diff --git a/source/gameengine/Ketsji/KX_Scene.cpp b/source/gameengine/Ketsji/KX_Scene.cpp index db4ed58d65f..a3e1d1562c9 100644 --- a/source/gameengine/Ketsji/KX_Scene.cpp +++ b/source/gameengine/Ketsji/KX_Scene.cpp @@ -43,6 +43,7 @@ #include "KX_FontObject.h" #include "RAS_IPolygonMaterial.h" #include "ListValue.h" +#include "KX_PythonCallBack.h" #include "SCA_LogicManager.h" #include "SCA_TimeEventManager.h" //#include "SCA_AlwaysEventManager.h" @@ -2132,30 +2133,10 @@ void KX_Scene::Render2DFilters(RAS_ICanvas* canvas) void KX_Scene::RunDrawingCallbacks(PyObject *cb_list) { - Py_ssize_t len; + if (!cb_list || PyList_GET_SIZE(cb_list) == 0) + return; - if (cb_list && (len=PyList_GET_SIZE(cb_list))) - { - PyObject *args = PyTuple_New(0); // save python creating each call - PyObject *func; - PyObject *ret; - - // Iterate the list and run the callbacks - for (Py_ssize_t pos=0; pos < len; pos++) - { - func= PyList_GET_ITEM(cb_list, pos); - ret= PyObject_Call(func, args, NULL); - if (ret==NULL) { - PyErr_Print(); - PyErr_Clear(); - } - else { - Py_DECREF(ret); - } - } - - Py_DECREF(args); - } + RunPythonCallBackList(cb_list, NULL, 0, 0); } //----------------------------------------------------------------------------