From 378a69ea85e596180a6442e7f53fe7144100a9ab Mon Sep 17 00:00:00 2001 From: Elad <18193363+elad335@users.noreply.github.com> Date: Tue, 26 Nov 2024 11:01:31 +0200 Subject: [PATCH] Qt: Deprecate processEvents() part 2 --- Utilities/File.h | 13 ++- rpcs3/Emu/System.cpp | 106 +++++++++++++------- rpcs3/main_application.cpp | 6 +- rpcs3/rpcs3qt/gui_application.cpp | 6 +- rpcs3/rpcs3qt/gui_settings.cpp | 17 +++- rpcs3/rpcs3qt/main_window.cpp | 122 ++++++++++++++++++----- rpcs3/rpcs3qt/ps_move_tracker_dialog.cpp | 4 +- 7 files changed, 202 insertions(+), 72 deletions(-) diff --git a/Utilities/File.h b/Utilities/File.h index 1a3bbd8481..4768b24ac0 100644 --- a/Utilities/File.h +++ b/Utilities/File.h @@ -475,7 +475,7 @@ namespace fs dir() = default; // Open dir handle - explicit dir(const std::string& path) + explicit dir(const std::string& path) noexcept { open(path); } @@ -484,7 +484,7 @@ namespace fs bool open(const std::string& path); // Check whether the handle is valid (opened directory) - explicit operator bool() const + explicit operator bool() const noexcept { return m_dir.operator bool(); } @@ -531,7 +531,7 @@ namespace fs from_current }; - iterator(const dir* parent, mode mode_ = mode::from_first) + iterator(const dir* parent, mode mode_ = mode::from_first) noexcept : m_parent(parent) { if (!m_parent) @@ -569,6 +569,13 @@ namespace fs return *this; } + iterator operator++(int) + { + iterator old = *this; + *this = {m_parent, mode::from_current}; + return old; + } + bool operator !=(const iterator& rhs) const { return m_parent != rhs.m_parent; diff --git a/rpcs3/Emu/System.cpp b/rpcs3/Emu/System.cpp index 96f4df324e..7d12405f44 100644 --- a/rpcs3/Emu/System.cpp +++ b/rpcs3/Emu/System.cpp @@ -2840,18 +2840,32 @@ void Emulator::Resume() u64 get_sysutil_cb_manager_read_count(); -void process_qt_events(); +void qt_events_aware_op(int repeat_duration_ms, std::function<bool()> wrapped_op); void Emulator::GracefulShutdown(bool allow_autoexit, bool async_op, bool savestate) { - const auto old_state = m_state.load(); + // Ensure no game has booted inbetween + const auto guard = Emu.MakeEmulationStateGuard(); + + stop_counter_t old_emu_id{}; + system_state old_state{}; + + // Perform atomic load of both + do + { + old_emu_id = GetEmulationIdentifier(); + old_state = m_state.load(); + } + while (old_emu_id != GetEmulationIdentifier() || old_state != m_state.load()); if (old_state == system_state::stopped || old_state == system_state::stopping) { - while (!async_op && m_state != system_state::stopped) + if (!async_op && old_state == system_state::stopping) { - process_qt_events(); - std::this_thread::sleep_for(16ms); + qt_events_aware_op(5, [&]() + { + return old_emu_id != GetEmulationIdentifier() || m_state != system_state::stopping; + }); } return; @@ -2859,10 +2873,12 @@ void Emulator::GracefulShutdown(bool allow_autoexit, bool async_op, bool savesta if (!savestate && m_emu_state_close_pending) { - while (!async_op && m_state != system_state::stopped) + if (!async_op) { - process_qt_events(); - std::this_thread::sleep_for(16ms); + qt_events_aware_op(5, [&]() + { + return old_emu_id != GetEmulationIdentifier() || m_state != system_state::stopping; + }); } return; @@ -2880,10 +2896,12 @@ void Emulator::GracefulShutdown(bool allow_autoexit, bool async_op, bool savesta // The callback has been rudely ignored, we have no other option but to force termination Kill(allow_autoexit && !savestate, savestate); - while (!async_op && m_state != system_state::stopped) + if (!async_op) { - process_qt_events(); - std::this_thread::sleep_for(16ms); + qt_events_aware_op(5, [&]() + { + return (old_emu_id != GetEmulationIdentifier() || m_state == system_state::stopped); + }); } return; @@ -2893,9 +2911,14 @@ void Emulator::GracefulShutdown(bool allow_autoexit, bool async_op, bool savesta { bool read_sysutil_signal = false; - for (u32 i = 100; i < 140; i++) + u32 i = 100; + + qt_events_aware_op(50, [&]() { - std::this_thread::sleep_for(50ms); + if (i >= 140) + { + return true; + } // TODO: Prevent pausing by other threads while in this loop CallFromMainThread([this]() @@ -2903,8 +2926,6 @@ void Emulator::GracefulShutdown(bool allow_autoexit, bool async_op, bool savesta Resume(); }, nullptr, true, read_counter); - process_qt_events(); // Is nullified when performed on non-main thread - if (!read_sysutil_signal && read_counter != get_sysutil_cb_manager_read_count()) { i -= 100; // Grant 5 seconds (if signal is not read force kill after two second) @@ -2913,9 +2934,13 @@ void Emulator::GracefulShutdown(bool allow_autoexit, bool async_op, bool savesta if (static_cast<u64>(info) != m_stop_ctr) { - return; + return true; } - } + + // Process events + i++; + return false; + }); // An inevitable attempt to terminate the *current* emulation course will be issued after 7s CallFromMainThread([allow_autoexit, this]() @@ -2932,11 +2957,10 @@ void Emulator::GracefulShutdown(bool allow_autoexit, bool async_op, bool savesta { perform_kill(); - while (m_state != system_state::stopped) + qt_events_aware_op(5, [&]() { - process_qt_events(); - std::this_thread::sleep_for(16ms); - } + return (old_emu_id != GetEmulationIdentifier() || m_state == system_state::stopped); + }); } } @@ -3932,25 +3956,37 @@ u32 Emulator::AddGamesFromDir(const std::string& path) games_added++; } - process_qt_events(); + fs::dir fs_dir{path}; - // search direct subdirectories, that way we can drop one folder containing all games - for (auto&& dir_entry : fs::dir(path)) + auto path_it = fs_dir.begin(); + + qt_events_aware_op(0, [&]() { - if (!dir_entry.is_directory || dir_entry.name == "." || dir_entry.name == "..") + // search direct subdirectories, that way we can drop one folder containing all games + for (; path_it != fs_dir.end(); ++path_it) { - continue; + auto dir_entry = std::move(*path_it); + + if (!dir_entry.is_directory || dir_entry.name == "." || dir_entry.name == "..") + { + continue; + } + + const std::string dir_path = path + '/' + dir_entry.name; + + if (const game_boot_result error = AddGame(dir_path); error == game_boot_result::no_errors) + { + games_added++; + } + + // Process events + ++path_it; + return false; } - const std::string dir_path = path + '/' + dir_entry.name; - - if (const game_boot_result error = AddGame(dir_path); error == game_boot_result::no_errors) - { - games_added++; - } - - process_qt_events(); - } + // Exit loop + return true; + }); m_games_config.set_save_on_dirty(true); diff --git a/rpcs3/main_application.cpp b/rpcs3/main_application.cpp index afbff02a8b..cf8373e95a 100644 --- a/rpcs3/main_application.cpp +++ b/rpcs3/main_application.cpp @@ -54,6 +54,8 @@ namespace rsx::overlays extern void reset_debug_overlay(); } +extern void qt_events_aware_op(int repeat_duration_ms, std::function<bool()> wrapped_op); + /** Emu.Init() wrapper for user management */ void main_application::InitializeEmulator(const std::string& user, bool show_gui) { @@ -182,8 +184,8 @@ EmuCallbacks main_application::CreateCallbacks() callbacks.init_pad_handler = [this](std::string_view title_id) { ensure(g_fxo->init<named_thread<pad_thread>>(get_thread(), m_game_window, title_id)); - extern void process_qt_events(); - while (!pad::g_started) process_qt_events(); + + qt_events_aware_op(0, [](){ return !!pad::g_started; }); }; callbacks.get_audio = []() -> std::shared_ptr<AudioBackend> diff --git a/rpcs3/rpcs3qt/gui_application.cpp b/rpcs3/rpcs3qt/gui_application.cpp index 22f01682f0..e41ee9ce69 100644 --- a/rpcs3/rpcs3qt/gui_application.cpp +++ b/rpcs3/rpcs3qt/gui_application.cpp @@ -577,12 +577,10 @@ void gui_application::InitializeCallbacks() callbacks.on_missing_fw = [this]() { - if (!m_main_window) + if (m_main_window) { - return; + m_main_window->OnMissingFw(); } - - m_main_window->OnMissingFw(); }; callbacks.handle_taskbar_progress = [this](s32 type, s32 value) diff --git a/rpcs3/rpcs3qt/gui_settings.cpp b/rpcs3/rpcs3qt/gui_settings.cpp index ecbee10aff..fc66dfccd9 100644 --- a/rpcs3/rpcs3qt/gui_settings.cpp +++ b/rpcs3/rpcs3qt/gui_settings.cpp @@ -13,6 +13,8 @@ LOG_CHANNEL(cfg_log, "CFG"); +extern void qt_events_aware_op(int repeat_duration_ms, std::function<bool()> wrapped_op); + namespace gui { QString stylesheet; @@ -193,11 +195,18 @@ void gui_settings::ShowInfoBox(const QString& title, const QString& text, const bool gui_settings::GetBootConfirmation(QWidget* parent, const gui_save& gui_save_entry) { - while (Emu.GetStatus(false) == system_state::stopping) + auto info = Emu.GetEmulationIdentifier(); + + qt_events_aware_op(16, [&]() { - QCoreApplication::processEvents(); - std::this_thread::sleep_for(16ms); - } + if (Emu.GetStatus(false) != system_state::stopping) + { + ensure(info == Emu.GetEmulationIdentifier()); + return true; + } + + return false; + }); if (!Emu.IsStopped()) { diff --git a/rpcs3/rpcs3qt/main_window.cpp b/rpcs3/rpcs3qt/main_window.cpp index 46d8406c11..8648b41d0c 100644 --- a/rpcs3/rpcs3qt/main_window.cpp +++ b/rpcs3/rpcs3qt/main_window.cpp @@ -82,6 +82,9 @@ #include "ui_main_window.h" +#include <QEventLoop> +#include <QTimer> + #if QT_CONFIG(permissions) #include <QGuiApplication> #include <QPermissions> @@ -96,15 +99,63 @@ std::shared_ptr<CPUDisAsm> make_basic_ppu_disasm(); inline std::string sstr(const QString& _in) { return _in.toStdString(); } -extern void process_qt_events() +extern void qt_events_aware_op(int repeat_duration_ms, std::function<bool()> wrapped_op) { + ensure(wrapped_op); + if (thread_ctrl::is_main()) { // NOTE: // I noticed that calling this from an Emu callback can cause the // caller to get stuck for a while during newly opened Qt dialogs. // Adding a timeout here doesn't seem to do anything in that case. - QApplication::processEvents(); + QEventLoop* event_loop = nullptr; + + std::shared_ptr<std::function<void()>> check_iteration; + check_iteration = std::make_shared<std::function<void()>>([&]() + { + if (wrapped_op()) + { + event_loop->exit(0); + } + else + { + QTimer::singleShot(repeat_duration_ms, *check_iteration); + } + }); + + while (!wrapped_op()) + { + // Init event loop + event_loop = new QEventLoop(); + + // Queue event initially + QTimer::singleShot(0, *check_iteration); + + // Event loop + event_loop->exec(); + + // Cleanup + event_loop->deleteLater(); + } + } + else + { + while (!wrapped_op()) + { + if (repeat_duration_ms == 0) + { + std::this_thread::yield(); + } + else if (thread_ctrl::get_current()) + { + thread_ctrl::wait_for(repeat_duration_ms * 1000); + } + else + { + std::this_thread::sleep_for(std::chrono::milliseconds(repeat_duration_ms)); + } + } } } @@ -1066,9 +1117,16 @@ bool main_window::HandlePackageInstallation(QStringList file_paths, bool from_bo pdlg.show(); // Wait for the completion - for (usz i = 0, set_text = umax; i < readers.size() && result.error == package_install_result::error_type::no_error;) + int reader_it = 0; + int set_text = -1; + + qt_events_aware_op(5, [&, readers_size = ::narrow<int>(readers.size())]() { - std::this_thread::sleep_for(5ms); + if (reader_it == readers_size || result.error != package_install_result::error_type::no_error) + { + // Exit loop + return true; + } if (pdlg.wasCanceled()) { @@ -1079,26 +1137,28 @@ bool main_window::HandlePackageInstallation(QStringList file_paths, bool from_bo reader.abort_extract(); } - break; + // Exit loop + return true; } // Update progress window - const int progress = readers[i].get_progress(pdlg.maximum()); + const int progress = readers[reader_it].get_progress(pdlg.maximum()); pdlg.SetValue(progress); - if (set_text != i) + if (set_text != reader_it) { - pdlg.setLabelText(tr("Installing package (%0/%1), please wait...\n\n%2").arg(i + 1).arg(readers.size()).arg(get_app_info(packages[i]))); - set_text = i; + pdlg.setLabelText(tr("Installing package (%0/%1), please wait...\n\n%2").arg(reader_it + 1).arg(readers_size).arg(get_app_info(packages[reader_it]))); + set_text = reader_it; } - QCoreApplication::processEvents(); - if (progress == pdlg.maximum()) { - i++; + reader_it++; } - } + + // Process events + return false; + }); const bool success = worker(); @@ -1350,13 +1410,19 @@ void main_window::ExtractTar() QString error; - for (const QString& file : files) + auto files_it = files.begin(); + int pdlg_progress = 0; + + qt_events_aware_op(0, [&]() { - if (pdlg.wasCanceled()) + if (pdlg.wasCanceled() || files_it == files.end()) { - break; + // Exit loop + return true; } + const QString& file = *files_it; + // Do not abort on failure here, in case the user selected a wrong file in multi-selection while the rest are valid if (!extract_tar(sstr(file), sstr(dir) + '/')) { @@ -1369,9 +1435,12 @@ void main_window::ExtractTar() error += file; } - pdlg.SetValue(pdlg.value() + 1); - QApplication::processEvents(); - } + pdlg_progress++; + pdlg.SetValue(pdlg_progress); + + files_it++; + return false; + }); if (!error.isEmpty()) { @@ -1629,18 +1698,25 @@ void main_window::HandlePupInstallation(const QString& file_path, const QString& }); // Wait for the completion - for (uint value = progress.load(); value < update_filenames.size(); std::this_thread::sleep_for(5ms), value = progress) + qt_events_aware_op(5, [&]() { + const uint value = progress.load(); + + if (value >= update_filenames.size()) + { + return true; + } + if (pdlg.wasCanceled()) { progress = -1; - break; + return true; } // Update progress window pdlg.SetValue(static_cast<int>(value)); - QCoreApplication::processEvents(); - } + return false; + }); // Join thread worker(); diff --git a/rpcs3/rpcs3qt/ps_move_tracker_dialog.cpp b/rpcs3/rpcs3qt/ps_move_tracker_dialog.cpp index 1e4baa9a55..a553656c43 100644 --- a/rpcs3/rpcs3qt/ps_move_tracker_dialog.cpp +++ b/rpcs3/rpcs3qt/ps_move_tracker_dialog.cpp @@ -24,6 +24,8 @@ static constexpr int radius_range = 1000; static const constexpr f64 min_radius_conversion = radius_range / g_cfg_move.min_radius.max; static const constexpr f64 max_radius_conversion = radius_range / g_cfg_move.max_radius.max; +extern void qt_events_aware_op(int repeat_duration_ms, std::function<bool()> wrapped_op); + ps_move_tracker_dialog::ps_move_tracker_dialog(QWidget* parent) : QDialog(parent) , ui(new Ui::ps_move_tracker_dialog) @@ -232,7 +234,7 @@ ps_move_tracker_dialog::ps_move_tracker_dialog(QWidget* parent) reset_camera(); m_input_thread = std::make_unique<named_thread<pad_thread>>(thread(), window(), ""); - while (!pad::g_started) QApplication::processEvents(); + qt_events_aware_op(0, [](){ return !!pad::g_started; }); adjustSize();