mirror of
https://github.com/hrydgard/ppsspp.git
synced 2026-10-01 14:58:14 +00:00
Debugger: Time input.buttons.press in emulated vblanks
The release was counted down on the WebSocket thread, one step per poll of host time however many vblanks had passed, so how long a scripted press lasted depended on how fast the emulator ran, and scripted runs went different ways. sceCtrl now releases it after that many vblank samples, on the emulator thread; the debugger only reports when it's done. Also: wsdbg's :screenshot works in headless with Vulkan. Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
This commit is contained in:
1 parent
00c9a2d389
commit
a974440c79
4 files changed
+73
-15
No files matched your search
@@ -70,12 +70,12 @@ protected:
|
||||
std::string ticket;
|
||||
uint32_t button;
|
||||
uint32_t duration;
|
||||
int pressId;
|
||||
|
||||
std::string Event();
|
||||
};
|
||||
|
||||
std::vector<PressInfo> pressTickets_;
|
||||
int lastCounter_ = -1;
|
||||
};
|
||||
|
||||
std::string WebSocketInputState::PressInfo::Event() {
|
||||
@@ -170,7 +170,8 @@ void WebSocketInputState::ButtonsSend(DebuggerRequest &req) {
|
||||
//
|
||||
// Parameters:
|
||||
// - button: required string indicating button name (see input.buttons.send.)
|
||||
// - duration: optional integer indicating frames to press for, defaults to 1.
|
||||
// - duration: optional integer indicating frames to press for, defaults to 1. Counted in emulated
|
||||
// vblanks: the game sees the button in exactly that many vblank samples.
|
||||
//
|
||||
// Response (same event name) with no extra data once released.
|
||||
void WebSocketInputState::ButtonsPress(DebuggerRequest &req) {
|
||||
@@ -193,27 +194,24 @@ void WebSocketInputState::ButtonsPress(DebuggerRequest &req) {
|
||||
}
|
||||
press.button = info->second;
|
||||
|
||||
__CtrlUpdateButtons(press.button, 0);
|
||||
// Released by sceCtrl after that many vblanks of emulated time, so a scripted press lasts the
|
||||
// same however fast the emulator runs. This thread only reports when it's over.
|
||||
press.pressId = __CtrlPressFor(press.button, (int)press.duration);
|
||||
pressTickets_.push_back(press);
|
||||
}
|
||||
|
||||
void WebSocketInputState::Broadcast(net::WebSocketServer *ws) {
|
||||
int counter = __DisplayGetNumVblanks();
|
||||
if (pressTickets_.empty() || lastCounter_ == counter)
|
||||
if (pressTickets_.empty())
|
||||
return;
|
||||
lastCounter_ = counter;
|
||||
|
||||
auto done = [](const PressInfo &press) -> bool {
|
||||
return !__CtrlPressActive(press.pressId);
|
||||
};
|
||||
for (PressInfo &press : pressTickets_) {
|
||||
press.duration--;
|
||||
if (press.duration == -1) {
|
||||
__CtrlUpdateButtons(0, press.button);
|
||||
if (done(press)) {
|
||||
ws->Send(press.Event());
|
||||
}
|
||||
}
|
||||
auto negative = [](const PressInfo &press) -> bool {
|
||||
return press.duration < 0;
|
||||
};
|
||||
pressTickets_.erase(std::remove_if(pressTickets_.begin(), pressTickets_.end(), negative), pressTickets_.end());
|
||||
pressTickets_.erase(std::remove_if(pressTickets_.begin(), pressTickets_.end(), done), pressTickets_.end());
|
||||
}
|
||||
|
||||
static bool AnalogValue(DebuggerRequest &req, float *value, const char *name) {
|
||||
|
||||
@@ -15,8 +15,10 @@
|
||||
// Official git repository and contact information can be found at
|
||||
// https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/.
|
||||
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
#include <mutex>
|
||||
#include <vector>
|
||||
|
||||
#include "Common/Serialize/Serializer.h"
|
||||
#include "Common/Serialize/SerializeFuncs.h"
|
||||
@@ -205,6 +207,58 @@ u32 __CtrlReadLatch()
|
||||
return ret;
|
||||
}
|
||||
|
||||
// Presses held for a number of vblank samples (the debugger's input.buttons.press). Counted down on
|
||||
// the emulator thread at each vblank, so how long a press lasts doesn't depend on how fast the
|
||||
// emulator runs. Guarded by ctrlMutex; not saved.
|
||||
struct TimedPress {
|
||||
int id;
|
||||
u32 buttons;
|
||||
int samplesLeft;
|
||||
};
|
||||
static std::vector<TimedPress> timedPresses;
|
||||
static int nextTimedPressId = 1;
|
||||
|
||||
int __CtrlPressFor(u32 buttons, int vblanks) {
|
||||
std::lock_guard<std::mutex> guard(ctrlMutex);
|
||||
buttons &= CTRL_MASK_USER;
|
||||
ctrlCurrent.buttons |= buttons;
|
||||
const int id = nextTimedPressId++;
|
||||
timedPresses.push_back(TimedPress{ id, buttons, std::max(vblanks, 1) });
|
||||
return id;
|
||||
}
|
||||
|
||||
bool __CtrlPressActive(int id) {
|
||||
std::lock_guard<std::mutex> guard(ctrlMutex);
|
||||
for (const TimedPress &press : timedPresses) {
|
||||
if (press.id == id) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// After the vblank's sample, so a press of N vblanks is seen by N samples.
|
||||
static void __CtrlUpdateTimedPresses() {
|
||||
std::lock_guard<std::mutex> guard(ctrlMutex);
|
||||
if (timedPresses.empty()) {
|
||||
return;
|
||||
}
|
||||
u32 released = 0;
|
||||
for (TimedPress &press : timedPresses) {
|
||||
if (--press.samplesLeft <= 0) {
|
||||
released |= press.buttons;
|
||||
}
|
||||
}
|
||||
timedPresses.erase(std::remove_if(timedPresses.begin(), timedPresses.end(), [](const TimedPress &press) {
|
||||
return press.samplesLeft <= 0;
|
||||
}), timedPresses.end());
|
||||
// Unless another press still holds them.
|
||||
for (const TimedPress &press : timedPresses) {
|
||||
released &= ~press.buttons;
|
||||
}
|
||||
ctrlCurrent.buttons &= ~released;
|
||||
}
|
||||
|
||||
void __CtrlUpdateButtons(u32 bitsToSet, u32 bitsToClear)
|
||||
{
|
||||
bitsToClear &= CTRL_MASK_USER;
|
||||
@@ -343,6 +397,7 @@ void __CtrlVblank() {
|
||||
// This always runs, so make sure we're in vblank mode.
|
||||
if (ctrlCycle == 0)
|
||||
__CtrlDoSample();
|
||||
__CtrlUpdateTimedPresses();
|
||||
}
|
||||
|
||||
static void __CtrlTimerUpdate(u64 userdata, int cyclesLate)
|
||||
@@ -423,6 +478,8 @@ void __CtrlDoState(PointerWrap &p)
|
||||
void __CtrlShutdown()
|
||||
{
|
||||
waitingThreads.clear();
|
||||
std::lock_guard<std::mutex> guard(ctrlMutex);
|
||||
timedPresses.clear();
|
||||
}
|
||||
|
||||
static u32 sceCtrlSetSamplingCycle(u32 cycle)
|
||||
|
||||
@@ -73,6 +73,9 @@ void __CtrlVblank();
|
||||
|
||||
// Clears and sets selected buttons. NOTE: Clearing happens first.
|
||||
void __CtrlUpdateButtons(u32 bitsToSet, u32 bitsToClear);
|
||||
// Presses buttons for a number of vblanks of emulated time; returns an id for __CtrlPressActive.
|
||||
int __CtrlPressFor(u32 buttons, int vblanks);
|
||||
bool __CtrlPressActive(int id);
|
||||
|
||||
// Call this to set the position of an analog stick, ideally when it changes.
|
||||
// X and Y values should be from -1 to 1, inclusive, in a square (no need to force to a circle.)
|
||||
|
||||
@@ -117,7 +117,7 @@ way took minutes per run. These run inside the one session instead:
|
||||
|---|---|
|
||||
| `:sleep <seconds>` | Wall-clock pause. Keeps draining and printing messages while it waits. |
|
||||
| `:wait <event> [timeout]` | Blocks until a message with that event name arrives. Exits non-zero if it never does. |
|
||||
| `:screenshot <file.png>` | Saves `gpu.buffer.screenshot` as a PNG, without printing the image data. Needs the CPU stopped. Use a native path (`C:/...`) on Windows. With headless, use `--graphics=software`: the Vulkan backend has no output image to read back there. |
|
||||
| `:screenshot <file.png>` | Saves `gpu.buffer.screenshot` as a PNG, without printing the image data. Needs the CPU stopped. Use a native path (`C:/...`) on Windows. Works in headless with `--graphics=vulkan`. |
|
||||
| `:echo <text>` | Prints text, for marking up a script's output. |
|
||||
| `# comment` | Ignored. |
|
||||
|
||||
|
||||
Reference in new issue
Block a user