diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/corelib/kernel/qproperty.cpp | 38 | ||||
-rw-r--r-- | src/corelib/kernel/qproperty_p.h | 226 | ||||
-rw-r--r-- | src/corelib/kernel/qpropertyprivate.h | 31 |
3 files changed, 243 insertions, 52 deletions
diff --git a/src/corelib/kernel/qproperty.cpp b/src/corelib/kernel/qproperty.cpp index b39b4c1c33..72f74ac1e8 100644 --- a/src/corelib/kernel/qproperty.cpp +++ b/src/corelib/kernel/qproperty.cpp @@ -82,6 +82,10 @@ void QPropertyBindingPrivate::markDirtyAndNotifyObservers() if (dirty) return; dirty = true; + if (requiresEagerEvaluation()) { + // these are compat properties that we will need to evaluate eagerly + evaluateIfDirtyAndReturnTrueIfValueChanged(propertyDataPtr); + } if (firstObserver) firstObserver.notify(this, propertyDataPtr); if (hasStaticObserver) @@ -109,7 +113,7 @@ bool QPropertyBindingPrivate::evaluateIfDirtyAndReturnTrueIfValueChanged(const Q QPropertyBindingPrivatePtr keepAlive {this}; QScopedValueRollback<bool> updateGuard(updating, true); - QBindingEvaluationState evaluationFrame(this); + BindingEvaluationState evaluationFrame(this); bool changed = false; @@ -117,7 +121,7 @@ bool QPropertyBindingPrivate::evaluateIfDirtyAndReturnTrueIfValueChanged(const Q QUntypedPropertyData *mutable_data = const_cast<QUntypedPropertyData *>(data); if (hasBindingWrapper) { - changed = staticBindingWrapper(metaType, propertyDataPtr, evaluationFunction); + changed = staticBindingWrapper(metaType, mutable_data, evaluationFunction); } else { changed = evaluationFunction(metaType, mutable_data); } @@ -260,6 +264,8 @@ QUntypedPropertyBinding QPropertyBindingData::setBinding(const QUntypedPropertyB if (observer) newBinding->prependObserver(observer); newBinding->setStaticObserver(staticObserverCallback, guardCallback); + if (newBinding->requiresEagerEvaluation()) + newBinding->evaluateIfDirtyAndReturnTrueIfValueChanged(propertyDataPtr); } else if (observer) { d.setObservers(observer.ptr); } else { @@ -280,27 +286,33 @@ QPropertyBindingPrivate *QPropertyBindingData::binding() const return nullptr; } -static thread_local QBindingEvaluationState *currentBindingEvaluationState = nullptr; +static thread_local QBindingStatus bindingStatus; -QBindingEvaluationState::QBindingEvaluationState(QPropertyBindingPrivate *binding) +BindingEvaluationState::BindingEvaluationState(QPropertyBindingPrivate *binding) : binding(binding) { // store a pointer to the currentBindingEvaluationState to avoid a TLS lookup in // the destructor (as these come with a non zero cost) - currentState = ¤tBindingEvaluationState; + currentState = &bindingStatus.currentlyEvaluatingBinding; previousState = *currentState; *currentState = this; binding->clearDependencyObservers(); } -QBindingEvaluationState::~QBindingEvaluationState() +CurrentCompatProperty::CurrentCompatProperty(QBindingStatus *status, QUntypedPropertyData *property) + : property(property) { - *currentState = previousState; + // store a pointer to the currentBindingEvaluationState to avoid a TLS lookup in + // the destructor (as these come with a non zero cost) + currentState = &status->currentCompatProperty; + previousState = *currentState; + *currentState = this; } QPropertyBindingPrivate *QPropertyBindingPrivate::currentlyEvaluatingBinding() { - return currentBindingEvaluationState ? currentBindingEvaluationState->binding : nullptr; + auto currentState = bindingStatus.currentlyEvaluatingBinding ; + return currentState ? currentState->binding : nullptr; } void QPropertyBindingData::evaluateIfDirty(const QUntypedPropertyData *property) const @@ -327,7 +339,7 @@ void QPropertyBindingData::removeBinding() void QPropertyBindingData::registerWithCurrentlyEvaluatingBinding() const { - auto currentState = currentBindingEvaluationState; + auto currentState = bindingStatus.currentlyEvaluatingBinding; if (!currentState) return; @@ -1448,8 +1460,8 @@ struct QBindingStoragePrivate QBindingStorage::QBindingStorage() { - currentlyEvaluatingBinding = ¤tBindingEvaluationState; - Q_ASSERT(currentlyEvaluatingBinding); + bindingStatus = &QT_PREPEND_NAMESPACE(bindingStatus); + Q_ASSERT(bindingStatus); } QBindingStorage::~QBindingStorage() @@ -1459,9 +1471,9 @@ QBindingStorage::~QBindingStorage() void QBindingStorage::maybeUpdateBindingAndRegister(const QUntypedPropertyData *data) const { - Q_ASSERT(currentlyEvaluatingBinding); + Q_ASSERT(bindingStatus); QUntypedPropertyData *dd = const_cast<QUntypedPropertyData *>(data); - auto storage = *currentlyEvaluatingBinding ? + auto storage = bindingStatus->currentlyEvaluatingBinding ? QBindingStoragePrivate(d).getAndCreate(dd) : QBindingStoragePrivate(d).get(dd); if (!storage) diff --git a/src/corelib/kernel/qproperty_p.h b/src/corelib/kernel/qproperty_p.h index 6c53101129..c5c74147c1 100644 --- a/src/corelib/kernel/qproperty_p.h +++ b/src/corelib/kernel/qproperty_p.h @@ -1,4 +1,4 @@ -/**************************************************************************** +/*************************************************************************** ** ** Copyright (C) 2020 The Qt Company Ltd. ** Contact: https://www.qt.io/licensing/ @@ -120,13 +120,39 @@ public: QString description; }; -struct QBindingEvaluationState +namespace QtPrivate { + +struct BindingEvaluationState { - QBindingEvaluationState(QPropertyBindingPrivate *binding); - ~QBindingEvaluationState(); + BindingEvaluationState(QPropertyBindingPrivate *binding); + ~BindingEvaluationState() + { + *currentState = previousState; + } + QPropertyBindingPrivate *binding; - QBindingEvaluationState *previousState = nullptr; - QBindingEvaluationState **currentState = nullptr; + BindingEvaluationState *previousState = nullptr; + BindingEvaluationState **currentState = nullptr; +}; + +struct CurrentCompatProperty +{ + Q_CORE_EXPORT CurrentCompatProperty(QBindingStatus *status, QUntypedPropertyData *property); + ~CurrentCompatProperty() + { + *currentState = previousState; + } + QUntypedPropertyData *property; + CurrentCompatProperty *previousState = nullptr; + CurrentCompatProperty **currentState = nullptr; +}; + +} + +struct QBindingStatus +{ + QtPrivate::BindingEvaluationState *currentlyEvaluatingBinding = nullptr; + QtPrivate::CurrentCompatProperty *currentCompatProperty = nullptr; }; class Q_CORE_EXPORT QPropertyBindingPrivate : public QSharedData @@ -144,12 +170,13 @@ private: QUntypedPropertyBinding::BindingEvaluationFunction evaluationFunction; - QPropertyObserverPointer firstObserver; union { QtPrivate::QPropertyObserverCallback staticObserverCallback = nullptr; QtPrivate::QPropertyBindingWrapper staticBindingWrapper; }; ObserverArray inlineDependencyObservers; + + QPropertyObserverPointer firstObserver; QScopedPointer<std::vector<QPropertyObserver>> heapObservers; QUntypedPropertyData *propertyDataPtr = nullptr; @@ -174,17 +201,17 @@ public: void setDirty(bool d) { dirty = d; } void setProperty(QUntypedPropertyData *propertyPtr) { propertyDataPtr = propertyPtr; } - void setStaticObserver(QtPrivate::QPropertyObserverCallback callback, QtPrivate::QPropertyBindingWrapper guardCallback) + void setStaticObserver(QtPrivate::QPropertyObserverCallback callback, QtPrivate::QPropertyBindingWrapper bindingWrapper) { - Q_ASSERT(!(callback && guardCallback)); + Q_ASSERT(!(callback && bindingWrapper)); if (callback) { hasStaticObserver = true; hasBindingWrapper = false; staticObserverCallback = callback; - } else if (guardCallback) { + } else if (bindingWrapper) { hasStaticObserver = false; hasBindingWrapper = true; - staticBindingWrapper = guardCallback; + staticBindingWrapper = bindingWrapper; } else { hasStaticObserver = false; hasBindingWrapper = false; @@ -245,6 +272,8 @@ public: clearDependencyObservers(); } + bool requiresEagerEvaluation() const { return hasBindingWrapper; } + static QPropertyBindingPrivate *currentlyEvaluatingBinding(); }; @@ -264,6 +293,181 @@ inline QPropertyObserverPointer QPropertyBindingDataPointer::firstObserver() con return {reinterpret_cast<QPropertyObserver*>(ptr->d_ptr & ~QtPrivate::QPropertyBindingData::FlagMask)}; } + +template<typename Class, typename T, auto Offset, auto Setter> +class QObjectCompatProperty : public QPropertyData<T> +{ + using ThisType = QObjectCompatProperty<Class, T, Offset, Setter>; + Class *owner() + { + char *that = reinterpret_cast<char *>(this); + return reinterpret_cast<Class *>(that - QtPrivate::detail::getOffset(Offset)); + } + const Class *owner() const + { + 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); + QPropertyData<T> copy; + binding(type, ©); + 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::CurrentCompatProperty guardThis(storage->bindingStatus, thisData); + (thisData->owner()->*Setter)(copy.valueBypassingBindings()); + return true; + } + inline bool inBindingWrapper(const QBindingStorage *storage) const + { + return storage->bindingStatus->currentCompatProperty && + storage->bindingStatus->currentCompatProperty->property == this; + } + +public: + using value_type = typename QPropertyData<T>::value_type; + using parameter_type = typename QPropertyData<T>::parameter_type; + using arrow_operator_result = typename QPropertyData<T>::arrow_operator_result; + + QObjectCompatProperty() = default; + explicit QObjectCompatProperty(const T &initialValue) : QPropertyData<T>(initialValue) {} + explicit QObjectCompatProperty(T &&initialValue) : QPropertyData<T>(std::move(initialValue)) {} + + parameter_type value() const { + const QBindingStorage *storage = qGetBindingStorage(owner()); + // make sure we don't register this binding as a dependency to itself + if (!inBindingWrapper(storage)) + storage->maybeUpdateBindingAndRegister(this); + return this->val; + } + + arrow_operator_result operator->() const + { + if constexpr (QTypeTraits::is_dereferenceable_v<T>) { + return value(); + } else if constexpr (std::is_pointer_v<T>) { + value(); + return this->val; + } else { + return; + } + } + + parameter_type operator*() const + { + return value(); + } + + operator parameter_type() const + { + return value(); + } + + 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 + if (bd && !inBindingWrapper(storage)) + bd->removeBinding(); + if constexpr (QTypeTraits::has_operator_equal_v<T>) + if (this->val == t) + return; + this->val = t; + notify(bd); + } + + QObjectCompatProperty &operator=(parameter_type newValue) + { + setValue(newValue); + return *this; + } + + QPropertyBinding<T> setBinding(const QPropertyBinding<T> &newBinding) + { + QtPrivate::QPropertyBindingData *bd = qGetBindingStorage(owner())->bindingData(this, true); + QUntypedPropertyBinding oldBinding(bd->setBinding(newBinding, this, nullptr, bindingWrapper)); + notify(bd); + return static_cast<QPropertyBinding<T> &>(oldBinding); + } + + bool setBinding(const QUntypedPropertyBinding &newBinding) + { + if (!newBinding.isNull() && newBinding.valueMetaType().id() != qMetaTypeId<T>()) + return false; + setBinding(static_cast<const QPropertyBinding<T> &>(newBinding)); + return true; + } + +#ifndef Q_CLANG_QDOC + template <typename Functor> + QPropertyBinding<T> setBinding(Functor &&f, + const QPropertyBindingSourceLocation &location = QT_PROPERTY_DEFAULT_BINDING_LOCATION, + std::enable_if_t<std::is_invocable_v<Functor>> * = nullptr) + { + return setBinding(Qt::makePropertyBinding(std::forward<Functor>(f), location)); + } +#else + template <typename Functor> + QPropertyBinding<T> setBinding(Functor f); +#endif + + bool hasBinding() const { + auto *bd = qGetBindingStorage(owner())->bindingData(this); + return bd && bd->binding() != nullptr; + } + + QPropertyBinding<T> binding() const + { + auto *bd = qGetBindingStorage(owner())->bindingData(this); + return static_cast<QPropertyBinding<T> &&>(QUntypedPropertyBinding(bd ? bd->binding() : nullptr)); + } + + QPropertyBinding<T> takeBinding() + { + return setBinding(QPropertyBinding<T>()); + } + + template<typename Functor> + QPropertyChangeHandler<Functor> onValueChanged(Functor f) + { + static_assert(std::is_invocable_v<Functor>, "Functor callback must be callable without any parameters"); + return QPropertyChangeHandler<Functor>(*this, f); + } + + template<typename Functor> + QPropertyChangeHandler<Functor> subscribe(Functor f) + { + static_assert(std::is_invocable_v<Functor>, "Functor callback must be callable without any parameters"); + f(); + return onValueChanged(f); + } + + QtPrivate::QPropertyBindingData &bindingData() const + { + 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); + } +}; + +#define Q_OBJECT_COMPAT_PROPERTY(Class, Type, name, setter) \ + 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> name; + + QT_END_NAMESPACE #endif // QPROPERTY_P_H diff --git a/src/corelib/kernel/qpropertyprivate.h b/src/corelib/kernel/qpropertyprivate.h index 15e54e9ba8..dd344c209a 100644 --- a/src/corelib/kernel/qpropertyprivate.h +++ b/src/corelib/kernel/qpropertyprivate.h @@ -76,10 +76,8 @@ namespace QtPrivate { // writes binding result into dataPtr using QPropertyBindingFunction = std::function<bool(QMetaType metaType, QUntypedPropertyData *dataPtr)>; - -using QPropertyBindingWrapper = bool(*)(QMetaType, QUntypedPropertyData *dataPtr, - QPropertyBindingFunction); using QPropertyObserverCallback = void (*)(QUntypedPropertyData *); +using QPropertyBindingWrapper = bool(*)(QMetaType, QUntypedPropertyData *dataPtr, QPropertyBindingFunction); class Q_CORE_EXPORT QPropertyBindingData { @@ -101,7 +99,8 @@ public: QUntypedPropertyBinding setBinding(const QUntypedPropertyBinding &newBinding, QUntypedPropertyData *propertyDataPtr, QPropertyObserverCallback staticObserverCallback = nullptr, - QPropertyBindingWrapper guardCallback = nullptr); + QPropertyBindingWrapper bindingWrapper = nullptr); + QPropertyBindingPrivate *binding() const; void evaluateIfDirty(const QUntypedPropertyData *property) const; @@ -188,30 +187,6 @@ namespace detail { } } -// type erased guard functions, casts its arguments to the correct types -template<typename T, typename Class, auto Guard, bool = std::is_same_v<decltype(Guard), std::nullptr_t>> -struct QPropertyGuardFunctionHelper -{ - static constexpr QPropertyBindingWrapper guard = nullptr; -}; -template<typename T, typename Class, auto Guard> -struct QPropertyGuardFunctionHelper<T, Class, Guard, false> -{ - static auto guard(QMetaType metaType, QUntypedPropertyData *dataPtr, - QPropertyBindingFunction eval, void *owner) -> bool - { - T t = T(); - eval(metaType, &t); - if (!(static_cast<Class *>(owner)->*Guard)(t)) - return false; - T *data = static_cast<T *>(dataPtr); - if (*data == t) - return false; - *data = std::move(t); - return true; - }; -}; - } // namespace QtPrivate QT_END_NAMESPACE |