Qt: Unify worker threads
Vicki Pfau vi@endrift.com
Tue, 17 Oct 2017 21:39:12 -0700
4 files changed,
134 insertions(+),
79 deletions(-)
M
src/platform/qt/GBAApp.cpp
→
src/platform/qt/GBAApp.cpp
@@ -67,10 +67,7 @@ }
} GBAApp::~GBAApp() { -#ifdef USE_SQLITE3 - m_parseThread.quit(); - m_parseThread.wait(); -#endif + m_workerThreads.waitForDone(); } bool GBAApp::event(QEvent* event) {@@ -180,14 +177,8 @@ if (db && m_db) {
NoIntroDBDestroy(m_db); } if (db) { - if (m_parseThread.isRunning()) { - m_parseThread.quit(); - m_parseThread.wait(); - } GameDBParser* parser = new GameDBParser(db); - m_parseThread.start(); - parser->moveToThread(&m_parseThread); - QMetaObject::invokeMethod(parser, "parseNoIntroDB"); + submitWorkerJob(std::bind(&GameDBParser::parseNoIntroDB, parser)); m_db = db; return true; }@@ -198,6 +189,77 @@ bool GBAApp::reloadGameDB() {
return false; } #endif + +qint64 GBAApp::submitWorkerJob(std::function<void ()> job, std::function<void ()> callback) { + return submitWorkerJob(job, nullptr, callback); +} + +qint64 GBAApp::submitWorkerJob(std::function<void ()> job, QObject* context, std::function<void ()> callback) { + qint64 jobId = m_nextJob; + ++m_nextJob; + WorkerJob* jobRunnable = new WorkerJob(jobId, job, this); + m_workerJobs.insert(jobId, jobRunnable); + if (callback) { + waitOnJob(jobId, context, callback); + } + m_workerThreads.start(jobRunnable); + return jobId; +} + +bool GBAApp::removeWorkerJob(qint64 jobId) { + for (auto& job : m_workerJobCallbacks.values(jobId)) { + disconnect(job); + } + m_workerJobCallbacks.remove(jobId); + if (!m_workerJobs.contains(jobId)) { + return true; + } + bool success = false; +#if (QT_VERSION >= QT_VERSION_CHECK(5, 9, 0)) + success = m_workerThreads.tryTake(m_workerJobs[jobId]); +#endif + if (success) { + m_workerJobs.remove(jobId); + } + return success; +} + + +bool GBAApp::waitOnJob(qint64 jobId, QObject* context, std::function<void ()> callback) { + if (!m_workerJobs.contains(jobId)) { + return false; + } + if (!context) { + context = this; + } + QMetaObject::Connection connection = connect(this, &GBAApp::jobFinished, context, [jobId, callback](qint64 testedJobId) { + if (jobId != testedJobId) { + return; + } + callback(); + }); + m_workerJobCallbacks.insert(m_nextJob, connection); + return true; +} + +void GBAApp::finishJob(qint64 jobId) { + m_workerJobs.remove(jobId); + emit jobFinished(jobId); + m_workerJobCallbacks.remove(jobId); +} + +GBAApp::WorkerJob::WorkerJob(qint64 id, std::function<void ()> job, GBAApp* owner) + : m_id(id) + , m_job(job) + , m_owner(owner) +{ + setAutoDelete(true); +} + +void GBAApp::WorkerJob::run() { + m_job(); + QMetaObject::invokeMethod(m_owner, "finishJob", Q_ARG(qint64, m_id)); +} #ifdef USE_SQLITE3 GameDBParser::GameDBParser(NoIntroDB* db, QObject* parent)
M
src/platform/qt/GBAApp.h
→
src/platform/qt/GBAApp.h
@@ -8,9 +8,14 @@
#include <QApplication> #include <QFileDialog> #include <QList> +#include <QMap> +#include <QMultiMap> #include <QObject> +#include <QRunnable> #include <QString> -#include <QThread> +#include <QThreadPool> + +#include <functional> #include "CoreManager.h" #include "MultiplayerController.h"@@ -61,10 +66,34 @@
const NoIntroDB* gameDB() const { return m_db; } bool reloadGameDB(); + qint64 submitWorkerJob(std::function<void ()> job, std::function<void ()> callback = {}); + qint64 submitWorkerJob(std::function<void ()> job, QObject* context, std::function<void ()> callback); + bool removeWorkerJob(qint64 jobId); + bool waitOnJob(qint64 jobId, QObject* context, std::function<void ()> callback); + +signals: + void jobFinished(qint64 jobId); + protected: bool event(QEvent*); +private slots: + void finishJob(qint64 jobId); + private: + class WorkerJob : public QRunnable { + public: + WorkerJob(qint64 id, std::function<void ()> job, GBAApp* owner); + + public: + void run() override; + + private: + qint64 m_id; + std::function<void ()> m_job; + GBAApp* m_owner; + }; + Window* newWindowInternal(); void pauseAll(QList<Window*>* paused);@@ -75,10 +104,12 @@ QList<Window*> m_windows;
MultiplayerController m_multiplayer; CoreManager m_manager; + QMap<qint64, WorkerJob*> m_workerJobs; + QMultiMap<qint64, QMetaObject::Connection> m_workerJobCallbacks; + QThreadPool m_workerThreads; + qint64 m_nextJob = 1; + NoIntroDB* m_db = nullptr; -#ifdef USE_SQLITE3 - QThread m_parseThread; -#endif }; }
M
src/platform/qt/library/LibraryController.cpp
→
src/platform/qt/library/LibraryController.cpp
@@ -29,16 +29,6 @@ removeEntry(o);
} } -LibraryLoaderThread::LibraryLoaderThread(QObject* parent) - : QThread(parent) -{ -} - -void LibraryLoaderThread::run() { - mLibraryLoadDirectory(m_library, m_directory.toUtf8().constData()); - m_directory = QString(); -} - LibraryController::LibraryController(QWidget* parent, const QString& path, ConfigController* config) : QStackedWidget(parent) , m_config(config)@@ -47,21 +37,19 @@ mLibraryListingInit(&m_listing, 0);
if (!path.isNull()) { // This can return NULL if the library is already open - m_library = mLibraryLoad(path.toUtf8().constData()); + m_library = std::shared_ptr<mLibrary>(mLibraryLoad(path.toUtf8().constData()), mLibraryDestroy); } if (!m_library) { - m_library = mLibraryCreateEmpty(); + m_library = std::shared_ptr<mLibrary>(mLibraryCreateEmpty(), mLibraryDestroy); } - mLibraryAttachGameDB(m_library, GBAApp::app()->gameDB()); + mLibraryAttachGameDB(m_library.get(), GBAApp::app()->gameDB()); m_libraryTree = new LibraryTree(this); addWidget(m_libraryTree->widget()); m_libraryGrid = new LibraryGrid(this); addWidget(m_libraryGrid->widget()); - - connect(&m_loaderThread, &QThread::finished, this, &LibraryController::refresh, Qt::QueuedConnection); setViewStyle(LibraryStyle::STYLE_LIST); refresh();@@ -69,17 +57,6 @@ }
LibraryController::~LibraryController() { mLibraryListingDeinit(&m_listing); - - if (m_loaderThread.isRunning()) { - m_loaderThread.wait(); - } - if (!m_loaderThread.isRunning() && m_loaderThread.m_library) { - m_library = m_loaderThread.m_library; - m_loaderThread.m_library = nullptr; - } - if (m_library) { - mLibraryDestroy(m_library); - } } void LibraryController::setViewStyle(LibraryStyle newStyle) {@@ -117,7 +94,7 @@
VFile* LibraryController::selectedVFile() { LibraryEntryRef entry = selectedEntry(); if (entry) { - return mLibraryOpenVFile(m_library, entry->entry); + return mLibraryOpenVFile(m_library.get(), entry->entry); } else { return nullptr; }@@ -129,35 +106,26 @@ return e ? qMakePair(e->base(), e->filename()) : qMakePair<QString, QString>("", "");
} void LibraryController::addDirectory(const QString& dir) { - m_loaderThread.m_directory = dir; - m_loaderThread.m_library = m_library; - // The m_loaderThread temporarily owns the library - m_library = nullptr; - m_loaderThread.start(); + // The worker thread temporarily owns the library + std::shared_ptr<mLibrary> library = m_library; + m_libraryJob = GBAApp::app()->submitWorkerJob(std::bind(&LibraryController::loadDirectory, this, dir), this, [this, library]() { + m_libraryJob = -1; + refresh(); + }); } void LibraryController::clear() { - if (!m_library) { - if (!m_loaderThread.isRunning() && m_loaderThread.m_library) { - m_library = m_loaderThread.m_library; - m_loaderThread.m_library = nullptr; - } else { - return; - } + if (m_libraryJob > 0) { + return; } - mLibraryClear(m_library); + mLibraryClear(m_library.get()); refresh(); } void LibraryController::refresh() { - if (!m_library) { - if (!m_loaderThread.isRunning() && m_loaderThread.m_library) { - m_library = m_loaderThread.m_library; - m_loaderThread.m_library = nullptr; - } else { - return; - } + if (m_libraryJob > 0) { + return; } setDisabled(true);@@ -166,7 +134,7 @@ QStringList allEntries;
QList<LibraryEntryRef> newEntries; mLibraryListingClear(&m_listing); - mLibraryGetEntries(m_library, &m_listing, 0, 0, nullptr); + mLibraryGetEntries(m_library.get(), &m_listing, 0, 0, nullptr); for (size_t i = 0; i < mLibraryListingSize(&m_listing); i++) { mLibraryEntry* entry = mLibraryListingGetPointer(&m_listing, i); QString fullpath = QString("%1/%2").arg(entry->base, entry->filename);@@ -208,6 +176,12 @@ const QString lastfile = m_config->getMRU().first();
if (m_entries.contains(lastfile)) { selectEntry(m_entries.value(lastfile)); } +} + +void LibraryController::loadDirectory(const QString& dir) { + // This class can get delted during this function (sigh) so we need to hold onto this + std::shared_ptr<mLibrary> library = m_library; + mLibraryLoadDirectory(library.get(), dir.toUtf8().constData()); } }
M
src/platform/qt/library/LibraryController.h
→
src/platform/qt/library/LibraryController.h
@@ -9,7 +9,6 @@ #include <memory>
#include <QList> #include <QMap> -#include <QThread> #include <QStackedWidget> #include <mgba/core/library.h>@@ -66,19 +65,6 @@
virtual QWidget* widget() = 0; }; -class LibraryLoaderThread final : public QThread { -Q_OBJECT - -public: - LibraryLoaderThread(QObject* parent = nullptr); - - mLibrary* m_library = nullptr; - QString m_directory; - -protected: - virtual void run() override; -}; - class LibraryController final : public QStackedWidget { Q_OBJECT@@ -110,9 +96,11 @@ private slots:
void refresh(); private: + void loadDirectory(const QString&); // Called on separate thread + ConfigController* m_config = nullptr; - LibraryLoaderThread m_loaderThread; - mLibrary* m_library = nullptr; + std::shared_ptr<mLibrary> m_library; + qint64 m_libraryJob = -1; mLibraryListing m_listing; QMap<QString, LibraryEntryRef> m_entries;