Qt: Unify worker threads

This commit is contained in:
Vicki Pfau 2017-10-17 21:39:12 -07:00
parent 7ebd2d6e75
commit c94aff135f
4 changed files with 134 additions and 79 deletions

View File

@ -67,10 +67,7 @@ GBAApp::GBAApp(int& argc, char* argv[], ConfigController* config)
} }
GBAApp::~GBAApp() { GBAApp::~GBAApp() {
#ifdef USE_SQLITE3 m_workerThreads.waitForDone();
m_parseThread.quit();
m_parseThread.wait();
#endif
} }
bool GBAApp::event(QEvent* event) { bool GBAApp::event(QEvent* event) {
@ -180,14 +177,8 @@ bool GBAApp::reloadGameDB() {
NoIntroDBDestroy(m_db); NoIntroDBDestroy(m_db);
} }
if (db) { if (db) {
if (m_parseThread.isRunning()) {
m_parseThread.quit();
m_parseThread.wait();
}
GameDBParser* parser = new GameDBParser(db); GameDBParser* parser = new GameDBParser(db);
m_parseThread.start(); submitWorkerJob(std::bind(&GameDBParser::parseNoIntroDB, parser));
parser->moveToThread(&m_parseThread);
QMetaObject::invokeMethod(parser, "parseNoIntroDB");
m_db = db; m_db = db;
return true; return true;
} }
@ -199,6 +190,77 @@ bool GBAApp::reloadGameDB() {
} }
#endif #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 #ifdef USE_SQLITE3
GameDBParser::GameDBParser(NoIntroDB* db, QObject* parent) GameDBParser::GameDBParser(NoIntroDB* db, QObject* parent)
: QObject(parent) : QObject(parent)

View File

@ -8,9 +8,14 @@
#include <QApplication> #include <QApplication>
#include <QFileDialog> #include <QFileDialog>
#include <QList> #include <QList>
#include <QMap>
#include <QMultiMap>
#include <QObject> #include <QObject>
#include <QRunnable>
#include <QString> #include <QString>
#include <QThread> #include <QThreadPool>
#include <functional>
#include "CoreManager.h" #include "CoreManager.h"
#include "MultiplayerController.h" #include "MultiplayerController.h"
@ -61,10 +66,34 @@ public:
const NoIntroDB* gameDB() const { return m_db; } const NoIntroDB* gameDB() const { return m_db; }
bool reloadGameDB(); 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: protected:
bool event(QEvent*); bool event(QEvent*);
private slots:
void finishJob(qint64 jobId);
private: 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(); Window* newWindowInternal();
void pauseAll(QList<Window*>* paused); void pauseAll(QList<Window*>* paused);
@ -75,10 +104,12 @@ private:
MultiplayerController m_multiplayer; MultiplayerController m_multiplayer;
CoreManager m_manager; 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; NoIntroDB* m_db = nullptr;
#ifdef USE_SQLITE3
QThread m_parseThread;
#endif
}; };
} }

View File

