diff options
author | Qt Forward Merge Bot <qt_forward_merge_bot@qt-project.org> | 2019-12-12 01:00:07 +0100 |
---|---|---|
committer | Ulf Hermann <ulf.hermann@qt.io> | 2019-12-12 10:06:06 +0100 |
commit | 1196b1ef6c5d2cb05ceba5d6f178dc7e2432ed61 (patch) | |
tree | 2a99ee28d15d8ee51fc28096e5559bfc2d453e3f /src/qmlmodels | |
parent | fb54af6638dcbeae8ad21249fe234ef4d82c005b (diff) | |
parent | ca206bceaff3667469986402e6143bf4c666b228 (diff) |
Merge remote-tracking branch 'origin/5.15' into dev
Conflicts:
src/qml/types/qqmlbind.cpp
Change-Id: Ib992d1a7ac6c1a96d39819be6f23955dc31b44b2
Diffstat (limited to 'src/qmlmodels')
-rw-r--r-- | src/qmlmodels/doc/images/listmodel-nested.png | bin | 0 -> 7493 bytes | |||
-rw-r--r-- | src/qmlmodels/doc/images/listmodel.png | bin | 0 -> 3407 bytes | |||
-rw-r--r-- | src/qmlmodels/doc/images/objectmodel.png | bin | 0 -> 347 bytes | |||
-rw-r--r-- | src/qmlmodels/doc/qtqmlmodels.qdocconf | 13 | ||||
-rw-r--r-- | src/qmlmodels/qqmldelegatemodel.cpp | 324 | ||||
-rw-r--r-- | src/qmlmodels/qqmldelegatemodel_p.h | 7 | ||||
-rw-r--r-- | src/qmlmodels/qqmldelegatemodel_p_p.h | 30 | ||||
-rw-r--r-- | src/qmlmodels/qqmllistmodel.cpp | 4 | ||||
-rw-r--r-- | src/qmlmodels/qqmlobjectmodel.cpp | 4 | ||||
-rw-r--r-- | src/qmlmodels/qqmlobjectmodel_p.h | 14 | ||||
-rw-r--r-- | src/qmlmodels/qqmltableinstancemodel.cpp | 101 | ||||
-rw-r--r-- | src/qmlmodels/qqmltableinstancemodel_p.h | 21 |
12 files changed, 348 insertions, 170 deletions
diff --git a/src/qmlmodels/doc/images/listmodel-nested.png b/src/qmlmodels/doc/images/listmodel-nested.png Binary files differnew file mode 100644 index 0000000000..ee7ffba67a --- /dev/null +++ b/src/qmlmodels/doc/images/listmodel-nested.png diff --git a/src/qmlmodels/doc/images/listmodel.png b/src/qmlmodels/doc/images/listmodel.png Binary files differnew file mode 100644 index 0000000000..7ab1771f15 --- /dev/null +++ b/src/qmlmodels/doc/images/listmodel.png diff --git a/src/qmlmodels/doc/images/objectmodel.png b/src/qmlmodels/doc/images/objectmodel.png Binary files differnew file mode 100644 index 0000000000..5e6d1325b2 --- /dev/null +++ b/src/qmlmodels/doc/images/objectmodel.png diff --git a/src/qmlmodels/doc/qtqmlmodels.qdocconf b/src/qmlmodels/doc/qtqmlmodels.qdocconf index a4153f4a53..3364988559 100644 --- a/src/qmlmodels/doc/qtqmlmodels.qdocconf +++ b/src/qmlmodels/doc/qtqmlmodels.qdocconf @@ -23,15 +23,18 @@ qhp.QtQmlModels.sortPages = true tagfile = qtqmlmodels.tags -depends += qtcore qtqml qtdoc +depends += qtcore qtqml qtquick qtdoc qtqmlworkerscript qtquickcontrols qtxmlpatterns -headerdirs += .. +headerdirs += .. \ + ../../imports/labsmodels sourcedirs += .. \ - ../../imports/models + ../../imports/models \ + ../../imports/labsmodels -exampledirs += ../../../examples/qml \ - ../ \ +exampledirs += .. \ snippets +imagedirs += images + navigation.qmltypespage = "Qt Qml Models QML Types" diff --git a/src/qmlmodels/qqmldelegatemodel.cpp b/src/qmlmodels/qqmldelegatemodel.cpp index e3c01d040a..7ad53eeb6c 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 { @@ -205,6 +207,9 @@ QQmlDelegateModelPrivate::~QQmlDelegateModelPrivate() { qDeleteAll(m_finishedIncubating); + // Free up all items in the pool + drainReusableItemsPool(0); + if (m_cacheMetaType) m_cacheMetaType->release(); } @@ -588,36 +593,47 @@ int QQmlDelegateModel::count() const return d->m_compositor.count(d->m_compositorGroup); } -QQmlDelegateModel::ReleaseFlags QQmlDelegateModelPrivate::release(QObject *object) +QQmlDelegateModel::ReleaseFlags QQmlDelegateModelPrivate::release(QObject *object, QQmlInstanceModel::ReusableFlag reusableFlag) { if (!object) - return QQmlDelegateModel::ReleaseFlags(0); + return QQmlDelegateModel::ReleaseFlags{}; QQmlDelegateModelItem *cacheItem = QQmlDelegateModelItem::dataForObject(object); if (!cacheItem) - return QQmlDelegateModel::ReleaseFlags(0); + return QQmlDelegateModel::ReleaseFlags{}; if (!cacheItem->releaseObject()) return QQmlDelegateModel::Referenced; + if (reusableFlag == QQmlInstanceModel::Reusable) { + removeCacheItem(cacheItem); + m_reusableItemsPool.insertItem(cacheItem); + emit q_func()->itemPooled(cacheItem->index, cacheItem->object); + return QQmlInstanceModel::Pooled; + } + + destroyCacheItem(cacheItem); + return QQmlInstanceModel::Destroyed; +} + +void QQmlDelegateModelPrivate::destroyCacheItem(QQmlDelegateModelItem *cacheItem) +{ + emitDestroyingItem(cacheItem->object); cacheItem->destroyObject(); - emitDestroyingItem(object); if (cacheItem->incubationTask) { releaseIncubator(cacheItem->incubationTask); cacheItem->incubationTask = nullptr; } cacheItem->Dispose(); - return QQmlInstanceModel::Destroyed; } /* Returns ReleaseStatus flags. */ - -QQmlDelegateModel::ReleaseFlags QQmlDelegateModel::release(QObject *item) +QQmlDelegateModel::ReleaseFlags QQmlDelegateModel::release(QObject *item, QQmlInstanceModel::ReusableFlag reusableFlag) { Q_D(QQmlDelegateModel); - QQmlInstanceModel::ReleaseFlags stat = d->release(item); + QQmlInstanceModel::ReleaseFlags stat = d->release(item, reusableFlag); return stat; } @@ -937,13 +953,14 @@ void PropertyUpdater::breakBinding() void QQDMIncubationTask::initializeRequiredProperties(QQmlDelegateModelItem *modelItemToIncubate, QObject *object) { auto incubatorPriv = QQmlIncubatorPrivate::get(this); - QQmlData *d = QQmlData::get(object); - auto contextData = d ? d->context : nullptr; - if (contextData) { - contextData->hasExtraObject = true; - contextData->extraObject = modelItemToIncubate; - } if (incubatorPriv->hadRequiredProperties()) { + QQmlData *d = QQmlData::get(object); + auto contextData = d ? d->context : nullptr; + if (contextData) { + contextData->hasExtraObject = true; + contextData->extraObject = modelItemToIncubate; + } + if (incubatorPriv->requiredProperties().empty()) return; RequiredProperties &requiredProperties = incubatorPriv->requiredProperties(); @@ -954,21 +971,25 @@ void QQDMIncubationTask::initializeRequiredProperties(QQmlDelegateModelItem *mod // column, model and more // the most derived subclass of QQmlDelegateModelItem is QQmlDMAbstractModelData at depth 2, // so 4 should be plenty - QVarLengthArray<const QMetaObject *, 4> mos; + QVarLengthArray<QPair<const QMetaObject *, QObject *>, 4> mos; // we first check the dynamic meta object for properties originating from the model - mos.push_back(qmlMetaObject); // contains abstractitemmodelproperties + // contains abstractitemmodelproperties + mos.push_back(qMakePair(qmlMetaObject, modelItemToIncubate)); auto delegateModelItemSubclassMO = qmlMetaObject->superClass(); - mos.push_back(delegateModelItemSubclassMO); + mos.push_back(qMakePair(delegateModelItemSubclassMO, modelItemToIncubate)); - while (strcmp(delegateModelItemSubclassMO->className(), modelItemToIncubate->staticMetaObject.className())) { + while (strcmp(delegateModelItemSubclassMO->className(), + modelItemToIncubate->staticMetaObject.className())) { delegateModelItemSubclassMO = delegateModelItemSubclassMO->superClass(); - mos.push_back(delegateModelItemSubclassMO); + mos.push_back(qMakePair(delegateModelItemSubclassMO, modelItemToIncubate)); } if (proxiedObject) - mos.push_back(proxiedObject->metaObject()); + mos.push_back(qMakePair(proxiedObject->metaObject(), proxiedObject)); auto updater = new PropertyUpdater(object); - for (const QMetaObject *mo : mos) { + for (const auto &metaObjectAndObject : mos) { + const QMetaObject *mo = metaObjectAndObject.first; + QObject *itemOrProxy = metaObjectAndObject.second; for (int i = mo->propertyOffset(); i < mo->propertyCount() + mo->propertyOffset(); ++i) { auto prop = mo->property(i); if (!prop.name()) @@ -980,19 +1001,23 @@ void QQDMIncubationTask::initializeRequiredProperties(QQmlDelegateModelItem *mod // only write to property if it was actually requested by the component if (wasInRequired && prop.hasNotifySignal()) { QMetaMethod changeSignal = prop.notifySignal(); - static QMetaMethod updateSlot = PropertyUpdater::staticMetaObject.method(PropertyUpdater::staticMetaObject.indexOfSlot("doUpdate()")); - QMetaObject::Connection conn = QObject::connect(modelItemToIncubate, changeSignal, updater, updateSlot); + static QMetaMethod updateSlot = PropertyUpdater::staticMetaObject.method( + PropertyUpdater::staticMetaObject.indexOfSlot("doUpdate()")); + QMetaObject::Connection conn = QObject::connect(itemOrProxy, changeSignal, + updater, updateSlot); auto propIdx = object->metaObject()->indexOfProperty(propName.toUtf8()); - QMetaMethod writeToPropSignal = object->metaObject()->property(propIdx).notifySignal(); + QMetaMethod writeToPropSignal + = object->metaObject()->property(propIdx).notifySignal(); updater->senderToConnection[writeToPropSignal.methodIndex()] = conn; - static QMetaMethod breakBinding = PropertyUpdater::staticMetaObject.method(PropertyUpdater::staticMetaObject.indexOfSlot("breakBinding()")); - componentProp.write(prop.read(modelItemToIncubate)); + static QMetaMethod breakBinding = PropertyUpdater::staticMetaObject.method( + PropertyUpdater::staticMetaObject.indexOfSlot("breakBinding()")); + componentProp.write(prop.read(itemOrProxy)); // the connection needs to established after the write, // else the signal gets triggered by it and breakBinding will remove the connection QObject::connect(object, writeToPropSignal, updater, breakBinding); } else if (wasInRequired) // we still have to write, even if there is no change signal - componentProp.write(prop.read(modelItemToIncubate)); + componentProp.write(prop.read(itemOrProxy)); } } } else { @@ -1033,6 +1058,71 @@ void QQmlDelegateModelPrivate::releaseIncubator(QQDMIncubationTask *incubationTa } } +void QQmlDelegateModelPrivate::reuseItem(QQmlDelegateModelItem *item, int newModelIndex, int newGroups) +{ + Q_ASSERT(item->object); + + // Update/reset which groups the item belongs to + item->groups = newGroups; + + // Update context property index (including row and column) on the delegate + // item, and inform the application about it. For a list, the row is the same + // as the index, and the column is always 0. We set alwaysEmit to true, to + // force all bindings to be reevaluated, even if the index didn't change. + const bool alwaysEmit = true; + item->setModelIndex(newModelIndex, newModelIndex, 0, alwaysEmit); + + // 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); + + if (QQmlDelegateModelAttached *att = static_cast<QQmlDelegateModelAttached *>( + qmlAttachedPropertiesObject<QQmlDelegateModel>(item->object, false))) { + // Update currentIndex of the attached DelegateModel object + // to the index the item has in the cache. + att->resetCurrentIndex(); + // emitChanges will emit both group-, and index changes to the application + att->emitChanges(); + } + + // Inform the view that the item is recycled. This will typically result + // in the view updating its own attached delegate item properties. + emit q_func()->itemReused(newModelIndex, item->object); +} + +void QQmlDelegateModelPrivate::drainReusableItemsPool(int maxPoolTime) +{ + m_reusableItemsPool.drain(maxPoolTime, [=](QQmlDelegateModelItem *cacheItem){ destroyCacheItem(cacheItem); }); +} + +void QQmlDelegateModel::drainReusableItemsPool(int maxPoolTime) +{ + d_func()->drainReusableItemsPool(maxPoolTime); +} + +int QQmlDelegateModel::poolSize() +{ + return d_func()->m_reusableItemsPool.size(); +} + +QQmlComponent *QQmlDelegateModelPrivate::resolveDelegate(int index) +{ + if (!m_delegateChooser) + return m_delegate; + + QQmlComponent *delegate = nullptr; + QQmlAbstractDelegateComponent *chooser = m_delegateChooser; + + do { + delegate = chooser->delegate(&m_adaptorModel, index); + chooser = qobject_cast<QQmlAbstractDelegateComponent *>(delegate); + } while (chooser); + + return delegate; +} + void QQmlDelegateModelPrivate::addCacheItem(QQmlDelegateModelItem *item, Compositor::iterator it) { m_cache.insert(it.cacheIndex, item); @@ -1124,13 +1214,33 @@ QObject *QQmlDelegateModelPrivate::object(Compositor::Group group, int index, QQ QQmlDelegateModelItem *cacheItem = it->inCache() ? m_cache.at(it.cacheIndex) : 0; - if (!cacheItem) { - cacheItem = m_adaptorModel.createItem(m_cacheMetaType, it.modelIndex()); - if (!cacheItem) + if (!cacheItem || !cacheItem->delegate) { + QQmlComponent *delegate = resolveDelegate(it.modelIndex()); + if (!delegate) return nullptr; - cacheItem->groups = it->flags; - addCacheItem(cacheItem, it); + if (!cacheItem) { + cacheItem = m_reusableItemsPool.takeItem(delegate, index); + if (cacheItem) { + // Move the pooled item back into the cache, update + // all related properties, and return the object (which + // has already been incubated, otherwise it wouldn't be in the pool). + addCacheItem(cacheItem, it); + reuseItem(cacheItem, index, it->flags); + cacheItem->referenceObject(); + return cacheItem->object; + } + + // Since we could't find an available item in the pool, we create a new one + cacheItem = m_adaptorModel.createItem(m_cacheMetaType, it.modelIndex()); + if (!cacheItem) + return nullptr; + + cacheItem->groups = it->flags; + addCacheItem(cacheItem, it); + } + + cacheItem->delegate = delegate; } // Bump the reference counts temporarily so neither the content data or the delegate object @@ -1145,18 +1255,7 @@ QObject *QQmlDelegateModelPrivate::object(Compositor::Group group, int index, QQ cacheItem->incubationTask->forceCompletion(); } } else if (!cacheItem->object) { - QQmlComponent *delegate = m_delegate; - if (m_delegateChooser) { - QQmlAbstractDelegateComponent *chooser = m_delegateChooser; - do { - delegate = chooser->delegate(&m_adaptorModel, index); - chooser = qobject_cast<QQmlAbstractDelegateComponent *>(delegate); - } while (chooser); - if (!delegate) - return nullptr; - } - - QQmlContext *creationContext = delegate->creationContext(); + QQmlContext *creationContext = cacheItem->delegate->creationContext(); cacheItem->scriptRef += 1; @@ -1185,10 +1284,10 @@ QObject *QQmlDelegateModelPrivate::object(Compositor::Group group, int index, QQ } } - QQmlComponentPrivate *cp = QQmlComponentPrivate::get(delegate); + QQmlComponentPrivate *cp = QQmlComponentPrivate::get(cacheItem->delegate); cp->incubateObject( cacheItem->incubationTask, - delegate, + cacheItem->delegate, m_context->engine(), ctxt, QQmlContextData::get(m_context)); @@ -2234,7 +2333,7 @@ void QQmlDelegateModelItem::Dispose() delete this; } -void QQmlDelegateModelItem::setModelIndex(int idx, int newRow, int newColumn) +void QQmlDelegateModelItem::setModelIndex(int idx, int newRow, int newColumn, bool alwaysEmit) { const int prevIndex = index; const int prevRow = row; @@ -2244,11 +2343,11 @@ void QQmlDelegateModelItem::setModelIndex(int idx, int newRow, int newColumn) row = newRow; column = newColumn; - if (idx != prevIndex) + if (idx != prevIndex || alwaysEmit) emit modelIndexChanged(); - if (row != prevRow) + if (row != prevRow || alwaysEmit) emit rowChanged(); - if (column != prevColumn) + if (column != prevColumn || alwaysEmit) emit columnChanged(); } @@ -3437,7 +3536,7 @@ QObject *QQmlPartsModel::object(int index, QQmlIncubator::IncubationMode incubat QObject *part = package->part(m_part); if (!part) return nullptr; - m_packaged.insertMulti(part, package); + m_packaged.insert(part, package); return part; } @@ -3451,11 +3550,11 @@ 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 = nullptr; + QQmlInstanceModel::ReleaseFlags flags; - QHash<QObject *, QQuickPackage *>::iterator it = m_packaged.find(item); + auto it = m_packaged.find(item); if (it != m_packaged.end()) { QQuickPackage *package = *it; QQmlDelegateModelPrivate *model = QQmlDelegateModelPrivate::get(m_model); @@ -3496,7 +3595,7 @@ QQmlIncubator::Status QQmlPartsModel::incubationStatus(int index) int QQmlPartsModel::indexOf(QObject *item, QObject *) const { - QHash<QObject *, QQuickPackage *>::const_iterator it = m_packaged.find(item); + auto it = m_packaged.find(item); if (it != m_packaged.end()) { if (QQmlDelegateModelItem *cacheItem = QQmlDelegateModelItem::dataForObject(*it)) return cacheItem->groupIndex(m_compositorGroup); @@ -3544,6 +3643,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..adb5f7008b 100644 --- a/src/qmlmodels/qqmldelegatemodel_p.h +++ b/src/qmlmodels/qqmldelegatemodel_p.h @@ -113,12 +113,15 @@ 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 reusableFlag = NotReusable) override; void cancel(int index) override; QVariant variantValue(int index, const QString &role) override; void setWatchedRoles(const QList<QByteArray> &roles) override; QQmlIncubator::Status incubationStatus(int index) override; + void drainReusableItemsPool(int maxPoolTime) override; + int poolSize() override; + int indexOf(QObject *object, QObject *objectContext) const override; QString filterGroup() const; @@ -141,6 +144,8 @@ Q_SIGNALS: void defaultGroupsChanged(); void rootIndexChanged(); void delegateChanged(); + void itemPooled(int index, QObject *object); + void itemReused(int index, QObject *object); private Q_SLOTS: void _q_itemsChanged(int index, int count, const QVector<int> &roles); diff --git a/src/qmlmodels/qqmldelegatemodel_p_p.h b/src/qmlmodels/qqmldelegatemodel_p_p.h index 06365a212f..a1c4555d01 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; @@ -129,7 +133,7 @@ public: int modelRow() const { return row; } int modelColumn() const { return column; } int modelIndex() const { return index; } - virtual void setModelIndex(int idx, int newRow, int newColumn); + virtual void setModelIndex(int idx, int newRow, int newColumn, bool alwaysEmit = false); virtual QV4::ReturnedValue get() { return QV4::QObjectWrapper::wrap(v4, this); } @@ -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 @@ -278,7 +293,7 @@ public: void requestMoreIfNecessary(); QObject *object(Compositor::Group group, int index, QQmlIncubator::IncubationMode incubationMode); - QQmlDelegateModel::ReleaseFlags release(QObject *object); + QQmlDelegateModel::ReleaseFlags release(QObject *object, QQmlInstanceModel::ReusableFlag reusable = QQmlInstanceModel::NotReusable); QVariant variantValue(Compositor::Group group, int index, const QString &name); void emitCreatedPackage(QQDMIncubationTask *incubationTask, QQuickPackage *package); void emitInitPackage(QQDMIncubationTask *incubationTask, QQuickPackage *package); @@ -290,9 +305,13 @@ public: void emitDestroyingItem(QObject *item) { Q_EMIT q_func()->destroyingItem(item); } void addCacheItem(QQmlDelegateModelItem *item, Compositor::iterator it); void removeCacheItem(QQmlDelegateModelItem *cacheItem); - + void destroyCacheItem(QQmlDelegateModelItem *cacheItem); void updateFilterGroup(); + void reuseItem(QQmlDelegateModelItem *item, int newModelIndex, int newGroups); + void drainReusableItemsPool(int maxPoolTime); + QQmlComponent *resolveDelegate(int index); + void addGroups(Compositor::iterator from, int count, Compositor::Group group, int groupFlags); void removeGroups(Compositor::iterator from, int count, Compositor::Group group, int groupFlags); void setGroups(Compositor::iterator from, int count, Compositor::Group group, int groupFlags); @@ -337,6 +356,7 @@ public: QQmlDelegateModelGroupEmitterList m_pendingParts; QList<QQmlDelegateModelItem *> m_cache; + QQmlReusableDelegateModelItemsPool m_reusableItemsPool; QList<QQDMIncubationTask *> m_finishedIncubating; QList<QByteArray> m_watchedRoles; @@ -380,7 +400,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; @@ -399,7 +419,7 @@ Q_SIGNALS: private: QQmlDelegateModel *m_model; - QHash<QObject *, QQuickPackage *> m_packaged; + QMultiHash<QObject *, QQuickPackage *> m_packaged; QString m_part; QString m_filterGroup; QList<QByteArray> m_watchedRoles; diff --git a/src/qmlmodels/qqmllistmodel.cpp b/src/qmlmodels/qqmllistmodel.cpp index e0a66e7170..f79910204a 100644 --- a/src/qmlmodels/qqmllistmodel.cpp +++ b/src/qmlmodels/qqmllistmodel.cpp @@ -1905,11 +1905,11 @@ void DynamicRoleModelNodeMetaObject::propertyWritten(int index) Here is an example that uses WorkerScript to periodically append the current time to a list model: - \snippet ../quick/threading/threadedlistmodel/timedisplay.qml 0 + \snippet ../../examples/quick/threading/threadedlistmodel/timedisplay.qml 0 The included file, \tt dataloader.mjs, looks like this: - \snippet ../quick/threading/threadedlistmodel/dataloader.mjs 0 + \snippet ../../examples/quick/threading/threadedlistmodel/dataloader.mjs 0 The timer in the main example sends messages to the worker script by calling \l WorkerScript::sendMessage(). When this message is received, diff --git a/src/qmlmodels/qqmlobjectmodel.cpp b/src/qmlmodels/qqmlobjectmodel.cpp index 8e7b0a9b5f..85e64cc2f9 100644 --- a/src/qmlmodels/qqmlobjectmodel.cpp +++ b/src/qmlmodels/qqmlobjectmodel.cpp @@ -265,7 +265,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); @@ -273,7 +273,7 @@ QQmlInstanceModel::ReleaseFlags QQmlObjectModel::release(QObject *item) if (!d->children[idx].deref()) return QQmlInstanceModel::Referenced; } - return nullptr; + return {}; } QVariant QQmlObjectModel::variantValue(int index, const QString &role) diff --git a/src/qmlmodels/qqmlobjectmodel_p.h b/src/qmlmodels/qqmlobjectmodel_p.h index 0aa818d724..6c68e55012 100644 --- a/src/qmlmodels/qqmlobjectmodel_p.h +++ b/src/qmlmodels/qqmlobjectmodel_p.h @@ -72,21 +72,29 @@ class Q_QMLMODELS_PRIVATE_EXPORT QQmlInstanceModel : public QObject QML_ANONYMOUS public: + enum ReusableFlag { + NotReusable, + Reusable + }; + virtual ~QQmlInstanceModel() {} - enum ReleaseFlag { Referenced = 0x01, Destroyed = 0x02 }; + enum ReleaseFlag { Referenced = 0x01, Destroyed = 0x02, Pooled = 0x04 }; Q_DECLARE_FLAGS(ReleaseFlags, ReleaseFlag) 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 a538ae4a1f..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); - return QQmlInstanceModel::Referenced; + 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,94 +262,22 @@ 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) { // Update the context properties index, row and column on // the delegate item, and inform the application about it. + // Note that we set alwaysEmit to true, to force all bindings + // to be reevaluated, even if the index didn't change (since + // the model can have changed size since last usage). + const bool alwaysEmit = true; const int newRow = m_adaptorModel.rowAt(newModelIndex); const int newColumn = m_adaptorModel.columnAt(newModelIndex); - item->setModelIndex(newModelIndex, newRow, newColumn); + item->setModelIndex(newModelIndex, newRow, newColumn, alwaysEmit); // Notify the application that all 'dynamic'/role-based context data has // changed as well (their getter function will use the updated index). diff --git a/src/qmlmodels/qqmltableinstancemodel_p.h b/src/qmlmodels/qqmltableinstancemodel_p.h index ce5a37bc98..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; @@ -103,6 +97,9 @@ public: bool isValid() const override { return true; } + bool canFetchMore() const { return m_adaptorModel.canFetchMore(); } + void fetchMore() { m_adaptorModel.fetchMore(); } + QVariant model() const; void setModel(const QVariant &model); @@ -112,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; @@ -142,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); @@ -150,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); |