From c6ee767851dee68bfc67bcbe7f176512080b7e7c Mon Sep 17 00:00:00 2001 From: JosJuice Date: Mon, 30 Mar 2020 00:28:16 +0200 Subject: [PATCH] DolphinQt: Run tasks that use progress dialogs on separate threads --- Source/Core/DolphinQt/CMakeLists.txt | 1 + .../DolphinQt/Config/FilesystemWidget.cpp | 44 ++++--- Source/Core/DolphinQt/Config/InfoWidget.cpp | 1 - Source/Core/DolphinQt/Config/VerifyWidget.cpp | 63 ++++++---- Source/Core/DolphinQt/DolphinQt.vcxproj | 4 +- Source/Core/DolphinQt/GameList/GameList.cpp | 59 ++++++---- Source/Core/DolphinQt/Host.cpp | 1 - Source/Core/DolphinQt/MainWindow.cpp | 25 ++-- .../Core/DolphinQt/NetPlay/NetPlayDialog.cpp | 1 - .../QtUtils/ParallelProgressDialog.h | 108 ++++++++++++++++++ 10 files changed, 225 insertions(+), 82 deletions(-) create mode 100644 Source/Core/DolphinQt/QtUtils/ParallelProgressDialog.h diff --git a/Source/Core/DolphinQt/CMakeLists.txt b/Source/Core/DolphinQt/CMakeLists.txt index f0951cb3b5..6a124cd03b 100644 --- a/Source/Core/DolphinQt/CMakeLists.txt +++ b/Source/Core/DolphinQt/CMakeLists.txt @@ -224,6 +224,7 @@ add_executable(dolphin-emu QtUtils/FlowLayout.h QtUtils/ModalMessageBox.cpp QtUtils/ModalMessageBox.h + QtUtils/ParallelProgressDialog.h QtUtils/ImageConverter.cpp QtUtils/ImageConverter.h QtUtils/WindowActivationEventFilter.cpp diff --git a/Source/Core/DolphinQt/Config/FilesystemWidget.cpp b/Source/Core/DolphinQt/Config/FilesystemWidget.cpp index 461a83ccc6..ddffe76963 100644 --- a/Source/Core/DolphinQt/Config/FilesystemWidget.cpp +++ b/Source/Core/DolphinQt/Config/FilesystemWidget.cpp @@ -10,7 +10,6 @@ #include #include #include -#include #include #include #include @@ -23,6 +22,7 @@ #include "DiscIO/Volume.h" #include "DolphinQt/QtUtils/ModalMessageBox.h" +#include "DolphinQt/QtUtils/ParallelProgressDialog.h" #include "DolphinQt/Resources.h" #include "UICommon/UICommon.h" @@ -282,28 +282,34 @@ void FilesystemWidget::ExtractDirectory(const DiscIO::Partition& partition, cons std::unique_ptr info = filesystem->FindFileInfo(path.toStdString()); u32 size = info->GetTotalChildren(); - QProgressDialog* dialog = new QProgressDialog(this); - dialog->setWindowFlags(dialog->windowFlags() & ~Qt::WindowContextHelpButtonHint); - dialog->setMinimum(0); - dialog->setMaximum(size); - dialog->show(); - dialog->setWindowTitle(tr("Progress")); + ParallelProgressDialog dialog(this); + dialog.GetRaw()->setMinimum(0); + dialog.GetRaw()->setMaximum(size); + dialog.GetRaw()->setWindowTitle(tr("Progress")); - bool all = path.isEmpty(); + const bool all = path.isEmpty(); - DiscIO::ExportDirectory( - *m_volume, partition, *info, true, path.toStdString(), out.toStdString(), - [all, dialog](const std::string& current) { - dialog->setLabelText( - (all ? QObject::tr("Extracting All Files...") : QObject::tr("Extracting Directory...")) - .append(QStringLiteral(" %1").arg(QString::fromStdString(current)))); - dialog->setValue(dialog->value() + 1); + std::future future = std::async(std::launch::async, [&] { + int progress = 0; - QCoreApplication::processEvents(); - return dialog->wasCanceled(); - }); + DiscIO::ExportDirectory( + *m_volume, partition, *info, true, path.toStdString(), out.toStdString(), + [all, &dialog, &progress](const std::string& current) { + dialog.SetLabelText( + (all ? QObject::tr("Extracting All Files...") : + QObject::tr("Extracting Directory...")) + .append(QStringLiteral(" %1").arg(QString::fromStdString(current)))); + dialog.SetValue(++progress); - dialog->close(); + QCoreApplication::processEvents(); + return dialog.WasCanceled(); + }); + + dialog.Reset(); + }); + + dialog.GetRaw()->exec(); + future.get(); } void FilesystemWidget::ExtractFile(const DiscIO::Partition& partition, const QString& path, diff --git a/Source/Core/DolphinQt/Config/InfoWidget.cpp b/Source/Core/DolphinQt/Config/InfoWidget.cpp index 416c1a8866..c382c7dc1b 100644 --- a/Source/Core/DolphinQt/Config/InfoWidget.cpp +++ b/Source/Core/DolphinQt/Config/InfoWidget.cpp @@ -10,7 +10,6 @@ #include #include #include -#include #include #include diff --git a/Source/Core/DolphinQt/Config/VerifyWidget.cpp b/Source/Core/DolphinQt/Config/VerifyWidget.cpp index 925409a81c..ce221bce8f 100644 --- a/Source/Core/DolphinQt/Config/VerifyWidget.cpp +++ b/Source/Core/DolphinQt/Config/VerifyWidget.cpp @@ -4,7 +4,9 @@ #include "DolphinQt/Config/VerifyWidget.h" +#include #include +#include #include #include @@ -12,12 +14,12 @@ #include #include #include -#include #include #include "Common/CommonTypes.h" #include "DiscIO/Volume.h" #include "DiscIO/VolumeVerifier.h" +#include "DolphinQt/QtUtils/ParallelProgressDialog.h" VerifyWidget::VerifyWidget(std::shared_ptr volume) : m_volume(std::move(volume)) { @@ -131,33 +133,44 @@ void VerifyWidget::Verify() // We have to divide the number of processed bytes with something so it won't make ints overflow constexpr int DIVISOR = 0x100; - QProgressDialog progress(tr("Verifying"), tr("Cancel"), 0, verifier.GetTotalBytes() / DIVISOR, - this); - progress.setWindowTitle(tr("Verifying")); - progress.setWindowFlags(progress.windowFlags() & ~Qt::WindowContextHelpButtonHint); - progress.setMinimumDuration(500); - progress.setWindowModality(Qt::WindowModal); + ParallelProgressDialog progress(tr("Verifying"), tr("Cancel"), 0, + static_cast(verifier.GetTotalBytes() / DIVISOR), this); + progress.GetRaw()->setWindowTitle(tr("Verifying")); + progress.GetRaw()->setMinimumDuration(500); + progress.GetRaw()->setWindowModality(Qt::WindowModal); - verifier.Start(); - while (verifier.GetBytesProcessed() != verifier.GetTotalBytes()) - { - progress.setValue(verifier.GetBytesProcessed() / DIVISOR); - if (progress.wasCanceled()) - return; + auto future = + std::async(std::launch::async, + [&verifier, &progress]() -> std::optional { + progress.SetValue(0); + verifier.Start(); + while (verifier.GetBytesProcessed() != verifier.GetTotalBytes()) + { + progress.SetValue(static_cast(verifier.GetBytesProcessed() / DIVISOR)); + if (progress.WasCanceled()) + return std::nullopt; - verifier.Process(); - } - verifier.Finish(); + verifier.Process(); + } + verifier.Finish(); - DiscIO::VolumeVerifier::Result result = verifier.GetResult(); - progress.setValue(verifier.GetBytesProcessed() / DIVISOR); + const DiscIO::VolumeVerifier::Result result = verifier.GetResult(); + progress.Reset(); - m_summary_text->setText(QString::fromStdString(result.summary_text)); + return result; + }); + progress.GetRaw()->exec(); - m_problems->setRowCount(static_cast(result.problems.size())); + std::optional result = future.get(); + if (!result) + return; + + m_summary_text->setText(QString::fromStdString(result->summary_text)); + + m_problems->setRowCount(static_cast(result->problems.size())); for (int i = 0; i < m_problems->rowCount(); ++i) { - const DiscIO::VolumeVerifier::Problem problem = result.problems[i]; + const DiscIO::VolumeVerifier::Problem problem = result->problems[i]; QString severity; switch (problem.severity) @@ -179,12 +192,12 @@ void VerifyWidget::Verify() SetProblemCellText(i, 1, severity); } - SetHash(m_crc32_line_edit, result.hashes.crc32); - SetHash(m_md5_line_edit, result.hashes.md5); - SetHash(m_sha1_line_edit, result.hashes.sha1); + SetHash(m_crc32_line_edit, result->hashes.crc32); + SetHash(m_md5_line_edit, result->hashes.md5); + SetHash(m_sha1_line_edit, result->hashes.sha1); if (m_redump_line_edit) - m_redump_line_edit->setText(QString::fromStdString(result.redump.message)); + m_redump_line_edit->setText(QString::fromStdString(result->redump.message)); } void VerifyWidget::SetProblemCellText(int row, int column, QString text) diff --git a/Source/Core/DolphinQt/DolphinQt.vcxproj b/Source/Core/DolphinQt/DolphinQt.vcxproj index de228eba5f..1763c06300 100644 --- a/Source/Core/DolphinQt/DolphinQt.vcxproj +++ b/Source/Core/DolphinQt/DolphinQt.vcxproj @@ -181,6 +181,7 @@ + @@ -282,6 +283,7 @@ + @@ -541,4 +543,4 @@ - + \ No newline at end of file diff --git a/Source/Core/DolphinQt/GameList/GameList.cpp b/Source/Core/DolphinQt/GameList/GameList.cpp index edcb38923e..3f3192a895 100644 --- a/Source/Core/DolphinQt/GameList/GameList.cpp +++ b/Source/Core/DolphinQt/GameList/GameList.cpp @@ -6,6 +6,7 @@ #include #include +#include #include #include @@ -20,7 +21,6 @@ #include #include #include -#include #include #include #include @@ -46,6 +46,7 @@ #include "DolphinQt/MenuBar.h" #include "DolphinQt/QtUtils/DoubleClickEventFilter.h" #include "DolphinQt/QtUtils/ModalMessageBox.h" +#include "DolphinQt/QtUtils/ParallelProgressDialog.h" #include "DolphinQt/Resources.h" #include "DolphinQt/Settings.h" #include "DolphinQt/WiiUpdate.h" @@ -574,34 +575,50 @@ void GameList::CompressISO(bool decompress) } } - QProgressDialog progress_dialog(decompress ? tr("Decompressing...") : tr("Compressing..."), - tr("Abort"), 0, 100, this); - progress_dialog.setWindowModality(Qt::WindowModal); - progress_dialog.setWindowFlags(progress_dialog.windowFlags() & - ~Qt::WindowContextHelpButtonHint); - progress_dialog.setWindowTitle(tr("Progress")); + ParallelProgressDialog progress_dialog( + decompress ? tr("Decompressing...") : tr("Compressing..."), tr("Abort"), 0, 100, this); + progress_dialog.GetRaw()->setWindowModality(Qt::WindowModal); + progress_dialog.GetRaw()->setWindowTitle(tr("Progress")); - bool good; + std::future good; if (decompress) { if (files.size() > 1) - progress_dialog.setLabelText(tr("Decompressing...") + QLatin1Char{'\n'} + - QFileInfo(QString::fromStdString(original_path)).fileName()); - good = DiscIO::DecompressBlobToFile(original_path, dst_path.toStdString(), &CompressCB, - &progress_dialog); + { + progress_dialog.GetRaw()->setLabelText( + tr("Decompressing...") + QLatin1Char{'\n'} + + QFileInfo(QString::fromStdString(original_path)).fileName()); + } + + good = std::async(std::launch::async, [&] { + const bool good = DiscIO::DecompressBlobToFile(original_path, dst_path.toStdString(), + &CompressCB, &progress_dialog); + progress_dialog.Reset(); + return good; + }); } else { if (files.size() > 1) - progress_dialog.setLabelText(tr("Compressing...") + QLatin1Char{'\n'} + - QFileInfo(QString::fromStdString(original_path)).fileName()); - good = DiscIO::CompressFileToBlob(original_path, dst_path.toStdString(), - file->GetPlatform() == DiscIO::Platform::WiiDisc ? 1 : 0, - 16384, &CompressCB, &progress_dialog); + { + progress_dialog.GetRaw()->setLabelText( + tr("Compressing...") + QLatin1Char{'\n'} + + QFileInfo(QString::fromStdString(original_path)).fileName()); + } + + good = std::async(std::launch::async, [&] { + const bool good = + DiscIO::CompressFileToBlob(original_path, dst_path.toStdString(), + file->GetPlatform() == DiscIO::Platform::WiiDisc ? 1 : 0, + 16384, &CompressCB, &progress_dialog); + progress_dialog.Reset(); + return good; + }); } - if (!good) + progress_dialog.GetRaw()->exec(); + if (!good.get()) { QErrorMessage(this).showMessage(tr("Dolphin failed to complete the requested action.")); return; @@ -934,10 +951,10 @@ static bool CompressCB(const std::string& text, float percent, void* ptr) if (ptr == nullptr) return false; - auto* progress_dialog = static_cast(ptr); + auto* progress_dialog = static_cast(ptr); - progress_dialog->setValue(percent * 100); - return !progress_dialog->wasCanceled(); + progress_dialog->SetValue(percent * 100); + return !progress_dialog->WasCanceled(); } void GameList::OnSectionResized(int index, int, int) diff --git a/Source/Core/DolphinQt/Host.cpp b/Source/Core/DolphinQt/Host.cpp index fc53954195..c2037b1640 100644 --- a/Source/Core/DolphinQt/Host.cpp +++ b/Source/Core/DolphinQt/Host.cpp @@ -6,7 +6,6 @@ #include #include -#include #include diff --git a/Source/Core/DolphinQt/MainWindow.cpp b/Source/Core/DolphinQt/MainWindow.cpp index 6080457c5a..1cd6eb4c6f 100644 --- a/Source/Core/DolphinQt/MainWindow.cpp +++ b/Source/Core/DolphinQt/MainWindow.cpp @@ -87,6 +87,7 @@ #include "DolphinQt/NetPlay/NetPlaySetupDialog.h" #include "DolphinQt/QtUtils/FileOpenEventFilter.h" #include "DolphinQt/QtUtils/ModalMessageBox.h" +#include "DolphinQt/QtUtils/ParallelProgressDialog.h" #include "DolphinQt/QtUtils/QueueOnObject.h" #include "DolphinQt/QtUtils/RunOnObject.h" #include "DolphinQt/QtUtils/WindowActivationEventFilter.h" @@ -1510,23 +1511,21 @@ void MainWindow::OnImportNANDBackup() if (file.isEmpty()) return; - QProgressDialog* dialog = new QProgressDialog(this); - dialog->setMinimum(0); - dialog->setMaximum(0); - dialog->setLabelText(tr("Importing NAND backup")); - dialog->setCancelButton(nullptr); + ParallelProgressDialog dialog(this); + dialog.GetRaw()->setMinimum(0); + dialog.GetRaw()->setMaximum(0); + dialog.GetRaw()->setLabelText(tr("Importing NAND backup")); + dialog.GetRaw()->setCancelButton(nullptr); auto beginning = QDateTime::currentDateTime().toMSecsSinceEpoch(); - auto result = std::async(std::launch::async, [&] { + std::future result = std::async(std::launch::async, [&] { DiscIO::NANDImporter().ImportNANDBin( file.toStdString(), [&dialog, beginning] { - QueueOnObject(dialog, [&dialog, beginning] { - dialog->setLabelText( - tr("Importing NAND backup\n Time elapsed: %1s") - .arg((QDateTime::currentDateTime().toMSecsSinceEpoch() - beginning) / 1000)); - }); + dialog.SetLabelText( + tr("Importing NAND backup\n Time elapsed: %1s") + .arg((QDateTime::currentDateTime().toMSecsSinceEpoch() - beginning) / 1000)); }, [this] { std::optional keys_file = RunOnObject(this, [this] { @@ -1540,10 +1539,10 @@ void MainWindow::OnImportNANDBackup() return *keys_file; return std::string(""); }); - QueueOnObject(dialog, &QProgressDialog::close); + dialog.Reset(); }); - dialog->exec(); + dialog.GetRaw()->exec(); result.wait(); diff --git a/Source/Core/DolphinQt/NetPlay/NetPlayDialog.cpp b/Source/Core/DolphinQt/NetPlay/NetPlayDialog.cpp index 31abe2f384..230823b8b6 100644 --- a/Source/Core/DolphinQt/NetPlay/NetPlayDialog.cpp +++ b/Source/Core/DolphinQt/NetPlay/NetPlayDialog.cpp @@ -14,7 +14,6 @@ #include #include #include -#include #include #include #include diff --git a/Source/Core/DolphinQt/QtUtils/ParallelProgressDialog.h b/Source/Core/DolphinQt/QtUtils/ParallelProgressDialog.h new file mode 100644 index 0000000000..274e3d4b11 --- /dev/null +++ b/Source/Core/DolphinQt/QtUtils/ParallelProgressDialog.h @@ -0,0 +1,108 @@ +#pragma once + +#include +#include + +#include +#include +#include + +#include "Common/Flag.h" + +class ParallelProgressDialog final : public QObject +{ + Q_OBJECT + +public: + // Only use this from the main thread + template + ParallelProgressDialog(Args&&... args) : m_dialog{std::forward(args)...} + { + setParent(m_dialog.parent()); + m_dialog.setWindowFlags(m_dialog.windowFlags() & ~Qt::WindowContextHelpButtonHint); + ConnectSignalsAndSlots(); + } + + // Only use this from the main thread + QProgressDialog* GetRaw() { return &m_dialog; } + + // All of the following can be called from any thread + void Cancel() { emit CancelSignal(); } + void Reset() { emit ResetSignal(); } + void SetCancelButtonText(const QString& text) { emit SetCancelButtonText(text); } + void SetLabelText(const QString& text) { emit SetLabelTextSignal(text); } + void SetMaximum(int maximum) { emit SetMaximumSignal(maximum); } + void SetMinimum(int minimum) { emit SetMinimumSignal(minimum); } + void SetMinimumDuration(int ms) { emit SetMinimumDurationSignal(ms); } + void SetRange(int minimum, int maximum) { emit SetRangeSignal(minimum, maximum); } + + // Can be called from any thread, but only from one thread at a time + void SetValue(int progress) + { + // HACK: To avoid the https://bugreports.qt.io/browse/QTBUG-10561 stack overflow, + // limit how often QProgressDialog::setValue can run + const auto current_time = std::chrono::steady_clock::now(); + if (current_time - m_last_setvalue_time >= std::chrono::milliseconds(50)) + { + m_last_setvalue_time = current_time; + emit SetValueSignal(progress); + } + } + + // Can be called from any thread + bool WasCanceled() { return m_was_cancelled.IsSet(); } + +signals: + void CancelSignal(); + void ResetSignal(); + void SetCancelButtonTextSignal(const QString& cancel_button_text); + void SetLabelTextSignal(const QString& text); + void SetMaximumSignal(int maximum); + void SetMinimumSignal(int minimum); + void SetMinimumDurationSignal(int ms); + void SetRangeSignal(int minimum, int maximum); + void SetValueSignal(int progress); + + void Canceled(); + void Finished(int result); + +private slots: + void OnCancelled() { m_was_cancelled.Set(); } + +private: + template + void ConnectSignal(Func1 signal, Func2 slot) + { + QObject::connect(this, signal, &m_dialog, slot); + } + + template + void ConnectSlot(Func1 signal, Func2 slot) + { + QObject::connect(&m_dialog, signal, this, slot); + } + + void ConnectSignalsAndSlots() + { + ConnectSignal(&ParallelProgressDialog::CancelSignal, &QProgressDialog::cancel); + ConnectSignal(&ParallelProgressDialog::ResetSignal, &QProgressDialog::reset); + ConnectSignal(&ParallelProgressDialog::SetCancelButtonTextSignal, + &QProgressDialog::setCancelButtonText); + ConnectSignal(&ParallelProgressDialog::SetLabelTextSignal, &QProgressDialog::setLabelText); + ConnectSignal(&ParallelProgressDialog::SetMaximumSignal, &QProgressDialog::setMaximum); + ConnectSignal(&ParallelProgressDialog::SetMinimumSignal, &QProgressDialog::setMinimum); + ConnectSignal(&ParallelProgressDialog::SetMinimumDurationSignal, + &QProgressDialog::setMinimumDuration); + ConnectSignal(&ParallelProgressDialog::SetRangeSignal, &QProgressDialog::setRange); + ConnectSignal(&ParallelProgressDialog::SetValueSignal, &QProgressDialog::setValue); + + ConnectSlot(&QProgressDialog::canceled, &ParallelProgressDialog::OnCancelled); + + ConnectSlot(&QProgressDialog::canceled, &ParallelProgressDialog::Canceled); + ConnectSlot(&QProgressDialog::finished, &ParallelProgressDialog::Finished); + } + + QProgressDialog m_dialog; + Common::Flag m_was_cancelled; + std::chrono::time_point m_last_setvalue_time; +};