From f6b39345f718b3ea9e6d01e9e71c6265a8511e58 Mon Sep 17 00:00:00 2001 From: rdb Date: Sun, 7 Feb 2021 12:30:15 +0100 Subject: [PATCH 1/3] event: don't exit task if future __await__ yields None This matches the behavior of asyncio's Task implementation, where this is the equivalent of `yield Task.cont`. I've kept regular generator tasks unaffected for now, since this might break existing usage. --- panda/src/event/pythonTask.cxx | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/panda/src/event/pythonTask.cxx b/panda/src/event/pythonTask.cxx index e6183c56dd..f9295b7aaf 100644 --- a/panda/src/event/pythonTask.cxx +++ b/panda/src/event/pythonTask.cxx @@ -585,6 +585,11 @@ do_python_task() { return DS_done; } + } else if (result == Py_None && PyCoro_CheckExact(_generator)) { + // Bare yield from a coroutine means to try again next frame. + Py_DECREF(result); + return DS_cont; + } else if (DtoolInstance_Check(result)) { // We are waiting for an AsyncFuture (eg. other task) to finish. AsyncFuture *fut = (AsyncFuture *)DtoolInstance_UPCAST(result, Dtool_AsyncFuture); From 28615c6a9e599a1fd46c2fd0af40e72e992e25b2 Mon Sep 17 00:00:00 2001 From: rdb Date: Sun, 7 Feb 2021 12:40:58 +0100 Subject: [PATCH 2/3] interval: Support awaiting CInterval from coroutines This is a partial implementation of #909 - it is somewhat inefficient (not suspending the task using a future) and does not implement cancellation. A more complete implementation may follow in 1.11.0. --- direct/src/interval/cInterval.h | 3 ++ direct/src/interval/cInterval_ext.cxx | 58 +++++++++++++++++++++++++++ direct/src/interval/cInterval_ext.h | 37 +++++++++++++++++ makepanda/makepanda.py | 2 + 4 files changed, 100 insertions(+) create mode 100644 direct/src/interval/cInterval_ext.cxx create mode 100644 direct/src/interval/cInterval_ext.h diff --git a/direct/src/interval/cInterval.h b/direct/src/interval/cInterval.h index 979dba4547..3c4617a801 100644 --- a/direct/src/interval/cInterval.h +++ b/direct/src/interval/cInterval.h @@ -19,6 +19,7 @@ #include "pvector.h" #include "config_interval.h" #include "pStatCollector.h" +#include "extension.h" class CIntervalManager; @@ -120,6 +121,8 @@ PUBLISHED: bool step_play(); PUBLISHED: + EXTENSION(PyObject *__await__(PyObject *self)); + MAKE_PROPERTY(name, get_name); MAKE_PROPERTY(duration, get_duration); MAKE_PROPERTY(open_ended, get_open_ended); diff --git a/direct/src/interval/cInterval_ext.cxx b/direct/src/interval/cInterval_ext.cxx new file mode 100644 index 0000000000..9604f8003b --- /dev/null +++ b/direct/src/interval/cInterval_ext.cxx @@ -0,0 +1,58 @@ +/** + * 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." + * + * @file cInterval_ext.cxx + * @author rdb + * @date 2020-10-17 + */ + +#include "cInterval_ext.h" +#include "cIntervalManager.h" +#include "asyncFuture.h" + +#ifdef HAVE_PYTHON + +#ifndef CPPPARSER +extern struct Dtool_PyTypedObject Dtool_CInterval; +#endif + +/** + * Yields continuously until the interval is done. + */ +static PyObject *gen_next(PyObject *self) { + const CInterval *ival; + if (!Dtool_Call_ExtractThisPointer(self, Dtool_CInterval, (void **)&ival)) { + return nullptr; + } + + if (ival->get_state() != CInterval::S_final) { + // Try again next frame. + Py_INCREF(Py_None); + return Py_None; + } + else { + PyErr_SetNone(PyExc_StopIteration); + return nullptr; + } +} + +/** + * Awaiting an interval starts it and yields a future until it is done. + */ +PyObject *Extension:: +__await__(PyObject *self) { + if (_this->get_state() != CInterval::S_initial) { + PyErr_SetString(PyExc_RuntimeError, "Can only await an interval that is in the initial state."); + return nullptr; + } + + _this->start(); + return Dtool_NewGenerator(self, &gen_next); +} + +#endif // HAVE_PYTHON diff --git a/direct/src/interval/cInterval_ext.h b/direct/src/interval/cInterval_ext.h new file mode 100644 index 0000000000..edec0cffd0 --- /dev/null +++ b/direct/src/interval/cInterval_ext.h @@ -0,0 +1,37 @@ +/** + * 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." + * + * @file cInterval_ext.h + * @author rdb + * @date 2020-10-17 + */ + +#ifndef CINTERVAL_EXT_H +#define CINTERVAL_EXT_H + +#include "dtoolbase.h" + +#ifdef HAVE_PYTHON + +#include "extension.h" +#include "cInterval.h" +#include "py_panda.h" + +/** + * This class defines the extension methods for CInterval, which are called + * instead of any C++ methods with the same prototype. + */ +template<> +class Extension : public ExtensionBase { +public: + PyObject *__await__(PyObject *self); +}; + +#endif // HAVE_PYTHON + +#endif // CINTERVAL_EXT_H diff --git a/makepanda/makepanda.py b/makepanda/makepanda.py index 6f65cc0253..69b0b207fd 100755 --- a/makepanda/makepanda.py +++ b/makepanda/makepanda.py @@ -5589,6 +5589,7 @@ if (PkgSkip("DIRECT")==0): IGATEFILES=GetDirectoryContents('direct/src/interval', ["*.h", "*_composite*.cxx"]) TargetAdd('libp3interval.in', opts=OPTS, input=IGATEFILES) TargetAdd('libp3interval.in', opts=['IMOD:panda3d.direct', 'ILIB:libp3interval', 'SRCDIR:direct/src/interval']) + PyTargetAdd('p3interval_cInterval_ext.obj', opts=OPTS, input='cInterval_ext.cxx') # # DIRECTORY: direct/src/showbase/ @@ -5647,6 +5648,7 @@ if (PkgSkip("DIRECT")==0): PyTargetAdd('direct.pyd', input='libp3showbase_igate.obj') PyTargetAdd('direct.pyd', input='libp3deadrec_igate.obj') PyTargetAdd('direct.pyd', input='libp3interval_igate.obj') + PyTargetAdd('direct.pyd', input='p3interval_cInterval_ext.obj') PyTargetAdd('direct.pyd', input='libp3distributed_igate.obj') PyTargetAdd('direct.pyd', input='libp3motiontrail_igate.obj') From 8c8a73a1a7dbc4bfea72db104bf9ae5b43b08563 Mon Sep 17 00:00:00 2001 From: rdb Date: Sun, 7 Feb 2021 14:49:10 +0100 Subject: [PATCH 3/3] event: Fix compilation with Python 2.7 --- panda/src/event/pythonTask.cxx | 2 ++ 1 file changed, 2 insertions(+) diff --git a/panda/src/event/pythonTask.cxx b/panda/src/event/pythonTask.cxx index f9295b7aaf..f25ab4837a 100644 --- a/panda/src/event/pythonTask.cxx +++ b/panda/src/event/pythonTask.cxx @@ -585,10 +585,12 @@ do_python_task() { return DS_done; } +#if PY_VERSION_HEX >= 0x03050000 } else if (result == Py_None && PyCoro_CheckExact(_generator)) { // Bare yield from a coroutine means to try again next frame. Py_DECREF(result); return DS_cont; +#endif } else if (DtoolInstance_Check(result)) { // We are waiting for an AsyncFuture (eg. other task) to finish.