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/globalreceiverv2.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/globalreceiverv2.cpp')
-rw-r--r-- | sources/pyside6/libpyside/globalreceiverv2.cpp | 180 |
1 files changed, 0 insertions, 180 deletions
diff --git a/sources/pyside6/libpyside/globalreceiverv2.cpp b/sources/pyside6/libpyside/globalreceiverv2.cpp deleted file mode 100644 index 41ed35263..000000000 --- a/sources/pyside6/libpyside/globalreceiverv2.cpp +++ /dev/null @@ -1,180 +0,0 @@ -// Copyright (C) 2016 The Qt Company Ltd. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only - -#include "globalreceiverv2.h" -#include "dynamicslot_p.h" -#include "pysideweakref.h" -#include "pysidestaticstrings.h" -#include "pysideutils.h" -#include "signalmanager.h" - -#include <autodecref.h> -#include <gilstate.h> -#include <pep384ext.h> - -#include <QtCore/QMetaMethod> -#include <QtCore/QSet> -#include <QtCore/QDebug> - -#include <cstring> -#include <utility> - -namespace PySide -{ - -GlobalReceiverKey GlobalReceiverV2::key(PyObject *callback) -{ - Shiboken::GilState gil; - switch (DynamicSlot::slotType(callback)) { - case DynamicSlot::SlotType::Method: - // PYSIDE-1422: Avoid hash on self which might be unhashable. - return {PyMethod_GET_SELF(callback), PyMethod_GET_FUNCTION(callback)}; - case DynamicSlot::SlotType::CompiledMethod: { - // PYSIDE-1589: Fix for slots in compiled functions - Shiboken::AutoDecRef self(PyObject_GetAttr(callback, PySide::PySideName::im_self())); - Shiboken::AutoDecRef func(PyObject_GetAttr(callback, PySide::PySideName::im_func())); - return {self, func}; - } - case DynamicSlot::SlotType::Callable: - break; - } - return {nullptr, callback}; -} - -const char *GlobalReceiverV2::senderDynamicProperty = "_q_pyside_sender"; - -GlobalReceiverV2::GlobalReceiverV2(PyObject *callback, QObject *receiver) : - QObject(nullptr), - m_metaObject("__GlobalReceiver__", &QObject::staticMetaObject), - m_data(DynamicSlot::create(callback, this)), - m_receiver(receiver) -{ -} - -GlobalReceiverV2::~GlobalReceiverV2() -{ - m_refs.clear(); - // Remove itself from map. - // Suppress handling of destroyed() for objects whose last reference is contained inside - // the callback object that will now be deleted. The reference could be a default argument, - // a callback local variable, etc. - // The signal has to be suppressed because it would lead to the following situation: - // Callback is deleted, hence the last reference is decremented, - // leading to the object being deleted, which emits destroyed(), which would try to invoke - // the already deleted callback, and also try to delete the object again. - delete std::exchange(m_data, nullptr); -} - -int GlobalReceiverV2::addSlot(const QByteArray &signature) -{ - auto it = m_signatures.find(signature); - if (it == m_signatures.end()) { - const int index = metaObjectBuilder().addSlot(signature); - it = m_signatures.insert(signature, index); - } - return it.value(); -} - -void GlobalReceiverV2::incRef(const QObject *link) -{ - Q_ASSERT(link); - m_refs.append(link); -} - -void GlobalReceiverV2::decRef(const QObject *link) -{ - Q_ASSERT(link); - m_refs.removeOne(link); -} - -void GlobalReceiverV2::notify() -{ - purgeDeletedSenders(); -} - -static bool isNull(const QPointer<const QObject> &p) -{ - return p.isNull(); -} - -void GlobalReceiverV2::purgeDeletedSenders() -{ - m_refs.erase(std::remove_if(m_refs.begin(), m_refs.end(), isNull), m_refs.end()); -} - -bool GlobalReceiverV2::isEmpty() const -{ - return std::all_of(m_refs.cbegin(), m_refs.cend(), isNull); -} - -const QMetaObject *GlobalReceiverV2::metaObject() const -{ - return const_cast<GlobalReceiverV2 *>(this)->m_metaObject.update(); -} - -int GlobalReceiverV2::qt_metacall(QMetaObject::Call call, int id, void **args) -{ - Shiboken::GilState gil; - Q_ASSERT(call == QMetaObject::InvokeMetaMethod); - Q_ASSERT(id >= QObject::staticMetaObject.methodCount()); - - QMetaMethod slot = metaObject()->method(id); - Q_ASSERT(slot.methodType() == QMetaMethod::Slot); - - if (!m_data) { - const QByteArray message = "PySide6 Warning: Skipping callback call " - + slot.methodSignature() + " because the callback object is being destructed."; - PyErr_WarnEx(PyExc_RuntimeWarning, message.constData(), 0); - return -1; - } - - const bool setSenderDynamicProperty = !m_receiver.isNull(); - if (setSenderDynamicProperty) - m_receiver->setProperty(senderDynamicProperty, QVariant::fromValue(sender())); - - m_data->call(slot.parameterTypes(), slot.typeName(), args); - - if (setSenderDynamicProperty) - m_receiver->setProperty(senderDynamicProperty, QVariant{}); - - // SignalManager::callPythonMetaMethod might have failed, in that case we have to print the - // error so it considered "handled". - if (PyErr_Occurred()) { - int reclimit = Py_GetRecursionLimit(); - // Inspired by Python's errors.c: PyErr_GivenExceptionMatches() function. - // Temporarily bump the recursion limit, so that PyErr_Print will not raise a recursion - // error again. Don't do it when the limit is already insanely high, to avoid overflow. - if (reclimit < (1 << 30)) - Py_SetRecursionLimit(reclimit + 5); - PyErr_Print(); - Py_SetRecursionLimit(reclimit); - } - - return -1; -} - -void GlobalReceiverV2::formatDebug(QDebug &debug) const -{ - debug << "receiver=" << m_receiver - << ", signatures=" << m_signatures.keys() << ", slot=" << m_data; - if (isEmpty()) - debug << ", empty"; - else - debug << ", refs=" << m_refs; -}; - -QDebug operator<<(QDebug debug, const GlobalReceiverV2 *g) -{ - QDebugStateSaver saver(debug); - debug.noquote(); - debug.nospace(); - debug << "GlobalReceiverV2("; - if (g) - g->formatDebug(debug); - else - debug << '0'; - debug << ')'; - return debug; -} - -} // namespace PySide |