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;
|
std::string ticket;
|
||||||
uint32_t button;
|
uint32_t button;
|
||||||
uint32_t duration;
|
uint32_t duration;
|
||||||
|
int pressId;
|
||||||
|
|
||||||
std::string Event();
|
std::string Event();
|
||||||
};
|
};
|
||||||
|
|
||||||
std::vector<PressInfo> pressTickets_;
|
std::vector<PressInfo> pressTickets_;
|
||||||
int lastCounter_ = -1;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
std::string WebSocketInputState::PressInfo::Event() {
|
std::string WebSocketInputState::PressInfo::Event() {
|
||||||
@@ -170,7 +170,8 @@ void WebSocketInputState::ButtonsSend(DebuggerRequest &req) {
|
|||||||
//
|
//
|
||||||
// Parameters:
|
// Parameters:
|
||||||
// - button: required string indicating button name (see input.buttons.send.)
|
// - 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.
|
// Response (same event name) with no extra data once released.
|
||||||
void WebSocketInputState::ButtonsPress(DebuggerRequest &req) {
|
void WebSocketInputState::ButtonsPress(DebuggerRequest &req) {
|
||||||
@@ -193,27 +194,24 @@ void WebSocketInputState::ButtonsPress(DebuggerRequest &req) {
|
|||||||
}
|
}
|
||||||
press.button = info->second;
|
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);
|
pressTickets_.push_back(press);
|
||||||
}
|
}
|
||||||
|
|
||||||
void WebSocketInputState::Broadcast(net::WebSocketServer *ws) {
|
void WebSocketInputState::Broadcast(net::WebSocketServer *ws) {
|
||||||
int counter = __DisplayGetNumVblanks();
|
if (pressTickets_.empty())
|
||||||
if (pressTickets_.empty() || lastCounter_ == counter)
|
|
||||||
return;
|
return;
|
||||||
lastCounter_ = counter;
|
auto done = [](const PressInfo &press) -> bool {
|
||||||
|
return !__CtrlPressActive(press.pressId);
|
||||||
|
};
|
||||||
for (PressInfo &press : pressTickets_) {
|
for (PressInfo &press : pressTickets_) {
|
||||||
press.duration--;
|
if (done(press)) {
|
||||||
if (press.duration == -1) {
|
|
||||||
__CtrlUpdateButtons(0, press.button);
|
|
||||||
ws->Send(press.Event());
|
ws->Send(press.Event());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
auto negative = [](const PressInfo &press) -> bool {
|
pressTickets_.erase(std::remove_if(pressTickets_.begin(), pressTickets_.end(), done), pressTickets_.end());
|
||||||
return press.duration < 0;
|
|
||||||
};
|
|
||||||
pressTickets_.erase(std::remove_if(pressTickets_.begin(), pressTickets_.end(), negative), pressTickets_.end());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool AnalogValue(DebuggerRequest &req, float *value, const char *name) {
|
static bool AnalogValue(DebuggerRequest &req, float *value, const char *name) {
|
||||||
|
|||||||
@@ -15,8 +15,10 @@
|
|||||||
// Official git repository and contact information can be found at
|
// Official git repository and contact information can be found at
|
||||||
// https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/.
|
// https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/.
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
#include <cmath>
|
#include <cmath>
|
||||||
#include <mutex>
|
#include <mutex>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
#include "Common/Serialize/Serializer.h"
|
#include "Common/Serialize/Serializer.h"
|
||||||
#include "Common/Serialize/SerializeFuncs.h"
|
#include "Common/Serialize/SerializeFuncs.h"
|
||||||
@@ -205,6 +207,58 @@ u32 __CtrlReadLatch()
|
|||||||
return ret;
|
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)
|
void __CtrlUpdateButtons(u32 bitsToSet, u32 bitsToClear)
|
||||||
{
|
{
|
||||||
bitsToClear &= CTRL_MASK_USER;
|
bitsToClear &= CTRL_MASK_USER;
|
||||||
@@ -343,6 +397,7 @@ void __CtrlVblank() {
|
|||||||
// This always runs, so make sure we're in vblank mode.
|
// This always runs, so make sure we're in vblank mode.
|
||||||
if (ctrlCycle == 0)
|
if (ctrlCycle == 0)
|
||||||
__CtrlDoSample();
|
__CtrlDoSample();
|
||||||
|
__CtrlUpdateTimedPresses();
|
||||||
}
|
}
|
||||||
|
|
||||||
static void __CtrlTimerUpdate(u64 userdata, int cyclesLate)
|
static void __CtrlTimerUpdate(u64 userdata, int cyclesLate)
|
||||||
@@ -423,6 +478,8 @@ void __CtrlDoState(PointerWrap &p)
|
|||||||
void __CtrlShutdown()
|
void __CtrlShutdown()
|
||||||
{
|
{
|
||||||
waitingThreads.clear();
|
waitingThreads.clear();
|
||||||
|
std::lock_guard<std::mutex> guard(ctrlMutex);
|
||||||
|
timedPresses.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
static u32 sceCtrlSetSamplingCycle(u32 cycle)
|
static u32 sceCtrlSetSamplingCycle(u32 cycle)
|
||||||
|
|||||||
@@ -73,6 +73,9 @@ void __CtrlVblank();
|
|||||||
|
|
||||||
// Clears and sets selected buttons. NOTE: Clearing happens first.
|
// Clears and sets selected buttons. NOTE: Clearing happens first.
|
||||||
void __CtrlUpdateButtons(u32 bitsToSet, u32 bitsToClear);
|
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.
|
// 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.)
|
// 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. |
|
| `: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. |
|
| `: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. |
|
| `:echo <text>` | Prints text, for marking up a script's output. |
|
||||||
| `# comment` | Ignored. |
|
| `# comment` | Ignored. |
|
||||||
|
|
||||||
|
|||||||
Reference in new issue
Block a user