From d07c7e4b6836775c391cde3f8a95e89dccd454e3 Mon Sep 17 00:00:00 2001 From: Stenzek Date: Fri, 20 Sep 2024 21:46:09 +1000 Subject: [PATCH] Qt: Fix 100% CPU usage while downloading files The wonders of having fast internet, you never realize when this happens because it completes too quickly... --- src/duckstation-qt/autoupdaterdialog.cpp | 12 ++++++---- src/duckstation-qt/mainwindow.cpp | 28 ++++++++++------------- src/duckstation-qt/qthost.cpp | 20 ++++++++-------- src/duckstation-qt/qtutils.h | 29 ++++++++++++++++++++++++ 4 files changed, 58 insertions(+), 31 deletions(-) diff --git a/src/duckstation-qt/autoupdaterdialog.cpp b/src/duckstation-qt/autoupdaterdialog.cpp index 320a53aba..70a887180 100644 --- a/src/duckstation-qt/autoupdaterdialog.cpp +++ b/src/duckstation-qt/autoupdaterdialog.cpp @@ -544,11 +544,13 @@ void AutoUpdaterDialog::downloadUpdateClicked() m_http_poll_timer->stop(); // Block until completion. - while (m_http->HasAnyRequests()) - { - QApplication::processEvents(QEventLoop::AllEvents, HTTP_POLL_INTERVAL); - m_http->PollRequests(); - } + QtUtils::ProcessEventsWithSleep( + QEventLoop::AllEvents, + [this]() { + m_http->PollRequests(); + return m_http->HasAnyRequests(); + }, + HTTP_POLL_INTERVAL); if (download_result.value_or(false)) { diff --git a/src/duckstation-qt/mainwindow.cpp b/src/duckstation-qt/mainwindow.cpp index d6f6e12b8..a627a060d 100644 --- a/src/duckstation-qt/mainwindow.cpp +++ b/src/duckstation-qt/mainwindow.cpp @@ -281,7 +281,7 @@ std::optional MainWindow::acquireRenderWindow(bool recreate_window, m_display_widget->setFocus(); updateWindowState(); - QCoreApplication::processEvents(QEventLoop::ExcludeUserInputEvents); + QApplication::processEvents(QEventLoop::ExcludeUserInputEvents); return m_display_widget->getWindowInfo(); } @@ -710,8 +710,7 @@ void MainWindow::quit() if (s_system_valid) { g_emu_thread->shutdownSystem(false, true); - while (s_system_valid) - QApplication::processEvents(QEventLoop::ExcludeUserInputEvents, 1); + QtUtils::ProcessEventsWithSleep(QEventLoop::ExcludeUserInputEvents, []() { return s_system_valid; }); } // Big picture might still be active. @@ -749,9 +748,8 @@ void MainWindow::recreate() if (was_display_created) { g_emu_thread->setSurfaceless(true); - while (m_display_widget || !g_emu_thread->isSurfaceless()) - QApplication::processEvents(QEventLoop::ExcludeUserInputEvents, 1); - + QtUtils::ProcessEventsWithSleep(QEventLoop::ExcludeUserInputEvents, + [this]() { return (m_display_widget || !g_emu_thread->isSurfaceless()); }); m_display_created = false; } @@ -2005,8 +2003,8 @@ void MainWindow::switchToGameListView() // switch to surfaceless. we have to wait until the display widget is gone before we swap over. g_emu_thread->setSurfaceless(true); - while (m_display_widget) - QApplication::processEvents(QEventLoop::ExcludeUserInputEvents, 1); + QtUtils::ProcessEventsWithSleep(QEventLoop::ExcludeUserInputEvents, + [this]() { return static_cast(m_display_widget); }); } } @@ -2915,12 +2913,11 @@ MainWindow::SystemLock MainWindow::pauseAndLockSystem() g_emu_thread->setFullscreen(false, false); // Container could change... thanks Wayland. - QWidget* container; - while (s_system_valid && - (g_emu_thread->isFullscreen() || !(container = getDisplayContainer()) || container->isFullScreen())) - { - QApplication::processEvents(QEventLoop::ExcludeUserInputEvents); - } + QtUtils::ProcessEventsWithSleep(QEventLoop::ExcludeUserInputEvents, [this]() { + QWidget* container; + return (s_system_valid && + (g_emu_thread->isFullscreen() || !(container = getDisplayContainer()) || container->isFullScreen())); + }); } if (!was_paused) @@ -2928,8 +2925,7 @@ MainWindow::SystemLock MainWindow::pauseAndLockSystem() g_emu_thread->setSystemPaused(true); // Need to wait for the pause to go through, and make the main window visible if needed. - while (!s_system_paused) - QApplication::processEvents(QEventLoop::ExcludeUserInputEvents, 1); + QtUtils::ProcessEventsWithSleep(QEventLoop::ExcludeUserInputEvents, []() { return !s_system_paused; }); // Ensure it's visible before we try to create any dialogs parented to us. QApplication::sync(); diff --git a/src/duckstation-qt/qthost.cpp b/src/duckstation-qt/qthost.cpp index 67b171d16..3f94d678d 100644 --- a/src/duckstation-qt/qthost.cpp +++ b/src/duckstation-qt/qthost.cpp @@ -311,11 +311,13 @@ std::optional QtHost::DownloadFile(QWidget* parent, const QString& title, &progress); // Block until completion. - while (http->HasAnyRequests()) - { - QApplication::processEvents(QEventLoop::AllEvents, HTTP_POLL_INTERVAL); - http->PollRequests(); - } + QtUtils::ProcessEventsWithSleep( + QEventLoop::AllEvents, + [http = http.get()]() { + http->PollRequests(); + return http->HasAnyRequests(); + }, + HTTP_POLL_INTERVAL); return download_result; } @@ -806,9 +808,8 @@ void EmuThread::stopFullscreenUI() QMetaObject::invokeMethod(this, &EmuThread::stopFullscreenUI, Qt::QueuedConnection); // wait until the host display is gone - while (!QtHost::IsSystemValid() && g_gpu_device) - QApplication::processEvents(QEventLoop::ExcludeUserInputEvents, 1); - + QtUtils::ProcessEventsWithSleep(QEventLoop::ExcludeUserInputEvents, + []() { return (!QtHost::IsSystemValid() && g_gpu_device); }); return; } @@ -1742,8 +1743,7 @@ void EmuThread::stop() AssertMsg(!g_emu_thread->isOnThread(), "Not called on the emu thread"); QMetaObject::invokeMethod(g_emu_thread, &EmuThread::stopInThread, Qt::QueuedConnection); - while (g_emu_thread->isRunning()) - QApplication::processEvents(QEventLoop::ExcludeUserInputEvents, 1); + QtUtils::ProcessEventsWithSleep(QEventLoop::ExcludeUserInputEvents, []() { return (g_emu_thread->isRunning()); }); } void EmuThread::stopInThread() diff --git a/src/duckstation-qt/qtutils.h b/src/duckstation-qt/qtutils.h index a031ee500..d51956003 100644 --- a/src/duckstation-qt/qtutils.h +++ b/src/duckstation-qt/qtutils.h @@ -8,8 +8,11 @@ #include "common/types.h" #include +#include +#include #include #include +#include #include #include #include @@ -121,4 +124,30 @@ bool SaveWindowGeometry(std::string_view window_name, QWidget* widget, bool auto /// Restores a window's geometry from configuration. Returns false if it was not found in the configuration. bool RestoreWindowGeometry(std::string_view window_name, QWidget* widget); +/// CPU-friendly way of blocking the UI thread while some predicate holds true. +template +[[maybe_unused]] static void ProcessEventsWithSleep(QEventLoop::ProcessEventsFlags flags, const Predicate& pred, + int sleep_time_ms = 10) +{ + if (sleep_time_ms == 0) + { + while (pred()) + QCoreApplication::processEvents(flags); + } + + if (!pred()) + return; + + QEventLoop loop; + QTimer timer; + QObject::connect(&timer, &QTimer::timeout, &timer, [&loop, &pred]() { + if (pred()) + return; + + loop.exit(); + }); + timer.start(sleep_time_ms); + loop.exec(flags); +} + } // namespace QtUtils