VR: report XR readiness without the debug server

This commit is contained in:
Matt Stanley
2026-10-22 18:02:33 -07:00
committed by GitHub
13 changed files with 550 additions and 6 deletions
+24
View File
@@ -92,3 +92,27 @@ game-specific and must be calibrated independently. This API also works without
a headset backend; it does not enable a headset mode by default.
The optional headset backend is described in [OPENXR_RENDERING.md](OPENXR_RENDERING.md).
## Production startup and performance receipts
`PSX_OPENXR_READY_FILE=<fresh-path>` requests one JSON startup receipt, including
in builds with `PSX_DEBUG_TOOLS=OFF`. Main publishes through a same-directory
temporary and atomic replacement, so partial JSON is never visible. An existing
destination is refused at startup; the caller owns a fresh path and removes it
afterward. Schema 2 records XR session generations, counters and the savestate
receipt. When `PSX_LOAD_SLOT` is set, success requires that load to settle
successfully and a later submission from the current XR session. A failed load
publishes `ready:false` with `state_load_failed`; session re-init publishes a
new receipt after its first current-session submission. An invalid, refused, or
netplay-disabled load request also publishes `ready:false` with a specific error
instead of being treated as if no load were requested. A game-session reboot
starts a fresh load lifecycle, so an earlier success cannot authorize the new
session; the newly staged `PSX_LOAD_SLOT` must settle before readiness can be
published. Without the variable the reporter immediately becomes a no-op.
`PSX_RUNTIME_PERF_DIAG=1` also prints VR pair/shed/empty/failure totals, the pair
EMA/last milliseconds and rollback verification totals at its existing configured
interval (`PSX_RUNTIME_PERF_DIAG_MS`). This exposes production behavior without
per-instruction debug tracing. The pair average is an EMA, not a percentile.
`PSX_FRAME_REPORT_MS` remains the production guest-frame cadence measurement.
@@ -0,0 +1,28 @@
# Report XR readiness without the debug server
Source: [FractalEngineer/psxrecomp](https://github.com/FractalEngineer/psxrecomp).
- [5932092c](https://github.com/FractalEngineer/psxrecomp/commit/5932092cdf20aaa707e286dbd840c63b1f0d0598)
Original author: Yves <calibratedbeats@gmail.com>; retained as commit author
and Co-authored-by on this reconstructed port.
Adapted: this feature and its tests. The readiness receipt uses same-directory
temporary publication, refuses an existing destination, records XR session
generations, and requires a submission from the current session after a
requested load succeeds. Each game-session reboot starts a fresh load lifecycle;
an earlier success cannot authorize the rebooted session. A failed, invalid, or
refused load publishes a non-ready receipt without exposing partial JSON.
Excluded: other VR features, game policy, assets and chronological
investigation notes.
Base: `fef7a1b6e3e1be7511eb82fa3b512258beacba04` (current upstream master or the preceding feature).
Validation: Windows/GCC 13.2 Release/OpenGL/OpenXR/PGXP MoH build passes with
debug tools OFF and existing UI. Atomic replacement is covered by a standalone
test; the readiness state-machine fixture covers success, XR re-init, reboot
with an old success, pending and failed reboot loads, recovery, and refused
requests. Fresh-boot headless readiness and missing-slot refusal pass. The older
slot-2 fixture failed to load on current upstream; no compatibility guard/header
was changed.
Consumer: [Medal of Honor v0.1.6](https://github.com/FractalEngineer/Medal-Of-Honor-PSX-Recomp/tree/1957e28a5dc7f5daa79e2b9b6ed867eb06b41ace).
Requires a framework pin bump and rebuild; no game/BIOS regeneration.
+9
View File
@@ -50,6 +50,15 @@ if(BUILD_TESTING)
add_test(NAME debug_socket_shutdown_test COMMAND debug_socket_shutdown_test)
set_tests_properties(debug_socket_shutdown_test PROPERTIES TIMEOUT 15)
add_executable(atomic_file_write_test tests/test_atomic_file_write.cpp)
target_include_directories(atomic_file_write_test PRIVATE include)
target_compile_features(atomic_file_write_test PRIVATE cxx_std_17)
add_test(NAME atomic_file_write_test COMMAND atomic_file_write_test)
add_executable(openxr_ready_state_test tests/test_openxr_ready_state.c)
target_include_directories(openxr_ready_state_test PRIVATE include)
add_test(NAME openxr_ready_state_test COMMAND openxr_ready_state_test)
add_executable(video_texture_filter_test tests/test_video_texture_filter.c src/video_texture_filter.c)
target_include_directories(video_texture_filter_test PRIVATE include)
add_test(NAME video_texture_filter_test COMMAND video_texture_filter_test)
+67
View File
@@ -0,0 +1,67 @@
#pragma once
#include <filesystem>
#include <fstream>
#include <string>
#include <string_view>
#ifdef _WIN32
#ifndef WIN32_LEAN_AND_MEAN
#define WIN32_LEAN_AND_MEAN
#endif
#include <windows.h>
#else
#include <unistd.h>
#endif
namespace psx {
/* Publish a complete file by writing a same-directory temporary and replacing
* the destination atomically. The destination is never observed half-written. */
inline bool atomic_write_file(const std::filesystem::path& target,
std::string_view data,
std::string* error = nullptr) {
if (target.empty() || target.filename().empty()) {
if (error) *error = "destination has no filename";
return false;
}
auto temporary = target;
#ifdef _WIN32
temporary += "." + std::to_string(GetCurrentProcessId()) + ".tmp";
#else
temporary += "." + std::to_string(getpid()) + ".tmp";
#endif
{
std::ofstream out(temporary, std::ios::binary | std::ios::trunc);
out.write(data.data(), static_cast<std::streamsize>(data.size()));
out.flush();
out.close();
if (!out) {
std::error_code ignored;
std::filesystem::remove(temporary, ignored);
if (error) *error = "temporary write failed";
return false;
}
}
#ifdef _WIN32
if (!MoveFileExW(temporary.c_str(), target.c_str(),
MOVEFILE_REPLACE_EXISTING | MOVEFILE_WRITE_THROUGH)) {
std::error_code ignored;
std::filesystem::remove(temporary, ignored);
if (error) *error = "atomic replace failed";
return false;
}
#else
std::error_code ec;
std::filesystem::rename(temporary, target, ec);
if (ec) {
std::error_code ignored;
std::filesystem::remove(temporary, ignored);
if (error) *error = ec.message();
return false;
}
#endif
return true;
}
} // namespace psx
+88
View File
@@ -0,0 +1,88 @@
#pragma once
#include <stdint.h>
typedef struct PSXOpenXRReadyLoadRequest {
uint64_t sequence;
uint64_t execution_generation;
uint32_t status_generation_before;
int slot;
const char *error;
} PSXOpenXRReadyLoadRequest;
typedef struct PSXOpenXRReadyLoadStatus {
uint32_t generation;
int pending;
int last_ok;
int last_load;
int last_slot;
} PSXOpenXRReadyLoadStatus;
typedef struct PSXOpenXRReadyLoadState {
uint64_t execution_generation;
uint64_t request_sequence;
uint32_t generation_floor;
int expected;
int request_seen;
int resolved;
int last_ok;
int slot;
const char *error;
} PSXOpenXRReadyLoadState;
static inline void psx_openxr_ready_load_begin_execution(
PSXOpenXRReadyLoadState *state, uint64_t execution_generation,
int expected, int slot) {
if (!state) return;
state->execution_generation = execution_generation;
state->request_sequence = 0;
state->generation_floor = 0;
state->expected = !!expected;
state->request_seen = 0;
state->resolved = !state->expected;
state->last_ok = 0;
state->slot = slot;
state->error = 0;
}
/* Returns nonzero when this is a new request for the current execution. */
static inline int psx_openxr_ready_load_observe_request(
PSXOpenXRReadyLoadState *state,
const PSXOpenXRReadyLoadRequest *request) {
if (!state || !request || !state->expected ||
request->execution_generation != state->execution_generation ||
request->sequence == 0 || request->sequence == state->request_sequence)
return 0;
state->request_sequence = request->sequence;
state->request_seen = 1;
state->generation_floor = request->status_generation_before;
state->slot = request->slot;
state->error = request->error;
state->resolved = request->error != 0;
state->last_ok = 0;
return 1;
}
/* Only a settled load after the request-generation floor can resolve success. */
static inline int psx_openxr_ready_load_observe_status(
PSXOpenXRReadyLoadState *state,
const PSXOpenXRReadyLoadStatus *status) {
if (!state || !status || !state->expected || !state->request_seen ||
state->resolved ||
status->pending || status->generation <= state->generation_floor ||
!status->last_load || status->last_slot != state->slot)
return 0;
state->resolved = 1;
state->last_ok = status->last_ok;
return 1;
}
static inline int psx_openxr_ready_load_ok(
const PSXOpenXRReadyLoadState *state) {
return state && (!state->expected || (state->resolved && state->last_ok));
}
static inline int psx_openxr_ready_load_failed(
const PSXOpenXRReadyLoadState *state) {
return state && state->expected && state->resolved && !state->last_ok;
}
+1
View File
@@ -10,6 +10,7 @@ typedef struct PSXOpenXRStats {
uint64_t view_flags;
const char *stage;
uint64_t waits, submitted, empty, failures, predicted_time;
uint64_t session_generation, submitted_session_generation;
float ipd_m;
float origin_up_dot;
uint32_t origin_inversion_streak, origin_ambiguous;
+5
View File
@@ -128,6 +128,11 @@ int savestate_pending(void);
/* Machine-readable lifecycle receipt for deterministic debug harnesses. */
void savestate_status_json(char* buf, size_t cap);
typedef struct PSXSavestateStatus {
uint32_t generation;
int pending, last_ok, last_load, last_slot;
} PSXSavestateStatus;
void savestate_get_status(PSXSavestateStatus* out);
/* 1 once after a successful load restore (before scheduler longjmp). Clears. */
int savestate_take_load_completed(void);
+6 -2
View File
@@ -9088,6 +9088,7 @@ static void handle_openxr_stats(int id, const char *json) {
"\"frame_open\":%u,\"result\":%d,\"stage\":\"%s\",\"runtime\":\"%s\","
"\"last_failure\":\"%s\",\"last_failure_result\":%d,\"view_flags\":%llu,"
"\"waits\":%llu,\"submitted\":%llu,\"empty\":%llu,\"failures\":%llu,"
"\"session_generation\":%llu,\"submitted_session_generation\":%llu,"
"\"predicted_time\":%llu,\"ipd_m\":%.6f,\"origin_up_dot\":%.6f,"
"\"origin_inversion_streak\":%u,\"origin_ambiguous\":%u,\"units_per_meter\":%.6f,"
"\"gl_version\":%llu,\"min_gl_version\":%llu,\"max_gl_version\":%llu,\"swapchain_format\":%lld,"
@@ -9098,8 +9099,11 @@ static void handle_openxr_stats(int id, const char *json) {
s.initialized,s.running,s.state,s.tracking,s.frame_open,s.result,
s.stage?s.stage:"off",s.runtime,s.last_failure?s.last_failure:"",s.last_failure_result,
(unsigned long long)s.view_flags,(unsigned long long)s.waits,
(unsigned long long)s.submitted,(unsigned long long)s.empty,
(unsigned long long)s.failures,(unsigned long long)s.predicted_time,s.ipd_m,
(unsigned long long)s.submitted,(unsigned long long)s.empty,
(unsigned long long)s.failures,
(unsigned long long)s.session_generation,
(unsigned long long)s.submitted_session_generation,
(unsigned long long)s.predicted_time,s.ipd_m,
s.origin_up_dot,s.origin_inversion_streak,s.origin_ambiguous,s.units_per_meter,
(unsigned long long)s.gl_version,(unsigned long long)s.min_gl_version,
(unsigned long long)s.max_gl_version,(long long)s.swapchain_format,s.submitted_layer,
+182 -4
View File
@@ -34,6 +34,9 @@
#include "starvation_ring.h"
#include "load_accel.h"
#include "savestate.h"
#include "psx_openxr.h"
#include "atomic_file_write.h"
#include "openxr_ready_state.h"
#include "psx_rewind.h"
#include "psx_savestate_menu.h"
#include "host_osd.h"
@@ -4827,6 +4830,145 @@ static void runtime_perf_section_end(uint64_t start, uint64_t *total) {
* waits on the render queue), the render thread's busy share, the dynamic
* resolution level and Smooth motion's generated / real presents per second. */
static uint64_t g_frame_report_wait_ticks = 0; /* offline pacer waits */
static uint64_t g_openxr_ready_execution_generation = 0;
static PSXOpenXRReadyLoadRequest g_openxr_ready_load_request{};
static void openxr_ready_record_load_request(int slot, const char *error) {
PSXSavestateStatus status{};
savestate_get_status(&status);
g_openxr_ready_load_request.sequence++;
if (g_openxr_ready_load_request.sequence == 0)
g_openxr_ready_load_request.sequence = 1;
g_openxr_ready_load_request.execution_generation =
g_openxr_ready_execution_generation;
g_openxr_ready_load_request.status_generation_before = status.generation;
g_openxr_ready_load_request.slot = slot;
g_openxr_ready_load_request.error = error;
}
/* One opt-in startup receipt per XR session, including production builds without
* debug TCP. The destination must not exist at process start. */
static void openxr_ready_report_tick(uint64_t frames) {
static bool initialized = false;
static bool requested = false;
static PSXOpenXRReadyLoadState load_state{};
static uint64_t observed_execution = 0;
static uint64_t observed_session = 0, submission_floor = 0;
static bool published_for_session = false;
static uint64_t retry_at_ms = 0;
static std::filesystem::path path;
if (!initialized) {
initialized = true;
const char *env_path = std::getenv("PSX_OPENXR_READY_FILE");
if (!env_path || !*env_path) return;
path = env_path;
std::error_code ec;
const bool present = std::filesystem::exists(path, ec);
if (ec || present) {
std::fprintf(stderr,
"psxrecomp: refusing %s OpenXR readiness path '%s'\n",
ec ? "unreadable" : "existing", path.string().c_str());
return;
}
requested = true;
const char *load = std::getenv("PSX_LOAD_SLOT");
const bool load_expected = load && *load;
int load_slot = -1;
if (load_expected) {
char *end = nullptr;
const long parsed = std::strtol(load, &end, 10);
if (end && *end == '\0' && parsed >= 0 && parsed < SAVESTATE_SLOTS)
load_slot = static_cast<int>(parsed);
}
psx_openxr_ready_load_begin_execution(
&load_state, g_openxr_ready_execution_generation,
load_expected, load_slot);
if (load_expected && load_slot < 0)
openxr_ready_record_load_request(
-1, "state_load_request_invalid");
observed_execution = g_openxr_ready_execution_generation;
}
if (!requested) return;
bool execution_changed =
observed_execution != g_openxr_ready_execution_generation;
if (execution_changed) {
psx_openxr_ready_load_begin_execution(
&load_state, g_openxr_ready_execution_generation,
load_state.expected, load_state.slot);
observed_execution = g_openxr_ready_execution_generation;
}
psx_openxr_ready_load_observe_request(
&load_state, &g_openxr_ready_load_request);
PSXOpenXRStats xr{};
psx_openxr_stats(&xr);
if (execution_changed) {
submission_floor = xr.submitted;
published_for_session = false;
}
if (xr.session_generation && xr.session_generation != observed_session) {
if (observed_session != 0) {
psx_openxr_ready_load_begin_execution(
&load_state, g_openxr_ready_execution_generation,
load_state.expected, load_state.slot);
psx_openxr_ready_load_observe_request(
&load_state, &g_openxr_ready_load_request);
}
observed_session = xr.session_generation;
submission_floor = xr.submitted;
published_for_session = false;
}
PSXSavestateStatus current_state{};
savestate_get_status(&current_state);
PSXOpenXRReadyLoadStatus load_status = {
current_state.generation, current_state.pending, current_state.last_ok,
current_state.last_load, current_state.last_slot};
if (psx_openxr_ready_load_observe_status(&load_state, &load_status))
submission_floor = xr.submitted;
const uint64_t now = SDL_GetTicks();
if (now < retry_at_ms || published_for_session) return;
const bool load_ok = psx_openxr_ready_load_ok(&load_state);
const bool fresh_submission =
xr.session_generation != 0 &&
xr.submitted_session_generation == xr.session_generation &&
xr.submitted > submission_floor;
const bool load_failed = psx_openxr_ready_load_failed(&load_state);
if (!load_failed && (!load_ok || !xr.enabled || !xr.running || !fresh_submission))
return;
char state[512];
savestate_status_json(state, sizeof state);
char receipt[2048];
std::snprintf(receipt, sizeof receipt,
"{\"schema\":2,\"ready\":%d,\"error\":\"%s\",\"frame\":%llu,"
"\"openxr\":{\"session_generation\":%llu,\"submitted_session_generation\":%llu,"
"\"running\":%u,\"tracking\":%u,\"submitted\":%llu,"
"\"empty\":%llu,\"failures\":%llu},\"savestate\":{%s}}\n",
load_failed ? 0 : 1,
load_failed
? (load_state.error ? load_state.error : "state_load_failed")
: "",
(unsigned long long)frames,
(unsigned long long)xr.session_generation,
(unsigned long long)xr.submitted_session_generation,
xr.running, xr.tracking, (unsigned long long)xr.submitted,
(unsigned long long)xr.empty, (unsigned long long)xr.failures, state);
std::string error;
if (psx::atomic_write_file(path, receipt, &error)) {
published_for_session = true;
} else {
retry_at_ms = now + 1000;
std::fprintf(stderr,
"psxrecomp: cannot publish OpenXR startup receipt '%s': %s\n",
path.string().c_str(), error.c_str());
}
}
static void frame_report_tick(uint64_t frames) {
static int interval_ms = -1;
static uint64_t first_ticks = 0, last_ticks = 0, last_frames = 0;
@@ -4970,6 +5112,22 @@ static void runtime_perf_diag_tick() {
current.capture_triggers - last.capture_triggers,
current.capture_overlays - last.capture_overlays,
(unsigned long long)current.capture_last_dispatch_delta);
PSXOpenXRStats xr{};
psx_openxr_stats(&xr);
if (xr.enabled) {
RenderStereoStats stereo{};
RenderPassStats passes{};
render_stereo_get_stats(&stereo);
render_pass_get_stats(&passes);
std::fprintf(stdout,
"psxrecomp: VR pairs=%llu shed=%llu empty=%llu failures=%llu "
"avg_pair_ms=%.3f last_pair_ms=%.3f verify=%llu mismatch=%llu\n",
(unsigned long long)stereo.pairs, (unsigned long long)stereo.shed,
(unsigned long long)xr.empty, (unsigned long long)xr.failures,
stereo.avg_pair_ms, stereo.last_pair_ms,
(unsigned long long)passes.verify_checks,
(unsigned long long)passes.verify_mismatch);
}
std::fflush(stdout);
last = current;
last_spu = audio.tap_frames[AUDIO_TAP_SPU_OUT];
@@ -9062,6 +9220,7 @@ static NetplayVblankEpilogue sdl_vblank_present_body(void) {
{
/* Outside every debug guard on purpose: production must be measurable. */
extern uint64_t s_frame_count;
openxr_ready_report_tick(s_frame_count);
frame_report_tick(s_frame_count);
}
runtime_perf_frame_begin();
@@ -20352,6 +20511,9 @@ session_reboot:
video_res_set_applied(PSX_IR_UNSET, 1000);
g_video_res_live_failed.store(false);
g_video_res_live_capable.store(false);
g_openxr_ready_execution_generation++;
if (g_openxr_ready_execution_generation == 0)
g_openxr_ready_execution_generation = 1;
mod_runtime_set_mounted_disc_resolver(nullptr, nullptr);
psx_disc_tray_reset_session();
/* Rematch after lobby soft-return re-enters here with updated net_cfg. */
@@ -21636,17 +21798,33 @@ session_reboot:
psx_rewind_set_depth((uint32_t)g_rewind_depth);
psx_rewind_set_interval((uint32_t)g_rewind_interval);
psx_rewind_configure(memory_get_bios_checksum(), game_entry_pc);
/* Headless / agent load: PSX_LOAD_SLOT=N stages slot load (0..11)
/* Headless load: PSX_LOAD_SLOT=N stages slot load (0..11)
* at the next safe block boundary after boot. */
if (const char *ls = std::getenv("PSX_LOAD_SLOT")) {
int slot = atoi(ls);
if (slot >= 0 && slot < 12) {
if (const char *ls = std::getenv("PSX_LOAD_SLOT"); ls && *ls) {
char *end = nullptr;
const long parsed = std::strtol(ls, &end, 10);
if (!end || *end != '\0' || parsed < 0 || parsed >= SAVESTATE_SLOTS) {
openxr_ready_record_load_request(
-1, "state_load_request_invalid");
std::fprintf(stderr,
"psxrecomp: PSX_LOAD_SLOT=%s ignored (expected 0..%d)\n",
ls, SAVESTATE_SLOTS - 1);
} else {
const int slot = static_cast<int>(parsed);
if (psx_netplay_active()) {
openxr_ready_record_load_request(
slot, "state_load_unavailable_netplay");
std::fprintf(stdout,
"psxrecomp: PSX_LOAD_SLOT=%d ignored (use the save-state menu after netplay starts)\n",
slot);
} else if (savestate_request_load(slot)) {
openxr_ready_record_load_request(slot, nullptr);
std::fprintf(stdout, "psxrecomp: PSX_LOAD_SLOT=%d staged\n", slot);
} else {
openxr_ready_record_load_request(
slot, "state_load_request_refused");
std::fprintf(stderr,
"psxrecomp: PSX_LOAD_SLOT=%d could not be staged\n", slot);
}
}
}
+2
View File
@@ -303,6 +303,7 @@ static int initialize(void) {
if(!binding.hDC || !binding.hGLRC){s_stats.stage="opengl_context";return 0;}
XrSessionCreateInfo sci={XR_TYPE_SESSION_CREATE_INFO};sci.next=&binding;sci.systemId=s_system;
if(!check(xrCreateSession(s_instance,&sci,&s_session),"session"))return 0;
if(++s_stats.session_generation==0)s_stats.session_generation=1;
if(!input_initialize())return 0;
XrReferenceSpaceCreateInfo space={XR_TYPE_REFERENCE_SPACE_CREATE_INFO};
space.referenceSpaceType=XR_REFERENCE_SPACE_TYPE_LOCAL;space.poseInReferenceSpace.orientation.w=1;
@@ -798,6 +799,7 @@ int psx_openxr_end(int keep,PSXOpenXRCopy copy) {
xr_trace_us(gl_renderer_perf_ticks()-s_pose_sample_ticks));
psx_frame_trace_xr_cadence(s_display_delta_us, complete);
if(ok && complete) {
s_stats.submitted_session_generation=s_stats.session_generation;
s_stats.submitted_surfaces=quad?0:((s_surface_mask&1)!=0)+((s_surface_mask&2)!=0);
s_stats.submitted++;s_stats.submitted_pair_id=s_pair_id;
s_stats.submitted_guest_cycle=s_pair_cycle;s_stats.submitted_predicted_time=(uint64_t)s_time;
+9
View File
@@ -873,6 +873,15 @@ int savestate_pending(void) {
return (s_save_pending >= 0 || s_load_pending >= 0) ? 1 : 0;
}
void savestate_get_status(PSXSavestateStatus* out) {
if (!out) return;
out->generation = s_status_generation;
out->pending = s_status_pending;
out->last_ok = s_status_last_ok;
out->last_load = s_status_last_load;
out->last_slot = s_status_last_slot;
}
void savestate_status_json(char* buf, size_t cap) {
if (!buf || cap == 0) return;
snprintf(buf, cap,
+46
View File
@@ -0,0 +1,46 @@
#include "atomic_file_write.h"
#include <chrono>
#include <cstdio>
#include <filesystem>
#include <fstream>
#include <string>
static bool read_file(const std::filesystem::path& path, std::string* out) {
std::ifstream in(path, std::ios::binary);
if (!in) return false;
out->assign(std::istreambuf_iterator<char>(in), std::istreambuf_iterator<char>());
return bool(in);
}
int main() {
const auto nonce = std::chrono::steady_clock::now().time_since_epoch().count();
const auto root = std::filesystem::temp_directory_path() /
("psx-atomic-write-" + std::to_string(nonce));
std::filesystem::create_directories(root);
const auto target = root / "receipt.json";
{
std::ofstream old(target, std::ios::binary);
old << "old";
}
std::string error;
if (!psx::atomic_write_file(target, "{\"complete\":true}\n", &error)) {
std::fprintf(stderr, "atomic write failed: %s\n", error.c_str());
return 1;
}
std::string got;
if (!read_file(target, &got) || got != "{\"complete\":true}\n") {
std::fprintf(stderr, "destination was not atomically replaced\n");
return 1;
}
for (const auto& entry : std::filesystem::directory_iterator(root)) {
if (entry.path() != target) {
std::fprintf(stderr, "temporary file leaked\n");
return 1;
}
}
std::error_code ec;
std::filesystem::remove_all(root, ec);
return ec ? 1 : 0;
}
+83
View File
@@ -0,0 +1,83 @@
#include "openxr_ready_state.h"
#include <stdio.h>
#define CHECK(v, m) do { if (!(v)) { \
fprintf(stderr, "FAIL: %s\n", (m)); return 1; } } while (0)
int main(void) {
PSXOpenXRReadyLoadState state = {0};
PSXOpenXRReadyLoadRequest request = {0};
PSXOpenXRReadyLoadStatus status = {0};
psx_openxr_ready_load_begin_execution(&state, 1, 0, -1);
CHECK(psx_openxr_ready_load_ok(&state), "no requested load is ready");
psx_openxr_ready_load_begin_execution(&state, 1, 1, 2);
CHECK(!psx_openxr_ready_load_ok(&state), "requested load waits for its request");
request = (PSXOpenXRReadyLoadRequest){1, 1, 10, 2, NULL};
CHECK(psx_openxr_ready_load_observe_request(&state, &request),
"first load request is accepted");
CHECK(!psx_openxr_ready_load_ok(&state), "pending request is not ready");
status = (PSXOpenXRReadyLoadStatus){10, 1, 0, 1, 2};
CHECK(!psx_openxr_ready_load_observe_status(&state, &status),
"pending request cannot resolve");
status = (PSXOpenXRReadyLoadStatus){11, 0, 1, 1, 2};
CHECK(psx_openxr_ready_load_observe_status(&state, &status),
"completed current request resolves");
CHECK(psx_openxr_ready_load_ok(&state), "successful load is ready");
/* An XR session re-init in the same execution rebinds the current request. */
psx_openxr_ready_load_begin_execution(&state, 1, 1, 2);
CHECK(!psx_openxr_ready_load_ok(&state), "session re-init clears readiness");
request = (PSXOpenXRReadyLoadRequest){1, 1, 10, 2, NULL};
CHECK(psx_openxr_ready_load_observe_request(&state, &request),
"current execution request is rebound");
status = (PSXOpenXRReadyLoadStatus){11, 0, 1, 1, 2};
CHECK(psx_openxr_ready_load_observe_status(&state, &status) &&
psx_openxr_ready_load_ok(&state),
"current execution stays ready after XR session re-init");
/* A reboot must ignore both the prior status and the prior request. */
psx_openxr_ready_load_begin_execution(&state, 2, 1, 2);
status = (PSXOpenXRReadyLoadStatus){11, 0, 1, 1, 2};
CHECK(!psx_openxr_ready_load_observe_status(&state, &status),
"previous session success cannot authorize reboot");
CHECK(!psx_openxr_ready_load_ok(&state), "reboot waits for a fresh load");
request = (PSXOpenXRReadyLoadRequest){1, 1, 10, 2, NULL};
CHECK(!psx_openxr_ready_load_observe_request(&state, &request),
"stale execution request is ignored");
request = (PSXOpenXRReadyLoadRequest){2, 2, 11, 2, NULL};
CHECK(psx_openxr_ready_load_observe_request(&state, &request),
"reboot load request is accepted");
CHECK(!psx_openxr_ready_load_ok(&state),
"reboot pending load cannot reuse the prior success");
status = (PSXOpenXRReadyLoadStatus){12, 0, 0, 1, 2};
CHECK(psx_openxr_ready_load_observe_status(&state, &status),
"reboot failed load resolves as failure");
CHECK(psx_openxr_ready_load_failed(&state),
"reboot failed load cannot authorize success");
psx_openxr_ready_load_begin_execution(&state, 3, 1, 2);
request = (PSXOpenXRReadyLoadRequest){3, 3, 12, 2, NULL};
CHECK(psx_openxr_ready_load_observe_request(&state, &request),
"recovery load request is accepted");
status = (PSXOpenXRReadyLoadStatus){13, 0, 1, 1, 2};
CHECK(psx_openxr_ready_load_observe_status(&state, &status),
"recovery load resolves");
CHECK(psx_openxr_ready_load_ok(&state), "recovery session is ready");
psx_openxr_ready_load_begin_execution(&state, 4, 1, 3);
request = (PSXOpenXRReadyLoadRequest){4, 4, 13, -1,
"state_load_request_refused"};
CHECK(psx_openxr_ready_load_observe_request(&state, &request),
"refused load request is accepted");
CHECK(psx_openxr_ready_load_failed(&state) &&
state.error && state.error[0],
"refused load reports failure in its execution");
puts("ALL PASS");
return 0;
}