diff --git a/panda/src/event/pythonTask.cxx b/panda/src/event/pythonTask.cxx index 44b0984559..6e513dbe4c 100644 --- a/panda/src/event/pythonTask.cxx +++ b/panda/src/event/pythonTask.cxx @@ -174,9 +174,15 @@ get_args() { PyTuple_SET_ITEM(with_task, i, Py_NewRef(item)); } - this->ref(); - PyObject *self = DTool_CreatePyInstance(this, Dtool_PythonTask, true, false); - PyTuple_SET_ITEM(with_task, num_args, self); + // Check whether we have a Python wrapper. This is not the case if the + // object has been created by C++ and never been exposed to Python code. + if (__self__ == nullptr) { + // A __self__ instance does not exist, let's create one now. + this->ref(); + __self__ = DTool_CreatePyInstance(this, Dtool_PythonTask, true, false); + } + + PyTuple_SET_ITEM(with_task, num_args, Py_NewRef(__self__)); return with_task; } else { @@ -1004,11 +1010,16 @@ call_owner_method(const char *method_name) { void PythonTask:: call_function(PyObject *function) { if (function != Py_None) { - this->ref(); - PyObject *self = DTool_CreatePyInstance(this, Dtool_PythonTask, true, false); - PyObject *result = PyObject_CallOneArg(function, self); + // Check whether we have a Python wrapper. This is not the case if the + // object has been created by C++ and never been exposed to Python code. + if (__self__ == nullptr) { + // A __self__ instance does not exist, let's create one now. + this->ref(); + __self__ = DTool_CreatePyInstance(this, Dtool_PythonTask, true, false); + } + + PyObject *result = PyObject_CallOneArg(function, __self__); Py_XDECREF(result); - Py_DECREF(self); } } diff --git a/tests/event/test_pythontask.py b/tests/event/test_pythontask.py index 584788494f..c793067f8b 100644 --- a/tests/event/test_pythontask.py +++ b/tests/event/test_pythontask.py @@ -1,4 +1,4 @@ -from panda3d.core import PythonTask +from panda3d.core import AsyncTaskManager, PythonTask from contextlib import contextmanager import pytest import types @@ -115,3 +115,39 @@ def test_pythontask_cycle(): break else: pytest.fail('not found in garbage') + +def test_task_persistent_wrapper(): + task_mgr = AsyncTaskManager.get_global_ptr() + task_chain = task_mgr.make_task_chain("test_task_persistent_wrapper") + + # Create a subclass of PythonTask + class PythonTaskSubclassTest(PythonTask): + pass + + def task_main(task): + # Set our result to be the input task we got into this function + task.set_result(task) + # Verify we got the subclass + assert isinstance(task, PythonTaskSubclassTest) + return task.done + + done_callback_reached = False + + def done_callback(task): + # Verify we got the subclass + assert isinstance(task, PythonTaskSubclassTest) + nonlocal done_callback_reached + done_callback_reached = True + + task = PythonTaskSubclassTest(task_main) + task.set_task_chain(task_chain.name) + task.set_upon_death(done_callback) + task_mgr.add(task) + task_chain.wait_for_tasks() + + assert task.done() + assert not task.cancelled() + # Verify the task passed into the function is the same task we created + assert task.result() is task + # Verify the done callback worked and was tested + assert done_callback_reached