fix: D435 -- replugged controllers never reopened (render-thread event pump swallowed hot-plug events)

SDL_CONTROLLERDEVICEADDED / REMOVED are dequeued by whichever of the two
event pumps polls first; fast3d's gfx_sdl_handle_events had no case for
them and dropped them, so inputRescanPads never ran after a replug (the
stale handle later aborted in malloc). Both pumps now post a request and
inputUpdate() consumes it on the pad-reading thread. Findings D435.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
dolentandClaude Fable 5.1 committed 2026-09-29 13:26:20 -07:00
1 parent 0e8c2ce213
commit 05af6d4e0d
6 files changed
+43 -1

No files matched your search

+5
View File
@@ -10,6 +10,7 @@
#include "input.h"
extern "C" void videoRequestQuit(const char *why); // port/src/video.c (D344)
extern "C" void inputRequestRescan(void); // port/src/input.c (D435)
static SDL_Window* wnd;
static SDL_GLContext ctx;
@@ -339,6 +340,10 @@ static void gfx_sdl_handle_events(void) {
case SDL_MOUSEWHEEL:
inputPostWheel(event.wheel.y); // weapon cycle
break;
case SDL_CONTROLLERDEVICEADDED: // D435: this pump and video.c's drain the same
case SDL_CONTROLLERDEVICEREMOVED: // SDL queue; request a rescan, never rescan here
inputRequestRescan();
break;
case SDL_WINDOWEVENT:
if (event.window.event == SDL_WINDOWEVENT_SIZE_CHANGED) {
SDL_GL_GetDrawableSize(wnd, &window_width, &window_height);
+1
View File
@@ -87,6 +87,7 @@ int inputMenuPointerLive(void);
/* Re-enumerate gamepads after a hotplug (SDL_CONTROLLERDEVICEADDED/REMOVED). */
void inputRescanPads(void);
void inputRequestRescan(void); /* D435: from either SDL event pump; consumed by inputUpdate() */
/* D401: Rumble Pak -> real gamepad haptics. The osMotor* shims in
* libultra.c (game's src/joy.c rumble state machine) route here.
+15
View File
@@ -926,8 +926,23 @@ void inputDestroy(void)
/* Poll once per controller-read: refresh SDL device state and integrate the
* mouse-aim delta into the accumulator. */
/* D435: controller hot-plug. SDL_CONTROLLERDEVICEADDED / REMOVED land on
* whichever of the two event pumps dequeues them first: video.c's host loop
* or fast3d's gfx_sdl_handle_events (render thread, every frame), and the
* latter used to drop them, so a replugged pad was never reopened. Both pumps
* now only post a request; the rescan runs here, at the top of inputUpdate(),
* on the thread that is about to read pads[], so a pad is never closed under
* a reader. */
static SDL_atomic_t s_rescanReq;
void inputRequestRescan(void)
{
SDL_AtomicSet(&s_rescanReq, 1);
}
void inputUpdate(void)
{
if (SDL_AtomicSet(&s_rescanReq, 0)) inputRescanPads(); /* D435 */
SDL_GameControllerUpdate();
/* D287: skip until the host thread has actually enabled relative mode
+1 -1
View File
@@ -865,7 +865,7 @@ void videoPumpEvents(void)
break;
case SDL_CONTROLLERDEVICEADDED:
case SDL_CONTROLLERDEVICEREMOVED:
inputRescanPads();
inputRequestRescan(); /* D435: the rescan runs on the pad-reading thread */
break;
case SDL_WINDOWEVENT:
if (ev.window.event == SDL_WINDOWEVENT_CLOSE) {