diff --git a/.gitignore b/.gitignore index c916cbdb..13ee69ee 100644 --- a/.gitignore +++ b/.gitignore @@ -41,7 +41,7 @@ release-stage/ *.zip # Vendored upstream sources (cloned locally, not checked in) -beetle-psx/ +beetle-psx # Explicit-fallback SDL2 / dep zips (large, regenerable) sdl2-dev.zip diff --git a/runtime/CMakeLists.txt b/runtime/CMakeLists.txt index 7f0c8393..88bbfe33 100644 --- a/runtime/CMakeLists.txt +++ b/runtime/CMakeLists.txt @@ -868,14 +868,17 @@ if(NOT MSVC) ) endif() -# ---- psx-beetle target (Beetle PSX libretro standalone, MinGW) ---- +# ---- psx-beetle target (Beetle PSX libretro standalone; MinGW, Linux, macOS) ---- # # Standalone binary that runs Beetle PSX (mednafen-psx libretro core) with # a debug server exposing the SAME wire-protocol commands as psx-runtime, # on a different port (4380). No recomp code linked. # -# Requires beetle-psx built via: +# Requires beetle-psx with the docs/beetle_*.patch hooks, built via: # cd beetle-psx && make platform=mingw_x86_64 STATIC_LINKING=1 HAVE_LIGHTREC=0 +# (platform=unix on Linux, platform=osx on macOS; there the archive is named +# mednafen_psx_libretro.so/.dylib; copy it to libmednafen_psx.a). Recipes: +# docs/beetle-linux.md, docs/beetle-macos.md. set(BEETLE_LIB "${CMAKE_SOURCE_DIR}/../beetle-psx/libmednafen_psx.a") diff --git a/runtime/src/beetle_debug_server.c b/runtime/src/beetle_debug_server.c index 9cd7045b..eb0964ce 100644 --- a/runtime/src/beetle_debug_server.c +++ b/runtime/src/beetle_debug_server.c @@ -15,6 +15,8 @@ #include #include +#include "host_time.h" + #ifndef DEFAULT_DEBUG_PORT #error DEFAULT_DEBUG_PORT must be defined by the beetle runtime target. #endif @@ -28,6 +30,7 @@ # define sock_close closesocket #else # include +# include # include # include # include @@ -189,17 +192,50 @@ static int s_input_override = -1; static int s_input_frames = 0; /* ---- Send helpers ---- */ + +/* The client socket is non-blocking. A reply larger than the free space in + * the kernel send buffer (a 2 MB read_ram is ~4 MB of hex; macOS starts a + * TCP send buffer at 128 KB) makes send() fail with EWOULDBLOCK + * part-way through. Wait for the socket to drain and carry on, like + * debug_server.c's send_all_blocking, instead of dropping the connection + * and leaving the client a truncated JSON line. */ +#define SEND_TOTAL_BUDGET_MS 15000 + +static int send_would_block(void) { +#ifdef _WIN32 + return WSAGetLastError() == WSAEWOULDBLOCK; +#else + return errno == EAGAIN || errno == EWOULDBLOCK || errno == EINTR; +#endif +} + +/* Wait up to 100 ms for the client socket to become writable. */ +static int send_wait_writable(void) { + fd_set wr; + FD_ZERO(&wr); + FD_SET(s_client, &wr); + struct timeval tv = { 0, 100 * 1000 }; + return select((int)s_client + 1, NULL, &wr, NULL, &tv) >= 0; +} + static void send_raw(const char *data, int n) { if (s_client == SOCK_INVALID) return; int off = 0; + const uint64_t t_start = psx_host_mono_ms(); while (off < n) { int k = send(s_client, data + off, n - off, 0); - if (k <= 0) { - sock_close(s_client); - s_client = SOCK_INVALID; - return; + if (k > 0) { + off += k; + continue; } - off += k; + if (k < 0 && send_would_block() && + psx_host_mono_ms() - t_start < SEND_TOTAL_BUDGET_MS && + send_wait_writable()) { + continue; + } + sock_close(s_client); + s_client = SOCK_INVALID; + return; } } diff --git a/runtime/src/beetle_libretro.cpp b/runtime/src/beetle_libretro.cpp index 8e4ccc0c..8a5fcee1 100644 --- a/runtime/src/beetle_libretro.cpp +++ b/runtime/src/beetle_libretro.cpp @@ -1018,7 +1018,9 @@ extern "C" uint64_t beetle_spu_event_total(void) { return s_spu_event_seq; } * Returns 0 if the CDC isn't up. */ extern "C" int beetle_cdc_decode_volume(unsigned out[4]) { - extern PS_CDC *PSX_CDC; + /* PSX_CDC is declared by mednafen/psx/psx.h. A block-scope redeclaration + * here would get C language linkage from the enclosing extern "C" + * function, which clang rejects against psx.h's C++ declaration. */ if (!PSX_CDC) return 0; uint8 m[2][2]; PSX_CDC->PSXRecomp_GetDecodeVolume(m);