Differentiate between SDL joy index/instance_id.

Hopefully fix various bugs caused by not differentiating between SDL
joystick_index and SDL_JoystickID (the joystick instance id.)

On controller disconnect, disconnect and reconnect all configured
devices because the SDL joystick index may change, and this will update
the mapping.

Also fix creating a state entry for the configured joystick even when
it's not connected, add the controller/joystick name to the connected
message and increase the poll time from 10ms to 25ms.

- Fix #718.
- Fix #726.

Signed-off-by: Rafael Kitover <rkitover@gmail.com>
This commit is contained in:
Rafael Kitover 2020-09-05 12:51:49 +00:00
parent 11693d8381
commit 6a7142813d
2 changed files with 110 additions and 84 deletions

View File

@ -18,8 +18,7 @@ wxSDLJoy::wxSDLJoy()
{ {
// Start up joystick if not already started // Start up joystick if not already started
// FIXME: check for errors // FIXME: check for errors
SDL_Init(SDL_INIT_JOYSTICK); SDL_Init(SDL_INIT_JOYSTICK|SDL_INIT_GAMECONTROLLER);
SDL_Init(SDL_INIT_GAMECONTROLLER);
SDL_GameControllerEventState(SDL_ENABLE); SDL_GameControllerEventState(SDL_ENABLE);
SDL_JoystickEventState(SDL_ENABLE); SDL_JoystickEventState(SDL_ENABLE);
} }
@ -69,20 +68,18 @@ void wxSDLJoy::Poll()
{ {
auto joy = e.cbutton.which; auto joy = e.cbutton.which;
if (!SDL_IsGameController(joy)) if (contains(instance_map, joy)) {
break; auto& state = *(instance_map[joy]);
if (contains(joystate, joy)) {
auto but = e.cbutton.button; auto but = e.cbutton.button;
auto val = e.cbutton.state; auto val = e.cbutton.state;
auto prev_val = joystate[joy].button[but]; auto prev_val = state.button[but];
if (val != prev_val) { if (val != prev_val) {
CreateAndSendEvent(joy, WXSDLJOY_BUTTON, but, val, prev_val); CreateAndSendEvent(state.index, WXSDLJOY_BUTTON, but, val, prev_val);
joystate[joy].button[but] = val; state.button[but] = val;
wxLogDebug("GOT SDL_CONTROLLERBUTTON: joy:%d but:%d val:%d prev_val:%d", joy, but, val, prev_val); wxLogDebug("GOT SDL_CONTROLLERBUTTON: joy:%d but:%d val:%d prev_val:%d", state.index, but, val, prev_val);
} }
} }
@ -94,20 +91,18 @@ void wxSDLJoy::Poll()
{ {
auto joy = e.caxis.which; auto joy = e.caxis.which;
if (!SDL_IsGameController(joy)) if (contains(instance_map, joy)) {
break; auto& state = *(instance_map[joy]);
if (contains(joystate, joy)) {
auto axis = e.caxis.axis; auto axis = e.caxis.axis;
auto val = axisval(e.caxis.value); auto val = axisval(e.caxis.value);
auto prev_val = joystate[joy].axis[axis]; auto prev_val = state.axis[axis];
if (val != prev_val) { if (val != prev_val) {
CreateAndSendEvent(joy, WXSDLJOY_AXIS, axis, val, prev_val); CreateAndSendEvent(state.index, WXSDLJOY_AXIS, axis, val, prev_val);
joystate[joy].axis[axis] = val; state.axis[axis] = val;
wxLogDebug("GOT SDL_CONTROLLERAXISMOTION: joy:%d axis:%d val:%d prev_val:%d", joy, axis, val, prev_val); wxLogDebug("GOT SDL_CONTROLLERAXISMOTION: joy:%d axis:%d val:%d prev_val:%d", state.index, axis, val, prev_val);
} }
} }
@ -116,18 +111,15 @@ void wxSDLJoy::Poll()
break; break;
} }
case SDL_CONTROLLERDEVICEADDED: case SDL_CONTROLLERDEVICEADDED:
case SDL_CONTROLLERDEVICEREMAPPED:
{ {
auto joy = e.cdevice.which; auto joy = e.cdevice.which;
if (!SDL_IsGameController(joy))
break;
if (add_all || contains(joystate, joy)) { if (add_all || contains(joystate, joy)) {
DisconnectController(joy); RemapControllers();
ConnectController(joy); auto& state = joystate[joy];
systemScreenMessage(wxString::Format(_("Connected game controller %d"), joy + 1)); if (state.dev)
systemScreenMessage(wxString::Format(_("Connected game controller %d: %s"), joy, SDL_GameControllerName(state.dev)));
} }
got_event = true; got_event = true;
@ -138,10 +130,11 @@ void wxSDLJoy::Poll()
{ {
auto joy = e.cdevice.which; auto joy = e.cdevice.which;
if (contains(joystate, joy)) { if (contains(instance_map, joy)) {
DisconnectController(joy); auto index = instance_map[joy]->index;
RemapControllers();
systemScreenMessage(wxString::Format(_("Disconnected game controller %d"), joy + 1)); systemScreenMessage(wxString::Format(_("Disconnected game controller %d"), index));
} }
got_event = true; got_event = true;
@ -156,20 +149,22 @@ void wxSDLJoy::Poll()
{ {
auto joy = e.jbutton.which; auto joy = e.jbutton.which;
if (SDL_IsGameController(joy)) if (contains(instance_map, joy)) {
break; auto& state = *(instance_map[joy]);
if (SDL_IsGameController(state.index))
break;
if (contains(joystate, joy)) {
auto but = e.jbutton.button; auto but = e.jbutton.button;
auto val = e.jbutton.state; auto val = e.jbutton.state;
auto prev_val = joystate[joy].button[but]; auto prev_val = state.button[but];
if (val != prev_val) { if (val != prev_val) {
CreateAndSendEvent(joy, WXSDLJOY_BUTTON, but, val, prev_val); CreateAndSendEvent(state.index, WXSDLJOY_BUTTON, but, val, prev_val);
joystate[joy].button[but] = val; state.button[but] = val;
wxLogDebug("GOT SDL_JOYBUTTON: joy:%d but:%d val:%d prev_val:%d", joy, but, val, prev_val); wxLogDebug("GOT SDL_JOYBUTTON: joy:%d but:%d val:%d prev_val:%d", state.index, but, val, prev_val);
} }
} }
@ -181,20 +176,22 @@ void wxSDLJoy::Poll()
{ {
auto joy = e.jaxis.which; auto joy = e.jaxis.which;
if (SDL_IsGameController(joy)) if (contains(instance_map, joy)) {
break; auto& state = *(instance_map[joy]);
if (SDL_IsGameController(state.index))
break;
if (contains(joystate, joy)) {
auto axis = e.jaxis.axis; auto axis = e.jaxis.axis;
auto val = axisval(e.jaxis.value); auto val = axisval(e.jaxis.value);
auto prev_val = joystate[joy].axis[axis]; auto prev_val = state.axis[axis];
if (val != prev_val) { if (val != prev_val) {
CreateAndSendEvent(joy, WXSDLJOY_AXIS, axis, val, prev_val); CreateAndSendEvent(state.index, WXSDLJOY_AXIS, axis, val, prev_val);
joystate[joy].axis[axis] = val; state.axis[axis] = val;
wxLogDebug("GOT SDL_JOYAXISMOTION: joy:%d axis:%d val:%d prev_val:%d", joy, axis, val, prev_val); wxLogDebug("GOT SDL_JOYAXISMOTION: joy:%d axis:%d val:%d prev_val:%d", state.index, axis, val, prev_val);
} }
} }
@ -204,16 +201,17 @@ void wxSDLJoy::Poll()
} }
case SDL_JOYDEVICEADDED: case SDL_JOYDEVICEADDED:
{ {
auto joy = e.cdevice.which; auto joy = e.jdevice.which;
if (SDL_IsGameController(joy)) if (SDL_IsGameController(joy))
break; break;
if (add_all || contains(joystate, joy)) { if (add_all || contains(joystate, joy)) {
DisconnectController(joy); RemapControllers();
ConnectController(joy); auto& state = joystate[joy];
systemScreenMessage(wxString::Format(_("Connected joystick %d"), joy + 1)); if (state.dev)
systemScreenMessage(wxString::Format(_("Connected joystick %d: %s"), joy, SDL_JoystickName(state.dev)));
} }
got_event = true; got_event = true;
@ -222,15 +220,13 @@ void wxSDLJoy::Poll()
} }
case SDL_JOYDEVICEREMOVED: case SDL_JOYDEVICEREMOVED:
{ {
auto joy = e.cdevice.which; auto joy = e.jdevice.which;
if (SDL_IsGameController(joy)) if (contains(instance_map, joy)) {
break; auto index = instance_map[joy]->index;
RemapControllers();
if (contains(joystate, joy)) { systemScreenMessage(wxString::Format(_("Disconnected joystick %d"), index));
DisconnectController(joy);
systemScreenMessage(wxString::Format(_("Disconnected joystick %d"), joy + 1));
} }
got_event = true; got_event = true;
@ -314,34 +310,59 @@ void wxSDLJoy::Poll()
void wxSDLJoy::ConnectController(uint8_t joy) void wxSDLJoy::ConnectController(uint8_t joy)
{ {
if (SDL_IsGameController(joy)) { SDL_Joystick* js_dev = nullptr;
if (!(joystate[joy].dev = SDL_GameControllerOpen(joy))) { bool is_gc;
wxLogDebug("SDL_GameControllerOpen(%d) failed: %s", joy, SDL_GetError());
return; if ((is_gc = SDL_IsGameController(joy))) {
auto dev = SDL_GameControllerOpen(joy);
if (dev) {
joystate[joy].dev = dev;
js_dev = SDL_GameControllerGetJoystick(dev);
} }
} }
else { else {
if (!(joystate[joy].dev = SDL_JoystickOpen(joy))) { if ((js_dev = SDL_JoystickOpen(joy)))
wxLogDebug("SDL_JoystickOpen(%d) failed: %s", joy, SDL_GetError()); joystate[joy].dev = js_dev;
return; }
}
if (js_dev) {
auto instance = SDL_JoystickInstanceID(js_dev);
instance_map[instance] = &(joystate[joy]);
joystate[joy].instance = instance;
}
joystate[joy].index = joy;
joystate[joy].is_gc = is_gc;
}
void wxSDLJoy::RemapControllers()
{
for (auto&& joy : joystate) {
auto& state = joy.second;
DisconnectController(state);
ConnectController(joy.first);
} }
} }
void wxSDLJoy::DisconnectController(uint8_t joy) void wxSDLJoy::DisconnectController(wxSDLJoyState& state)
{ {
if (auto& dev = joystate[joy].dev) { if (auto& dev = state.dev) {
if (SDL_IsGameController(joy)) { if (state.is_gc) {
if (SDL_GameControllerGetAttached(dev)) SDL_GameControllerClose(dev);
SDL_GameControllerClose(dev);
} }
else { else {
if (SDL_JoystickGetAttached(dev)) SDL_JoystickClose(dev);
SDL_JoystickClose(dev);
} }
dev = nullptr; dev = nullptr;
} }
instance_map.erase(state.instance);
} }
void wxSDLJoy::Add(int8_t joy_n) void wxSDLJoy::Add(int8_t joy_n)
@ -364,14 +385,14 @@ void wxSDLJoy::Remove(int8_t joy_n)
if (joy_n < 0) { if (joy_n < 0) {
for (auto&& joy : joystate) for (auto&& joy : joystate)
DisconnectController(joy.first); DisconnectController(joy.second);
joystate.clear(); joystate.clear();
return; return;
} }
DisconnectController(joy_n); DisconnectController(joystate[joy_n]);
joystate.erase(joy_n); joystate.erase(joy_n);
} }

View File

@ -20,25 +20,28 @@
#include "../common/contains.h" #include "../common/contains.h"
struct wxSDLJoyDev { struct wxSDLJoyDev {
private: private:
union { union {
SDL_GameController* dev_gc = nullptr; SDL_GameController* dev_gc = nullptr;
SDL_Joystick* dev_js; SDL_Joystick* dev_js;
}; };
public: public:
operator SDL_GameController*&(); operator SDL_GameController*&();
SDL_GameController*& operator=(SDL_GameController* ptr); SDL_GameController*& operator=(SDL_GameController* ptr);
operator SDL_Joystick*&(); operator SDL_Joystick*&();
SDL_Joystick*& operator=(SDL_Joystick* ptr); SDL_Joystick*& operator=(SDL_Joystick* ptr);
operator bool(); operator bool();
std::nullptr_t& operator=(std::nullptr_t&& null_ptr); std::nullptr_t& operator=(std::nullptr_t&& null_ptr);
}; };
struct wxSDLJoyState { struct wxSDLJoyState {
wxSDLJoyDev dev; wxSDLJoyDev dev;
uint8_t index = 0;
bool is_gc = true;
SDL_JoystickID instance = 0;
std::unordered_map<uint8_t, int16_t> axis{}; std::unordered_map<uint8_t, int16_t> axis{};
std::unordered_map<uint8_t, uint8_t> button{}; std::unordered_map<uint8_t, uint8_t> button{};
}; };
@ -70,13 +73,15 @@ protected:
// used to continue rumbling on a timer // used to continue rumbling on a timer
void Notify(); void Notify();
void ConnectController(uint8_t joy); void ConnectController(uint8_t joy);
void DisconnectController(uint8_t joy); void RemapControllers();
void DisconnectController(wxSDLJoyState& dev);
void CreateAndSendEvent(unsigned short joy, unsigned short ctrl_type, unsigned short ctrl_idx, short ctrl_val, short prev_val); void CreateAndSendEvent(unsigned short joy, unsigned short ctrl_type, unsigned short ctrl_idx, short ctrl_val, short prev_val);
const uint8_t POLL_TIME_MS = 10; const uint8_t POLL_TIME_MS = 25;
private: private:
std::unordered_map<uint8_t, wxSDLJoyState> joystate; std::unordered_map<uint8_t, wxSDLJoyState> joystate;
std::unordered_map<SDL_JoystickID, wxSDLJoyState*> instance_map;
bool add_all = false, rumbling = false; bool add_all = false, rumbling = false;
wxLongLong last_poll = wxGetUTCTimeMillis(); wxLongLong last_poll = wxGetUTCTimeMillis();