From 93c6dba3ff8ec34d66479b7c2edae6e367bc0988 Mon Sep 17 00:00:00 2001 From: Dipshet <264011288+Dipshet@users.noreply.github.com> Date: Thu, 6 Aug 2026 21:14:10 +0200 Subject: [PATCH] Stop a controller disconnect/reconnect from closing the game A failed SDL_OpenGamepad on hot-plug hit assert_always(), which is fatal in release: one transient open failure on reconnect closed the game. The axis/button handlers also dereferenced the controller-index optional unchecked, so orphan pad events were UB -> std::terminate. Now a failed open logs a WARN and returns; the pad retries on its next ADDED event. Orphan events are dropped with a rate-limited WARN. --- .../src/input/sdl/sdl_input_driver.cpp | 38 ++++++++++++++++--- 1 file changed, 33 insertions(+), 5 deletions(-) diff --git a/thirdparty/rexglue-sdk/src/input/sdl/sdl_input_driver.cpp b/thirdparty/rexglue-sdk/src/input/sdl/sdl_input_driver.cpp index bdef5857..fa97cf5d 100644 --- a/thirdparty/rexglue-sdk/src/input/sdl/sdl_input_driver.cpp +++ b/thirdparty/rexglue-sdk/src/input/sdl/sdl_input_driver.cpp @@ -10,6 +10,7 @@ */ #include +#include #include #include @@ -25,6 +26,22 @@ REXCVAR_DEFINE_STRING(hid_mappings_file, "gamecontrollerdb.txt", "Input", namespace rex::input::sdl { +namespace { +// Gamepad events whose instance id is not in our controller table are expected +// around hot-plug: the device failed to open, or was opened and immediately +// closed for lack of a free slot while SDL had already buffered events for it. +// Dropping such an event is always safe. Log a few so field reports name the +// instance id, without per-event spam. +void WarnOrphanEvent(const char* handler, SDL_JoystickID instance_id) { + static std::atomic dropped{0}; + const uint32_t n = dropped.fetch_add(1, std::memory_order_relaxed) + 1; + if (n <= 4 || (n & 0xFF) == 0) { + REXLOG_WARN("SDL {}: dropped event for unknown gamepad instance {} ({} dropped so far).", + handler, instance_id, n); + } +} +} // namespace + SDLInputDriver::SDLInputDriver(rex::ui::Window* window, size_t window_z_order) : InputDriver(window, window_z_order), sdl_events_initialized_(false), @@ -437,7 +454,11 @@ void SDLInputDriver::OnControllerDeviceAddedLocked(const SDL_Event& event) { // Open the controller. const auto controller = SDL_OpenGamepad(event.cdevice.which); if (!controller) { - assert_always(); + // Transient open failures are real on hot-plug (e.g. a Bluetooth pad that + // reconnects before the stack has settled). The device is simply not + // added; it gets a fresh chance on its next SDL_EVENT_GAMEPAD_ADDED. + REXLOG_WARN("SDL OnControllerDeviceAdded: SDL_OpenGamepad failed for device {}: {}", + event.cdevice.which, SDL_GetError()); return; } REXLOG_INFO( @@ -501,7 +522,10 @@ void SDLInputDriver::OnControllerDeviceRemovedLocked(const SDL_Event& event) { void SDLInputDriver::OnControllerDeviceAxisMotionLocked(const SDL_Event& event) { auto idx = GetControllerIndexFromInstanceID(event.gaxis.which); - assert(idx); + if (!idx) { + WarnOrphanEvent("OnControllerDeviceAxisMotion", event.gaxis.which); + return; + } auto& pad = controllers_.at(*idx).state.gamepad; switch (event.gaxis.axis) { case SDL_GAMEPAD_AXIS_LEFTX: @@ -523,8 +547,9 @@ void SDLInputDriver::OnControllerDeviceAxisMotionLocked(const SDL_Event& event) pad.right_trigger = static_cast(event.gaxis.value >> 7); break; default: - assert_always(); - break; + // A newer SDL version may have added new axes. + REXLOG_INFO("SDL HID: Unknown axis was moved: {}.", event.gaxis.axis); + return; } controllers_.at(*idx).state_changed = true; } @@ -566,7 +591,10 @@ void SDLInputDriver::OnControllerDeviceButtonChangedLocked(const SDL_Event& even static_assert(SDL_GAMEPAD_BUTTON_DPAD_RIGHT == 14); auto idx = GetControllerIndexFromInstanceID(event.gdevice.which); - assert(idx); + if (!idx) { + WarnOrphanEvent("OnControllerDeviceButtonChanged", event.gdevice.which); + return; + } auto& controller = controllers_.at(*idx); uint16_t xbuttons = controller.state.gamepad.buttons;