Simplify the safeguarding logic in notify()
The logic in notify() was doing quite a bit more work than it needed to. By inserting a dummy node after the current one instead of replacing it, we can avoid half of the data shuffling that has been happening and also don't need a back pointer when executing the notification. Also avoid calling a semi expensive destructor of QPropertyObserver. Reduces the overhead of notify() by ~30%. Pick-to: dev 6.0.0 Change-Id: I7ce16bcf9cd9c4368c18bf875fc959223452fd4f Reviewed-by: Fabian Kosmale <fabian.kosmale@qt.io> Reviewed-by: Qt CI Bot <qt_ci_bot@qt-project.org>
This commit is contained in:
parent
c63901c5f3
commit
e8ef871e35
@ -398,10 +398,6 @@ void QPropertyObserver::setSource(const QPropertyBindingData &property)
|
|||||||
|
|
||||||
QPropertyObserver::~QPropertyObserver()
|
QPropertyObserver::~QPropertyObserver()
|
||||||
{
|
{
|
||||||
if (next.tag() == ActivelyExecuting) {
|
|
||||||
if (nodeState)
|
|
||||||
*nodeState = nullptr;
|
|
||||||
}
|
|
||||||
QPropertyObserverPointer d{this};
|
QPropertyObserverPointer d{this};
|
||||||
d.unlink();
|
d.unlink();
|
||||||
}
|
}
|
||||||
@ -415,8 +411,6 @@ QPropertyObserver::QPropertyObserver(QPropertyObserver &&other) noexcept
|
|||||||
next->prev = &next;
|
next->prev = &next;
|
||||||
if (prev)
|
if (prev)
|
||||||
prev.setPointer(this);
|
prev.setPointer(this);
|
||||||
if (next.tag() == ActivelyExecuting)
|
|
||||||
*nodeState = this;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
QPropertyObserver &QPropertyObserver::operator=(QPropertyObserver &&other) noexcept
|
QPropertyObserver &QPropertyObserver::operator=(QPropertyObserver &&other) noexcept
|
||||||
@ -435,8 +429,6 @@ QPropertyObserver &QPropertyObserver::operator=(QPropertyObserver &&other) noexc
|
|||||||
next->prev = &next;
|
next->prev = &next;
|
||||||
if (prev)
|
if (prev)
|
||||||
prev.setPointer(this);
|
prev.setPointer(this);
|
||||||
if (next.tag() == ActivelyExecuting)
|
|
||||||
*nodeState = this;
|
|
||||||
|
|
||||||
return *this;
|
return *this;
|
||||||
}
|
}
|
||||||
@ -455,21 +447,21 @@ void QPropertyObserverPointer::unlink()
|
|||||||
|
|
||||||
void QPropertyObserverPointer::setChangeHandler(QPropertyObserver::ChangeHandler changeHandler)
|
void QPropertyObserverPointer::setChangeHandler(QPropertyObserver::ChangeHandler changeHandler)
|
||||||
{
|
{
|
||||||
Q_ASSERT(ptr->next.tag() != QPropertyObserver::ActivelyExecuting);
|
Q_ASSERT(ptr->next.tag() != QPropertyObserver::ObserverIsPlaceholder);
|
||||||
ptr->changeHandler = changeHandler;
|
ptr->changeHandler = changeHandler;
|
||||||
ptr->next.setTag(QPropertyObserver::ObserverNotifiesChangeHandler);
|
ptr->next.setTag(QPropertyObserver::ObserverNotifiesChangeHandler);
|
||||||
}
|
}
|
||||||
|
|
||||||
void QPropertyObserverPointer::setAliasedProperty(QUntypedPropertyData *property)
|
void QPropertyObserverPointer::setAliasedProperty(QUntypedPropertyData *property)
|
||||||
{
|
{
|
||||||
Q_ASSERT(ptr->next.tag() != QPropertyObserver::ActivelyExecuting);
|
Q_ASSERT(ptr->next.tag() != QPropertyObserver::ObserverIsPlaceholder);
|
||||||
ptr->aliasedPropertyData = property;
|
ptr->aliasedPropertyData = property;
|
||||||
ptr->next.setTag(QPropertyObserver::ObserverNotifiesAlias);
|
ptr->next.setTag(QPropertyObserver::ObserverNotifiesAlias);
|
||||||
}
|
}
|
||||||
|
|
||||||
void QPropertyObserverPointer::setBindingToMarkDirty(QPropertyBindingPrivate *binding)
|
void QPropertyObserverPointer::setBindingToMarkDirty(QPropertyBindingPrivate *binding)
|
||||||
{
|
{
|
||||||
Q_ASSERT(ptr->next.tag() != QPropertyObserver::ActivelyExecuting);
|
Q_ASSERT(ptr->next.tag() != QPropertyObserver::ObserverIsPlaceholder);
|
||||||
ptr->bindingToMarkDirty = binding;
|
ptr->bindingToMarkDirty = binding;
|
||||||
ptr->next.setTag(QPropertyObserver::ObserverNotifiesBinding);
|
ptr->next.setTag(QPropertyObserver::ObserverNotifiesBinding);
|
||||||
}
|
}
|
||||||
@ -479,55 +471,26 @@ void QPropertyObserverPointer::setBindingToMarkDirty(QPropertyBindingPrivate *bi
|
|||||||
QPropertyObserverNodeProtector is a RAII wrapper which takes care of the internal switching logic
|
QPropertyObserverNodeProtector is a RAII wrapper which takes care of the internal switching logic
|
||||||
for QPropertyObserverPointer::notify (described ibidem)
|
for QPropertyObserverPointer::notify (described ibidem)
|
||||||
*/
|
*/
|
||||||
template <QPropertyObserver::ObserverTag tag>
|
|
||||||
struct [[nodiscard]] QPropertyObserverNodeProtector {
|
struct [[nodiscard]] QPropertyObserverNodeProtector {
|
||||||
QPropertyObserver m_placeHolder;
|
QPropertyObserverBase m_placeHolder;
|
||||||
QPropertyObserver *&m_observer;
|
QPropertyObserverNodeProtector(QPropertyObserver *observer)
|
||||||
union {
|
|
||||||
QPropertyBindingPrivate *m_binding;
|
|
||||||
QPropertyObserver::ChangeHandler m_changeHandler;
|
|
||||||
};
|
|
||||||
QPropertyObserverNodeProtector(QPropertyObserver *&observer)
|
|
||||||
: m_observer(observer)
|
|
||||||
{
|
{
|
||||||
static_assert(tag == QPropertyObserver::ObserverNotifiesBinding ||
|
// insert m_placeholder after observer into the linked list
|
||||||
tag == QPropertyObserver::ObserverNotifiesChangeHandler);
|
QPropertyObserver *next = observer->next.data();
|
||||||
if constexpr (tag == QPropertyObserver::ObserverNotifiesBinding)
|
m_placeHolder.next = next;
|
||||||
m_binding = m_observer->bindingToMarkDirty;
|
observer->next = static_cast<QPropertyObserver *>(&m_placeHolder);
|
||||||
else
|
if (next)
|
||||||
m_changeHandler = m_observer->changeHandler;
|
next->prev = &m_placeHolder.next;
|
||||||
switchNodes(m_placeHolder, m_observer);
|
m_placeHolder.prev = &observer->next;
|
||||||
m_observer->nodeState = &m_observer;
|
m_placeHolder.next.setTag(QPropertyObserver::ObserverIsPlaceholder);
|
||||||
m_observer->next.setTag(QPropertyObserver::ActivelyExecuting);
|
|
||||||
m_placeHolder.next.setTag(QPropertyObserver::ActivelyExecuting);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QPropertyObserver *next() const { return m_placeHolder.next.data(); }
|
||||||
|
|
||||||
~QPropertyObserverNodeProtector() {
|
~QPropertyObserverNodeProtector() {
|
||||||
if (m_observer) {
|
QPropertyObserverPointer d{static_cast<QPropertyObserver *>(&m_placeHolder)};
|
||||||
if constexpr (tag == QPropertyObserver::ObserverNotifiesBinding)
|
d.unlink();
|
||||||
m_observer->bindingToMarkDirty = m_binding;
|
|
||||||
else
|
|
||||||
m_observer->changeHandler = m_changeHandler;
|
|
||||||
switchNodes(*m_observer, &m_placeHolder);
|
|
||||||
m_observer->next.setTag(tag);
|
|
||||||
}
|
|
||||||
// set tag to a safer value where we don't execute anything in the dtor
|
|
||||||
m_placeHolder.next.setTag(QPropertyObserver::ObserverNotifiesChangeHandler);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/*!
|
|
||||||
\internal
|
|
||||||
replaces a node \a observer in the list with another node \a placeholder which must not be in the list
|
|
||||||
*/
|
|
||||||
static void switchNodes(QPropertyObserver &placeHolder, QPropertyObserver *observer) {
|
|
||||||
placeHolder.next = std::exchange(observer->next, {});
|
|
||||||
placeHolder.prev = std::exchange(observer->prev, {});
|
|
||||||
if (placeHolder.next) {
|
|
||||||
placeHolder.next->prev = &placeHolder.next;
|
|
||||||
}
|
|
||||||
if (placeHolder.prev)
|
|
||||||
placeHolder.prev.setPointer(&placeHolder);
|
|
||||||
};
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/*! \internal
|
/*! \internal
|
||||||
@ -539,7 +502,7 @@ struct [[nodiscard]] QPropertyObserverNodeProtector {
|
|||||||
ObserverNotifiesChangeHandler case would not work. Thus we instead pass the knowledge of
|
ObserverNotifiesChangeHandler case would not work. Thus we instead pass the knowledge of
|
||||||
whether the value has changed we obtained when evaluating the binding eagerly along
|
whether the value has changed we obtained when evaluating the binding eagerly along
|
||||||
*/
|
*/
|
||||||
void QPropertyObserverPointer::notify(QPropertyBindingPrivate *triggeringBinding, QUntypedPropertyData *propertyDataPtr,bool alreadyKnownToHaveChanged)
|
void QPropertyObserverPointer::notify(QPropertyBindingPrivate *triggeringBinding, QUntypedPropertyData *propertyDataPtr, bool alreadyKnownToHaveChanged)
|
||||||
{
|
{
|
||||||
bool knownIfPropertyChanged = alreadyKnownToHaveChanged;
|
bool knownIfPropertyChanged = alreadyKnownToHaveChanged;
|
||||||
bool propertyChanged = true;
|
bool propertyChanged = true;
|
||||||
@ -551,56 +514,56 @@ void QPropertyObserverPointer::notify(QPropertyBindingPrivate *triggeringBinding
|
|||||||
* However, complication arise due to the fact that the triggered operations might modify the list,
|
* 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.
|
* which includes deletion and move of the current and next nodes.
|
||||||
* Therefore, we take a few safety precautions:
|
* Therefore, we take a few safety precautions:
|
||||||
* 1. Before executing any action which might modify the list, we replace the actual node in the list with
|
* 1. Before executing any action which might modify the list, we insert a placeholder node after the current node.
|
||||||
* a placeholder node. As that one is stack allocated and owned by us, we can rest assured that it is
|
* 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.
|
* 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 execuated action might either move
|
* Note that taking next at the beginning of the loop does not work, as the execuated action might either move
|
||||||
* or delete that node.
|
* or delete that node.
|
||||||
* 2. To properly handle deletion or moves of the real current node, we store a pointer to a pointer to itself in
|
* 2. After the triggered action has finished, we can use the next pointer in the placeholder node as a safe way to
|
||||||
* its nodeState. Whenever the node is reallocated and moved, we update that pointer to point to its new
|
* retrieve the next node.
|
||||||
* location. If the node is actually deleted, we set it to nullptr.
|
* 3. Some care needs to be taken to avoid infinite recursion with change handlers, so we add an extra test there, that
|
||||||
* 3. After the triggered action has finished, we can use that information to restore the list to contain the actual
|
* checks whether we're already have the same change handler in our call stack. This can be done by checking whether
|
||||||
* node again. We either switch the nodes with the real nodes current location, or, if the real node has been
|
* the node after the current one is a placeholder node.
|
||||||
* deleted, we simply unlink the temporary node.
|
|
||||||
*/
|
*/
|
||||||
while (observer) {
|
while (observer) {
|
||||||
QPropertyObserver *next = nullptr;
|
QPropertyObserver *next = observer->next.data();
|
||||||
|
|
||||||
char preventBug[1] = {'\0'}; // QTBUG-87245
|
char preventBug[1] = {'\0'}; // QTBUG-87245
|
||||||
Q_UNUSED(preventBug);
|
Q_UNUSED(preventBug);
|
||||||
switch (observer->next.tag()) {
|
switch (QPropertyObserver::ObserverTag(observer->next.tag())) {
|
||||||
case QPropertyObserver::ObserverNotifiesChangeHandler:
|
case QPropertyObserver::ObserverNotifiesChangeHandler:
|
||||||
if (auto handlerToCall = observer->changeHandler) {
|
{
|
||||||
// both evaluateIfDirtyAndReturnTrueIfValueChanged and handlerToCall might modify the list
|
auto handlerToCall = observer->changeHandler;
|
||||||
QPropertyObserverNodeProtector<QPropertyObserver::ObserverNotifiesChangeHandler> protector(observer);
|
// prevent recursion
|
||||||
if (!knownIfPropertyChanged && triggeringBinding) {
|
if (next && next->next.tag() == QPropertyObserver::ObserverIsPlaceholder) {
|
||||||
knownIfPropertyChanged = true;
|
observer = next->next.data();
|
||||||
propertyChanged = triggeringBinding->evaluateIfDirtyAndReturnTrueIfValueChanged(propertyDataPtr);
|
continue;
|
||||||
}
|
|
||||||
if (!propertyChanged)
|
|
||||||
return;
|
|
||||||
handlerToCall(observer, propertyDataPtr);
|
|
||||||
next = protector.m_placeHolder.next.data();
|
|
||||||
} else {
|
|
||||||
next = observer->next.data();
|
|
||||||
}
|
}
|
||||||
|
// both evaluateIfDirtyAndReturnTrueIfValueChanged and handlerToCall might modify the list
|
||||||
|
QPropertyObserverNodeProtector protector(observer);
|
||||||
|
if (!knownIfPropertyChanged && triggeringBinding) {
|
||||||
|
knownIfPropertyChanged = true;
|
||||||
|
propertyChanged = triggeringBinding->evaluateIfDirtyAndReturnTrueIfValueChanged(propertyDataPtr);
|
||||||
|
}
|
||||||
|
if (!propertyChanged)
|
||||||
|
return;
|
||||||
|
handlerToCall(observer, propertyDataPtr);
|
||||||
|
next = protector.next();
|
||||||
break;
|
break;
|
||||||
|
}
|
||||||
case QPropertyObserver::ObserverNotifiesBinding:
|
case QPropertyObserver::ObserverNotifiesBinding:
|
||||||
if (auto bindingToMarkDirty = observer->bindingToMarkDirty) {
|
{
|
||||||
QPropertyObserverNodeProtector<QPropertyObserver::ObserverNotifiesBinding> protector(observer);
|
auto bindingToMarkDirty = observer->bindingToMarkDirty;
|
||||||
bindingToMarkDirty->markDirtyAndNotifyObservers();
|
QPropertyObserverNodeProtector protector(observer);
|
||||||
next = protector.m_placeHolder.next.data();
|
bindingToMarkDirty->markDirtyAndNotifyObservers();
|
||||||
} else {
|
next = protector.next();
|
||||||
next = observer->next.data();
|
|
||||||
}
|
|
||||||
break;
|
break;
|
||||||
|
}
|
||||||
case QPropertyObserver::ObserverNotifiesAlias:
|
case QPropertyObserver::ObserverNotifiesAlias:
|
||||||
next = observer->next.data();
|
|
||||||
break;
|
break;
|
||||||
case QPropertyObserver::ActivelyExecuting:
|
case QPropertyObserver::ObserverIsPlaceholder:
|
||||||
// recursion is already properly handled somewhere else
|
// recursion is already properly handled somewhere else
|
||||||
return;
|
break;
|
||||||
default:
|
|
||||||
Q_UNREACHABLE();
|
|
||||||
}
|
}
|
||||||
observer = next;
|
observer = next;
|
||||||
}
|
}
|
||||||
|
@ -205,8 +205,9 @@ namespace Qt {
|
|||||||
|
|
||||||
struct QPropertyObserverPrivate;
|
struct QPropertyObserverPrivate;
|
||||||
struct QPropertyObserverPointer;
|
struct QPropertyObserverPointer;
|
||||||
|
class QPropertyObserver;
|
||||||
|
|
||||||
class Q_CORE_EXPORT QPropertyObserver
|
class QPropertyObserverBase
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
// Internal
|
// Internal
|
||||||
@ -214,10 +215,33 @@ public:
|
|||||||
ObserverNotifiesBinding, // observer was installed to notify bindings that obsverved property changed
|
ObserverNotifiesBinding, // observer was installed to notify bindings that obsverved property changed
|
||||||
ObserverNotifiesChangeHandler, // observer is a change handler, which runs on every change
|
ObserverNotifiesChangeHandler, // observer is a change handler, which runs on every change
|
||||||
ObserverNotifiesAlias, // used for QPropertyAlias
|
ObserverNotifiesAlias, // used for QPropertyAlias
|
||||||
ActivelyExecuting // the observer is currently evaluated in QPropertyObserver::notifyObservers or its
|
ObserverIsPlaceholder // the observer before this one is currently evaluated in QPropertyObserver::notifyObservers.
|
||||||
// placeholder. We only can store 4 different values, therefore those two conflate
|
|
||||||
};
|
};
|
||||||
|
protected:
|
||||||
|
using ChangeHandler = void (*)(QPropertyObserver*, QUntypedPropertyData *);
|
||||||
|
|
||||||
|
private:
|
||||||
|
friend struct QPropertyObserverNodeProtector;
|
||||||
|
friend class QPropertyObserver;
|
||||||
|
friend struct QPropertyObserverPointer;
|
||||||
|
friend struct QPropertyBindingDataPointer;
|
||||||
|
friend class QPropertyBindingPrivate;
|
||||||
|
|
||||||
|
QTaggedPointer<QPropertyObserver, ObserverTag> next;
|
||||||
|
// prev is a pointer to the "next" element within the previous node, or to the "firstObserverPtr" if it is the
|
||||||
|
// first node.
|
||||||
|
QtPrivate::QTagPreservingPointerToPointer<QPropertyObserver, ObserverTag> prev;
|
||||||
|
|
||||||
|
union {
|
||||||
|
QPropertyBindingPrivate *bindingToMarkDirty = nullptr;
|
||||||
|
ChangeHandler changeHandler;
|
||||||
|
QUntypedPropertyData *aliasedPropertyData;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
class Q_CORE_EXPORT QPropertyObserver : public QPropertyObserverBase
|
||||||
|
{
|
||||||
|
public:
|
||||||
constexpr QPropertyObserver() = default;
|
constexpr QPropertyObserver() = default;
|
||||||
QPropertyObserver(QPropertyObserver &&other) noexcept;
|
QPropertyObserver(QPropertyObserver &&other) noexcept;
|
||||||
QPropertyObserver &operator=(QPropertyObserver &&other) noexcept;
|
QPropertyObserver &operator=(QPropertyObserver &&other) noexcept;
|
||||||
@ -229,7 +253,6 @@ public:
|
|||||||
void setSource(const QtPrivate::QPropertyBindingData &property);
|
void setSource(const QtPrivate::QPropertyBindingData &property);
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
using ChangeHandler = void (*)(QPropertyObserver*, QUntypedPropertyData *);
|
|
||||||
QPropertyObserver(ChangeHandler changeHandler);
|
QPropertyObserver(ChangeHandler changeHandler);
|
||||||
QPropertyObserver(QUntypedPropertyData *aliasedPropertyPtr);
|
QPropertyObserver(QUntypedPropertyData *aliasedPropertyPtr);
|
||||||
|
|
||||||
@ -240,26 +263,9 @@ protected:
|
|||||||
|
|
||||||
private:
|
private:
|
||||||
|
|
||||||
QTaggedPointer<QPropertyObserver, ObserverTag> next;
|
|
||||||
// prev is a pointer to the "next" element within the previous node, or to the "firstObserverPtr" if it is the
|
|
||||||
// first node.
|
|
||||||
QtPrivate::QTagPreservingPointerToPointer<QPropertyObserver, ObserverTag> prev;
|
|
||||||
|
|
||||||
union {
|
|
||||||
QPropertyBindingPrivate *bindingToMarkDirty = nullptr;
|
|
||||||
ChangeHandler changeHandler;
|
|
||||||
QUntypedPropertyData *aliasedPropertyData;
|
|
||||||
QPropertyObserver **nodeState;
|
|
||||||
};
|
|
||||||
|
|
||||||
QPropertyObserver(const QPropertyObserver &) = delete;
|
QPropertyObserver(const QPropertyObserver &) = delete;
|
||||||
QPropertyObserver &operator=(const QPropertyObserver &) = delete;
|
QPropertyObserver &operator=(const QPropertyObserver &) = delete;
|
||||||
|
|
||||||
friend struct QPropertyObserverPointer;
|
|
||||||
friend struct QPropertyBindingDataPointer;
|
|
||||||
friend class QPropertyBindingPrivate;
|
|
||||||
template<ObserverTag>
|
|
||||||
friend struct QPropertyObserverNodeProtector;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
template <typename Functor>
|
template <typename Functor>
|
||||||
|
@ -245,13 +245,6 @@ public:
|
|||||||
void clearDependencyObservers() {
|
void clearDependencyObservers() {
|
||||||
for (size_t i = 0; i < qMin(dependencyObserverCount, inlineDependencyObservers.size()); ++i) {
|
for (size_t i = 0; i < qMin(dependencyObserverCount, inlineDependencyObservers.size()); ++i) {
|
||||||
QPropertyObserverPointer p{&inlineDependencyObservers[i]};
|
QPropertyObserverPointer p{&inlineDependencyObservers[i]};
|
||||||
if (p.ptr->next.tag() == QPropertyObserver::ActivelyExecuting) {
|
|
||||||
*(p.ptr->nodeState) = nullptr;
|
|
||||||
p.ptr->nodeState = nullptr;
|
|
||||||
|
|
||||||
// set tag to "safer" value, as we return the same observer pointer from allocateDependencyObserver
|
|
||||||
p.ptr->next.setTag(QPropertyObserver::ObserverNotifiesChangeHandler);
|
|
||||||
}
|
|
||||||
p.unlink();
|
p.unlink();
|
||||||
}
|
}
|
||||||
if (heapObservers)
|
if (heapObservers)
|
||||||
|
Loading…
x
Reference in New Issue
Block a user