Browse Source

event: use the persistent python wrapper when appending task object (#1681)

John C. Allwein 1 year ago
parent
commit
cfe3885c0e
2 changed files with 55 additions and 8 deletions
  1. 18 7
      panda/src/event/pythonTask.cxx
  2. 37 1
      tests/event/test_pythontask.py

+ 18 - 7
panda/src/event/pythonTask.cxx

@@ -174,9 +174,15 @@ get_args() {
       PyTuple_SET_ITEM(with_task, i, Py_NewRef(item));
       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;
     return with_task;
   }
   }
   else {
   else {
@@ -1004,11 +1010,16 @@ call_owner_method(const char *method_name) {
 void PythonTask::
 void PythonTask::
 call_function(PyObject *function) {
 call_function(PyObject *function) {
   if (function != Py_None) {
   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_XDECREF(result);
-    Py_DECREF(self);
   }
   }
 }
 }
 
 

+ 37 - 1
tests/event/test_pythontask.py

@@ -1,4 +1,4 @@
-from panda3d.core import PythonTask
+from panda3d.core import AsyncTaskManager, PythonTask
 from contextlib import contextmanager
 from contextlib import contextmanager
 import pytest
 import pytest
 import types
 import types
@@ -115,3 +115,39 @@ def test_pythontask_cycle():
                 break
                 break
         else:
         else:
             pytest.fail('not found in garbage')
             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