diff --git a/rpcs3/Emu/Cell/lv2/sys_process.cpp b/rpcs3/Emu/Cell/lv2/sys_process.cpp index 9a6369cd8192..3a0a3323ed55 100644 --- a/rpcs3/Emu/Cell/lv2/sys_process.cpp +++ b/rpcs3/Emu/Cell/lv2/sys_process.cpp @@ -425,7 +425,6 @@ void lv2_exitspawn(ppu_thread& ppu, std::vector& argv, std::vector< std::string path = vfs::get(argv[0]); std::string hdd1 = vfs::get("/dev_hdd1/"); - std::string old_config = Emu.GetUsedConfig(); const u128 klic = g_fxo->get().last_key(); @@ -457,27 +456,32 @@ void lv2_exitspawn(ppu_thread& ppu, std::vector& argv, std::vector< g_fxo->init(std::min(old_size - total_size, sdk_suggested_mem) + total_size); }; - Emu.Kill(false); - Emu.argv = std::move(argv); - Emu.envp = std::move(envp); - Emu.data = std::move(data); - Emu.disc = std::move(disc); - Emu.hdd1 = std::move(hdd1); - Emu.init_mem_containers = std::move(func); - - if (klic) + Emu.after_kill_callback = [func = std::move(func), argv = std::move(argv), envp = std::move(envp), data = std::move(data), + disc = std::move(disc), path = std::move(path), hdd1 = std::move(hdd1), old_config = Emu.GetUsedConfig(), klic]() mutable { - Emu.klic.emplace_back(klic); - } + Emu.argv = std::move(argv); + Emu.envp = std::move(envp); + Emu.data = std::move(data); + Emu.disc = std::move(disc); + Emu.hdd1 = std::move(hdd1); + Emu.init_mem_containers = std::move(func); + + if (klic) + { + Emu.klic.emplace_back(klic); + } - Emu.SetForceBoot(true); + Emu.SetForceBoot(true); - auto res = Emu.BootGame(path, "", true, cfg_mode::continuous, old_config); + auto res = Emu.BootGame(path, "", true, cfg_mode::continuous, old_config); - if (res != game_boot_result::no_errors) - { - sys_process.fatal("Failed to boot from exitspawn! (path=\"%s\", error=%s)", path, res); - } + if (res != game_boot_result::no_errors) + { + sys_process.fatal("Failed to boot from exitspawn! (path=\"%s\", error=%s)", path, res); + } + }; + + Emu.Kill(false); }); // Wait for GUI thread diff --git a/rpcs3/Emu/RSX/Overlays/HomeMenu/overlay_home_menu_main_menu.cpp b/rpcs3/Emu/RSX/Overlays/HomeMenu/overlay_home_menu_main_menu.cpp index 4eddf98e9fff..7b40365dc3df 100644 --- a/rpcs3/Emu/RSX/Overlays/HomeMenu/overlay_home_menu_main_menu.cpp +++ b/rpcs3/Emu/RSX/Overlays/HomeMenu/overlay_home_menu_main_menu.cpp @@ -52,12 +52,12 @@ namespace rsx Emu.CallFromMainThread([suspend_mode]() { - Emu.Kill(false, true); - - if (!suspend_mode) + Emu.after_kill_callback = [suspend_mode]() { Emu.Restart(); - } + }; + + Emu.Kill(false, true); }); return page_navigation::exit; diff --git a/rpcs3/Emu/System.cpp b/rpcs3/Emu/System.cpp index 6b534c75c245..dc1a980cdde7 100644 --- a/rpcs3/Emu/System.cpp +++ b/rpcs3/Emu/System.cpp @@ -2412,7 +2412,7 @@ void Emulator::GracefulShutdown(bool allow_autoexit, bool async_op, bool savesta { const auto old_state = m_state.load(); - if (old_state == system_state::stopped) + if (old_state == system_state::stopped || old_state == system_state::stopping) { return; } @@ -2472,17 +2472,23 @@ void Emulator::GracefulShutdown(bool allow_autoexit, bool async_op, bool savesta } else { + // TODO: Need to be always async + perform_kill(); + + while (GetStatus(false) != system_state::stopped) + { + process_qt_events(); + std::this_thread::sleep_for(16ms); + } } } extern bool try_lock_vdec_context_creation(); extern bool try_lock_spu_threads_in_a_state_compatible_with_savestates(bool revert_lock = false); -std::shared_ptr Emulator::Kill(bool allow_autoexit, bool savestate) +void Emulator::Kill(bool allow_autoexit, bool savestate) { - std::shared_ptr to_ar; - if (savestate && !try_lock_spu_threads_in_a_state_compatible_with_savestates()) { sys_log.error("Failed to savestate: failed to lock SPU threads execution."); @@ -2497,7 +2503,7 @@ std::shared_ptr Emulator::Kill(bool allow_autoexit, bool savestat "\nYou need to close the game for to take effect." "\nIf you cannot close the game due to losing important progress your best chance is to skip the current cutscenes if any are played and retry."); - return to_ar; + return; } g_tls_log_prefix = []() @@ -2505,8 +2511,23 @@ std::shared_ptr Emulator::Kill(bool allow_autoexit, bool savestat return std::string(); }; - if (m_state.exchange(system_state::stopped) == system_state::stopped) + if (system_state old_state = m_state.fetch_op([](system_state& state) { + if (state == system_state::stopping || state == system_state::stopped) + { + return false; + } + + state = system_state::stopping; + return true; + }).first; old_state <= system_state::stopping) + { + if (old_state == system_state::stopping) + { + // Termination is in progress + return; + } + // Ensure clean state m_ar.reset(); argv.clear(); @@ -2516,11 +2537,12 @@ std::shared_ptr Emulator::Kill(bool allow_autoexit, bool savestat klic.clear(); hdd1.clear(); init_mem_containers = nullptr; + after_kill_callback = nullptr; m_config_path.clear(); m_config_mode = cfg_mode::custom; read_used_savestate_versions(); m_savestate_extension_flags1 = {}; - return to_ar; + return; } sys_log.notice("Stopping emulator..."); @@ -2536,28 +2558,6 @@ std::shared_ptr Emulator::Kill(bool allow_autoexit, bool savestat } } - named_thread stop_watchdog("Stop Watchdog", [&]() - { - for (int i = 0; thread_ctrl::state() != thread_state::aborting;) - { - // We don't need accurate timekeeping, using clocks may interfere with debugging - if (i >= (savestate ? 2000 : 1000)) - { - // Total amount of waiting: about 5s - report_fatal_error("Stopping emulator took too long." - "\nSome thread has probably deadlocked. Aborting."); - } - - thread_ctrl::wait_for(5'000); - - if (!g_watchdog_hold_ctr) - { - // Don't count if there are still uninterruptable threads like PPU LLVM workers - i++; - } - } - }); - // Signal threads // Stop the replay thread "game" first @@ -2585,257 +2585,311 @@ std::shared_ptr Emulator::Kill(bool allow_autoexit, bool savestat // Wait fot newly created cpu_thread to see that emulation has been stopped id_manager::g_mutex.lock_unlock(); - GetCallbacks().on_stop(); + // Type-less smart pointer container for thread (cannot know its type with this approach) + // There is no race condition because it is only accessed by the same thread + std::shared_ptr> join_thread = std::make_shared>(); - // Join threads - for (const auto& [type, data] : *g_fxo) + auto make_ptr = [](auto ptr) { - if (type.stop) + return std::shared_ptr>(ptr); + }; + + *join_thread = make_ptr(new named_thread("Emulation Join Thread"sv, [join_thread, savestate, allow_autoexit, this]() mutable + { + GetCallbacks().on_stop(); + + named_thread stop_watchdog("Stop Watchdog"sv, [this]() { - type.stop(data, thread_state::finished); - } - } + const auto closed_sucessfully = std::make_shared>(false); - // Save it first for maximum timing accuracy - const u64 timestamp = get_timebased_time(); + for (int i = 0; thread_ctrl::state() != thread_state::aborting;) + { + // We don't need accurate timekeeping, using clocks may interfere with debugging + if (i >= 2000) + { + // Total amount of waiting: about 10s + GetCallbacks().on_emulation_stop_no_response(closed_sucessfully); - stop_watchdog = thread_state::aborting; + while (thread_ctrl::state() != thread_state::aborting) + { + thread_ctrl::wait_for(5'000); + } - sys_log.notice("All threads have been stopped."); + break; + } - if (savestate) - { - to_ar = std::make_unique(); + thread_ctrl::wait_for(5'000); + + if (!g_watchdog_hold_ctr) + { + // Don't count if there are still uninterruptable threads like PPU LLVM workers + i++; + } + } + + *closed_sucessfully = true; + }); - // Savestate thread - named_thread emu_state_cap_thread("Emu State Capture Thread", [&]() + // Join threads + for (const auto& [type, data] : *g_fxo) { - g_tls_log_prefix = []() + if (type.stop) { - return fmt::format("Emu State Capture Thread: '%s'", g_tls_serialize_name); - }; + type.stop(data, thread_state::finished); + } + } - auto& ar = *to_ar; + // Save it first for maximum timing accuracy + const u64 timestamp = get_timebased_time(); - read_used_savestate_versions(); // Reset version data - USING_SERIALIZATION_VERSION(global_version); + sys_log.notice("All threads have been stopped."); - // Avoid duplicating TAR object memory because it can be very large - auto save_tar = [&](const std::string& path) - { - ar(usz{}); // Reserve memory to be patched later with correct size - const usz old_size = ar.data.size(); - ar.data = tar_object::save_directory(path, std::move(ar.data)); - ar.seek_end(); - const usz tar_size = ar.data.size() - old_size; - std::memcpy(ar.data.data() + old_size - sizeof(usz), &tar_size, sizeof(usz)); - sys_log.success("Saved the contents of directory '%s' (size=0x%x)", path, tar_size); - }; + std::unique_ptr to_ar; + + if (savestate) + { + to_ar = std::make_unique(); - auto save_hdd1 = [&]() + // Savestate thread + named_thread emu_state_cap_thread("Emu State Capture Thread", [&]() { - const std::string _path = vfs::get("/dev_hdd1"); - std::string_view path = _path; + g_tls_log_prefix = []() + { + return fmt::format("Emu State Capture Thread: '%s'", g_tls_serialize_name); + }; - path = path.substr(0, path.find_last_not_of(fs::delim) + 1); + auto& ar = *to_ar; - ar(std::string(path.substr(path.find_last_of(fs::delim) + 1))); + read_used_savestate_versions(); // Reset version data + USING_SERIALIZATION_VERSION(global_version); - if (!_path.empty()) + // Avoid duplicating TAR object memory because it can be very large + auto save_tar = [&](const std::string& path) { - save_tar(_path); - } - }; + ar(usz{}); // Reserve memory to be patched later with correct size + const usz old_size = ar.data.size(); + ar.data = tar_object::save_directory(path, std::move(ar.data)); + ar.seek_end(); + const usz tar_size = ar.data.size() - old_size; + std::memcpy(ar.data.data() + old_size - sizeof(usz), &tar_size, sizeof(usz)); + sys_log.success("Saved the contents of directory '%s' (size=0x%x)", path, tar_size); + }; - auto save_hdd0 = [&]() - { - if (g_cfg.savestate.save_disc_game_data) + auto save_hdd1 = [&]() { - const std::string path = vfs::get("/dev_hdd0/game/"); + const std::string _path = vfs::get("/dev_hdd1"); + std::string_view path = _path; - for (auto& entry : fs::dir(path)) + path = path.substr(0, path.find_last_not_of(fs::delim) + 1); + + ar(std::string(path.substr(path.find_last_of(fs::delim) + 1))); + + if (!_path.empty()) { - if (entry.is_directory && entry.name != "." && entry.name != "..") + save_tar(_path); + } + }; + + auto save_hdd0 = [&]() + { + if (g_cfg.savestate.save_disc_game_data) + { + const std::string path = vfs::get("/dev_hdd0/game/"); + + for (auto& entry : fs::dir(path)) { - if (auto res = psf::load(path + entry.name + "/PARAM.SFO"); res && /*!m_title_id.empty() &&*/ psf::get_string(res.sfo, "TITLE_ID") == m_title_id && psf::get_string(res.sfo, "CATEGORY") == "GD") + if (entry.is_directory && entry.name != "." && entry.name != "..") { - ar(entry.name); - save_tar(path + entry.name); + if (auto res = psf::load(path + entry.name + "/PARAM.SFO"); res && /*!m_title_id.empty() &&*/ psf::get_string(res.sfo, "TITLE_ID") == m_title_id && psf::get_string(res.sfo, "CATEGORY") == "GD") + { + ar(entry.name); + save_tar(path + entry.name); + } } } } - } - ar(std::string{}); - }; + ar(std::string{}); + }; - ar("RPCS3SAV"_u64); - ar(std::endian::native == std::endian::little); - ar(g_cfg.savestate.state_inspection_mode.get()); - ar(std::array{}); // Reserved for future use - ar(usz{0}); // Offset of versioning data, to be overwritten at the end of saving + ar("RPCS3SAV"_u64); + ar(std::endian::native == std::endian::little); + ar(g_cfg.savestate.state_inspection_mode.get()); + ar(std::array{}); // Reserved for future use + ar(usz{0}); // Offset of versioning data, to be overwritten at the end of saving - if (auto dir = vfs::get("/dev_bdvd/PS3_GAME"); fs::is_dir(dir) && !fs::is_file(fs::get_parent_dir(dir) + "/PS3_DISC.SFB")) - { - // Fake /dev_bdvd/PS3_GAME detected, use HDD0 for m_path restoration - ensure(vfs::unmount("/dev_bdvd/PS3_GAME")); - ar(vfs::retrieve(m_path)); - ar(vfs::retrieve(disc)); - ensure(vfs::mount("/dev_bdvd/PS3_GAME", dir)); - } - else - { - ar(vfs::retrieve(m_path)); - ar(!m_title_id.empty() && !vfs::get("/dev_bdvd").empty() ? m_title_id : vfs::retrieve(disc)); - } + if (auto dir = vfs::get("/dev_bdvd/PS3_GAME"); fs::is_dir(dir) && !fs::is_file(fs::get_parent_dir(dir) + "/PS3_DISC.SFB")) + { + // Fake /dev_bdvd/PS3_GAME detected, use HDD0 for m_path restoration + ensure(vfs::unmount("/dev_bdvd/PS3_GAME")); + ar(vfs::retrieve(m_path)); + ar(vfs::retrieve(disc)); + ensure(vfs::mount("/dev_bdvd/PS3_GAME", dir)); + } + else + { + ar(vfs::retrieve(m_path)); + ar(!m_title_id.empty() && !vfs::get("/dev_bdvd").empty() ? m_title_id : vfs::retrieve(disc)); + } - ar(klic.empty() ? std::array{} : std::bit_cast>(klic[0])); - ar(m_game_dir); - save_hdd1(); - save_hdd0(); - ar(std::array{}); // Reserved for future use - vm::save(ar); - g_fxo->save(ar); + ar(klic.empty() ? std::array{} : std::bit_cast>(klic[0])); + ar(m_game_dir); + save_hdd1(); + save_hdd0(); + ar(std::array{}); // Reserved for future use + vm::save(ar); + g_fxo->save(ar); - bs_t extension_flags{SaveStateExtentionFlags1::SupportsMenuOpenResume}; + bs_t extension_flags{SaveStateExtentionFlags1::SupportsMenuOpenResume}; - if (g_fxo->get().active) - { - extension_flags += SaveStateExtentionFlags1::ShouldCloseMenu; - } + if (g_fxo->get().active) + { + extension_flags += SaveStateExtentionFlags1::ShouldCloseMenu; + } - ar(extension_flags); + ar(extension_flags); - ar(std::array{}); // Reserved for future use - ar(timestamp); - }); + ar(std::array{}); // Reserved for future use + ar(timestamp); + }); - // Join it - emu_state_cap_thread(); + // Join it + emu_state_cap_thread(); - if (emu_state_cap_thread == thread_state::errored) - { - sys_log.error("Saving savestate failed due to fatal error!"); - to_ar.reset(); - savestate = false; + if (emu_state_cap_thread == thread_state::errored) + { + sys_log.error("Saving savestate failed due to fatal error!"); + to_ar.reset(); + savestate = false; + } } - } - cpu_thread::cleanup(); + stop_watchdog = thread_state::aborting; - initialize_timebased_time(0, true); + if (savestate) + { + const std::string path = get_savestate_path(m_title_id, m_path); - lv2_obj::cleanup(); + fs::pending_file file(path); - g_fxo->reset(); + // Identifer -> version + std::vector> used_serial = read_used_savestate_versions(); - sys_log.notice("Objects cleared..."); + auto& ar = *to_ar; + const usz pos = ar.seek_end(); + std::memcpy(&ar.data[10], &pos, 8);// Set offset + ar(used_serial); - vm::close(); + if (!file.file || (file.file.write(ar.data), !file.commit())) + { + sys_log.error("Failed to write savestate to file! (path='%s', %s)", path, fs::g_tls_error); + savestate = false; + } + else + { + std::string old_path = path.substr(0, path.find_last_not_of(fs::delim)); + std::string old_path2 = old_path; - jit_runtime::finalize(); + old_path2.insert(old_path.find_last_of(fs::delim) + 1, "old-"sv); + old_path.insert(old_path.find_last_of(fs::delim) + 1, "used_"sv); - perf_stat_base::report(); + if (fs::remove_file(old_path)) + { + sys_log.success("Old savestate has been removed: path='%s'", old_path); + } - static u64 aw_refs = 0; - static u64 aw_colm = 0; - static u64 aw_colc = 0; - static u64 aw_used = 0; + // For backwards compatibility - avoid having loose files + if (fs::remove_file(old_path2)) + { + sys_log.success("Old savestate has been removed: path='%s'", old_path2); + } - aw_refs = 0; - aw_colm = 0; - aw_colc = 0; - aw_used = 0; + sys_log.success("Saved savestate! path='%s'", path); - atomic_wait::parse_hashtable([](u64 /*id*/, u32 refs, u64 ptr, u32 maxc) -> bool - { - aw_refs += refs != 0; - aw_used += ptr != 0; + if (!g_cfg.savestate.suspend_emu) + { + // Allow to reboot from GUI + m_path = path; + } + } - aw_colm = std::max(aw_colm, maxc); - aw_colc += maxc != 0; + ar.set_reading_state(); + } - return false; - }); + // Final termination from main thread (move the last ownership of join thread in order to destroy it) + CallFromMainThread([join_thread = std::move(join_thread), savestate, allow_autoexit, this]() mutable + { + cpu_thread::cleanup(); - sys_log.notice("Atomic wait hashtable stats: [in_use=%u, used=%u, max_collision_weight=%u, total_collisions=%u]", aw_refs, aw_used, aw_colm, aw_colc); + initialize_timebased_time(0, true); - m_stop_ctr++; - m_stop_ctr.notify_all(); + lv2_obj::cleanup(); - if (savestate) - { - const std::string path = get_savestate_path(m_title_id, m_path); + g_fxo->reset(); - fs::pending_file file(path); + sys_log.notice("Objects cleared..."); - // Identifer -> version - std::vector> used_serial = read_used_savestate_versions(); + vm::close(); - auto& ar = *to_ar; - const usz pos = ar.seek_end(); - std::memcpy(&ar.data[10], &pos, 8);// Set offset - ar(used_serial); + jit_runtime::finalize(); - if (!file.file || (file.file.write(ar.data), !file.commit())) - { - sys_log.error("Failed to write savestate to file! (path='%s', %s)", path, fs::g_tls_error); - savestate = false; - } - else - { - std::string old_path = path.substr(0, path.find_last_not_of(fs::delim)); - std::string old_path2 = old_path; + perf_stat_base::report(); - old_path2.insert(old_path.find_last_of(fs::delim) + 1, "old-"sv); - old_path.insert(old_path.find_last_of(fs::delim) + 1, "used_"sv); + static u64 aw_refs = 0; + static u64 aw_colm = 0; + static u64 aw_colc = 0; + static u64 aw_used = 0; - if (fs::remove_file(old_path)) - { - sys_log.success("Old savestate has been removed: path='%s'", old_path); - } + aw_refs = 0; + aw_colm = 0; + aw_colc = 0; + aw_used = 0; - // For backwards compatibility - avoid having loose files - if (fs::remove_file(old_path2)) + atomic_wait::parse_hashtable([](u64 /*id*/, u32 refs, u64 ptr, u32 maxc) -> bool { - sys_log.success("Old savestate has been removed: path='%s'", old_path2); - } + aw_refs += refs != 0; + aw_used += ptr != 0; - sys_log.success("Saved savestate! path='%s'", path); + aw_colm = std::max(aw_colm, maxc); + aw_colc += maxc != 0; - if (!g_cfg.savestate.suspend_emu) - { - // Allow to reboot from GUI - m_path = path; - } - } + return false; + }); - ar.set_reading_state(); - } + sys_log.notice("Atomic wait hashtable stats: [in_use=%u, used=%u, max_collision_weight=%u, total_collisions=%u]", aw_refs, aw_used, aw_colm, aw_colc); - // Boot arg cleanup (preserved in the case restarting) - argv.clear(); - envp.clear(); - data.clear(); - disc.clear(); - klic.clear(); - hdd1.clear(); - init_mem_containers = nullptr; - m_config_path.clear(); - m_config_mode = cfg_mode::custom; - m_ar.reset(); - read_used_savestate_versions(); - m_savestate_extension_flags1 = {}; + m_stop_ctr++; + m_stop_ctr.notify_all(); - // Always Enable display sleep, not only if it was prevented. - enable_display_sleep(); + // Boot arg cleanup (preserved in the case restarting) + argv.clear(); + envp.clear(); + data.clear(); + disc.clear(); + klic.clear(); + hdd1.clear(); + init_mem_containers = nullptr; + m_config_path.clear(); + m_config_mode = cfg_mode::custom; + m_ar.reset(); + read_used_savestate_versions(); + m_savestate_extension_flags1 = {}; - if (allow_autoexit) - { - Quit(g_cfg.misc.autoexit.get()); - } + // Always Enable display sleep, not only if it was prevented. + enable_display_sleep(); - return to_ar; + if (allow_autoexit) + { + Quit(g_cfg.misc.autoexit.get()); + } + + if (after_kill_callback) + { + after_kill_callback(); + after_kill_callback = nullptr; + } + }); + })); } game_boot_result Emulator::Restart(bool graceful) diff --git a/rpcs3/Emu/System.h b/rpcs3/Emu/System.h index a06ed67653e9..645f562c71df 100644 --- a/rpcs3/Emu/System.h +++ b/rpcs3/Emu/System.h @@ -21,6 +21,7 @@ enum class video_renderer; enum class system_state : u32 { stopped, + stopping, running, paused, frozen, // paused but cannot resume @@ -59,6 +60,7 @@ struct EmuCallbacks std::function on_stop; std::function on_ready; std::function on_missing_fw; + std::function>)> on_emulation_stop_no_response; std::function enable_disc_eject; std::function enable_disc_insert; std::function)> try_to_quit; // (force_quit, on_exit) Try to close RPCS3 @@ -215,6 +217,7 @@ class Emulator final std::string disc; std::string hdd1; std::function init_mem_containers; + std::function after_kill_callback; u32 m_boot_source_type = 0; // CELL_GAME_GAMETYPE_SYS @@ -322,17 +325,17 @@ class Emulator final bool Pause(bool freeze_emulation = false, bool show_resume_message = true); void Resume(); void GracefulShutdown(bool allow_autoexit = true, bool async_op = false, bool savestate = false); - std::shared_ptr Kill(bool allow_autoexit = true, bool savestate = false); + void Kill(bool allow_autoexit = true, bool savestate = false); game_boot_result Restart(bool graceful = true); bool Quit(bool force_quit); static void CleanUp(); bool IsRunning() const { return m_state == system_state::running; } bool IsPaused() const { return m_state >= system_state::paused; } // ready/starting are also considered paused by this function - bool IsStopped() const { return m_state == system_state::stopped; } + bool IsStopped() const { return m_state <= system_state::stopping; } bool IsReady() const { return m_state == system_state::ready; } bool IsStarting() const { return m_state == system_state::starting; } - auto GetStatus(bool fixup = true) const { system_state state = m_state; return fixup && state == system_state::frozen ? system_state::paused : state; } + auto GetStatus(bool fixup = true) const { system_state state = m_state; return fixup && state == system_state::frozen ? system_state::paused : fixup && state == system_state::stopping ? system_state::stopped : state; } bool HasGui() const { return m_has_gui; } void SetHasGui(bool has_gui) { m_has_gui = has_gui; } diff --git a/rpcs3/headless_application.cpp b/rpcs3/headless_application.cpp index c1c2f50e6ba1..c926b59d9df2 100644 --- a/rpcs3/headless_application.cpp +++ b/rpcs3/headless_application.cpp @@ -137,6 +137,7 @@ void headless_application::InitializeCallbacks() callbacks.on_resume = []() {}; callbacks.on_stop = []() {}; callbacks.on_ready = []() {}; + callbacks.on_emulation_stop_no_response = [](std::shared_ptr> closed_successfully) { if (!*closed_successfully) std::abort(); }; callbacks.enable_disc_eject = [](bool) {}; callbacks.enable_disc_insert = [](bool) {}; diff --git a/rpcs3/rpcs3qt/gs_frame.cpp b/rpcs3/rpcs3qt/gs_frame.cpp index 22ea2dbabd3d..44b927738a46 100644 --- a/rpcs3/rpcs3qt/gs_frame.cpp +++ b/rpcs3/rpcs3qt/gs_frame.cpp @@ -605,10 +605,13 @@ void gs_frame::close() if (!Emu.IsStopped()) { // Blocking shutdown request. Obsolete, but I'm keeping it here as last resort. - Emu.GracefulShutdown(true, false); + Emu.after_kill_callback = [this](){ deleteLater(); }; + Emu.GracefulShutdown(true); + } + else + { + deleteLater(); } - - deleteLater(); }); } diff --git a/rpcs3/rpcs3qt/gui_application.cpp b/rpcs3/rpcs3qt/gui_application.cpp index cae798750561..c5a3372d4e74 100644 --- a/rpcs3/rpcs3qt/gui_application.cpp +++ b/rpcs3/rpcs3qt/gui_application.cpp @@ -569,6 +569,53 @@ void gui_application::InitializeCallbacks() }; } + callbacks.on_emulation_stop_no_response = [this](std::shared_ptr> closed_successfully) + { + Emu.CallFromMainThread([this, closed_successfully] + { + const auto seconds = std::make_shared(10); + + QMessageBox* mb = new QMessageBox(); + mb->setWindowTitle(tr("PS3 Game/Application Is Unresponsive")); + mb->setIcon(QMessageBox::Critical); + mb->setStandardButtons(QMessageBox::Yes | QMessageBox::No); + mb->setDefaultButton(QMessageBox::No); + mb->button(QMessageBox::Yes)->setText(tr("Close Unsafely")); + mb->button(QMessageBox::No)->setText(tr("Keep Waiting")); + + QString text_base = tr("Waiting for %0 seconds already to stop emulation without success." + "\nDo you want to wait more for it to safely stop or to stop it unsafely now?"); + + mb->setText(text_base.arg(10)); + mb->layout()->setSizeConstraint(QLayout::SetFixedSize); + mb->setAttribute(Qt::WA_DeleteOnClose); + + QTimer* update_time = new QTimer(mb); + connect(update_time, &QTimer::timeout, [mb, seconds, text_base, closed_successfully]() + { + *seconds += 1; + mb->setText(text_base.arg(*seconds)); + + if (*closed_successfully) + { + mb->reject(); + } + }); + + update_time->start(1000); + + connect(mb, &QDialog::accepted, mb, [closed_successfully] + { + if (!*closed_successfully) + { + std::abort(); + } + }); + + mb->open(); + }); + }; + Emu.SetCallbacks(std::move(callbacks)); }