mirror of
https://github.com/RPCS3/rpcs3.git
synced 2025-03-18 04:21:00 +00:00
Make stopping emulation not pause or crash UI
* Make the UI and main thread available when stopping emulation. * Make BlockingCallFromMainThread always execute, preventing bugs when it unexpectedly did not. * Add error code for when starting emulation when Emu.Kill() is in progress.
This commit is contained in:
parent
4f5348c7d4
commit
d34b3190f7
@ -425,7 +425,6 @@ void lv2_exitspawn(ppu_thread& ppu, std::vector<std::string>& 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<loaded_npdrm_keys>().last_key();
|
||||
|
||||
@ -457,27 +456,32 @@ void lv2_exitspawn(ppu_thread& ppu, std::vector<std::string>& argv, std::vector<
|
||||
g_fxo->init<lv2_memory_container>(std::min(old_size - total_size, sdk_suggested_mem) + total_size);
|
||||
};
|
||||
|
||||
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.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);
|
||||
|
||||
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);
|
||||
}
|
||||
};
|
||||
|
||||
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.klic.emplace_back(klic);
|
||||
}
|
||||
|
||||
Emu.SetForceBoot(true);
|
||||
|
||||
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);
|
||||
}
|
||||
});
|
||||
|
||||
// Wait for GUI thread
|
||||
|
@ -52,12 +52,15 @@ namespace rsx
|
||||
|
||||
Emu.CallFromMainThread([suspend_mode]()
|
||||
{
|
||||
Emu.Kill(false, true);
|
||||
|
||||
if (!suspend_mode)
|
||||
{
|
||||
Emu.Restart();
|
||||
Emu.after_kill_callback = []()
|
||||
{
|
||||
Emu.Restart();
|
||||
};
|
||||
}
|
||||
|
||||
Emu.Kill(false, true);
|
||||
});
|
||||
|
||||
return page_navigation::exit;
|
||||
|
@ -177,10 +177,10 @@ void Emulator::BlockingCallFromMainThread(std::function<void()>&& func) const
|
||||
|
||||
CallFromMainThread(std::move(func), &wake_up);
|
||||
|
||||
while (!wake_up && !IsStopped())
|
||||
while (!wake_up)
|
||||
{
|
||||
ensure(thread_ctrl::get_current());
|
||||
thread_ctrl::wait_on(wake_up, false);
|
||||
wake_up.wait(false);
|
||||
}
|
||||
}
|
||||
|
||||
@ -635,6 +635,11 @@ std::string Emulator::GetBackgroundPicturePath() const
|
||||
|
||||
bool Emulator::BootRsxCapture(const std::string& path)
|
||||
{
|
||||
if (m_state != system_state::stopped)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
fs::file in_file(path);
|
||||
|
||||
if (!in_file)
|
||||
@ -781,6 +786,11 @@ void Emulator::SetForceBoot(bool force_boot)
|
||||
|
||||
game_boot_result Emulator::Load(const std::string& title_id, bool is_disc_patch, usz recursion_count)
|
||||
{
|
||||
if (m_state != system_state::stopped)
|
||||
{
|
||||
return game_boot_result::still_running;
|
||||
}
|
||||
|
||||
m_ar.reset();
|
||||
|
||||
{
|
||||
@ -817,11 +827,6 @@ game_boot_result Emulator::Load(const std::string& title_id, bool is_disc_patch,
|
||||
}
|
||||
}
|
||||
|
||||
if (!IsStopped())
|
||||
{
|
||||
Kill();
|
||||
}
|
||||
|
||||
if (!title_id.empty())
|
||||
{
|
||||
m_title_id = title_id;
|
||||
@ -2422,8 +2427,14 @@ 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)
|
||||
{
|
||||
while (!async_op && m_state != system_state::stopped)
|
||||
{
|
||||
process_qt_events();
|
||||
std::this_thread::sleep_for(16ms);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
@ -2438,6 +2449,13 @@ 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)
|
||||
{
|
||||
process_qt_events();
|
||||
std::this_thread::sleep_for(16ms);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
@ -2483,22 +2501,26 @@ void Emulator::GracefulShutdown(bool allow_autoexit, bool async_op, bool savesta
|
||||
else
|
||||
{
|
||||
perform_kill();
|
||||
|
||||
while (m_state != 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<utils::serial> Emulator::Kill(bool allow_autoexit, bool savestate)
|
||||
void Emulator::Kill(bool allow_autoexit, bool savestate)
|
||||
{
|
||||
std::shared_ptr<utils::serial> to_ar;
|
||||
|
||||
if (savestate && !try_lock_spu_threads_in_a_state_compatible_with_savestates())
|
||||
if (!IsStopped() && savestate && !try_lock_spu_threads_in_a_state_compatible_with_savestates())
|
||||
{
|
||||
sys_log.error("Failed to savestate: failed to lock SPU threads execution.");
|
||||
}
|
||||
|
||||
if (savestate && !try_lock_vdec_context_creation())
|
||||
if (!IsStopped() && savestate && !try_lock_vdec_context_creation())
|
||||
{
|
||||
try_lock_spu_threads_in_a_state_compatible_with_savestates(true);
|
||||
|
||||
@ -2507,7 +2529,7 @@ std::shared_ptr<utils::serial> 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 = []()
|
||||
@ -2515,8 +2537,23 @@ std::shared_ptr<utils::serial> 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();
|
||||
@ -2526,11 +2563,12 @@ std::shared_ptr<utils::serial> 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...");
|
||||
@ -2546,28 +2584,6 @@ std::shared_ptr<utils::serial> 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
|
||||
|
||||
if (auto rsx = g_fxo->try_get<rsx::thread>())
|
||||
@ -2588,257 +2604,313 @@ std::shared_ptr<utils::serial> 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<std::shared_ptr<void>> join_thread = std::make_shared<std::shared_ptr<void>>();
|
||||
|
||||
// Join threads
|
||||
for (const auto& [type, data] : *g_fxo)
|
||||
auto make_ptr = [](auto ptr)
|
||||
{
|
||||
if (type.stop)
|
||||
{
|
||||
type.stop(data, thread_state::finished);
|
||||
}
|
||||
}
|
||||
return std::shared_ptr<std::remove_pointer_t<decltype(ptr)>>(ptr);
|
||||
};
|
||||
|
||||
// Save it first for maximum timing accuracy
|
||||
const u64 timestamp = get_timebased_time();
|
||||
|
||||
stop_watchdog = thread_state::aborting;
|
||||
|
||||
sys_log.notice("All threads have been stopped.");
|
||||
|
||||
if (savestate)
|
||||
*join_thread = make_ptr(new named_thread("Emulation Join Thread"sv, [join_thread, savestate, allow_autoexit, this]() mutable
|
||||
{
|
||||
to_ar = std::make_unique<utils::serial>();
|
||||
|
||||
// Savestate thread
|
||||
named_thread emu_state_cap_thread("Emu State Capture Thread", [&]()
|
||||
named_thread stop_watchdog("Stop Watchdog"sv, [this]()
|
||||
{
|
||||
g_tls_log_prefix = []()
|
||||
const auto closed_sucessfully = std::make_shared<atomic_t<bool>>(false);
|
||||
|
||||
for (int i = 0; thread_ctrl::state() != thread_state::aborting;)
|
||||
{
|
||||
return fmt::format("Emu State Capture Thread: '%s'", g_tls_serialize_name);
|
||||
};
|
||||
|
||||
auto& ar = *to_ar;
|
||||
|
||||
read_used_savestate_versions(); // Reset version data
|
||||
USING_SERIALIZATION_VERSION(global_version);
|
||||
|
||||
// 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);
|
||||
};
|
||||
|
||||
auto save_hdd1 = [&]()
|
||||
{
|
||||
const std::string _path = vfs::get("/dev_hdd1");
|
||||
std::string_view path = _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())
|
||||
// We don't need accurate timekeeping, using clocks may interfere with debugging
|
||||
if (i >= 2000)
|
||||
{
|
||||
save_tar(_path);
|
||||
}
|
||||
};
|
||||
// Total amount of waiting: about 10s
|
||||
GetCallbacks().on_emulation_stop_no_response(closed_sucessfully, 10);
|
||||
|
||||
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))
|
||||
while (thread_ctrl::state() != thread_state::aborting)
|
||||
{
|
||||
if (entry.is_directory && entry.name != "." && entry.name != "..")
|
||||
thread_ctrl::wait_for(5'000);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
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;
|
||||
});
|
||||
|
||||
// Join threads
|
||||
for (const auto& [type, data] : *g_fxo)
|
||||
{
|
||||
if (type.stop)
|
||||
{
|
||||
type.stop(data, thread_state::finished);
|
||||
}
|
||||
}
|
||||
|
||||
// Save it first for maximum timing accuracy
|
||||
const u64 timestamp = get_timebased_time();
|
||||
|
||||
sys_log.notice("All threads have been stopped.");
|
||||
|
||||
std::unique_ptr<utils::serial> to_ar;
|
||||
|
||||
if (savestate)
|
||||
{
|
||||
to_ar = std::make_unique<utils::serial>();
|
||||
|
||||
// Savestate thread
|
||||
named_thread emu_state_cap_thread("Emu State Capture Thread", [&]()
|
||||
{
|
||||
g_tls_log_prefix = []()
|
||||
{
|
||||
return fmt::format("Emu State Capture Thread: '%s'", g_tls_serialize_name);
|
||||
};
|
||||
|
||||
auto& ar = *to_ar;
|
||||
|
||||
read_used_savestate_versions(); // Reset version data
|
||||
USING_SERIALIZATION_VERSION(global_version);
|
||||
|
||||
// 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);
|
||||
};
|
||||
|
||||
auto save_hdd1 = [&]()
|
||||
{
|
||||
const std::string _path = vfs::get("/dev_hdd1");
|
||||
std::string_view path = _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())
|
||||
{
|
||||
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("RPCS3SAV"_u64);
|
||||
ar(std::endian::native == std::endian::little);
|
||||
ar(g_cfg.savestate.state_inspection_mode.get());
|
||||
ar(std::array<u8, 32>{}); // 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));
|
||||
}
|
||||
|
||||
ar(std::string{});
|
||||
};
|
||||
ar(klic.empty() ? std::array<u8, 16>{} : std::bit_cast<std::array<u8, 16>>(klic[0]));
|
||||
ar(m_game_dir);
|
||||
save_hdd1();
|
||||
save_hdd0();
|
||||
ar(std::array<u8, 32>{}); // Reserved for future use
|
||||
vm::save(ar);
|
||||
g_fxo->save(ar);
|
||||
|
||||
ar("RPCS3SAV"_u64);
|
||||
ar(std::endian::native == std::endian::little);
|
||||
ar(g_cfg.savestate.state_inspection_mode.get());
|
||||
ar(std::array<u8, 32>{}); // Reserved for future use
|
||||
ar(usz{0}); // Offset of versioning data, to be overwritten at the end of saving
|
||||
bs_t<SaveStateExtentionFlags1> extension_flags{SaveStateExtentionFlags1::SupportsMenuOpenResume};
|
||||
|
||||
if (auto dir = vfs::get("/dev_bdvd/PS3_GAME"); fs::is_dir(dir) && !fs::is_file(fs::get_parent_dir(dir) + "/PS3_DISC.SFB"))
|
||||
if (g_fxo->get<SysutilMenuOpenStatus>().active)
|
||||
{
|
||||
extension_flags += SaveStateExtentionFlags1::ShouldCloseMenu;
|
||||
}
|
||||
|
||||
ar(extension_flags);
|
||||
|
||||
ar(std::array<u8, 32>{}); // Reserved for future use
|
||||
ar(timestamp);
|
||||
});
|
||||
|
||||
// Join it
|
||||
emu_state_cap_thread();
|
||||
|
||||
if (emu_state_cap_thread == thread_state::errored)
|
||||
{
|
||||
// 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));
|
||||
sys_log.error("Saving savestate failed due to fatal error!");
|
||||
to_ar.reset();
|
||||
savestate = false;
|
||||
}
|
||||
}
|
||||
|
||||
stop_watchdog = thread_state::aborting;
|
||||
|
||||
if (savestate)
|
||||
{
|
||||
const std::string path = get_savestate_path(m_title_id, m_path);
|
||||
|
||||
fs::pending_file file(path);
|
||||
|
||||
// Identifer -> version
|
||||
std::vector<std::pair<u16, u16>> used_serial = read_used_savestate_versions();
|
||||
|
||||
auto& ar = *to_ar;
|
||||
const usz pos = ar.seek_end();
|
||||
std::memcpy(&ar.data[10], &pos, 8);// Set offset
|
||||
ar(used_serial);
|
||||
|
||||
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
|
||||
{
|
||||
ar(vfs::retrieve(m_path));
|
||||
ar(!m_title_id.empty() && !vfs::get("/dev_bdvd").empty() ? m_title_id : vfs::retrieve(disc));
|
||||
std::string old_path = path.substr(0, path.find_last_not_of(fs::delim));
|
||||
std::string old_path2 = old_path;
|
||||
|
||||
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);
|
||||
|
||||
if (fs::remove_file(old_path))
|
||||
{
|
||||
sys_log.success("Old savestate has been removed: path='%s'", old_path);
|
||||
}
|
||||
|
||||
// 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);
|
||||
}
|
||||
|
||||
sys_log.success("Saved savestate! path='%s'", path);
|
||||
|
||||
if (!g_cfg.savestate.suspend_emu)
|
||||
{
|
||||
// Allow to reboot from GUI
|
||||
m_path = path;
|
||||
}
|
||||
}
|
||||
|
||||
ar(klic.empty() ? std::array<u8, 16>{} : std::bit_cast<std::array<u8, 16>>(klic[0]));
|
||||
ar(m_game_dir);
|
||||
save_hdd1();
|
||||
save_hdd0();
|
||||
ar(std::array<u8, 32>{}); // Reserved for future use
|
||||
vm::save(ar);
|
||||
g_fxo->save(ar);
|
||||
ar.set_reading_state();
|
||||
}
|
||||
|
||||
bs_t<SaveStateExtentionFlags1> extension_flags{SaveStateExtentionFlags1::SupportsMenuOpenResume};
|
||||
// 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
|
||||
{
|
||||
cpu_thread::cleanup();
|
||||
|
||||
if (g_fxo->get<SysutilMenuOpenStatus>().active)
|
||||
initialize_timebased_time(0, true);
|
||||
|
||||
lv2_obj::cleanup();
|
||||
|
||||
g_fxo->reset();
|
||||
|
||||
sys_log.notice("Objects cleared...");
|
||||
|
||||
vm::close();
|
||||
|
||||
jit_runtime::finalize();
|
||||
|
||||
perf_stat_base::report();
|
||||
|
||||
static u64 aw_refs = 0;
|
||||
static u64 aw_colm = 0;
|
||||
static u64 aw_colc = 0;
|
||||
static u64 aw_used = 0;
|
||||
|
||||
aw_refs = 0;
|
||||
aw_colm = 0;
|
||||
aw_colc = 0;
|
||||
aw_used = 0;
|
||||
|
||||
atomic_wait::parse_hashtable([](u64 /*id*/, u32 refs, u64 ptr, u32 maxc) -> bool
|
||||
{
|
||||
extension_flags += SaveStateExtentionFlags1::ShouldCloseMenu;
|
||||
aw_refs += refs != 0;
|
||||
aw_used += ptr != 0;
|
||||
|
||||
aw_colm = std::max<u64>(aw_colm, maxc);
|
||||
aw_colc += maxc != 0;
|
||||
|
||||
return false;
|
||||
});
|
||||
|
||||
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);
|
||||
|
||||
m_stop_ctr++;
|
||||
m_stop_ctr.notify_all();
|
||||
|
||||
// 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 = {};
|
||||
|
||||
// Complete the operation
|
||||
m_state = system_state::stopped;
|
||||
GetCallbacks().on_stop();
|
||||
|
||||
// Always Enable display sleep, not only if it was prevented.
|
||||
enable_display_sleep();
|
||||
|
||||
if (allow_autoexit)
|
||||
{
|
||||
Quit(g_cfg.misc.autoexit.get());
|
||||
}
|
||||
|
||||
ar(extension_flags);
|
||||
|
||||
ar(std::array<u8, 32>{}); // Reserved for future use
|
||||
ar(timestamp);
|
||||
if (after_kill_callback)
|
||||
{
|
||||
after_kill_callback();
|
||||
after_kill_callback = nullptr;
|
||||
}
|
||||
});
|
||||
|
||||
// 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;
|
||||
}
|
||||
}
|
||||
|
||||
cpu_thread::cleanup();
|
||||
|
||||
initialize_timebased_time(0, true);
|
||||
|
||||
lv2_obj::cleanup();
|
||||
|
||||
g_fxo->reset();
|
||||
|
||||
sys_log.notice("Objects cleared...");
|
||||
|
||||
vm::close();
|
||||
|
||||
jit_runtime::finalize();
|
||||
|
||||
perf_stat_base::report();
|
||||
|
||||
static u64 aw_refs = 0;
|
||||
static u64 aw_colm = 0;
|
||||
static u64 aw_colc = 0;
|
||||
static u64 aw_used = 0;
|
||||
|
||||
aw_refs = 0;
|
||||
aw_colm = 0;
|
||||
aw_colc = 0;
|
||||
aw_used = 0;
|
||||
|
||||
atomic_wait::parse_hashtable([](u64 /*id*/, u32 refs, u64 ptr, u32 maxc) -> bool
|
||||
{
|
||||
aw_refs += refs != 0;
|
||||
aw_used += ptr != 0;
|
||||
|
||||
aw_colm = std::max<u64>(aw_colm, maxc);
|
||||
aw_colc += maxc != 0;
|
||||
|
||||
return false;
|
||||
});
|
||||
|
||||
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);
|
||||
|
||||
m_stop_ctr++;
|
||||
m_stop_ctr.notify_all();
|
||||
|
||||
if (savestate)
|
||||
{
|
||||
const std::string path = get_savestate_path(m_title_id, m_path);
|
||||
|
||||
fs::pending_file file(path);
|
||||
|
||||
// Identifer -> version
|
||||
std::vector<std::pair<u16, u16>> used_serial = read_used_savestate_versions();
|
||||
|
||||
auto& ar = *to_ar;
|
||||
const usz pos = ar.seek_end();
|
||||
std::memcpy(&ar.data[10], &pos, 8);// Set offset
|
||||
ar(used_serial);
|
||||
|
||||
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;
|
||||
|
||||
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);
|
||||
|
||||
if (fs::remove_file(old_path))
|
||||
{
|
||||
sys_log.success("Old savestate has been removed: path='%s'", old_path);
|
||||
}
|
||||
|
||||
// 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);
|
||||
}
|
||||
|
||||
sys_log.success("Saved savestate! path='%s'", path);
|
||||
|
||||
if (!g_cfg.savestate.suspend_emu)
|
||||
{
|
||||
// Allow to reboot from GUI
|
||||
m_path = path;
|
||||
}
|
||||
}
|
||||
|
||||
ar.set_reading_state();
|
||||
}
|
||||
|
||||
// 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 = {};
|
||||
|
||||
// Always Enable display sleep, not only if it was prevented.
|
||||
enable_display_sleep();
|
||||
|
||||
if (allow_autoexit)
|
||||
{
|
||||
Quit(g_cfg.misc.autoexit.get());
|
||||
}
|
||||
|
||||
return to_ar;
|
||||
}));
|
||||
}
|
||||
|
||||
game_boot_result Emulator::Restart(bool graceful)
|
||||
|
@ -21,6 +21,7 @@ enum class video_renderer;
|
||||
enum class system_state : u32
|
||||
{
|
||||
stopped,
|
||||
stopping,
|
||||
running,
|
||||
paused,
|
||||
frozen, // paused but cannot resume
|
||||
@ -43,6 +44,7 @@ enum class game_boot_result : u32
|
||||
unsupported_disc_type,
|
||||
savestate_corrupted,
|
||||
savestate_version_unsupported,
|
||||
still_running,
|
||||
};
|
||||
|
||||
constexpr bool is_error(game_boot_result res)
|
||||
@ -59,6 +61,7 @@ struct EmuCallbacks
|
||||
std::function<void()> on_stop;
|
||||
std::function<void()> on_ready;
|
||||
std::function<bool()> on_missing_fw;
|
||||
std::function<void(std::shared_ptr<atomic_t<bool>>, int)> on_emulation_stop_no_response;
|
||||
std::function<void(bool enabled)> enable_disc_eject;
|
||||
std::function<void(bool enabled)> enable_disc_insert;
|
||||
std::function<bool(bool, std::function<void()>)> try_to_quit; // (force_quit, on_exit) Try to close RPCS3
|
||||
@ -215,6 +218,7 @@ public:
|
||||
std::string disc;
|
||||
std::string hdd1;
|
||||
std::function<void(u32)> init_mem_containers;
|
||||
std::function<void()> after_kill_callback;
|
||||
|
||||
u32 m_boot_source_type = 0; // CELL_GAME_GAMETYPE_SYS
|
||||
|
||||
@ -322,17 +326,17 @@ public:
|
||||
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<utils::serial> 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; }
|
||||
|
@ -10,6 +10,8 @@
|
||||
|
||||
#include <clocale>
|
||||
|
||||
[[noreturn]] void report_fatal_error(std::string_view text, bool is_html = false, bool include_help_text = true);
|
||||
|
||||
// For now, a trivial constructor/destructor. May add command line usage later.
|
||||
headless_application::headless_application(int& argc, char** argv) : QCoreApplication(argc, argv)
|
||||
{
|
||||
@ -137,6 +139,14 @@ void headless_application::InitializeCallbacks()
|
||||
callbacks.on_resume = []() {};
|
||||
callbacks.on_stop = []() {};
|
||||
callbacks.on_ready = []() {};
|
||||
callbacks.on_emulation_stop_no_response = [](std::shared_ptr<atomic_t<bool>> closed_successfully, int /*seconds_waiting_already*/)
|
||||
{
|
||||
if (!closed_successfully || !*closed_successfully)
|
||||
{
|
||||
report_fatal_error(tr("Stopping emulator took too long."
|
||||
"\nSome thread has probably deadlocked. Aborting.").toStdString());
|
||||
}
|
||||
};
|
||||
|
||||
callbacks.enable_disc_eject = [](bool) {};
|
||||
callbacks.enable_disc_insert = [](bool) {};
|
||||
|
@ -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();
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -49,6 +49,8 @@
|
||||
|
||||
LOG_CHANNEL(gui_log, "GUI");
|
||||
|
||||
[[noreturn]] void report_fatal_error(std::string_view text, bool is_html = false, bool include_help_text = true);
|
||||
|
||||
gui_application::gui_application(int& argc, char** argv) : QApplication(argc, argv)
|
||||
{
|
||||
}
|
||||
@ -569,6 +571,61 @@ void gui_application::InitializeCallbacks()
|
||||
};
|
||||
}
|
||||
|
||||
callbacks.on_emulation_stop_no_response = [this](std::shared_ptr<atomic_t<bool>> closed_successfully, int seconds_waiting_already)
|
||||
{
|
||||
const std::string terminate_message = tr("Stopping emulator took too long."
|
||||
"\nSome thread has probably deadlocked. Aborting.").toStdString();
|
||||
|
||||
if (!closed_successfully)
|
||||
{
|
||||
report_fatal_error(terminate_message);
|
||||
}
|
||||
|
||||
Emu.CallFromMainThread([this, closed_successfully, seconds_waiting_already, terminate_message]
|
||||
{
|
||||
const auto seconds = std::make_shared<int>(seconds_waiting_already);
|
||||
|
||||
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("Terminate RPCS3"));
|
||||
mb->button(QMessageBox::No)->setText(tr("Keep Waiting"));
|
||||
|
||||
QString text_base = tr("Waiting for %0 second(s) already to stop emulation without success."
|
||||
"\nKeep waiting or terminate RPCS3 unsafely at your own risk?");
|
||||
|
||||
mb->setText(text_base.arg(10));
|
||||
mb->layout()->setSizeConstraint(QLayout::SetFixedSize);
|
||||
mb->setAttribute(Qt::WA_DeleteOnClose);
|
||||
|
||||
QTimer* update_timer = new QTimer(mb);
|
||||
|
||||
connect(update_timer, &QTimer::timeout, [mb, seconds, text_base, closed_successfully]()
|
||||
{
|
||||
*seconds += 1;
|
||||
mb->setText(text_base.arg(*seconds));
|
||||
|
||||
if (*closed_successfully)
|
||||
{
|
||||
mb->reject();
|
||||
}
|
||||
});
|
||||
|
||||
connect(mb, &QDialog::accepted, mb, [closed_successfully, terminate_message]
|
||||
{
|
||||
if (!*closed_successfully)
|
||||
{
|
||||
report_fatal_error(terminate_message);
|
||||
}
|
||||
});
|
||||
|
||||
mb->open();
|
||||
update_timer->start(1000);
|
||||
});
|
||||
};
|
||||
|
||||
Emu.SetCallbacks(std::move(callbacks));
|
||||
}
|
||||
|
||||
|
@ -439,6 +439,9 @@ void main_window::show_boot_error(game_boot_result status)
|
||||
case game_boot_result::savestate_version_unsupported:
|
||||
message = tr("Savestate versioning data differes from your RPCS3 build.");
|
||||
break;
|
||||
case game_boot_result::still_running:
|
||||
message = tr("A game or PS3 application is still running or has yet to be fully stopped.");
|
||||
break;
|
||||
case game_boot_result::firmware_missing: // Handled elsewhere
|
||||
case game_boot_result::no_errors:
|
||||
return;
|
||||
@ -1854,8 +1857,6 @@ void main_window::OnEmuStop()
|
||||
const QString title = GetCurrentTitle();
|
||||
const QString play_tooltip = tr("Play %0").arg(title);
|
||||
|
||||
m_debugger_frame->UpdateUI();
|
||||
|
||||
ui->sysPauseAct->setText(tr("&Play"));
|
||||
ui->sysPauseAct->setIcon(m_icon_play);
|
||||
#ifdef _WIN32
|
||||
|
Loading…
x
Reference in New Issue
Block a user