diff options
Diffstat (limited to 'src/qmlmodels/qqmldelegatemodel.cpp')
-rw-r--r-- | src/qmlmodels/qqmldelegatemodel.cpp | 434 |
1 files changed, 383 insertions, 51 deletions
diff --git a/src/qmlmodels/qqmldelegatemodel.cpp b/src/qmlmodels/qqmldelegatemodel.cpp index d5b30df45b..cee096035a 100644 --- a/src/qmlmodels/qqmldelegatemodel.cpp +++ b/src/qmlmodels/qqmldelegatemodel.cpp @@ -38,17 +38,16 @@ ****************************************************************************/ #include "qqmldelegatemodel_p_p.h" -#include "qqmldelegatecomponent_p.h" #include <QtQml/qqmlinfo.h> +#include <private/qqmlabstractdelegatecomponent_p.h> #include <private/qquickpackage_p.h> #include <private/qmetaobjectbuilder_p.h> #include <private/qqmladaptormodel_p.h> #include <private/qqmlchangeset_p.h> #include <private/qqmlengine_p.h> #include <private/qqmlcomponent_p.h> -#include <private/qqmlincubator_p.h> #include <private/qv4value_p.h> #include <private/qv4functionobject_p.h> @@ -56,6 +55,8 @@ QT_BEGIN_NAMESPACE +Q_LOGGING_CATEGORY(lcItemViewDelegateRecycling, "qt.quick.itemview.delegaterecycling") + class QQmlDelegateModelItem; namespace QV4 { @@ -206,6 +207,9 @@ QQmlDelegateModelPrivate::~QQmlDelegateModelPrivate() { qDeleteAll(m_finishedIncubating); + // Free up all items in the pool + drainReusableItemsPool(0); + if (m_cacheMetaType) m_cacheMetaType->release(); } @@ -265,13 +269,23 @@ QQmlDelegateModel::~QQmlDelegateModel() Q_ASSERT(cacheItem->contextData->refCount == 1); cacheItem->contextData = nullptr; cacheItem->scriptRef -= 1; + } else if (cacheItem->incubationTask) { + // Both the incubationTask and the object may hold a scriptRef, + // but if both are present, only one scriptRef is held in total. + cacheItem->scriptRef -= 1; } + cacheItem->groups &= ~Compositor::UnresolvedFlag; cacheItem->objectRef = 0; + + if (cacheItem->incubationTask) { + d->releaseIncubator(cacheItem->incubationTask); + cacheItem->incubationTask->vdm = nullptr; + cacheItem->incubationTask = nullptr; + } + if (!cacheItem->isReferenced()) delete cacheItem; - else if (cacheItem->incubationTask) - cacheItem->incubationTask->vdm = nullptr; } } @@ -589,36 +603,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; } @@ -626,7 +651,7 @@ QQmlDelegateModel::ReleaseFlags QQmlDelegateModel::release(QObject *item) void QQmlDelegateModel::cancel(int index) { Q_D(QQmlDelegateModel); - if (!d->m_delegate || index < 0 || index >= d->m_compositor.count(d->m_compositorGroup)) { + if (index < 0 || index >= d->m_compositor.count(d->m_compositorGroup)) { qWarning() << "DelegateModel::cancel: index out range" << index << d->m_compositor.count(d->m_compositorGroup); return; } @@ -716,7 +741,7 @@ QQmlListProperty<QQmlDelegateModelGroup> QQmlDelegateModel::groups() QQmlDelegateModelPrivate::group_append, QQmlDelegateModelPrivate::group_count, QQmlDelegateModelPrivate::group_at, - nullptr); + nullptr, nullptr, nullptr); } /*! @@ -892,6 +917,125 @@ static bool isDoneIncubating(QQmlIncubator::Status status) return status == QQmlIncubator::Ready || status == QQmlIncubator::Error; } +PropertyUpdater::PropertyUpdater(QObject *parent) : + QObject(parent) {} + +void PropertyUpdater::doUpdate() +{ + auto sender = QObject::sender(); + auto mo = sender->metaObject(); + auto signalIndex = QObject::senderSignalIndex(); + ++updateCount; + auto property = mo->property(changeSignalIndexToPropertyIndex[signalIndex]); + // we synchronize between required properties and model rolenames by name + // that's why the QQmlProperty and the metaobject property must have the same name + QQmlProperty qmlProp(parent(), QString::fromLatin1(property.name())); + qmlProp.write(property.read(QObject::sender())); +} + +void PropertyUpdater::breakBinding() +{ + auto it = senderToConnection.find(QObject::senderSignalIndex()); + if (it == senderToConnection.end()) + return; + if (updateCount == 0) { + QObject::disconnect(*it); + senderToConnection.erase(it); + QQmlError warning; + if (auto context = qmlContext(QObject::sender())) + warning.setUrl(context->baseUrl()); + else + return; + auto signalName = QString::fromLatin1(QObject::sender()->metaObject()->method(QObject::senderSignalIndex()).name()); + signalName.chop(sizeof("changed")-1); + QString propName = signalName; + propName[0] = propName[0].toLower(); + warning.setDescription(QString::fromUtf8("Writing to \"%1\" broke the binding to the underlying model").arg(propName)); + qmlWarning(this, warning); + } else { + --updateCount; + } +} + +void QQDMIncubationTask::initializeRequiredProperties(QQmlDelegateModelItem *modelItemToIncubate, QObject *object) +{ + auto incubatorPriv = QQmlIncubatorPrivate::get(this); + 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(); + + auto qmlMetaObject = modelItemToIncubate->metaObject(); + // if a required property was not in the model, it might still be a static property of the + // QQmlDelegateModelItem or one of its derived classes this is the case for index, row, + // column, model and more + // the most derived subclass of QQmlDelegateModelItem is QQmlDMAbstractModelData at depth 2, + // so 4 should be plenty + QVarLengthArray<QPair<const QMetaObject *, QObject *>, 4> mos; + // we first check the dynamic meta object for properties originating from the model + // contains abstractitemmodelproperties + mos.push_back(qMakePair(qmlMetaObject, modelItemToIncubate)); + auto delegateModelItemSubclassMO = qmlMetaObject->superClass(); + mos.push_back(qMakePair(delegateModelItemSubclassMO, modelItemToIncubate)); + + while (strcmp(delegateModelItemSubclassMO->className(), + modelItemToIncubate->staticMetaObject.className())) { + delegateModelItemSubclassMO = delegateModelItemSubclassMO->superClass(); + mos.push_back(qMakePair(delegateModelItemSubclassMO, modelItemToIncubate)); + } + if (proxiedObject) + mos.push_back(qMakePair(proxiedObject->metaObject(), proxiedObject)); + + auto updater = new PropertyUpdater(object); + 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()) + continue; + auto propName = QString::fromUtf8(prop.name()); + bool wasInRequired = false; + QQmlProperty componentProp = QQmlComponentPrivate::removePropertyFromRequired( + object, propName, requiredProperties, &wasInRequired); + // 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(itemOrProxy, changeSignal, + updater, updateSlot); + updater->changeSignalIndexToPropertyIndex[changeSignal.methodIndex()] = i; + auto propIdx = object->metaObject()->indexOfProperty(propName.toUtf8()); + 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(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(itemOrProxy)); + } + } + } else { + modelItemToIncubate->contextData->contextObject = modelItemToIncubate; + if (proxiedObject) + proxyContext->contextObject = proxiedObject; + } +} + void QQDMIncubationTask::statusChanged(Status status) { if (vdm) { @@ -923,6 +1067,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); @@ -992,6 +1201,7 @@ void QQDMIncubationTask::setInitialState(QObject *o) void QQmlDelegateModelPrivate::setInitialState(QQDMIncubationTask *incubationTask, QObject *o) { QQmlDelegateModelItem *cacheItem = incubationTask->incubating; + incubationTask->initializeRequiredProperties(incubationTask->incubating, o); cacheItem->object = o; if (QQuickPackage *package = qmlobject_cast<QQuickPackage *>(cacheItem->object)) @@ -1010,16 +1220,38 @@ QObject *QQmlDelegateModelPrivate::object(Compositor::Group group, int index, QQ } Compositor::iterator it = m_compositor.find(group, index); + const auto flags = it->flags; + const auto modelIndex = it.modelIndex(); 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(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, 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, modelIndex); + if (!cacheItem) + return nullptr; + + cacheItem->groups = flags; + addCacheItem(cacheItem, it); + } + + cacheItem->delegate = delegate; } // Bump the reference counts temporarily so neither the content data or the delegate object @@ -1034,18 +1266,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, cacheItem->index); - chooser = qobject_cast<QQmlAbstractDelegateComponent *>(delegate); - } while (chooser); - if (!delegate) - return nullptr; - } - - QQmlContext *creationContext = delegate->creationContext(); + QQmlContext *creationContext = cacheItem->delegate->creationContext(); cacheItem->scriptRef += 1; @@ -1058,7 +1279,6 @@ QObject *QQmlDelegateModelPrivate::object(Compositor::Group group, int index, QQ QQmlContextData *ctxt = new QQmlContextData; ctxt->setParent(QQmlContextData::get(creationContext ? creationContext : m_context.data())); - ctxt->contextObject = cacheItem; cacheItem->contextData = ctxt; if (m_adaptorModel.hasProxyObject()) { @@ -1067,17 +1287,18 @@ QObject *QQmlDelegateModelPrivate::object(Compositor::Group group, int index, QQ ctxt = new QQmlContextData; ctxt->setParent(cacheItem->contextData, /*stronglyReferencedByParent*/true); QObject *proxied = proxy->proxiedObject(); - ctxt->contextObject = proxied; + cacheItem->incubationTask->proxiedObject = proxied; + cacheItem->incubationTask->proxyContext = ctxt; // We don't own the proxied object. We need to clear it if it goes away. QObject::connect(proxied, &QObject::destroyed, cacheItem, &QQmlDelegateModelItem::childContextObjectDestroyed); } } - 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)); @@ -1801,7 +2022,7 @@ bool QQmlDelegateModelPrivate::insert(Compositor::insert_iterator &before, const propertyName = it.nextPropertyNameAsString(v); if (propertyName->isNull()) break; - cacheItem->setValue(propertyName->toQStringNoThrow(), scope.engine->toVariant(v, QVariant::Invalid)); + cacheItem->setValue(propertyName->toQStringNoThrow(), scope.engine->toVariant(v, QMetaType::UnknownType)); } cacheItem->groups = groups | Compositor::UnresolvedFlag | Compositor::CacheFlag; @@ -2057,9 +2278,10 @@ void QV4::Heap::QQmlDelegateModelItemObject::destroy() } -QQmlDelegateModelItem::QQmlDelegateModelItem(QQmlDelegateModelItemMetaType *metaType, - QQmlAdaptorModel::Accessors *accessor, - int modelIndex, int row, int column) +QQmlDelegateModelItem::QQmlDelegateModelItem( + const QQmlRefPointer<QQmlDelegateModelItemMetaType> &metaType, + QQmlAdaptorModel::Accessors *accessor, + int modelIndex, int row, int column) : v4(metaType->v4Engine) , metaType(metaType) , contextData(nullptr) @@ -2075,8 +2297,6 @@ QQmlDelegateModelItem::QQmlDelegateModelItem(QQmlDelegateModelItemMetaType *meta , row(row) , column(column) { - metaType->addref(); - if (accessor->propertyCache) { // The property cache in the accessor is common for all the model // items in the model it wraps. It describes available model roles, @@ -2105,9 +2325,6 @@ QQmlDelegateModelItem::~QQmlDelegateModelItem() else delete incubationTask; } - - metaType->release(); - } void QQmlDelegateModelItem::Dispose() @@ -2171,6 +2388,8 @@ QQmlDelegateModelItem *QQmlDelegateModelItem::dataForObject(QObject *object) { QQmlData *d = QQmlData::get(object); QQmlContextData *context = d ? d->context : nullptr; + if (context && context->hasExtraObject) + return qobject_cast<QQmlDelegateModelItem *>(context->extraObject); for (context = context ? context->parent : nullptr; context; context = context->parent) { if (QQmlDelegateModelItem *cacheItem = qobject_cast<QQmlDelegateModelItem *>( context->contextObject)) { @@ -3182,8 +3401,6 @@ void QQmlDelegateModelGroup::move(QQmlV4Function *args) Each index is adjusted for previous changes with all removed items preceding any inserted items. - - The corresponding handler is \c onChanged. */ //============================================================================ @@ -3324,7 +3541,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; } @@ -3338,11 +3555,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); @@ -3383,7 +3600,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); @@ -3431,6 +3648,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 |