summaryrefslogtreecommitdiffstats
path: root/tests
diff options
context:
space:
mode:
authorGiuseppe D'Angelo <giuseppe.dangelo@kdab.com>2020-05-05 22:45:01 +0200
committerGiuseppe D'Angelo <giuseppe.dangelo@kdab.com>2020-09-02 22:51:42 +0200
commit39e07ebf64e115460e2a7573c13dde014c8e33ca (patch)
tree3d35b2b4ce3db83eba1f46b5bcb49c1f96f9faa5 /tests
parent19874d6a6386b55fe502e3a36a9adb6813b315dc (diff)
Long live QAIM::multiData!
Views / delegates absolutely *adore* hammering data(). A simple QListView showing a couple of dozens entries can call data() a hundred of times on the first show. Back of the hand calculation, * 2 times per visible item (sizeHint() + paint()), * times 9 roles used by the default delegate, * times 20 visible items = 360 as a bare minimum, assuming the view doesn't redraw twice accidentally. Move the mouse over the view, and that'll cause a full update with certain styles: 360 calls to data() per update. This has an overhead visible in profilers. The model's data() has to re-fetch the index from its data structure and extract the requested field every time. Also, QVariant is used for the data interexchange, meaning anything that won't fit in one is also a memory allocation. This problem will likely be gone in Qt6Variant as that will store sizeof(void*) * 3, meaning QImage/QPixmap and similar polymorphic classes will fit in a QVariant now... So I'm trying to to remove part of that overhead by allowing views to request all the data they need in one go. For now, one index a a time. A view might also store the data returned. The idea is that the same role on different indexes will _very likely_ return variants of the same type. So a model could move-assign the data into the variant, avoiding the memory allocation /deallocation for the variant's private. This patch: 1) Introduces QModelRoleData as a holder for role+data. 2) Introduces QModelRoleDataSpan as a span over QModelRoleData. The idea of a span type is twofold. First and foremost, we are in no position to choose which kind of container a view should use to store the QModelRoleData objects for a multiData() call; a span abstracts any contiguous sequence, leaving the view free to do whatever it wants (statically allocate, use a vector, etc.). It also solves the problem of efficient passing the roles and gathering the returned variants from multiData(). 3) Add multiData(), which populates a span of roles for a given model index. The main advantage here is that a model can fetch all the needed information for a given index just once, then iterate on the span and provide data for each requested role. Cf. this with data(), where every call has to re-fetch the information for the index. A couple of models have been ported to multiData(), as well as QStyledItemDelegate. [ChangeLog][QtCore][QModelRoleData] New class. [ChangeLog][QtCore][QModelRoleDataSpan] New class. [ChangeLog][QtCore][QAbstractItemModel] Added the multiData() function. Change-Id: Icce0d108ad4e156c9fb05c83ce6df5f58f99f118 Reviewed-by: Fabian Kosmale <fabian.kosmale@qt.io>
Diffstat (limited to 'tests')
-rw-r--r--tests/auto/corelib/itemmodels/qabstractitemmodel/tst_qabstractitemmodel.cpp141
1 files changed, 141 insertions, 0 deletions
diff --git a/tests/auto/corelib/itemmodels/qabstractitemmodel/tst_qabstractitemmodel.cpp b/tests/auto/corelib/itemmodels/qabstractitemmodel/tst_qabstractitemmodel.cpp
index bf4f33fbd1..df3104eddf 100644
--- a/tests/auto/corelib/itemmodels/qabstractitemmodel/tst_qabstractitemmodel.cpp
+++ b/tests/auto/corelib/itemmodels/qabstractitemmodel/tst_qabstractitemmodel.cpp
@@ -35,6 +35,13 @@
#include "dynamictreemodel.h"
+// for testing QModelRoleDataSpan construction
+#include <QVarLengthArray>
+#include <array>
+#include <vector>
+#include <deque>
+#include <list>
+
/*!
Note that this doesn't test models, but any functionality that QAbstractItemModel should provide
*/
@@ -108,6 +115,10 @@ private slots:
void checkIndex();
+ void modelRoleDataSpanConstruction();
+ void modelRoleDataSpan();
+
+ void multiData();
private:
DynamicTreeModel *m_model;
};
@@ -2384,5 +2395,135 @@ void tst_QAbstractItemModel::checkIndex()
QVERIFY(!model.checkIndex(topLevelIndex, QAbstractItemModel::CheckIndexOption::IndexIsValid));
}
+template <typename T>
+inline constexpr bool CanConvertToSpan = std::is_convertible_v<T, QModelRoleDataSpan>;
+
+void tst_QAbstractItemModel::modelRoleDataSpanConstruction()
+{
+ // Compile time test
+ static_assert(CanConvertToSpan<QModelRoleData &>);
+ static_assert(CanConvertToSpan<QModelRoleData (&)[123]>);
+ static_assert(CanConvertToSpan<QVector<QModelRoleData> &>);
+ static_assert(CanConvertToSpan<QVarLengthArray<QModelRoleData> &>);
+ static_assert(CanConvertToSpan<std::vector<QModelRoleData> &>);
+ static_assert(CanConvertToSpan<std::array<QModelRoleData, 123> &>);
+
+ static_assert(!CanConvertToSpan<QModelRoleData>);
+ static_assert(!CanConvertToSpan<QVector<QModelRoleData>>);
+ static_assert(!CanConvertToSpan<const QVector<QModelRoleData> &>);
+ static_assert(!CanConvertToSpan<std::vector<QModelRoleData>>);
+ static_assert(!CanConvertToSpan<std::deque<QModelRoleData>>);
+ static_assert(!CanConvertToSpan<std::deque<QModelRoleData> &>);
+ static_assert(!CanConvertToSpan<std::list<QModelRoleData> &>);
+ static_assert(!CanConvertToSpan<std::list<QModelRoleData>>);
+}
+
+void tst_QAbstractItemModel::modelRoleDataSpan()
+{
+ QModelRoleData data[3] = {
+ QModelRoleData(Qt::DisplayRole),
+ QModelRoleData(Qt::DecorationRole),
+ QModelRoleData(Qt::EditRole)
+ };
+ QModelRoleData *dataPtr = data;
+
+ QModelRoleDataSpan span(data);
+
+ QCOMPARE(span.size(), 3);
+ QCOMPARE(span.length(), 3);
+ QCOMPARE(span.data(), dataPtr);
+ QCOMPARE(span.begin(), dataPtr);
+ QCOMPARE(span.end(), dataPtr + 3);
+ for (int i = 0; i < 3; ++i)
+ QCOMPARE(span[i].role(), data[i].role());
+
+ data[0].setData(42);
+ data[1].setData(QStringLiteral("a string"));
+ data[2].setData(123.5);
+
+ QCOMPARE(span.dataForRole(Qt::DisplayRole)->toInt(), 42);
+ QCOMPARE(span.dataForRole(Qt::DecorationRole)->toString(), "a string");
+ QCOMPARE(span.dataForRole(Qt::EditRole)->toDouble(), 123.5);
+}
+
+// model implementing data(), but not multiData(); check that the
+// default implementation of multiData() does the right thing
+class NonMultiDataRoleModel : public QAbstractListModel
+{
+ Q_OBJECT
+
+public:
+ int rowCount(const QModelIndex &) const override
+ {
+ return 1000;
+ }
+
+ // We handle roles <= 10. All such roles return a QVariant(int) containing
+ // the same value as the role, except for 10 which returns a string.
+ QVariant data(const QModelIndex &index, int role) const override
+ {
+ Q_ASSERT(checkIndex(index, CheckIndexOption::IndexIsValid));
+
+ if (role < 10)
+ return QVariant::fromValue(role);
+ else if (role == 10)
+ return QVariant::fromValue(QStringLiteral("Hello!"));
+
+ return QVariant();
+ }
+};
+
+void tst_QAbstractItemModel::multiData()
+{
+ QModelRoleData data[] = {
+ QModelRoleData(1),
+ QModelRoleData(42),
+ QModelRoleData(5),
+ QModelRoleData(2),
+ QModelRoleData(12),
+ QModelRoleData(2),
+ QModelRoleData(10),
+ QModelRoleData(-123)
+ };
+
+ QModelRoleDataSpan span(data);
+
+ for (const auto &roledata : span)
+ QVERIFY(roledata.data().isNull());
+
+ NonMultiDataRoleModel model;
+ const QModelIndex index = model.index(0, 0);
+ QVERIFY(index.isValid());
+
+ const auto check = [&]() {
+ for (auto &roledata : span) {
+ const auto role = roledata.role();
+ if (role < 10) {
+ QVERIFY(!roledata.data().isNull());
+ QVERIFY(roledata.data().userType() == qMetaTypeId<int>());
+ QCOMPARE(roledata.data().toInt(), role);
+ } else if (role == 10) {
+ QVERIFY(!roledata.data().isNull());
+ QVERIFY(roledata.data().userType() == qMetaTypeId<QString>());
+ QCOMPARE(roledata.data().toString(), QStringLiteral("Hello!"));
+ } else {
+ QVERIFY(roledata.data().isNull());
+ }
+ }
+ };
+
+ model.multiData(index, span);
+ check();
+
+ model.multiData(index, span);
+ check();
+
+ for (auto &roledata : span)
+ roledata.clearData();
+
+ model.multiData(index, span);
+ check();
+}
+
QTEST_MAIN(tst_QAbstractItemModel)
#include "tst_qabstractitemmodel.moc"