diff options
author | Giuseppe D'Angelo <giuseppe.dangelo@kdab.com> | 2020-05-05 22:45:01 +0200 |
---|---|---|
committer | Giuseppe D'Angelo <giuseppe.dangelo@kdab.com> | 2020-09-02 22:51:42 +0200 |
commit | 39e07ebf64e115460e2a7573c13dde014c8e33ca (patch) | |
tree | 3d35b2b4ce3db83eba1f46b5bcb49c1f96f9faa5 /src/corelib/itemmodels/qabstractitemmodel.h | |
parent | 19874d6a6386b55fe502e3a36a9adb6813b315dc (diff) | |
download | qtbase-39e07ebf64e115460e2a7573c13dde014c8e33ca.tar.gz |
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 'src/corelib/itemmodels/qabstractitemmodel.h')
-rw-r--r-- | src/corelib/itemmodels/qabstractitemmodel.h | 110 |
1 files changed, 110 insertions, 0 deletions
diff --git a/src/corelib/itemmodels/qabstractitemmodel.h b/src/corelib/itemmodels/qabstractitemmodel.h index 325ff1df4a..9cda91e66e 100644 --- a/src/corelib/itemmodels/qabstractitemmodel.h +++ b/src/corelib/itemmodels/qabstractitemmodel.h @@ -1,6 +1,7 @@ /**************************************************************************** ** ** Copyright (C) 2016 The Qt Company Ltd. +** Copyright (C) 2020 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com, author Giuseppe D'Angelo <giuseppe.dangelo@kdab.com> ** Contact: https://www.qt.io/licensing/ ** ** This file is part of the QtCore module of the Qt Toolkit. @@ -49,6 +50,108 @@ QT_REQUIRE_CONFIG(itemmodel); QT_BEGIN_NAMESPACE +class QModelRoleData +{ + int m_role; + QVariant m_data; + +public: + explicit QModelRoleData(int role) noexcept + : m_role(role) + {} + + constexpr int role() const noexcept { return m_role; } + constexpr QVariant &data() noexcept { return m_data; } + constexpr const QVariant &data() const noexcept { return m_data; } + + template <typename T> + constexpr void setData(T &&value) noexcept(noexcept(m_data.setValue(std::forward<T>(value)))) + { m_data.setValue(std::forward<T>(value)); } + + void clearData() noexcept { m_data.clear(); } +}; + +Q_DECLARE_TYPEINFO(QModelRoleData, Q_MOVABLE_TYPE); + +class QModelRoleDataSpan; + +namespace QtPrivate { +template <typename T, typename Enable = void> +struct IsContainerCompatibleWithModelRoleDataSpan : std::false_type {}; + +template <typename T> +struct IsContainerCompatibleWithModelRoleDataSpan<T, std::enable_if_t<std::conjunction_v< + // lacking concepts and ranges, we accept any T whose std::data yields a suitable pointer ... + std::is_convertible<decltype( std::data(std::declval<T &>()) ), QModelRoleData *>, + // ... and that has a suitable size ... + std::is_convertible<decltype( std::size(std::declval<T &>()) ), qsizetype>, + // ... and it's a range as it defines an iterator-like API + std::is_convertible< + typename std::iterator_traits<decltype( std::begin(std::declval<T &>()) )>::value_type, + QModelRoleData + >, + std::is_convertible< + decltype( std::begin(std::declval<T &>()) != std::end(std::declval<T &>()) ), + bool>, + // Don't make an accidental copy constructor + std::negation<std::is_same<std::decay_t<T>, QModelRoleDataSpan>> + >>> : std::true_type {}; +} // namespace QtPrivate + +class QModelRoleDataSpan +{ + QModelRoleData *m_modelRoleData = nullptr; + qsizetype m_len = 0; + + template <typename T> + using if_compatible_container = std::enable_if_t<QtPrivate::IsContainerCompatibleWithModelRoleDataSpan<T>::value, bool>; + +public: + constexpr QModelRoleDataSpan() noexcept {} + + constexpr QModelRoleDataSpan(QModelRoleData &modelRoleData) noexcept + : m_modelRoleData(&modelRoleData), + m_len(1) + {} + + constexpr QModelRoleDataSpan(QModelRoleData *modelRoleData, qsizetype len) + : m_modelRoleData(modelRoleData), + m_len(len) + {} + + template <typename Container, if_compatible_container<Container> = true> + constexpr QModelRoleDataSpan(Container &c) noexcept(noexcept(std::data(c)) && noexcept(std::size(c))) + : m_modelRoleData(std::data(c)), + m_len(qsizetype(std::size(c))) + {} + + constexpr qsizetype size() const noexcept { return m_len; } + constexpr qsizetype length() const noexcept { return m_len; } + constexpr QModelRoleData *data() const noexcept { return m_modelRoleData; } + constexpr QModelRoleData *begin() const noexcept { return m_modelRoleData; } + constexpr QModelRoleData *end() const noexcept { return m_modelRoleData + m_len; } + constexpr QModelRoleData &operator[](qsizetype index) const { return m_modelRoleData[index]; } + + constexpr QVariant *dataForRole(int role) const + { +#ifdef __cpp_lib_constexpr_algorithms + auto result = std::find_if(begin(), end(), [role](const QModelRoleData &roleData) { + return roleData.role() == role; + }); +#else + auto result = begin(); + const auto e = end(); + for (; result != e; ++result) { + if (result->role() == role) + break; + } +#endif + + return Q_ASSERT(result != end()), &result->data(); + } +}; + +Q_DECLARE_TYPEINFO(QModelRoleDataSpan, Q_MOVABLE_TYPE); class QAbstractItemModel; class QPersistentModelIndex; @@ -69,6 +172,7 @@ public: inline QModelIndex siblingAtColumn(int column) const; inline QModelIndex siblingAtRow(int row) const; inline QVariant data(int role = Qt::DisplayRole) const; + inline void multiData(QModelRoleDataSpan roleDataSpan) const; inline Qt::ItemFlags flags() const; constexpr inline const QAbstractItemModel *model() const noexcept { return m; } constexpr inline bool isValid() const noexcept { return (r >= 0) && (c >= 0) && (m != nullptr); } @@ -132,6 +236,7 @@ public: QModelIndex parent() const; QModelIndex sibling(int row, int column) const; QVariant data(int role = Qt::DisplayRole) const; + void multiData(QModelRoleDataSpan roleDataSpan) const; Qt::ItemFlags flags() const; const QAbstractItemModel *model() const; bool isValid() const; @@ -256,6 +361,8 @@ public: Q_REQUIRED_RESULT bool checkIndex(const QModelIndex &index, CheckIndexOptions options = CheckIndexOption::NoOption) const; + virtual void multiData(const QModelIndex &index, QModelRoleDataSpan roleDataSpan) const; + Q_SIGNALS: void dataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight, const QList<int> &roles = QList<int>()); @@ -421,6 +528,9 @@ inline QModelIndex QModelIndex::siblingAtRow(int arow) const inline QVariant QModelIndex::data(int arole) const { return m ? m->data(*this, arole) : QVariant(); } +inline void QModelIndex::multiData(QModelRoleDataSpan roleDataSpan) const +{ if (m) m->multiData(*this, roleDataSpan); } + inline Qt::ItemFlags QModelIndex::flags() const { return m ? m->flags(*this) : Qt::ItemFlags(); } |