Browse Source

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.
pull/134/head
Pierre Lamot 4 years ago
committed by Jean-Baptiste Kempf
parent
commit
6bf9a88bac
  1. 124
      modules/gui/qt/medialibrary/mlbasemodel.cpp
  2. 9
      modules/gui/qt/medialibrary/mlbasemodel.hpp
  3. 378
      modules/gui/qt/medialibrary/mllistcache.cpp
  4. 101
      modules/gui/qt/medialibrary/mllistcache.hpp

124
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_desc = (order == Qt::SortOrder::DescendingOrder);
m_sort = nameToCriteria(name); m_sort = nameToCriteria(name);
clear(); resetCache();
} }
//------------------------------------------------------------------------------------------------- //-------------------------------------------------------------------------------------------------
@ -169,7 +169,7 @@ QVariant MLBaseModel::data(const QModelIndex &index, int role) const
void MLBaseModel::onResetRequested() void MLBaseModel::onResetRequested()
{ {
clear(); invalidateCache();
} }
void MLBaseModel::onLocalSizeAboutToBeChanged(size_t size) void MLBaseModel::onLocalSizeAboutToBeChanged(size_t size)
@ -185,14 +185,6 @@ void MLBaseModel::onLocalSizeChanged(size_t size)
emit countChanged(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) void MLBaseModel::onVlcMlEvent(const MLEvent &event)
{ {
switch(event.i_type) switch(event.i_type)
@ -268,14 +260,14 @@ MLItemId MLBaseModel::parentId() const
void MLBaseModel::setParentId(MLItemId parentId) void MLBaseModel::setParentId(MLItemId parentId)
{ {
m_parent = parentId; m_parent = parentId;
clear(); invalidateCache();
emit parentIdChanged(); emit parentIdChanged();
} }
void MLBaseModel::unsetParentId() void MLBaseModel::unsetParentId()
{ {
m_parent = MLItemId(); m_parent = MLItemId();
clear(); invalidateCache();
emit parentIdChanged(); emit parentIdChanged();
} }
@ -305,7 +297,7 @@ void MLBaseModel::setSearchPattern( const QString& pattern )
return; return;
m_search_pattern = patternToApply; m_search_pattern = patternToApply;
clear(); invalidateCache();
} }
Qt::SortOrder MLBaseModel::getSortOrder() const Qt::SortOrder MLBaseModel::getSortOrder() const
@ -316,7 +308,7 @@ Qt::SortOrder MLBaseModel::getSortOrder() const
void MLBaseModel::setSortOder(Qt::SortOrder order) void MLBaseModel::setSortOder(Qt::SortOrder order)
{ {
m_sort_desc = (order == Qt::SortOrder::DescendingOrder); m_sort_desc = (order == Qt::SortOrder::DescendingOrder);
clear(); resetCache();
emit sortOrderChanged(); emit sortOrderChanged();
} }
@ -328,14 +320,14 @@ const QString MLBaseModel::getSortCriteria() const
void MLBaseModel::setSortCriteria(const QString& criteria) void MLBaseModel::setSortCriteria(const QString& criteria)
{ {
m_sort = nameToCriteria(criteria.toUtf8()); m_sort = nameToCriteria(criteria.toUtf8());
clear(); resetCache();
emit sortCriteriaChanged(); emit sortCriteriaChanged();
} }
void MLBaseModel::unsetSortCriteria() void MLBaseModel::unsetSortCriteria()
{ {
m_sort = VLC_ML_SORTING_DEFAULT; m_sort = VLC_ML_SORTING_DEFAULT;
clear(); resetCache();
emit sortCriteriaChanged(); emit sortCriteriaChanged();
} }
@ -349,14 +341,6 @@ int MLBaseModel::rowCount(const QModelIndex &parent) const
return m_cache->count(); return m_cache->count();
} }
void MLBaseModel::clear()
{
beginResetModel();
invalidateCache();
endResetModel();
emit countChanged( static_cast<unsigned int>(0) );
}
QVariant MLBaseModel::getIdForIndex(QVariant index) const QVariant MLBaseModel::getIdForIndex(QVariant index) const
{ {
MLItem* obj = nullptr; MLItem* obj = nullptr;
@ -417,26 +401,78 @@ unsigned MLBaseModel::getCount() const
return static_cast<unsigned>(m_cache->count()); return static_cast<unsigned>(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 void MLBaseModel::validateCache() const
{ {
if (m_cache) if (m_cache)
return; return;
if (!m_mediaLib)
return;
auto loader = createLoader(); auto loader = createLoader();
m_cache.reset(new MLListCache(m_mediaLib, loader.release())); m_cache = std::make_unique<MLListCache>(m_mediaLib, std::move(loader));
connect(&*m_cache, &MLListCache::localSizeAboutToBeChanged, connect(m_cache.get(), &MLListCache::localSizeAboutToBeChanged,
this, &MLBaseModel::onLocalSizeAboutToBeChanged); this, &MLBaseModel::onLocalSizeAboutToBeChanged);
connect(&*m_cache, &MLListCache::localSizeChanged, connect(m_cache.get(), &MLListCache::localSizeChanged,
this, &MLBaseModel::onLocalSizeChanged); 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(); m_cache->initCount();
} }
void MLBaseModel::invalidateCache()
void MLBaseModel::resetCache()
{ {
beginResetModel();
m_cache.reset(); m_cache.reset();
endResetModel();
validateCache();
}
void MLBaseModel::invalidateCache()
{
if (m_cache)
m_cache->invalidate();
else
validateCache();
} }
//------------------------------------------------------------------------------------------------- //-------------------------------------------------------------------------------------------------
@ -445,9 +481,12 @@ MLItem *MLBaseModel::item(int signedidx) const
{ {
validateCache(); validateCache();
if (!m_cache)
return nullptr;
ssize_t count = m_cache->count(); ssize_t count = m_cache->count();
if (count == COUNT_UNINITIALIZED || signedidx < 0 || signedidx >= count) if (count == 0 || signedidx < 0 || signedidx >= count)
return nullptr; return nullptr;
unsigned int idx = static_cast<unsigned int>(signedidx); unsigned int idx = static_cast<unsigned int>(signedidx);
@ -468,6 +507,9 @@ MLItem *MLBaseModel::itemCache(int signedidx) const
{ {
unsigned int idx = static_cast<unsigned int>(signedidx); unsigned int idx = static_cast<unsigned int>(signedidx);
if (!m_cache)
return nullptr;
const std::unique_ptr<MLItem> *item = m_cache->get(idx); const std::unique_ptr<MLItem> *item = m_cache->get(idx);
if (!item) if (!item)
@ -514,13 +556,7 @@ void MLBaseModel::updateItemInCache(const MLItemId& mlid)
if (!ctx.item) if (!ctx.item)
return; return;
int row = m_cache->updateItem(std::move(ctx.item)); 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
}); });
} }
@ -531,17 +567,7 @@ void MLBaseModel::deleteItemInCache(const MLItemId& mlid)
emit resetRequested(); emit resetRequested();
return; return;
} }
int row = m_cache->deleteItem(mlid); 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();
}
} }
//------------------------------------------------------------------------------------------------- //-------------------------------------------------------------------------------------------------

