diff options
Diffstat (limited to 'src/corelib/kernel/qobject_p_p.h')
-rw-r--r-- | src/corelib/kernel/qobject_p_p.h | 257 |
1 files changed, 257 insertions, 0 deletions
diff --git a/src/corelib/kernel/qobject_p_p.h b/src/corelib/kernel/qobject_p_p.h new file mode 100644 index 0000000000..2277af0497 --- /dev/null +++ b/src/corelib/kernel/qobject_p_p.h @@ -0,0 +1,257 @@ +// Copyright (C) 2019 The Qt Company Ltd. +// Copyright (C) 2013 Olivier Goffart <ogoffart@woboq.com> +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#ifndef QOBJECT_P_P_H +#define QOBJECT_P_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists for the convenience +// of qobject.cpp. This header file may change from version to version +// without notice, or even be removed. +// +// We mean it. +// + +// Even though this file is only used by qobject.cpp, the only reason this +// code lives here is that some special apps/libraries for e.g., QtJambi, +// Gammaray need access to the structs in this file. + +#include <QtCore/qobject.h> +#include <QtCore/private/qobject_p.h> + +QT_BEGIN_NAMESPACE + +// ConnectionList is a singly-linked list +struct QObjectPrivate::ConnectionList +{ + QAtomicPointer<Connection> first; + QAtomicPointer<Connection> last; +}; +static_assert(std::is_trivially_destructible_v<QObjectPrivate::ConnectionList>); +Q_DECLARE_TYPEINFO(QObjectPrivate::ConnectionList, Q_RELOCATABLE_TYPE); + +struct QObjectPrivate::TaggedSignalVector +{ + quintptr c; + + TaggedSignalVector() = default; + TaggedSignalVector(std::nullptr_t) noexcept : c(0) {} + TaggedSignalVector(Connection *v) noexcept : c(reinterpret_cast<quintptr>(v)) { Q_ASSERT(v && (reinterpret_cast<quintptr>(v) & 0x1) == 0); } + TaggedSignalVector(SignalVector *v) noexcept : c(reinterpret_cast<quintptr>(v) | quintptr(1u)) { Q_ASSERT(v); } + explicit operator SignalVector *() const noexcept + { + if (c & 0x1) + return reinterpret_cast<SignalVector *>(c & ~quintptr(1u)); + return nullptr; + } + explicit operator Connection *() const noexcept + { + return reinterpret_cast<Connection *>(c); + } + operator uintptr_t() const noexcept { return c; } +}; + +struct QObjectPrivate::ConnectionOrSignalVector +{ + union { + // linked list of orphaned connections that need cleaning up + TaggedSignalVector nextInOrphanList; + // linked list of connections connected to slots in this object + Connection *next; + }; +}; +static_assert(std::is_trivial_v<QObjectPrivate::ConnectionOrSignalVector>); + +struct QObjectPrivate::Connection : public ConnectionOrSignalVector +{ + // linked list of connections connected to slots in this object, next is in base class + Connection **prev; + // linked list of connections connected to signals in this object + QAtomicPointer<Connection> nextConnectionList; + Connection *prevConnectionList; + + QObject *sender; + QAtomicPointer<QObject> receiver; + QAtomicPointer<QThreadData> receiverThreadData; + union { + StaticMetaCallFunction callFunction; + QtPrivate::QSlotObjectBase *slotObj; + }; + QAtomicPointer<const int> argumentTypes; + QAtomicInt ref_{ + 2 + }; // ref_ is 2 for the use in the internal lists, and for the use in QMetaObject::Connection + uint id = 0; + ushort method_offset; + ushort method_relative; + signed int signal_index : 27; // In signal range (see QObjectPrivate::signalIndex()) + ushort connectionType : 2; // 0 == auto, 1 == direct, 2 == queued, 3 == blocking + ushort isSlotObject : 1; + ushort ownArgumentTypes : 1; + ushort isSingleShot : 1; + Connection() : ownArgumentTypes(true) { } + ~Connection(); + int method() const + { + Q_ASSERT(!isSlotObject); + return method_offset + method_relative; + } + void ref() { ref_.ref(); } + void freeSlotObject() + { + if (isSlotObject) { + slotObj->destroyIfLastRef(); + isSlotObject = false; + } + } + void deref() + { + if (!ref_.deref()) { + Q_ASSERT(!receiver.loadRelaxed()); + Q_ASSERT(!isSlotObject); + delete this; + } + } +}; +Q_DECLARE_TYPEINFO(QObjectPrivate::Connection, Q_RELOCATABLE_TYPE); + +struct QObjectPrivate::SignalVector : public ConnectionOrSignalVector +{ + quintptr allocated; + // ConnectionList signals[] + ConnectionList &at(int i) { return reinterpret_cast<ConnectionList *>(this + 1)[i + 1]; } + const ConnectionList &at(int i) const + { + return reinterpret_cast<const ConnectionList *>(this + 1)[i + 1]; + } + int count() const { return static_cast<int>(allocated); } +}; +static_assert( + std::is_trivial_v<QObjectPrivate::SignalVector>); // it doesn't need to be, but it helps + +struct QObjectPrivate::ConnectionData +{ + // the id below is used to avoid activating new connections. When the object gets + // deleted it's set to 0, so that signal emission stops + QAtomicInteger<uint> currentConnectionId; + QAtomicInt ref; + QAtomicPointer<SignalVector> signalVector; + Connection *senders = nullptr; + Sender *currentSender = nullptr; // object currently activating the object + std::atomic<TaggedSignalVector> orphaned = {}; + + ~ConnectionData() + { + Q_ASSERT(ref.loadRelaxed() == 0); + TaggedSignalVector c = orphaned.exchange(nullptr, std::memory_order_relaxed); + if (c) + deleteOrphaned(c); + SignalVector *v = signalVector.loadRelaxed(); + if (v) { + v->~SignalVector(); + free(v); + } + } + + // must be called on the senders connection data + // assumes the senders and receivers lock are held + void removeConnection(Connection *c); + enum LockPolicy { + NeedToLock, + // Beware that we need to temporarily release the lock + // and thus calling code must carefully consider whether + // invariants still hold. + AlreadyLockedAndTemporarilyReleasingLock + }; + void cleanOrphanedConnections(QObject *sender, LockPolicy lockPolicy = NeedToLock) + { + if (orphaned.load(std::memory_order_relaxed) && ref.loadAcquire() == 1) + cleanOrphanedConnectionsImpl(sender, lockPolicy); + } + void cleanOrphanedConnectionsImpl(QObject *sender, LockPolicy lockPolicy); + + ConnectionList &connectionsForSignal(int signal) + { + return signalVector.loadRelaxed()->at(signal); + } + + void resizeSignalVector(uint size) + { + SignalVector *vector = this->signalVector.loadRelaxed(); + if (vector && vector->allocated > size) + return; + size = (size + 7) & ~7; + void *ptr = malloc(sizeof(SignalVector) + (size + 1) * sizeof(ConnectionList)); + auto newVector = new (ptr) SignalVector; + + int start = -1; + if (vector) { + // not (yet) existing trait: + // static_assert(std::is_relocatable_v<SignalVector>); + // static_assert(std::is_relocatable_v<ConnectionList>); + memcpy(newVector, vector, + sizeof(SignalVector) + (vector->allocated + 1) * sizeof(ConnectionList)); + start = vector->count(); + } + for (int i = start; i < int(size); ++i) + new (&newVector->at(i)) ConnectionList(); + newVector->next = nullptr; + newVector->allocated = size; + + signalVector.storeRelaxed(newVector); + if (vector) { + TaggedSignalVector o = nullptr; + /* No ABA issue here: When adding a node, we only care about the list head, it doesn't + * matter if the tail changes. + */ + o = orphaned.load(std::memory_order_acquire); + do { + vector->nextInOrphanList = o; + } while (!orphaned.compare_exchange_strong(o, TaggedSignalVector(vector), std::memory_order_release)); + } + } + int signalVectorCount() const + { + return signalVector.loadAcquire() ? signalVector.loadRelaxed()->count() : -1; + } + + static void deleteOrphaned(TaggedSignalVector o); +}; + +struct QObjectPrivate::Sender +{ + Sender(QObject *receiver, QObject *sender, int signal, ConnectionData *receiverConnections) + : receiver(receiver), sender(sender), signal(signal) + { + if (receiverConnections) { + previous = receiverConnections->currentSender; + receiverConnections->currentSender = this; + } + } + ~Sender() + { + if (receiver) + receiver->d_func()->connections.loadAcquire()->currentSender = previous; + } + void receiverDeleted() + { + Sender *s = this; + while (s) { + s->receiver = nullptr; + s = s->previous; + } + } + Sender *previous = nullptr; + QObject *receiver; + QObject *sender; + int signal; +}; +Q_DECLARE_TYPEINFO(QObjectPrivate::Sender, Q_RELOCATABLE_TYPE); + +QT_END_NAMESPACE + +#endif |