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.
This commit is contained in:
Pierre Lamot 2021-12-20 17:08:03 +01:00 committed by Jean-Baptiste Kempf
parent d6ae28da1a
commit 6bf9a88bac
4 changed files with 489 additions and 137 deletions

View File

@ -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<unsigned int>(0) );
}
QVariant MLBaseModel::getIdForIndex(QVariant index) const
{
MLItem* obj = nullptr;
@ -417,26 +401,78 @@ unsigned MLBaseModel::getCount() const
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
{
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<MLListCache>(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<unsigned int>(signedidx);
@ -468,6 +507,9 @@ MLItem *MLBaseModel::itemCache(int signedidx) const
{
unsigned int idx = static_cast<unsigned int>(signedidx);
if (!m_cache)
return nullptr;
const std::unique_ptr<MLItem> *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);
}
//-------------------------------------------------------------------------------------------------

View File

@ -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;

View File

@ -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<const std::vector<MLListCache::ItemType>*>(data);
assert(list);
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();
}
}
MLListCache::MLListCache(MediaLib* medialib, std::unique_ptr<ListCacheLoader<MLListCache::ItemType>>&& 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<size_t>(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<bool (const MLListCache::ItemType&)> &&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<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();
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<size_t>(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<Ctx>(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<ItemType> list;
};
m_countTask = m_medialib->runOnMLThread<Ctx>(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<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;
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<ItemType> list;
};
m_loadTask = m_medialib->runOnMLThread<Ctx>(this,
m_appendTask = m_medialib->runOnMLThread<Ctx>(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();
}
}
);
}

View File

@ -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<MLItem> ItemType;
public:
static constexpr ssize_t COUNT_UNINITIALIZED = -1;
MLListCache(MediaLib* medialib, ListCacheLoader<ItemType> *loader,
size_t chunkSize = 100)
: m_medialib(medialib)
, m_loader(loader)
, m_chunkSize(chunkSize) {}
MLListCache(MediaLib* medialib, std::unique_ptr<ListCacheLoader<ItemType>>&& 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<ListCacheLoader<ItemType>> m_loader;
size_t m_chunkSize;
std::vector<ItemType> 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<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