QProperty: Steal currentCompatProperty while evaluating a different one
currentCompatProperty should point to the compat property that's currently being evaluated. As soon as we start evaluating a new compat property, it's invalid by definition. Temporarily disable it then. Pick-to: 6.6 6.5 6.2 Fixes: QTBUG-109465 Change-Id: I7baba9350ebf488370a63a71f0f8dbd7516bf578 Reviewed-by: Fabian Kosmale <fabian.kosmale@qt.io> Reviewed-by: Qt CI Bot <qt_ci_bot@qt-project.org> Reviewed-by: Ivan Solovev <ivan.solovev@qt.io>
This commit is contained in:
parent
9de4133da2
commit
c4bfd32cca
@ -225,6 +225,33 @@ struct CompatPropertySafePoint
|
|||||||
QtPrivate::BindingEvaluationState *bindingState = nullptr;
|
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
|
class Q_CORE_EXPORT QPropertyBindingPrivate : public QtPrivate::RefCounted
|
||||||
@ -493,13 +520,16 @@ class QObjectCompatProperty : public QPropertyData<T>
|
|||||||
static bool bindingWrapper(QMetaType type, QUntypedPropertyData *dataPtr, QtPrivate::QPropertyBindingFunction binding)
|
static bool bindingWrapper(QMetaType type, QUntypedPropertyData *dataPtr, QtPrivate::QPropertyBindingFunction binding)
|
||||||
{
|
{
|
||||||
auto *thisData = static_cast<ThisType *>(dataPtr);
|
auto *thisData = static_cast<ThisType *>(dataPtr);
|
||||||
|
QBindingStorage *storage = qGetBindingStorage(thisData->owner());
|
||||||
QPropertyData<T> copy;
|
QPropertyData<T> copy;
|
||||||
|
{
|
||||||
|
QtPrivate::CurrentCompatPropertyThief thief(storage->bindingStatus);
|
||||||
binding.vtable->call(type, ©, binding.functor);
|
binding.vtable->call(type, ©, binding.functor);
|
||||||
if constexpr (QTypeTraits::has_operator_equal_v<T>)
|
if constexpr (QTypeTraits::has_operator_equal_v<T>)
|
||||||
if (copy.valueBypassingBindings() == thisData->valueBypassingBindings())
|
if (copy.valueBypassingBindings() == thisData->valueBypassingBindings())
|
||||||
return false;
|
return false;
|
||||||
|
}
|
||||||
// ensure value and setValue know we're currently evaluating our binding
|
// ensure value and setValue know we're currently evaluating our binding
|
||||||
QBindingStorage *storage = qGetBindingStorage(thisData->owner());
|
|
||||||
QtPrivate::CompatPropertySafePoint guardThis(storage->bindingStatus, thisData);
|
QtPrivate::CompatPropertySafePoint guardThis(storage->bindingStatus, thisData);
|
||||||
(thisData->owner()->*Setter)(copy.valueBypassingBindings());
|
(thisData->owner()->*Setter)(copy.valueBypassingBindings());
|
||||||
return true;
|
return true;
|
||||||
|
@ -106,6 +106,7 @@ private slots:
|
|||||||
void notifyAfterAllDepsGone();
|
void notifyAfterAllDepsGone();
|
||||||
|
|
||||||
void propertyAdaptorBinding();
|
void propertyAdaptorBinding();
|
||||||
|
void propertyUpdateViaSignaledProperty();
|
||||||
};
|
};
|
||||||
|
|
||||||
void tst_QProperty::functorBinding()
|
void tst_QProperty::functorBinding()
|
||||||
@ -2372,6 +2373,110 @@ void tst_QProperty::notifyAfterAllDepsGone()
|
|||||||
QCOMPARE(changeCounter, 2);
|
QCOMPARE(changeCounter, 2);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class TestObject : public QObject
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
Q_PROPERTY(int signaled READ signaled WRITE setSignaled NOTIFY signaledChanged FINAL)
|
||||||
|
Q_PROPERTY(int bindable1 READ bindable1 WRITE setBindable1 BINDABLE bindable1Bindable NOTIFY bindable1Changed FINAL)
|
||||||
|
Q_PROPERTY(int bindable2 READ bindable2 WRITE setBindable2 BINDABLE bindable2Bindable NOTIFY bindable2Changed FINAL)
|
||||||
|
|
||||||
|
public:
|
||||||
|
int signaled() const
|
||||||
|
{
|
||||||
|
return m_signaled;
|
||||||
|
}
|
||||||
|
|
||||||
|
void setSignaled(int newSignaled)
|
||||||
|
{
|
||||||
|
if (m_signaled == newSignaled)
|
||||||
|
return;
|
||||||
|
m_signaled = newSignaled;
|
||||||
|
emit signaledChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
int bindable1() const
|
||||||
|
{
|
||||||
|
return m_bindable1;
|
||||||
|
}
|
||||||
|
|
||||||
|
void setBindable1(int newBindable1)
|
||||||
|
{
|
||||||
|
if (m_bindable1 == newBindable1)
|
||||||
|
return;
|
||||||
|
m_bindable1 = newBindable1;
|
||||||
|
emit bindable1Changed();
|
||||||
|
}
|
||||||
|
|
||||||
|
QBindable<int> bindable1Bindable()
|
||||||
|
{
|
||||||
|
return QBindable<int>(&m_bindable1);
|
||||||
|
}
|
||||||
|
|
||||||
|
int bindable2() const
|
||||||
|
{
|
||||||
|
return m_bindable2;
|
||||||
|
}
|
||||||
|
|
||||||
|
void setBindable2(int newBindable2)
|
||||||
|
{
|
||||||
|
if (m_bindable2 == newBindable2)
|
||||||
|
return;
|
||||||
|
m_bindable2 = newBindable2;
|
||||||
|
emit bindable2Changed();
|
||||||
|
}
|
||||||
|
|
||||||
|
QBindable<int> bindable2Bindable()
|
||||||
|
{
|
||||||
|
return QBindable<int>(&m_bindable2);
|
||||||
|
}
|
||||||
|
|
||||||
|
signals:
|
||||||
|
void signaledChanged();
|
||||||
|
void bindable1Changed();
|
||||||
|
void bindable2Changed();
|
||||||
|
|
||||||
|
private:
|
||||||
|
int m_signaled = 0;
|
||||||
|
Q_OBJECT_COMPAT_PROPERTY(TestObject, int, m_bindable1, &TestObject::setBindable1, &TestObject::bindable1Changed);
|
||||||
|
Q_OBJECT_COMPAT_PROPERTY(TestObject, int, m_bindable2, &TestObject::setBindable2, &TestObject::bindable2Changed);
|
||||||
|
};
|
||||||
|
|
||||||
|
void tst_QProperty::propertyUpdateViaSignaledProperty()
|
||||||
|
{
|
||||||
|
TestObject o;
|
||||||
|
QProperty<int> rootTrigger;
|
||||||
|
QProperty<int> signalTrigger;
|
||||||
|
|
||||||
|
o.bindable1Bindable().setBinding([&]() {
|
||||||
|
return rootTrigger.value();
|
||||||
|
});
|
||||||
|
|
||||||
|
QObject::connect(&o, &TestObject::bindable1Changed, &o, [&]() {
|
||||||
|
// Signaled changes only once, doesn't actually depend on bindable1.
|
||||||
|
// In reality, there could be some complicated calculation behind this that changes
|
||||||
|
// on certain checkpoints, but not on every iteration.
|
||||||
|
o.setSignaled(40);
|
||||||
|
});
|
||||||
|
|
||||||
|
o.bindable2Bindable().setBinding([&]() {
|
||||||
|
return signalTrigger.value() - o.bindable1();
|
||||||
|
});
|
||||||
|
|
||||||
|
QObject::connect(&o, &TestObject::signaledChanged, &o, [&]() {
|
||||||
|
signalTrigger.setValue(o.signaled());
|
||||||
|
});
|
||||||
|
|
||||||
|
rootTrigger.setValue(2);
|
||||||
|
QCOMPARE(o.bindable1(), 2);
|
||||||
|
QCOMPARE(o.bindable2(), 38);
|
||||||
|
rootTrigger.setValue(3);
|
||||||
|
QCOMPARE(o.bindable1(), 3);
|
||||||
|
QCOMPARE(o.bindable2(), 37);
|
||||||
|
rootTrigger.setValue(4);
|
||||||
|
QCOMPARE(o.bindable1(), 4);
|
||||||
|
QCOMPARE(o.bindable2(), 36);
|
||||||
|
}
|
||||||
|
|
||||||
QTEST_MAIN(tst_QProperty);
|
QTEST_MAIN(tst_QProperty);
|
||||||
|
|
||||||
#undef QT_SOURCE_LOCATION_NAMESPACE
|
#undef QT_SOURCE_LOCATION_NAMESPACE
|
||||||
|
Loading…
x
Reference in New Issue
Block a user