Files
psxrecomp/runtime/tests/test_menu_video_snapshot.py
T
Shokunin c3a2f2c068 Menu host follow-ups: restart bits for touched groups, local return, default provider
- Restart bits: a group waits only when the edit touches it (next differs
  from the live value) and the result differs from boot. Dynamic
  resolution moves the live internal resolution off boot, so an in-game
  texture filter change relaunched the game. test_menu_video_snapshot
  covers both cases.
- Return to the frontend from a local game opens the launcher as at
  boot; only a lobby match start (the runtime's pending-launch exit, or
  the provider's lobby view asking once its match started) keeps the
  launch and resumes the room.
- Default provider: with no title choice, the UI library's provider is
  used when it ships one (recomp_ui_psx_menu.h), so recomp-ui stays the
  default; PsxMenuHost.default_selection tells it to keep the title's
  default presentation.
- Debug: menu_host reports provider, open, pause, capture and snapshot.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 12:17:18 -07:00

81 lines
3.8 KiB
Python

"""Execute production menu getters while the main thread publishes settings."""
import argparse, subprocess, tempfile
from pathlib import Path
from test_netplay_load_apply_flow import function_body
ap=argparse.ArgumentParser()
ap.add_argument('--cxx',required=True)
args=ap.parse_args()
runtime=Path(__file__).resolve().parents[1]
source=(runtime/'src/main.cpp').read_text(encoding='utf-8')
fixture=r'''
#include "host_overlay.h"
#include <mutex>
#include <atomic>
#include <thread>
#include <cassert>
#include <cstdio>
static std::mutex g_ovl_mu;
static PsxHostVideoSettings g_ovl_video_current{},g_ovl_video_boot{},g_video_boot{}, live{};
static bool g_ovl_netplay=false;
static std::atomic<int> netplay{0},wrong_thread_reads{0};
static std::thread::id main_id;
static bool g_ovl_video_pending=false;
static PsxHostVideoSettings g_ovl_video_post{};
#define PSX_MENU_RESTART_AFTER_MATCH (1u<<31)
static int psx_netplay_active() { return netplay.load(); }
extern "C" void psx_video_current(PsxHostVideoSettings *out) {
if (std::this_thread::get_id()!=main_id) { ++wrong_thread_reads; return; }
*out=live;
}
extern "C" unsigned psx_host_video_restart_bits(const PsxHostVideoSettings *b,const PsxHostVideoSettings *n) {
return b->renderer!=n->renderer ? PSX_VIDEO_RESTART_RENDERER : 0;
}
extern "C" unsigned psx_host_video_live_changes(const PsxHostVideoSettings *b,const PsxHostVideoSettings *n) {
return b->texture_filter!=n->texture_filter ? PSX_VIDEO_LIVE_TEXTURE_FILTER : 0;
}
'''
for signature in ['void mh_refresh_snapshot()', 'void mh_video_get(PsxHostVideoSettings* out)',
'unsigned mh_restart_bits(const PsxHostVideoSettings& next)',
'unsigned mh_video_apply(const PsxHostVideoSettings* next)']:
fixture += signature+' {'+function_body(source,signature)+'}\n'
fixture += r'''
static PsxHostVideoSettings values(int v) { return {v,v,v,v,v,v,v,v,v,v,v,v,v}; }
int main() {
main_id=std::this_thread::get_id();
live=values(1); g_video_boot=values(1); mh_refresh_snapshot();
std::atomic<int> reads{0};
std::thread draw([&] {
for (int i=0;i<50000;++i) {
PsxHostVideoSettings got{};
mh_video_get(&got);
assert(got.fullscreen==got.texture_filter && got.texture_filter==got.renderer &&
got.renderer==got.frame_generation && got.frame_generation==got.supersampling &&
got.supersampling==got.render_thread && got.render_thread==got.present_thread);
const auto next=values(9); /* differs from every live and boot value */
const unsigned restart=mh_video_apply(&next);
assert(restart & PSX_VIDEO_RESTART_RENDERER);
++reads;
}
});
for (int i=0;i<50000;++i) { live=values(1+i%8); netplay=i%2; mh_refresh_snapshot(); }
draw.join();
assert(reads==50000 && wrong_thread_reads==0 && g_ovl_video_pending);
/* Restart bits only for groups the edit touches: when the live value has
* moved off boot (dynamic resolution moves the internal resolution), an
* edit that leaves that group at its live value asks for no restart; one
* that changes it does. */
live=values(1); live.renderer=3; g_video_boot=values(1); netplay=0; mh_refresh_snapshot();
PsxHostVideoSettings same=live; same.texture_filter=5;
assert(mh_restart_bits(same)==0);
PsxHostVideoSettings changed=live; changed.renderer=4;
assert(mh_restart_bits(changed) & PSX_VIDEO_RESTART_RENDERER);
puts("menu video snapshot: coherent draw-thread reads and main-thread ownership PASS");
}
'''
with tempfile.TemporaryDirectory(prefix='psx-menu-snapshot-') as d:
d=Path(d); cpp=d/'snapshot.cpp'; exe=d/'snapshot.exe'
cpp.write_text(fixture,encoding='utf-8')
subprocess.run([args.cxx,'-std=c++17','-O2','-pthread','-I'+str(runtime/'include'),str(cpp),'-o',str(exe)],check=True)
subprocess.run([str(exe)],check=True)