Files
ppsspp/Core/Debugger/WebSocket/SteppingBroadcaster.cpp
T
Henrik RydgårdandClaude Opus 5 3273e8081e Push game and stepping events from the CPU thread instead of polling for them
GameBroadcaster and SteppingBroadcaster ran per connection on the WebSocket
thread, so every connected debugger was reading pc, the tick count, coreState,
the UI state and the param SFO out from under the CPU thread on every lap of its
loop - up to 1000 times a second in high-activity mode.

Inverted: the CPU thread notices the transition once in WebSocketDebuggerTick(),
formats the event there, and drops it into a per-connection mailbox that the
connection's own thread drains and sends. Same events, same conditions, no core
reads off the CPU thread, and no per-connection polling of emulator state.

The tick hangs off Core_ProcessCPUQueue(), the one function reliably called on
the CPU thread both in game (Core_RunLoopUntil) and at the menu (NativeFrame).
It polls even with nothing connected, since skipping would let the "previous
state" go stale and fire a bogus event at whoever connects next.

Behavior preserved including the awkward bit: a debugger that connects while the
CPU is already stopped still gets an immediate cpu.stepping, which used to fall
out of SteppingBroadcaster's counter starting at 0. That's now an explicit
per-connection prime instead of an accident.

Part of removing the WebSocket debugger's lifecycleLock.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GZq8ZtJmFY7bkX5FVkr3P9
2026-08-17 13:11:16 +02:00

89 lines
3.3 KiB
C++

// Copyright (c) 2018- PPSSPP Project.
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, version 2.0 or later versions.
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License 2.0 for more details.
// A copy of the GPL 2.0 should have been included with the program.
// If not, see http://www.gnu.org/licenses/
// Official git repository and contact information can be found at
// https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/.
#include "Core/Core.h"
#include "Core/CoreTiming.h"
#include "Core/Debugger/WebSocket/SteppingBroadcaster.h"
#include "Core/Debugger/WebSocket/WebSocketUtils.h"
#include "Core/MIPS/MIPS.h"
#include "Core/System.h"
struct CPUSteppingEvent {
CPUSteppingEvent(const SteppingReason &reason) : reason_(reason) {
}
operator std::string() {
JsonWriter j;
j.begin();
j.writeString("event", "cpu.stepping");
j.writeUint("pc", currentMIPS->pc);
// A double ought to be good enough for a 156 day debug session.
j.writeFloat("ticks", CoreTiming::GetTicks(currentMIPS));
if (reason_.reason != BreakReason::None) {
j.writeString("reason", BreakReasonToString(reason_.reason));
j.writeUint("relatedAddress", reason_.relatedAddress);
}
j.end();
return j.str();
}
private:
const SteppingReason &reason_;
};
// CPU has begun stepping (cpu.stepping)
//
// Sent unexpectedly with these properties:
// - pc: number value of PC register (inaccurate unless stepping.)
// - ticks: number of CPU cycles into emulation.
// - reason: an optional property, if present, it's equal to the value submitted to Core_EnableStepping ("jit.branchdebug", "savestate.load", "ui.lost_focus", etc.)
// - relatedAddress: an optional address (often zero, but it can be a value of PC saved at some point, a related memory address, etc.), always present if 'reason' is present
// CPU has resumed from stepping (cpu.resume)
//
// Sent unexpectedly with no other properties.
// Tracked globally rather than per connection: this runs on the CPU thread, which owns the state
// being read, and the resulting event is then handed to every connected debugger.
static CoreState g_prevState = CORE_POWERDOWN;
static int g_lastCounter = 0;
std::string SteppingBroadcaster::PollChange() {
if (PSP_GetBootState() != BootState::Complete) {
g_lastCounter = -1;
g_prevState = CORE_POWERDOWN;
return std::string();
}
std::string result;
const int steppingCounter = Core_GetSteppingCounter();
// We ignore CORE_POWERDOWN as a stepping state.
if (coreState == CORE_STEPPING_CPU && steppingCounter != g_lastCounter) {
result = CPUSteppingEvent(Core_GetSteppingReason());
} else if (g_prevState == CORE_STEPPING_CPU && coreState != CORE_STEPPING_CPU && Core_IsActive()) {
result = R"({"event":"cpu.resume"})";
}
g_lastCounter = steppingCounter;
g_prevState = coreState;
return result;
}
std::string SteppingBroadcaster::CurrentState() {
if (PSP_GetBootState() != BootState::Complete || coreState != CORE_STEPPING_CPU)
return std::string();
return CPUSteppingEvent(Core_GetSteppingReason());
}