Files
dolphin/Source/Core/Core/HW/CPU.cpp
JosJuice d194e69bbd Core: Don't store game ID inside TimePlayed
When you use TimePlayed, you have to provide a game ID either when
creating the object or when calling GetTimePlayed on it. If you don't
provide a game ID when creating the object, function calls that don't
take a game ID will silently fail, except for Reload. This isn't very
obvious, and there's no strong benefit to storing the game ID inside
TimePlayed anyway (it just lets TimePlayed skip calling EscapeFileName),
so this commit removes the TimePlayed constructor that takes a game ID
and instead makes the functions that need game IDs always take a game ID
argument.
2025-04-12 15:20:49 +02:00

424 lines
11 KiB
C++

// Copyright 2008 Dolphin Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include "Core/HW/CPU.h"
#include <condition_variable>
#include <mutex>
#include <queue>
#include "AudioCommon/AudioCommon.h"
#include "Common/CommonTypes.h"
#include "Common/Event.h"
#include "Common/Thread.h"
#include "Common/Timer.h"
#include "Core/CPUThreadConfigCallback.h"
#include "Core/Config/MainSettings.h"
#include "Core/ConfigManager.h"
#include "Core/Core.h"
#include "Core/Host.h"
#include "Core/PowerPC/GDBStub.h"
#include "Core/PowerPC/PowerPC.h"
#include "Core/System.h"
#include "Core/TimePlayed.h"
#include "VideoCommon/Fifo.h"
namespace CPU
{
CPUManager::CPUManager(Core::System& system) : m_system(system)
{
}
CPUManager::~CPUManager() = default;
void CPUManager::Init(PowerPC::CPUCore cpu_core)
{
m_system.GetPowerPC().Init(cpu_core);
m_state = State::Stepping;
}
void CPUManager::Shutdown()
{
Stop();
m_system.GetPowerPC().Shutdown();
}
// Requires holding m_state_change_lock
void CPUManager::FlushStepSyncEventLocked()
{
if (!m_state_cpu_step_instruction)
return;
if (m_state_cpu_step_instruction_sync)
{
m_state_cpu_step_instruction_sync->Set();
m_state_cpu_step_instruction_sync = nullptr;
}
m_state_cpu_step_instruction = false;
}
void CPUManager::ExecutePendingJobs(std::unique_lock<std::mutex>& state_lock)
{
while (!m_pending_jobs.empty())
{
auto callback = m_pending_jobs.front();
m_pending_jobs.pop();
state_lock.unlock();
callback();
state_lock.lock();
}
}
void CPUManager::StartTimePlayedTimer()
{
Common::SetCurrentThreadName("Play Time Tracker");
// Steady clock for greater accuracy of timing
std::chrono::steady_clock timer;
auto prev_time = timer.now();
while (true)
{
TimePlayed time_played;
auto curr_time = timer.now();
// Check that emulation is not paused
// If the emulation is paused, wait for SetStepping() to reactivate
if (m_state == State::Running)
{
const std::string game_id = SConfig::GetInstance().GetGameID();
const auto diff_time =
std::chrono::duration_cast<std::chrono::milliseconds>(curr_time - prev_time);
time_played.AddTime(game_id, diff_time);
}
else if (m_state == State::Stepping)
{
m_time_played_finish_sync.Wait();
curr_time = timer.now();
}
prev_time = curr_time;
if (m_state == State::PowerDown)
return;
m_time_played_finish_sync.WaitFor(std::chrono::seconds(30));
}
}
void CPUManager::Run()
{
auto& power_pc = m_system.GetPowerPC();
// Updating the host CPU's rounding mode must be done on the CPU thread.
// We can't rely on PowerPC::Init doing it, since it's called from EmuThread.
PowerPC::RoundingModeUpdated(power_pc.GetPPCState());
// Start a separate time tracker thread
std::thread timing;
if (Config::Get(Config::MAIN_TIME_TRACKING))
{
timing = std::thread(&CPUManager::StartTimePlayedTimer, this);
}
std::unique_lock state_lock(m_state_change_lock);
while (m_state != State::PowerDown)
{
m_state_cpu_cvar.wait(state_lock, [this] { return !m_state_paused_and_locked; });
ExecutePendingJobs(state_lock);
CPUThreadConfigCallback::CheckForConfigChanges();
Common::Event gdb_step_sync_event;
switch (m_state)
{
case State::Running:
m_state_cpu_thread_active = true;
state_lock.unlock();
// Adjust PC when debugging
// SingleStep so that the "continue", "step over" and "step out" debugger functions
// work when the PC is at a breakpoint at the beginning of the block
// Don't use PowerPCManager::CheckBreakPoints, otherwise you get double logging
// If watchpoints are enabled, any instruction could be a breakpoint.
if (power_pc.GetBreakPoints().IsAddressBreakPoint(power_pc.GetPPCState().pc) ||
power_pc.GetMemChecks().HasAny())
{
m_state = State::Stepping;
PowerPC::CoreMode old_mode = power_pc.GetMode();
power_pc.SetMode(PowerPC::CoreMode::Interpreter);
power_pc.SingleStep();
power_pc.SetMode(old_mode);
m_state = State::Running;
}
// Enter a fast runloop
power_pc.RunLoop();
state_lock.lock();
m_state_cpu_thread_active = false;
m_state_cpu_idle_cvar.notify_all();
break;
case State::Stepping:
// Wait for step command.
m_state_cpu_cvar.wait(state_lock, [this, &state_lock, &gdb_step_sync_event] {
ExecutePendingJobs(state_lock);
CPUThreadConfigCallback::CheckForConfigChanges();
state_lock.unlock();
if (GDBStub::IsActive() && GDBStub::HasControl())
{
if (!GDBStub::JustConnected())
GDBStub::SendSignal(GDBStub::Signal::Sigtrap);
GDBStub::ProcessCommands(true);
// If we are still going to step, emulate the fact we just sent a step command
if (GDBStub::HasControl())
{
// Make sure the previous step by gdb was serviced
if (m_state_cpu_step_instruction_sync &&
m_state_cpu_step_instruction_sync != &gdb_step_sync_event)
{
m_state_cpu_step_instruction_sync->Set();
}
m_state_cpu_step_instruction = true;
m_state_cpu_step_instruction_sync = &gdb_step_sync_event;
}
}
state_lock.lock();
return m_state_cpu_step_instruction || !IsStepping();
});
if (!IsStepping())
{
// Signal event if the mode changes.
FlushStepSyncEventLocked();
continue;
}
if (m_state_paused_and_locked)
continue;
// Do step
m_state_cpu_thread_active = true;
state_lock.unlock();
power_pc.SingleStep();
state_lock.lock();
m_state_cpu_thread_active = false;
m_state_cpu_idle_cvar.notify_all();
// Update disasm dialog
FlushStepSyncEventLocked();
Host_UpdateDisasmDialog();
break;
case State::PowerDown:
break;
}
}
if (timing.joinable())
{
m_time_played_finish_sync.Set();
timing.join();
}
state_lock.unlock();
Host_UpdateDisasmDialog();
}
// Requires holding m_state_change_lock
void CPUManager::RunAdjacentSystems(bool running)
{
// NOTE: We're assuming these will not try to call Break or SetStepping.
m_system.GetFifo().EmulatorState(running);
// Core is responsible for shutting down the sound stream.
if (m_state != State::PowerDown)
AudioCommon::SetSoundStreamRunning(m_system, running);
}
void CPUManager::Stop()
{
// Change state and wait for it to be acknowledged.
// We don't need the stepping lock because State::PowerDown is a priority state which
// will stick permanently.
std::unique_lock state_lock(m_state_change_lock);
m_state = State::PowerDown;
m_state_cpu_cvar.notify_one();
while (m_state_cpu_thread_active)
{
m_state_cpu_idle_cvar.wait(state_lock);
}
RunAdjacentSystems(false);
FlushStepSyncEventLocked();
}
bool CPUManager::IsStepping() const
{
return m_state == State::Stepping;
}
State CPUManager::GetState() const
{
return m_state;
}
const State* CPUManager::GetStatePtr() const
{
return &m_state;
}
void CPUManager::Reset()
{
}
void CPUManager::StepOpcode(Common::Event* event)
{
std::lock_guard state_lock(m_state_change_lock);
// If we're not stepping then this is pointless
if (!IsStepping())
{
if (event)
event->Set();
return;
}
// Potential race where the previous step has not been serviced yet.
if (m_state_cpu_step_instruction_sync && m_state_cpu_step_instruction_sync != event)
m_state_cpu_step_instruction_sync->Set();
m_state_cpu_step_instruction = true;
m_state_cpu_step_instruction_sync = event;
m_state_cpu_cvar.notify_one();
}
// Requires m_state_change_lock
bool CPUManager::SetStateLocked(State s)
{
if (m_state == State::PowerDown)
return false;
if (s == State::Stepping)
m_system.GetPowerPC().GetBreakPoints().ClearTemporary();
m_state = s;
return true;
}
void CPUManager::SetStepping(bool stepping)
{
std::lock_guard stepping_lock(m_stepping_lock);
std::unique_lock state_lock(m_state_change_lock);
if (stepping)
{
SetStateLocked(State::Stepping);
while (m_state_cpu_thread_active)
{
m_state_cpu_idle_cvar.wait(state_lock);
}
RunAdjacentSystems(false);
}
else if (SetStateLocked(State::Running))
{
m_state_cpu_cvar.notify_one();
m_time_played_finish_sync.Set();
RunAdjacentSystems(true);
}
}
void CPUManager::Break()
{
std::lock_guard state_lock(m_state_change_lock);
// If another thread is trying to PauseAndLock then we need to remember this
// for later to ignore the unpause_on_unlock.
if (m_state_paused_and_locked)
{
m_state_system_request_stepping = true;
return;
}
// We'll deadlock if we synchronize, the CPU may block waiting for our caller to
// finish resulting in the CPU loop never terminating.
SetStateLocked(State::Stepping);
RunAdjacentSystems(false);
}
void CPUManager::Continue()
{
SetStepping(false);
Core::CallOnStateChangedCallbacks(Core::State::Running);
}
bool CPUManager::PauseAndLock(bool do_lock, bool unpause_on_unlock, bool control_adjacent)
{
// NOTE: This is protected by m_stepping_lock.
static bool s_have_fake_cpu_thread = false;
bool was_unpaused = false;
if (do_lock)
{
m_stepping_lock.lock();
std::unique_lock state_lock(m_state_change_lock);
m_state_paused_and_locked = true;
was_unpaused = m_state == State::Running;
SetStateLocked(State::Stepping);
while (m_state_cpu_thread_active)
{
m_state_cpu_idle_cvar.wait(state_lock);
}
if (control_adjacent)
RunAdjacentSystems(false);
state_lock.unlock();
// NOTE: It would make more sense for Core::DeclareAsCPUThread() to keep a
// depth counter instead of being a boolean.
if (!Core::IsCPUThread())
{
s_have_fake_cpu_thread = true;
Core::DeclareAsCPUThread();
}
}
else
{
// Only need the stepping lock for this
if (s_have_fake_cpu_thread)
{
s_have_fake_cpu_thread = false;
Core::UndeclareAsCPUThread();
}
{
std::lock_guard state_lock(m_state_change_lock);
if (m_state_system_request_stepping)
{
m_state_system_request_stepping = false;
}
else if (unpause_on_unlock && SetStateLocked(State::Running))
{
was_unpaused = true;
}
m_state_paused_and_locked = false;
m_state_cpu_cvar.notify_one();
if (control_adjacent)
RunAdjacentSystems(m_state == State::Running);
}
m_stepping_lock.unlock();
}
return was_unpaused;
}
void CPUManager::AddCPUThreadJob(std::function<void()> function)
{
std::unique_lock state_lock(m_state_change_lock);
m_pending_jobs.push(std::move(function));
}
} // namespace CPU