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

Romain Vimont rom1v at videolabs.io
Mon Nov 23 17:30:25 CET 2020


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
 {
     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
-- 
2.29.2



More information about the vlc-devel mailing list