Improved assignment logic in AutoAssignController.cs

This commit is contained in:
uncavo-hdmi 2025-02-12 19:44:16 +01:00
parent 62dfbb5dcb
commit 8cc74dab08

View File

@ -1,4 +1,5 @@
using Ryujinx.Ava.UI.ViewModels;
using Ryujinx.Ava.UI.Models.Input;
using Ryujinx.Ava.UI.ViewModels;
using Ryujinx.Ava.Utilities.Configuration;
using Ryujinx.Common.Configuration.Hid;
using Ryujinx.Common.Configuration.Hid.Controller;
@ -61,79 +62,159 @@ namespace Ryujinx.Ava.Input
public void RefreshControllers()
{
if (!_configurationState.Hid.EnableAutoAssign) return;
// Get every controller config and update the configuration state
List<IGamepad> controllers = _inputManager.GamepadDriver.GetGamepads().ToList();
List<InputConfig> oldConfig = _configurationState.Hid.InputConfig.Value.Where(x => x != null).ToList();
(List<InputConfig> newConfig, bool hasNewControllersConnected) = GetOrderedConfig(controllers, oldConfig);
(List<InputConfig> newConfig, bool hasNewControllersConnected) = GetOrderedConfig(controllers, oldConfig);
_viewModel.AppHost?.NpadManager.ReloadConfiguration(newConfig, _configurationState.Hid.EnableKeyboard, _configurationState.Hid.EnableMouse);
// update the configuration state only if there are new controllers connected
if(hasNewControllersConnected)
if (hasNewControllersConnected)
{
_configurationState.Hid.InputConfig.Value = newConfig;
ConfigurationState.Instance.ToFileFormat().SaveConfig(Program.ConfigurationPath);
}
else
{
// we must switch the order of the controllers in oldConfig to match the new order
newConfig = ReorderControllers(newConfig, oldConfig);
}
_configurationState.Hid.InputConfig.Value = newConfig;
ConfigurationUpdated?.Invoke();
}
/// <summary>
/// Ensures that the order of controllers remains the same unless a new controller is connected.
/// </summary>
private List<InputConfig> ReorderControllers(List<InputConfig> newConfig, List<InputConfig> oldConfig)
{
List<InputConfig> reorderedConfig = oldConfig.Select(config => new GamepadInputConfig(config).GetConfig()).ToList();
foreach (var config in newConfig)
{
InputConfig substitute = reorderedConfig.FirstOrDefault(x => x.Id == config.Id);
InputConfig toBeReplaced = reorderedConfig.FirstOrDefault(x => x.PlayerIndex == config.PlayerIndex);
if (substitute == null || toBeReplaced == null || substitute.PlayerIndex == toBeReplaced.PlayerIndex) continue;
(substitute.PlayerIndex, toBeReplaced.PlayerIndex) = (toBeReplaced.PlayerIndex, substitute.PlayerIndex);
}
return reorderedConfig;
}
/// <summary>
/// Orders controllers, preserving existing mappings while assigning new controllers to available slots.
/// </summary>
private (List<InputConfig>, bool) GetOrderedConfig(List<IGamepad> controllers, List<InputConfig> oldConfig)
{
Dictionary<string, InputConfig> oldConfigMap = oldConfig.Where(c => c?.Id != null).ToDictionary(x => x.Id);
{
Dictionary<string, InputConfig> oldConfigMap = oldConfig.Where(c => c?.Id != null)
.ToDictionary(x => x.Id);
Dictionary<int, InputConfig> playerIndexMap = new();
HashSet<int> usedIndices = new();
int recognizedControllersCount = 0;
// Assign controllers that have an old config
List<IGamepad> remainingControllers = controllers.Where(c => c?.Id != null).ToList();
foreach (var controller in remainingControllers.ToList())
// Assign controllers that have an existing configuration
AssignExistingControllers(remainingControllers, oldConfigMap, playerIndexMap, usedIndices, ref recognizedControllersCount);
// Assign new controllers
AssignNewControllers(remainingControllers, playerIndexMap, usedIndices);
List<InputConfig> orderedConfigs = playerIndexMap.OrderBy(x => x.Key).Select(x => x.Value).ToList();
// Sequentially update PlayerIndex and LED colors
UpdatePlayerIndicesAndLEDs(orderedConfigs);
bool hasNewControllersConnected = controllers.Count > recognizedControllersCount;
return (orderedConfigs, hasNewControllersConnected);
}
/// <summary>
/// Assigns controllers with existing configurations while keeping their PlayerIndex if available.
/// </summary>
private void AssignExistingControllers(
List<IGamepad> controllers,
Dictionary<string, InputConfig> oldConfigMap,
Dictionary<int, InputConfig> playerIndexMap,
HashSet<int> usedIndices,
ref int recognizedControllersCount)
{
foreach (var controller in controllers.ToList())
{
if (oldConfigMap.TryGetValue(controller.Id, out InputConfig existingConfig))
{
int desiredIndex = (int)existingConfig.PlayerIndex;
// Check if the desired index is valid and available
// Ensure the index is valid and available
if (desiredIndex < 0 || desiredIndex >= MaxControllers || usedIndices.Contains(desiredIndex))
{
// Find the first available index
desiredIndex = Enumerable.Range(0, MaxControllers).First(i => !usedIndices.Contains(i));
desiredIndex = GetFirstAvailableIndex(usedIndices);
}
existingConfig.PlayerIndex = (PlayerIndex)desiredIndex;
InputConfig config = new GamepadInputConfig(existingConfig).GetConfig();
config.PlayerIndex = (PlayerIndex)desiredIndex;
usedIndices.Add(desiredIndex);
playerIndexMap[desiredIndex] = existingConfig;
playerIndexMap[desiredIndex] = config;
recognizedControllersCount++;
remainingControllers.Remove(controller);
controllers.Remove(controller);
}
}
}
// Assign remaining (new) controllers
foreach (var controller in remainingControllers.OrderBy(c => c.Id))
/// <summary>
/// Assigns new controllers to the first available PlayerIndex.
/// </summary>
private void AssignNewControllers(
List<IGamepad> controllers,
Dictionary<int, InputConfig> playerIndexMap,
HashSet<int> usedIndices)
{
foreach (var controller in controllers)
{
InputConfig config = CreateConfigFromController(controller);
int freeIndex = Enumerable.Range(0, MaxControllers).First(i => !usedIndices.Contains(i));
int freeIndex = GetFirstAvailableIndex(usedIndices);
config.PlayerIndex = (PlayerIndex)freeIndex;
usedIndices.Add(freeIndex);
playerIndexMap[freeIndex] = config;
}
}
List<InputConfig> orderedConfigs = playerIndexMap.OrderBy(x => x.Key).Select(x => x.Value).ToList();
// Sequential reassignment of PlayerIndex and LED colors
int index = 0;
foreach (var config in orderedConfigs)
/// <summary>
/// Finds the first available PlayerIndex that isn't in use.
/// </summary>
private int GetFirstAvailableIndex(HashSet<int> usedIndices)
{
for (int i = 0; i < MaxControllers; i++)
{
config.PlayerIndex = (PlayerIndex)index;
if (config is StandardControllerInputConfig standardConfig)
{
Logger.Warning?.Print(LogClass.Application, $"Setting color for Player{index+1}");
standardConfig.Led = new LedConfigController { EnableLed = true, LedColor = _playerColors[index] };
}
index++;
if (!usedIndices.Contains(i)) return i;
}
return -1; // Should not happen unless MaxControllers is exceeded
}
bool hasNewControllersConnected = (controllers.Count > recognizedControllersCount);
return (orderedConfigs, hasNewControllersConnected);
/// <summary>
/// Updates PlayerIndex and LED configurations sequentially.
/// </summary>
private void UpdatePlayerIndicesAndLEDs(List<InputConfig> orderedConfigs)
{
for (int index = 0; index < orderedConfigs.Count; index++)
{
orderedConfigs[index].PlayerIndex = (PlayerIndex)index;
if (orderedConfigs[index] is StandardControllerInputConfig standardConfig)
{
Logger.Warning?.Print(LogClass.Application, $"Setting color for Player{index + 1}");
standardConfig.Led = new LedConfigController
{
EnableLed = true,
LedColor = _playerColors[index]
};
}
}
}
private static InputConfig CreateConfigFromController(IGamepad controller)