diff --git a/rpcs3/Emu/Cell/lv2/sys_game.cpp b/rpcs3/Emu/Cell/lv2/sys_game.cpp index 40fc85febd..a6cab86c1c 100644 --- a/rpcs3/Emu/Cell/lv2/sys_game.cpp +++ b/rpcs3/Emu/Cell/lv2/sys_game.cpp @@ -5,53 +5,107 @@ #include "Emu/System.h" #include "Emu/system_config.h" -#include +#include "Emu/IdManager.h" +#include "Utilities/Thread.h" #include "sys_game.h" LOG_CHANNEL(sys_game); -atomic_t watchdog_stopped = true; -atomic_t watchdog_last_clear; -u64 get_timestamp() +struct watchdog_t { - return (get_system_time() - Emu.GetPauseTime()); -} + struct alignas(8) control_t + { + bool needs_restart = false; + bool active = false; + char pad[sizeof(u32) - sizeof(bool) * 2]{}; + u32 timeout = 0; + }; + + atomic_t control; + + void operator()() + { + u64 start_time = get_system_time(); + u64 old_time = start_time; + u64 current_time = old_time; + + constexpr u64 sleep_time = 50'000; + + while (thread_ctrl::state() != thread_state::aborting) + { + if (Emu.GetStatus(false) == system_state::paused) + { + start_time += current_time - old_time; + old_time = current_time; + thread_ctrl::wait_for(sleep_time); + current_time = get_system_time(); + continue; + } + + old_time = std::exchange(current_time, get_system_time()); + + const auto old = control.fetch_op([&](control_t& data) + { + if (data.needs_restart) + { + data.needs_restart = false; + return true; + } + + return false; + }).first; + + if (old.active && old.needs_restart) + { + start_time = current_time; + old_time = current_time; + continue; + } + + if (old.active && current_time - start_time >= old.timeout) + { + sys_game.warning("Watchdog timeout! Restarting the game..."); + + Emu.CallFromMainThread([]() + { + Emu.Restart(); + }); + + return; + } + + thread_ctrl::wait_for(sleep_time); + } + } + + static constexpr auto thread_name = "LV2 Watchdog Thread"sv; +}; error_code _sys_game_watchdog_start(u32 timeout) { sys_game.trace("sys_game_watchdog_start(timeout=%d)", timeout); - if (!watchdog_stopped) + // According to disassembly + timeout *= 1'000'000; + timeout &= -64; + + if (!g_fxo->get>().control.fetch_op([&](watchdog_t::control_t& data) + { + if (data.active) + { + return false; + } + + data.needs_restart = true; + data.active = true; + data.timeout = timeout; + return true; + }).second) { return CELL_EABORT; } - auto watchdog = [=]() - { - while (!watchdog_stopped) - { - if (Emu.IsStopped() || get_timestamp() - watchdog_last_clear > timeout * 1000000) - { - watchdog_stopped = true; - if (!Emu.IsStopped()) - { - sys_game.warning("Watchdog timeout! Restarting the game..."); - Emu.CallFromMainThread([]() - { - Emu.Restart(); - }); - } - break; - } - std::this_thread::sleep_for(1s); - } - }; - - watchdog_stopped = false; - watchdog_last_clear = get_timestamp(); - std::thread(watchdog).detach(); - return CELL_OK; } @@ -59,7 +113,16 @@ error_code _sys_game_watchdog_stop() { sys_game.trace("sys_game_watchdog_stop()"); - watchdog_stopped = true; + g_fxo->get>().control.fetch_op([](watchdog_t::control_t& data) + { + if (!data.active) + { + return false; + } + + data.active = false; + return true; + }); return CELL_OK; } @@ -68,7 +131,16 @@ error_code _sys_game_watchdog_clear() { sys_game.trace("sys_game_watchdog_clear()"); - watchdog_last_clear = get_timestamp(); + g_fxo->get>().control.fetch_op([](watchdog_t::control_t& data) + { + if (!data.active || data.needs_restart) + { + return false; + } + + data.needs_restart = true; + return true; + }); return CELL_OK; } diff --git a/rpcs3/Emu/System.h b/rpcs3/Emu/System.h index 51839a438a..4b7921e193 100644 --- a/rpcs3/Emu/System.h +++ b/rpcs3/Emu/System.h @@ -314,7 +314,7 @@ public: bool IsStopped() const { return m_state == system_state::stopped; } bool IsReady() const { return m_state == system_state::ready; } bool IsStarting() const { return m_state == system_state::starting; } - auto GetStatus() const { system_state state = m_state; return 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 : state; } bool HasGui() const { return m_has_gui; } void SetHasGui(bool has_gui) { m_has_gui = has_gui; }