sdl: safer code to resolve intermittent controller crashes related to disconnections (#2755)

This commit is contained in:
Tyler Wilding
2023-06-23 14:35:32 -05:00
committed by GitHub
parent 533d2f6f2b
commit 679986e79f
11 changed files with 214 additions and 44 deletions
+4
View File
@@ -470,6 +470,10 @@ void GLDisplay::render() {
auto p = scoped_prof("display-manager-ee-events");
m_display_manager->process_ee_events();
}
{
auto p = scoped_prof("input-manager-ee-events");
m_input_manager->process_ee_events();
}
// imgui start of frame
{
+15 -4
View File
@@ -648,7 +648,7 @@ void pc_set_keyboard_enabled(u32 sym_val) {
void pc_set_mouse_options(u32 enabled, u32 control_camera, u32 control_movement) {
if (Display::GetMainDisplay()) {
Display::GetMainDisplay()->get_input_manager()->update_mouse_options(
Display::GetMainDisplay()->get_input_manager()->enqueue_update_mouse_options(
symbol_to_bool(enabled), symbol_to_bool(control_camera), symbol_to_bool(control_movement));
}
}
@@ -665,7 +665,7 @@ void pc_set_mouse_camera_sens(u32 xsens, u32 ysens) {
void pc_ignore_background_controller_events(u32 sym_val) {
if (Display::GetMainDisplay()) {
Display::GetMainDisplay()->get_input_manager()->ignore_background_controller_events(
Display::GetMainDisplay()->get_input_manager()->enqueue_ignore_background_controller_events(
symbol_to_bool(sym_val));
}
}
@@ -677,9 +677,17 @@ u64 pc_current_controller_has_led() {
return bool_to_symbol(false);
}
u64 pc_current_controller_has_rumble() {
if (Display::GetMainDisplay()) {
return bool_to_symbol(Display::GetMainDisplay()->get_input_manager()->controller_has_rumble(0));
}
return bool_to_symbol(false);
}
void pc_set_controller_led(const int port, const u8 red, const u8 green, const u8 blue) {
if (Display::GetMainDisplay()) {
Display::GetMainDisplay()->get_input_manager()->set_controller_led(port, red, green, blue);
Display::GetMainDisplay()->get_input_manager()->enqueue_set_controller_led(port, red, green,
blue);
}
}
@@ -715,7 +723,8 @@ void pc_reset_bindings_to_defaults(const int port, const InputDeviceType device_
void pc_set_auto_hide_cursor(u32 val) {
if (Display::GetMainDisplay()) {
Display::GetMainDisplay()->get_input_manager()->set_auto_hide_mouse(symbol_to_bool(val));
Display::GetMainDisplay()->get_input_manager()->enqueue_set_auto_hide_mouse(
symbol_to_bool(val));
}
}
@@ -873,6 +882,8 @@ void init_common_pc_port_functions(
make_func_symbol_func("pc-ignore-background-controller-events!",
(void*)pc_ignore_background_controller_events);
make_func_symbol_func("pc-current-controller-has-led?", (void*)pc_current_controller_has_led);
make_func_symbol_func("pc-current-controller-has-rumble?",
(void*)pc_current_controller_has_rumble);
make_func_symbol_func("pc-set-controller-led!", (void*)pc_set_controller_led);
make_func_symbol_func("pc-waiting-for-bind?", (void*)pc_waiting_for_bind);
make_func_symbol_func("pc-set-waiting-for-bind!", (void*)pc_set_waiting_for_bind);
+5 -1
View File
@@ -95,7 +95,11 @@ int scePadSetActDirect(int port, int /*slot*/, const u8* data) {
// for small motor
// also, the "slow" motor corresponds to the "large" motor on the PS2
if (Display::GetMainDisplay()) {
return Display::GetMainDisplay()->get_input_manager()->update_rumble(port, data[1], data[0]);
if (!Display::GetMainDisplay()->get_input_manager()->controller_has_rumble(port)) {
return 0;
}
Display::GetMainDisplay()->get_input_manager()->enqueue_update_rumble(port, data[1], data[0]);
return 1; // TODO - assuming 1 means rumble is supported, and 0 is no?
}
return 0;
}
+24 -7
View File
@@ -11,7 +11,8 @@ GameController::GameController(int sdl_device_id,
m_loaded = false;
m_device_handle = SDL_GameControllerOpen(sdl_device_id);
if (!m_device_handle) {
sdl_util::log_error(fmt::format("Could not read data from device index: {}", sdl_device_id));
sdl_util::log_error(
fmt::format("Could not open game controller with device id: {}", sdl_device_id));
return;
}
@@ -24,14 +25,27 @@ GameController::GameController(int sdl_device_id,
m_sdl_instance_id = SDL_JoystickInstanceID(joystick);
if (m_sdl_instance_id < 0) {
sdl_util::log_error(
fmt::format("Could not get instance id for gamecontroller: id {}", sdl_device_id));
fmt::format("Could not determine instance id for gamecontroller: id {}", sdl_device_id));
return;
}
const auto controller_guid = SDL_JoystickGetGUID(joystick);
if (sdl_util::is_SDL_GUID_zero(controller_guid)) {
sdl_util::log_error(fmt::format("Could not determine controller guid: id {}", sdl_device_id));
return;
}
char guidStr[33];
SDL_JoystickGetGUIDString(controller_guid, guidStr, sizeof(guidStr));
m_guid = guidStr;
// NOTE - it has been observed that this function will return `NULL` which indicates a bad
// controller this seems to happen if you disconnect the controller while it's in the process of
// connecting
//
// However, SDL_GameControllerGetAttached returns true, and `NULL` here is not an outright failure
// there could be controllers that just have no name.
//
// So I'm letting this one go through, in most cases it had an invalid guid as well (so caught
// above).
auto name = SDL_GameControllerName(m_device_handle);
if (!name) {
sdl_util::log_error(fmt::format("Could not get device name with id: {}", sdl_device_id));
@@ -40,6 +54,7 @@ GameController::GameController(int sdl_device_id,
m_device_name = name;
}
m_has_led = sdl_util::from_sdl_bool(SDL_GameControllerHasLED(m_device_handle));
m_has_rumble = sdl_util::from_sdl_bool(SDL_GameControllerHasRumble(m_device_handle));
if (m_settings->controller_binds.find(m_guid) == m_settings->controller_binds.end()) {
m_settings->controller_binds[m_guid] = DEFAULT_CONTROLLER_BINDS;
@@ -52,7 +67,7 @@ void GameController::process_event(const SDL_Event& event,
const CommandBindingGroups& commands,
std::shared_ptr<PadData> data,
std::optional<InputBindAssignmentMeta>& bind_assignment,
bool /*ignore_inputs*/) {
bool ignore_inputs) {
if (event.type == SDL_CONTROLLERAXISMOTION && event.caxis.which == m_sdl_instance_id) {
// https://wiki.libsdl.org/SDL2/SDL_GameControllerAxis
if ((int)event.caxis.axis <= SDL_CONTROLLER_AXIS_INVALID ||
@@ -134,7 +149,7 @@ void GameController::close_device() {
}
int GameController::update_rumble(const u8 low_rumble, const u8 high_rumble) {
if (m_device_handle) {
if (m_has_rumble && m_device_handle) {
// https://wiki.libsdl.org/SDL2/SDL_GameControllerRumble
// Arbitrary duration, since it's called every frame anyway, just so the vibration doesn't last
// forever. SDL expects a value in the range of 0-0xFFFF, so the `257` is just normalizing the
@@ -150,8 +165,10 @@ void GameController::set_led(const u8 red, const u8 green, const u8 blue) {
if (!m_has_led) {
return;
}
auto ok = SDL_GameControllerSetLED(m_device_handle, red, green, blue);
if (ok != 0) {
m_has_led = false;
if (m_device_handle) {
auto ok = SDL_GameControllerSetLED(m_device_handle, red, green, blue);
if (ok != 0) {
m_has_led = false;
}
}
}
@@ -17,6 +17,7 @@ class GameController : public InputDevice {
int update_rumble(const u8 low_rumble, const u8 high_rumble);
std::string get_name() const { return m_device_name; }
bool has_led() { return m_has_led; }
bool has_rumble() { return m_has_rumble; }
void set_led(const u8 red, const u8 green, const u8 blue);
std::string get_guid() { return m_guid; }
@@ -25,5 +26,6 @@ class GameController : public InputDevice {
SDL_GameController* m_device_handle;
std::string m_device_name = "";
bool m_has_led;
bool m_has_rumble;
std::string m_guid = "";
};
+93 -14
View File
@@ -53,6 +53,8 @@ void InputManager::refresh_device_list() {
m_available_controllers.clear();
m_controller_port_mapping.clear();
// Enumerate devices
// TODO - if this was done on a separate thread, there would be no hitch in the game thread
// but of course, that presents other synchronization challenges.
const auto num_joysticks = SDL_NumJoysticks();
if (num_joysticks > 0) {
for (int i = 0; i < num_joysticks; i++) {
@@ -61,6 +63,10 @@ void InputManager::refresh_device_list() {
continue;
}
auto controller = std::make_shared<GameController>(i, m_settings);
if (!controller->is_loaded()) {
lg::error("Unable to successfully connect to GameController with id {}, skipping", i);
continue;
}
m_available_controllers.push_back(controller);
// By default, controller port mapping is on a first-come-first-served basis
//
@@ -103,6 +109,11 @@ void InputManager::refresh_device_list() {
}
}
void InputManager::enqueue_ignore_background_controller_events(const bool ignore) {
const std::lock_guard<std::mutex> lock(m_event_queue_mtx);
ee_event_queue.push({EEInputEventType::IGNORE_BACKGROUND_CONTROLLER_EVENTS, ignore});
}
void InputManager::ignore_background_controller_events(const bool ignore) {
m_ignore_background_controller_events = ignore;
// TODO - ignoring return value (atleast log it)
@@ -169,19 +180,33 @@ void InputManager::process_sdl_event(const SDL_Event& event,
}
}
std::optional<std::shared_ptr<PadData>> InputManager::get_current_data(const int port) const {
if (m_data.find(port) == m_data.end()) {
return {};
void InputManager::process_ee_events() {
const std::lock_guard<std::mutex> lock(m_event_queue_mtx);
// Fully process any events from the EE
while (!ee_event_queue.empty()) {
const auto& evt = ee_event_queue.front();
switch (evt.type) {
case EEInputEventType::IGNORE_BACKGROUND_CONTROLLER_EVENTS:
ignore_background_controller_events(std::get<bool>(evt.param1));
break;
case EEInputEventType::UPDATE_RUMBLE:
update_rumble(std::get<int>(evt.param1), std::get<u8>(evt.param2),
std::get<u8>(evt.param3));
break;
case EEInputEventType::SET_CONTROLLER_LED:
set_controller_led(std::get<int>(evt.param1), std::get<u8>(evt.param2),
std::get<u8>(evt.param3), std::get<u8>(evt.param4));
break;
case EEInputEventType::UPDATE_MOUSE_OPTIONS:
update_mouse_options(std::get<bool>(evt.param1), std::get<bool>(evt.param2),
std::get<bool>(evt.param3));
break;
case EEInputEventType::SET_AUTO_HIDE_MOUSE:
set_auto_hide_mouse(std::get<bool>(evt.param1));
break;
}
ee_event_queue.pop();
}
return m_data.at(port);
}
int InputManager::update_rumble(int port, u8 low_intensity, u8 high_intensity) {
if (m_controller_port_mapping.find(port) == m_controller_port_mapping.end()) {
return 0;
}
return m_available_controllers.at(m_controller_port_mapping.at(port))
->update_rumble(low_intensity, high_intensity);
}
void InputManager::register_command(const CommandBinding::Source source,
@@ -210,6 +235,13 @@ void InputManager::register_command(const CommandBinding::Source source,
}
}
std::optional<std::shared_ptr<PadData>> InputManager::get_current_data(const int port) const {
if (m_data.find(port) == m_data.end()) {
return {};
}
return m_data.at(port);
}
std::string InputManager::get_controller_name(const int controller_id) {
if ((size_t)controller_id >= m_available_controllers.size()) {
return "";
@@ -271,7 +303,7 @@ void InputManager::set_controller_for_port(const int controller_id, const int po
}
bool InputManager::controller_has_led(const int port) {
if (m_controller_port_mapping.find(0) == m_controller_port_mapping.end()) {
if (m_controller_port_mapping.find(port) == m_controller_port_mapping.end()) {
return false;
}
const auto id = m_controller_port_mapping.at(port);
@@ -281,8 +313,27 @@ bool InputManager::controller_has_led(const int port) {
return m_available_controllers.at(id)->has_led();
}
bool InputManager::controller_has_rumble(const int port) {
if (m_controller_port_mapping.find(port) == m_controller_port_mapping.end()) {
return false;
}
const auto id = m_controller_port_mapping.at(port);
if (id >= (int)m_available_controllers.size()) {
return false;
}
return m_available_controllers.at(id)->has_rumble();
}
void InputManager::enqueue_set_controller_led(const int port,
const u8 red,
const u8 green,
const u8 blue) {
const std::lock_guard<std::mutex> lock(m_event_queue_mtx);
ee_event_queue.push({EEInputEventType::SET_CONTROLLER_LED, port, red, green, blue});
}
void InputManager::set_controller_led(const int port, const u8 red, const u8 green, const u8 blue) {
if (m_controller_port_mapping.find(0) == m_controller_port_mapping.end()) {
if (m_controller_port_mapping.find(port) == m_controller_port_mapping.end()) {
return;
}
const auto id = m_controller_port_mapping.at(port);
@@ -292,6 +343,21 @@ void InputManager::set_controller_led(const int port, const u8 red, const u8 gre
m_available_controllers.at(id)->set_led(red, green, blue);
}
void InputManager::enqueue_update_rumble(const int port,
const u8 low_intensity,
const u8 high_intensity) {
const std::lock_guard<std::mutex> lock(m_event_queue_mtx);
ee_event_queue.push({EEInputEventType::UPDATE_RUMBLE, port, low_intensity, high_intensity});
}
int InputManager::update_rumble(int port, u8 low_intensity, u8 high_intensity) {
if (m_controller_port_mapping.find(port) == m_controller_port_mapping.end()) {
return 0;
}
return m_available_controllers.at(m_controller_port_mapping.at(port))
->update_rumble(low_intensity, high_intensity);
}
void InputManager::enable_keyboard(const bool enabled) {
m_keyboard_enabled = enabled;
if (!m_keyboard_enabled) {
@@ -300,6 +366,14 @@ void InputManager::enable_keyboard(const bool enabled) {
}
}
void InputManager::enqueue_update_mouse_options(const bool enabled,
const bool control_camera,
const bool control_movement) {
const std::lock_guard<std::mutex> lock(m_event_queue_mtx);
ee_event_queue.push(
{EEInputEventType::UPDATE_MOUSE_OPTIONS, enabled, control_camera, control_movement});
}
void InputManager::update_mouse_options(const bool enabled,
const bool control_camera,
const bool control_movement) {
@@ -360,6 +434,11 @@ void InputManager::reset_input_bindings_to_defaults(const int port,
}
}
void InputManager::enqueue_set_auto_hide_mouse(const bool auto_hide_mouse) {
const std::lock_guard<std::mutex> lock(m_event_queue_mtx);
ee_event_queue.push({EEInputEventType::SET_AUTO_HIDE_MOUSE, auto_hide_mouse});
}
void InputManager::set_auto_hide_mouse(const bool auto_hide_mouse) {
m_auto_hide_mouse = auto_hide_mouse;
if (!auto_hide_mouse) {
+58 -17
View File
@@ -2,9 +2,12 @@
#include <array>
#include <memory>
#include <mutex>
#include <optional>
#include <queue>
#include <string>
#include <unordered_map>
#include <variant>
#include "common/common_types.h"
@@ -21,22 +24,52 @@
/// - polls data from the input devices considered active
/// - fetches said data to be sent to the game
class InputManager {
private:
enum class EEInputEventType {
IGNORE_BACKGROUND_CONTROLLER_EVENTS,
UPDATE_RUMBLE,
SET_CONTROLLER_LED,
UPDATE_MOUSE_OPTIONS,
SET_AUTO_HIDE_MOUSE
};
struct EEInputEvent {
EEInputEventType type;
std::variant<bool, int> param1;
std::variant<bool, u8> param2;
std::variant<bool, u8> param3;
std::variant<u8> param4;
};
public:
InputManager();
~InputManager();
// Propagate and handle the SDL event, ignored it if it's not relevant
void process_sdl_event(const SDL_Event& event, const bool ignore_mouse, const bool ignore_kb);
// TODO - organize the functions here
void refresh_device_list();
void ignore_background_controller_events(const bool ignore);
std::optional<std::shared_ptr<PadData>> get_current_data(const int port) const;
int update_rumble(const int port, const u8 low_intensity, const u8 high_intensity);
std::pair<int, int> get_mouse_pos() const { return m_mouse.get_mouse_pos(); }
/// Any event coming from the EE thread that interacts directly with SDL should be enqueued as an
/// event so it can be ran from the proper thread context (the graphics thread)
void process_ee_events();
void register_command(const CommandBinding::Source source, const CommandBinding bind);
std::optional<std::shared_ptr<PadData>> get_current_data(const int port) const;
std::pair<int, int> get_mouse_pos() const { return m_mouse.get_mouse_pos(); }
// These functions can be called from the EE and interact with SDL directly
// These should be enqueued in the event that for example, you try to set
// the controller LED while the controller is disconnecting
void enqueue_ignore_background_controller_events(const bool ignore);
void enqueue_update_rumble(const int port, const u8 low_intensity, const u8 high_intensity);
void enqueue_set_controller_led(const int port, const u8 red, const u8 green, const u8 blue);
void enqueue_update_mouse_options(const bool enabled,
const bool control_camera,
const bool control_movement);
void enqueue_set_auto_hide_mouse(const bool auto_hide_mouse);
// These functions can be called from the EE but they only interact with
// the InputManager, so it shouldn't hurt (they don't need to be enqueued)
// Also these are generally waiting for a response and...OpenGOAL does not have an async/await
// pattern!
int get_num_controllers() const { return m_available_controllers.size(); }
std::string get_controller_name(const int controller_id);
std::string get_current_bind(const int port,
@@ -44,13 +77,10 @@ class InputManager {
const bool buttons,
const int input_idx,
const bool analog_for_minimum);
bool controller_has_led(const int port);
void set_controller_for_port(const int controller_id, const int port);
void set_controller_led(const int port, const u8 red, const u8 green, const u8 blue);
bool controller_has_led(const int port);
bool controller_has_rumble(const int port);
void enable_keyboard(const bool enabled);
void update_mouse_options(const bool enabled,
const bool control_camera,
const bool control_movement);
bool get_waiting_for_bind() const { return m_waiting_for_bind.has_value(); }
void set_wait_for_bind(const InputDeviceType device_type,
const bool for_analog,
@@ -59,10 +89,11 @@ class InputManager {
void stop_waiting_for_bind() { m_waiting_for_bind = std::nullopt; }
void set_camera_sens(const float xsens, const float ysens);
void reset_input_bindings_to_defaults(const int port, const InputDeviceType device_type);
void set_auto_hide_mouse(const bool auto_hide_mouse);
void clear_inputs();
private:
std::mutex m_event_queue_mtx;
std::queue<EEInputEvent> ee_event_queue;
std::shared_ptr<game_settings::InputSettings> m_settings;
/// This data can be shared throughout the runtime, it is the current state of the
@@ -91,8 +122,6 @@ class InputManager {
bool m_mouse_enabled = false;
bool m_auto_hide_mouse = true;
bool m_mouse_currently_hidden = false;
void hide_cursor(const bool hide_cursor);
bool m_ignore_background_controller_events = false;
/// No inputs will be processed while in this mode the first input detected from the relevant
@@ -100,4 +129,16 @@ class InputManager {
///
/// The game will poll for the status of this flag to know if a bind has been assigned
std::optional<InputBindAssignmentMeta> m_waiting_for_bind = std::nullopt;
void refresh_device_list();
void hide_cursor(const bool hide_cursor);
void clear_inputs();
void ignore_background_controller_events(const bool ignore);
int update_rumble(const int port, const u8 low_intensity, const u8 high_intensity);
void set_controller_led(const int port, const u8 red, const u8 green, const u8 blue);
void update_mouse_options(const bool enabled,
const bool control_camera,
const bool control_movement);
void set_auto_hide_mouse(const bool auto_hide_mouse);
};
+10
View File
@@ -23,6 +23,15 @@ SDL_bool sdl_bool(const bool val) {
bool from_sdl_bool(const SDL_bool val) {
return val == SDL_TRUE ? true : false;
}
bool is_SDL_GUID_zero(SDL_GUID guid) {
for (int i = 0; i < 16; i++) {
if (guid.data[i] != 0) {
return false;
}
}
return true;
}
std::vector<std::string> get_modifier_strings(InputModifiers modifiers) {
std::vector<std::string> result = {};
if (modifiers.need_ctrl) {
@@ -91,6 +100,7 @@ std::string get_controller_axis_name(const int sdl_axis_id) {
}
return result;
}
bool is_modifier_key(const SDL_Keycode key_code) {
return key_code == SDLK_LSHIFT || key_code == SDLK_RSHIFT || key_code == SDLK_LALT ||
key_code == SDLK_RALT || key_code == SDLK_LCTRL || key_code == SDLK_RCTRL ||
+1
View File
@@ -12,6 +12,7 @@ void log_error(const std::string& msg = "");
bool is_any_event_type(uint32_t event_type, const std::vector<uint32_t>& allowed_types);
SDL_bool sdl_bool(const bool val);
bool from_sdl_bool(const SDL_bool val);
bool is_SDL_GUID_zero(SDL_GUID guid);
std::string get_mouse_button_name(const int sdl_mouse_button_id, InputModifiers modifiers);
std::string get_keyboard_button_name(const int sdl_key_code, InputModifiers modifiers);
+1
View File
@@ -349,6 +349,7 @@
(define-extern pc-set-mouse-options! (function symbol symbol symbol none))
(define-extern pc-set-mouse-camera-sens! (function float float none))
(define-extern pc-current-controller-has-led? (function symbol))
(define-extern pc-current-controller-has-rumble? (function symbol))
(define-extern pc-set-controller-led! (function int int int int none))
(define-extern pc-ignore-background-controller-events! (function symbol none))
(define-extern pc-reset-bindings-to-defaults! (function int int none))
+1 -1
View File
@@ -188,7 +188,7 @@
(define *controller-options* (new 'static 'boxed-array :type game-option
(new 'static 'game-option :option-type (game-option-type menu) :name (text-id input-opts-select-controller) :scale #t :param3 (game-option-menu select-controller))
(new 'static 'game-option :option-type (game-option-type on-off) :name (text-id vibrations) :scale #t)
(new 'static 'game-option :option-type (game-option-type on-off) :name (text-id vibrations) :scale #t :option-disabled-func (lambda () (not (pc-current-controller-has-rumble?))))
(new 'static 'game-option :option-type (game-option-type slider) :name (text-id input-opts-analog-deadzone) :scale #t :param1 0.0 :param2 1.0
:slider-step-size 0.01 :slider-show-decimal? #t)
(new 'static 'game-option :option-type (game-option-type on-off) :name (text-id input-opts-ignore-controller-win-focus) :scale #t