[vlc-devel] [PATCH 11/13] qt: medialib: make cache load() asynchronous

Pierre Lamot pierre at videolabs.io
Tue Nov 24 11:16:36 CET 2020


On 2020-11-23 17:30, Romain Vimont wrote:
> Make the list cache use async tasks to load the data asynchronously 
> (and
> notify data changes once available).
> ---
>  modules/gui/qt/medialibrary/mlbasemodel.hpp |   5 +-
>  modules/gui/qt/util/listcache.hpp           | 132 ++++++++++++++++++--
>  2 files changed, 124 insertions(+), 13 deletions(-)
> 
> diff --git a/modules/gui/qt/medialibrary/mlbasemodel.hpp
> b/modules/gui/qt/medialibrary/mlbasemodel.hpp
> index fdd98f5626..d108df976e 100644
> --- a/modules/gui/qt/medialibrary/mlbasemodel.hpp
> +++ b/modules/gui/qt/medialibrary/mlbasemodel.hpp
> @@ -216,9 +216,10 @@ public:
>          if (m_cache)
>              return;
> 
> +        auto &threadPool = m_mediaLib->threadPool();
>          auto loader = createLoader();
> -        m_cache.reset(new ListCache<std::unique_ptr<T>>(loader));
> -        connect(&*m_cache, &ListCacheBase::localDataChanged,
> +        m_cache.reset(new ListCache<std::unique_ptr<T>>(threadPool, 
> loader));
> +        connect(&*m_cache, &BaseListCache::localDataChanged,
>                  this, &MLBaseModel::onLocalDataChanged);
> 
>          m_cache->initCount();
> diff --git a/modules/gui/qt/util/listcache.hpp
> b/modules/gui/qt/util/listcache.hpp
> index 56016dce77..f8d5c4d729 100644
> --- a/modules/gui/qt/util/listcache.hpp
> +++ b/modules/gui/qt/util/listcache.hpp
> @@ -24,10 +24,16 @@
>  #endif
> 
>  #include "vlc_common.h"
> +#include <cassert>
>  #include <memory>
>  #include <vector>
>  #include <QtGlobal>
> +#include <QMutex>
> +#include <QMutexLocker>
>  #include <QObject>
> +#include <QRunnable>
> +#include <QSharedPointer>
> +#include "util/asynctask.hpp"
> 
>  /**
>   * `ListCache<T>` represents a cache for a (constant) list of items.
> @@ -37,6 +43,9 @@
>   *  - `count()` returns the number of items in the list;
>   *  - `load(index, count)` returning the items for the requested 
> interval.
>   *
> + * These functions are assumed to be long-running, so they executed 
> from a
> + * separate thread, not to block the UI thread.
> + *
>   * The precise cache strategy is unspecified (it may change in the 
> future), but
>   * the general principle is to keep locally only a part of the whole 
> data.
>   *
> @@ -61,23 +70,54 @@ struct ListCacheLoader
>  };
> 
>  /* Non-template class for signals */
> -class ListCacheBase : public QObject
> +class BaseListCache : public QObject

nit: this can probably have the final name in the former patch

>  {
>      Q_OBJECT
> 
>  signals:
>      void localDataChanged(size_t index, size_t count);
> +
> +protected slots:
> +    virtual void onLoadResult() = 0;
>  };
> 
>  template <typename T>
> -class ListCache : public ListCacheBase
> +class LoadTask;
> +
> +struct MLRange
> +{
> +    size_t offset = 0;
> +    size_t count = 0;
> +
> +    MLRange() = default;
> +
> +    MLRange(size_t offset, size_t count)
> +        : offset(offset)
> +        , count(count)
> +    {
> +    }
> +
> +    bool isEmpty() {
> +        return count == 0;
> +    }
> +
> +    bool contains(size_t index) {
> +        return index >= offset && index < offset + count;
> +    }
> +};
> +
> +template <typename T>
> +class ListCache : public BaseListCache
>  {
>  public:
>      static constexpr ssize_t COUNT_UNINITIALIZED = -1;
> 
> -    ListCache(ListCacheLoader<T> *loader, size_t chunkSize = 100)
> -        : m_loader(loader)
> +    ListCache(QThreadPool &threadPool, ListCacheLoader<T> *loader,
> +              size_t chunkSize = 100)
> +        : m_threadPool(threadPool)
> +        , m_loader(loader)
>          , m_chunkSize(chunkSize) {}
> +    ~ListCache();
> 
>      /**
>       * Return the item at specified index
> @@ -110,14 +150,32 @@ public:
>      void refer(size_t index);
> 
>  private:
> -    std::unique_ptr<ListCacheLoader<T>> m_loader;
> +    void onLoadResult() override;
> +
> +    void asyncLoad(size_t offset, size_t count);
> +
> +    QThreadPool &m_threadPool;
> +    /* Ownershipshared between this cache and the runnable spawned to 
> execute
> +     * loader callbacks */
> +    QSharedPointer<ListCacheLoader<T>> m_loader;
>      size_t m_chunkSize;
> 
>      std::vector<T> m_list;
>      ssize_t m_total_count = COUNT_UNINITIALIZED;
>      size_t m_offset = 0;
> +
> +    MLRange m_lastRangeRequested;
> +
> +    LoadTask<T> *m_loadTask = nullptr;
>  };
> 
> +template <typename T>
> +ListCache<T>::~ListCache()
> +{
> +    if (m_loadTask)
> +        m_loadTask->abandon();
> +}
> +
>  template <typename T>
>  const T *ListCache<T>::get(size_t index) const
>  {
> @@ -156,16 +214,68 @@ void ListCache<T>::refer(size_t index)
>      }
> 
>      /* index outside the known portion of the list */
> -    if (index < m_offset || index >= m_offset + m_list.size())
> +    if (!m_lastRangeRequested.contains(index))
>      {
>          /* FIXME bad heuristic if the interval of visible items 
> crosses a cache
>           * page boundary */
> -        m_offset = index - index % m_chunkSize;
> -        size_t count = qMin(m_total_count - m_offset, m_chunkSize);
> -        m_list = m_loader->load(m_offset, count);
> -        if (m_list.size())
> -            emit localDataChanged(m_offset, m_list.size());
> +        size_t offset = index - index % m_chunkSize;
> +        size_t count = qMin(m_total_count - offset, m_chunkSize);
> +        asyncLoad(offset, count);
>      }
>  }
> 
> +template <typename T>
> +class LoadTask : public AsyncTask<std::vector<T>>
> +{
> +public:
> +    LoadTask(QSharedPointer<ListCacheLoader<T>> loader, size_t offset,
> +             size_t count)
> +        : m_loader(loader)
> +        , m_offset(offset)
> +        , m_count(count)
> +    {
> +    }
> +
> +    std::vector<T> execute() override
> +    {
> +        return m_loader->load(m_offset, m_count);
> +    }
> +
> +private:
> +    QSharedPointer<ListCacheLoader<T>> m_loader;
> +    size_t m_offset;
> +    size_t m_count;
> +
> +    friend class ListCache<T>;
> +};
> +
> +template <typename T>
> +void ListCache<T>::asyncLoad(size_t offset, size_t count)
> +{
> +    if (m_loadTask)
> +        /* Cancel any current pending task */
> +        m_loadTask->abandon();
> +
> +    m_loadTask = new LoadTask<T>(m_loader, offset, count);
> +    connect(m_loadTask, &BaseAsyncTask::result,
> +            this, &BaseListCache::onLoadResult);
> +    m_lastRangeRequested = { offset, count };
> +    m_loadTask->start(m_threadPool);
> +}
> +
> +template <typename T>
> +void ListCache<T>::onLoadResult()
> +{
> +    LoadTask<T> *task = static_cast<LoadTask<T> *>(sender());
> +    assert(task == m_loadTask);
> +
> +    m_offset = task->m_offset;
> +    m_list = task->takeResult();
> +    if (m_list.size())
> +        emit localDataChanged(m_offset, m_list.size());
> +
> +    task->abandon();
> +    m_loadTask = nullptr;
> +}
> +
>  #endif


More information about the vlc-devel mailing list