aboutsummaryrefslogtreecommitdiffstats
path: root/sources/pyside6/libpyside/qobjectconnect.cpp
diff options
context:
space:
mode:
authorFriedemann Kleint <Friedemann.Kleint@qt.io>2024-07-09 13:08:05 +0200
committerFriedemann Kleint <Friedemann.Kleint@qt.io>2024-08-21 22:41:13 +0200
commit33bd61d13d8d9e3794b6049891be62f3351313d9 (patch)
tree73204bed688aa7c3268d5173dbe567cf78be8b3f /sources/pyside6/libpyside/qobjectconnect.cpp
parent05c4e6372e9515f81534ff1d0816695d4e6a9b27 (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/qobjectconnect.cpp')
-rw-r--r--sources/pyside6/libpyside/qobjectconnect.cpp139
1 files changed, 46 insertions, 93 deletions
diff --git a/sources/pyside6/libpyside/qobjectconnect.cpp b/sources/pyside6/libpyside/qobjectconnect.cpp
index 04a191ea1..b3b0633fa 100644
--- a/sources/pyside6/libpyside/qobjectconnect.cpp
+++ b/sources/pyside6/libpyside/qobjectconnect.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 "qobjectconnect.h"
+#include "dynamicslot_p.h"
#include "pysideqobject.h"
#include "pysideqslotobject_p.h"
#include "pysidesignal.h"
@@ -58,7 +59,7 @@ struct GetReceiverResult
QObject *receiver = nullptr;
PyObject *self = nullptr;
QByteArray callbackSig;
- bool usingGlobalReceiver = false;
+ bool forceDynamicSlot = false;
int slotIndex = -1;
};
@@ -69,8 +70,8 @@ QDebug operator<<(QDebug d, const GetReceiverResult &r)
d.noquote();
d.nospace();
d << "GetReceiverResult(receiver=" << r.receiver << ", self=" << r.self
- << ", sig=\"" << r.callbackSig << "\", slotIndex=" << r.slotIndex
- << ", usingGlobalReceiver=" << r.usingGlobalReceiver << ')';
+ << ", forceDynamicSlot=" << r.forceDynamicSlot
+ << ", sig=\"" << r.callbackSig << "\", slotIndex=" << r.slotIndex << ')';
return d;
}
#endif // QT_NO_DEBUG_STREAM
@@ -94,16 +95,15 @@ static bool isDeclaredIn(PyObject *method, const char *className)
return result;
}
-static GetReceiverResult getReceiver(QObject *source, QMetaMethod signal,
- PyObject *callback)
+static GetReceiverResult getReceiver(QMetaMethod signal, PyObject *callback)
{
GetReceiverResult result;
- bool forceGlobalReceiver = false;
if (PyMethod_Check(callback)) {
result.self = PyMethod_GET_SELF(callback);
result.receiver = PySide::convertToQObject(result.self, false);
- forceGlobalReceiver = isMethodDecorator(callback, true, result.self);
+ // Prevent dynamic slot creation for decorators
+ result.forceDynamicSlot = isMethodDecorator(callback, true, result.self);
#ifdef PYPY_VERSION
} else if (Py_TYPE(callback) == PepBuiltinMethod_TypePtr) {
result.self = PyObject_GetAttrString(callback, "__self__");
@@ -117,23 +117,23 @@ static GetReceiverResult getReceiver(QObject *source, QMetaMethod signal,
result.self = PyObject_GetAttr(callback, Shiboken::PyName::im_self());
Py_DECREF(result.self);
result.receiver = PySide::convertToQObject(result.self, false);
- forceGlobalReceiver = isMethodDecorator(callback, false, result.self);
+ // Prevent dynamic slot creation for decorators
+ result.forceDynamicSlot = isMethodDecorator(callback, false, result.self);
} else if (PyCallable_Check(callback)) {
// Ok, just a callable object
result.receiver = nullptr;
result.self = nullptr;
}
- result.usingGlobalReceiver = !result.receiver || forceGlobalReceiver;
+ result.callbackSig =
+ PySide::Signal::getCallbackSignature(signal, result.receiver, callback,
+ false);
// Check if this callback is a overwrite of a non-virtual Qt slot (pre-Jira bug 1019).
// Make it possible to connect to a MyWidget.show() although QWidget.show()
// is a non-virtual slot which would be found by QMetaObject search.
// FIXME PYSIDE7: This is arguably a bit of a misguided "feature", remove?
- if (!result.usingGlobalReceiver && result.receiver && result.self) {
- result.callbackSig =
- PySide::Signal::getCallbackSignature(signal, result.receiver, callback,
- result.usingGlobalReceiver);
+ if (result.receiver && result.self) {
const QMetaObject *metaObject = result.receiver->metaObject();
result.slotIndex = metaObject->indexOfSlot(result.callbackSig.constData());
if (PyMethod_Check(callback) != 0 && result.slotIndex != -1
@@ -143,27 +143,13 @@ static GetReceiverResult getReceiver(QObject *source, QMetaMethod signal,
metaObject = metaObject->superClass();
// If the Python callback is not declared in the same class, assume it is
// a Python override. Resort to global receiver (PYSIDE-2418).
- if (!isDeclaredIn(callback, metaObject->className()))
- result.usingGlobalReceiver = true;
+ if (!isDeclaredIn(callback, metaObject->className())) {
+ result.receiver = nullptr;
+ result.slotIndex = -1;
+ }
}
}
- auto *receiverThread = result.receiver ? result.receiver->thread() : nullptr;
-
- if (result.usingGlobalReceiver) {
- PySide::SignalManager &signalManager = PySide::SignalManager::instance();
- result.receiver = signalManager.globalReceiver(source, callback, result.receiver);
- // PYSIDE-1354: Move the global receiver to the original receivers's thread
- // so that autoconnections work correctly.
- if (receiverThread && receiverThread != result.receiver->thread())
- result.receiver->moveToThread(receiverThread);
- result.callbackSig =
- PySide::Signal::getCallbackSignature(signal, result.receiver, callback,
- result.usingGlobalReceiver);
- const QMetaObject *metaObject = result.receiver->metaObject();
- result.slotIndex = metaObject->indexOfSlot(result.callbackSig.constData());
- }
-
return result;
}
@@ -213,54 +199,39 @@ QMetaObject::Connection qobjectConnectCallback(QObject *source, const char *sign
return {};
// Extract receiver from callback
- const GetReceiverResult receiver = getReceiver(source,
- source->metaObject()->method(signalIndex),
- callback);
- if (receiver.receiver == nullptr && receiver.self == nullptr)
- return {};
-
- int slotIndex = receiver.slotIndex;
-
- PySide::SignalManager &signalManager = PySide::SignalManager::instance();
- if (slotIndex == -1) {
- if (!receiver.usingGlobalReceiver && receiver.self
- && !Shiboken::Object::hasCppWrapper(reinterpret_cast<SbkObject *>(receiver.self))) {
- qWarning("You can't add dynamic slots on an object originated from C++.");
- if (receiver.usingGlobalReceiver)
- signalManager.releaseGlobalReceiver(source, receiver.receiver);
-
- return {};
- }
-
- slotIndex = receiver.usingGlobalReceiver
- ? signalManager.globalReceiverSlotIndex(receiver.receiver, receiver.callbackSig)
- : PySide::SignalManager::registerMetaMethodGetIndexBA(receiver.receiver,
- receiver.callbackSig,
- QMetaMethod::Slot);
-
- if (slotIndex == -1) {
- if (receiver.usingGlobalReceiver)
- signalManager.releaseGlobalReceiver(source, receiver.receiver);
-
- return {};
- }
+ const QMetaMethod signalMethod = source->metaObject()->method(signalIndex);
+ GetReceiverResult receiver = getReceiver(signalMethod, callback);
+ if (!receiver.forceDynamicSlot && receiver.receiver != nullptr && receiver.slotIndex == -1) {
+ receiver.slotIndex = PySide::SignalManager::registerMetaMethodGetIndexBA(receiver.receiver,
+ receiver.callbackSig,
+ QMetaMethod::Slot);
}
QMetaObject::Connection connection{};
Py_BEGIN_ALLOW_THREADS // PYSIDE-2367, prevent threading deadlocks with connectNotify()
- connection = QMetaObject::connect(source, signalIndex, receiver.receiver, slotIndex, type);
+ if (!receiver.forceDynamicSlot && receiver.receiver != nullptr && receiver.slotIndex != -1) {
+ connection = QMetaObject::connect(source, signalIndex,
+ receiver.receiver, receiver.slotIndex, type);
+ } else {
+ auto parameterTypes = signalMethod.parameterTypes();
+ // Slots might have fewer arguments.
+ if (!receiver.callbackSig.isEmpty()) {
+ const auto paramCount = receiver.callbackSig.endsWith("()")
+ ? qsizetype(0) : receiver.callbackSig.count(',') + 1;
+ if (parameterTypes.size() > paramCount)
+ parameterTypes.resize(paramCount);
+ }
+ auto *slotObject = new PySideQSlotObject(callback,
+ parameterTypes,
+ signalMethod.typeName());
+ connection = QObjectPrivate::connect(source, signalIndex, slotObject, type);
+ }
Py_END_ALLOW_THREADS
- if (!connection) {
- if (receiver.usingGlobalReceiver)
- signalManager.releaseGlobalReceiver(source, receiver.receiver);
+ if (!connection)
return {};
- }
- Q_ASSERT(receiver.receiver);
- if (receiver.usingGlobalReceiver)
- signalManager.notifyGlobalReceiver(receiver.receiver);
+ registerSlotConnection(source, signalIndex, callback, connection);
- const QMetaMethod signalMethod = source->metaObject()->method(signalIndex);
static_cast<FriendlyQObject *>(source)->connectNotify(signalMethod);
return connection;
}
@@ -298,34 +269,16 @@ bool qobjectDisconnectCallback(QObject *source, const char *signal, PyObject *ca
if (!PySide::Signal::checkQtSignal(signal))
return false;
- const int signalIndex = source->metaObject()->indexOfSignal(signal + 1);
+ const auto *metaObject = source->metaObject();
+ const int signalIndex = metaObject->indexOfSignal(signal + 1);
if (signalIndex == -1)
return false;
- // Extract receiver from callback
- const GetReceiverResult receiver = getReceiver(nullptr,
- source->metaObject()->method(signalIndex),
- callback);
- if (receiver.receiver == nullptr && receiver.self == nullptr)
- return false;
-
- const int slotIndex = receiver.slotIndex;
-
- bool ok{};
- Py_BEGIN_ALLOW_THREADS // PYSIDE-2367, prevent threading deadlocks with disconnectNotify()
- ok = QMetaObject::disconnectOne(source, signalIndex, receiver.receiver, slotIndex);
- Py_END_ALLOW_THREADS
- if (!ok)
+ if (!disconnectSlot(source, signalIndex, callback))
return false;
- Q_ASSERT(receiver.receiver);
- const QMetaMethod signalMethod = source->metaObject()->method(signalIndex);
+ const QMetaMethod signalMethod = metaObject->method(signalIndex);
static_cast<FriendlyQObject *>(source)->disconnectNotify(signalMethod);
-
- if (receiver.usingGlobalReceiver) { // might delete the receiver
- PySide::SignalManager &signalManager = PySide::SignalManager::instance();
- signalManager.releaseGlobalReceiver(source, receiver.receiver);
- }
return true;
}