From 962230f91e06a60b2395de6f0974fdd213854d4c Mon Sep 17 00:00:00 2001 From: JosJuice Date: Sun, 2 Jun 2024 15:10:25 +0200 Subject: [PATCH] Core: Store current state in less places Core::GetState reads from four different pieces of state: s_is_stopping, s_hardware_initialized, s_is_booting, and CPUManager::IsStepping. I'm keeping that last one as is for now because there's code in Dolphin that sets it directly, but we can unify the other three to make things easier to reason about. This commit also gets rid of s_is_started. This was previously used in Core::IsRunningAndStarted to ensure true wouldn't be returned until the CPU thread was started, but it wasn't used in Core::GetState, so Core::GetState would happily return State::Running after we had initialized the hardware but before we had initialized the CPU thread. As far as I know, there are no callers that have any real need to know whether the boot process is currently initializing the hardware or the CPU thread. Perhaps once upon a time there was a desire to make the apploader debuggable, but a long time has passed without anyone stepping up to implement it, and the way CBoot::RunApploader is implemented makes it rather difficult. So this commit makes all the functions in Core.cpp consider the core to still be starting until the CPU thread is started. --- Source/Android/jni/MainAndroid.cpp | 2 +- Source/Core/Core/ConfigManager.cpp | 10 +-- Source/Core/Core/Core.cpp | 90 ++++++++----------- Source/Core/Core/Core.h | 4 +- .../Core/Core/Debugger/PPCDebugInterface.cpp | 2 +- Source/Core/Core/IOS/ES/ES.cpp | 8 +- Source/Core/Core/IOS/IOS.cpp | 2 +- Source/Core/Core/Movie.cpp | 6 +- Source/Core/Core/State.cpp | 2 +- Source/Core/DolphinQt/HotkeyScheduler.cpp | 5 +- Source/Core/DolphinQt/MainWindow.cpp | 2 +- Source/Core/DolphinQt/RenderWidget.cpp | 2 +- Source/Core/VideoCommon/VideoConfig.cpp | 2 +- 13 files changed, 61 insertions(+), 76 deletions(-) diff --git a/Source/Android/jni/MainAndroid.cpp b/Source/Android/jni/MainAndroid.cpp index 9fcf1b33a3..66a0694a94 100644 --- a/Source/Android/jni/MainAndroid.cpp +++ b/Source/Android/jni/MainAndroid.cpp @@ -279,7 +279,7 @@ JNIEXPORT jboolean JNICALL Java_org_dolphinemu_dolphinemu_NativeLibrary_IsRunnin JNIEXPORT jboolean JNICALL Java_org_dolphinemu_dolphinemu_NativeLibrary_IsRunningAndStarted(JNIEnv*, jclass) { - return static_cast(Core::IsRunningAndStarted()); + return static_cast(Core::IsRunning(Core::System::GetInstance())); } JNIEXPORT jboolean JNICALL diff --git a/Source/Core/Core/ConfigManager.cpp b/Source/Core/Core/ConfigManager.cpp index e3c1ec1d15..8882076daa 100644 --- a/Source/Core/Core/ConfigManager.cpp +++ b/Source/Core/Core/ConfigManager.cpp @@ -185,22 +185,22 @@ void SConfig::SetRunningGameMetadata(const std::string& game_id, const std::stri m_title_description = title_database.Describe(m_gametdb_id, language); NOTICE_LOG_FMT(CORE, "Active title: {}", m_title_description); Host_TitleChanged(); - if (Core::IsRunning(system)) - { + + const bool is_running_or_starting = Core::IsRunningOrStarting(system); + if (is_running_or_starting) Core::UpdateTitle(system); - } Config::AddLayer(ConfigLoaders::GenerateGlobalGameConfigLoader(game_id, revision)); Config::AddLayer(ConfigLoaders::GenerateLocalGameConfigLoader(game_id, revision)); - if (Core::IsRunning(system)) + if (is_running_or_starting) DolphinAnalytics::Instance().ReportGameStart(); } void SConfig::OnNewTitleLoad(const Core::CPUThreadGuard& guard) { auto& system = guard.GetSystem(); - if (!Core::IsRunning(system)) + if (!Core::IsRunningOrStarting(system)) return; auto& ppc_symbol_db = system.GetPPCSymbolDB(); diff --git a/Source/Core/Core/Core.cpp b/Source/Core/Core/Core.cpp index f28a41f836..58423377be 100644 --- a/Source/Core/Core/Core.cpp +++ b/Source/Core/Core/Core.cpp @@ -101,10 +101,6 @@ namespace Core static bool s_wants_determinism; // Declarations and definitions -static bool s_is_stopping = false; -static bool s_hardware_initialized = false; -static bool s_is_started = false; -static Common::Flag s_is_booting; static std::thread s_emu_thread; static std::vector s_on_state_changed_callbacks; @@ -114,6 +110,10 @@ static std::atomic s_last_actual_emulation_speed{1.0}; static bool s_frame_step = false; static std::atomic s_stop_frame_step; +// The value Paused is never stored in this variable. The core is considered to be in +// the Paused state if this variable is Running and the CPU reports that it's stepping. +static std::atomic s_state = State::Uninitialized; + #ifdef USE_MEMORYWATCHER static std::unique_ptr s_memory_watcher; #endif @@ -190,7 +190,7 @@ std::string StopMessage(bool main_thread, std::string_view message) void DisplayMessage(std::string message, int time_in_ms) { - if (!IsRunning(Core::System::GetInstance())) + if (!IsRunningOrStarting(Core::System::GetInstance())) return; // Actually displaying non-ASCII could cause things to go pear-shaped @@ -202,12 +202,13 @@ void DisplayMessage(std::string message, int time_in_ms) bool IsRunning(Core::System& system) { - return (GetState(system) != State::Uninitialized || s_hardware_initialized) && !s_is_stopping; + return s_state.load() == State::Running; } -bool IsRunningAndStarted() +bool IsRunningOrStarting(Core::System& system) { - return s_is_started && !s_is_stopping; + const State state = s_state.load(); + return state == State::Running || state == State::Starting; } bool IsCPUThread() @@ -262,7 +263,7 @@ bool Init(Core::System& system, std::unique_ptr boot, const Wind g_video_backend->PrepareWindow(prepared_wsi); // Start the emu thread - s_is_booting.Set(); + s_state.store(State::Starting); s_emu_thread = std::thread(EmuThread, std::ref(system), std::move(boot), prepared_wsi); return true; } @@ -281,15 +282,13 @@ static void ResetRumble() // Called from GUI thread void Stop(Core::System& system) // - Hammertime! { - if (const State state = GetState(system); - state == State::Stopping || state == State::Uninitialized) - { + const State state = s_state.load(); + if (state == State::Stopping || state == State::Uninitialized) return; - } AchievementManager::GetInstance().CloseGame(); - s_is_stopping = true; + s_state.store(State::Stopping); CallOnStateChangedCallbacks(State::Stopping); @@ -394,7 +393,11 @@ static void CpuThread(Core::System& system, const std::optional& sa File::Delete(*savestate_path); } - s_is_started = true; + // If s_state is Starting, change it to Running. But if it's already been set to Stopping + // by the host thread, don't change it. + State expected = State::Starting; + s_state.compare_exchange_strong(expected, State::Running); + { #ifndef _WIN32 std::string gdb_socket = Config::Get(Config::MAIN_GDB_SOCKET); @@ -426,8 +429,6 @@ static void CpuThread(Core::System& system, const std::optional& sa s_memory_watcher.reset(); #endif - s_is_started = false; - if (exception_handler) EMM::UninstallExceptionHandler(); @@ -453,12 +454,15 @@ static void FifoPlayerThread(Core::System& system, const std::optional boot { CallOnStateChangedCallbacks(State::Starting); Common::ScopeGuard flag_guard{[] { - s_is_booting.Clear(); - s_is_started = false; - s_is_stopping = false; - s_wants_determinism = false; + s_state.store(State::Uninitialized); CallOnStateChangedCallbacks(State::Uninitialized); @@ -557,8 +558,6 @@ static void EmuThread(Core::System& system, std::unique_ptr boot NetPlay::IsNetPlayRunning() ? &(boot_session_data.GetNetplaySettings()->sram) : nullptr); Common::ScopeGuard hw_guard{[&system] { - // We must set up this flag before executing HW::Shutdown() - s_hardware_initialized = false; INFO_LOG_FMT(CONSOLE, "{}", StopMessage(false, "Shutting down HW")); HW::Shutdown(system); INFO_LOG_FMT(CONSOLE, "{}", StopMessage(false, "HW shutdown")); @@ -602,10 +601,6 @@ static void EmuThread(Core::System& system, std::unique_ptr boot AudioCommon::PostInitSoundStream(system); - // The hardware is initialized. - s_hardware_initialized = true; - s_is_booting.Clear(); - // Set execution state to known values (CPU/FIFO/Audio Paused) system.GetCPU().Break(); @@ -701,7 +696,7 @@ static void EmuThread(Core::System& system, std::unique_ptr boot void SetState(Core::System& system, State state, bool report_state_change) { // State cannot be controlled until the CPU Thread is operational - if (!IsRunningAndStarted()) + if (s_state.load() != State::Running) return; switch (state) @@ -732,21 +727,11 @@ void SetState(Core::System& system, State state, bool report_state_change) State GetState(Core::System& system) { - if (s_is_stopping) - return State::Stopping; - - if (s_hardware_initialized) - { - if (system.GetCPU().IsStepping()) - return State::Paused; - - return State::Running; - } - - if (s_is_booting.IsSet()) - return State::Starting; - - return State::Uninitialized; + const State state = s_state.load(); + if (state == State::Running && system.GetCPU().IsStepping()) + return State::Paused; + else + return state; } static std::string GenerateScreenshotFolderPath() @@ -800,7 +785,7 @@ static bool PauseAndLock(Core::System& system, bool do_lock, bool unpause_on_unl { // WARNING: PauseAndLock is not fully threadsafe so is only valid on the Host Thread - if (!IsRunningAndStarted()) + if (!IsRunning(system)) return true; bool was_unpaused = true; @@ -1027,13 +1012,12 @@ void HostDispatchJobs(Core::System& system) HostJob job = std::move(s_host_jobs_queue.front()); s_host_jobs_queue.pop(); - // NOTE: Memory ordering is important. The booting flag needs to be - // checked first because the state transition is: - // Core::State::Uninitialized: s_is_booting -> s_hardware_initialized - // We need to check variables in the same order as the state - // transition, otherwise we race and get transient failures. - if (!job.run_after_stop && !s_is_booting.IsSet() && !IsRunning(system)) - continue; + if (!job.run_after_stop) + { + const State state = s_state.load(); + if (state == State::Stopping || state == State::Uninitialized) + continue; + } guard.unlock(); job.job(system); diff --git a/Source/Core/Core/Core.h b/Source/Core/Core/Core.h index 980d336750..e6c440566c 100644 --- a/Source/Core/Core/Core.h +++ b/Source/Core/Core/Core.h @@ -135,8 +135,8 @@ void UndeclareAsHostThread(); std::string StopMessage(bool main_thread, std::string_view message); bool IsRunning(Core::System& system); -bool IsRunningAndStarted(); // is running and the CPU loop has been entered -bool IsCPUThread(); // this tells us whether we are the CPU thread. +bool IsRunningOrStarting(Core::System& system); +bool IsCPUThread(); // this tells us whether we are the CPU thread. bool IsGPUThread(); bool IsHostThread(); diff --git a/Source/Core/Core/Debugger/PPCDebugInterface.cpp b/Source/Core/Core/Debugger/PPCDebugInterface.cpp index a003e98156..9389fe76d8 100644 --- a/Source/Core/Core/Debugger/PPCDebugInterface.cpp +++ b/Source/Core/Core/Debugger/PPCDebugInterface.cpp @@ -349,7 +349,7 @@ u32 PPCDebugInterface::ReadInstruction(const Core::CPUThreadGuard& guard, u32 ad bool PPCDebugInterface::IsAlive() const { - return Core::IsRunningAndStarted(); + return Core::IsRunning(m_system); } bool PPCDebugInterface::IsBreakpoint(u32 address) const diff --git a/Source/Core/Core/IOS/ES/ES.cpp b/Source/Core/Core/IOS/ES/ES.cpp index 6bd894dd27..d04a3ef889 100644 --- a/Source/Core/Core/IOS/ES/ES.cpp +++ b/Source/Core/Core/IOS/ES/ES.cpp @@ -112,9 +112,9 @@ ESCore::~ESCore() = default; ESDevice::ESDevice(EmulationKernel& ios, ESCore& core, const std::string& device_name) : EmulationDevice(ios, device_name), m_core(core) { - if (Core::IsRunningAndStarted()) + auto& system = ios.GetSystem(); + if (Core::IsRunning(system)) { - auto& system = ios.GetSystem(); auto& core_timing = system.GetCoreTiming(); core_timing.RemoveEvent(s_finish_init_event); core_timing.ScheduleEvent(GetESBootTicks(ios.GetVersion()), s_finish_init_event); @@ -446,7 +446,7 @@ bool ESDevice::LaunchPPCTitle(u64 title_id) } const u64 required_ios = tmd.GetIOSId(); - if (!Core::IsRunningAndStarted()) + if (!Core::IsRunning(system)) return LaunchTitle(required_ios, HangPPC::Yes); core_timing.RemoveEvent(s_reload_ios_for_ppc_launch_event); core_timing.ScheduleEvent(ticks, s_reload_ios_for_ppc_launch_event, required_ios); @@ -475,7 +475,7 @@ bool ESDevice::LaunchPPCTitle(u64 title_id) return false; m_pending_ppc_boot_content_path = m_core.GetContentPath(tmd.GetTitleId(), content); - if (!Core::IsRunningAndStarted()) + if (!Core::IsRunning(system)) return BootstrapPPC(); INFO_LOG_FMT(ACHIEVEMENTS, diff --git a/Source/Core/Core/IOS/IOS.cpp b/Source/Core/Core/IOS/IOS.cpp index 0e046b8534..d15eb9317d 100644 --- a/Source/Core/Core/IOS/IOS.cpp +++ b/Source/Core/Core/IOS/IOS.cpp @@ -518,7 +518,7 @@ bool EmulationKernel::BootIOS(const u64 ios_title_id, HangPPC hang_ppc, if (hang_ppc == HangPPC::Yes) ResetAndPausePPC(m_system); - if (Core::IsRunningAndStarted()) + if (Core::IsRunning(m_system)) { m_system.GetCoreTiming().ScheduleEvent(GetIOSBootTicks(GetVersion()), s_event_finish_ios_boot, ios_title_id); diff --git a/Source/Core/Core/Movie.cpp b/Source/Core/Core/Movie.cpp index 47ad3c8c97..325381f0e9 100644 --- a/Source/Core/Core/Movie.cpp +++ b/Source/Core/Core/Movie.cpp @@ -536,7 +536,7 @@ bool MovieManager::BeginRecordingInput(const ControllerTypeArray& controllers, m_bongos |= (1 << i); } - if (Core::IsRunningAndStarted()) + if (Core::IsRunning(m_system)) { const std::string save_path = File::GetUserPath(D_STATESAVES_IDX) + "dtm.sav"; if (File::Exists(save_path)) @@ -551,7 +551,7 @@ bool MovieManager::BeginRecordingInput(const ControllerTypeArray& controllers, } // Wiimotes cause desync issues if they're not reset before launching the game - if (!Core::IsRunningAndStarted()) + if (!Core::IsRunning(m_system)) { // This will also reset the Wiimotes for GameCube games, but that shouldn't do anything Wiimote::ResetAllWiimotes(); @@ -1339,7 +1339,7 @@ void MovieManager::EndPlayInput(bool cont) { // We can be called by EmuThread during boot (CPU::State::PowerDown) auto& cpu = m_system.GetCPU(); - const bool was_running = Core::IsRunningAndStarted() && !cpu.IsStepping(); + const bool was_running = Core::IsRunning(m_system) && !cpu.IsStepping(); if (was_running && Config::Get(Config::MAIN_MOVIE_PAUSE_MOVIE)) cpu.Break(); m_rerecords = 0; diff --git a/Source/Core/Core/State.cpp b/Source/Core/Core/State.cpp index adc1cbd57a..11c8e9660e 100644 --- a/Source/Core/Core/State.cpp +++ b/Source/Core/Core/State.cpp @@ -854,7 +854,7 @@ static void LoadFileStateData(const std::string& filename, std::vector& ret_ void LoadAs(Core::System& system, const std::string& filename) { - if (!Core::IsRunning(system)) + if (!Core::IsRunningOrStarting(system)) return; if (NetPlay::IsNetPlayRunning()) diff --git a/Source/Core/DolphinQt/HotkeyScheduler.cpp b/Source/Core/DolphinQt/HotkeyScheduler.cpp index 10f7540ca7..0a6d13e6c0 100644 --- a/Source/Core/DolphinQt/HotkeyScheduler.cpp +++ b/Source/Core/DolphinQt/HotkeyScheduler.cpp @@ -159,7 +159,8 @@ void HotkeyScheduler::Run() if (!HotkeyManagerEmu::IsEnabled()) continue; - if (Core::GetState(Core::System::GetInstance()) != Core::State::Stopping) + Core::System& system = Core::System::GetInstance(); + if (Core::GetState(system) != Core::State::Stopping) { // Obey window focus (config permitting) before checking hotkeys. Core::UpdateInputGate(Config::Get(Config::MAIN_FOCUSED_HOTKEYS)); @@ -187,7 +188,7 @@ void HotkeyScheduler::Run() if (IsHotkey(HK_EXIT)) emit ExitHotkey(); - if (!Core::IsRunningAndStarted()) + if (!Core::IsRunning(system)) { // Only check for Play Recording hotkey when no game is running if (IsHotkey(HK_PLAY_RECORDING)) diff --git a/Source/Core/DolphinQt/MainWindow.cpp b/Source/Core/DolphinQt/MainWindow.cpp index 67860c464a..f62a7496e7 100644 --- a/Source/Core/DolphinQt/MainWindow.cpp +++ b/Source/Core/DolphinQt/MainWindow.cpp @@ -1882,7 +1882,7 @@ void MainWindow::OnStartRecording() { auto& system = Core::System::GetInstance(); auto& movie = system.GetMovie(); - if ((!Core::IsRunningAndStarted() && Core::IsRunning(system)) || movie.IsRecordingInput() || + if (Core::GetState(system) == Core::State::Starting || movie.IsRecordingInput() || movie.IsPlayingInput()) { return; diff --git a/Source/Core/DolphinQt/RenderWidget.cpp b/Source/Core/DolphinQt/RenderWidget.cpp index 597ac78e6c..d88a17e3d3 100644 --- a/Source/Core/DolphinQt/RenderWidget.cpp +++ b/Source/Core/DolphinQt/RenderWidget.cpp @@ -513,7 +513,7 @@ bool RenderWidget::event(QEvent* event) void RenderWidget::PassEventToPresenter(const QEvent* event) { - if (!Core::IsRunningAndStarted()) + if (!Core::IsRunning(Core::System::GetInstance())) return; switch (event->type()) diff --git a/Source/Core/VideoCommon/VideoConfig.cpp b/Source/Core/VideoCommon/VideoConfig.cpp index 0266cb9c19..efa159795c 100644 --- a/Source/Core/VideoCommon/VideoConfig.cpp +++ b/Source/Core/VideoCommon/VideoConfig.cpp @@ -66,7 +66,7 @@ void VideoConfig::Refresh() CPUThreadConfigCallback::AddConfigChangedCallback([]() { auto& system = Core::System::GetInstance(); - const bool lock_gpu_thread = Core::IsRunningAndStarted(); + const bool lock_gpu_thread = Core::IsRunning(system); if (lock_gpu_thread) system.GetFifo().PauseAndLock(true, false);