diff --git a/rpcs3/Crypto/unzip.cpp b/rpcs3/Crypto/unzip.cpp index a2a0d94c51..6485e4d947 100644 --- a/rpcs3/Crypto/unzip.cpp +++ b/rpcs3/Crypto/unzip.cpp @@ -139,7 +139,8 @@ bool zip(const void* src, usz size, fs::file& out, bool multi_thread_it) return false; } - utils::serial compressor(!multi_thread_it || size < 0x40'0000); + utils::serial compressor; + compressor.set_expect_little_data(!multi_thread_it || size < 0x40'0000); compressor.m_file_handler = make_compressed_serialization_file_handler(out); std::string_view buffer_view{static_cast(src), size}; diff --git a/rpcs3/Emu/System.cpp b/rpcs3/Emu/System.cpp index ed8ee4a077..b57b4aba3d 100644 --- a/rpcs3/Emu/System.cpp +++ b/rpcs3/Emu/System.cpp @@ -2970,13 +2970,16 @@ void Emulator::Kill(bool allow_autoexit, bool savestate, savestate_stage* save_s *join_thread = make_ptr(new named_thread("Emulation Join Thread"sv, [join_thread, savestate, allow_autoexit, this]() mutable { - named_thread stop_watchdog("Stop Watchdog"sv, [this]() + std::shared_ptr join_ended = std::make_shared(false); + atomic_ptr to_ar; + + named_thread stop_watchdog("Stop Watchdog"sv, [&to_ar, join_ended, this]() { const auto closed_sucessfully = std::make_shared>(false); bool is_being_held_longer = false; - for (int i = 0; thread_ctrl::state() != thread_state::aborting;) + for (int i = 0; !*join_ended && thread_ctrl::state() != thread_state::aborting;) { if (g_watchdog_hold_ctr) { @@ -3000,6 +3003,25 @@ void Emulator::Kill(bool allow_autoexit, bool savestate, savestate_stage* save_s thread_ctrl::wait_for(5'000); } + for (int i = 0; thread_ctrl::state() != thread_state::aborting;) + { + if (auto ar_ptr = to_ar.load()) + { + // Total amount of waiting: about 10s + GetCallbacks().on_save_state_progress(closed_sucessfully, ar_ptr); + + while (thread_ctrl::state() != thread_state::aborting) + { + thread_ctrl::wait_for(5'000); + } + + break; + } + + thread_ctrl::wait_for(5'000); + } + + *closed_sucessfully = true; }); @@ -3018,8 +3040,6 @@ void Emulator::Kill(bool allow_autoexit, bool savestate, savestate_stage* save_s sys_log.notice("All threads have been stopped."); - std::unique_ptr to_ar; - fs::pending_file file; std::string path; @@ -3048,13 +3068,15 @@ void Emulator::Kill(bool allow_autoexit, bool savestate, savestate_stage* save_s break; } - to_ar = std::make_unique(); - to_ar->m_file_handler = make_compressed_serialization_file_handler(file.file); + to_ar = stx::make_single(); + to_ar.load()->m_file_handler = make_compressed_serialization_file_handler(file.file); signal_system_cache_can_stay(); break; } + *join_ended = true; + if (savestate) { // Savestate thread @@ -3065,7 +3087,7 @@ void Emulator::Kill(bool allow_autoexit, bool savestate, savestate_stage* save_s return fmt::format("Emu State Capture Thread: '%s'", g_tls_serialize_name); }; - auto& ar = *to_ar; + auto& ar = *to_ar.load(); read_used_savestate_versions(); // Reset version data USING_SERIALIZATION_VERSION(global_version); @@ -3196,6 +3218,10 @@ void Emulator::Kill(bool allow_autoexit, bool savestate, savestate_stage* save_s ar(std::array{}); // Reserved for future use ar(timestamp); + + // Final file write, the file is ready to be committed + ar.seek_end(); + ar.m_file_handler->finalize(ar); }); // Join it @@ -3209,15 +3235,9 @@ void Emulator::Kill(bool allow_autoexit, bool savestate, savestate_stage* save_s } } - stop_watchdog = thread_state::aborting; - if (savestate) { - auto& ar = *to_ar; - - // Final file write, the file is ready to be committed - ar.seek_end(); - ar.m_file_handler->finalize(ar); + auto& ar = *to_ar.load(); fs::stat_t file_stat{}; @@ -3257,6 +3277,8 @@ void Emulator::Kill(bool allow_autoexit, bool savestate, savestate_stage* save_s ar.set_reading_state(); } + stop_watchdog = thread_state::aborting; + // Log additional debug information - do not do it on the main thread due to the concern of halting UI events if (g_tty && sys_log.notice) diff --git a/rpcs3/Emu/System.h b/rpcs3/Emu/System.h index 2f9dd53163..51c9ddfab6 100644 --- a/rpcs3/Emu/System.h +++ b/rpcs3/Emu/System.h @@ -2,6 +2,7 @@ #include "util/types.hpp" #include "util/atomic.hpp" +#include "util/shared_ptr.hpp" #include "Utilities/bit_set.h" #include "config_mode.h" #include "games_config.h" @@ -63,6 +64,7 @@ struct EmuCallbacks std::function on_ready; std::function on_missing_fw; std::function>, int)> on_emulation_stop_no_response; + std::function>, stx::shared_ptr)> on_save_state_progress; std::function enable_disc_eject; std::function enable_disc_insert; std::function)> try_to_quit; // (force_quit, on_exit) Try to close RPCS3 diff --git a/rpcs3/headless_application.cpp b/rpcs3/headless_application.cpp index b10586daee..6d3692f1e3 100644 --- a/rpcs3/headless_application.cpp +++ b/rpcs3/headless_application.cpp @@ -148,6 +148,10 @@ void headless_application::InitializeCallbacks() } }; + callbacks.on_save_state_progress = [](std::shared_ptr>, stx::shared_ptr) + { + }; + callbacks.enable_disc_eject = [](bool) {}; callbacks.enable_disc_insert = [](bool) {}; diff --git a/rpcs3/rpcs3qt/gui_application.cpp b/rpcs3/rpcs3qt/gui_application.cpp index 52585ed92d..fab1bb187f 100644 --- a/rpcs3/rpcs3qt/gui_application.cpp +++ b/rpcs3/rpcs3qt/gui_application.cpp @@ -28,6 +28,7 @@ #include "recvmessage_dialog_frame.h" #include "sendmessage_dialog_frame.h" #include "stylesheets.h" +#include "progress_dialog.h" #include #include @@ -665,6 +666,45 @@ void gui_application::InitializeCallbacks() }); }; + callbacks.on_save_state_progress = [this](std::shared_ptr> closed_successfully, stx::shared_ptr ar_ptr) + { + Emu.CallFromMainThread([this, closed_successfully, ar_ptr] + { + const auto half_seconds = std::make_shared(1); + + progress_dialog* pdlg = new progress_dialog(tr("Creating Save-State / Do Not Close RPCS3"), tr("Please wait..."), tr("Hide Progress"), 0, 100, true, m_main_window); + pdlg->setAutoReset(false); + pdlg->setAutoClose(true); + pdlg->show(); + + QString text_base = tr("Waiting for %0 second(s), %1 written"); + + pdlg->setLabelText(text_base.arg(0).arg("0B")); + pdlg->setAttribute(Qt::WA_DeleteOnClose); + + QTimer* update_timer = new QTimer(pdlg); + + connect(update_timer, &QTimer::timeout, [pdlg, ar_ptr, half_seconds, text_base, closed_successfully]() + { + *half_seconds += 1; + + const usz bytes_written = atomic_storage::load(ar_ptr->pos); + pdlg->setLabelText(text_base.arg(*half_seconds / 2).arg(gui::utils::format_byte_size(bytes_written))); + + // 300MB -> 50%, 600MB -> 75%, 1200MB -> 87.5% etc + pdlg->setValue(std::clamp(static_cast(100. - 100. / std::pow(2., std::fmax(0.01, bytes_written * 1. / (300 * 1024 * 1024)))), 2, 100)); + + if (*closed_successfully) + { + pdlg->reject(); + } + }); + + pdlg->open(); + update_timer->start(500); + }); + }; + callbacks.add_breakpoint = [this](u32 addr) { Emu.BlockingCallFromMainThread([this, addr]() diff --git a/rpcs3/util/serialization.hpp b/rpcs3/util/serialization.hpp index f29338a185..a231a22ee8 100644 --- a/rpcs3/util/serialization.hpp +++ b/rpcs3/util/serialization.hpp @@ -83,11 +83,7 @@ public: usz m_max_data = umax; std::unique_ptr m_file_handler; - serial(bool expect_little_data = false) noexcept - : m_expect_little_data(expect_little_data) - { - } - + serial() noexcept = default; serial(const serial&) = delete; serial& operator=(const serial&) = delete; explicit serial(serial&&) noexcept = default; @@ -100,6 +96,11 @@ public: return m_is_writing; } + void set_expect_little_data(bool value) + { + m_expect_little_data = value; + } + // Return true if small amounts of both input and output memory are expected (performance hint) bool expect_little_data() const {