diff options
Diffstat (limited to 'src/corelib/kernel/qproperty_p.h')
-rw-r--r-- | src/corelib/kernel/qproperty_p.h | 546 |
1 files changed, 443 insertions, 103 deletions
diff --git a/src/corelib/kernel/qproperty_p.h b/src/corelib/kernel/qproperty_p.h index e92c8f878a..8ae6664a2b 100644 --- a/src/corelib/kernel/qproperty_p.h +++ b/src/corelib/kernel/qproperty_p.h @@ -1,41 +1,5 @@ -/*************************************************************************** -** -** Copyright (C) 2020 The Qt Company Ltd. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of the QtCore module of the Qt Toolkit. -** -** $QT_BEGIN_LICENSE:LGPL$ -** Commercial License Usage -** Licensees holding valid commercial Qt licenses may use this file in -** accordance with the commercial license agreement provided with the -** Software or, alternatively, in accordance with the terms contained in -** a written agreement between you and The Qt Company. For licensing terms -** and conditions see https://www.qt.io/terms-conditions. For further -** information use the contact form at https://www.qt.io/contact-us. -** -** GNU Lesser General Public License Usage -** Alternatively, this file may be used under the terms of the GNU Lesser -** General Public License version 3 as published by the Free Software -** Foundation and appearing in the file LICENSE.LGPL3 included in the -** packaging of this file. Please review the following information to -** ensure the GNU Lesser General Public License version 3 requirements -** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. -** -** GNU General Public License Usage -** Alternatively, this file may be used under the terms of the GNU -** General Public License version 2.0 or (at your option) the GNU General -** Public license version 3 or any later version approved by the KDE Free -** Qt Foundation. The licenses are as published by the Free Software -** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 -** included in the packaging of this file. Please review the following -** information to ensure the GNU General Public License requirements will -** be met: https://www.gnu.org/licenses/gpl-2.0.html and -** https://www.gnu.org/licenses/gpl-3.0.html. -** -** $QT_END_LICENSE$ -** -****************************************************************************/ +// Copyright (C) 2022 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 QPROPERTY_P_H #define QPROPERTY_P_H @@ -51,23 +15,56 @@ // We mean it. // -#include <qglobal.h> +#include <private/qglobal_p.h> #include <qproperty.h> +#include <qmetaobject.h> #include <qscopedpointer.h> +#include <qscopedvaluerollback.h> +#include <qvariant.h> #include <vector> +#include <QtCore/QVarLengthArray> QT_BEGIN_NAMESPACE namespace QtPrivate { Q_CORE_EXPORT bool isAnyBindingEvaluating(); + struct QBindingStatusAccessToken {}; } + +/*! + \internal + Similar to \c QPropertyBindingPrivatePtr, but stores a + \c QPropertyObserver * linking to the QPropertyBindingPrivate* + instead of the QPropertyBindingPrivate* itself + */ +struct QBindingObserverPtr +{ +private: + QPropertyObserver *d = nullptr; +public: + QBindingObserverPtr() = default; + Q_DISABLE_COPY(QBindingObserverPtr); + void swap(QBindingObserverPtr &other) noexcept + { qt_ptr_swap(d, other.d); } + QBindingObserverPtr(QBindingObserverPtr &&other) noexcept : d(std::exchange(other.d, nullptr)) {} + QT_MOVE_ASSIGNMENT_OPERATOR_IMPL_VIA_MOVE_AND_SWAP(QBindingObserverPtr); + + + inline QBindingObserverPtr(QPropertyObserver *observer) noexcept; + inline ~QBindingObserverPtr(); + inline QPropertyBindingPrivate *binding() const noexcept; + inline QPropertyObserver *operator ->(); +}; + +using PendingBindingObserverList = QVarLengthArray<QBindingObserverPtr>; + // Keep all classes related to QProperty in one compilation unit. Performance of this code is crucial and // we need to allow the compiler to inline where it makes sense. // This is a helper "namespace" -struct Q_AUTOTEST_EXPORT QPropertyBindingDataPointer +struct QPropertyBindingDataPointer { const QtPrivate::QPropertyBindingData *ptr = nullptr; @@ -83,11 +80,12 @@ struct Q_AUTOTEST_EXPORT QPropertyBindingDataPointer d = reinterpret_cast<quintptr>(observer); } static void fixupAfterMove(QtPrivate::QPropertyBindingData *ptr); - void addObserver(QPropertyObserver *observer); - void setFirstObserver(QPropertyObserver *observer); - QPropertyObserverPointer firstObserver() const; + void Q_ALWAYS_INLINE addObserver(QPropertyObserver *observer); + inline void setFirstObserver(QPropertyObserver *observer); + inline QPropertyObserverPointer firstObserver() const; + static QPropertyProxyBindingData *proxyData(QtPrivate::QPropertyBindingData *ptr); - int observerCount() const; + inline int observerCount() const; template <typename T> static QPropertyBindingDataPointer get(QProperty<T> &property) @@ -96,28 +94,96 @@ struct Q_AUTOTEST_EXPORT QPropertyBindingDataPointer } }; +struct QPropertyObserverNodeProtector +{ + Q_DISABLE_COPY_MOVE(QPropertyObserverNodeProtector) + + QPropertyObserverBase m_placeHolder; + Q_NODISCARD_CTOR + QPropertyObserverNodeProtector(QPropertyObserver *observer) + { + // insert m_placeholder after observer into the linked list + QPropertyObserver *next = observer->next.data(); + m_placeHolder.next = next; + observer->next = static_cast<QPropertyObserver *>(&m_placeHolder); + if (next) + next->prev = &m_placeHolder.next; + m_placeHolder.prev = &observer->next; + m_placeHolder.next.setTag(QPropertyObserver::ObserverIsPlaceholder); + } + + QPropertyObserver *next() const { return m_placeHolder.next.data(); } + + ~QPropertyObserverNodeProtector(); +}; + // This is a helper "namespace" struct QPropertyObserverPointer { QPropertyObserver *ptr = nullptr; - void unlink(); + void unlink() + { + unlink_common(); +#if QT_DEPRECATED_SINCE(6, 6) + QT_WARNING_PUSH QT_WARNING_DISABLE_DEPRECATED + if (ptr->next.tag() == QPropertyObserver::ObserverIsAlias) + ptr->aliasData = nullptr; + QT_WARNING_POP +#endif + } + + void unlink_fast() + { +#if QT_DEPRECATED_SINCE(6, 6) + QT_WARNING_PUSH QT_WARNING_DISABLE_DEPRECATED + Q_ASSERT(ptr->next.tag() != QPropertyObserver::ObserverIsAlias); + QT_WARNING_POP +#endif + unlink_common(); + } + + void setBindingToNotify(QPropertyBindingPrivate *binding) + { + Q_ASSERT(ptr->next.tag() != QPropertyObserver::ObserverIsPlaceholder); + ptr->binding = binding; + ptr->next.setTag(QPropertyObserver::ObserverNotifiesBinding); + } - void setBindingToNotify(QPropertyBindingPrivate *binding); + void setBindingToNotify_unsafe(QPropertyBindingPrivate *binding); void setChangeHandler(QPropertyObserver::ChangeHandler changeHandler); + enum class Notify {Everything, OnlyChangeHandlers}; + void notify(QUntypedPropertyData *propertyDataPtr); #ifndef QT_NO_DEBUG void noSelfDependencies(QPropertyBindingPrivate *binding); #else void noSelfDependencies(QPropertyBindingPrivate *) {} #endif - void evaluateBindings(); + void evaluateBindings(PendingBindingObserverList &bindingObservers, QBindingStatus *status); void observeProperty(QPropertyBindingDataPointer property); explicit operator bool() const { return ptr != nullptr; } QPropertyObserverPointer nextObserver() const { return {ptr->next.data()}; } + + QPropertyBindingPrivate *binding() const + { + Q_ASSERT(ptr->next.tag() == QPropertyObserver::ObserverNotifiesBinding); + return ptr->binding; + } + +private: + void unlink_common() + { + if (ptr->next) + ptr->next->prev = ptr->prev; + if (ptr->prev) + ptr->prev.setPointer(ptr->next.data()); + ptr->next = nullptr; + ptr->prev.clear(); + } }; class QPropertyBindingErrorPrivate : public QSharedData @@ -131,7 +197,7 @@ namespace QtPrivate { struct BindingEvaluationState { - BindingEvaluationState(QPropertyBindingPrivate *binding, QBindingStatus *status = nullptr); + BindingEvaluationState(QPropertyBindingPrivate *binding, QBindingStatus *status); ~BindingEvaluationState() { *currentState = previousState; @@ -140,6 +206,7 @@ struct BindingEvaluationState QPropertyBindingPrivate *binding; BindingEvaluationState *previousState = nullptr; BindingEvaluationState **currentState = nullptr; + QVarLengthArray<const QPropertyBindingData *, 8> alreadyCaptureProperties; }; /*! @@ -164,6 +231,33 @@ struct CompatPropertySafePoint QtPrivate::BindingEvaluationState *bindingState = nullptr; }; +/*! + * \internal + * While the regular QProperty notification for a compat property runs we + * don't want to have any currentCompatProperty set. This would be a _different_ + * one than the one we are current evaluating. Therefore it's misleading and + * prevents the registering of actual dependencies. + */ +struct CurrentCompatPropertyThief +{ + Q_DISABLE_COPY_MOVE(CurrentCompatPropertyThief) +public: + CurrentCompatPropertyThief(QBindingStatus *status) + : status(&status->currentCompatProperty) + , stolen(std::exchange(status->currentCompatProperty, nullptr)) + { + } + + ~CurrentCompatPropertyThief() + { + *status = stolen; + } + +private: + CompatPropertySafePoint **status = nullptr; + CompatPropertySafePoint *stolen = nullptr; +}; + } class Q_CORE_EXPORT QPropertyBindingPrivate : public QtPrivate::RefCounted @@ -208,9 +302,8 @@ protected: is stored somewhere else. To make efficient use of the space, we instead provide a scratch space for QQmlPropertyBinding (which stores further binding information there). Anything stored in the union must be trivially destructible. + (checked in qproperty.cpp) */ - static_assert(std::is_trivially_destructible_v<QPropertyBindingSourceLocation>); - static_assert(std::is_trivially_destructible_v<std::byte[sizeof(QPropertyBindingSourceLocation)]>); using DeclarativeErrorCallback = void(*)(QPropertyBindingPrivate *); union { QPropertyBindingSourceLocation location; @@ -240,6 +333,7 @@ public: bool isUpdating() {return updating;} void setSticky(bool keep = true) {m_sticky = keep;} bool isSticky() {return m_sticky;} + void scheduleNotify() {pendingNotify = true;} QPropertyBindingPrivate(QMetaType metaType, const QtPrivate::BindingFunctionVTable *vtable, const QPropertyBindingSourceLocation &location, bool isQQmlPropertyBinding=false) @@ -284,27 +378,18 @@ public: return observers; } - void clearDependencyObservers() { - for (size_t i = 0; i < qMin(dependencyObserverCount, inlineDependencyObservers.size()); ++i) { - QPropertyObserverPointer p{&inlineDependencyObservers[i]}; - p.unlink(); - } - if (heapObservers) - heapObservers->clear(); - dependencyObserverCount = 0; - } - QPropertyObserverPointer allocateDependencyObserver() - { + void clearDependencyObservers(); + + Q_ALWAYS_INLINE QPropertyObserverPointer allocateDependencyObserver() { if (dependencyObserverCount < inlineDependencyObservers.size()) { ++dependencyObserverCount; return {&inlineDependencyObservers[dependencyObserverCount - 1]}; } - ++dependencyObserverCount; - if (!heapObservers) - heapObservers.reset(new std::vector<QPropertyObserver>()); - return {&heapObservers->emplace_back()}; + return allocateDependencyObserver_slow(); } + QPropertyObserverPointer allocateDependencyObserver_slow(); + QPropertyBindingSourceLocation sourceLocation() const { if (!hasCustomVTable()) @@ -319,8 +404,13 @@ public: void unlinkAndDeref(); - void evaluateRecursive(); - void notifyRecursive(); + bool evaluateRecursive(PendingBindingObserverList &bindingObservers, QBindingStatus *status = nullptr); + + bool Q_ALWAYS_INLINE evaluateRecursive_inline(PendingBindingObserverList &bindingObservers, QBindingStatus *status); + + void notifyNonRecursive(const PendingBindingObserverList &bindingObservers); + enum NotificationState : bool { Delayed, Sent }; + NotificationState notifyNonRecursive(); static QPropertyBindingPrivate *get(const QUntypedPropertyBinding &binding) { return static_cast<QPropertyBindingPrivate *>(binding.d.data()); } @@ -369,9 +459,9 @@ inline void QPropertyBindingDataPointer::fixupAfterMove(QtPrivate::QPropertyBind { auto &d = ptr->d_ref(); if (ptr->isNotificationDelayed()) { - QPropertyProxyBindingData *proxyData - = reinterpret_cast<QPropertyProxyBindingData*>(d & ~QtPrivate::QPropertyBindingData::BindingBit); - proxyData->originalBindingData = ptr; + QPropertyProxyBindingData *proxy = ptr->proxyData(); + Q_ASSERT(proxy); + proxy->originalBindingData = ptr; } // If QPropertyBindingData has been moved, and it has an observer // we have to adjust the firstObserver's prev pointer to point to @@ -389,14 +479,38 @@ inline QPropertyObserverPointer QPropertyBindingDataPointer::firstObserver() con return { reinterpret_cast<QPropertyObserver *>(ptr->d()) }; } +/*! + \internal + Returns the proxy data of \a ptr, or \c nullptr if \a ptr has no delayed notification + */ +inline QPropertyProxyBindingData *QPropertyBindingDataPointer::proxyData(QtPrivate::QPropertyBindingData *ptr) +{ + if (!ptr->isNotificationDelayed()) + return nullptr; + return ptr->proxyData(); +} + +inline int QPropertyBindingDataPointer::observerCount() const +{ + int count = 0; + for (auto observer = firstObserver(); observer; observer = observer.nextObserver()) + ++count; + return count; +} + namespace QtPrivate { Q_CORE_EXPORT bool isPropertyInBindingWrapper(const QUntypedPropertyData *property); + void Q_CORE_EXPORT initBindingStatusThreadId(); } -template<typename Class, typename T, auto Offset, auto Setter, auto Signal=nullptr> +template<typename Class, typename T, auto Offset, auto Setter, auto Signal = nullptr, + auto Getter = nullptr> class QObjectCompatProperty : public QPropertyData<T> { - using ThisType = QObjectCompatProperty<Class, T, Offset, Setter, Signal>; + template<typename Property, typename> + friend class QtPrivate::QBindableInterfaceForProperty; + + using ThisType = QObjectCompatProperty<Class, T, Offset, Setter, Signal, Getter>; using SignalTakesValue = std::is_invocable<decltype(Signal), Class, T>; Class *owner() { @@ -408,26 +522,38 @@ class QObjectCompatProperty : public QPropertyData<T> char *that = const_cast<char *>(reinterpret_cast<const char *>(this)); return reinterpret_cast<Class *>(that - QtPrivate::detail::getOffset(Offset)); } + static bool bindingWrapper(QMetaType type, QUntypedPropertyData *dataPtr, QtPrivate::QPropertyBindingFunction binding) { auto *thisData = static_cast<ThisType *>(dataPtr); + QBindingStorage *storage = qGetBindingStorage(thisData->owner()); QPropertyData<T> copy; - binding.vtable->call(type, ©, binding.functor); - if constexpr (QTypeTraits::has_operator_equal_v<T>) - if (copy.valueBypassingBindings() == thisData->valueBypassingBindings()) - return false; + { + QtPrivate::CurrentCompatPropertyThief thief(storage->bindingStatus); + binding.vtable->call(type, ©, binding.functor); + if constexpr (QTypeTraits::has_operator_equal_v<T>) + if (copy.valueBypassingBindings() == thisData->valueBypassingBindings()) + return false; + } // ensure value and setValue know we're currently evaluating our binding - QBindingStorage *storage = qGetBindingStorage(thisData->owner()); QtPrivate::CompatPropertySafePoint guardThis(storage->bindingStatus, thisData); (thisData->owner()->*Setter)(copy.valueBypassingBindings()); return true; } bool inBindingWrapper(const QBindingStorage *storage) const { - return storage->bindingStatus->currentCompatProperty + return storage->bindingStatus && storage->bindingStatus->currentCompatProperty && QtPrivate::isPropertyInBindingWrapper(this); } + inline static T getPropertyValue(const QUntypedPropertyData *d) { + auto prop = static_cast<const ThisType *>(d); + if constexpr (std::is_null_pointer_v<decltype(Getter)>) + return prop->value(); + else + return (prop->owner()->*Getter)(); + } + public: using value_type = typename QPropertyData<T>::value_type; using parameter_type = typename QPropertyData<T>::parameter_type; @@ -441,8 +567,8 @@ public: { const QBindingStorage *storage = qGetBindingStorage(owner()); // make sure we don't register this binding as a dependency to itself - if (!inBindingWrapper(storage)) - storage->registerDependency(this); + if (storage->bindingStatus && storage->bindingStatus->currentlyEvaluatingBinding && !inBindingWrapper(storage)) + storage->registerDependency_helper(this); return this->val; } @@ -471,11 +597,11 @@ public: void setValue(parameter_type t) { QBindingStorage *storage = qGetBindingStorage(owner()); - auto *bd = storage->bindingData(this); - // make sure we don't remove the binding if called from the bindingWrapper - const bool inWrapper = inBindingWrapper(storage); - if (bd && !inWrapper) - bd->removeBinding(); + if (auto *bd = storage->bindingData(this)) { + // make sure we don't remove the binding if called from the bindingWrapper + if (bd->hasBinding() && !inBindingWrapper(storage)) + bd->removeBinding_helper(); + } this->val = t; } @@ -501,7 +627,7 @@ public: return true; } -#ifndef Q_CLANG_QDOC +#ifndef Q_QDOC template <typename Functor> QPropertyBinding<T> setBinding(Functor &&f, const QPropertyBindingSourceLocation &location = QT_PROPERTY_DEFAULT_BINDING_LOCATION, @@ -522,23 +648,40 @@ public: void removeBindingUnlessInWrapper() { QBindingStorage *storage = qGetBindingStorage(owner()); - auto *bd = storage->bindingData(this); - // make sure we don't remove the binding if called from the bindingWrapper - const bool inWrapper = inBindingWrapper(storage); - if (bd && !inWrapper) - bd->removeBinding(); + if (auto *bd = storage->bindingData(this)) { + // make sure we don't remove the binding if called from the bindingWrapper + if (bd->hasBinding() && !inBindingWrapper(storage)) + bd->removeBinding_helper(); + } } void notify() { QBindingStorage *storage = qGetBindingStorage(owner()); - auto bd = storage->bindingData(this, false); - const bool inWrapper = inBindingWrapper(storage); - if (bd && !inWrapper) - notify(bd); - if constexpr (Signal != nullptr) { + if (auto bd = storage->bindingData(this, false)) { + // This partly duplicates QPropertyBindingData::notifyObservers because we want to + // check for inBindingWrapper() after checking for isNotificationDelayed() and + // firstObserver. This is because inBindingWrapper() is the most expensive check. + if (!bd->isNotificationDelayed()) { + QPropertyBindingDataPointer d{bd}; + if (QPropertyObserverPointer observer = d.firstObserver()) { + if (!inBindingWrapper(storage)) { + PendingBindingObserverList bindingObservers; + if (bd->notifyObserver_helper(this, storage, observer, bindingObservers) + == QtPrivate::QPropertyBindingData::Evaluated) { + // evaluateBindings() can trash the observers. We need to re-fetch here. + if (QPropertyObserverPointer observer = d.firstObserver()) + observer.notify(this); + for (auto&& bindingObserver: bindingObservers) + bindingObserver.binding()->notifyNonRecursive(); + } + } + } + } + } + if constexpr (!std::is_null_pointer_v<decltype(Signal)>) { if constexpr (SignalTakesValue::value) - (owner()->*Signal)(value()); + (owner()->*Signal)(getPropertyValue(this)); else (owner()->*Signal)(); } @@ -582,14 +725,35 @@ public: auto *storage = const_cast<QBindingStorage *>(qGetBindingStorage(owner())); return *storage->bindingData(const_cast<QObjectCompatProperty *>(this), true); } +}; -private: - void notify(const QtPrivate::QPropertyBindingData *binding) - { - if (binding) - binding->notifyObservers(this); - } +namespace QtPrivate { +template<typename Class, typename Ty, auto Offset, auto Setter, auto Signal, auto Getter> +class QBindableInterfaceForProperty< + QObjectCompatProperty<Class, Ty, Offset, Setter, Signal, Getter>, std::void_t<Class>> +{ + using Property = QObjectCompatProperty<Class, Ty, Offset, Setter, Signal, Getter>; + using T = typename Property::value_type; +public: + static constexpr QBindableInterface iface = { + [](const QUntypedPropertyData *d, void *value) -> void + { *static_cast<T*>(value) = Property::getPropertyValue(d); }, + [](QUntypedPropertyData *d, const void *value) -> void + { + (static_cast<Property *>(d)->owner()->*Setter)(*static_cast<const T*>(value)); + }, + [](const QUntypedPropertyData *d) -> QUntypedPropertyBinding + { return static_cast<const Property *>(d)->binding(); }, + [](QUntypedPropertyData *d, const QUntypedPropertyBinding &binding) -> QUntypedPropertyBinding + { return static_cast<Property *>(d)->setBinding(static_cast<const QPropertyBinding<T> &>(binding)); }, + [](const QUntypedPropertyData *d, const QPropertyBindingSourceLocation &location) -> QUntypedPropertyBinding + { return Qt::makePropertyBinding([d]() -> T { return Property::getPropertyValue(d); }, location); }, + [](const QUntypedPropertyData *d, QPropertyObserver *observer) -> void + { observer->setSource(static_cast<const Property *>(d)->bindingData()); }, + []() { return QMetaType::fromType<T>(); } + }; }; +} #define QT_OBJECT_COMPAT_PROPERTY_4(Class, Type, name, setter) \ static constexpr size_t _qt_property_##name##_offset() { \ @@ -632,6 +796,16 @@ private: QObjectCompatProperty<Class, Type, Class::_qt_property_##name##_offset, setter, \ signal>(value); +#define QT_OBJECT_COMPAT_PROPERTY_WITH_ARGS_7(Class, Type, name, setter, signal, getter, value) \ + static constexpr size_t _qt_property_##name##_offset() { \ + QT_WARNING_PUSH QT_WARNING_DISABLE_INVALID_OFFSETOF \ + return offsetof(Class, name); \ + QT_WARNING_POP \ + } \ + QObjectCompatProperty<Class, Type, Class::_qt_property_##name##_offset, setter, signal, getter>\ + name = QObjectCompatProperty<Class, Type, Class::_qt_property_##name##_offset, setter, \ + signal, getter>(value); + #define Q_OBJECT_COMPAT_PROPERTY_WITH_ARGS(...) \ QT_WARNING_PUSH QT_WARNING_DISABLE_INVALID_OFFSETOF \ QT_OVERLOADED_MACRO(QT_OBJECT_COMPAT_PROPERTY_WITH_ARGS, __VA_ARGS__) \ @@ -656,6 +830,172 @@ struct QUntypedBindablePrivate } }; +inline bool QPropertyBindingPrivate::evaluateRecursive_inline(PendingBindingObserverList &bindingObservers, QBindingStatus *status) +{ + if (updating) { + error = QPropertyBindingError(QPropertyBindingError::BindingLoop); + if (isQQmlPropertyBinding) + errorCallBack(this); + return false; + } + + /* + * Evaluating the binding might lead to the binding being broken. This can + * cause ref to reach zero at the end of the function. However, the + * updateGuard's destructor will then still trigger, trying to set the + * updating bool to its old value + * To prevent this, we create a QPropertyBindingPrivatePtr which ensures + * that the object is still alive when updateGuard's dtor runs. + */ + QPropertyBindingPrivatePtr keepAlive {this}; + + QScopedValueRollback<bool> updateGuard(updating, true); + + QtPrivate::BindingEvaluationState evaluationFrame(this, status); + + auto bindingFunctor = reinterpret_cast<std::byte *>(this) + + QPropertyBindingPrivate::getSizeEnsuringAlignment(); + bool changed = false; + if (hasBindingWrapper) { + changed = staticBindingWrapper(metaType, propertyDataPtr, + {vtable, bindingFunctor}); + } else { + changed = vtable->call(metaType, propertyDataPtr, bindingFunctor); + } + // If there was a change, we must set pendingNotify. + // If there was not, we must not clear it, as that only should happen in notifyRecursive + pendingNotify = pendingNotify || changed; + if (!changed || !firstObserver) + return changed; + + firstObserver.noSelfDependencies(this); + firstObserver.evaluateBindings(bindingObservers, status); + return true; +} + +/*! + \internal + + Walks through the list of property observers, and calls any ChangeHandler + found there. + It doesn't do anything with bindings, which are only handled in + QPropertyBindingPrivate::evaluateRecursive. + */ +inline void QPropertyObserverPointer::notify(QUntypedPropertyData *propertyDataPtr) +{ + auto observer = const_cast<QPropertyObserver*>(ptr); + /* + * The basic idea of the loop is as follows: We iterate over all observers in the linked list, + * and execute the functionality corresponding to their tag. + * However, complication arise due to the fact that the triggered operations might modify the list, + * which includes deletion and move of the current and next nodes. + * Therefore, we take a few safety precautions: + * 1. Before executing any action which might modify the list, we insert a placeholder node after the current node. + * As that one is stack allocated and owned by us, we can rest assured that it is + * still there after the action has executed, and placeHolder->next points to the actual next node in the list. + * Note that taking next at the beginning of the loop does not work, as the executed action might either move + * or delete that node. + * 2. After the triggered action has finished, we can use the next pointer in the placeholder node as a safe way to + * retrieve the next node. + * 3. Some care needs to be taken to avoid infinite recursion with change handlers, so we add an extra test there, that + * checks whether we're already have the same change handler in our call stack. This can be done by checking whether + * the node after the current one is a placeholder node. + */ + while (observer) { + QPropertyObserver *next = observer->next.data(); + switch (QPropertyObserver::ObserverTag(observer->next.tag())) { + case QPropertyObserver::ObserverNotifiesChangeHandler: + { + auto handlerToCall = observer->changeHandler; + // prevent recursion + if (next && next->next.tag() == QPropertyObserver::ObserverIsPlaceholder) { + observer = next->next.data(); + continue; + } + // handlerToCall might modify the list + QPropertyObserverNodeProtector protector(observer); + handlerToCall(observer, propertyDataPtr); + next = protector.next(); + break; + } + case QPropertyObserver::ObserverNotifiesBinding: + break; + case QPropertyObserver::ObserverIsPlaceholder: + // recursion is already properly handled somewhere else + break; +#if QT_DEPRECATED_SINCE(6, 6) + QT_WARNING_PUSH QT_WARNING_DISABLE_DEPRECATED + case QPropertyObserver::ObserverIsAlias: + break; + QT_WARNING_POP +#endif + default: Q_UNREACHABLE(); + } + observer = next; + } +} + +inline QPropertyObserverNodeProtector::~QPropertyObserverNodeProtector() +{ + QPropertyObserverPointer d{static_cast<QPropertyObserver *>(&m_placeHolder)}; + d.unlink_fast(); +} + +QBindingObserverPtr::QBindingObserverPtr(QPropertyObserver *observer) noexcept : d(observer) +{ + Q_ASSERT(d); + QPropertyObserverPointer{d}.binding()->addRef(); +} + +QBindingObserverPtr::~QBindingObserverPtr() +{ + if (!d) + return; + + QPropertyBindingPrivate *bindingPrivate = binding(); + if (!bindingPrivate->deref()) + QPropertyBindingPrivate::destroyAndFreeMemory(bindingPrivate); +} + +QPropertyBindingPrivate *QBindingObserverPtr::binding() const noexcept { return QPropertyObserverPointer{d}.binding(); } + +QPropertyObserver *QBindingObserverPtr::operator->() { return d; } + +namespace QtPrivate { +class QPropertyAdaptorSlotObject : public QUntypedPropertyData, public QSlotObjectBase +{ + QPropertyBindingData bindingData_; + QObject *obj; + QMetaProperty metaProperty_; + +#if QT_VERSION < QT_VERSION_CHECK(7, 0, 0) + static void impl(int which, QSlotObjectBase *this_, QObject *r, void **a, bool *ret); +#else + static void impl(QSlotObjectBase *this_, QObject *r, void **a, int which, bool *ret); +#endif + + QPropertyAdaptorSlotObject(QObject *o, const QMetaProperty& p); + +public: + static QPropertyAdaptorSlotObject *cast(QSlotObjectBase *ptr, int propertyIndex) + { + if (ptr->isImpl(&QPropertyAdaptorSlotObject::impl)) { + auto p = static_cast<QPropertyAdaptorSlotObject *>(ptr); + if (p->metaProperty_.propertyIndex() == propertyIndex) + return p; + } + return nullptr; + } + + inline const QPropertyBindingData &bindingData() const { return bindingData_; } + inline QPropertyBindingData &bindingData() { return bindingData_; } + inline QObject *object() const { return obj; } + inline const QMetaProperty &metaProperty() const { return metaProperty_; } + + friend class QT_PREPEND_NAMESPACE(QUntypedBindable); +}; +} + QT_END_NAMESPACE #endif // QPROPERTY_P_H |