diff options
author | Leander Beernaert <leander.beernaert@qt.io> | 2020-01-16 16:25:06 +0100 |
---|---|---|
committer | Leander Beernaert <leander.beernaert@qt.io> | 2020-01-16 16:25:06 +0100 |
commit | 1d333d3375874efb8d37df37dc5ef561573794ad (patch) | |
tree | 2d8c995f64c05c84c1fcceb2c5cb40fcae69855f /src/qmlmodels | |
parent | b106d86c433706928b0b0c206a0d9f831681e1bf (diff) | |
parent | e79a2658cde899d6ee11ec3c0d0a3768eb2c864b (diff) |
Merge remote-tracking branch 'origin/dev' into wip/cmake
Change-Id: I0c5b939c70bdb91ccdf7068784308416dcaa5736
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/qmlmodels.pro | 9 | ||||
-rw-r--r-- | src/qmlmodels/qqmladaptormodel.cpp | 76 | ||||
-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 | 64 | ||||
-rw-r--r-- | src/qmlmodels/qqmllistmodel_p_p.h | 15 | ||||
-rw-r--r-- | src/qmlmodels/qqmlmodelsmodule.cpp | 82 | ||||
-rw-r--r-- | src/qmlmodels/qqmlmodelsmodule_p.h | 7 | ||||
-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 |
17 files changed, 430 insertions, 337 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/qmlmodels.pro b/src/qmlmodels/qmlmodels.pro index 78bf579903..6abc5bf186 100644 --- a/src/qmlmodels/qmlmodels.pro +++ b/src/qmlmodels/qmlmodels.pro @@ -12,8 +12,7 @@ HEADERS += \ $$PWD/qtqmlmodelsglobal.h SOURCES += \ - $$PWD/qqmlchangeset.cpp \ - $$PWD/qqmlmodelsmodule.cpp + $$PWD/qqmlchangeset.cpp qtConfig(qml-object-model) { SOURCES += \ @@ -64,4 +63,10 @@ qtConfig(qml-delegate-model) { $$PWD/qquickpackage_p.h } +QMLTYPES_FILENAME = plugins.qmltypes +QMLTYPES_INSTALL_DIR = $$[QT_INSTALL_QML]/QtQml/Models.2 +QML_IMPORT_NAME = QtQml.Models +IMPORT_VERSION = 2.15 +CONFIG += qmltypes install_qmltypes install_metatypes + load(qt_module) diff --git a/src/qmlmodels/qqmladaptormodel.cpp b/src/qmlmodels/qqmladaptormodel.cpp index 012540244f..cf0d8fbb2f 100644 --- a/src/qmlmodels/qqmladaptormodel.cpp +++ b/src/qmlmodels/qqmladaptormodel.cpp @@ -199,12 +199,11 @@ public: RETURN_RESULT(scope.engine->throwTypeError(QStringLiteral("Not a valid DelegateModel object"))); const QQmlAdaptorModel *const model = static_cast<QQmlDMCachedModelData *>(o->d()->item)->type->model; - if (o->d()->item->index >= 0 && *model) { - const QAbstractItemModel * const aim = model->aim(); - RETURN_RESULT(QV4::Encode(aim->hasChildren(aim->index(o->d()->item->index, 0, model->rootIndex)))); - } else { - RETURN_RESULT(QV4::Encode(false)); + if (o->d()->item->index >= 0) { + if (const QAbstractItemModel *const aim = model->aim()) + RETURN_RESULT(QV4::Encode(aim->hasChildren(aim->index(o->d()->item->index, 0, model->rootIndex)))); } + RETURN_RESULT(QV4::Encode(false)); } @@ -400,23 +399,24 @@ public: bool hasModelChildren() const { - if (index >= 0 && *type->model) { - const QAbstractItemModel * const model = type->model->aim(); - return model->hasChildren(model->index(row, column, type->model->rootIndex)); - } else { - return false; + if (index >= 0) { + if (const QAbstractItemModel *const model = type->model->aim()) + return model->hasChildren(model->index(row, column, type->model->rootIndex)); } + return false; } QVariant value(int role) const override { - return type->model->aim()->index(row, column, type->model->rootIndex).data(role); + if (const QAbstractItemModel *aim = type->model->aim()) + return aim->index(row, column, type->model->rootIndex).data(role); + return QVariant(); } void setValue(int role, const QVariant &value) override { - type->model->aim()->setData( - type->model->aim()->index(row, column, type->model->rootIndex), value, role); + if (QAbstractItemModel *aim = type->model->aim()) + aim->setData(aim->index(row, column, type->model->rootIndex), value, role); } QV4::ReturnedValue get() override @@ -444,12 +444,16 @@ public: int rowCount(const QQmlAdaptorModel &model) const override { - return model.aim()->rowCount(model.rootIndex); + if (const QAbstractItemModel *aim = model.aim()) + return aim->rowCount(model.rootIndex); + return 0; } int columnCount(const QQmlAdaptorModel &model) const override { - return model.aim()->columnCount(model.rootIndex); + if (const QAbstractItemModel *aim = model.aim()) + return aim->columnCount(model.rootIndex); + return 0; } void cleanup(QQmlAdaptorModel &) const override @@ -464,39 +468,46 @@ public: dataType->initializeMetaType(model); } - QHash<QByteArray, int>::const_iterator it = roleNames.find(role.toUtf8()); - if (it != roleNames.end()) { - return model.aim()->index(model.rowAt(index), model.columnAt(index), model.rootIndex).data(*it); - } else if (role == QLatin1String("hasModelChildren")) { - return QVariant(model.aim()->hasChildren(model.aim()->index(model.rowAt(index), model.columnAt(index), model.rootIndex))); - } else { - return QVariant(); + if (const QAbstractItemModel *aim = model.aim()) { + QHash<QByteArray, int>::const_iterator it = roleNames.find(role.toUtf8()); + if (it != roleNames.end()) { + return aim->index(model.rowAt(index), model.columnAt(index), + model.rootIndex).data(*it); + } else if (role == QLatin1String("hasModelChildren")) { + return QVariant(aim->hasChildren(aim->index(model.rowAt(index), + model.columnAt(index), + model.rootIndex))); + } } + return QVariant(); } QVariant parentModelIndex(const QQmlAdaptorModel &model) const override { - return model - ? QVariant::fromValue(model.aim()->parent(model.rootIndex)) - : QVariant(); + if (const QAbstractItemModel *aim = model.aim()) + return QVariant::fromValue(aim->parent(model.rootIndex)); + return QVariant(); } QVariant modelIndex(const QQmlAdaptorModel &model, int index) const override { - return model - ? QVariant::fromValue(model.aim()->index(model.rowAt(index), model.columnAt(index), model.rootIndex)) - : QVariant(); + if (const QAbstractItemModel *aim = model.aim()) + return QVariant::fromValue(aim->index(model.rowAt(index), model.columnAt(index), + model.rootIndex)); + return QVariant(); } bool canFetchMore(const QQmlAdaptorModel &model) const override { - return model && model.aim()->canFetchMore(model.rootIndex); + if (const QAbstractItemModel *aim = model.aim()) + return aim->canFetchMore(model.rootIndex); + return false; } void fetchMore(QQmlAdaptorModel &model) const override { - if (model) - model.aim()->fetchMore(model.rootIndex); + if (QAbstractItemModel *aim = model.aim()) + aim->fetchMore(model.rootIndex); } QQmlDelegateModelItem *createItem( @@ -516,7 +527,8 @@ public: setModelDataType<QQmlDMAbstractItemModelData>(&builder, this); const QByteArray propertyType = QByteArrayLiteral("QVariant"); - const QHash<int, QByteArray> names = model.aim()->roleNames(); + const QAbstractItemModel *aim = model.aim(); + const QHash<int, QByteArray> names = aim ? aim->roleNames() : QHash<int, QByteArray>(); for (QHash<int, QByteArray>::const_iterator it = names.begin(), cend = names.end(); it != cend; ++it) { const int propertyId = propertyRoles.count(); propertyRoles.append(it.key()); 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..1bbe9360bb 100644 --- a/src/qmlmodels/qqmllistmodel.cpp +++ b/src/qmlmodels/qqmllistmodel.cpp @@ -269,19 +269,6 @@ const ListLayout::Role *ListLayout::getExistingRole(QV4::String *key) const return r; } -StringOrTranslation::StringOrTranslation(const QString &s) -{ - d.setFlag(); - setString(s); -} - -StringOrTranslation::StringOrTranslation(const QV4::CompiledData::Binding *binding) -{ - d.setFlag(); - clear(); - d = binding; -} - StringOrTranslation::~StringOrTranslation() { clear(); @@ -289,53 +276,48 @@ StringOrTranslation::~StringOrTranslation() void StringOrTranslation::setString(const QString &s) { - d.setFlag(); clear(); - QStringData *stringData = const_cast<QString &>(s).data_ptr(); - d = stringData; - if (stringData) - stringData->ref.ref(); + QString mutableString(s); + QString::DataPointer dataPointer = mutableString.data_ptr(); + arrayData = dataPointer->d_ptr(); + stringData = dataPointer->data(); + stringSize = mutableString.length(); + arrayData->ref(); } void StringOrTranslation::setTranslation(const QV4::CompiledData::Binding *binding) { - d.setFlag(); clear(); - d = binding; + this->binding = binding; } QString StringOrTranslation::toString(const QQmlListModel *owner) const { - if (d.isNull()) - return QString(); - if (d.isT1()) { - QStringDataPtr holder = { d.asT1() }; - holder.ptr->ref.ref(); - return QString(holder); + if (arrayData) { + arrayData->ref(); + return QString(QStringPrivate(arrayData, stringData, stringSize)); } if (!owner) return QString(); - return owner->m_compilationUnit->bindingValueAsString(d.asT2()); + return owner->m_compilationUnit->bindingValueAsString(binding); } QString StringOrTranslation::asString() const { - if (d.isNull()) + if (!arrayData) return QString(); - if (!d.isT1()) - return QString(); - QStringDataPtr holder = { d.asT1() }; - holder.ptr->ref.ref(); - return QString(holder); + arrayData->ref(); + return QString(QStringPrivate(arrayData, stringData, stringSize)); } void StringOrTranslation::clear() { - if (QStringData *strData = d.isT1() ? d.asT1() : nullptr) { - if (!strData->ref.deref()) - QStringData::deallocate(strData); - } - d = static_cast<QStringData *>(nullptr); + if (arrayData && !arrayData->deref()) + QTypedArrayData<ushort>::deallocate(arrayData); + arrayData = nullptr; + stringData = nullptr; + stringSize = 0; + binding = nullptr; } QObject *ListModel::getOrCreateModelObject(QQmlListModel *model, int elementIndex) @@ -1148,7 +1130,7 @@ int ListElement::setTranslationProperty(const ListLayout::Role &role, const QV4: void ListElement::setStringPropertyFast(const ListLayout::Role &role, const QString &s) { char *mem = getPropertyMemory(role); - new (mem) StringOrTranslation(s); + reinterpret_cast<StringOrTranslation *>(mem)->setString(s); } void ListElement::setDoublePropertyFast(const ListLayout::Role &role, double d) @@ -1905,11 +1887,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/qqmllistmodel_p_p.h b/src/qmlmodels/qqmllistmodel_p_p.h index 2ad5158050..f17004ed3b 100644 --- a/src/qmlmodels/qqmllistmodel_p_p.h +++ b/src/qmlmodels/qqmllistmodel_p_p.h @@ -252,18 +252,23 @@ private: struct StringOrTranslation { - explicit StringOrTranslation(const QString &s); - explicit StringOrTranslation(const QV4::CompiledData::Binding *binding); ~StringOrTranslation(); - bool isSet() const { return d.flag(); } - bool isTranslation() const { return d.isT2(); } + bool isSet() const { return binding || arrayData; } + bool isTranslation() const { return binding && !arrayData; } void setString(const QString &s); void setTranslation(const QV4::CompiledData::Binding *binding); QString toString(const QQmlListModel *owner) const; QString asString() const; private: void clear(); - QBiPointer<QStringData, const QV4::CompiledData::Binding> d; + + union { + ushort *stringData = nullptr; + const QV4::CompiledData::Binding *binding; + }; + + QTypedArrayData<ushort> *arrayData = nullptr; + uint stringSize = 0; }; /*! diff --git a/src/qmlmodels/qqmlmodelsmodule.cpp b/src/qmlmodels/qqmlmodelsmodule.cpp deleted file mode 100644 index 8bd9b179b3..0000000000 --- a/src/qmlmodels/qqmlmodelsmodule.cpp +++ /dev/null @@ -1,82 +0,0 @@ -/**************************************************************************** -** -** Copyright (C) 2016 Research In Motion. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of the QtQml module of the Qt Toolkit. -** -** $QT_BEGIN_LICENSE:LGPL$ -** Commercial License Usage -** Licensees holding valid commercial Qt licenses may use this file in -** accordance with the commercial license agreement provided with the -** Software or, alternatively, in accordance with the terms contained in -** a written agreement between you and The Qt Company. For licensing terms -** and conditions see https://www.qt.io/terms-conditions. For further -** information use the contact form at https://www.qt.io/contact-us. -** -** GNU Lesser General Public License Usage -** Alternatively, this file may be used under the terms of the GNU Lesser -** General Public License version 3 as published by the Free Software -** Foundation and appearing in the file LICENSE.LGPL3 included in the -** packaging of this file. Please review the following information to -** ensure the GNU Lesser General Public License version 3 requirements -** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. -** -** GNU General Public License Usage -** Alternatively, this file may be used under the terms of the GNU -** General Public License version 2.0 or (at your option) the GNU General -** Public license version 3 or any later version approved by the KDE Free -** Qt Foundation. The licenses are as published by the Free Software -** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 -** included in the packaging of this file. Please review the following -** information to ensure the GNU General Public License requirements will -** be met: https://www.gnu.org/licenses/gpl-2.0.html and -** https://www.gnu.org/licenses/gpl-3.0.html. -** -** $QT_END_LICENSE$ -** -****************************************************************************/ - -#include "qqmlmodelsmodule_p.h" -#include <private/qtqmlmodelsglobal_p.h> - -#if QT_CONFIG(qml_list_model) -#include <private/qqmllistmodel_p.h> -#include <private/qqmllistmodelworkeragent_p.h> -#endif -#if QT_CONFIG(qml_delegate_model) -#include <private/qqmlabstractdelegatecomponent_p.h> -#include <private/qqmldelegatemodel_p.h> -#include <private/qquickpackage_p.h> -#include <private/qqmlcomponentattached_p.h> -#endif -#if QT_CONFIG(qml_object_model) -#include <private/qqmlobjectmodel_p.h> -#include <private/qqmlinstantiator_p.h> -#endif - -QT_BEGIN_NAMESPACE - -void QQmlModelsModule::defineModule() -{ - const char uri[] = "QtQml.Models"; - -#if QT_CONFIG(qml_list_model) - qmlRegisterTypesAndRevisions<QQmlListElement, QQmlListModel, QQmlListModelWorkerAgent>(uri, 2); -#endif -#if QT_CONFIG(qml_delegate_model) - // TODO: Get rid of these. It's called DelegateModel and DelegateModelGroup these days. - qmlRegisterType<QQmlDelegateModel>(uri, 2, 0, "VisualDataModel"); - qmlRegisterType<QQmlDelegateModelGroup>(uri, 2, 0, "VisualDataGroup"); - - qmlRegisterTypesAndRevisions<QQmlDelegateModel, QQmlDelegateModelGroup, QQuickPackage, QQmlAbstractDelegateComponent>(uri, 2); -#endif -#if QT_CONFIG(qml_object_model) - qmlRegisterTypesAndRevisions<QQmlObjectModel, QQmlInstantiator, QQmlInstanceModel>(uri, 2); -#endif -#if QT_CONFIG(itemmodel) - qmlRegisterTypesAndRevisions<QItemSelectionModelForeign>(uri, 2); -#endif -} - -QT_END_NAMESPACE diff --git a/src/qmlmodels/qqmlmodelsmodule_p.h b/src/qmlmodels/qqmlmodelsmodule_p.h index c697b08bf7..e3e43f3922 100644 --- a/src/qmlmodels/qqmlmodelsmodule_p.h +++ b/src/qmlmodels/qqmlmodelsmodule_p.h @@ -61,13 +61,6 @@ QT_BEGIN_NAMESPACE -class Q_QMLMODELS_PRIVATE_EXPORT QQmlModelsModule -{ -public: - static void defineModule(); - static void defineLabsModule(); -}; - #if QT_CONFIG(itemmodel) struct QItemSelectionModelForeign { 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); |