diff options
| author | Friedemann Kleint <Friedemann.Kleint@qt.io> | 2024-07-09 13:08:05 +0200 |
|---|---|---|
| committer | Friedemann Kleint <Friedemann.Kleint@qt.io> | 2024-08-21 22:41:13 +0200 |
| commit | 33bd61d13d8d9e3794b6049891be62f3351313d9 (patch) | |
| tree | 73204bed688aa7c3268d5173dbe567cf78be8b3f /sources/pyside6/libpyside/signalmanager.cpp | |
| parent | 05c4e6372e9515f81534ff1d0816695d4e6a9b27 (diff) | |
libpyside: Reimplement signal connections for Python callables not targeting a QMetaMethod
The code previously used a instances of class GlobalReceiverV2 inheriting QObject in a hash in
SignalManager per slot tracking the list of senders to be able to use standard signal/slot
connections in Qt. This was a complicated data structure and had issues with cleanups.
This has been replaced by using an invoker object based on QtPrivate::QSlotObjectBase which
can be passed to
QObjectPrivate::connect(const QObject *, int signal, QtPrivate::QSlotObjectBase *, ...).
The connections (identified by ConnectionKey) are now stored in a hash with QMetaObject::Connection
as value, which can be used to disconnect using QObject::disconnect(QMetaObject::Connection).
Deletion tracking is done by using signal QObject::destroyed(QObject*) which requires
adapting some tests checking on the connection count and weak ref notification on receivers
as was the case before.
[ChangeLog][PySide6] Signal connections for Python callables not targeting a QMetaMethod
has be reimplemented to simplify code and prepare for removal of the GIL.
Task-number: PYSIDE-2810
Task-number: PYSIDE-2221
Change-Id: Ib55e73d4d7bfe6d7a8b7adc3ce3734eac5789bea
Reviewed-by: Shyamnath Premnadh <Shyamnath.Premnadh@qt.io>
Reviewed-by: Cristian Maureira-Fredes <cristian.maureira-fredes@qt.io>
Diffstat (limited to 'sources/pyside6/libpyside/signalmanager.cpp')
| -rw-r--r-- | sources/pyside6/libpyside/signalmanager.cpp | 216 |
1 files changed, 9 insertions, 207 deletions
diff --git a/sources/pyside6/libpyside/signalmanager.cpp b/sources/pyside6/libpyside/signalmanager.cpp index f6b36c8e6..35116648b 100644 --- a/sources/pyside6/libpyside/signalmanager.cpp +++ b/sources/pyside6/libpyside/signalmanager.cpp @@ -6,11 +6,9 @@ #include "pysidelogging_p.h" #include "pysideproperty.h" #include "pysideproperty_p.h" -#include "pysidecleanup.h" #include "pyside_p.h" #include "dynamicqmetaobject.h" #include "pysidemetafunction_p.h" -#include "pysidestaticstrings.h" #include <autodecref.h> #include <basewrapper.h> @@ -28,8 +26,6 @@ #include <QtCore/QScopedPointer> #include <QtCore/QTimerEvent> -#include <algorithm> -#include <limits> #include <memory> using namespace Qt::StringLiterals; @@ -38,12 +34,8 @@ using namespace Qt::StringLiterals; #error QSLOT_CODE and/or QSIGNAL_CODE changed! change the hardcoded stuff to the correct value! #endif -#include "globalreceiverv2.h" - static PyObject *metaObjectAttr = nullptr; -static bool qAppRunning = false; - static void destroyMetaObject(PyObject *obj) { void *ptr = PyCapsule_GetPointer(obj, nullptr); @@ -231,76 +223,20 @@ QDataStream &operator>>(QDataStream &in, PyObjectWrapper &myObj) }; -namespace PySide { -using GlobalReceiverV2Ptr = std::shared_ptr<GlobalReceiverV2>; -using GlobalReceiverV2Map = QHash<PySide::GlobalReceiverKey, GlobalReceiverV2Ptr>; -} - using namespace PySide; -// Listen for destroy() of main thread objects and ensure cleanup -class SignalManagerDestroyListener : public QObject -{ - Q_OBJECT -public: - Q_DISABLE_COPY_MOVE(SignalManagerDestroyListener) - - using QObject::QObject; - ~SignalManagerDestroyListener() override = default; - -public Q_SLOTS: - void destroyNotify(const QObject *); - -protected: - void timerEvent(QTimerEvent *event) override; - -private: - int m_timerId = -1; -}; - -void SignalManagerDestroyListener::destroyNotify(const QObject *) -{ - if (qAppRunning && m_timerId == -1) - m_timerId = startTimer(0); -} - -void SignalManagerDestroyListener::timerEvent(QTimerEvent *event) +struct SignalManagerPrivate { - if (event->timerId() == m_timerId) { - killTimer(std::exchange(m_timerId, -1)); - SignalManager::instance().purgeEmptyGlobalReceivers(); - } -} - -struct SignalManager::SignalManagerPrivate -{ - Q_DISABLE_COPY_MOVE(SignalManagerPrivate) - - SignalManagerPrivate() noexcept = default; - ~SignalManagerPrivate() { clear(); } - - void deleteGlobalReceiver(const QObject *gr); - void clear(); - void purgeEmptyGlobalReceivers(); - - GlobalReceiverV2Map m_globalReceivers; static SignalManager::QmlMetaCallErrorHandler m_qmlMetaCallErrorHandler; static void handleMetaCallError(QObject *object, int *result); static int qtPropertyMetacall(QObject *object, QMetaObject::Call call, int id, void **args); static int qtMethodMetacall(QObject *object, int id, void **args); - - QPointer<SignalManagerDestroyListener> m_listener; }; SignalManager::QmlMetaCallErrorHandler - SignalManager::SignalManagerPrivate::m_qmlMetaCallErrorHandler = nullptr; - -static void clearSignalManager() -{ - PySide::SignalManager::instance().clear(); -} + SignalManagerPrivate::m_qmlMetaCallErrorHandler = nullptr; static void PyObject_PythonToCpp_PyObject_PTR(PyObject *pyIn, void *cppOut) { @@ -318,7 +254,7 @@ static PyObject *PyObject_PTR_CppToPython_PyObject(const void *cppIn) return pyOut; } -SignalManager::SignalManager() : m_d(new SignalManagerPrivate) +void SignalManager::init() { // Register Qt primitive typedefs used on signals. using namespace Shiboken; @@ -336,147 +272,15 @@ SignalManager::SignalManager() : m_d(new SignalManagerPrivate) Shiboken::Conversions::registerConverterName(converter, "PyObjectWrapper"); Shiboken::Conversions::registerConverterName(converter, "PySide::PyObjectWrapper"); - PySide::registerCleanupFunction(clearSignalManager); - if (!metaObjectAttr) metaObjectAttr = Shiboken::String::fromCString("__METAOBJECT__"); } -void SignalManager::clear() -{ - m_d->clear(); -} - -SignalManager::~SignalManager() -{ - delete m_d; -} - -SignalManager &SignalManager::instance() -{ - static SignalManager me; - return me; -} - void SignalManager::setQmlMetaCallErrorHandler(QmlMetaCallErrorHandler handler) { SignalManagerPrivate::m_qmlMetaCallErrorHandler = handler; } -static void qAppAboutToQuit() -{ - qAppRunning = false; - SignalManager::instance().purgeEmptyGlobalReceivers(); -} - -static bool isInMainThread(const QObject *o) -{ - if (o->isWidgetType() || o->isWindowType() || o->isQuickItemType()) - return true; - auto *app = QCoreApplication::instance(); - return app != nullptr && app->thread() == o->thread(); -} - -QObject *SignalManager::globalReceiver(QObject *sender, PyObject *callback, QObject *receiver) -{ - if (m_d->m_listener.isNull() && !QCoreApplication::closingDown()) { - if (auto *app = QCoreApplication::instance()) { - // The signal manager potentially outlives QCoreApplication, ensure deletion - m_d->m_listener = new SignalManagerDestroyListener(app); - m_d->m_listener->setObjectName("qt_pyside_signalmanagerdestroylistener"); - QObject::connect(app, &QCoreApplication::aboutToQuit, qAppAboutToQuit); - qAppRunning = true; - } - } - - auto &globalReceivers = m_d->m_globalReceivers; - const GlobalReceiverKey key = GlobalReceiverV2::key(callback); - auto it = globalReceivers.find(key); - if (it == globalReceivers.end()) { - auto gr = std::make_shared<GlobalReceiverV2>(callback, receiver); - it = globalReceivers.insert(key, gr); - } - - if (sender != nullptr) { - it.value()->incRef(sender); // create a link reference - - // For main thread-objects, add a notification for destroy (PYSIDE-2646, 2141) - if (qAppRunning && !m_d->m_listener.isNull() && isInMainThread(sender)) { - QObject::connect(sender, &QObject::destroyed, - m_d->m_listener, &SignalManagerDestroyListener::destroyNotify, - Qt::UniqueConnection); - } - } - - return it.value().get(); -} - -void SignalManager::purgeEmptyGlobalReceivers() -{ - m_d->purgeEmptyGlobalReceivers(); -} - -void SignalManager::notifyGlobalReceiver(QObject *receiver) -{ - reinterpret_cast<GlobalReceiverV2 *>(receiver)->notify(); - purgeEmptyGlobalReceivers(); -} - -void SignalManager::releaseGlobalReceiver(const QObject *source, QObject *receiver) -{ - auto *gr = static_cast<GlobalReceiverV2 *>(receiver); - gr->decRef(source); - if (gr->isEmpty()) - m_d->deleteGlobalReceiver(gr); -} - -void SignalManager::deleteGlobalReceiver(const QObject *gr) -{ - SignalManager::instance().m_d->deleteGlobalReceiver(gr); -} - -void SignalManager::SignalManagerPrivate::deleteGlobalReceiver(const QObject *gr) -{ - for (auto it = m_globalReceivers.begin(), end = m_globalReceivers.end(); it != end; ++it) { - if (it.value().get() == gr) { - m_globalReceivers.erase(it); - break; - } - } -} - -void SignalManager::SignalManagerPrivate::clear() -{ - // Delete receivers by always retrieving the current first element, - // because deleting a receiver can indirectly delete another one - // via ~DynamicSlotDataV2(). Using ~QHash/clear() could cause an - // iterator invalidation, and thus undefined behavior. - while (!m_globalReceivers.isEmpty()) - m_globalReceivers.erase(m_globalReceivers.cbegin()); -} - -static bool isEmptyGlobalReceiver(const GlobalReceiverV2Ptr &g) -{ - return g->isEmpty(); -} - -void SignalManager::SignalManagerPrivate::purgeEmptyGlobalReceivers() -{ - // Delete repetitively (see comment in clear()). - while (true) { - auto it = std::find_if(m_globalReceivers.cbegin(), m_globalReceivers.cend(), - isEmptyGlobalReceiver); - if (it == m_globalReceivers.cend()) - break; - m_globalReceivers.erase(it); - } -} - -int SignalManager::globalReceiverSlotIndex(QObject *receiver, const QByteArray &signature) -{ - return static_cast<GlobalReceiverV2 *>(receiver)->addSlot(signature); -} - bool SignalManager::emitSignal(QObject *source, const char *signal, PyObject *args) { if (!Signal::checkQtSignal(signal)) @@ -488,7 +292,7 @@ bool SignalManager::emitSignal(QObject *source, const char *signal, PyObject *ar } // Handle errors from meta calls. Requires GIL and PyErr_Occurred() -void SignalManager::SignalManagerPrivate::handleMetaCallError(QObject *object, int *result) +void SignalManagerPrivate::handleMetaCallError(QObject *object, int *result) { // Bubbles Python exceptions up to the Javascript engine, if called from one if (m_qmlMetaCallErrorHandler) { @@ -508,9 +312,9 @@ void SignalManager::SignalManagerPrivate::handleMetaCallError(QObject *object, i } // Handler for QMetaObject::ReadProperty/WriteProperty/ResetProperty: -int SignalManager::SignalManagerPrivate::qtPropertyMetacall(QObject *object, - QMetaObject::Call call, - int id, void **args) +int SignalManagerPrivate::qtPropertyMetacall(QObject *object, + QMetaObject::Call call, + int id, void **args) { const QMetaObject *metaObject = object->metaObject(); int result = id - metaObject->propertyCount(); @@ -565,8 +369,8 @@ int SignalManager::SignalManagerPrivate::qtPropertyMetacall(QObject *object, } // Handler for QMetaObject::InvokeMetaMethod -int SignalManager::SignalManagerPrivate::qtMethodMetacall(QObject *object, - int id, void **args) +int SignalManagerPrivate::qtMethodMetacall(QObject *object, + int id, void **args) { const QMetaObject *metaObject = object->metaObject(); const QMetaMethod method = metaObject->method(id); @@ -909,5 +713,3 @@ const QMetaObject *SignalManager::retrieveMetaObject(PyObject *self) return builder->update(); } - -#include "signalmanager.moc" |
