Input: Improve controller identification (#6029)
* Input: Improve controller identification Controllers were identified before by a combination of their _global_ index in the list of controllers and their GUID. The problem is, disconnecting and reconnecting a controller can change its global index; the controller can appear at the end. This would give it another ID, and the controller would need to be reconfigured. This happened to me a lot with a switch pro controller and a USB game controller, it was essentially random which appeared first. Now, it consistently detects them. This PR changes the controller identification to be a combination of an index of controllers with the same GUID (generally 0), and its GUID. It also reworks managing the list of controllers to properly consider instance IDs. This also changes the NpadManager to attempt to reuse old controllers when refreshing input configuration, which can prevent input from going dead for seconds whenever a controller connects or disconnects (and the switch pro controller just entirely dying). Testing with different controller types, OS and Avalonia is welcome. Remember that the target is connecting a ton of controllers, and pulling/reconnecting them. * Remove double empty line
This commit is contained in:
parent
edc76883db
commit
90455a05e6
2 changed files with 91 additions and 34 deletions
|
@ -9,8 +9,18 @@ namespace Ryujinx.Input.SDL2
|
|||
{
|
||||
private readonly Dictionary<int, string> _gamepadsInstanceIdsMapping;
|
||||
private readonly List<string> _gamepadsIds;
|
||||
private readonly object _lock = new object();
|
||||
|
||||
public ReadOnlySpan<string> GamepadsIds => _gamepadsIds.ToArray();
|
||||
public ReadOnlySpan<string> GamepadsIds
|
||||
{
|
||||
get
|
||||
{
|
||||
lock (_lock)
|
||||
{
|
||||
return _gamepadsIds.ToArray();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public string DriverName => "SDL2";
|
||||
|
||||
|
@ -35,28 +45,39 @@ namespace Ryujinx.Input.SDL2
|
|||
}
|
||||
}
|
||||
|
||||
private static string GenerateGamepadId(int joystickIndex)
|
||||
private string GenerateGamepadId(int joystickIndex)
|
||||
{
|
||||
Guid guid = SDL_JoystickGetDeviceGUID(joystickIndex);
|
||||
|
||||
// Add a unique identifier to the start of the GUID in case of duplicates.
|
||||
|
||||
if (guid == Guid.Empty)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return joystickIndex + "-" + guid;
|
||||
}
|
||||
string id;
|
||||
|
||||
private static int GetJoystickIndexByGamepadId(string id)
|
||||
{
|
||||
string[] data = id.Split("-");
|
||||
|
||||
if (data.Length != 6 || !int.TryParse(data[0], out int joystickIndex))
|
||||
lock (_lock)
|
||||
{
|
||||
return -1;
|
||||
int guidIndex = 0;
|
||||
id = guidIndex + "-" + guid;
|
||||
|
||||
while (_gamepadsIds.Contains(id))
|
||||
{
|
||||
id = (++guidIndex) + "-" + guid;
|
||||
}
|
||||
}
|
||||
|
||||
return joystickIndex;
|
||||
return id;
|
||||
}
|
||||
|
||||
private int GetJoystickIndexByGamepadId(string id)
|
||||
{
|
||||
lock (_lock)
|
||||
{
|
||||
return _gamepadsIds.IndexOf(id);
|
||||
}
|
||||
}
|
||||
|
||||
private void HandleJoyStickDisconnected(int joystickInstanceId)
|
||||
|
@ -64,7 +85,11 @@ namespace Ryujinx.Input.SDL2
|
|||
if (_gamepadsInstanceIdsMapping.TryGetValue(joystickInstanceId, out string id))
|
||||
{
|
||||
_gamepadsInstanceIdsMapping.Remove(joystickInstanceId);
|
||||
_gamepadsIds.Remove(id);
|
||||
|
||||
lock (_lock)
|
||||
{
|
||||
_gamepadsIds.Remove(id);
|
||||
}
|
||||
|
||||
OnGamepadDisconnected?.Invoke(id);
|
||||
}
|
||||
|
@ -74,6 +99,13 @@ namespace Ryujinx.Input.SDL2
|
|||
{
|
||||
if (SDL_IsGameController(joystickDeviceId) == SDL_bool.SDL_TRUE)
|
||||
{
|
||||
if (_gamepadsInstanceIdsMapping.ContainsKey(joystickInstanceId))
|
||||
{
|
||||
// Sometimes a JoyStick connected event fires after the app starts even though it was connected before
|
||||
// so it is rejected to avoid doubling the entries.
|
||||
return;
|
||||
}
|
||||
|
||||
string id = GenerateGamepadId(joystickDeviceId);
|
||||
|
||||
if (id == null)
|
||||
|
@ -81,16 +113,12 @@ namespace Ryujinx.Input.SDL2
|
|||
return;
|
||||
}
|
||||
|
||||
// Sometimes a JoyStick connected event fires after the app starts even though it was connected before
|
||||
// so it is rejected to avoid doubling the entries.
|
||||
if (_gamepadsIds.Contains(id))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (_gamepadsInstanceIdsMapping.TryAdd(joystickInstanceId, id))
|
||||
{
|
||||
_gamepadsIds.Add(id);
|
||||
lock (_lock)
|
||||
{
|
||||
_gamepadsIds.Add(id);
|
||||
}
|
||||
|
||||
OnGamepadConnected?.Invoke(id);
|
||||
}
|
||||
|
@ -110,7 +138,10 @@ namespace Ryujinx.Input.SDL2
|
|||
OnGamepadDisconnected?.Invoke(id);
|
||||
}
|
||||
|
||||
_gamepadsIds.Clear();
|
||||
lock (_lock)
|
||||
{
|
||||
_gamepadsIds.Clear();
|
||||
}
|
||||
|
||||
SDL2Driver.Instance.Dispose();
|
||||
}
|
||||
|
@ -131,11 +162,6 @@ namespace Ryujinx.Input.SDL2
|
|||
return null;
|
||||
}
|
||||
|
||||
if (id != GenerateGamepadId(joystickIndex))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
IntPtr gamepadHandle = SDL_GameControllerOpen(joystickIndex);
|
||||
|
||||
if (gamepadHandle == IntPtr.Zero)
|
||||
|
|
|
@ -5,6 +5,7 @@ using Ryujinx.HLE.HOS.Services.Hid;
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Runtime.CompilerServices;
|
||||
using CemuHookClient = Ryujinx.Input.Motion.CemuHook.Client;
|
||||
using ControllerType = Ryujinx.Common.Configuration.Hid.ControllerType;
|
||||
|
@ -69,7 +70,20 @@ namespace Ryujinx.Input.HLE
|
|||
private void HandleOnGamepadDisconnected(string obj)
|
||||
{
|
||||
// Force input reload
|
||||
ReloadConfiguration(_inputConfig, _enableKeyboard, _enableMouse);
|
||||
lock (_lock)
|
||||
{
|
||||
// Forcibly disconnect any controllers with this ID.
|
||||
for (int i = 0; i < _controllers.Length; i++)
|
||||
{
|
||||
if (_controllers[i]?.Id == obj)
|
||||
{
|
||||
_controllers[i]?.Dispose();
|
||||
_controllers[i] = null;
|
||||
}
|
||||
}
|
||||
|
||||
ReloadConfiguration(_inputConfig, _enableKeyboard, _enableMouse);
|
||||
}
|
||||
}
|
||||
|
||||
private void HandleOnGamepadConnected(string id)
|
||||
|
@ -106,31 +120,48 @@ namespace Ryujinx.Input.HLE
|
|||
{
|
||||
lock (_lock)
|
||||
{
|
||||
for (int i = 0; i < _controllers.Length; i++)
|
||||
{
|
||||
_controllers[i]?.Dispose();
|
||||
_controllers[i] = null;
|
||||
}
|
||||
NpadController[] oldControllers = _controllers.ToArray();
|
||||
|
||||
List<InputConfig> validInputs = new();
|
||||
|
||||
foreach (InputConfig inputConfigEntry in inputConfig)
|
||||
{
|
||||
NpadController controller = new(_cemuHookClient);
|
||||
NpadController controller;
|
||||
int index = (int)inputConfigEntry.PlayerIndex;
|
||||
|
||||
if (oldControllers[index] != null)
|
||||
{
|
||||
// Try reuse the existing controller.
|
||||
controller = oldControllers[index];
|
||||
oldControllers[index] = null;
|
||||
}
|
||||
else
|
||||
{
|
||||
controller = new(_cemuHookClient);
|
||||
}
|
||||
|
||||
bool isValid = DriverConfigurationUpdate(ref controller, inputConfigEntry);
|
||||
|
||||
if (!isValid)
|
||||
{
|
||||
_controllers[index] = null;
|
||||
controller.Dispose();
|
||||
}
|
||||
else
|
||||
{
|
||||
_controllers[(int)inputConfigEntry.PlayerIndex] = controller;
|
||||
_controllers[index] = controller;
|
||||
validInputs.Add(inputConfigEntry);
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = 0; i < oldControllers.Length; i++)
|
||||
{
|
||||
// Disconnect any controllers that weren't reused by the new configuration.
|
||||
|
||||
oldControllers[i]?.Dispose();
|
||||
oldControllers[i] = null;
|
||||
}
|
||||
|
||||
_inputConfig = inputConfig;
|
||||
_enableKeyboard = enableKeyboard;
|
||||
_enableMouse = enableMouse;
|
||||
|
|
Reference in a new issue