diff --git a/rpcs3/Emu/System.cpp b/rpcs3/Emu/System.cpp index 7447f33170..b02f7be0fa 100644 --- a/rpcs3/Emu/System.cpp +++ b/rpcs3/Emu/System.cpp @@ -3022,11 +3022,14 @@ 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 { fs::pending_file file; - std::shared_ptr init_mtx = std::make_shared(); - std::shared_ptr join_ended = std::make_shared(false); - atomic_ptr to_ar; - named_thread stop_watchdog("Stop Watchdog"sv, [&to_ar, init_mtx, join_ended, this]() + auto verbose_message = std::make_shared>(); + auto init_mtx = std::make_shared(); + auto join_ended = std::make_shared(false); + auto to_ar = std::make_shared>(); + + auto stop_watchdog = make_ptr(new named_thread("Stop Watchdog"sv, + [to_ar, init_mtx, join_ended, verbose_message, this]() { const auto closed_sucessfully = std::make_shared>(false); @@ -3058,10 +3061,10 @@ void Emulator::Kill(bool allow_autoexit, bool savestate, savestate_stage* save_s while (thread_ctrl::state() != thread_state::aborting) { - if (auto ar_ptr = to_ar.load()) + if (auto ar_ptr = to_ar->load()) { // Total amount of waiting: about 10s - GetCallbacks().on_save_state_progress(closed_sucessfully, ar_ptr, init_mtx); + GetCallbacks().on_save_state_progress(closed_sucessfully, ar_ptr, verbose_message.get(), init_mtx); while (thread_ctrl::state() != thread_state::aborting) { @@ -3076,7 +3079,7 @@ void Emulator::Kill(bool allow_autoexit, bool savestate, savestate_stage* save_s *closed_sucessfully = true; - }); + })); // Join threads for (const auto& [type, data] : *g_fxo) @@ -3097,8 +3100,15 @@ void Emulator::Kill(bool allow_autoexit, bool savestate, savestate_stage* save_s static_cast(init_mtx->init()); + auto set_progress_message = [&](std::string_view text) + { + *verbose_message = stx::make_single(text); + }; + while (savestate) { + set_progress_message("Creating File"); + path = get_savestate_file(m_title_id, m_path, 0, 0); // The function is meant for reading files, so if there is no GZ file it would not return compressed file path @@ -3124,7 +3134,7 @@ void Emulator::Kill(bool allow_autoexit, bool savestate, savestate_stage* save_s auto serial_ptr = stx::make_single(); serial_ptr->m_file_handler = make_compressed_serialization_file_handler(file.file); - to_ar = std::move(serial_ptr); + *to_ar = std::move(serial_ptr); signal_system_cache_can_stay(); break; @@ -3142,7 +3152,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.load(); + auto& ar = *to_ar->load(); read_used_savestate_versions(); // Reset version data USING_SERIALIZATION_VERSION(global_version); @@ -3217,6 +3227,8 @@ void Emulator::Kill(bool allow_autoexit, bool savestate, savestate_stage* save_s ar(std::string{}); }; + set_progress_message("Creating Header"); + ar("RPCS3SAV"_u64); ar(std::endian::native == std::endian::little); ar(g_cfg.savestate.state_inspection_mode.get()); @@ -3256,12 +3268,22 @@ void Emulator::Kill(bool allow_autoexit, bool savestate, savestate_stage* save_s ar(klic.empty() ? std::array{} : std::bit_cast>(klic[0])); ar(m_game_dir); + + set_progress_message("Saving HDD1"); save_hdd1(); + set_progress_message("Saving HDD0"); save_hdd0(); + ar(std::array{}); // Reserved for future use + + set_progress_message("Saving VMemory"); vm::save(ar); + + set_progress_message("Saving FXO"); g_fxo->save(ar); + set_progress_message("Finalizing File"); + bs_t extension_flags{SaveStateExtentionFlags1::SupportsMenuOpenResume}; if (g_fxo->get().active) @@ -3285,18 +3307,17 @@ void Emulator::Kill(bool allow_autoexit, bool savestate, savestate_stage* save_s if (emu_state_cap_thread == thread_state::errored) { sys_log.error("Saving savestate failed due to fatal error!"); - to_ar.reset(); + to_ar->reset(); savestate = false; } } - stop_watchdog = thread_state::finished; - static_cast(init_mtx->reset()); - if (savestate) { fs::stat_t file_stat{}; + set_progress_message("Commiting File"); + if (!file.commit() || !fs::get_stat(path, file_stat)) { sys_log.error("Failed to write savestate to file! (path='%s', %s)", path, fs::g_tls_error); @@ -3394,8 +3415,10 @@ void Emulator::Kill(bool allow_autoexit, bool savestate, savestate_stage* save_s } } + set_progress_message("Resetting Objects"); + // Final termination from main thread (move the last ownership of join thread in order to destroy it) - CallFromMainThread([join_thread = std::move(join_thread), allow_autoexit, this]() mutable + CallFromMainThread([join_thread = std::move(join_thread), verbose_message, stop_watchdog, init_mtx, allow_autoexit, this]() { cpu_thread::cleanup(); @@ -3407,6 +3430,9 @@ void Emulator::Kill(bool allow_autoexit, bool savestate, savestate_stage* save_s vm::close(); + *stop_watchdog = thread_state::finished; + static_cast(init_mtx->reset()); + jit_runtime::finalize(); perf_stat_base::report(); diff --git a/rpcs3/Emu/System.h b/rpcs3/Emu/System.h index f4b86cf01a..adbc97e358 100644 --- a/rpcs3/Emu/System.h +++ b/rpcs3/Emu/System.h @@ -64,7 +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, std::shared_ptr)> on_save_state_progress; + std::function>, stx::shared_ptr, stx::atomic_ptr*, std::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 eb57704d09..1204ba85ef 100644 --- a/rpcs3/headless_application.cpp +++ b/rpcs3/headless_application.cpp @@ -148,7 +148,7 @@ void headless_application::InitializeCallbacks() } }; - callbacks.on_save_state_progress = [](std::shared_ptr>, stx::shared_ptr, std::shared_ptr) + callbacks.on_save_state_progress = [](std::shared_ptr>, stx::shared_ptr, stx::atomic_ptr*, std::shared_ptr) { }; diff --git a/rpcs3/rpcs3qt/gui_application.cpp b/rpcs3/rpcs3qt/gui_application.cpp index a334e929bf..0304630815 100644 --- a/rpcs3/rpcs3qt/gui_application.cpp +++ b/rpcs3/rpcs3qt/gui_application.cpp @@ -685,9 +685,9 @@ void gui_application::InitializeCallbacks() }); }; - callbacks.on_save_state_progress = [this](std::shared_ptr> closed_successfully, stx::shared_ptr ar_ptr, std::shared_ptr init_mtx) + callbacks.on_save_state_progress = [this](std::shared_ptr> closed_successfully, stx::shared_ptr ar_ptr, stx::atomic_ptr* code_location, std::shared_ptr init_mtx) { - Emu.CallFromMainThread([this, closed_successfully, ar_ptr, init_mtx] + Emu.CallFromMainThread([this, closed_successfully, ar_ptr, code_location, init_mtx] { const auto half_seconds = std::make_shared(1); @@ -696,30 +696,72 @@ void gui_application::InitializeCallbacks() pdlg->setAutoClose(true); pdlg->show(); - QString text_base = tr("Waiting for %0 second(s), %1 written"); + QString text_base = tr("%0 written, %1 second(s) passed%2"); - pdlg->setLabelText(text_base.arg(0).arg("0B")); + pdlg->setLabelText(text_base.arg("0B").arg(1).arg("")); 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, init_mtx]() + connect(update_timer, &QTimer::timeout, [pdlg, ar_ptr, half_seconds, text_base, closed_successfully + , code_location, init_mtx, old_written = usz{0}, repeat_count = u32{0}]() mutable { - auto init = static_cast(init_mtx.get())->access(); + std::string verbose_message; + usz bytes_written = 0; - if (!init) { - pdlg->reject(); - return; + auto init = static_cast(init_mtx.get())->access(); + + if (!init) + { + pdlg->reject(); + return; + } + + if (auto str_ptr = code_location->load()) + { + verbose_message = "\n" + *str_ptr; + } + + *half_seconds += 1; + + bytes_written = ar_ptr->get_size(); } - *half_seconds += 1; + if (old_written == bytes_written) + { + if (repeat_count == 60) + { + if (verbose_message.empty()) + { + verbose_message += "\n"; + } + else + { + verbose_message += ". "; + } - const usz bytes_written = ar_ptr->get_size(); - pdlg->setLabelText(text_base.arg(*half_seconds / 2).arg(gui::utils::format_byte_size(bytes_written))); + verbose_message += "If Stuck, Report To Developers"; + } + else + { + repeat_count++; + } + } + else + { + repeat_count = 0; + } + + old_written = bytes_written; + + pdlg->setLabelText(text_base.arg(gui::utils::format_byte_size(bytes_written)).arg(*half_seconds / 2).arg(qstr(verbose_message))); // 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)); + const int percent = std::clamp(static_cast(100. - 100. / std::pow(2., std::fmax(0.01, bytes_written * 1. / (300 * 1024 * 1024)))), 2, 100); + + // Add a third of the remaining progress when the keyword is found + pdlg->setValue(verbose_message.find("Finalizing") != umax ? 100 - ((100 - percent) * 2 / 3) : percent); if (*closed_successfully) {