From 6bf9a88bac53ea0942b89bfa628294ad33aec1ed Mon Sep 17 00:00:00 2001 From: Pierre Lamot Date: Mon, 20 Dec 2021 17:08:03 +0100 Subject: [PATCH] qt: use diff util algorithm to perform partial update of the model this allows to submit to the view only the changes made on the model when the database changes instead of invalidating and resetting the whole view. data cache is changed from a windowed view over the data, to a model where data is loaded from the start and loaded by chunk (append to the cache), when data is invalidated the data is reloaded in background and the difference (insertions and deletions) are propagated to the view. --- modules/gui/qt/medialibrary/mlbasemodel.cpp | 124 ++++--- modules/gui/qt/medialibrary/mlbasemodel.hpp | 9 +- modules/gui/qt/medialibrary/mllistcache.cpp | 390 ++++++++++++++++---- modules/gui/qt/medialibrary/mllistcache.hpp | 103 +++++- 4 files changed, 489 insertions(+), 137 deletions(-) diff --git a/modules/gui/qt/medialibrary/mlbasemodel.cpp b/modules/gui/qt/medialibrary/mlbasemodel.cpp index 888ffa6ff1..b16386b1e2 100644 --- a/modules/gui/qt/medialibrary/mlbasemodel.cpp +++ b/modules/gui/qt/medialibrary/mlbasemodel.cpp @@ -48,7 +48,7 @@ void MLBaseModel::sortByColumn(QByteArray name, Qt::SortOrder order) { m_sort_desc = (order == Qt::SortOrder::DescendingOrder); m_sort = nameToCriteria(name); - clear(); + resetCache(); } //------------------------------------------------------------------------------------------------- @@ -169,7 +169,7 @@ QVariant MLBaseModel::data(const QModelIndex &index, int role) const void MLBaseModel::onResetRequested() { - clear(); + invalidateCache(); } void MLBaseModel::onLocalSizeAboutToBeChanged(size_t size) @@ -185,14 +185,6 @@ void MLBaseModel::onLocalSizeChanged(size_t size) emit countChanged(size); } -void MLBaseModel::onLocalDataChanged(size_t offset, size_t count) -{ - assert(count); - auto first = index(offset); - auto last = index(offset + count - 1); - emit dataChanged(first, last); -} - void MLBaseModel::onVlcMlEvent(const MLEvent &event) { switch(event.i_type) @@ -268,14 +260,14 @@ MLItemId MLBaseModel::parentId() const void MLBaseModel::setParentId(MLItemId parentId) { m_parent = parentId; - clear(); + invalidateCache(); emit parentIdChanged(); } void MLBaseModel::unsetParentId() { m_parent = MLItemId(); - clear(); + invalidateCache(); emit parentIdChanged(); } @@ -305,7 +297,7 @@ void MLBaseModel::setSearchPattern( const QString& pattern ) return; m_search_pattern = patternToApply; - clear(); + invalidateCache(); } Qt::SortOrder MLBaseModel::getSortOrder() const @@ -316,7 +308,7 @@ Qt::SortOrder MLBaseModel::getSortOrder() const void MLBaseModel::setSortOder(Qt::SortOrder order) { m_sort_desc = (order == Qt::SortOrder::DescendingOrder); - clear(); + resetCache(); emit sortOrderChanged(); } @@ -328,14 +320,14 @@ const QString MLBaseModel::getSortCriteria() const void MLBaseModel::setSortCriteria(const QString& criteria) { m_sort = nameToCriteria(criteria.toUtf8()); - clear(); + resetCache(); emit sortCriteriaChanged(); } void MLBaseModel::unsetSortCriteria() { m_sort = VLC_ML_SORTING_DEFAULT; - clear(); + resetCache(); emit sortCriteriaChanged(); } @@ -349,14 +341,6 @@ int MLBaseModel::rowCount(const QModelIndex &parent) const return m_cache->count(); } -void MLBaseModel::clear() -{ - beginResetModel(); - invalidateCache(); - endResetModel(); - emit countChanged( static_cast(0) ); -} - QVariant MLBaseModel::getIdForIndex(QVariant index) const { MLItem* obj = nullptr; @@ -417,26 +401,78 @@ unsigned MLBaseModel::getCount() const return static_cast(m_cache->count()); } + +void MLBaseModel::onCacheDataChanged(int first, int last) +{ + emit dataChanged(index(first), index(last)); +} + +void MLBaseModel::onCacheBeginInsertRows(int first, int last) +{ + emit beginInsertRows({}, first, last); +} + +void MLBaseModel::onCacheBeginRemoveRows(int first, int last) +{ + emit beginRemoveRows({}, first, last); +} + +void MLBaseModel::onCacheBeginMoveRows(int first, int last, int destination) +{ + emit beginMoveRows({}, first, last, {}, destination); +} + void MLBaseModel::validateCache() const { if (m_cache) return; + if (!m_mediaLib) + return; + auto loader = createLoader(); - m_cache.reset(new MLListCache(m_mediaLib, loader.release())); - connect(&*m_cache, &MLListCache::localSizeAboutToBeChanged, + m_cache = std::make_unique(m_mediaLib, std::move(loader)); + connect(m_cache.get(), &MLListCache::localSizeAboutToBeChanged, this, &MLBaseModel::onLocalSizeAboutToBeChanged); - connect(&*m_cache, &MLListCache::localSizeChanged, + connect(m_cache.get(), &MLListCache::localSizeChanged, this, &MLBaseModel::onLocalSizeChanged); - connect(&*m_cache, &MLListCache::localDataChanged, - this, &MLBaseModel::onLocalDataChanged); + + connect(m_cache.get(), &MLListCache::localDataChanged, + this, &MLBaseModel::onCacheDataChanged); + + connect(m_cache.get(), &MLListCache::beginInsertRows, + this, &MLBaseModel::onCacheBeginInsertRows); + connect(m_cache.get(), &MLListCache::endInsertRows, + this, &MLBaseModel::endInsertRows); + + connect(m_cache.get(), &MLListCache::beginRemoveRows, + this, &MLBaseModel::onCacheBeginRemoveRows); + connect(m_cache.get(), &MLListCache::endRemoveRows, + this, &MLBaseModel::endRemoveRows); + + connect(m_cache.get(), &MLListCache::endMoveRows, + this, &MLBaseModel::endMoveRows); + connect(m_cache.get(), &MLListCache::beginMoveRows, + this, &MLBaseModel::onCacheBeginMoveRows); m_cache->initCount(); } + +void MLBaseModel::resetCache() +{ + beginResetModel(); + m_cache.reset(); + endResetModel(); + validateCache(); +} + void MLBaseModel::invalidateCache() { - m_cache.reset(); + if (m_cache) + m_cache->invalidate(); + else + validateCache(); } //------------------------------------------------------------------------------------------------- @@ -445,9 +481,12 @@ MLItem *MLBaseModel::item(int signedidx) const { validateCache(); + if (!m_cache) + return nullptr; + ssize_t count = m_cache->count(); - if (count == COUNT_UNINITIALIZED || signedidx < 0 || signedidx >= count) + if (count == 0 || signedidx < 0 || signedidx >= count) return nullptr; unsigned int idx = static_cast(signedidx); @@ -468,6 +507,9 @@ MLItem *MLBaseModel::itemCache(int signedidx) const { unsigned int idx = static_cast(signedidx); + if (!m_cache) + return nullptr; + const std::unique_ptr *item = m_cache->get(idx); if (!item) @@ -514,13 +556,7 @@ void MLBaseModel::updateItemInCache(const MLItemId& mlid) if (!ctx.item) return; - int row = m_cache->updateItem(std::move(ctx.item)); - if (row != -1) - { - //notify every roles - emit dataChanged(index(row), index(row)); - } - //otherwise don't notify, it will be updated when the cache reload the corresponding segment + m_cache->updateItem(std::move(ctx.item)); }); } @@ -531,17 +567,7 @@ void MLBaseModel::deleteItemInCache(const MLItemId& mlid) emit resetRequested(); return; } - int row = m_cache->deleteItem(mlid); - if (row < 0) - { - // items isn't in cache, we don't know if it's before or after our cache, request a reset - emit resetRequested(); - } - else - { - beginRemoveRows({}, row, row); - endRemoveRows(); - } + m_cache->deleteItem(mlid); } //------------------------------------------------------------------------------------------------- diff --git a/modules/gui/qt/medialibrary/mlbasemodel.hpp b/modules/gui/qt/medialibrary/mlbasemodel.hpp index df0ae28784..5ca9540c48 100644 --- a/modules/gui/qt/medialibrary/mlbasemodel.hpp +++ b/modules/gui/qt/medialibrary/mlbasemodel.hpp @@ -91,13 +91,11 @@ protected slots: void onResetRequested(); void onLocalSizeAboutToBeChanged(size_t size); void onLocalSizeChanged(size_t size); - void onLocalDataChanged(size_t index, size_t count); private: static void onVlcMlEvent( void* data, const vlc_ml_event_t* event ); protected: - virtual void clear(); virtual vlc_ml_sorting_criteria_t roleToCriteria(int role) const = 0; static QString getFirstSymbol(QString str); virtual vlc_ml_sorting_criteria_t nameToCriteria(QByteArray) const { @@ -109,6 +107,7 @@ protected: } void validateCache() const; + void resetCache(); void invalidateCache(); MLItem *item(int signedidx) const; @@ -170,6 +169,12 @@ public: int rowCount(const QModelIndex &parent = {}) const override; virtual unsigned int getCount() const; +private: + void onCacheDataChanged(int first, int last); + void onCacheBeginInsertRows(int first, int last); + void onCacheBeginRemoveRows(int first, int last); + void onCacheBeginMoveRows(int first, int last, int destination); + protected: MLItemId m_parent; diff --git a/modules/gui/qt/medialibrary/mllistcache.cpp b/modules/gui/qt/medialibrary/mllistcache.cpp index 78c7a7ff09..7ff6930d9c 100644 --- a/modules/gui/qt/medialibrary/mllistcache.cpp +++ b/modules/gui/qt/medialibrary/mllistcache.cpp @@ -17,147 +17,403 @@ *****************************************************************************/ #include "mllistcache.hpp" +#include "vlc_diffutil.h" + +namespace { + +//callbacks for the diff algorithm to access the data + +uint32_t cacheDataLength(const void* data) +{ + auto list = static_cast*>(data); + assert(list); + return list->size(); +} + +bool cacheDataCompare(const void* dataOld, uint32_t oldIndex, const void* dataNew, uint32_t newIndex) +{ + auto listOld = static_cast*>(dataOld); + auto listNew = static_cast*>(dataNew); + assert(listOld); + assert(listNew); + assert(oldIndex < listOld->size()); + assert(newIndex < listNew->size()); + return listOld->at(oldIndex)->getId() == listNew->at(newIndex)->getId(); +} + +} + +MLListCache::MLListCache(MediaLib* medialib, std::unique_ptr>&& loader, size_t chunkSize) + : m_medialib(medialib) + , m_loader(loader.release()) + , m_chunkSize(chunkSize) +{ + assert(medialib); +} const MLListCache::ItemType* MLListCache::get(size_t index) const { - assert(m_total_count >= 0 && index < static_cast(m_total_count)); - if (index < m_offset || index >= m_offset + m_list.size()) + //the view may access the model while we're updating it + //everything before m_partialIndex is updated in the new model, + //everything after m_partialIndex is still valid in the old model + if (unlikely(m_oldData)) + { + if (m_cachedData) + { + if (index >= m_partialLoadedCount) + return nullptr; + else if (index >= m_partialIndex) + return &m_oldData->list.at(index - m_partialIndex + m_partialX); + else + return &m_cachedData->list.at(index); + } + else + { + if (index >= m_oldData->loadedCount) + return nullptr; + + return &m_oldData->list.at(index); + } + } + + if (!m_cachedData) return nullptr; - return &m_list[index - m_offset]; + if (index + 1 > m_cachedData->loadedCount) + return nullptr; + + return &m_cachedData->list.at(index); } const MLListCache::ItemType* MLListCache::find(const std::function &&f, int *index) const { - if (m_total_count <= 0) + + if (!m_cachedData || m_cachedData->totalCount == 0) return nullptr; - for (auto iter = std::begin(m_list); iter != std::end(m_list); ++iter) - { - if (f(*iter)) - { - if (index) - *index = m_offset + std::distance(std::begin(m_list), iter); + auto it = std::find_if(m_cachedData->list.cbegin(), m_cachedData->list.cend(), f); + if (it == m_cachedData->list.cend()) + return nullptr; - return &(*iter); - } - } + if (index) + *index = std::distance(m_cachedData->list.cbegin(), it); - return nullptr; + return &(*it); } int MLListCache::updateItem(std::unique_ptr&& newItem) { + //we can't update an item locally while the model has pending updates + //no worry, we'll receive the update once the actual model notifies us + if (m_oldData) + return -1; + MLItemId mlid = newItem->getId(); - auto it = std::find_if(m_list.begin(), m_list.end(), [mlid](const ItemType& item) { + //this may be inneficient to look at every items, maybe we can have a hashmap to access the items by id + auto it = std::find_if(m_cachedData->list.begin(), m_cachedData->list.end(), [mlid](const ItemType& item) { return (item->getId() == mlid); }); //item not found - if (it == m_list.end()) + if (it == m_cachedData->list.end()) return -1; - int pos = m_offset + std::distance(m_list.begin(), it); + int pos = std::distance(m_cachedData->list.begin(), it); *it = std::move(newItem); + emit localDataChanged(pos, pos); return pos; } int MLListCache::deleteItem(const MLItemId& mlid) { - auto it = std::find_if(m_list.begin(), m_list.end(), [mlid](const ItemType& item) { + //we can't update an item locally while the model has pending updates + //no worry, we'll receive the update once the actual model notifies us + if (m_oldData) + return -1; + + auto it = std::find_if(m_cachedData->list.begin(), m_cachedData->list.end(), [mlid](const ItemType& item) { return (item->getId() == mlid); }); + //item not found - if (it == m_list.end()) + if (it == m_cachedData->list.end()) return -1; - int pos = m_offset + std::distance(m_list.begin(), it); - m_list.erase(it, it); - m_total_count -= 1; + + int pos = std::distance(m_cachedData->list.begin(), it); + + emit beginRemoveRows(pos, pos); + m_cachedData->list.erase(it, it+1); + size_t delta = m_cachedData->loadedCount - m_cachedData->list.size(); + m_cachedData->loadedCount -= delta; + m_cachedData->totalCount -= delta; + emit endRemoveRows(); + emit localSizeChanged(m_cachedData->totalCount); + return pos; } ssize_t MLListCache::count() const { - return m_total_count; + if (!m_cachedData) + return -1; + return m_cachedData->totalCount; } void MLListCache::initCount() { - assert(!m_countRequested); - asyncCount(); + assert(!m_cachedData); + asyncCountAndLoad(); } void MLListCache::refer(size_t index) { - if (m_total_count == -1 || index >= static_cast(m_total_count)) - { - /* - * The request is incompatible with the total count of the list. - * - * Either the count is not retrieved yet, or the content has changed in - * the loader source. - */ - return; - } + //m_maxReferedIndex is in terms of number of item, not the index + index++; - /* index outside the known portion of the list */ - if (!m_lastRangeRequested.contains(index)) + if (!m_cachedData) + return; + + if (index > m_cachedData->totalCount) + return; + + /* index is already in the list */ + if (index <= m_cachedData->loadedCount) + return; + + if (index > m_maxReferedIndex) { - /* FIXME bad heuristic if the interval of visible items crosses a cache - * page boundary */ - size_t offset = index - index % m_chunkSize; - size_t count = qMin(m_total_count - offset, m_chunkSize); - asyncLoad(offset, count); + m_maxReferedIndex = index; + if (!m_appendTask && !m_countTask) + { + if (m_cachedData) + asyncFetchMore(); + else + asyncCountAndLoad(); + } } } -void MLListCache::asyncCount() +void MLListCache::invalidate() { - assert(!m_countTask); - - m_countRequested = true; - struct Ctx { - ssize_t totalCount; - }; - m_medialib->runOnMLThread(this, - //ML thread - [loader = m_loader](vlc_medialibrary_t* ml, Ctx& ctx) + if (m_cachedData) + { + if (m_cachedData && !m_oldData) { + m_oldData = std::move(m_cachedData); + m_partialX = 0; + } + else + m_cachedData.reset(); + } + + if (m_appendTask) + { + m_medialib->cancelMLTask(this, m_appendTask); + m_appendTask = 0; + } + + if (m_countTask) + { + m_needReload = true; + } + else + { + asyncCountAndLoad(); + } +} + +void MLListCache::partialUpdate() +{ + //compare the model the user have and the updated model + //and notify for changes + + vlc_diffutil_callback_t diffOp = { + cacheDataLength, + cacheDataLength, + cacheDataCompare + }; + + diffutil_snake_t* snake = vlc_diffutil_build_snake(&diffOp, &m_oldData->list, &m_cachedData->list); + vlc_diffutil_changelist_t* changes = vlc_diffutil_build_change_list( + snake, &diffOp, &m_oldData->list, &m_cachedData->list, + VLC_DIFFUTIL_RESULT_AGGREGATE); + + m_partialIndex = 0; + m_partialLoadedCount = m_oldData->loadedCount; + size_t partialTotalCount = m_oldData->totalCount; + for (size_t i = 0; i < changes->size; i++) + { + vlc_diffutil_change_t& op = changes->data[i]; + switch (op.type) + { + case VLC_DIFFUTIL_OP_IGNORE: + break; + case VLC_DIFFUTIL_OP_INSERT: + m_partialX = op.op.insert.x; + m_partialIndex = op.op.insert.index; + emit beginInsertRows(op.op.insert.index, op.op.insert.index + op.count - 1); + m_partialIndex += op.count; + m_partialLoadedCount += op.count; + partialTotalCount += op.count; + emit endInsertRows(); + emit localSizeChanged(partialTotalCount); + break; + case VLC_DIFFUTIL_OP_REMOVE: + m_partialX = op.op.remove.x; + m_partialIndex = op.op.remove.index + op.count - 1; + emit beginRemoveRows(op.op.remove.index, op.op.remove.index + op.count - 1); + m_partialLoadedCount -= op.count; + m_partialX += op.count; + m_partialIndex = op.op.remove.index + 1; + partialTotalCount -= op.count; + emit endRemoveRows(); + emit localSizeChanged(partialTotalCount); + break; + case VLC_DIFFUTIL_OP_MOVE: + emit beginMoveRows(op.op.move.from, op.op.move.from + op.count - 1, op.op.move.to); + //TODO + emit endMoveRows(); + break; + } + } + vlc_diffutil_free_change_list(changes); + vlc_diffutil_free_snake(snake); + + //ditch old model + m_oldData.reset(); + + //if we have change outside our cache + //just notify for addition/removal at a the end of the list + if (partialTotalCount != m_cachedData->totalCount) + { + if (partialTotalCount > m_cachedData->totalCount) + { + emit beginRemoveRows(m_cachedData->totalCount - 1, partialTotalCount - 1); + emit endRemoveRows(); + emit localSizeChanged(m_cachedData->totalCount); + } + else + { + emit beginInsertRows(partialTotalCount - 1, m_cachedData->totalCount - 1); + emit endInsertRows(); + emit localSizeChanged(m_cachedData->totalCount); + } + } +} + +void MLListCache::asyncCountAndLoad() +{ + if (m_countTask) + m_medialib->cancelMLTask(this, m_countTask); + + size_t count = std::max(m_maxReferedIndex, m_chunkSize); + + struct Ctx { + size_t totalCount; + std::vector list; + }; + + m_countTask = m_medialib->runOnMLThread(this, + //ML thread + [loader = m_loader, count = count](vlc_medialibrary_t* ml, Ctx& ctx) + { + ctx.list = loader->load(ml, 0, count); ctx.totalCount = loader->count(ml); }, //UI thread - [this](quint64, Ctx& ctx){ - m_total_count = ctx.totalCount; + [this](quint64 taskId, Ctx& ctx) + { + if (m_countTask != taskId) + return; + + //quite unlikley but model may change between count and load + if (unlikely(ctx.list.size() > ctx.totalCount)) + { + ctx.totalCount = ctx.list.size(); + m_needReload = true; + } + + m_cachedData = std::make_unique(std::move(ctx.list), ctx.totalCount); + + if (m_oldData) + { + partialUpdate(); + } + else + { + if (m_cachedData->totalCount > 0) + { + //no previous data, we insert everything + emit beginInsertRows(0, m_cachedData->totalCount - 1); + emit endInsertRows(); + emit localSizeChanged(m_cachedData->totalCount); + } + } + m_countTask = 0; - emit localSizeChanged(m_total_count); + if (m_needReload) + { + m_needReload = false; + m_oldData = std::move(m_cachedData); + m_partialX = 0; + asyncCountAndLoad(); + } + else if (m_maxReferedIndex < m_cachedData->loadedCount) + { + m_maxReferedIndex = m_cachedData->loadedCount; + } + else if (m_maxReferedIndex > m_cachedData->loadedCount + && m_maxReferedIndex <= m_cachedData->totalCount) + { + asyncFetchMore(); + } } ); } -void MLListCache::asyncLoad(size_t offset, size_t count) +void MLListCache::asyncFetchMore() { - if (m_loadTask) - m_medialib->cancelMLTask(this, m_loadTask); + if (m_maxReferedIndex <= m_cachedData->loadedCount) + return; + + assert(m_cachedData); + if (m_appendTask) + m_medialib->cancelMLTask(this, m_appendTask); + + m_maxReferedIndex = std::min(m_cachedData->totalCount, m_maxReferedIndex); + size_t count = ((m_maxReferedIndex - m_cachedData->loadedCount) / m_chunkSize + 1 ) * m_chunkSize; - m_lastRangeRequested = { offset, count }; struct Ctx { std::vector list; }; - m_loadTask = m_medialib->runOnMLThread(this, + m_appendTask = m_medialib->runOnMLThread(this, //ML thread - [loader = m_loader, offset, count] + [loader = m_loader, offset = m_cachedData->loadedCount, count] (vlc_medialibrary_t* ml, Ctx& ctx) { ctx.list = loader->load(ml, offset, count); }, //UI thread - [this, offset](quint64, Ctx& ctx) + [this](quint64 taskId, Ctx& ctx) { - m_loadTask = 0; + if (taskId != m_appendTask) + return; - m_offset = offset; - m_list = std::move(ctx.list); - if (m_list.size()) - emit localDataChanged(offset, m_list.size()); + assert(m_cachedData); + + int updatedCount = ctx.list.size(); + if (updatedCount >= 0) + { + int updatedOffset = m_cachedData->loadedCount; + std::move(ctx.list.begin(), ctx.list.end(), std::back_inserter(m_cachedData->list)); + m_cachedData->loadedCount += updatedCount; + emit localDataChanged(updatedOffset, updatedOffset + updatedCount - 1); + } + + m_appendTask = 0; + if (m_maxReferedIndex > m_cachedData->loadedCount) + { + asyncFetchMore(); + } } ); } diff --git a/modules/gui/qt/medialibrary/mllistcache.hpp b/modules/gui/qt/medialibrary/mllistcache.hpp index 96e23a2286..d6b5e3c276 100644 --- a/modules/gui/qt/medialibrary/mllistcache.hpp +++ b/modules/gui/qt/medialibrary/mllistcache.hpp @@ -72,13 +72,44 @@ struct MLRange { } - bool isEmpty() { + MLRange(const MLRange& other) + : offset(other.offset) + , count(other.count) + {} + + MLRange& operator=(const MLRange& other) + { + offset = other.offset; + count = other.count; + return *this; + } + + bool isEmpty() const { return count == 0; } - bool contains(size_t index) { + bool contains(size_t index) const { return index >= offset && index < offset + count; } + + void reset() + { + offset = 0; + count = 0; + } + + ///returns the overlapping range of the current range and @a other + MLRange overlap(const MLRange& other) const + { + if (isEmpty() || other.isEmpty()) + return MLRange{}; + if (contains(other.offset)) + return MLRange(other.offset, (offset + count) - other.offset); + else if (other.contains(offset)) + return MLRange(offset, (other.offset + other.count) - offset); + else + return MLRange{}; + } }; class MLListCache : public QObject @@ -87,14 +118,12 @@ class MLListCache : public QObject public: typedef std::unique_ptr ItemType; + public: static constexpr ssize_t COUNT_UNINITIALIZED = -1; - MLListCache(MediaLib* medialib, ListCacheLoader *loader, - size_t chunkSize = 100) - : m_medialib(medialib) - , m_loader(loader) - , m_chunkSize(chunkSize) {} + MLListCache(MediaLib* medialib, std::unique_ptr>&& loader, + size_t chunkSize = 100); /** * Return the item at specified index @@ -147,16 +176,30 @@ public: */ void refer(size_t index); -signals: - /* useful for signaling QAbstractItemModel::modelAboutToBeReset() */ - void localSizeAboutToBeChanged(size_t size); + /* + * reload + */ + void invalidate(); +signals: + void localSizeAboutToBeChanged(size_t size); void localSizeChanged(size_t size); - void localDataChanged(size_t index, size_t count); + + void localDataChanged(int sourceFirst, int sourceLast); + + void beginInsertRows(int sourceFirst, int sourceLast); + void endInsertRows(); + + void beginRemoveRows(int sourceFirst, int sourceLast); + void endRemoveRows(); + + void beginMoveRows(int sourceFirst, int sourceLast, int destination); + void endMoveRows(); private: - void asyncLoad(size_t offset, size_t count); - void asyncCount(); + void asyncFetchMore(); + void asyncCountAndLoad(); + void partialUpdate(); MediaLib* m_medialib = nullptr; @@ -165,15 +208,37 @@ private: QSharedPointer> m_loader; size_t m_chunkSize; - std::vector m_list; - ssize_t m_total_count = COUNT_UNINITIALIZED; - size_t m_offset = 0; + //highest index requested by the view (1 based, 0 is nothing referenced) + size_t m_maxReferedIndex = 0; - bool m_countRequested = false; - MLRange m_lastRangeRequested; + bool m_needReload = false; - uint64_t m_loadTask = 0; + uint64_t m_appendTask = 0; uint64_t m_countTask = 0; + + struct CacheData { + explicit CacheData(std::vector&& list_, size_t totalCount_) + : list(std::move(list_)) + , totalCount(totalCount_) + { + loadedCount = list.size(); + } + + std::vector list; + size_t totalCount = 0; + size_t loadedCount = 0; + }; + + std::unique_ptr m_cachedData; + std::unique_ptr m_oldData; + + MLRange m_rangeRequested; + + + //access the list while it's being updated + size_t m_partialIndex = 0; + size_t m_partialX = 0; + size_t m_partialLoadedCount = 0; }; #endif