[HID] file-pad: nanosecond change detection, and log every state change
Some checks failed
Orchestrator / Commit Message Validation (push) Has been skipped
Orchestrator / Lint (push) Failing after 2m24s
Orchestrator / Windows (x86-64) (push) Has been skipped
Orchestrator / Linux (x86-64) (push) Has been skipped
Orchestrator / Create Release (push) Has been skipped

Two things that only show up once you actually script this pad.

st_mtime is whole seconds. Combined with size it looked like enough and is not:
a script stepping a menu writes several same-length states per second
(`press=A` then `press=B`, both 8 bytes), and every one after the first was
silently dropped -- the emulator simply did not react, with nothing in any log
to say why. Compare st_mtim.tv_nsec as well, and track whether the file existed
at all so a delete is registered once rather than every frame.

Also log one line per state change (not per frame, so it stays quiet). Driving
the emulator headless means there is nothing to watch; this line is the only
proof that a scripted press was picked up, which turns "did my input land?" from
a guess into a grep.
This commit is contained in:
2026-08-13 20:14:29 +00:00
parent d15c8cfab6
commit e3e17e4951

View File

@@ -139,16 +139,31 @@ class FileInputDriver final : public InputDriver {
private: private:
// Re-parse only when the file actually changed: `GetState` is polled every // Re-parse only when the file actually changed: `GetState` is polled every
// frame and a stat is far cheaper than a read+parse. // frame and a stat is far cheaper than a read+parse.
//
// The change test uses **nanosecond** mtime, not `st_mtime`. Whole-second
// granularity plus size looked sufficient and is not: a script that steps a
// menu writes several same-length states per second (`press=A` then `press=B`,
// both 8 bytes), and every one of those after the first would be silently
// dropped. That failure is invisible — the emulator just does not react — so
// it is worth the extra field.
void Refresh() { void Refresh() {
struct stat st; struct stat st;
if (::stat(cvars::pad_file.c_str(), &st) != 0) { if (::stat(cvars::pad_file.c_str(), &st) != 0) {
Neutral(); if (present_) {
present_ = false;
Neutral();
++packet_;
XELOGI("[file-pad] {} gone -> neutral", cvars::pad_file);
}
return; return;
} }
if (st.st_mtime == mtime_ && st.st_size == size_) { if (present_ && st.st_mtim.tv_sec == mtime_sec_ &&
st.st_mtim.tv_nsec == mtime_nsec_ && st.st_size == size_) {
return; return;
} }
mtime_ = st.st_mtime; present_ = true;
mtime_sec_ = st.st_mtim.tv_sec;
mtime_nsec_ = st.st_mtim.tv_nsec;
size_ = st.st_size; size_ = st.st_size;
std::FILE* f = std::fopen(cvars::pad_file.c_str(), "rb"); std::FILE* f = std::fopen(cvars::pad_file.c_str(), "rb");
if (!f) { if (!f) {
@@ -161,6 +176,10 @@ class FileInputDriver final : public InputDriver {
buf[n] = '\0'; buf[n] = '\0';
Parse(buf); Parse(buf);
++packet_; ++packet_;
// One line per change (not per frame): with no display to watch, this log is
// the only proof that a scripted press was actually picked up.
XELOGI("[file-pad] #{} buttons={:04X} lt={} rt={} lx={} ly={} rx={} ry={}",
packet_, buttons_, lt_, rt_, lx_, ly_, rx_, ry_);
} }
void Neutral() { void Neutral() {
@@ -230,7 +249,9 @@ class FileInputDriver final : public InputDriver {
uint8_t lt_ = 0, rt_ = 0; uint8_t lt_ = 0, rt_ = 0;
int16_t lx_ = 0, ly_ = 0, rx_ = 0, ry_ = 0; int16_t lx_ = 0, ly_ = 0, rx_ = 0, ry_ = 0;
uint32_t packet_ = 1; uint32_t packet_ = 1;
time_t mtime_ = 0; bool present_ = false;
time_t mtime_sec_ = 0;
long mtime_nsec_ = -1;
off_t size_ = -1; off_t size_ = -1;
}; };