aboutsummaryrefslogtreecommitdiffstats
path: root/src/qmlmodels
diff options
context:
space:
mode:
authorRichard Moe Gustavsen <richard.gustavsen@qt.io>2019-09-12 14:14:05 +0200
committerRichard Moe Gustavsen <richard.gustavsen@qt.io>2019-12-04 01:34:07 +0100
commit4b58d69d56c5876a1b1d71ce6a96b4c6c81a833f (patch)
tree094a0240be7462a2768084985d49f2f58a294ab1 /src/qmlmodels
parentaf9a96ca10e72517a7e8aa1ada7ec2d635e2a9ff (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.cpp121
-rw-r--r--src/qmlmodels/qqmldelegatemodel_p.h2
-rw-r--r--src/qmlmodels/qqmldelegatemodel_p_p.h17
-rw-r--r--src/qmlmodels/qqmlobjectmodel.cpp2
-rw-r--r--src/qmlmodels/qqmlobjectmodel_p.h12
-rw-r--r--src/qmlmodels/qqmltableinstancemodel.cpp93
-rw-r--r--src/qmlmodels/qqmltableinstancemodel_p.h18
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);