diff options
Diffstat (limited to 'sources/pyside6/libpyside')
-rw-r--r-- | sources/pyside6/libpyside/CMakeLists.txt | 2 | ||||
-rw-r--r-- | sources/pyside6/libpyside/dynamicslot.cpp | 220 | ||||
-rw-r--r-- | sources/pyside6/libpyside/dynamicslot_p.h | 46 | ||||
-rw-r--r-- | sources/pyside6/libpyside/globalreceiverv2.cpp | 155 | ||||
-rw-r--r-- | sources/pyside6/libpyside/globalreceiverv2.h | 8 |
5 files changed, 282 insertions, 149 deletions
diff --git a/sources/pyside6/libpyside/CMakeLists.txt b/sources/pyside6/libpyside/CMakeLists.txt index ebfe123dd..b14a0ab52 100644 --- a/sources/pyside6/libpyside/CMakeLists.txt +++ b/sources/pyside6/libpyside/CMakeLists.txt @@ -10,6 +10,7 @@ set(CMAKE_AUTOMOC ON) set(libpyside_HEADERS # installed below class_property.h dynamicqmetaobject.h + dynamicslot_p.h feature_select.h globalreceiverv2.h pysideclassdecorator_p.h @@ -46,6 +47,7 @@ set(libpyside_HEADERS # installed below set(libpyside_SRC class_property.cpp dynamicqmetaobject.cpp + dynamicslot.cpp feature_select.cpp signalmanager.cpp globalreceiverv2.cpp diff --git a/sources/pyside6/libpyside/dynamicslot.cpp b/sources/pyside6/libpyside/dynamicslot.cpp new file mode 100644 index 000000000..182c2949f --- /dev/null +++ b/sources/pyside6/libpyside/dynamicslot.cpp @@ -0,0 +1,220 @@ +// Copyright (C) 2024 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 "dynamicslot_p.h" +#include "globalreceiverv2.h" // for GlobalReceiverMethodSlot +#include "pysidestaticstrings.h" +#include "pysideutils.h" +#include "pysideweakref.h" +#include "signalmanager.h" + +#include <autodecref.h> +#include <gilstate.h> +#include <pep384ext.h> + +#include <QtCore/QDebug> + +namespace PySide +{ + +DynamicSlot::SlotType DynamicSlot::slotType(PyObject *callback) +{ + if (PyMethod_Check(callback) != 0) + return SlotType::Method; + if (PySide::isCompiledMethod(callback) != 0) + return SlotType::CompiledMethod; + return SlotType::Callable; +} + +// Simple callable slot. +class CallbackDynamicSlot : public DynamicSlot +{ + Q_DISABLE_COPY_MOVE(CallbackDynamicSlot) +public: + explicit CallbackDynamicSlot(PyObject *callback) noexcept; + ~CallbackDynamicSlot() override; + + void call(const QByteArrayList ¶meterTypes, const char *returnType, + void **cppArgs) override; + void formatDebug(QDebug &debug) const override; + +private: + PyObject *m_callback; +}; + +CallbackDynamicSlot::CallbackDynamicSlot(PyObject *callback) noexcept : + m_callback(callback) +{ + Py_INCREF(m_callback); +} + +CallbackDynamicSlot::~CallbackDynamicSlot() +{ + Shiboken::GilState gil; + Py_DECREF(m_callback); +} + +void CallbackDynamicSlot::call(const QByteArrayList ¶meterTypes, const char *returnType, + void **cppArgs) +{ + SignalManager::callPythonMetaMethod(parameterTypes, returnType, cppArgs, m_callback); +} + +void CallbackDynamicSlot::formatDebug(QDebug &debug) const +{ + debug << "CallbackDynamicSlot(" << PySide::debugPyObject(m_callback) << ')'; +} + +// A method given by "signal.connect(foo.method)" is a temporarily created +// callable/partial function where self is bound as a first parameter. +// It can be split into self and the function. Keeping a reference on +// the callable itself would prevent object deletion. Instead, keep a +// reference on the function. +class MethodDynamicSlot : public DynamicSlot +{ + Q_DISABLE_COPY_MOVE(MethodDynamicSlot) +public: + + explicit MethodDynamicSlot(PyObject *function, PyObject *pythonSelf); + ~MethodDynamicSlot() override; + + void call(const QByteArrayList ¶meterTypes, const char *returnType, + void **cppArgs) override; + void formatDebug(QDebug &debug) const override; + +private: + PyObject *m_function; + PyObject *m_pythonSelf; +}; + +MethodDynamicSlot::MethodDynamicSlot(PyObject *function, PyObject *pythonSelf) : + m_function(function), + m_pythonSelf(pythonSelf) +{ +} + +MethodDynamicSlot::~MethodDynamicSlot() +{ + Shiboken::GilState gil; + Py_DECREF(m_function); +} + +void MethodDynamicSlot::call(const QByteArrayList ¶meterTypes, const char *returnType, + void **cppArgs) +{ + // create a callback based on method data + Shiboken::AutoDecRef callable(PepExt_Type_CallDescrGet(m_function, + m_pythonSelf, nullptr)); + SignalManager::callPythonMetaMethod(parameterTypes, returnType, + cppArgs, callable.object()); +} + +void MethodDynamicSlot::formatDebug(QDebug &debug) const +{ + debug << "MethodDynamicSlot(self=" << PySide::debugPyObject(m_pythonSelf) + << ", function=" << PySide::debugPyObject(m_function) << ')'; +} + +// Store a weak reference on pythonSelf. +class TrackingMethodDynamicSlot : public MethodDynamicSlot +{ + Q_DISABLE_COPY_MOVE(TrackingMethodDynamicSlot) +public: + explicit TrackingMethodDynamicSlot(PyObject *function, PyObject *pythonSelf, + PyObject *weakRef); + ~TrackingMethodDynamicSlot() override; + + void releaseWeakRef() { m_weakRef = nullptr; } + +private: + PyObject *m_weakRef; +}; + +TrackingMethodDynamicSlot::TrackingMethodDynamicSlot(PyObject *function, PyObject *pythonSelf, + PyObject *weakRef) : + MethodDynamicSlot(function, pythonSelf), + m_weakRef(weakRef) +{ +} + +TrackingMethodDynamicSlot::~TrackingMethodDynamicSlot() +{ + Shiboken::GilState gil; + Py_XDECREF(m_weakRef); + m_weakRef = nullptr; +} + +// Delete the GlobalReceiver on pythonSelf deletion +class GlobalReceiverMethodSlot : public TrackingMethodDynamicSlot +{ + Q_DISABLE_COPY_MOVE(GlobalReceiverMethodSlot) +public: + explicit GlobalReceiverMethodSlot(PyObject *function, PyObject *pythonSelf, + GlobalReceiverV2 *parent); + ~GlobalReceiverMethodSlot() override = default; + + GlobalReceiverV2 *parent() const { return m_parent; } + +private: + GlobalReceiverV2 *m_parent; +}; + +static void onGlobalReceiverSlotDestroyed(void *data) +{ + auto *self = reinterpret_cast<GlobalReceiverMethodSlot *>(data); + self->releaseWeakRef(); + Py_BEGIN_ALLOW_THREADS + SignalManager::deleteGlobalReceiver(self->parent()); + Py_END_ALLOW_THREADS +} + +// monitor class from method lifetime +GlobalReceiverMethodSlot::GlobalReceiverMethodSlot(PyObject *function, PyObject *pythonSelf, + GlobalReceiverV2 *parent) : + TrackingMethodDynamicSlot(function, pythonSelf, + WeakRef::create(pythonSelf, onGlobalReceiverSlotDestroyed, this)), + m_parent(parent) +{ +} + +DynamicSlot* DynamicSlot::create(PyObject *callback, GlobalReceiverV2 *parent) +{ + Shiboken::GilState gil; + switch (slotType(callback)) { + case SlotType::Method: { + PyObject *function = PyMethod_GET_FUNCTION(callback); + Py_INCREF(function); + PyObject *pythonSelf = PyMethod_GET_SELF(callback); + if (parent != nullptr) + return new GlobalReceiverMethodSlot(function, pythonSelf, parent); + return new MethodDynamicSlot(function, pythonSelf); + } + case SlotType::CompiledMethod: { + // PYSIDE-1523: PyMethod_Check is not accepting compiled form, we just go by attributes. + PyObject *function = PyObject_GetAttr(callback, PySide::PySideName::im_func()); + Py_DECREF(function); + PyObject *pythonSelf = PyObject_GetAttr(callback, PySide::PySideName::im_self()); + Py_DECREF(pythonSelf); + if (parent != nullptr) + return new GlobalReceiverMethodSlot(function, pythonSelf, parent); + return new MethodDynamicSlot(function, pythonSelf); + } + case SlotType::Callable: + break; + } + return new CallbackDynamicSlot(callback); +} + +QDebug operator<<(QDebug debug, const DynamicSlot *ds) +{ + QDebugStateSaver saver(debug); + debug.noquote(); + debug.nospace(); + if (ds != nullptr) + ds->formatDebug(debug); + else + debug << "DynamicSlot(0"; + return debug; +} + +} // namespace PySide diff --git a/sources/pyside6/libpyside/dynamicslot_p.h b/sources/pyside6/libpyside/dynamicslot_p.h new file mode 100644 index 000000000..7f35b7d7b --- /dev/null +++ b/sources/pyside6/libpyside/dynamicslot_p.h @@ -0,0 +1,46 @@ +// Copyright (C) 2024 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 + +#ifndef DYNAMICSLOT_P_H +#define DYNAMICSLOT_P_H + +#include <sbkpython.h> + +#include <QtCore/QtCompare> + +QT_FORWARD_DECLARE_CLASS(QDebug) + +namespace PySide +{ + +class GlobalReceiverV2; + +class DynamicSlot +{ + Q_DISABLE_COPY_MOVE(DynamicSlot) +public: + enum SlotType + { + Callable, + Method, + CompiledMethod + }; + + virtual ~DynamicSlot() = default; + + virtual void call(const QByteArrayList ¶meterTypes, const char *returnType, + void **cppArgs) = 0; + virtual void formatDebug(QDebug &debug) const = 0; + + static SlotType slotType(PyObject *callback); + static DynamicSlot *create(PyObject *callback, GlobalReceiverV2 *parent = nullptr); + +protected: + DynamicSlot() noexcept = default; +}; + +QDebug operator<<(QDebug debug, const DynamicSlot *ds); + +} + +#endif // DYNAMICSLOT_P_H diff --git a/sources/pyside6/libpyside/globalreceiverv2.cpp b/sources/pyside6/libpyside/globalreceiverv2.cpp index 4a061d566..41ed35263 100644 --- a/sources/pyside6/libpyside/globalreceiverv2.cpp +++ b/sources/pyside6/libpyside/globalreceiverv2.cpp @@ -2,6 +2,7 @@ // 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" @@ -16,157 +17,38 @@ #include <QtCore/QDebug> #include <cstring> +#include <utility> namespace PySide { -class DynamicSlotDataV2 -{ - Q_DISABLE_COPY_MOVE(DynamicSlotDataV2) - public: - DynamicSlotDataV2(PyObject *callback, GlobalReceiverV2 *parent); - ~DynamicSlotDataV2(); - - PyObject *callback(); - GlobalReceiverKey key() const { return {m_pythonSelf, m_callback}; } - - static void onCallbackDestroyed(void *data); - static GlobalReceiverKey key(PyObject *callback); - - void formatDebug(QDebug &debug) const; - - private: - bool m_isMethod; - PyObject *m_callback; - PyObject *m_pythonSelf = nullptr; - PyObject *m_pyClass = nullptr; - PyObject *m_weakRef = nullptr; - GlobalReceiverV2 *m_parent; -}; - -void DynamicSlotDataV2::formatDebug(QDebug &debug) const -{ - debug << "method=" << m_isMethod << ", m_callback=" << m_callback; - if (m_callback != nullptr) - debug << '/' << Py_TYPE(m_callback)->tp_name; - debug << ", self=" << m_pythonSelf; - if (m_pythonSelf != nullptr) - debug << '/' << Py_TYPE(m_pythonSelf)->tp_name; - debug << ", m_pyClass=" << m_pyClass; - if (m_pyClass != nullptr) - debug << '/' << Py_TYPE(m_pyClass)->tp_name; -} - -QDebug operator<<(QDebug debug, const DynamicSlotDataV2 *d) -{ - QDebugStateSaver saver(debug); - debug.noquote(); - debug.nospace(); - debug << "DynamicSlotDataV2("; - if (d) - d->formatDebug(debug); - else - debug << '0'; - debug << ')'; - return debug; -} - -using namespace PySide; - -DynamicSlotDataV2::DynamicSlotDataV2(PyObject *callback, GlobalReceiverV2 *parent) : - m_parent(parent) -{ - Shiboken::GilState gil; - - if (PyMethod_Check(callback)) { - m_isMethod = true; - // A method given by "signal.connect(foo.method)" is a temporarily created - // callable/partial function where self is bound as a first parameter. - // It can be split into self and the function. Keeping a reference on - // the callable itself would prevent object deletion. Instead, keep a - // reference on the function and listen for destruction of the object - // using a weak reference with notification. - m_callback = PyMethod_GET_FUNCTION(callback); - Py_INCREF(m_callback); - m_pythonSelf = PyMethod_GET_SELF(callback); - - m_weakRef = WeakRef::create(m_pythonSelf, DynamicSlotDataV2::onCallbackDestroyed, this); - } else if (PySide::isCompiledMethod(callback)) { - // PYSIDE-1523: PyMethod_Check is not accepting compiled form, we just go by attributes. - m_isMethod = true; - - m_callback = PyObject_GetAttr(callback, PySide::PySideName::im_func()); - Py_DECREF(m_callback); - - m_pythonSelf = PyObject_GetAttr(callback, PySide::PySideName::im_self()); - Py_DECREF(m_pythonSelf); - - //monitor class from method lifetime - m_weakRef = WeakRef::create(m_pythonSelf, DynamicSlotDataV2::onCallbackDestroyed, this); - } else { - m_isMethod = false; - - m_callback = callback; - Py_INCREF(m_callback); - } -} - -GlobalReceiverKey DynamicSlotDataV2::key(PyObject *callback) +GlobalReceiverKey GlobalReceiverV2::key(PyObject *callback) { Shiboken::GilState gil; - if (PyMethod_Check(callback)) { + 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)}; - } - if (PySide::isCompiledMethod(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}; } -PyObject *DynamicSlotDataV2::callback() -{ - PyObject *callback = m_callback; - - //create a callback based on method data - if (m_isMethod) - callback = PepExt_Type_CallDescrGet(m_callback, m_pythonSelf, nullptr); - else - Py_INCREF(callback); - - return callback; -} - -void DynamicSlotDataV2::onCallbackDestroyed(void *data) -{ - auto *self = reinterpret_cast<DynamicSlotDataV2 *>(data); - self->m_weakRef = nullptr; - Py_BEGIN_ALLOW_THREADS - SignalManager::deleteGlobalReceiver(self->m_parent); - Py_END_ALLOW_THREADS -} - -DynamicSlotDataV2::~DynamicSlotDataV2() -{ - Shiboken::GilState gil; - - Py_XDECREF(m_weakRef); - m_weakRef = nullptr; - - Py_DECREF(m_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) { - m_data = new DynamicSlotDataV2(callback, this); } GlobalReceiverV2::~GlobalReceiverV2() @@ -180,9 +62,7 @@ GlobalReceiverV2::~GlobalReceiverV2() // 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. - DynamicSlotDataV2 *data = m_data; - m_data = nullptr; - delete data; + delete std::exchange(m_data, nullptr); } int GlobalReceiverV2::addSlot(const QByteArray &signature) @@ -227,16 +107,6 @@ bool GlobalReceiverV2::isEmpty() const return std::all_of(m_refs.cbegin(), m_refs.cend(), isNull); } -GlobalReceiverKey GlobalReceiverV2::key() const -{ - return m_data->key(); -} - -GlobalReceiverKey GlobalReceiverV2::key(PyObject *callback) -{ - return DynamicSlotDataV2::key(callback); -} - const QMetaObject *GlobalReceiverV2::metaObject() const { return const_cast<GlobalReceiverV2 *>(this)->m_metaObject.update(); @@ -262,8 +132,7 @@ int GlobalReceiverV2::qt_metacall(QMetaObject::Call call, int id, void **args) if (setSenderDynamicProperty) m_receiver->setProperty(senderDynamicProperty, QVariant::fromValue(sender())); - Shiboken::AutoDecRef callback(m_data->callback()); - SignalManager::callPythonMetaMethod(slot, args, callback); + m_data->call(slot.parameterTypes(), slot.typeName(), args); if (setSenderDynamicProperty) m_receiver->setProperty(senderDynamicProperty, QVariant{}); diff --git a/sources/pyside6/libpyside/globalreceiverv2.h b/sources/pyside6/libpyside/globalreceiverv2.h index d2c09cf1f..351007596 100644 --- a/sources/pyside6/libpyside/globalreceiverv2.h +++ b/sources/pyside6/libpyside/globalreceiverv2.h @@ -22,7 +22,7 @@ QT_FORWARD_DECLARE_CLASS(QDebug); namespace PySide { -class DynamicSlotDataV2; +class DynamicSlot; class GlobalReceiverV2; struct GlobalReceiverKey @@ -82,10 +82,6 @@ public: /// Returns whether any senders are registered. bool isEmpty() const; - /// Use to retrieve the unique hash of this GlobalReceiver object - /// @return hash key - GlobalReceiverKey key() const; - /// Use to retrieve the unique hash of the PyObject based on GlobalReceiver rules /// @param callback The Python callable object used to calculate the id /// @return hash key @@ -102,7 +98,7 @@ private: void purgeDeletedSenders(); MetaObjectBuilder m_metaObject; - DynamicSlotDataV2 *m_data; + DynamicSlot *m_data; using QObjectPointer = QPointer<const QObject>; QList<QObjectPointer> m_refs; QPointer<QObject> m_receiver; |