forked from qt-creator/qt-creator
QmlDesigner: Add task queue
To share the code TaskQueue is introduced. The Thread and the synchronization code is moved into TaskQueue. Only a dispatch and cean must be implemented in the callback. Change-Id: I19c85c891ef700aae85b54630714555e862200a4 Reviewed-by: Tim Jenssen <tim.jenssen@qt.io> Reviewed-by: Qt CI Patch Build Bot <ci_patchbuild_bot@qt.io>
This commit is contained in:
@@ -176,6 +176,7 @@ extend_qtc_library(QmlDesignerCore
|
||||
meshimagecachecollector.cpp
|
||||
meshimagecachecollector.h
|
||||
synchronousimagecache.cpp
|
||||
taskqueue.h
|
||||
textureimagecachecollector.cpp
|
||||
textureimagecachecollector.h
|
||||
timestampprovider.cpp
|
||||
|
@@ -17,8 +17,6 @@ AsynchronousExplicitImageCache::AsynchronousExplicitImageCache(ImageCacheStorage
|
||||
|
||||
AsynchronousExplicitImageCache::~AsynchronousExplicitImageCache()
|
||||
{
|
||||
clean();
|
||||
wait();
|
||||
}
|
||||
|
||||
void AsynchronousExplicitImageCache::request(Utils::SmallStringView name,
|
||||
@@ -57,21 +55,16 @@ void AsynchronousExplicitImageCache::request(Utils::SmallStringView name,
|
||||
}
|
||||
}
|
||||
|
||||
void AsynchronousExplicitImageCache::wait()
|
||||
{
|
||||
stopThread();
|
||||
m_condition.notify_all();
|
||||
if (m_backgroundThread.joinable())
|
||||
m_backgroundThread.join();
|
||||
}
|
||||
|
||||
void AsynchronousExplicitImageCache::requestImage(Utils::SmallStringView name,
|
||||
ImageCache::CaptureImageCallback captureCallback,
|
||||
ImageCache::AbortCallback abortCallback,
|
||||
Utils::SmallStringView extraId)
|
||||
{
|
||||
addEntry(name, extraId, std::move(captureCallback), std::move(abortCallback), RequestType::Image);
|
||||
m_condition.notify_all();
|
||||
m_taskQueue.addTask(name,
|
||||
extraId,
|
||||
std::move(captureCallback),
|
||||
std::move(abortCallback),
|
||||
RequestType::Image);
|
||||
}
|
||||
|
||||
void AsynchronousExplicitImageCache::requestMidSizeImage(Utils::SmallStringView name,
|
||||
@@ -79,8 +72,11 @@ void AsynchronousExplicitImageCache::requestMidSizeImage(Utils::SmallStringView
|
||||
ImageCache::AbortCallback abortCallback,
|
||||
Utils::SmallStringView extraId)
|
||||
{
|
||||
addEntry(name, extraId, std::move(captureCallback), std::move(abortCallback), RequestType::MidSizeImage);
|
||||
m_condition.notify_all();
|
||||
m_taskQueue.addTask(name,
|
||||
extraId,
|
||||
std::move(captureCallback),
|
||||
std::move(abortCallback),
|
||||
RequestType::MidSizeImage);
|
||||
}
|
||||
|
||||
void AsynchronousExplicitImageCache::requestSmallImage(Utils::SmallStringView name,
|
||||
@@ -88,108 +84,16 @@ void AsynchronousExplicitImageCache::requestSmallImage(Utils::SmallStringView na
|
||||
ImageCache::AbortCallback abortCallback,
|
||||
Utils::SmallStringView extraId)
|
||||
{
|
||||
addEntry(name, extraId, std::move(captureCallback), std::move(abortCallback), RequestType::SmallImage);
|
||||
m_condition.notify_all();
|
||||
m_taskQueue.addTask(name,
|
||||
extraId,
|
||||
std::move(captureCallback),
|
||||
std::move(abortCallback),
|
||||
RequestType::SmallImage);
|
||||
}
|
||||
|
||||
void AsynchronousExplicitImageCache::clean()
|
||||
{
|
||||
clearEntries();
|
||||
}
|
||||
|
||||
std::optional<AsynchronousExplicitImageCache::RequestEntry> AsynchronousExplicitImageCache::getEntry(
|
||||
std::unique_lock<std::mutex> lock)
|
||||
{
|
||||
auto l = std::move(lock);
|
||||
|
||||
if (m_requestEntries.empty())
|
||||
return {};
|
||||
|
||||
RequestEntry entry = m_requestEntries.front();
|
||||
m_requestEntries.pop_front();
|
||||
|
||||
return {entry};
|
||||
}
|
||||
|
||||
void AsynchronousExplicitImageCache::addEntry(Utils::PathString &&name,
|
||||
Utils::SmallString &&extraId,
|
||||
ImageCache::CaptureImageCallback &&captureCallback,
|
||||
ImageCache::AbortCallback &&abortCallback,
|
||||
RequestType requestType)
|
||||
{
|
||||
std::unique_lock lock{m_mutex};
|
||||
|
||||
ensureThreadIsRunning();
|
||||
|
||||
m_requestEntries.emplace_back(std::move(name),
|
||||
std::move(extraId),
|
||||
std::move(captureCallback),
|
||||
std::move(abortCallback),
|
||||
requestType);
|
||||
}
|
||||
|
||||
void AsynchronousExplicitImageCache::clearEntries()
|
||||
{
|
||||
std::unique_lock lock{m_mutex};
|
||||
for (RequestEntry &entry : m_requestEntries)
|
||||
entry.abortCallback(ImageCache::AbortReason::Abort);
|
||||
m_requestEntries.clear();
|
||||
}
|
||||
|
||||
std::tuple<std::unique_lock<std::mutex>, bool> AsynchronousExplicitImageCache::waitForEntries()
|
||||
{
|
||||
using namespace std::literals::chrono_literals;
|
||||
std::unique_lock lock{m_mutex};
|
||||
if (m_finishing)
|
||||
return {std::move(lock), true};
|
||||
if (m_requestEntries.empty()) {
|
||||
auto timedOutWithoutEntriesOrFinishing = !m_condition.wait_for(lock, 10min, [&] {
|
||||
return m_requestEntries.size() || m_finishing;
|
||||
});
|
||||
|
||||
if (timedOutWithoutEntriesOrFinishing || m_finishing) {
|
||||
m_sleeping = true;
|
||||
return {std::move(lock), true};
|
||||
}
|
||||
}
|
||||
return {std::move(lock), false};
|
||||
}
|
||||
|
||||
void AsynchronousExplicitImageCache::stopThread()
|
||||
{
|
||||
std::unique_lock lock{m_mutex};
|
||||
m_finishing = true;
|
||||
}
|
||||
|
||||
void AsynchronousExplicitImageCache::ensureThreadIsRunning()
|
||||
{
|
||||
if (m_finishing)
|
||||
return;
|
||||
|
||||
if (!m_sleeping)
|
||||
return;
|
||||
|
||||
if (m_backgroundThread.joinable())
|
||||
m_backgroundThread.join();
|
||||
|
||||
m_sleeping = false;
|
||||
|
||||
m_backgroundThread = std::thread{[this] {
|
||||
while (true) {
|
||||
auto [lock, abort] = waitForEntries();
|
||||
if (abort)
|
||||
return;
|
||||
|
||||
if (auto entry = getEntry(std::move(lock)); entry) {
|
||||
request(entry->name,
|
||||
entry->extraId,
|
||||
entry->requestType,
|
||||
std::move(entry->captureCallback),
|
||||
std::move(entry->abortCallback),
|
||||
m_storage);
|
||||
}
|
||||
}
|
||||
}};
|
||||
m_taskQueue.clean();
|
||||
}
|
||||
|
||||
} // namespace QmlDesigner
|
||||
|
@@ -22,8 +22,7 @@ AsynchronousImageCache::AsynchronousImageCache(ImageCacheStorageInterface &stora
|
||||
|
||||
AsynchronousImageCache::~AsynchronousImageCache()
|
||||
{
|
||||
clean();
|
||||
wait();
|
||||
m_generator.clean();
|
||||
}
|
||||
|
||||
void AsynchronousImageCache::request(Utils::SmallStringView name,
|
||||
@@ -93,27 +92,18 @@ void AsynchronousImageCache::request(Utils::SmallStringView name,
|
||||
}
|
||||
}
|
||||
|
||||
void AsynchronousImageCache::wait()
|
||||
{
|
||||
stopThread();
|
||||
m_condition.notify_all();
|
||||
if (m_backgroundThread.joinable())
|
||||
m_backgroundThread.join();
|
||||
}
|
||||
|
||||
void AsynchronousImageCache::requestImage(Utils::SmallStringView name,
|
||||
ImageCache::CaptureImageCallback captureCallback,
|
||||
ImageCache::AbortCallback abortCallback,
|
||||
Utils::SmallStringView extraId,
|
||||
ImageCache::AuxiliaryData auxiliaryData)
|
||||
{
|
||||
addEntry(std::move(name),
|
||||
std::move(extraId),
|
||||
std::move(captureCallback),
|
||||
std::move(abortCallback),
|
||||
std::move(auxiliaryData),
|
||||
RequestType::Image);
|
||||
m_condition.notify_all();
|
||||
m_taskQueue.addTask(std::move(name),
|
||||
std::move(extraId),
|
||||
std::move(captureCallback),
|
||||
std::move(abortCallback),
|
||||
std::move(auxiliaryData),
|
||||
RequestType::Image);
|
||||
}
|
||||
|
||||
void AsynchronousImageCache::requestMidSizeImage(Utils::SmallStringView name,
|
||||
@@ -122,13 +112,12 @@ void AsynchronousImageCache::requestMidSizeImage(Utils::SmallStringView name,
|
||||
Utils::SmallStringView extraId,
|
||||
ImageCache::AuxiliaryData auxiliaryData)
|
||||
{
|
||||
addEntry(std::move(name),
|
||||
std::move(extraId),
|
||||
std::move(captureCallback),
|
||||
std::move(abortCallback),
|
||||
std::move(auxiliaryData),
|
||||
RequestType::MidSizeImage);
|
||||
m_condition.notify_all();
|
||||
m_taskQueue.addTask(std::move(name),
|
||||
std::move(extraId),
|
||||
std::move(captureCallback),
|
||||
std::move(abortCallback),
|
||||
std::move(auxiliaryData),
|
||||
RequestType::MidSizeImage);
|
||||
}
|
||||
|
||||
void AsynchronousImageCache::requestSmallImage(Utils::SmallStringView name,
|
||||
@@ -137,117 +126,18 @@ void AsynchronousImageCache::requestSmallImage(Utils::SmallStringView name,
|
||||
Utils::SmallStringView extraId,
|
||||
ImageCache::AuxiliaryData auxiliaryData)
|
||||
{
|
||||
addEntry(std::move(name),
|
||||
std::move(extraId),
|
||||
std::move(captureCallback),
|
||||
std::move(abortCallback),
|
||||
std::move(auxiliaryData),
|
||||
RequestType::SmallImage);
|
||||
m_condition.notify_all();
|
||||
m_taskQueue.addTask(std::move(name),
|
||||
std::move(extraId),
|
||||
std::move(captureCallback),
|
||||
std::move(abortCallback),
|
||||
std::move(auxiliaryData),
|
||||
RequestType::SmallImage);
|
||||
}
|
||||
|
||||
void AsynchronousImageCache::clean()
|
||||
{
|
||||
clearEntries();
|
||||
m_generator.clean();
|
||||
}
|
||||
|
||||
std::optional<AsynchronousImageCache::Entry> AsynchronousImageCache::getEntry(
|
||||
std::unique_lock<std::mutex> lock)
|
||||
{
|
||||
auto l = std::move(lock);
|
||||
if (m_entries.empty())
|
||||
return {};
|
||||
|
||||
Entry entry = m_entries.front();
|
||||
m_entries.pop_front();
|
||||
|
||||
return {entry};
|
||||
}
|
||||
|
||||
void AsynchronousImageCache::addEntry(Utils::PathString &&name,
|
||||
Utils::SmallString &&extraId,
|
||||
ImageCache::CaptureImageCallback &&captureCallback,
|
||||
ImageCache::AbortCallback &&abortCallback,
|
||||
ImageCache::AuxiliaryData &&auxiliaryData,
|
||||
RequestType requestType)
|
||||
{
|
||||
std::unique_lock lock{m_mutex};
|
||||
|
||||
ensureThreadIsRunning();
|
||||
|
||||
m_entries.emplace_back(std::move(name),
|
||||
std::move(extraId),
|
||||
std::move(captureCallback),
|
||||
std::move(abortCallback),
|
||||
std::move(auxiliaryData),
|
||||
requestType);
|
||||
}
|
||||
|
||||
void AsynchronousImageCache::clearEntries()
|
||||
{
|
||||
std::unique_lock lock{m_mutex};
|
||||
for (Entry &entry : m_entries)
|
||||
entry.abortCallback(ImageCache::AbortReason::Abort);
|
||||
m_entries.clear();
|
||||
}
|
||||
|
||||
std::tuple<std::unique_lock<std::mutex>, bool> AsynchronousImageCache::waitForEntries()
|
||||
{
|
||||
using namespace std::literals::chrono_literals;
|
||||
std::unique_lock lock{m_mutex};
|
||||
if (m_finishing)
|
||||
return {std::move(lock), true};
|
||||
if (m_entries.empty()) {
|
||||
auto timedOutWithoutEntriesOrFinishing = !m_condition.wait_for(lock, 10min, [&] {
|
||||
return m_entries.size() || m_finishing;
|
||||
});
|
||||
|
||||
if (timedOutWithoutEntriesOrFinishing || m_finishing) {
|
||||
m_sleeping = true;
|
||||
return {std::move(lock), true};
|
||||
}
|
||||
}
|
||||
return {std::move(lock), false};
|
||||
}
|
||||
|
||||
void AsynchronousImageCache::stopThread()
|
||||
{
|
||||
std::unique_lock lock{m_mutex};
|
||||
m_finishing = true;
|
||||
}
|
||||
|
||||
void AsynchronousImageCache::ensureThreadIsRunning()
|
||||
{
|
||||
if (m_finishing)
|
||||
return;
|
||||
|
||||
if (!m_sleeping)
|
||||
return;
|
||||
|
||||
if (m_backgroundThread.joinable())
|
||||
m_backgroundThread.join();
|
||||
|
||||
m_sleeping = false;
|
||||
|
||||
m_backgroundThread = std::thread{[this] {
|
||||
while (true) {
|
||||
auto [lock, abort] = waitForEntries();
|
||||
if (abort)
|
||||
return;
|
||||
if (auto entry = getEntry(std::move(lock)); entry) {
|
||||
request(entry->name,
|
||||
entry->extraId,
|
||||
entry->requestType,
|
||||
std::move(entry->captureCallback),
|
||||
std::move(entry->abortCallback),
|
||||
std::move(entry->auxiliaryData),
|
||||
m_storage,
|
||||
m_generator,
|
||||
m_timeStampProvider);
|
||||
}
|
||||
}
|
||||
}};
|
||||
m_taskQueue.clean();
|
||||
}
|
||||
|
||||
} // namespace QmlDesigner
|
||||
|
@@ -20,93 +20,14 @@ AsynchronousImageFactory::AsynchronousImageFactory(ImageCacheStorageInterface &s
|
||||
|
||||
}
|
||||
|
||||
AsynchronousImageFactory::~AsynchronousImageFactory()
|
||||
{
|
||||
clean();
|
||||
wait();
|
||||
}
|
||||
|
||||
void AsynchronousImageFactory::generate(Utils::SmallStringView name,
|
||||
Utils::SmallStringView extraId,
|
||||
ImageCache::AuxiliaryData auxiliaryData)
|
||||
{
|
||||
addEntry(name, extraId, std::move(auxiliaryData));
|
||||
m_condition.notify_all();
|
||||
m_taskQueue.addTask(name, extraId, std::move(auxiliaryData));
|
||||
}
|
||||
|
||||
void AsynchronousImageFactory::addEntry(Utils::SmallStringView name,
|
||||
Utils::SmallStringView extraId,
|
||||
ImageCache::AuxiliaryData &&auxiliaryData)
|
||||
{
|
||||
std::unique_lock lock{m_mutex};
|
||||
|
||||
ensureThreadIsRunning();
|
||||
|
||||
m_entries.emplace_back(std::move(name), std::move(extraId), std::move(auxiliaryData));
|
||||
}
|
||||
|
||||
void AsynchronousImageFactory::ensureThreadIsRunning()
|
||||
{
|
||||
if (m_finishing)
|
||||
return;
|
||||
|
||||
if (!m_sleeping)
|
||||
return;
|
||||
|
||||
if (m_backgroundThread.joinable())
|
||||
m_backgroundThread.join();
|
||||
|
||||
m_sleeping = false;
|
||||
|
||||
m_backgroundThread = std::thread{[this] {
|
||||
while (true) {
|
||||
auto [lock, abort] = waitForEntries();
|
||||
if (abort)
|
||||
return;
|
||||
if (auto entry = getEntry(std::move(lock)); entry) {
|
||||
request(entry->name,
|
||||
entry->extraId,
|
||||
std::move(entry->auxiliaryData),
|
||||
m_storage,
|
||||
m_timeStampProvider,
|
||||
m_collector);
|
||||
}
|
||||
}
|
||||
}};
|
||||
}
|
||||
|
||||
std::tuple<std::unique_lock<std::mutex>, bool> AsynchronousImageFactory::waitForEntries()
|
||||
{
|
||||
using namespace std::literals::chrono_literals;
|
||||
std::unique_lock lock{m_mutex};
|
||||
if (m_finishing)
|
||||
return {std::move(lock), true};
|
||||
if (m_entries.empty()) {
|
||||
auto timedOutWithoutEntriesOrFinishing = !m_condition.wait_for(lock, 10min, [&] {
|
||||
return m_entries.size() || m_finishing;
|
||||
});
|
||||
|
||||
if (timedOutWithoutEntriesOrFinishing || m_finishing) {
|
||||
m_sleeping = true;
|
||||
return {std::move(lock), true};
|
||||
}
|
||||
}
|
||||
return {std::move(lock), false};
|
||||
}
|
||||
|
||||
std::optional<AsynchronousImageFactory::Entry> AsynchronousImageFactory::getEntry(
|
||||
std::unique_lock<std::mutex> lock)
|
||||
{
|
||||
auto l = std::move(lock);
|
||||
|
||||
if (m_entries.empty())
|
||||
return {};
|
||||
|
||||
Entry entry = m_entries.front();
|
||||
m_entries.pop_front();
|
||||
|
||||
return {entry};
|
||||
}
|
||||
AsynchronousImageFactory::~AsynchronousImageFactory() {}
|
||||
|
||||
void AsynchronousImageFactory::request(Utils::SmallStringView name,
|
||||
Utils::SmallStringView extraId,
|
||||
@@ -125,8 +46,8 @@ void AsynchronousImageFactory::request(Utils::SmallStringView name,
|
||||
if (currentModifiedTime < (storageModifiedTime + pause))
|
||||
return;
|
||||
|
||||
auto capture = [=](const QImage &image, const QImage &midSizeImage, const QImage &smallImage) {
|
||||
m_storage.storeImage(id, currentModifiedTime, image, midSizeImage, smallImage);
|
||||
auto capture = [&](const QImage &image, const QImage &midSizeImage, const QImage &smallImage) {
|
||||
storage.storeImage(id, currentModifiedTime, image, midSizeImage, smallImage);
|
||||
};
|
||||
|
||||
collector.start(name,
|
||||
@@ -138,27 +59,6 @@ void AsynchronousImageFactory::request(Utils::SmallStringView name,
|
||||
|
||||
void AsynchronousImageFactory::clean()
|
||||
{
|
||||
clearEntries();
|
||||
m_taskQueue.clean();
|
||||
}
|
||||
|
||||
void AsynchronousImageFactory::wait()
|
||||
{
|
||||
stopThread();
|
||||
m_condition.notify_all();
|
||||
if (m_backgroundThread.joinable())
|
||||
m_backgroundThread.join();
|
||||
}
|
||||
|
||||
void AsynchronousImageFactory::clearEntries()
|
||||
{
|
||||
std::unique_lock lock{m_mutex};
|
||||
m_entries.clear();
|
||||
}
|
||||
|
||||
void AsynchronousImageFactory::stopThread()
|
||||
{
|
||||
std::unique_lock lock{m_mutex};
|
||||
m_finishing = true;
|
||||
}
|
||||
|
||||
} // namespace QmlDesigner
|
||||
|
@@ -5,6 +5,8 @@
|
||||
|
||||
#include "imagecacheauxiliarydata.h"
|
||||
|
||||
#include <imagecache/taskqueue.h>
|
||||
|
||||
#include <utils/smallstring.h>
|
||||
|
||||
#include <condition_variable>
|
||||
@@ -50,32 +52,41 @@ private:
|
||||
ImageCache::AuxiliaryData auxiliaryData;
|
||||
};
|
||||
|
||||
void addEntry(Utils::SmallStringView name,
|
||||
Utils::SmallStringView extraId,
|
||||
ImageCache::AuxiliaryData &&auxiliaryData);
|
||||
void ensureThreadIsRunning();
|
||||
[[nodiscard]] std::tuple<std::unique_lock<std::mutex>, bool> waitForEntries();
|
||||
[[nodiscard]] std::optional<Entry> getEntry(std::unique_lock<std::mutex> lock);
|
||||
void request(Utils::SmallStringView name,
|
||||
Utils::SmallStringView extraId,
|
||||
ImageCache::AuxiliaryData auxiliaryData,
|
||||
ImageCacheStorageInterface &storage,
|
||||
TimeStampProviderInterface &timeStampProvider,
|
||||
ImageCacheCollectorInterface &collector);
|
||||
void wait();
|
||||
void clearEntries();
|
||||
void stopThread();
|
||||
static void request(Utils::SmallStringView name,
|
||||
Utils::SmallStringView extraId,
|
||||
ImageCache::AuxiliaryData auxiliaryData,
|
||||
ImageCacheStorageInterface &storage,
|
||||
TimeStampProviderInterface &timeStampProvider,
|
||||
ImageCacheCollectorInterface &collector);
|
||||
|
||||
struct Dispatch
|
||||
{
|
||||
void operator()(Entry &entry)
|
||||
{
|
||||
request(entry.name,
|
||||
entry.extraId,
|
||||
std::move(entry.auxiliaryData),
|
||||
storage,
|
||||
timeStampProvider,
|
||||
collector);
|
||||
}
|
||||
|
||||
ImageCacheStorageInterface &storage;
|
||||
TimeStampProviderInterface &timeStampProvider;
|
||||
ImageCacheCollectorInterface &collector;
|
||||
};
|
||||
|
||||
struct Clean
|
||||
{
|
||||
void operator()(Entry &) {}
|
||||
};
|
||||
|
||||
private:
|
||||
std::deque<Entry> m_entries;
|
||||
std::mutex m_mutex;
|
||||
std::condition_variable m_condition;
|
||||
std::thread m_backgroundThread;
|
||||
ImageCacheStorageInterface &m_storage;
|
||||
TimeStampProviderInterface &m_timeStampProvider;
|
||||
ImageCacheCollectorInterface &m_collector;
|
||||
bool m_finishing{false};
|
||||
bool m_sleeping{true};
|
||||
TaskQueue<Entry, Dispatch, Clean> m_taskQueue{Dispatch{m_storage, m_timeStampProvider, m_collector},
|
||||
Clean{}};
|
||||
};
|
||||
|
||||
} // namespace QmlDesigner
|
||||
|
133
src/plugins/qmldesigner/designercore/imagecache/taskqueue.h
Normal file
133
src/plugins/qmldesigner/designercore/imagecache/taskqueue.h
Normal file
@@ -0,0 +1,133 @@
|
||||
// Copyright (C) 2023 The Qt Company Ltd.
|
||||
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <condition_variable>
|
||||
#include <deque>
|
||||
#include <mutex>
|
||||
#include <thread>
|
||||
|
||||
namespace QmlDesigner {
|
||||
|
||||
template<typename Task, typename DispatchCallback, typename ClearCallback>
|
||||
class TaskQueue
|
||||
{
|
||||
public:
|
||||
TaskQueue(DispatchCallback dispatchCallback, ClearCallback clearCallback)
|
||||
: m_dispatchCallback(std::move(dispatchCallback))
|
||||
, m_clearCallback(std::move(clearCallback))
|
||||
{}
|
||||
|
||||
~TaskQueue()
|
||||
{
|
||||
auto lock = clearTasks();
|
||||
stopThread(std::move(lock));
|
||||
joinThread();
|
||||
}
|
||||
|
||||
template<typename... Arguments>
|
||||
void addTask(Arguments &&...arguments)
|
||||
{
|
||||
{
|
||||
std::unique_lock lock{m_mutex};
|
||||
|
||||
ensureThreadIsRunning();
|
||||
|
||||
m_tasks.emplace_back(std::forward<Arguments>(arguments)...);
|
||||
}
|
||||
m_condition.notify_all();
|
||||
}
|
||||
|
||||
void clean() { clearTasks(); }
|
||||
|
||||
private:
|
||||
[[nodiscard]] std::tuple<std::unique_lock<std::mutex>, bool> waitForTasks()
|
||||
{
|
||||
using namespace std::literals::chrono_literals;
|
||||
std::unique_lock lock{m_mutex};
|
||||
if (m_finishing)
|
||||
return {std::move(lock), true};
|
||||
if (m_tasks.empty()) {
|
||||
auto timedOutWithoutEntriesOrFinishing = !m_condition.wait_for(lock, 10min, [&] {
|
||||
return m_tasks.size() || m_finishing;
|
||||
});
|
||||
|
||||
if (timedOutWithoutEntriesOrFinishing || m_finishing) {
|
||||
m_sleeping = true;
|
||||
return {std::move(lock), true};
|
||||
}
|
||||
}
|
||||
return {std::move(lock), false};
|
||||
}
|
||||
|
||||
[[nodiscard]] std::optional<Task> getTask(std::unique_lock<std::mutex> lock)
|
||||
{
|
||||
auto l = std::move(lock);
|
||||
|
||||
if (m_tasks.empty())
|
||||
return {};
|
||||
|
||||
Task task = std::move(m_tasks.front());
|
||||
m_tasks.pop_front();
|
||||
|
||||
return {task};
|
||||
}
|
||||
|
||||
void ensureThreadIsRunning()
|
||||
{
|
||||
if (m_finishing || !m_sleeping)
|
||||
return;
|
||||
|
||||
if (m_backgroundThread.joinable())
|
||||
m_backgroundThread.join();
|
||||
|
||||
m_sleeping = false;
|
||||
|
||||
m_backgroundThread = std::thread{[this] {
|
||||
while (true) {
|
||||
auto [lock, abort] = waitForTasks();
|
||||
if (abort)
|
||||
return;
|
||||
if (auto task = getTask(std::move(lock)); task)
|
||||
m_dispatchCallback(*task);
|
||||
}
|
||||
}};
|
||||
}
|
||||
|
||||
std::unique_lock<std::mutex> clearTasks()
|
||||
{
|
||||
std::unique_lock lock{m_mutex};
|
||||
for (Task &task : m_tasks)
|
||||
m_clearCallback(task);
|
||||
m_tasks.clear();
|
||||
|
||||
return lock;
|
||||
}
|
||||
|
||||
void stopThread(std::unique_lock<std::mutex> lock)
|
||||
{
|
||||
{
|
||||
auto l = std::move(lock);
|
||||
m_finishing = true;
|
||||
}
|
||||
m_condition.notify_all();
|
||||
}
|
||||
|
||||
void joinThread()
|
||||
{
|
||||
if (m_backgroundThread.joinable())
|
||||
m_backgroundThread.join();
|
||||
}
|
||||
|
||||
private:
|
||||
std::deque<Task> m_tasks;
|
||||
std::mutex m_mutex;
|
||||
std::condition_variable m_condition;
|
||||
std::thread m_backgroundThread;
|
||||
DispatchCallback m_dispatchCallback;
|
||||
ClearCallback m_clearCallback;
|
||||
bool m_finishing{false};
|
||||
bool m_sleeping{true};
|
||||
};
|
||||
} // namespace QmlDesigner
|
@@ -5,6 +5,8 @@
|
||||
|
||||
#include "asynchronousimagecacheinterface.h"
|
||||
|
||||
#include <imagecache/taskqueue.h>
|
||||
|
||||
#include <condition_variable>
|
||||
#include <deque>
|
||||
#include <functional>
|
||||
@@ -62,16 +64,6 @@ private:
|
||||
RequestType requestType = RequestType::Image;
|
||||
};
|
||||
|
||||
[[nodiscard]] std::optional<RequestEntry> getEntry(std::unique_lock<std::mutex> lock);
|
||||
void addEntry(Utils::PathString &&name,
|
||||
Utils::SmallString &&extraId,
|
||||
ImageCache::CaptureImageCallback &&captureCallback,
|
||||
ImageCache::AbortCallback &&abortCallback,
|
||||
RequestType requestType);
|
||||
void clearEntries();
|
||||
[[nodiscard]] std::tuple<std::unique_lock<std::mutex>, bool> waitForEntries();
|
||||
void stopThread();
|
||||
void ensureThreadIsRunning();
|
||||
static void request(Utils::SmallStringView name,
|
||||
Utils::SmallStringView extraId,
|
||||
AsynchronousExplicitImageCache::RequestType requestType,
|
||||
@@ -79,8 +71,28 @@ private:
|
||||
ImageCache::AbortCallback abortCallback,
|
||||
ImageCacheStorageInterface &storage);
|
||||
|
||||
private:
|
||||
void wait();
|
||||
struct Dispatch
|
||||
{
|
||||
void operator()(RequestEntry &entry)
|
||||
{
|
||||
request(entry.name,
|
||||
entry.extraId,
|
||||
entry.requestType,
|
||||
std::move(entry.captureCallback),
|
||||
std::move(entry.abortCallback),
|
||||
storage);
|
||||
}
|
||||
|
||||
ImageCacheStorageInterface &storage;
|
||||
};
|
||||
|
||||
struct Clean
|
||||
{
|
||||
void operator()(RequestEntry &entry)
|
||||
{
|
||||
entry.abortCallback(ImageCache::AbortReason::Abort);
|
||||
}
|
||||
};
|
||||
|
||||
private:
|
||||
std::deque<RequestEntry> m_requestEntries;
|
||||
@@ -88,8 +100,7 @@ private:
|
||||
std::condition_variable m_condition;
|
||||
std::thread m_backgroundThread;
|
||||
ImageCacheStorageInterface &m_storage;
|
||||
bool m_finishing{false};
|
||||
bool m_sleeping{true};
|
||||
TaskQueue<RequestEntry, Dispatch, Clean> m_taskQueue{Dispatch{m_storage}, Clean{}};
|
||||
};
|
||||
|
||||
} // namespace QmlDesigner
|
||||
|
@@ -5,6 +5,8 @@
|
||||
|
||||
#include "asynchronousimagecacheinterface.h"
|
||||
|
||||
#include <imagecache/taskqueue.h>
|
||||
|
||||
#include <condition_variable>
|
||||
#include <deque>
|
||||
#include <functional>
|
||||
@@ -73,17 +75,6 @@ private:
|
||||
RequestType requestType = RequestType::Image;
|
||||
};
|
||||
|
||||
[[nodiscard]] std::optional<Entry> getEntry(std::unique_lock<std::mutex> lock);
|
||||
void addEntry(Utils::PathString &&name,
|
||||
Utils::SmallString &&extraId,
|
||||
ImageCache::CaptureImageCallback &&captureCallback,
|
||||
ImageCache::AbortCallback &&abortCallback,
|
||||
ImageCache::AuxiliaryData &&auxiliaryData,
|
||||
RequestType requestType);
|
||||
void clearEntries();
|
||||
[[nodiscard]] std::tuple<std::unique_lock<std::mutex>, bool> waitForEntries();
|
||||
void stopThread();
|
||||
void ensureThreadIsRunning();
|
||||
static void request(Utils::SmallStringView name,
|
||||
Utils::SmallStringView extraId,
|
||||
AsynchronousImageCache::RequestType requestType,
|
||||
@@ -94,19 +85,37 @@ private:
|
||||
ImageCacheGeneratorInterface &generator,
|
||||
TimeStampProviderInterface &timeStampProvider);
|
||||
|
||||
private:
|
||||
void wait();
|
||||
struct Dispatch
|
||||
{
|
||||
void operator()(Entry &entry)
|
||||
{
|
||||
request(entry.name,
|
||||
entry.extraId,
|
||||
entry.requestType,
|
||||
std::move(entry.captureCallback),
|
||||
std::move(entry.abortCallback),
|
||||
std::move(entry.auxiliaryData),
|
||||
storage,
|
||||
generator,
|
||||
timeStampProvider);
|
||||
}
|
||||
|
||||
ImageCacheStorageInterface &storage;
|
||||
ImageCacheGeneratorInterface &generator;
|
||||
TimeStampProviderInterface &timeStampProvider;
|
||||
};
|
||||
|
||||
struct Clean
|
||||
{
|
||||
void operator()(Entry &entry) { entry.abortCallback(ImageCache::AbortReason::Abort); }
|
||||
};
|
||||
|
||||
private:
|
||||
std::deque<Entry> m_entries;
|
||||
mutable std::mutex m_mutex;
|
||||
std::condition_variable m_condition;
|
||||
std::thread m_backgroundThread;
|
||||
ImageCacheStorageInterface &m_storage;
|
||||
ImageCacheGeneratorInterface &m_generator;
|
||||
TimeStampProviderInterface &m_timeStampProvider;
|
||||
bool m_finishing{false};
|
||||
bool m_sleeping{true};
|
||||
TaskQueue<Entry, Dispatch, Clean> m_taskQueue{Dispatch{m_storage, m_generator, m_timeStampProvider},
|
||||
Clean{}};
|
||||
};
|
||||
|
||||
} // namespace QmlDesigner
|
||||
|
@@ -52,6 +52,7 @@ add_qtc_library(TestDesignerCore OBJECT
|
||||
imagecache/imagecachedispatchcollector.h
|
||||
imagecache/imagecachestorageinterface.h
|
||||
imagecache/synchronousimagecache.cpp
|
||||
imagecache/taskqueue.h
|
||||
imagecache/timestampproviderinterface.h
|
||||
include/abstractproperty.h
|
||||
include/asynchronousexplicitimagecache.h
|
||||
|
@@ -8,4 +8,5 @@ extend_qtc_test(unittest
|
||||
imagecachegenerator-test.cpp
|
||||
imagecachestorage-test.cpp
|
||||
synchronousimagecache-test.cpp
|
||||
taskqueue-test.cpp
|
||||
)
|
||||
|
103
tests/unit/tests/unittests/imagecache/taskqueue-test.cpp
Normal file
103
tests/unit/tests/unittests/imagecache/taskqueue-test.cpp
Normal file
@@ -0,0 +1,103 @@
|
||||
// Copyright (C) 2023 The Qt Company Ltd.
|
||||
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
|
||||
|
||||
#include "../utils/googletest.h"
|
||||
#include "../utils/notification.h"
|
||||
|
||||
#include <imagecache/taskqueue.h>
|
||||
|
||||
namespace {
|
||||
struct Task
|
||||
{
|
||||
Task(int i)
|
||||
: i{i}
|
||||
{}
|
||||
|
||||
int i = 5;
|
||||
|
||||
friend bool operator==(Task first, Task second) { return first.i == second.i; }
|
||||
};
|
||||
|
||||
template<typename Matcher>
|
||||
auto IsTask(Matcher matcher)
|
||||
{
|
||||
return Field(&Task::i, matcher);
|
||||
}
|
||||
|
||||
class TaskQueue : public testing::Test
|
||||
{
|
||||
protected:
|
||||
Notification notification;
|
||||
Notification waitInThread;
|
||||
NiceMock<MockFunction<void(Task &task)>> mockDispatchCallback;
|
||||
NiceMock<MockFunction<void(Task &task)>> mockCleanCallback;
|
||||
using Queue = QmlDesigner::TaskQueue<Task,
|
||||
decltype(mockDispatchCallback.AsStdFunction()),
|
||||
decltype(mockCleanCallback.AsStdFunction())>;
|
||||
};
|
||||
|
||||
TEST_F(TaskQueue, add_task_dispatches_task)
|
||||
{
|
||||
Queue queue{mockDispatchCallback.AsStdFunction(), mockCleanCallback.AsStdFunction()};
|
||||
|
||||
EXPECT_CALL(mockDispatchCallback, Call(IsTask(22))).WillRepeatedly([&](Task) {
|
||||
notification.notify();
|
||||
});
|
||||
|
||||
queue.addTask(22);
|
||||
notification.wait();
|
||||
}
|
||||
|
||||
TEST_F(TaskQueue, depatches_task_in_order)
|
||||
{
|
||||
InSequence s;
|
||||
Queue queue{mockDispatchCallback.AsStdFunction(), mockCleanCallback.AsStdFunction()};
|
||||
|
||||
EXPECT_CALL(mockDispatchCallback, Call(IsTask(22))).WillRepeatedly([&](Task) {});
|
||||
EXPECT_CALL(mockDispatchCallback, Call(IsTask(32))).WillRepeatedly([&](Task) {
|
||||
notification.notify();
|
||||
});
|
||||
|
||||
queue.addTask(22);
|
||||
queue.addTask(32);
|
||||
notification.wait();
|
||||
}
|
||||
|
||||
TEST_F(TaskQueue, cleanup_at_destruction)
|
||||
{
|
||||
InSequence s;
|
||||
ON_CALL(mockDispatchCallback, Call(IsTask(22))).WillByDefault([&](Task) {
|
||||
notification.notify();
|
||||
waitInThread.wait();
|
||||
});
|
||||
|
||||
EXPECT_CALL(mockCleanCallback, Call(IsTask(32))).WillRepeatedly([&](Task) {});
|
||||
|
||||
{
|
||||
Queue queue{mockDispatchCallback.AsStdFunction(), mockCleanCallback.AsStdFunction()};
|
||||
queue.addTask(22);
|
||||
queue.addTask(32);
|
||||
notification.wait();
|
||||
waitInThread.notify();
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(TaskQueue, clean_task_in_queue)
|
||||
{
|
||||
InSequence s;
|
||||
ON_CALL(mockDispatchCallback, Call(IsTask(22))).WillByDefault([&](Task) {
|
||||
notification.notify();
|
||||
waitInThread.wait();
|
||||
});
|
||||
Queue queue{mockDispatchCallback.AsStdFunction(), mockCleanCallback.AsStdFunction()};
|
||||
queue.addTask(22);
|
||||
queue.addTask(32);
|
||||
|
||||
EXPECT_CALL(mockCleanCallback, Call(IsTask(32))).WillRepeatedly([&](Task) {});
|
||||
|
||||
notification.wait();
|
||||
waitInThread.notify();
|
||||
queue.clean();
|
||||
}
|
||||
|
||||
} // namespace
|
Reference in New Issue
Block a user