9
modules/gui/qt/medialibrary/mlbasemodel.hpp

@ -91,13 +91,11 @@ protected slots:
void onResetRequested(); void onResetRequested();
void onLocalSizeAboutToBeChanged(size_t size); void onLocalSizeAboutToBeChanged(size_t size);
void onLocalSizeChanged(size_t size); void onLocalSizeChanged(size_t size);
void onLocalDataChanged(size_t index, size_t count);
private: private:
static void onVlcMlEvent( void* data, const vlc_ml_event_t* event ); static void onVlcMlEvent( void* data, const vlc_ml_event_t* event );
protected: protected:
virtual void clear();
virtual vlc_ml_sorting_criteria_t roleToCriteria(int role) const = 0; virtual vlc_ml_sorting_criteria_t roleToCriteria(int role) const = 0;
static QString getFirstSymbol(QString str); static QString getFirstSymbol(QString str);
virtual vlc_ml_sorting_criteria_t nameToCriteria(QByteArray) const { virtual vlc_ml_sorting_criteria_t nameToCriteria(QByteArray) const {
@ -109,6 +107,7 @@ protected:
} }
void validateCache() const; void validateCache() const;
void resetCache();
void invalidateCache(); void invalidateCache();
MLItem *item(int signedidx) const; MLItem *item(int signedidx) const;
@ -170,6 +169,12 @@ public:
int rowCount(const QModelIndex &parent = {}) const override; int rowCount(const QModelIndex &parent = {}) const override;
virtual unsigned int getCount() const; 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: protected:
MLItemId m_parent; MLItemId m_parent;

