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
// FIXME: check for errors
SDL_Init(SDL_INIT_JOYSTICK);
SDL_Init(SDL_INIT_GAMECONTROLLER);
SDL_Init(SDL_INIT_JOYSTICK|SDL_INIT_GAMECONTROLLER);
SDL_GameControllerEventState(SDL_ENABLE);
SDL_JoystickEventState(SDL_ENABLE);
}
@ -69,20 +68,18 @@ void wxSDLJoy::Poll()
{
auto joy = e.cbutton.which;
if (!SDL_IsGameController(joy))
break;
if (contains(joystate, joy)) {
if (contains(instance_map, joy)) {
auto& state = *(instance_map[joy]);
auto but = e.cbutton.button;
auto val = e.cbutton.state;
auto prev_val = joystate[joy].button[but];
auto prev_val = state.button[but];
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;
if (!SDL_IsGameController(joy))
break;
if (contains(joystate, joy)) {
if (contains(instance_map, joy)) {
auto& state = *(instance_map[joy]);
auto axis = e.caxis.axis;
auto val = axisval(e.caxis.value);
auto prev_val = joystate[joy].axis[axis];
auto prev_val = state.axis[axis];
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;
}
case SDL_CONTROLLERDEVICEADDED:
case SDL_CONTROLLERDEVICEREMAPPED:
{
auto joy = e.cdevice.which;
if (!SDL_IsGameController(joy))
break;
if (add_all || contains(joystate, joy)) {
DisconnectController(joy);
ConnectController(joy);
RemapControllers();
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;
@ -138,10 +130,11 @@ void wxSDLJoy::Poll()
{
auto joy = e.cdevice.which;
if (contains(joystate, joy)) {
DisconnectController(joy);
if (contains(instance_map, 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;
@ -156,20 +149,22 @@ void wxSDLJoy::Poll()
{
auto joy = e.jbutton.which;
if (SDL_IsGameController(joy))
break;
if (contains(instance_map, joy)) {
auto& state = *(instance_map[joy]);
if (SDL_IsGameController(state.index))
break;
if (contains(joystate, joy)) {
auto but = e.jbutton.button;
auto val = e.jbutton.state;
auto prev_val = joystate[joy].button[but];
auto prev_val = state.button[but];
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;
if (SDL_IsGameController(joy))
break;
if (contains(instance_map, joy)) {
auto& state = *(instance_map[joy]);
if (SDL_IsGameController(state.index))
break;
if (contains(joystate, joy)) {
auto axis = e.jaxis.axis;
auto val = axisval(e.jaxis.value);
auto prev_val = joystate[joy].axis[axis];
auto prev_val = state.axis[axis];
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:
{
auto joy = e.cdevice.which;
auto joy = e.jdevice.which;
if (SDL_IsGameController(joy))
break;
if (add_all || contains(joystate, joy)) {
DisconnectController(joy);
ConnectController(joy);
RemapControllers();
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;
@ -222,15 +220,13 @@ void wxSDLJoy::Poll()
}
case SDL_JOYDEVICEREMOVED:
{
auto joy = e.cdevice.which;
auto joy = e.jdevice.which;
if (SDL_IsGameController(joy))
break;
if (contains(instance_map, joy)) {
auto index = instance_map[joy]->index;
RemapControllers();
if (contains(joystate, joy)) {
DisconnectController(joy);
systemScreenMessage(wxString::Format(_("Disconnected joystick %d"), joy + 1));
systemScreenMessage(wxString::Format(_("Disconnected joystick %d"), index));
}
got_event = true;
@ -314,34 +310,59 @@ void wxSDLJoy::Poll()
void wxSDLJoy::ConnectController(uint8_t joy)
{
if (SDL_IsGameController(joy)) {
if (!(joystate[joy].dev = SDL_GameControllerOpen(joy))) {
wxLogDebug("SDL_GameControllerOpen(%d) failed: %s", joy, SDL_GetError());
return;
SDL_Joystick* js_dev = nullptr;
bool is_gc;
if ((is_gc = SDL_IsGameController(joy))) {
auto dev = SDL_GameControllerOpen(joy);
if (dev) {
joystate[joy].dev = dev;
js_dev = SDL_GameControllerGetJoystick(dev);
}
}
else {
if (!(joystate[joy].dev = SDL_JoystickOpen(joy))) {
wxLogDebug("SDL_JoystickOpen(%d) failed: %s", joy, SDL_GetError());
return;
}
if ((js_dev = SDL_JoystickOpen(joy)))
joystate[joy].dev = js_dev;
}
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 (SDL_IsGameController(joy)) {
if (SDL_GameControllerGetAttached(dev))
SDL_GameControllerClose(dev);
if (auto& dev = state.dev) {
if (state.is_gc) {
SDL_GameControllerClose(dev);
}
else {
if (SDL_JoystickGetAttached(dev))
SDL_JoystickClose(dev);
SDL_JoystickClose(dev);
}
dev = nullptr;
}
instance_map.erase(state.instance);
}
void wxSDLJoy::Add(int8_t joy_n)
@ -364,14 +385,14 @@ void wxSDLJoy::Remove(int8_t joy_n)
if (joy_n < 0) {
for (auto&& joy : joystate)
DisconnectController(joy.first);
DisconnectController(joy.second);
joystate.clear();
return;
}
DisconnectController(joy_n);
DisconnectController(joystate[joy_n]);
joystate.erase(joy_n);
}

View File

@ -20,25 +20,28 @@
#include "../common/contains.h"
struct wxSDLJoyDev {
private:
union {
SDL_GameController* dev_gc = nullptr;
SDL_Joystick* dev_js;
};
public:
operator SDL_GameController*&();
SDL_GameController*& operator=(SDL_GameController* ptr);
private:
union {
SDL_GameController* dev_gc = nullptr;
SDL_Joystick* dev_js;
};
public:
operator SDL_GameController*&();
SDL_GameController*& operator=(SDL_GameController* ptr);
operator SDL_Joystick*&();
SDL_Joystick*& operator=(SDL_Joystick* ptr);
operator SDL_Joystick*&();
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 {
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, uint8_t> button{};
};
@ -70,13 +73,15 @@ protected:
// used to continue rumbling on a timer
void Notify();
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);
const uint8_t POLL_TIME_MS = 10;
const uint8_t POLL_TIME_MS = 25;
private:
std::unordered_map<uint8_t, wxSDLJoyState> joystate;
std::unordered_map<SDL_JoystickID, wxSDLJoyState*> instance_map;
bool add_all = false, rumbling = false;
wxLongLong last_poll = wxGetUTCTimeMillis();