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();