mirror of
https://github.com/RetroPortingToolKit/psxrecomp
synced 2026-10-26 18:25:35 -04:00
05931590a3
Review (mstan): the title key is now experimental_jogcon_force_feedback( _invert) and the runtime logs it as EXPERIMENTAL. Force feedback goes through a pure driver (psx_jogcon_ffb.h) and an SDL haptic backend (psx_jogcon_ffb_sdl.cpp): every force has a 120 ms lifetime renewed each VBlank, so a pause, stall or crash goes slack by itself, and the Rewind and save-state pause loops, disconnect (close_player) and session end (close_controller) stop it explicitly. Tests: jogcon_ffb_test drives the driver against a recording backend (lifetime, renew, stop once, neutral, idempotence, disable mid-force); jogcon_ffb_sdl_test runs the SDL backend against an SDL3 virtual wheel. SDL has no virtual haptic device, so it checks that such a device is not driven, never falls back to rumble and closes cleanly. Also fixes the SDL3 haptic subsystem init check (psx_sdl.h returns 0 on success). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
65 lines
2.7 KiB
C++
65 lines
2.7 KiB
C++
/* JogCon force feedback SDL backend against SDL's virtual joystick.
|
|
*
|
|
* SDL has no virtual haptic device: SDL3 virtual joysticks expose rumble,
|
|
* trigger rumble, LED and sensors, never SDL_Haptic effects, and no software
|
|
* haptic driver exists. So this checks what SDL can represent: a virtual
|
|
* wheel-type joystick with rumble is NOT driven by the force-feedback path
|
|
* (gamepads keep their rumble path), the backend calls are safe no-ops on it,
|
|
* and close / neutral leave nothing running. Real constant / spring output
|
|
* needs a physical force-feedback wheel. Exit 77 = skipped (SDL2 build). */
|
|
#include "psx_sdl.h"
|
|
#include "psx_jogcon_ffb_sdl.h"
|
|
|
|
#include <cstdio>
|
|
|
|
static int s_fail;
|
|
#define CHECK(c, m) do { if (!(c)) { std::fprintf(stderr, "FAIL %s\n", m); ++s_fail; } } while (0)
|
|
|
|
#if defined(PSX_SDL3)
|
|
static int s_rumbles;
|
|
static bool SDLCALL virt_rumble(void*, Uint16, Uint16) { ++s_rumbles; return true; }
|
|
|
|
int main() {
|
|
SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI, "0");
|
|
if (SDL_Init(SDL_INIT_JOYSTICK | SDL_INIT_GAMEPAD) != 0) { /* psx_sdl.h: 0 = ok */
|
|
std::printf("skip: SDL_Init: %s\n", SDL_GetError());
|
|
return 77;
|
|
}
|
|
SDL_VirtualJoystickDesc desc;
|
|
SDL_INIT_INTERFACE(&desc);
|
|
desc.type = SDL_JOYSTICK_TYPE_WHEEL;
|
|
desc.naxes = 6;
|
|
desc.nbuttons = 15;
|
|
desc.name = "psxrecomp virtual wheel";
|
|
desc.Rumble = virt_rumble;
|
|
const SDL_JoystickID id = SDL_AttachVirtualJoystick(&desc);
|
|
CHECK(id != 0, "virtual wheel attached");
|
|
SDL_Joystick* joy = id ? SDL_OpenJoystick(id) : nullptr;
|
|
CHECK(joy != nullptr, "virtual wheel opened");
|
|
|
|
PsxJogconHaptic h;
|
|
const int opened = psx_jogcon_haptic_open(&h, joy);
|
|
CHECK(opened == 0 && h.haptic == nullptr && h.constant < 0 && h.spring < 0,
|
|
"no SDL haptic behind a virtual joystick: FFB path stays off");
|
|
PsxJogconFfbBackend b = psx_jogcon_haptic_backend(&h);
|
|
PsxJogconFfbDriver d = {{0, 0}, 0};
|
|
psx_jogcon_ffb_tick(&d, &b, 1, 3, 15, 0); /* plays into no device */
|
|
psx_jogcon_ffb_neutral(&d, &b);
|
|
CHECK(s_rumbles == 0, "FFB never falls back to gamepad rumble");
|
|
CHECK(SDL_RumbleJoystick(joy, 0x4000, 0x4000, 50) && s_rumbles == 1,
|
|
"the virtual device itself is live (rumble reaches it)");
|
|
psx_jogcon_haptic_close(&h);
|
|
psx_jogcon_haptic_close(&h); /* idempotent */
|
|
CHECK(psx_jogcon_haptic_open(&h, nullptr) == 0, "null joystick is safe");
|
|
|
|
if (joy) SDL_CloseJoystick(joy);
|
|
if (id) SDL_DetachVirtualJoystick(id);
|
|
SDL_Quit();
|
|
if (s_fail) return 1;
|
|
std::puts("JogCon FFB SDL (virtual joystick) tests passed");
|
|
return 0;
|
|
}
|
|
#else
|
|
int main() { std::puts("skip: needs SDL3 virtual joysticks"); return 77; }
|
|
#endif
|