diff options
author | Richard Moe Gustavsen <richard.gustavsen@qt.io> | 2019-09-12 14:14:05 +0200 |
---|---|---|
committer | Richard Moe Gustavsen <richard.gustavsen@qt.io> | 2019-12-04 01:34:07 +0100 |
commit | 4b58d69d56c5876a1b1d71ce6a96b4c6c81a833f (patch) | |
tree | 094a0240be7462a2768084985d49f2f58a294ab1 /src/qmlmodels | |
parent | af9a96ca10e72517a7e8aa1ada7ec2d635e2a9ff (diff) |
QQmlTableInstanceModel: factor out reuse pool
Factor out the reuse pool logic in TableInstanceModel into a
separate class, so that we can share it with the upcoming implementation
for recycling items in QQmlDelegateModel.
Change-Id: If8f700b7a0208bac7d1cb1de087792e2c3a9b512
Reviewed-by: Shawn Rutledge <shawn.rutledge@qt.io>
Diffstat (limited to 'src/qmlmodels')
-rw-r--r-- | src/qmlmodels/qqmldelegatemodel.cpp | 121 | ||||
-rw-r--r-- | src/qmlmodels/qqmldelegatemodel_p.h | 2 | ||||
-rw-r--r-- | src/qmlmodels/qqmldelegatemodel_p_p.h | 17 | ||||
-rw-r--r-- | src/qmlmodels/qqmlobjectmodel.cpp | 2 | ||||
-rw-r--r-- | src/qmlmodels/qqmlobjectmodel_p.h | 12 | ||||
-rw-r--r-- | src/qmlmodels/qqmltableinstancemodel.cpp | 93 | ||||
-rw-r--r-- | src/qmlmodels/qqmltableinstancemodel_p.h | 18 |
7 files changed, 163 insertions, 102 deletions
diff --git a/src/qmlmodels/qqmldelegatemodel.cpp b/src/qmlmodels/qqmldelegatemodel.cpp index 40de1410fa..7abf59f8a0 100644 --- a/src/qmlmodels/qqmldelegatemodel.cpp +++ b/src/qmlmodels/qqmldelegatemodel.cpp @@ -55,6 +55,8 @@ QT_BEGIN_NAMESPACE +Q_LOGGING_CATEGORY(lcItemViewDelegateRecycling, "qt.quick.itemview.delegaterecycling") + class QQmlDelegateModelItem; namespace QV4 { @@ -614,7 +616,7 @@ QQmlDelegateModel::ReleaseFlags QQmlDelegateModelPrivate::release(QObject *objec Returns ReleaseStatus flags. */ -QQmlDelegateModel::ReleaseFlags QQmlDelegateModel::release(QObject *item) +QQmlDelegateModel::ReleaseFlags QQmlDelegateModel::release(QObject *item, QQmlInstanceModel::ReusableFlag) { Q_D(QQmlDelegateModel); QQmlInstanceModel::ReleaseFlags stat = d->release(item); @@ -3460,7 +3462,7 @@ QObject *QQmlPartsModel::object(int index, QQmlIncubator::IncubationMode incubat return nullptr; } -QQmlInstanceModel::ReleaseFlags QQmlPartsModel::release(QObject *item) +QQmlInstanceModel::ReleaseFlags QQmlPartsModel::release(QObject *item, ReusableFlag) { QQmlInstanceModel::ReleaseFlags flags; @@ -3553,6 +3555,121 @@ void QQmlPartsModel::emitModelUpdated(const QQmlChangeSet &changeSet, bool reset } } +void QQmlReusableDelegateModelItemsPool::insertItem(QQmlDelegateModelItem *modelItem) +{ + // Currently, the only way for a view to reuse items is to call release() + // in the model class with the second argument explicitly set to + // QQmlReuseableDelegateModelItemsPool::Reusable. If the released item is + // no longer referenced, it will be added to the pool. Reusing of items can + // be specified per item, in case certain items cannot be recycled. A + // QQmlDelegateModelItem knows which delegate its object was created from. + // So when we are about to create a new item, we first check if the pool + // contains an item based on the same delegate from before. If so, we take + // it out of the pool (instead of creating a new item), and update all its + // context properties and attached properties. + + // When a view is recycling items, it should call drain() regularly. As + // there is currently no logic to 'hibernate' items in the pool, they are + // only meant to rest there for a short while, ideally only from the time + // e.g a row is unloaded on one side of the view, and until a new row is + // loaded on the opposite side. Between these times, the application will + // see the item as fully functional and 'alive' (just not visible on + // screen). Since this time is supposed to be short, we don't take any + // action to notify the application about it, since we don't want to + // trigger any bindings that can disturb performance. + + // A recommended time for calling drain() is each time a view has finished + // loading e.g a new row or column. If there are more items in the pool + // after that, it means that the view most likely doesn't need them anytime + // soon. Those items should be destroyed to reduce resource consumption. + + // Depending on if a view is a list or a table, it can sometimes be + // performant to keep items in the pool for a bit longer than one "row + // out/row in" cycle. E.g for a table, if the number of visible rows in a + // view is much larger than the number of visible columns. In that case, if + // you flick out a row, and then flick in a column, you would throw away a + // lot of items in the pool if completely draining it. The reason is that + // unloading a row places more items in the pool than what ends up being + // recycled when loading a new column. And then, when you next flick in a + // new row, you would need to load all those drained items again from + // scratch. For that reason, you can specify a maxPoolTime to the + // drainReusableItemsPool() that allows you to keep items in the pool for a + // bit longer, effectively keeping more items in circulation. A recommended + // maxPoolTime would be equal to the number of dimensions in the view, + // which means 1 for a list view and 2 for a table view. If you specify 0, + // all items will be drained. + + Q_ASSERT(!modelItem->incubationTask); + Q_ASSERT(!modelItem->isObjectReferenced()); + Q_ASSERT(modelItem->object); + Q_ASSERT(modelItem->delegate); + + modelItem->poolTime = 0; + m_reusableItemsPool.append(modelItem); + + qCDebug(lcItemViewDelegateRecycling) + << "item:" << modelItem + << "delegate:" << modelItem->delegate + << "index:" << modelItem->modelIndex() + << "row:" << modelItem->modelRow() + << "column:" << modelItem->modelColumn() + << "pool size:" << m_reusableItemsPool.size(); +} + +QQmlDelegateModelItem *QQmlReusableDelegateModelItemsPool::takeItem(const QQmlComponent *delegate, int newIndexHint) +{ + // Find the oldest item in the pool that was made from the same delegate as + // the given argument, remove it from the pool, and return it. + for (auto it = m_reusableItemsPool.begin(); it != m_reusableItemsPool.end(); ++it) { + if ((*it)->delegate != delegate) + continue; + auto modelItem = *it; + m_reusableItemsPool.erase(it); + + qCDebug(lcItemViewDelegateRecycling) + << "item:" << modelItem + << "delegate:" << delegate + << "old index:" << modelItem->modelIndex() + << "old row:" << modelItem->modelRow() + << "old column:" << modelItem->modelColumn() + << "new index:" << newIndexHint + << "pool size:" << m_reusableItemsPool.size(); + + return modelItem; + } + + qCDebug(lcItemViewDelegateRecycling) + << "no available item for delegate:" << delegate + << "new index:" << newIndexHint + << "pool size:" << m_reusableItemsPool.size(); + + return nullptr; +} + +void QQmlReusableDelegateModelItemsPool::drain(int maxPoolTime, std::function<void(QQmlDelegateModelItem *cacheItem)> releaseItem) +{ + // Rather than releasing all pooled items upon a call to this function, each + // item has a poolTime. The poolTime specifies for how many loading cycles an item + // has been resting in the pool. And for each invocation of this function, poolTime + // will increase. If poolTime is equal to, or exceeds, maxPoolTime, it will be removed + // from the pool and released. This way, the view can tweak a bit for how long + // items should stay in "circulation", even if they are not recycled right away. + qCDebug(lcItemViewDelegateRecycling) << "pool size before drain:" << m_reusableItemsPool.size(); + + for (auto it = m_reusableItemsPool.begin(); it != m_reusableItemsPool.end();) { + auto modelItem = *it; + modelItem->poolTime++; + if (modelItem->poolTime <= maxPoolTime) { + ++it; + } else { + it = m_reusableItemsPool.erase(it); + releaseItem(modelItem); + } + } + + qCDebug(lcItemViewDelegateRecycling) << "pool size after drain:" << m_reusableItemsPool.size(); +} + //============================================================================ struct QQmlDelegateModelGroupChange : QV4::Object diff --git a/src/qmlmodels/qqmldelegatemodel_p.h b/src/qmlmodels/qqmldelegatemodel_p.h index 02904a71d7..6db5768f63 100644 --- a/src/qmlmodels/qqmldelegatemodel_p.h +++ b/src/qmlmodels/qqmldelegatemodel_p.h @@ -113,7 +113,7 @@ public: int count() const override; bool isValid() const override { return delegate() != nullptr; } QObject *object(int index, QQmlIncubator::IncubationMode incubationMode = QQmlIncubator::AsynchronousIfNested) override; - ReleaseFlags release(QObject *object) override; + ReleaseFlags release(QObject *object, ReusableFlag reusable = NotReusable) override; void cancel(int index) override; QVariant variantValue(int index, const QString &role) override; void setWatchedRoles(const QList<QByteArray> &roles) override; diff --git a/src/qmlmodels/qqmldelegatemodel_p_p.h b/src/qmlmodels/qqmldelegatemodel_p_p.h index 286e124f2f..59fc3fc36e 100644 --- a/src/qmlmodels/qqmldelegatemodel_p_p.h +++ b/src/qmlmodels/qqmldelegatemodel_p_p.h @@ -49,6 +49,8 @@ #include <private/qqmladaptormodel_p.h> #include <private/qqmlopenmetaobject_p.h> +#include <QtCore/qloggingcategory.h> + // // W A R N I N G // ------------- @@ -64,6 +66,8 @@ QT_REQUIRE_CONFIG(qml_delegate_model); QT_BEGIN_NAMESPACE +Q_DECLARE_LOGGING_CATEGORY(lcItemViewDelegateRecycling) + typedef QQmlListCompositor Compositor; class QQmlDelegateModelAttachedMetaObject; @@ -190,7 +194,18 @@ void QV4::Heap::QQmlDelegateModelItemObject::init(QQmlDelegateModelItem *item) this->item = item; } +class QQmlReusableDelegateModelItemsPool +{ +public: + void insertItem(QQmlDelegateModelItem *modelItem); + QQmlDelegateModelItem *takeItem(const QQmlComponent *delegate, int newIndexHint); + void reuseItem(QQmlDelegateModelItem *item, int newModelIndex); + void drain(int maxPoolTime, std::function<void(QQmlDelegateModelItem *cacheItem)> releaseItem); + int size() { return m_reusableItemsPool.size(); } +private: + QList<QQmlDelegateModelItem *> m_reusableItemsPool; +}; class QQmlDelegateModelPrivate; class QQDMIncubationTask : public QQmlIncubator @@ -380,7 +395,7 @@ public: int count() const override; bool isValid() const override; QObject *object(int index, QQmlIncubator::IncubationMode incubationMode = QQmlIncubator::AsynchronousIfNested) override; - ReleaseFlags release(QObject *item) override; + ReleaseFlags release(QObject *item, ReusableFlag reusable = NotReusable) override; QVariant variantValue(int index, const QString &role) override; QList<QByteArray> watchedRoles() const { return m_watchedRoles; } void setWatchedRoles(const QList<QByteArray> &roles) override; diff --git a/src/qmlmodels/qqmlobjectmodel.cpp b/src/qmlmodels/qqmlobjectmodel.cpp index ffe4101e35..01e91e98dd 100644 --- a/src/qmlmodels/qqmlobjectmodel.cpp +++ b/src/qmlmodels/qqmlobjectmodel.cpp @@ -262,7 +262,7 @@ QObject *QQmlObjectModel::object(int index, QQmlIncubator::IncubationMode) return item.item; } -QQmlInstanceModel::ReleaseFlags QQmlObjectModel::release(QObject *item) +QQmlInstanceModel::ReleaseFlags QQmlObjectModel::release(QObject *item, ReusableFlag) { Q_D(QQmlObjectModel); int idx = d->indexOf(item); diff --git a/src/qmlmodels/qqmlobjectmodel_p.h b/src/qmlmodels/qqmlobjectmodel_p.h index 99a1fe2379..6c68e55012 100644 --- a/src/qmlmodels/qqmlobjectmodel_p.h +++ b/src/qmlmodels/qqmlobjectmodel_p.h @@ -72,6 +72,11 @@ class Q_QMLMODELS_PRIVATE_EXPORT QQmlInstanceModel : public QObject QML_ANONYMOUS public: + enum ReusableFlag { + NotReusable, + Reusable + }; + virtual ~QQmlInstanceModel() {} enum ReleaseFlag { Referenced = 0x01, Destroyed = 0x02, Pooled = 0x04 }; @@ -80,13 +85,16 @@ public: virtual int count() const = 0; virtual bool isValid() const = 0; virtual QObject *object(int index, QQmlIncubator::IncubationMode incubationMode = QQmlIncubator::AsynchronousIfNested) = 0; - virtual ReleaseFlags release(QObject *object) = 0; + virtual ReleaseFlags release(QObject *object, ReusableFlag reusableFlag = NotReusable) = 0; virtual void cancel(int) {} QString stringValue(int index, const QString &role) { return variantValue(index, role).toString(); } virtual QVariant variantValue(int, const QString &) = 0; virtual void setWatchedRoles(const QList<QByteArray> &roles) = 0; virtual QQmlIncubator::Status incubationStatus(int index) = 0; + virtual void drainReusableItemsPool(int maxPoolTime) { Q_UNUSED(maxPoolTime) } + virtual int poolSize() { return 0; } + virtual int indexOf(QObject *object, QObject *objectContext) const = 0; virtual const QAbstractItemModel *abstractItemModel() const { return nullptr; } @@ -125,7 +133,7 @@ public: int count() const override; bool isValid() const override; QObject *object(int index, QQmlIncubator::IncubationMode incubationMode = QQmlIncubator::AsynchronousIfNested) override; - ReleaseFlags release(QObject *object) override; + ReleaseFlags release(QObject *object, ReusableFlag reusable = NotReusable) override; QVariant variantValue(int index, const QString &role) override; void setWatchedRoles(const QList<QByteArray> &) override {} QQmlIncubator::Status incubationStatus(int index) override; diff --git a/src/qmlmodels/qqmltableinstancemodel.cpp b/src/qmlmodels/qqmltableinstancemodel.cpp index 1af93e87d8..b4d1e61e31 100644 --- a/src/qmlmodels/qqmltableinstancemodel.cpp +++ b/src/qmlmodels/qqmltableinstancemodel.cpp @@ -141,7 +141,7 @@ QQmlDelegateModelItem *QQmlTableInstanceModel::resolveModelItem(int index) return nullptr; // Check if the pool contains an item that can be reused - modelItem = takeFromReusableItemsPool(delegate); + modelItem = m_reusableItemsPool.takeItem(delegate, index); if (modelItem) { reuseItem(modelItem, index); m_modelItems.insert(index, modelItem); @@ -225,16 +225,21 @@ QQmlInstanceModel::ReleaseFlags QQmlTableInstanceModel::release(QObject *object, m_modelItems.remove(modelItem->index); if (reusable == Reusable) { - insertIntoReusableItemsPool(modelItem); + m_reusableItemsPool.insertItem(modelItem); + emit itemPooled(modelItem->index, modelItem->object); return QQmlInstanceModel::Pooled; } // The item is not reused or referenced by anyone, so just delete it - modelItem->destroyObject(); - emit destroyingItem(object); + destroyModelItem(modelItem); + return QQmlInstanceModel::Destroyed; +} +void QQmlTableInstanceModel::destroyModelItem(QQmlDelegateModelItem *modelItem) +{ + emit destroyingItem(modelItem->object); + modelItem->destroyObject(); delete modelItem; - return QQmlInstanceModel::Destroyed; } void QQmlTableInstanceModel::cancel(int index) @@ -257,85 +262,9 @@ void QQmlTableInstanceModel::cancel(int index) delete modelItem; } -void QQmlTableInstanceModel::insertIntoReusableItemsPool(QQmlDelegateModelItem *modelItem) -{ - // Currently, the only way for a view to reuse items is to call QQmlTableInstanceModel::release() - // with the second argument explicitly set to QQmlTableInstanceModel::Reusable. If the released - // item is no longer referenced, it will be added to the pool. Reusing of items can be specified - // per item, in case certain items cannot be recycled. - // A QQmlDelegateModelItem knows which delegate its object was created from. So when we are - // about to create a new item, we first check if the pool contains an item based on the same - // delegate from before. If so, we take it out of the pool (instead of creating a new item), and - // update all its context-, and attached properties. - // When a view is recycling items, it should call QQmlTableInstanceModel::drainReusableItemsPool() - // regularly. As there is currently no logic to 'hibernate' items in the pool, they are only - // meant to rest there for a short while, ideally only from the time e.g a row is unloaded - // on one side of the view, and until a new row is loaded on the opposite side. In-between - // this time, the application will see the item as fully functional and 'alive' (just not - // visible on screen). Since this time is supposed to be short, we don't take any action to - // notify the application about it, since we don't want to trigger any bindings that can - // disturb performance. - // A recommended time for calling drainReusableItemsPool() is each time a view has finished - // loading e.g a new row or column. If there are more items in the pool after that, it means - // that the view most likely doesn't need them anytime soon. Those items should be destroyed to - // not consume resources. - // Depending on if a view is a list or a table, it can sometimes be performant to keep - // items in the pool for a bit longer than one "row out/row in" cycle. E.g for a table, if the - // number of visible rows in a view is much larger than the number of visible columns. - // In that case, if you flick out a row, and then flick in a column, you would throw away a lot - // of items in the pool if completely draining it. The reason is that unloading a row places more - // items in the pool than what ends up being recycled when loading a new column. And then, when you - // next flick in a new row, you would need to load all those drained items again from scratch. For - // that reason, you can specify a maxPoolTime to the drainReusableItemsPool() that allows you to keep - // items in the pool for a bit longer, effectively keeping more items in circulation. - // A recommended maxPoolTime would be equal to the number of dimenstions in the view, which - // means 1 for a list view and 2 for a table view. If you specify 0, all items will be drained. - Q_ASSERT(!modelItem->incubationTask); - Q_ASSERT(!modelItem->isObjectReferenced()); - Q_ASSERT(!modelItem->isReferenced()); - Q_ASSERT(modelItem->object); - - modelItem->poolTime = 0; - m_reusableItemsPool.append(modelItem); - emit itemPooled(modelItem->index, modelItem->object); -} - -QQmlDelegateModelItem *QQmlTableInstanceModel::takeFromReusableItemsPool(const QQmlComponent *delegate) -{ - // Find the oldest item in the pool that was made from the same delegate as - // the given argument, remove it from the pool, and return it. - if (m_reusableItemsPool.isEmpty()) - return nullptr; - - for (auto it = m_reusableItemsPool.begin(); it != m_reusableItemsPool.end(); ++it) { - if ((*it)->delegate != delegate) - continue; - auto modelItem = *it; - m_reusableItemsPool.erase(it); - return modelItem; - } - - return nullptr; -} - void QQmlTableInstanceModel::drainReusableItemsPool(int maxPoolTime) { - // Rather than releasing all pooled items upon a call to this function, each - // item has a poolTime. The poolTime specifies for how many loading cycles an item - // has been resting in the pool. And for each invocation of this function, poolTime - // will increase. If poolTime is equal to, or exceeds, maxPoolTime, it will be removed - // from the pool and released. This way, the view can tweak a bit for how long - // items should stay in "circulation", even if they are not recycled right away. - for (auto it = m_reusableItemsPool.begin(); it != m_reusableItemsPool.end();) { - auto modelItem = *it; - modelItem->poolTime++; - if (modelItem->poolTime <= maxPoolTime) { - ++it; - } else { - it = m_reusableItemsPool.erase(it); - release(modelItem->object, NotReusable); - } - } + m_reusableItemsPool.drain(maxPoolTime, [=](QQmlDelegateModelItem *modelItem){ destroyModelItem(modelItem); }); } void QQmlTableInstanceModel::reuseItem(QQmlDelegateModelItem *item, int newModelIndex) diff --git a/src/qmlmodels/qqmltableinstancemodel_p.h b/src/qmlmodels/qqmltableinstancemodel_p.h index 1ea5ee7401..d924455918 100644 --- a/src/qmlmodels/qqmltableinstancemodel_p.h +++ b/src/qmlmodels/qqmltableinstancemodel_p.h @@ -86,12 +86,6 @@ class Q_QMLMODELS_PRIVATE_EXPORT QQmlTableInstanceModel : public QQmlInstanceMod Q_OBJECT public: - - enum ReusableFlag { - NotReusable, - Reusable - }; - QQmlTableInstanceModel(QQmlContext *qmlContext, QObject *parent = nullptr); ~QQmlTableInstanceModel() override; @@ -115,14 +109,11 @@ public: const QAbstractItemModel *abstractItemModel() const override; QObject *object(int index, QQmlIncubator::IncubationMode incubationMode = QQmlIncubator::AsynchronousIfNested) override; - ReleaseFlags release(QObject *object) override { return release(object, NotReusable); } - ReleaseFlags release(QObject *object, ReusableFlag reusable); + ReleaseFlags release(QObject *object, ReusableFlag reusable = NotReusable) override; void cancel(int) override; - void insertIntoReusableItemsPool(QQmlDelegateModelItem *modelItem); - QQmlDelegateModelItem *takeFromReusableItemsPool(const QQmlComponent *delegate); - void drainReusableItemsPool(int maxPoolTime); - int poolSize() { return m_reusableItemsPool.size(); } + void drainReusableItemsPool(int maxPoolTime) override; + int poolSize() override { return m_reusableItemsPool.size(); } void reuseItem(QQmlDelegateModelItem *item, int newModelIndex); QQmlIncubator::Status incubationStatus(int index) override; @@ -145,7 +136,7 @@ private: QQmlDelegateModelItemMetaType *m_metaType; QHash<int, QQmlDelegateModelItem *> m_modelItems; - QList<QQmlDelegateModelItem *> m_reusableItemsPool; + QQmlReusableDelegateModelItemsPool m_reusableItemsPool; QList<QQmlIncubator *> m_finishedIncubationTasks; void incubateModelItem(QQmlDelegateModelItem *modelItem, QQmlIncubator::IncubationMode incubationMode); @@ -153,6 +144,7 @@ private: void deleteIncubationTaskLater(QQmlIncubator *incubationTask); void deleteAllFinishedIncubationTasks(); QQmlDelegateModelItem *resolveModelItem(int index); + void destroyModelItem(QQmlDelegateModelItem *modelItem); void dataChangedCallback(const QModelIndex &begin, const QModelIndex &end, const QVector<int> &roles); |