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.
This commit is contained in:
JosJuice 2024-06-02 15:10:25 +02:00
parent 04c246d11f
commit 962230f91e
13 changed files with 61 additions and 76 deletions

View File

@ -279,7 +279,7 @@ JNIEXPORT jboolean JNICALL Java_org_dolphinemu_dolphinemu_NativeLibrary_IsRunnin
JNIEXPORT jboolean JNICALL Java_org_dolphinemu_dolphinemu_NativeLibrary_IsRunningAndStarted(JNIEnv*, JNIEXPORT jboolean JNICALL Java_org_dolphinemu_dolphinemu_NativeLibrary_IsRunningAndStarted(JNIEnv*,
jclass) jclass)
{ {
return static_cast<jboolean>(Core::IsRunningAndStarted()); return static_cast<jboolean>(Core::IsRunning(Core::System::GetInstance()));
} }
JNIEXPORT jboolean JNICALL JNIEXPORT jboolean JNICALL

View File

@ -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); m_title_description = title_database.Describe(m_gametdb_id, language);
NOTICE_LOG_FMT(CORE, "Active title: {}", m_title_description); NOTICE_LOG_FMT(CORE, "Active title: {}", m_title_description);
Host_TitleChanged(); Host_TitleChanged();
if (Core::IsRunning(system))
{ const bool is_running_or_starting = Core::IsRunningOrStarting(system);
if (is_running_or_starting)
Core::UpdateTitle(system); Core::UpdateTitle(system);
}
Config::AddLayer(ConfigLoaders::GenerateGlobalGameConfigLoader(game_id, revision)); Config::AddLayer(ConfigLoaders::GenerateGlobalGameConfigLoader(game_id, revision));
Config::AddLayer(ConfigLoaders::GenerateLocalGameConfigLoader(game_id, revision)); Config::AddLayer(ConfigLoaders::GenerateLocalGameConfigLoader(game_id, revision));
if (Core::IsRunning(system)) if (is_running_or_starting)
DolphinAnalytics::Instance().ReportGameStart(); DolphinAnalytics::Instance().ReportGameStart();
} }
void SConfig::OnNewTitleLoad(const Core::CPUThreadGuard& guard) void SConfig::OnNewTitleLoad(const Core::CPUThreadGuard& guard)
{ {
auto& system = guard.GetSystem(); auto& system = guard.GetSystem();
if (!Core::IsRunning(system)) if (!Core::IsRunningOrStarting(system))
return; return;
auto& ppc_symbol_db = system.GetPPCSymbolDB(); auto& ppc_symbol_db = system.GetPPCSymbolDB();

View File

@ -101,10 +101,6 @@ namespace Core
static bool s_wants_determinism; static bool s_wants_determinism;
// Declarations and definitions // 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::thread s_emu_thread;
static std::vector<StateChangedCallbackFunc> s_on_state_changed_callbacks; static std::vector<StateChangedCallbackFunc> s_on_state_changed_callbacks;
@ -114,6 +110,10 @@ static std::atomic<double> s_last_actual_emulation_speed{1.0};
static bool s_frame_step = false; static bool s_frame_step = false;
static std::atomic<bool> s_stop_frame_step; static std::atomic<bool> 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<State> s_state = State::Uninitialized;
#ifdef USE_MEMORYWATCHER #ifdef USE_MEMORYWATCHER
static std::unique_ptr<MemoryWatcher> s_memory_watcher; static std::unique_ptr<MemoryWatcher> s_memory_watcher;
#endif #endif
@ -190,7 +190,7 @@ std::string StopMessage(bool main_thread, std::string_view message)
void DisplayMessage(std::string message, int time_in_ms) void DisplayMessage(std::string message, int time_in_ms)
{ {
if (!IsRunning(Core::System::GetInstance())) if (!IsRunningOrStarting(Core::System::GetInstance()))
return; return;
// Actually displaying non-ASCII could cause things to go pear-shaped // 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) 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() bool IsCPUThread()
@ -262,7 +263,7 @@ bool Init(Core::System& system, std::unique_ptr<BootParameters> boot, const Wind
g_video_backend->PrepareWindow(prepared_wsi); g_video_backend->PrepareWindow(prepared_wsi);
// Start the emu thread // 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); s_emu_thread = std::thread(EmuThread, std::ref(system), std::move(boot), prepared_wsi);
return true; return true;
} }
@ -281,15 +282,13 @@ static void ResetRumble()
// Called from GUI thread // Called from GUI thread
void Stop(Core::System& system) // - Hammertime! void Stop(Core::System& system) // - Hammertime!
{ {
if (const State state = GetState(system); const State state = s_state.load();
state == State::Stopping || state == State::Uninitialized) if (state == State::Stopping || state == State::Uninitialized)
{
return; return;
}
AchievementManager::GetInstance().CloseGame(); AchievementManager::GetInstance().CloseGame();
s_is_stopping = true; s_state.store(State::Stopping);
CallOnStateChangedCallbacks(State::Stopping); CallOnStateChangedCallbacks(State::Stopping);
@ -394,7 +393,11 @@ static void CpuThread(Core::System& system, const std::optional<std::string>& sa
File::Delete(*savestate_path); 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 #ifndef _WIN32
std::string gdb_socket = Config::Get(Config::MAIN_GDB_SOCKET); std::string gdb_socket = Config::Get(Config::MAIN_GDB_SOCKET);
@ -426,8 +429,6 @@ static void CpuThread(Core::System& system, const std::optional<std::string>& sa
s_memory_watcher.reset(); s_memory_watcher.reset();
#endif #endif
s_is_started = false;
if (exception_handler) if (exception_handler)
EMM::UninstallExceptionHandler(); EMM::UninstallExceptionHandler();
@ -453,12 +454,15 @@ static void FifoPlayerThread(Core::System& system, const std::optional<std::stri
if (auto cpu_core = system.GetFifoPlayer().GetCPUCore()) if (auto cpu_core = system.GetFifoPlayer().GetCPUCore())
{ {
system.GetPowerPC().InjectExternalCPUCore(cpu_core.get()); system.GetPowerPC().InjectExternalCPUCore(cpu_core.get());
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);
CPUSetInitialExecutionState(); CPUSetInitialExecutionState();
system.GetCPU().Run(); system.GetCPU().Run();
s_is_started = false;
system.GetPowerPC().InjectExternalCPUCore(nullptr); system.GetPowerPC().InjectExternalCPUCore(nullptr);
system.GetFifoPlayer().Close(); system.GetFifoPlayer().Close();
} }
@ -479,10 +483,7 @@ static void EmuThread(Core::System& system, std::unique_ptr<BootParameters> boot
{ {
CallOnStateChangedCallbacks(State::Starting); CallOnStateChangedCallbacks(State::Starting);
Common::ScopeGuard flag_guard{[] { Common::ScopeGuard flag_guard{[] {
s_is_booting.Clear(); s_state.store(State::Uninitialized);
s_is_started = false;
s_is_stopping = false;
s_wants_determinism = false;
CallOnStateChangedCallbacks(State::Uninitialized); CallOnStateChangedCallbacks(State::Uninitialized);
@ -557,8 +558,6 @@ static void EmuThread(Core::System& system, std::unique_ptr<BootParameters> boot
NetPlay::IsNetPlayRunning() ? &(boot_session_data.GetNetplaySettings()->sram) : nullptr); NetPlay::IsNetPlayRunning() ? &(boot_session_data.GetNetplaySettings()->sram) : nullptr);
Common::ScopeGuard hw_guard{[&system] { 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")); INFO_LOG_FMT(CONSOLE, "{}", StopMessage(false, "Shutting down HW"));
HW::Shutdown(system); HW::Shutdown(system);
INFO_LOG_FMT(CONSOLE, "{}", StopMessage(false, "HW shutdown")); INFO_LOG_FMT(CONSOLE, "{}", StopMessage(false, "HW shutdown"));
@ -602,10 +601,6 @@ static void EmuThread(Core::System& system, std::unique_ptr<BootParameters> boot
AudioCommon::PostInitSoundStream(system); 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) // Set execution state to known values (CPU/FIFO/Audio Paused)
system.GetCPU().Break(); system.GetCPU().Break();
@ -701,7 +696,7 @@ static void EmuThread(Core::System& system, std::unique_ptr<BootParameters> boot
void SetState(Core::System& system, State state, bool report_state_change) void SetState(Core::System& system, State state, bool report_state_change)
{ {
// State cannot be controlled until the CPU Thread is operational // State cannot be controlled until the CPU Thread is operational
if (!IsRunningAndStarted()) if (s_state.load() != State::Running)
return; return;
switch (state) switch (state)
@ -732,21 +727,11 @@ void SetState(Core::System& system, State state, bool report_state_change)
State GetState(Core::System& system) State GetState(Core::System& system)
{ {
if (s_is_stopping) const State state = s_state.load();
return State::Stopping; if (state == State::Running && system.GetCPU().IsStepping())
return State::Paused;
if (s_hardware_initialized) else
{ return state;
if (system.GetCPU().IsStepping())
return State::Paused;
return State::Running;
}
if (s_is_booting.IsSet())
return State::Starting;
return State::Uninitialized;
} }
static std::string GenerateScreenshotFolderPath() 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 // WARNING: PauseAndLock is not fully threadsafe so is only valid on the Host Thread
if (!IsRunningAndStarted()) if (!IsRunning(system))
return true; return true;
bool was_unpaused = true; bool was_unpaused = true;
@ -1027,13 +1012,12 @@ void HostDispatchJobs(Core::System& system)
HostJob job = std::move(s_host_jobs_queue.front()); HostJob job = std::move(s_host_jobs_queue.front());
s_host_jobs_queue.pop(); s_host_jobs_queue.pop();
// NOTE: Memory ordering is important. The booting flag needs to be if (!job.run_after_stop)
// checked first because the state transition is: {
// Core::State::Uninitialized: s_is_booting -> s_hardware_initialized const State state = s_state.load();
// We need to check variables in the same order as the state if (state == State::Stopping || state == State::Uninitialized)
// transition, otherwise we race and get transient failures. continue;
if (!job.run_after_stop && !s_is_booting.IsSet() && !IsRunning(system)) }
continue;
guard.unlock(); guard.unlock();
job.job(system); job.job(system);

View File

@ -135,8 +135,8 @@ void UndeclareAsHostThread();
std::string StopMessage(bool main_thread, std::string_view message); std::string StopMessage(bool main_thread, std::string_view message);
bool IsRunning(Core::System& system); bool IsRunning(Core::System& system);
bool IsRunningAndStarted(); // is running and the CPU loop has been entered bool IsRunningOrStarting(Core::System& system);
bool IsCPUThread(); // this tells us whether we are the CPU thread. bool IsCPUThread(); // this tells us whether we are the CPU thread.
bool IsGPUThread(); bool IsGPUThread();
bool IsHostThread(); bool IsHostThread();

View File

@ -349,7 +349,7 @@ u32 PPCDebugInterface::ReadInstruction(const Core::CPUThreadGuard& guard, u32 ad
bool PPCDebugInterface::IsAlive() const bool PPCDebugInterface::IsAlive() const
{ {
return Core::IsRunningAndStarted(); return Core::IsRunning(m_system);
} }
bool PPCDebugInterface::IsBreakpoint(u32 address) const bool PPCDebugInterface::IsBreakpoint(u32 address) const

View File

@ -112,9 +112,9 @@ ESCore::~ESCore() = default;
ESDevice::ESDevice(EmulationKernel& ios, ESCore& core, const std::string& device_name) ESDevice::ESDevice(EmulationKernel& ios, ESCore& core, const std::string& device_name)
: EmulationDevice(ios, device_name), m_core(core) : 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(); auto& core_timing = system.GetCoreTiming();
core_timing.RemoveEvent(s_finish_init_event); core_timing.RemoveEvent(s_finish_init_event);
core_timing.ScheduleEvent(GetESBootTicks(ios.GetVersion()), 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(); const u64 required_ios = tmd.GetIOSId();
if (!Core::IsRunningAndStarted()) if (!Core::IsRunning(system))
return LaunchTitle(required_ios, HangPPC::Yes); return LaunchTitle(required_ios, HangPPC::Yes);
core_timing.RemoveEvent(s_reload_ios_for_ppc_launch_event); core_timing.RemoveEvent(s_reload_ios_for_ppc_launch_event);
core_timing.ScheduleEvent(ticks, s_reload_ios_for_ppc_launch_event, required_ios); 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; return false;
m_pending_ppc_boot_content_path = m_core.GetContentPath(tmd.GetTitleId(), content); m_pending_ppc_boot_content_path = m_core.GetContentPath(tmd.GetTitleId(), content);
if (!Core::IsRunningAndStarted()) if (!Core::IsRunning(system))
return BootstrapPPC(); return BootstrapPPC();
INFO_LOG_FMT(ACHIEVEMENTS, INFO_LOG_FMT(ACHIEVEMENTS,

View File

@ -518,7 +518,7 @@ bool EmulationKernel::BootIOS(const u64 ios_title_id, HangPPC hang_ppc,
if (hang_ppc == HangPPC::Yes) if (hang_ppc == HangPPC::Yes)
ResetAndPausePPC(m_system); ResetAndPausePPC(m_system);
if (Core::IsRunningAndStarted()) if (Core::IsRunning(m_system))
{ {
m_system.GetCoreTiming().ScheduleEvent(GetIOSBootTicks(GetVersion()), s_event_finish_ios_boot, m_system.GetCoreTiming().ScheduleEvent(GetIOSBootTicks(GetVersion()), s_event_finish_ios_boot,
ios_title_id); ios_title_id);

View File

@ -536,7 +536,7 @@ bool MovieManager::BeginRecordingInput(const ControllerTypeArray& controllers,
m_bongos |= (1 << i); m_bongos |= (1 << i);
} }
if (Core::IsRunningAndStarted()) if (Core::IsRunning(m_system))
{ {
const std::string save_path = File::GetUserPath(D_STATESAVES_IDX) + "dtm.sav"; const std::string save_path = File::GetUserPath(D_STATESAVES_IDX) + "dtm.sav";
if (File::Exists(save_path)) 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 // 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 // This will also reset the Wiimotes for GameCube games, but that shouldn't do anything
Wiimote::ResetAllWiimotes(); Wiimote::ResetAllWiimotes();
@ -1339,7 +1339,7 @@ void MovieManager::EndPlayInput(bool cont)
{ {
// We can be called by EmuThread during boot (CPU::State::PowerDown) // We can be called by EmuThread during boot (CPU::State::PowerDown)
auto& cpu = m_system.GetCPU(); 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)) if (was_running && Config::Get(Config::MAIN_MOVIE_PAUSE_MOVIE))
cpu.Break(); cpu.Break();
m_rerecords = 0; m_rerecords = 0;

View File

@ -854,7 +854,7 @@ static void LoadFileStateData(const std::string& filename, std::vector<u8>& ret_
void LoadAs(Core::System& system, const std::string& filename) void LoadAs(Core::System& system, const std::string& filename)
{ {
if (!Core::IsRunning(system)) if (!Core::IsRunningOrStarting(system))
return; return;
if (NetPlay::IsNetPlayRunning()) if (NetPlay::IsNetPlayRunning())

View File

@ -159,7 +159,8 @@ void HotkeyScheduler::Run()
if (!HotkeyManagerEmu::IsEnabled()) if (!HotkeyManagerEmu::IsEnabled())
continue; 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. // Obey window focus (config permitting) before checking hotkeys.
Core::UpdateInputGate(Config::Get(Config::MAIN_FOCUSED_HOTKEYS)); Core::UpdateInputGate(Config::Get(Config::MAIN_FOCUSED_HOTKEYS));
@ -187,7 +188,7 @@ void HotkeyScheduler::Run()
if (IsHotkey(HK_EXIT)) if (IsHotkey(HK_EXIT))
emit ExitHotkey(); emit ExitHotkey();
if (!Core::IsRunningAndStarted()) if (!Core::IsRunning(system))
{ {
// Only check for Play Recording hotkey when no game is running // Only check for Play Recording hotkey when no game is running
if (IsHotkey(HK_PLAY_RECORDING)) if (IsHotkey(HK_PLAY_RECORDING))

View File

@ -1882,7 +1882,7 @@ void MainWindow::OnStartRecording()
{ {
auto& system = Core::System::GetInstance(); auto& system = Core::System::GetInstance();
auto& movie = system.GetMovie(); auto& movie = system.GetMovie();
if ((!Core::IsRunningAndStarted() && Core::IsRunning(system)) || movie.IsRecordingInput() || if (Core::GetState(system) == Core::State::Starting || movie.IsRecordingInput() ||
movie.IsPlayingInput()) movie.IsPlayingInput())
{ {
return; return;

View File

@ -513,7 +513,7 @@ bool RenderWidget::event(QEvent* event)
void RenderWidget::PassEventToPresenter(const QEvent* event) void RenderWidget::PassEventToPresenter(const QEvent* event)
{ {
if (!Core::IsRunningAndStarted()) if (!Core::IsRunning(Core::System::GetInstance()))
return; return;
switch (event->type()) switch (event->type())

View File

@ -66,7 +66,7 @@ void VideoConfig::Refresh()
CPUThreadConfigCallback::AddConfigChangedCallback([]() { CPUThreadConfigCallback::AddConfigChangedCallback([]() {
auto& system = Core::System::GetInstance(); auto& system = Core::System::GetInstance();
const bool lock_gpu_thread = Core::IsRunningAndStarted(); const bool lock_gpu_thread = Core::IsRunning(system);
if (lock_gpu_thread) if (lock_gpu_thread)
system.GetFifo().PauseAndLock(true, false); system.GetFifo().PauseAndLock(true, false);