@ -29,16 +29,6 @@ void AbstractGameList::removeEntries(QList<LibraryEntryRef> items) {
} }
} }
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) LibraryController::LibraryController(QWidget* parent, const QString& path, ConfigController* config)
: QStackedWidget(parent) : QStackedWidget(parent)
, m_config(config) , m_config(config)
@ -47,13 +37,13 @@ LibraryController::LibraryController(QWidget* parent, const QString& path, Confi
if (!path.isNull()) { if (!path.isNull()) {
// This can return NULL if the library is already open // 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) { 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); m_libraryTree = new LibraryTree(this);
addWidget(m_libraryTree->widget()); addWidget(m_libraryTree->widget());
@ -61,25 +51,12 @@ LibraryController::LibraryController(QWidget* parent, const QString& path, Confi
m_libraryGrid = new LibraryGrid(this); m_libraryGrid = new LibraryGrid(this);
addWidget(m_libraryGrid->widget()); addWidget(m_libraryGrid->widget());
connect(&m_loaderThread, &QThread::finished, this, &LibraryController::refresh, Qt::QueuedConnection);
setViewStyle(LibraryStyle::STYLE_LIST); setViewStyle(LibraryStyle::STYLE_LIST);
refresh(); refresh();
} }
LibraryController::~LibraryController() { LibraryController::~LibraryController() {
mLibraryListingDeinit(&m_listing); 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) { void LibraryController::setViewStyle(LibraryStyle newStyle) {
@ -117,7 +94,7 @@ LibraryEntryRef LibraryController::selectedEntry() {
VFile* LibraryController::selectedVFile() { VFile* LibraryController::selectedVFile() {
LibraryEntryRef entry = selectedEntry(); LibraryEntryRef entry = selectedEntry();
if (entry) { if (entry) {
return mLibraryOpenVFile(m_library, entry->entry); return mLibraryOpenVFile(m_library.get(), entry->entry);
} else { } else {
return nullptr; return nullptr;
} }
@ -129,35 +106,26 @@ QPair<QString, QString> LibraryController::selectedPath() {
} }
void LibraryController::addDirectory(const QString& dir) { void LibraryController::addDirectory(const QString& dir) {
m_loaderThread.m_directory = dir; // The worker thread temporarily owns the library
m_loaderThread.m_library = m_library; std::shared_ptr<mLibrary> library = m_library;
// The m_loaderThread temporarily owns the library m_libraryJob = GBAApp::app()->submitWorkerJob(std::bind(&LibraryController::loadDirectory, this, dir), this, [this, library]() {
m_library = nullptr; m_libraryJob = -1;
m_loaderThread.start(); refresh();
});
} }
void LibraryController::clear() { void LibraryController::clear() {
if (!m_library) { if (m_libraryJob > 0) {
if (!m_loaderThread.isRunning() && m_loaderThread.m_library) { return;
m_library = m_loaderThread.m_library;
m_loaderThread.m_library = nullptr;
} else {
return;
}
} }
mLibraryClear(m_library); mLibraryClear(m_library.get());
refresh(); refresh();
} }
void LibraryController::refresh() { void LibraryController::refresh() {
if (!m_library) { if (m_libraryJob > 0) {
if (!m_loaderThread.isRunning() && m_loaderThread.m_library) { return;
m_library = m_loaderThread.m_library;
m_loaderThread.m_library = nullptr;
} else {
return;
}
} }
setDisabled(true); setDisabled(true);
@ -166,7 +134,7 @@ void LibraryController::refresh() {
QList<LibraryEntryRef> newEntries; QList<LibraryEntryRef> newEntries;
mLibraryListingClear(&m_listing); 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++) { for (size_t i = 0; i < mLibraryListingSize(&m_listing); i++) {
mLibraryEntry* entry = mLibraryListingGetPointer(&m_listing, i); mLibraryEntry* entry = mLibraryListingGetPointer(&m_listing, i);
QString fullpath = QString("%1/%2").arg(entry->base, entry->filename); QString fullpath = QString("%1/%2").arg(entry->base, entry->filename);
@ -210,4 +178,10 @@ void LibraryController::selectLastBootedGame() {
} }
} }
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());
}
} }

View File

@ -9,7 +9,6 @@
#include <QList> #include <QList>
#include <QMap> #include <QMap>
#include <QThread>
#include <QStackedWidget> #include <QStackedWidget>
#include <mgba/core/library.h> #include <mgba/core/library.h>
@ -66,19 +65,6 @@ public:
virtual QWidget* widget() = 0; 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 { class LibraryController final : public QStackedWidget {
Q_OBJECT Q_OBJECT
@ -110,9 +96,11 @@ private slots:
void refresh(); void refresh();
private: private:
void loadDirectory(const QString&); // Called on separate thread
ConfigController* m_config = nullptr; ConfigController* m_config = nullptr;
LibraryLoaderThread m_loaderThread; std::shared_ptr<mLibrary> m_library;
mLibrary* m_library = nullptr; qint64 m_libraryJob = -1;
mLibraryListing m_listing; mLibraryListing m_listing;
QMap<QString, LibraryEntryRef> m_entries; QMap<QString, LibraryEntryRef> m_entries;