aboutsummaryrefslogtreecommitdiffstats
path: root/src/qml/types
diff options
context:
space:
mode:
Diffstat (limited to 'src/qml/types')
-rw-r--r--src/qml/types/qqmldelegatemodel.cpp2
-rw-r--r--src/qml/types/qqmldelegatemodel_p_p.h2
-rw-r--r--src/qml/types/qqmltableinstancemodel.cpp232
-rw-r--r--src/qml/types/qqmltableinstancemodel_p.h23
4 files changed, 212 insertions, 47 deletions
diff --git a/src/qml/types/qqmldelegatemodel.cpp b/src/qml/types/qqmldelegatemodel.cpp
index 7a128d7070..d9adb7808e 100644
--- a/src/qml/types/qqmldelegatemodel.cpp
+++ b/src/qml/types/qqmldelegatemodel.cpp
@@ -2005,6 +2005,8 @@ QQmlDelegateModelItem::QQmlDelegateModelItem(QQmlDelegateModelItemMetaType *meta
, object(nullptr)
, attached(nullptr)
, incubationTask(nullptr)
+ , delegate(nullptr)
+ , poolTime(0)
, objectRef(0)
, scriptRef(0)
, groups(0)
diff --git a/src/qml/types/qqmldelegatemodel_p_p.h b/src/qml/types/qqmldelegatemodel_p_p.h
index e87f8d4440..43d288b5c5 100644
--- a/src/qml/types/qqmldelegatemodel_p_p.h
+++ b/src/qml/types/qqmldelegatemodel_p_p.h
@@ -143,6 +143,8 @@ public:
QPointer<QObject> object;
QPointer<QQmlDelegateModelAttached> attached;
QQDMIncubationTask *incubationTask;
+ QQmlComponent *delegate;
+ int poolTime;
int objectRef;
int scriptRef;
int groups;
diff --git a/src/qml/types/qqmltableinstancemodel.cpp b/src/qml/types/qqmltableinstancemodel.cpp
index bab70ecaa6..6604e009c1 100644
--- a/src/qml/types/qqmltableinstancemodel.cpp
+++ b/src/qml/types/qqmltableinstancemodel.cpp
@@ -91,6 +91,37 @@ QQmlTableInstanceModel::~QQmlTableInstanceModel()
// the view needs to be deleted first (and thereby release all
// its items), before this destructor is run.
Q_ASSERT(m_modelItems.isEmpty());
+
+ drainReusableItemsPool(0);
+}
+
+QQmlDelegateModelItem *QQmlTableInstanceModel::resolveModelItem(int index)
+{
+ // Check if an item for the given index is already loaded and ready
+ QQmlDelegateModelItem *modelItem = m_modelItems.value(index, nullptr);
+ if (modelItem)
+ return modelItem;
+
+ QQmlComponent *delegate = m_delegate;
+
+ // Check if the pool contains an item that can be reused
+ modelItem = takeFromReusableItemsPool(delegate);
+ if (modelItem) {
+ reuseItem(modelItem, index);
+ m_modelItems.insert(index, modelItem);
+ return modelItem;
+ }
+
+ // Create a new item from scratch
+ modelItem = m_adaptorModel.createItem(m_metaType, index);
+ if (modelItem) {
+ modelItem->delegate = delegate;
+ m_modelItems.insert(index, modelItem);
+ return modelItem;
+ }
+
+ qWarning() << Q_FUNC_INFO << "failed creating a model item for index: " << index;
+ return nullptr;
}
QObject *QQmlTableInstanceModel::object(int index, QQmlIncubator::IncubationMode incubationMode)
@@ -99,18 +130,9 @@ QObject *QQmlTableInstanceModel::object(int index, QQmlIncubator::IncubationMode
Q_ASSERT(index >= 0 && index < m_adaptorModel.count());
Q_ASSERT(m_qmlContext && m_qmlContext->isValid());
- // Check if we've already created an item for the given index
- QQmlDelegateModelItem *modelItem = m_modelItems.value(index, nullptr);
-
- if (!modelItem) {
- // Create a new model item
- modelItem = m_adaptorModel.createItem(m_metaType, index);
- if (!modelItem) {
- qWarning() << Q_FUNC_INFO << "Adaptor model failed creating a model item";
- return nullptr;
- }
- m_modelItems.insert(index, modelItem);
- }
+ QQmlDelegateModelItem *modelItem = resolveModelItem(index);
+ if (!modelItem)
+ return nullptr;
if (modelItem->object) {
// The model item has already been incubated. So
@@ -119,41 +141,12 @@ QObject *QQmlTableInstanceModel::object(int index, QQmlIncubator::IncubationMode
return modelItem->object;
}
- // Guard the model item temporarily so that it's not deleted from
- // incubatorStatusChanged(), in case the incubation is done synchronously.
- modelItem->scriptRef++;
-
- if (modelItem->incubationTask) {
- // We're already incubating the model item from a previous request. If the previous call requested
- // the item async, but the current request needs it sync, we need to force-complete the incubation.
- const bool sync = (incubationMode == QQmlIncubator::Synchronous || incubationMode == QQmlIncubator::AsynchronousIfNested);
- if (sync && modelItem->incubationTask->incubationMode() == QQmlIncubator::Asynchronous)
- modelItem->incubationTask->forceCompletion();
- } else {
- modelItem->incubationTask = new QQmlTableInstanceModelIncubationTask(this, modelItem, incubationMode);
-
- QQmlContextData *ctxt = new QQmlContextData;
- QQmlContext *creationContext = m_delegate->creationContext();
- ctxt->setParent(QQmlContextData::get(creationContext ? creationContext : m_qmlContext.data()));
- ctxt->contextObject = modelItem;
- modelItem->contextData = ctxt;
-
- QQmlComponentPrivate::get(m_delegate)->incubateObject(
- modelItem->incubationTask,
- m_delegate,
- m_qmlContext->engine(),
- ctxt,
- QQmlContextData::get(m_qmlContext));
- }
-
- // Remove the temporary guard
- modelItem->scriptRef--;
- Q_ASSERT(modelItem->scriptRef == 0);
-
+ // The object is not ready, and needs to be incubated
+ incubateModelItem(modelItem, incubationMode);
if (!isDoneIncubating(modelItem))
return nullptr;
- // When incubation is done, the task should be removed
+ // Incubation is done, so the task should be removed
Q_ASSERT(!modelItem->incubationTask);
if (!modelItem->object) {
@@ -173,7 +166,7 @@ QObject *QQmlTableInstanceModel::object(int index, QQmlIncubator::IncubationMode
return modelItem->object;
}
-QQmlInstanceModel::ReleaseFlags QQmlTableInstanceModel::release(QObject *object)
+QQmlInstanceModel::ReleaseFlags QQmlTableInstanceModel::release(QObject *object, ReusableFlag reusable)
{
Q_ASSERT(object);
auto modelItem = qvariant_cast<QQmlDelegateModelItem *>(object->property(kModelItemTag));
@@ -188,11 +181,19 @@ QQmlInstanceModel::ReleaseFlags QQmlTableInstanceModel::release(QObject *object)
// are e.g delivered async, and the user flicks back and forth quicker than the loading can catch
// up with. The view might then find that the object is no longer visible and should be released.
// We detect this case in incubatorStatusChanged(), and delete it there instead. But from the callers
- // points of view, it should consider it destroyed.
+ // point of view, it should consider it destroyed.
return QQmlDelegateModel::Destroyed;
}
+ // The item is not referenced by anyone
m_modelItems.remove(modelItem->index);
+
+ if (reusable == Reusable) {
+ insertIntoReusableItemsPool(modelItem);
+ return QQmlInstanceModel::Referenced;
+ }
+
+ // The item is not reused or referenced by anyone, so just delete it
modelItem->destroyObject();
emit destroyingItem(object);
@@ -220,6 +221,139 @@ 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);
+ }
+ }
+}
+
+void QQmlTableInstanceModel::reuseItem(QQmlDelegateModelItem *item, int newModelIndex)
+{
+ // Update the context properties index, row and column on
+ // the delegate item, and inform the application about it.
+ const int newRow = m_adaptorModel.rowAt(newModelIndex);
+ const int newColumn = m_adaptorModel.columnAt(newModelIndex);
+ item->setModelIndex(newModelIndex, newRow, newColumn);
+
+ // Notify the application that all 'dynamic'/role-based context data has
+ // changed as well (their getter function will use the updated index).
+ auto const itemAsList = QList<QQmlDelegateModelItem *>() << item;
+ auto const updateAllRoles = QVector<int>();
+ m_adaptorModel.notify(itemAsList, newModelIndex, 1, updateAllRoles);
+
+ // Inform the view that the item is recycled. This will typically result
+ // in the view updating its own attached delegate item properties.
+ emit itemReused(newModelIndex, item->object);
+}
+
+void QQmlTableInstanceModel::incubateModelItem(QQmlDelegateModelItem *modelItem, QQmlIncubator::IncubationMode incubationMode)
+{
+ // Guard the model item temporarily so that it's not deleted from
+ // incubatorStatusChanged(), in case the incubation is done synchronously.
+ modelItem->scriptRef++;
+
+ if (modelItem->incubationTask) {
+ // We're already incubating the model item from a previous request. If the previous call requested
+ // the item async, but the current request needs it sync, we need to force-complete the incubation.
+ const bool sync = (incubationMode == QQmlIncubator::Synchronous || incubationMode == QQmlIncubator::AsynchronousIfNested);
+ if (sync && modelItem->incubationTask->incubationMode() == QQmlIncubator::Asynchronous)
+ modelItem->incubationTask->forceCompletion();
+ } else {
+ modelItem->incubationTask = new QQmlTableInstanceModelIncubationTask(this, modelItem, incubationMode);
+
+ QQmlContextData *ctxt = new QQmlContextData;
+ QQmlContext *creationContext = modelItem->delegate->creationContext();
+ ctxt->setParent(QQmlContextData::get(creationContext ? creationContext : m_qmlContext.data()));
+ ctxt->contextObject = modelItem;
+ modelItem->contextData = ctxt;
+
+ QQmlComponentPrivate::get(modelItem->delegate)->incubateObject(
+ modelItem->incubationTask,
+ modelItem->delegate,
+ m_qmlContext->engine(),
+ ctxt,
+ QQmlContextData::get(m_qmlContext));
+ }
+
+ // Remove the temporary guard
+ modelItem->scriptRef--;
+}
+
void QQmlTableInstanceModel::incubatorStatusChanged(QQmlTableInstanceModelIncubationTask *incubationTask, QQmlIncubator::Status status)
{
QQmlDelegateModelItem *modelItem = incubationTask->modelItemToIncubate;
@@ -299,6 +433,10 @@ QVariant QQmlTableInstanceModel::model() const
void QQmlTableInstanceModel::setModel(const QVariant &model)
{
+ // Pooled items are still accessible/alive for the application, and
+ // needs to stay in sync with the model. So we need to drain the pool
+ // completely when the model changes.
+ drainReusableItemsPool(0);
m_adaptorModel.setModel(model, this, m_qmlContext->engine());
}
@@ -337,5 +475,7 @@ void QQmlTableInstanceModelIncubationTask::statusChanged(QQmlIncubator::Status s
tableInstanceModel->incubatorStatusChanged(this, status);
}
+#include "moc_qqmltableinstancemodel_p.cpp"
+
QT_END_NAMESPACE
diff --git a/src/qml/types/qqmltableinstancemodel_p.h b/src/qml/types/qqmltableinstancemodel_p.h
index fb222d8bc4..23243e7f0e 100644
--- a/src/qml/types/qqmltableinstancemodel_p.h
+++ b/src/qml/types/qqmltableinstancemodel_p.h
@@ -80,7 +80,15 @@ public:
class Q_QML_PRIVATE_EXPORT QQmlTableInstanceModel : public QQmlInstanceModel
{
+ Q_OBJECT
+
public:
+
+ enum ReusableFlag {
+ NotReusable,
+ Reusable
+ };
+
QQmlTableInstanceModel(QQmlContext *qmlContext, QObject *parent = nullptr);
~QQmlTableInstanceModel() override;
@@ -99,15 +107,25 @@ public:
const QAbstractItemModel *abstractItemModel() const override;
QObject *object(int index, QQmlIncubator::IncubationMode incubationMode = QQmlIncubator::AsynchronousIfNested) override;
- ReleaseFlags release(QObject *) override;
+ ReleaseFlags release(QObject *object) override { return release(object, NotReusable); }
+ ReleaseFlags release(QObject *object, ReusableFlag reusable);
void cancel(int) override;
+ void insertIntoReusableItemsPool(QQmlDelegateModelItem *modelItem);
+ QQmlDelegateModelItem *takeFromReusableItemsPool(const QQmlComponent *delegate);
+ void drainReusableItemsPool(int maxPoolTime);
+ void reuseItem(QQmlDelegateModelItem *item, int newModelIndex);
+
QQmlIncubator::Status incubationStatus(int index) override;
QString stringValue(int, const QString &) override { Q_UNREACHABLE(); return QString(); }
void setWatchedRoles(const QList<QByteArray> &) override { Q_UNREACHABLE(); }
int indexOf(QObject *, QObject *) const override { Q_UNREACHABLE(); return 0; }
+Q_SIGNALS:
+ void itemPooled(int index, QObject *object);
+ void itemReused(int index, QObject *object);
+
private:
QQmlComponent *m_delegate = nullptr;
QQmlAdaptorModel m_adaptorModel;
@@ -115,11 +133,14 @@ private:
QQmlDelegateModelItemMetaType *m_metaType;
QHash<int, QQmlDelegateModelItem *> m_modelItems;
+ QList<QQmlDelegateModelItem *> m_reusableItemsPool;
QList<QQmlIncubator *> m_finishedIncubationTasks;
+ void incubateModelItem(QQmlDelegateModelItem *modelItem, QQmlIncubator::IncubationMode incubationMode);
void incubatorStatusChanged(QQmlTableInstanceModelIncubationTask *dmIncubationTask, QQmlIncubator::Status status);
void deleteIncubationTaskLater(QQmlIncubator *incubationTask);
void deleteAllFinishedIncubationTasks();
+ QQmlDelegateModelItem *resolveModelItem(int index);
static bool isDoneIncubating(QQmlDelegateModelItem *modelItem);
static void deleteModelItemLater(QQmlDelegateModelItem *modelItem);