From 5920efb89df3d0e827240f17bb0a471a46d1994e Mon Sep 17 00:00:00 2001 From: Alex Vanderveen Date: Fri, 7 Aug 2026 16:42:06 -0400 Subject: [PATCH] Windows scheduling paths --- lib/recomp-net | 2 +- runtime/CMakeLists.txt | 1 + runtime/include/host_time.h | 25 +++++++ runtime/runtime.cmake | 1 + runtime/src/frame_pacing.c | 4 +- runtime/src/host_time.c | 124 ++++++++++++++++++++++++++++++++ runtime/src/psx_netplay.c | 42 ++--------- runtime/src/psx_netplay_rb.c | 14 +--- runtime/src/psx_netplay_sched.c | 19 +---- 9 files changed, 165 insertions(+), 67 deletions(-) create mode 100644 runtime/include/host_time.h create mode 100644 runtime/src/host_time.c diff --git a/lib/recomp-net b/lib/recomp-net index ded27391..198ebc81 160000 --- a/lib/recomp-net +++ b/lib/recomp-net @@ -1 +1 @@ -Subproject commit ded2739149b653196b5ab373a04702e356aa0845 +Subproject commit 198ebc8160661f2a39139d24c3b6cb5eefcfbdba diff --git a/runtime/CMakeLists.txt b/runtime/CMakeLists.txt index 43fa5d6d..bda60ea5 100644 --- a/runtime/CMakeLists.txt +++ b/runtime/CMakeLists.txt @@ -244,6 +244,7 @@ if(NOT MSVC AND EXISTS "${BEETLE_LIB}") src/beetle_libretro.cpp src/beetle_debug_server.c src/frame_pacing.c + src/host_time.c src/parity_trace.c src/device_trace.c src/audio_trace.c diff --git a/runtime/include/host_time.h b/runtime/include/host_time.h new file mode 100644 index 00000000..5c9613f8 --- /dev/null +++ b/runtime/include/host_time.h @@ -0,0 +1,25 @@ +/* + * host_time.h — cross-platform monotonic ms + high-res sleep. + * + * Windows netplay/pacing must not use GetTickCount64 / Sleep for short + * slices (coarse ~1–15.6 ms). Match recomp-net / BattleShip: QPC + waitable + * timer. Unix: CLOCK_MONOTONIC + usleep. + */ +#ifndef PSX_HOST_TIME_H +#define PSX_HOST_TIME_H + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +uint64_t psx_host_mono_ms(void); +void psx_host_sleep_micros(unsigned usec); +void psx_host_sleep_ms(unsigned ms); + +#ifdef __cplusplus +} +#endif + +#endif /* PSX_HOST_TIME_H */ diff --git a/runtime/runtime.cmake b/runtime/runtime.cmake index e059d6f7..9848cdc1 100644 --- a/runtime/runtime.cmake +++ b/runtime/runtime.cmake @@ -196,6 +196,7 @@ set(PSXRECOMP_RUNTIME_SOURCES ${PSXRECOMP_ROOT}/runtime/src/timers.c ${PSXRECOMP_ROOT}/runtime/src/interrupts.c ${PSXRECOMP_ROOT}/runtime/src/frame_pacing.c + ${PSXRECOMP_ROOT}/runtime/src/host_time.c ${PSXRECOMP_ROOT}/runtime/src/psx_fiber.c ${PSXRECOMP_ROOT}/runtime/src/sio.c ${PSXRECOMP_ROOT}/runtime/src/memcard.c diff --git a/runtime/src/frame_pacing.c b/runtime/src/frame_pacing.c index c6a22e1f..be30e5af 100644 --- a/runtime/src/frame_pacing.c +++ b/runtime/src/frame_pacing.c @@ -8,6 +8,7 @@ * function (tests/test_frame_pacing.c includes this file directly). */ #ifndef FRAME_PACING_PURE_ONLY #include "psx_sdl.h" +#include "host_time.h" #endif uint32_t frame_pacing_sleep_ms(uint64_t now, uint64_t deadline, @@ -65,7 +66,8 @@ void frame_pacer_wait(FramePacer *p, double period_ms) { now = SDL_GetPerformanceCounter(); /* ONE read per iteration */ uint32_t ms = frame_pacing_sleep_ms(now, p->next_deadline, freq, period); if (ms == 0) break; - SDL_Delay(ms); + /* Waitable timer on Win32; usleep on Unix — not coarse Sleep/SDL_Delay. */ + psx_host_sleep_ms(ms); } while (SDL_GetPerformanceCounter() < p->next_deadline) { /* final sub-ms spin */ diff --git a/runtime/src/host_time.c b/runtime/src/host_time.c new file mode 100644 index 00000000..c70cd94b --- /dev/null +++ b/runtime/src/host_time.c @@ -0,0 +1,124 @@ +/* Feature macros before any system headers (strict C11 / -std=c11). */ +#if !defined(_WIN32) +#if !defined(_POSIX_C_SOURCE) +#define _POSIX_C_SOURCE 200809L +#endif +#if !defined(_DEFAULT_SOURCE) +#define _DEFAULT_SOURCE 1 +#endif +#endif + +#include "host_time.h" + +#if defined(_WIN32) +#ifndef WIN32_LEAN_AND_MEAN +#define WIN32_LEAN_AND_MEAN +#endif +#include + +#ifndef CREATE_WAITABLE_TIMER_HIGH_RESOLUTION +#define CREATE_WAITABLE_TIMER_HIGH_RESOLUTION 0x00000002 +#endif + +static LARGE_INTEGER s_qpc_freq; +static int s_qpc_freq_init; +static HANDLE s_sleep_timer; +static int s_sleep_timer_init; + +static void sleep_timer_ensure(void) +{ + if (s_sleep_timer_init) + return; + s_sleep_timer_init = 1; + s_sleep_timer = + CreateWaitableTimerExW(NULL, NULL, CREATE_WAITABLE_TIMER_HIGH_RESOLUTION, + TIMER_ALL_ACCESS); + if (!s_sleep_timer) + s_sleep_timer = CreateWaitableTimerW(NULL, FALSE, NULL); +} + +uint64_t psx_host_mono_ms(void) +{ + LARGE_INTEGER counter; + + if (!s_qpc_freq_init) { + if (!QueryPerformanceFrequency(&s_qpc_freq)) + s_qpc_freq.QuadPart = 0; + s_qpc_freq_init = 1; + } + if (s_qpc_freq.QuadPart == 0) + return (uint64_t)GetTickCount64(); + if (!QueryPerformanceCounter(&counter)) + return (uint64_t)GetTickCount64(); + return (uint64_t)((counter.QuadPart * 1000ULL) / (uint64_t)s_qpc_freq.QuadPart); +} + +void psx_host_sleep_micros(unsigned usec) +{ + LARGE_INTEGER due; + LONGLONG hundred_ns; + + if (usec == 0u) + return; + + sleep_timer_ensure(); + if (s_sleep_timer) { + hundred_ns = -((LONGLONG)usec * 10LL); + if (hundred_ns >= 0) + hundred_ns = -1LL; + due.QuadPart = hundred_ns; + if (SetWaitableTimer(s_sleep_timer, &due, 0, NULL, NULL, FALSE)) { + (void)WaitForSingleObject(s_sleep_timer, INFINITE); + return; + } + } + if (usec < 1000u) { + Sleep(1u); + return; + } + Sleep((DWORD)(usec / 1000u)); +} + +#else /* !_WIN32 */ + +#include +#include +#include + +uint64_t psx_host_mono_ms(void) +{ +#if defined(CLOCK_MONOTONIC) + struct timespec ts; + if (clock_gettime(CLOCK_MONOTONIC, &ts) == 0) + return (uint64_t)ts.tv_sec * 1000ull + + (uint64_t)ts.tv_nsec / 1000000ull; +#endif + return (uint64_t)time(NULL) * 1000ull; +} + +void psx_host_sleep_micros(unsigned usec) +{ + struct timespec req; + struct timespec rem; + + if (usec == 0u) + return; + req.tv_sec = (time_t)(usec / 1000000u); + req.tv_nsec = (long)(usec % 1000000u) * 1000L; + while (nanosleep(&req, &rem) != 0) { + if (errno != EINTR) + break; + req = rem; + } +} + +#endif /* _WIN32 */ + +void psx_host_sleep_ms(unsigned ms) +{ + if (ms == 0u) + return; + if (ms > 1000000u) + ms = 1000000u; + psx_host_sleep_micros(ms * 1000u); +} diff --git a/runtime/src/psx_netplay.c b/runtime/src/psx_netplay.c index badc5f8d..7d0e2f5c 100644 --- a/runtime/src/psx_netplay.c +++ b/runtime/src/psx_netplay.c @@ -4,6 +4,7 @@ #include "psx_netplay.h" +#include "host_time.h" #include "memcard.h" #include "savestate.h" #include "sio.h" @@ -191,14 +192,7 @@ void psx_netplay_release_pads(void) static uint32_t psx_start_bisect_wall_ms(void) { -#if defined(_WIN32) - return (uint32_t)GetTickCount(); -#else - struct timespec ts; - if (clock_gettime(CLOCK_MONOTONIC, &ts) != 0) - return 0u; - return (uint32_t)(ts.tv_sec * 1000ull + (uint64_t)ts.tv_nsec / 1000000ull); -#endif + return (uint32_t)psx_host_mono_ms(); } static int psx_start_bisect_env_flag(const char *name) @@ -1000,28 +994,12 @@ static int g_diag_mkdir_done; static void np_sleep_ms(unsigned ms) { -#if defined(_WIN32) - Sleep(ms); -#else - usleep(ms * 1000u); -#endif + psx_host_sleep_ms(ms); } static uint32_t np_mono_ms(void) { - /* Prefer Win32 clocks: MinGW defines CLOCK_MONOTONIC and would otherwise - * emit a clock_gettime64 ref that static links without -lwinpthread miss. */ -#if defined(_WIN32) - return (uint32_t)GetTickCount64(); -#elif defined(CLOCK_MONOTONIC) - struct timespec ts; - if (clock_gettime(CLOCK_MONOTONIC, &ts) == 0) - return (uint32_t)((uint64_t)ts.tv_sec * 1000ull + - (uint64_t)ts.tv_nsec / 1000000ull); - return (uint32_t)((uint64_t)time(NULL) * 1000ull); -#else - return (uint32_t)((uint64_t)time(NULL) * 1000ull); -#endif + return (uint32_t)psx_host_mono_ms(); } static void np_enter_load_ready(int slot); @@ -1796,17 +1774,7 @@ static int np_pad_diag_enabled(void) static uint32_t np_pad_mono_ms(void) { -#if defined(_WIN32) - return (uint32_t)GetTickCount64(); -#elif defined(CLOCK_MONOTONIC) - struct timespec ts; - if (clock_gettime(CLOCK_MONOTONIC, &ts) == 0) - return (uint32_t)((uint64_t)ts.tv_sec * 1000ull + - (uint64_t)ts.tv_nsec / 1000000ull); - return (uint32_t)((uint64_t)time(NULL) * 1000ull); -#else - return (uint32_t)((uint64_t)time(NULL) * 1000ull); -#endif + return (uint32_t)psx_host_mono_ms(); } static int np_pad_start_down(uint16_t buttons) diff --git a/runtime/src/psx_netplay_rb.c b/runtime/src/psx_netplay_rb.c index 4e1427b3..85bafc80 100644 --- a/runtime/src/psx_netplay_rb.c +++ b/runtime/src/psx_netplay_rb.c @@ -113,6 +113,7 @@ uint32_t psx_netplay_rb_rtt_estimate_ms(void) { return 0; } #include "netplay_state_digest.h" #include "psx_netplay.h" #include "psx_netplay_sched.h" +#include "host_time.h" #include "boot_state.h" #include "cdrom.h" #include "cpu_state.h" @@ -133,10 +134,6 @@ uint32_t psx_netplay_rb_rtt_estimate_ms(void) { return 0; } #include #include -#if defined(_WIN32) -#include -#endif - static PsxNetplayRbBindings g_b; static int g_bound; static RNetRbSession *g_rb; @@ -2558,14 +2555,7 @@ static uint8_t host_hash_confirm_promote(void *ctx) static uint64_t rb_mono_ms(void) { -#if defined(_WIN32) - return (uint64_t)GetTickCount64(); -#else - struct timespec ts; - if (clock_gettime(CLOCK_MONOTONIC, &ts) != 0) - return 0; - return (uint64_t)ts.tv_sec * 1000ull + (uint64_t)ts.tv_nsec / 1000000ull; -#endif + return psx_host_mono_ms(); } /* 1 for ~300ms after tip-hold commit — covers peer SAFETY-deferred tip-hold. */ diff --git a/runtime/src/psx_netplay_sched.c b/runtime/src/psx_netplay_sched.c index 226ac9b1..019c6f12 100644 --- a/runtime/src/psx_netplay_sched.c +++ b/runtime/src/psx_netplay_sched.c @@ -18,10 +18,7 @@ #include #include -#if defined(_WIN32) -#include -#endif - +#include "host_time.h" #include "psx_netplay.h" #include "psx_netplay_rb.h" #include "recomp_net/recomp_net.h" @@ -61,18 +58,8 @@ static int sched_local_slot(void) static uint32_t sched_mono_ms(void) { - /* Win32 first: MinGW advertises CLOCK_MONOTONIC via winpthread. */ -#if defined(_WIN32) - return (uint32_t)GetTickCount64(); -#elif defined(CLOCK_MONOTONIC) - struct timespec ts; - if (clock_gettime(CLOCK_MONOTONIC, &ts) == 0) - return (uint32_t)((uint64_t)ts.tv_sec * 1000ull + - (uint64_t)ts.tv_nsec / 1000000ull); - return (uint32_t)((uint64_t)time(NULL) * 1000ull); -#else - return (uint32_t)((uint64_t)time(NULL) * 1000ull); -#endif + /* QPC / CLOCK_MONOTONIC — not GetTickCount64 (coarse on Win32). */ + return (uint32_t)psx_host_mono_ms(); } /* ------------------------------------------------------------------ */