summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorLars Knoll <lars.knoll@qt.io>2020-08-25 12:27:09 +0200
committerLars Knoll <lars.knoll@qt.io>2020-09-02 22:44:28 +0200
commit6778b247a8c20adfb3f4e3094077baae43f3e65c (patch)
tree61da119dc749554c6b09b79f0b39ac6c8d4e8306
parent9b6df7deb3fff6179071ee59603250cb59c03222 (diff)
Add a QBindingStorage class
QBindingStorage is a class that can store a set of binding objects for the properties of a QObject. This will get used to reduce the memory overhead of the property system when adding bindable properties to QObject based classes. The binding storage has a pointer to the TLS entry containing the currently evaluating binding. Like that we avoid repeated TLS lookups and reduce the overhead of the property system to one pointer lookup and one compare for the case that properties aren't being used. Each QObject now owns one binding storage object, that can be used to store binding data for properties that members of the QObject. Change-Id: I27427c03c2ba281f072e074be96147bdbcaac246 Reviewed-by: Fabian Kosmale <fabian.kosmale@qt.io>
-rw-r--r--src/corelib/kernel/qobject.cpp22
-rw-r--r--src/corelib/kernel/qobject.h11
-rw-r--r--src/corelib/kernel/qobject_p.h12
-rw-r--r--src/corelib/kernel/qproperty.cpp167
-rw-r--r--src/corelib/kernel/qproperty.h21
-rw-r--r--src/corelib/kernel/qproperty_p.h10
-rw-r--r--tests/auto/other/toolsupport/tst_toolsupport.cpp4
7 files changed, 236 insertions, 11 deletions
diff --git a/src/corelib/kernel/qobject.cpp b/src/corelib/kernel/qobject.cpp
index 4d0523c898..5bb225d396 100644
--- a/src/corelib/kernel/qobject.cpp
+++ b/src/corelib/kernel/qobject.cpp
@@ -1532,6 +1532,10 @@ void QObject::moveToThread(QThread *targetThread)
qWarning("QObject::moveToThread: Widgets cannot be moved to a new thread");
return;
}
+ if (!d->bindingStorage.isEmpty()) {
+ qWarning("QObject::moveToThread: Can not move objects that contain bindings or are used in bindings to a new thread.");
+ return;
+ }
QThreadData *currentData = QThreadData::current();
QThreadData *targetData = targetThread ? QThreadData::get2(targetThread) : nullptr;
@@ -4046,6 +4050,24 @@ QList<QByteArray> QObject::dynamicPropertyNames() const
return QList<QByteArray>();
}
+/*!
+ \internal
+*/
+QBindingStorage *QObject::bindingStorage()
+{
+ Q_D(QObject);
+ return &d->bindingStorage;
+}
+
+/*!
+ \internal
+*/
+const QBindingStorage *QObject::bindingStorage() const
+{
+ Q_D(const QObject);
+ return &d->bindingStorage;
+}
+
#endif // QT_NO_PROPERTIES
diff --git a/src/corelib/kernel/qobject.h b/src/corelib/kernel/qobject.h
index d1a0c3562c..21081fa569 100644
--- a/src/corelib/kernel/qobject.h
+++ b/src/corelib/kernel/qobject.h
@@ -62,6 +62,7 @@
QT_BEGIN_NAMESPACE
+class QBindingStorage;
class QEvent;
class QTimerEvent;
class QChildEvent;
@@ -371,6 +372,8 @@ public:
bool setProperty(const char *name, const QVariant &value);
QVariant property(const char *name) const;
QList<QByteArray> dynamicPropertyNames() const;
+ QBindingStorage *bindingStorage();
+ const QBindingStorage *bindingStorage() const;
#endif // QT_NO_PROPERTIES
Q_SIGNALS:
@@ -457,6 +460,14 @@ inline T qobject_cast(const QObject *object)
template <class T> inline const char * qobject_interface_iid()
{ return nullptr; }
+inline const QBindingStorage *qGetBindingStorage(const QObject *o)
+{
+ return o->bindingStorage();
+}
+inline QBindingStorage *qGetBindingStorage(QObject *o)
+{
+ return o->bindingStorage();
+}
#if defined(Q_CLANG_QDOC)
# define Q_DECLARE_INTERFACE(IFace, IId)
diff --git a/src/corelib/kernel/qobject_p.h b/src/corelib/kernel/qobject_p.h
index 1f13274945..5399014cf6 100644
--- a/src/corelib/kernel/qobject_p.h
+++ b/src/corelib/kernel/qobject_p.h
@@ -60,6 +60,7 @@
#include "QtCore/qreadwritelock.h"
#include "QtCore/qsharedpointer.h"
#include "QtCore/qvariant.h"
+#include "QtCore/qproperty.h"
QT_BEGIN_NAMESPACE
@@ -383,6 +384,7 @@ public:
// these objects are all used to indicate that a QObject was deleted
// plus QPointer, which keeps a separate list
QAtomicPointer<QtSharedPointer::ExternalRefCountData> sharedRefcount;
+ QBindingStorage bindingStorage;
};
Q_DECLARE_TYPEINFO(QObjectPrivate::ConnectionList, Q_MOVABLE_TYPE);
@@ -604,6 +606,16 @@ struct Q_CORE_EXPORT QAbstractDynamicMetaObject : public QDynamicMetaObjectData,
virtual int metaCall(QMetaObject::Call, int _id, void **) { return _id; } // Compat overload
};
+inline const QBindingStorage *qGetBindingStorage(const QObjectPrivate *o)
+{
+ return &o->bindingStorage;
+}
+inline QBindingStorage *qGetBindingStorage(QObjectPrivate *o)
+{
+ return &o->bindingStorage;
+}
+
+
QT_END_NAMESPACE
#endif // QOBJECT_P_H
diff --git a/src/corelib/kernel/qproperty.cpp b/src/corelib/kernel/qproperty.cpp
index e0a1341e20..7cacd3f873 100644
--- a/src/corelib/kernel/qproperty.cpp
+++ b/src/corelib/kernel/qproperty.cpp
@@ -111,7 +111,7 @@ bool QPropertyBindingPrivate::evaluateIfDirtyAndReturnTrueIfValueChanged()
QPropertyBindingPrivatePtr keepAlive {this};
QScopedValueRollback<bool> updateGuard(updating, true);
- BindingEvaluationState evaluationFrame(this);
+ QBindingEvaluationState evaluationFrame(this);
bool changed = false;
@@ -276,9 +276,9 @@ QPropertyBindingPrivate *QPropertyBindingData::binding() const
return nullptr;
}
-static thread_local BindingEvaluationState *currentBindingEvaluationState = nullptr;
+static thread_local QBindingEvaluationState *currentBindingEvaluationState = nullptr;
-BindingEvaluationState::BindingEvaluationState(QPropertyBindingPrivate *binding)
+QBindingEvaluationState::QBindingEvaluationState(QPropertyBindingPrivate *binding)
: binding(binding)
{
// store a pointer to the currentBindingEvaluationState to avoid a TLS lookup in
@@ -289,7 +289,7 @@ BindingEvaluationState::BindingEvaluationState(QPropertyBindingPrivate *binding)
binding->clearDependencyObservers();
}
-BindingEvaluationState::~BindingEvaluationState()
+QBindingEvaluationState::~QBindingEvaluationState()
{
*currentState = previousState;
}
@@ -1083,4 +1083,163 @@ QString QPropertyBindingError::description() const
If the aliased property doesn't exist, all other method calls are ignored.
*/
+struct QBindingStorageData
+{
+ size_t size = 0;
+ size_t used = 0;
+ // Pair[] pairs;
+};
+
+struct QBindingStoragePrivate
+{
+ // This class basically implements a simple and fast hash map to store bindings for a QObject
+ // The reason that we're not using QHash is that QPropertyBindingData can not be copied, only
+ // moved. That doesn't work well together with an implicitly shared class.
+ struct Pair
+ {
+ QUntypedPropertyData *data;
+ QPropertyBindingData bindingData;
+ };
+ static_assert(alignof(Pair) == alignof(void *));
+ static_assert(alignof(size_t) == alignof(void *));
+
+ QBindingStorageData *&d;
+
+ static inline Pair *pairs(QBindingStorageData *dd)
+ {
+ Q_ASSERT(dd);
+ return reinterpret_cast<Pair *>(dd + 1);
+ }
+ void reallocate(size_t newSize)
+ {
+ Q_ASSERT(!d || newSize > d->size);
+ size_t allocSize = sizeof(QBindingStorageData) + newSize*sizeof(Pair);
+ void *nd = malloc(allocSize);
+ memset(nd, 0, allocSize);
+ QBindingStorageData *newData = new (nd) QBindingStorageData;
+ newData->size = newSize;
+ if (!d) {
+ d = newData;
+ return;
+ }
+ newData->used = d->used;
+ Pair *p = pairs(d);
+ for (size_t i = 0; i < d->size; ++i, ++p) {
+ if (p->data) {
+ Pair *pp = pairs(newData);
+ size_t index = qHash(p->data);
+ while (pp[index].data) {
+ ++index;
+ if (index == newData->size)
+ index = 0;
+ }
+ new (pp + index) Pair{p->data, QPropertyBindingData(std::move(p->bindingData), p->data)};
+ }
+ }
+ // data has been moved, no need to call destructors on old Pairs
+ free(d);
+ d = newData;
+ }
+
+ QBindingStoragePrivate(QBindingStorageData *&_d) : d(_d) {}
+
+ QPropertyBindingData *get(const QUntypedPropertyData *data)
+ {
+ if (!d)
+ return nullptr;
+ Q_ASSERT(d->size && (d->size & (d->size - 1)) == 0); // size is a power of two
+ size_t index = qHash(data) & (d->size - 1);
+ Pair *p = pairs(d);
+ while (p[index].data) {
+ if (p[index].data == data)
+ return &p[index].bindingData;
+ ++index;
+ if (index == d->size)
+ index = 0;
+ }
+ return nullptr;
+ }
+ QPropertyBindingData *getAndCreate(QUntypedPropertyData *data)
+ {
+ if (!d)
+ reallocate(8);
+ else if (d->used*2 >= d->size)
+ reallocate(d->size*2);
+ Q_ASSERT(d->size && (d->size & (d->size - 1)) == 0); // size is a power of two
+ size_t index = qHash(data) & (d->size - 1);
+ Pair *p = pairs(d);
+ while (p[index].data) {
+ if (p[index].data == data)
+ return &p[index].bindingData;
+ ++index;
+ if (index == d->size)
+ index = 0;
+ }
+ ++d->used;
+ new (p + index) Pair{data, QPropertyBindingData()};
+ return &p[index].bindingData;
+ }
+
+ void destroy()
+ {
+ if (!d)
+ return;
+ Pair *p = pairs(d);
+ for (size_t i = 0; i < d->size; ++i) {
+ if (p->data)
+ p->~Pair();
+ ++p;
+ }
+ free(d);
+ }
+};
+
+/*!
+ \class QBindingStorage
+ \internal
+
+ QBindingStorage acts as a storage for property binding related data in QObject.
+ Any property in a QObject can be made bindable, by using the Q_BINDABLE_PROPERTY_DATA
+ macro to declare the data storage. Then implement a setter and getter for the property
+ and declare it as a Q_PROPERTY as usual. Finally make it bindable, but using
+ the Q_BINDABLE_PROPERTY macro after the declaration of the setter and getter.
+*/
+
+QBindingStorage::QBindingStorage()
+{
+ currentlyEvaluatingBinding = &currentBindingEvaluationState;
+}
+
+QBindingStorage::~QBindingStorage()
+{
+ QBindingStoragePrivate(d).destroy();
+}
+
+void QBindingStorage::maybeUpdateBindingAndRegister(const QUntypedPropertyData *data) const
+{
+ QUntypedPropertyData *dd = const_cast<QUntypedPropertyData *>(data);
+ auto storage = *currentlyEvaluatingBinding ?
+ QBindingStoragePrivate(d).getAndCreate(dd) :
+ QBindingStoragePrivate(d).get(dd);
+ if (!storage)
+ return;
+ if (auto *binding = storage->binding())
+ binding->evaluateIfDirtyAndReturnTrueIfValueChanged();
+ storage->registerWithCurrentlyEvaluatingBinding();
+}
+
+QPropertyBindingData *QBindingStorage::bindingData(const QUntypedPropertyData *data) const
+{
+ return QBindingStoragePrivate(d).get(data);
+}
+
+QPropertyBindingData *QBindingStorage::bindingData(QUntypedPropertyData *data, bool create)
+{
+ auto storage = create ?
+ QBindingStoragePrivate(d).getAndCreate(data) :
+ QBindingStoragePrivate(d).get(data);
+ return storage;
+}
+
+
QT_END_NAMESPACE
diff --git a/src/corelib/kernel/qproperty.h b/src/corelib/kernel/qproperty.h
index 85bdfdcd56..bb89dbf94f 100644
--- a/src/corelib/kernel/qproperty.h
+++ b/src/corelib/kernel/qproperty.h
@@ -721,6 +721,27 @@ public:
}
};
+struct QBindingStatus;
+
+struct QBindingStorageData;
+class Q_CORE_EXPORT QBindingStorage
+{
+ mutable QBindingStorageData *d = nullptr;
+ QBindingStatus *bindingStatus = nullptr;
+
+ template<typename Class, typename T, auto Offset, auto Setter>
+ friend class QObjectCompatProperty;
+public:
+ QBindingStorage();
+ ~QBindingStorage();
+
+ bool isEmpty() { return !d; }
+
+ void maybeUpdateBindingAndRegister(const QUntypedPropertyData *data) const;
+ QtPrivate::QPropertyBindingData *bindingData(const QUntypedPropertyData *data) const;
+ QtPrivate::QPropertyBindingData *bindingData(QUntypedPropertyData *data, bool create);
+};
+
QT_END_NAMESPACE
#endif // QPROPERTY_H
diff --git a/src/corelib/kernel/qproperty_p.h b/src/corelib/kernel/qproperty_p.h
index d93db988f9..1085c037e6 100644
--- a/src/corelib/kernel/qproperty_p.h
+++ b/src/corelib/kernel/qproperty_p.h
@@ -120,13 +120,13 @@ public:
QString description;
};
-struct BindingEvaluationState
+struct QBindingEvaluationState
{
- BindingEvaluationState(QPropertyBindingPrivate *binding);
- ~BindingEvaluationState();
+ QBindingEvaluationState(QPropertyBindingPrivate *binding);
+ ~QBindingEvaluationState();
QPropertyBindingPrivate *binding;
- BindingEvaluationState *previousState = nullptr;
- BindingEvaluationState **currentState = nullptr;
+ QBindingEvaluationState *previousState = nullptr;
+ QBindingEvaluationState **currentState = nullptr;
};
class Q_CORE_EXPORT QPropertyBindingPrivate : public QSharedData
diff --git a/tests/auto/other/toolsupport/tst_toolsupport.cpp b/tests/auto/other/toolsupport/tst_toolsupport.cpp
index 52fa821be7..7acf1ad08d 100644
--- a/tests/auto/other/toolsupport/tst_toolsupport.cpp
+++ b/tests/auto/other/toolsupport/tst_toolsupport.cpp
@@ -126,9 +126,9 @@ void tst_toolsupport::offsets_data()
#ifdef Q_PROCESSOR_X86
// x86 32-bit has weird alignment rules. Refer to QtPrivate::AlignOf in
// qglobal.h for more details.
- data << 180 << 288;
+ data << 188 << 304;
#else
- data << 184 << 288;
+ data << 192 << 304;
#endif
}
#endif