378
modules/gui/qt/medialibrary/mllistcache.cpp

@ -17,147 +17,403 @@
*****************************************************************************/ *****************************************************************************/
#include "mllistcache.hpp" #include "mllistcache.hpp"
#include "vlc_diffutil.h"
const MLListCache::ItemType* MLListCache::get(size_t index) const namespace {
//callbacks for the diff algorithm to access the data
uint32_t cacheDataLength(const void* data)
{ {
assert(m_total_count >= 0 && index < static_cast<size_t>(m_total_count)); auto list = static_cast<const std::vector<MLListCache::ItemType>*>(data);
if (index < m_offset || index >= m_offset + m_list.size()) assert(list);
return nullptr; return list->size();
}
bool cacheDataCompare(const void* dataOld, uint32_t oldIndex, const void* dataNew, uint32_t newIndex)
{
auto listOld = static_cast<const std::vector<MLListCache::ItemType>*>(dataOld);
auto listNew = static_cast<const std::vector<MLListCache::ItemType>*>(dataNew);
assert(listOld);
assert(listNew);
assert(oldIndex < listOld->size());
assert(newIndex < listNew->size());
return listOld->at(oldIndex)->getId() == listNew->at(newIndex)->getId();
}
return &m_list[index - m_offset];
} }
const MLListCache::ItemType* MLListCache::find(const std::function<bool (const MLListCache::ItemType&)> &&f, int *index) const MLListCache::MLListCache(MediaLib* medialib, std::unique_ptr<ListCacheLoader<MLListCache::ItemType>>&& loader, size_t chunkSize)
: m_medialib(medialib)
, m_loader(loader.release())
, m_chunkSize(chunkSize)
{ {
if (m_total_count <= 0) assert(medialib);
return nullptr; }
for (auto iter = std::begin(m_list); iter != std::end(m_list); ++iter) const MLListCache::ItemType* MLListCache::get(size_t index) const
{
//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 (f(*iter)) 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) if (index >= m_oldData->loadedCount)
*index = m_offset + std::distance(std::begin(m_list), iter); return nullptr;
return &(*iter); return &m_oldData->list.at(index);
} }
} }
return nullptr; if (!m_cachedData)
return nullptr;
if (index + 1 > m_cachedData->loadedCount)
return nullptr;
return &m_cachedData->list.at(index);
}
const MLListCache::ItemType* MLListCache::find(const std::function<bool (const MLListCache::ItemType&)> &&f, int *index) const
{
if (!m_cachedData || m_cachedData->totalCount == 0)
return nullptr;
auto it = std::find_if(m_cachedData->list.cbegin(), m_cachedData->list.cend(), f);
if (it == m_cachedData->list.cend())
return nullptr;
if (index)
*index = std::distance(m_cachedData->list.cbegin(), it);
return &(*it);
} }
int MLListCache::updateItem(std::unique_ptr<MLItem>&& newItem) int MLListCache::updateItem(std::unique_ptr<MLItem>&& 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(); 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); return (item->getId() == mlid);
}); });
//item not found //item not found
if (it == m_list.end()) if (it == m_cachedData->list.end())
return -1; 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); *it = std::move(newItem);
emit localDataChanged(pos, pos);
return pos; return pos;
} }
int MLListCache::deleteItem(const MLItemId& mlid) 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); return (item->getId() == mlid);
}); });
//item not found //item not found
if (it == m_list.end()) if (it == m_cachedData->list.end())
return -1; return -1;
int pos = m_offset + std::distance(m_list.begin(), it);
m_list.erase(it, it); int pos = std::distance(m_cachedData->list.begin(), it);
m_total_count -= 1;
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; return pos;
} }
ssize_t MLListCache::count() const ssize_t MLListCache::count() const
{ {
return m_total_count; if (!m_cachedData)
return -1;
return m_cachedData->totalCount;
} }
void MLListCache::initCount() void MLListCache::initCount()
{ {
assert(!m_countRequested); assert(!m_cachedData);
asyncCount(); asyncCountAndLoad();
} }
void MLListCache::refer(size_t index) void MLListCache::refer(size_t index)
{ {
if (m_total_count == -1 || index >= static_cast<size_t>(m_total_count)) //m_maxReferedIndex is in terms of number of item, not the index
{ index++;
/*
* The request is incompatible with the total count of the list. if (!m_cachedData)
*
* Either the count is not retrieved yet, or the content has changed in
* the loader source.
*/
return; return;
if (index > m_cachedData->totalCount)
return;
/* index is already in the list */
if (index <= m_cachedData->loadedCount)
return;
if (index > m_maxReferedIndex)
{
m_maxReferedIndex = index;
if (!m_appendTask && !m_countTask)
{
if (m_cachedData)
asyncFetchMore();
else
asyncCountAndLoad();
}
}
}
void MLListCache::invalidate()
{
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();
/* index outside the known portion of the list */ //if we have change outside our cache
if (!m_lastRangeRequested.contains(index)) //just notify for addition/removal at a the end of the list
if (partialTotalCount != m_cachedData->totalCount)
{ {
/* FIXME bad heuristic if the interval of visible items crosses a cache if (partialTotalCount > m_cachedData->totalCount)
* page boundary */ {
size_t offset = index - index % m_chunkSize; emit beginRemoveRows(m_cachedData->totalCount - 1, partialTotalCount - 1);
size_t count = qMin(m_total_count - offset, m_chunkSize); emit endRemoveRows();
asyncLoad(offset, count); emit localSizeChanged(m_cachedData->totalCount);
}
else
{
emit beginInsertRows(partialTotalCount - 1, m_cachedData->totalCount - 1);
emit endInsertRows();
emit localSizeChanged(m_cachedData->totalCount);
}
} }
} }
void MLListCache::asyncCount() void MLListCache::asyncCountAndLoad()
{ {
assert(!m_countTask); if (m_countTask)
m_medialib->cancelMLTask(this, m_countTask);
size_t count = std::max(m_maxReferedIndex, m_chunkSize);
m_countRequested = true;
struct Ctx { struct Ctx {
ssize_t totalCount; size_t totalCount;
std::vector<ItemType> list;
}; };
m_medialib->runOnMLThread<Ctx>(this,
m_countTask = m_medialib->runOnMLThread<Ctx>(this,
//ML thread //ML thread
[loader = m_loader](vlc_medialibrary_t* ml, Ctx& ctx) [loader = m_loader, count = count](vlc_medialibrary_t* ml, Ctx& ctx)
{ {
ctx.list = loader->load(ml, 0, count);
ctx.totalCount = loader->count(ml); ctx.totalCount = loader->count(ml);
}, },
//UI thread //UI thread
[this](quint64, Ctx& ctx){ [this](quint64 taskId, Ctx& ctx)
m_total_count = ctx.totalCount; {
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<CacheData>(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; 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) if (m_maxReferedIndex <= m_cachedData->loadedCount)
m_medialib->cancelMLTask(this, m_loadTask); 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 { struct Ctx {
std::vector<ItemType> list; std::vector<ItemType> list;
}; };
m_loadTask = m_medialib->runOnMLThread<Ctx>(this, m_appendTask = m_medialib->runOnMLThread<Ctx>(this,
//ML thread //ML thread
[loader = m_loader, offset, count] [loader = m_loader, offset = m_cachedData->loadedCount, count]
(vlc_medialibrary_t* ml, Ctx& ctx) (vlc_medialibrary_t* ml, Ctx& ctx)
{ {
ctx.list = loader->load(ml, offset, count); ctx.list = loader->load(ml, offset, count);
}, },
//UI thread //UI thread
[this, offset](quint64, Ctx& ctx) [this](quint64 taskId, Ctx& ctx)
{ {
m_loadTask = 0; if (taskId != m_appendTask)
return;
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_offset = offset; m_appendTask = 0;
m_list = std::move(ctx.list); if (m_maxReferedIndex > m_cachedData->loadedCount)
if (m_list.size()) {
emit localDataChanged(offset, m_list.size()); asyncFetchMore();
}
} }
); );
} }

101
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; return count == 0;
} }
bool contains(size_t index) { bool contains(size_t index) const {
return index >= offset && index < offset + count; 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 class MLListCache : public QObject
@ -87,14 +118,12 @@ class MLListCache : public QObject
public: public:
typedef std::unique_ptr<MLItem> ItemType; typedef std::unique_ptr<MLItem> ItemType;
public: public:
static constexpr ssize_t COUNT_UNINITIALIZED = -1; static constexpr ssize_t COUNT_UNINITIALIZED = -1;
MLListCache(MediaLib* medialib, ListCacheLoader<ItemType> *loader, MLListCache(MediaLib* medialib, std::unique_ptr<ListCacheLoader<ItemType>>&& loader,
size_t chunkSize = 100) size_t chunkSize = 100);
: m_medialib(medialib)
, m_loader(loader)
, m_chunkSize(chunkSize) {}
/** /**
* Return the item at specified index * Return the item at specified index
@ -147,16 +176,30 @@ public:
*/ */
void refer(size_t index); void refer(size_t index);
/*
* reload
*/
void invalidate();
signals: signals:
/* useful for signaling QAbstractItemModel::modelAboutToBeReset() */
void localSizeAboutToBeChanged(size_t size); void localSizeAboutToBeChanged(size_t size);
void localSizeChanged(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: private:
void asyncLoad(size_t offset, size_t count); void asyncFetchMore();
void asyncCount(); void asyncCountAndLoad();
void partialUpdate();
MediaLib* m_medialib = nullptr; MediaLib* m_medialib = nullptr;
@ -165,15 +208,37 @@ private:
QSharedPointer<ListCacheLoader<ItemType>> m_loader; QSharedPointer<ListCacheLoader<ItemType>> m_loader;
size_t m_chunkSize; size_t m_chunkSize;
std::vector<ItemType> m_list; //highest index requested by the view (1 based, 0 is nothing referenced)
ssize_t m_total_count = COUNT_UNINITIALIZED; size_t m_maxReferedIndex = 0;
size_t m_offset = 0;
bool m_countRequested = false; bool m_needReload = false;
MLRange m_lastRangeRequested;
uint64_t m_loadTask = 0; uint64_t m_appendTask = 0;
uint64_t m_countTask = 0; uint64_t m_countTask = 0;
struct CacheData {
explicit CacheData(std::vector<ItemType>&& list_, size_t totalCount_)
: list(std::move(list_))
, totalCount(totalCount_)
{
loadedCount = list.size();
}
std::vector<ItemType> list;
size_t totalCount = 0;
size_t loadedCount = 0;
};
std::unique_ptr<CacheData> m_cachedData;
std::unique_ptr<CacheData> 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 #endif

Loading…
Cancel
Save