| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656 |
- using System;
- using System.Collections.Concurrent;
- using System.Collections.Generic;
- using System.Runtime.CompilerServices;
- using Ryujinx.Common;
- using Ryujinx.Common.Configuration.Hid;
- using Ryujinx.Common.Logging;
- using Ryujinx.HLE.HOS.Kernel.Threading;
- using Ryujinx.HLE.HOS.Services.Hid.Types;
- using Ryujinx.HLE.HOS.Services.Hid.Types.SharedMemory.Common;
- using Ryujinx.HLE.HOS.Services.Hid.Types.SharedMemory.Npad;
- namespace Ryujinx.HLE.HOS.Services.Hid
- {
- public class NpadDevices : BaseDevice
- {
- private const int NoMatchNotifyFrequencyMs = 2000;
- private int _activeCount;
- private long _lastNotifyTimestamp;
- public const int MaxControllers = 9; // Players 1-8 and Handheld
- private ControllerType[] _configuredTypes;
- private KEvent[] _styleSetUpdateEvents;
- private bool[] _supportedPlayers;
- private static HidVibrationValue _neutralVibrationValue = new HidVibrationValue
- {
- AmplitudeLow = 0f,
- FrequencyLow = 160f,
- AmplitudeHigh = 0f,
- FrequencyHigh = 320f
- };
- internal NpadJoyHoldType JoyHold { get; set; }
- internal bool SixAxisActive = false; // TODO: link to hidserver when implemented
- internal ControllerType SupportedStyleSets { get; set; }
- public Dictionary<PlayerIndex, ConcurrentQueue<(HidVibrationValue, HidVibrationValue)>> RumbleQueues = new Dictionary<PlayerIndex, ConcurrentQueue<(HidVibrationValue, HidVibrationValue)>>();
- public Dictionary<PlayerIndex, (HidVibrationValue, HidVibrationValue)> LastVibrationValues = new Dictionary<PlayerIndex, (HidVibrationValue, HidVibrationValue)>();
- public NpadDevices(Switch device, bool active = true) : base(device, active)
- {
- _configuredTypes = new ControllerType[MaxControllers];
- SupportedStyleSets = ControllerType.Handheld | ControllerType.JoyconPair |
- ControllerType.JoyconLeft | ControllerType.JoyconRight |
- ControllerType.ProController;
- _supportedPlayers = new bool[MaxControllers];
- _supportedPlayers.AsSpan().Fill(true);
- _styleSetUpdateEvents = new KEvent[MaxControllers];
- for (int i = 0; i < _styleSetUpdateEvents.Length; ++i)
- {
- _styleSetUpdateEvents[i] = new KEvent(_device.System.KernelContext);
- }
- _activeCount = 0;
- JoyHold = NpadJoyHoldType.Vertical;
- }
- internal ref KEvent GetStyleSetUpdateEvent(PlayerIndex player)
- {
- return ref _styleSetUpdateEvents[(int)player];
- }
- internal void ClearSupportedPlayers()
- {
- _supportedPlayers.AsSpan().Clear();
- }
- internal void SetSupportedPlayer(PlayerIndex player, bool supported = true)
- {
- _supportedPlayers[(int)player] = supported;
- }
- internal IEnumerable<PlayerIndex> GetSupportedPlayers()
- {
- for (int i = 0; i < _supportedPlayers.Length; ++i)
- {
- if (_supportedPlayers[i])
- {
- yield return (PlayerIndex)i;
- }
- }
- }
- public bool Validate(int playerMin, int playerMax, ControllerType acceptedTypes, out int configuredCount, out PlayerIndex primaryIndex)
- {
- primaryIndex = PlayerIndex.Unknown;
- configuredCount = 0;
- for (int i = 0; i < MaxControllers; ++i)
- {
- ControllerType npad = _configuredTypes[i];
- if (npad == ControllerType.Handheld && _device.System.State.DockedMode)
- {
- continue;
- }
- ControllerType currentType = (ControllerType)_device.Hid.SharedMemory.Npads[i].InternalState.StyleSet;
- if (currentType != ControllerType.None && (npad & acceptedTypes) != 0 && _supportedPlayers[i])
- {
- configuredCount++;
- if (primaryIndex == PlayerIndex.Unknown)
- {
- primaryIndex = (PlayerIndex)i;
- }
- }
- }
- if (configuredCount < playerMin || configuredCount > playerMax || primaryIndex == PlayerIndex.Unknown)
- {
- return false;
- }
- return true;
- }
- public void Configure(params ControllerConfig[] configs)
- {
- _configuredTypes = new ControllerType[MaxControllers];
- for (int i = 0; i < configs.Length; ++i)
- {
- PlayerIndex player = configs[i].Player;
- ControllerType controllerType = configs[i].Type;
- if (player > PlayerIndex.Handheld)
- {
- throw new ArgumentOutOfRangeException("Player must be Player1-8 or Handheld");
- }
- if (controllerType == ControllerType.Handheld)
- {
- player = PlayerIndex.Handheld;
- }
- _configuredTypes[(int)player] = controllerType;
- Logger.Info?.Print(LogClass.Hid, $"Configured Controller {controllerType} to {player}");
- }
- }
- public void Update(IList<GamepadInput> states)
- {
- Remap();
- Span<bool> updated = stackalloc bool[10];
- // Update configured inputs
- for (int i = 0; i < states.Count; ++i)
- {
- GamepadInput state = states[i];
- updated[(int)state.PlayerId] = true;
- UpdateInput(state);
- }
- for (int i = 0; i < updated.Length; i++)
- {
- if (!updated[i])
- {
- UpdateDisconnectedInput((PlayerIndex)i);
- }
- }
- }
- private void Remap()
- {
- // Remap/Init if necessary
- for (int i = 0; i < MaxControllers; ++i)
- {
- ControllerType config = _configuredTypes[i];
- // Remove Handheld config when Docked
- if (config == ControllerType.Handheld && _device.System.State.DockedMode)
- {
- config = ControllerType.None;
- }
- // Auto-remap ProController and JoyconPair
- if (config == ControllerType.JoyconPair && (SupportedStyleSets & ControllerType.JoyconPair) == 0 && (SupportedStyleSets & ControllerType.ProController) != 0)
- {
- config = ControllerType.ProController;
- }
- else if (config == ControllerType.ProController && (SupportedStyleSets & ControllerType.ProController) == 0 && (SupportedStyleSets & ControllerType.JoyconPair) != 0)
- {
- config = ControllerType.JoyconPair;
- }
- // Check StyleSet and PlayerSet
- if ((config & SupportedStyleSets) == 0 || !_supportedPlayers[i])
- {
- config = ControllerType.None;
- }
- SetupNpad((PlayerIndex)i, config);
- }
- if (_activeCount == 0 && PerformanceCounter.ElapsedMilliseconds > _lastNotifyTimestamp + NoMatchNotifyFrequencyMs)
- {
- Logger.Warning?.Print(LogClass.Hid, $"No matching controllers found. Application requests '{SupportedStyleSets}' on '{string.Join(", ", GetSupportedPlayers())}'");
- _lastNotifyTimestamp = PerformanceCounter.ElapsedMilliseconds;
- }
- }
- private void SetupNpad(PlayerIndex player, ControllerType type)
- {
- ref NpadInternalState controller = ref _device.Hid.SharedMemory.Npads[(int)player].InternalState;
- ControllerType oldType = (ControllerType)controller.StyleSet;
- if (oldType == type)
- {
- return; // Already configured
- }
- controller = NpadInternalState.Create(); // Reset it
- if (type == ControllerType.None)
- {
- _styleSetUpdateEvents[(int)player].ReadableEvent.Signal(); // Signal disconnect
- _activeCount--;
- Logger.Info?.Print(LogClass.Hid, $"Disconnected Controller {oldType} from {player}");
- return;
- }
- // TODO: Allow customizing colors at config
- controller.JoyAssignmentMode = NpadJoyAssignmentMode.Dual;
- controller.FullKeyColor.FullKeyBody = (uint)NpadColor.BodyGray;
- controller.FullKeyColor.FullKeyButtons = (uint)NpadColor.ButtonGray;
- controller.JoyColor.LeftBody = (uint)NpadColor.BodyNeonBlue;
- controller.JoyColor.LeftButtons = (uint)NpadColor.ButtonGray;
- controller.JoyColor.RightBody = (uint)NpadColor.BodyNeonRed;
- controller.JoyColor.RightButtons = (uint)NpadColor.ButtonGray;
- controller.SystemProperties = NpadSystemProperties.IsPoweredJoyDual |
- NpadSystemProperties.IsPoweredJoyLeft |
- NpadSystemProperties.IsPoweredJoyRight;
- controller.BatteryLevelJoyDual = NpadBatteryLevel.Percent100;
- controller.BatteryLevelJoyLeft = NpadBatteryLevel.Percent100;
- controller.BatteryLevelJoyRight = NpadBatteryLevel.Percent100;
- switch (type)
- {
- case ControllerType.ProController:
- controller.StyleSet = NpadStyleTag.FullKey;
- controller.DeviceType = DeviceType.FullKey;
- controller.SystemProperties |= NpadSystemProperties.IsAbxyButtonOriented |
- NpadSystemProperties.IsPlusAvailable |
- NpadSystemProperties.IsMinusAvailable;
- controller.AppletFooterUiType = AppletFooterUiType.SwitchProController;
- break;
- case ControllerType.Handheld:
- controller.StyleSet = NpadStyleTag.Handheld;
- controller.DeviceType = DeviceType.HandheldLeft |
- DeviceType.HandheldRight;
- controller.SystemProperties |= NpadSystemProperties.IsAbxyButtonOriented |
- NpadSystemProperties.IsPlusAvailable |
- NpadSystemProperties.IsMinusAvailable;
- controller.AppletFooterUiType = AppletFooterUiType.HandheldJoyConLeftJoyConRight;
- break;
- case ControllerType.JoyconPair:
- controller.StyleSet = NpadStyleTag.JoyDual;
- controller.DeviceType = DeviceType.JoyLeft |
- DeviceType.JoyRight;
- controller.SystemProperties |= NpadSystemProperties.IsAbxyButtonOriented |
- NpadSystemProperties.IsPlusAvailable |
- NpadSystemProperties.IsMinusAvailable;
- controller.AppletFooterUiType = _device.System.State.DockedMode ? AppletFooterUiType.JoyDual : AppletFooterUiType.HandheldJoyConLeftJoyConRight;
- break;
- case ControllerType.JoyconLeft:
- controller.StyleSet = NpadStyleTag.JoyLeft;
- controller.JoyAssignmentMode = NpadJoyAssignmentMode.Single;
- controller.DeviceType = DeviceType.JoyLeft;
- controller.SystemProperties |= NpadSystemProperties.IsSlSrButtonOriented |
- NpadSystemProperties.IsMinusAvailable;
- controller.AppletFooterUiType = _device.System.State.DockedMode ? AppletFooterUiType.JoyDualLeftOnly : AppletFooterUiType.HandheldJoyConLeftOnly;
- break;
- case ControllerType.JoyconRight:
- controller.StyleSet = NpadStyleTag.JoyRight;
- controller.JoyAssignmentMode = NpadJoyAssignmentMode.Single;
- controller.DeviceType = DeviceType.JoyRight;
- controller.SystemProperties |= NpadSystemProperties.IsSlSrButtonOriented |
- NpadSystemProperties.IsPlusAvailable;
- controller.AppletFooterUiType = _device.System.State.DockedMode ? AppletFooterUiType.JoyDualRightOnly : AppletFooterUiType.HandheldJoyConRightOnly;
- break;
- case ControllerType.Pokeball:
- controller.StyleSet = NpadStyleTag.Palma;
- controller.DeviceType = DeviceType.Palma;
- controller.AppletFooterUiType = AppletFooterUiType.None;
- break;
- }
- _styleSetUpdateEvents[(int)player].ReadableEvent.Signal();
- _activeCount++;
- Logger.Info?.Print(LogClass.Hid, $"Connected Controller {type} to {player}");
- }
- private ref RingLifo<NpadCommonState> GetCommonStateLifo(ref NpadInternalState npad)
- {
- switch (npad.StyleSet)
- {
- case NpadStyleTag.FullKey:
- return ref npad.FullKey;
- case NpadStyleTag.Handheld:
- return ref npad.Handheld;
- case NpadStyleTag.JoyDual:
- return ref npad.JoyDual;
- case NpadStyleTag.JoyLeft:
- return ref npad.JoyLeft;
- case NpadStyleTag.JoyRight:
- return ref npad.JoyRight;
- case NpadStyleTag.Palma:
- return ref npad.Palma;
- default:
- return ref npad.SystemExt;
- }
- }
- private void UpdateUnusedInputIfNotEqual(ref RingLifo<NpadCommonState> currentlyUsed, ref RingLifo<NpadCommonState> possiblyUnused)
- {
- bool isEquals;
- unsafe
- {
- var aPointer = Unsafe.AsPointer(ref currentlyUsed);
- var bPointer = Unsafe.AsPointer(ref possiblyUnused);
- isEquals = aPointer == bPointer;
- }
- if (!isEquals)
- {
- NpadCommonState newState = new NpadCommonState();
- WriteNewInputEntry(ref possiblyUnused, ref newState);
- }
- }
- private void WriteNewInputEntry(ref RingLifo<NpadCommonState> lifo, ref NpadCommonState state)
- {
- ref NpadCommonState previousEntry = ref lifo.GetCurrentEntryRef();
- state.SamplingNumber = previousEntry.SamplingNumber + 1;
- lifo.Write(ref state);
- }
- private void UpdateUnusedSixInputIfNotEqual(ref RingLifo<SixAxisSensorState> currentlyUsed, ref RingLifo<SixAxisSensorState> possiblyUnused)
- {
- bool isEquals;
- unsafe
- {
- var aPointer = Unsafe.AsPointer(ref currentlyUsed);
- var bPointer = Unsafe.AsPointer(ref possiblyUnused);
- isEquals = aPointer == bPointer;
- }
- if (!isEquals)
- {
- SixAxisSensorState newState = new SixAxisSensorState();
- WriteNewSixInputEntry(ref possiblyUnused, ref newState);
- }
- }
- private void WriteNewSixInputEntry(ref RingLifo<SixAxisSensorState> lifo, ref SixAxisSensorState state)
- {
- ref SixAxisSensorState previousEntry = ref lifo.GetCurrentEntryRef();
- state.SamplingNumber = previousEntry.SamplingNumber + 1;
- lifo.Write(ref state);
- }
- private void UpdateInput(GamepadInput state)
- {
- if (state.PlayerId == PlayerIndex.Unknown)
- {
- return;
- }
- ref NpadInternalState currentNpad = ref _device.Hid.SharedMemory.Npads[(int)state.PlayerId].InternalState;
- if (currentNpad.StyleSet == NpadStyleTag.None)
- {
- return;
- }
- ref RingLifo<NpadCommonState> lifo = ref GetCommonStateLifo(ref currentNpad);
- NpadCommonState newState = new NpadCommonState
- {
- Buttons = (NpadButton)state.Buttons,
- AnalogStickL = new AnalogStickState
- {
- X = state.LStick.Dx,
- Y = state.LStick.Dy,
- },
- AnalogStickR = new AnalogStickState
- {
- X = state.RStick.Dx,
- Y = state.RStick.Dy,
- }
- };
- newState.Attributes = NpadAttribute.IsConnected;
- switch (currentNpad.StyleSet)
- {
- case NpadStyleTag.Handheld:
- case NpadStyleTag.FullKey:
- newState.Attributes |= NpadAttribute.IsWired;
- break;
- case NpadStyleTag.JoyDual:
- newState.Attributes |= NpadAttribute.IsLeftConnected |
- NpadAttribute.IsRightConnected;
- break;
- case NpadStyleTag.JoyLeft:
- newState.Attributes |= NpadAttribute.IsLeftConnected;
- break;
- case NpadStyleTag.JoyRight:
- newState.Attributes |= NpadAttribute.IsRightConnected;
- break;
- }
- WriteNewInputEntry(ref lifo, ref newState);
- // Mirror data to Default layout just in case
- if (!currentNpad.StyleSet.HasFlag(NpadStyleTag.SystemExt))
- {
- WriteNewInputEntry(ref currentNpad.SystemExt, ref newState);
- }
- UpdateUnusedInputIfNotEqual(ref lifo, ref currentNpad.FullKey);
- UpdateUnusedInputIfNotEqual(ref lifo, ref currentNpad.Handheld);
- UpdateUnusedInputIfNotEqual(ref lifo, ref currentNpad.JoyDual);
- UpdateUnusedInputIfNotEqual(ref lifo, ref currentNpad.JoyLeft);
- UpdateUnusedInputIfNotEqual(ref lifo, ref currentNpad.JoyRight);
- UpdateUnusedInputIfNotEqual(ref lifo, ref currentNpad.Palma);
- }
- private void UpdateDisconnectedInput(PlayerIndex index)
- {
- ref NpadInternalState currentNpad = ref _device.Hid.SharedMemory.Npads[(int)index].InternalState;
- NpadCommonState newState = new NpadCommonState();
- WriteNewInputEntry(ref currentNpad.FullKey, ref newState);
- WriteNewInputEntry(ref currentNpad.Handheld, ref newState);
- WriteNewInputEntry(ref currentNpad.JoyDual, ref newState);
- WriteNewInputEntry(ref currentNpad.JoyLeft, ref newState);
- WriteNewInputEntry(ref currentNpad.JoyRight, ref newState);
- WriteNewInputEntry(ref currentNpad.Palma, ref newState);
- }
- public void UpdateSixAxis(IList<SixAxisInput> states)
- {
- Span<bool> updated = stackalloc bool[10];
- for (int i = 0; i < states.Count; ++i)
- {
- updated[(int)states[i].PlayerId] = true;
- if (SetSixAxisState(states[i]))
- {
- i++;
- if (i >= states.Count)
- {
- return;
- }
- SetSixAxisState(states[i], true);
- }
- }
- for (int i = 0; i < updated.Length; i++)
- {
- if (!updated[i])
- {
- UpdateDisconnectedInputSixAxis((PlayerIndex)i);
- }
- }
- }
- private ref RingLifo<SixAxisSensorState> GetSixAxisSensorLifo(ref NpadInternalState npad, bool isRightPair)
- {
- switch (npad.StyleSet)
- {
- case NpadStyleTag.FullKey:
- return ref npad.FullKeySixAxisSensor;
- case NpadStyleTag.Handheld:
- return ref npad.HandheldSixAxisSensor;
- case NpadStyleTag.JoyDual:
- if (isRightPair)
- {
- return ref npad.JoyDualRightSixAxisSensor;
- }
- else
- {
- return ref npad.JoyDualSixAxisSensor;
- }
- case NpadStyleTag.JoyLeft:
- return ref npad.JoyLeftSixAxisSensor;
- case NpadStyleTag.JoyRight:
- return ref npad.JoyRightSixAxisSensor;
- default:
- throw new NotImplementedException($"{npad.StyleSet}");
- }
- }
- private bool SetSixAxisState(SixAxisInput state, bool isRightPair = false)
- {
- if (state.PlayerId == PlayerIndex.Unknown)
- {
- return false;
- }
- ref NpadInternalState currentNpad = ref _device.Hid.SharedMemory.Npads[(int)state.PlayerId].InternalState;
- if (currentNpad.StyleSet == NpadStyleTag.None)
- {
- return false;
- }
- HidVector accel = new HidVector()
- {
- X = state.Accelerometer.X,
- Y = state.Accelerometer.Y,
- Z = state.Accelerometer.Z
- };
- HidVector gyro = new HidVector()
- {
- X = state.Gyroscope.X,
- Y = state.Gyroscope.Y,
- Z = state.Gyroscope.Z
- };
- HidVector rotation = new HidVector()
- {
- X = state.Rotation.X,
- Y = state.Rotation.Y,
- Z = state.Rotation.Z
- };
- SixAxisSensorState newState = new SixAxisSensorState
- {
- Acceleration = accel,
- AngularVelocity = gyro,
- Angle = rotation,
- Attributes = SixAxisSensorAttribute.IsConnected
- };
- state.Orientation.AsSpan().CopyTo(newState.Direction.ToSpan());
- ref RingLifo<SixAxisSensorState> lifo = ref GetSixAxisSensorLifo(ref currentNpad, isRightPair);
- WriteNewSixInputEntry(ref lifo, ref newState);
- bool needUpdateRight = currentNpad.StyleSet == NpadStyleTag.JoyDual && !isRightPair;
- if (!isRightPair)
- {
- UpdateUnusedSixInputIfNotEqual(ref lifo, ref currentNpad.FullKeySixAxisSensor);
- UpdateUnusedSixInputIfNotEqual(ref lifo, ref currentNpad.HandheldSixAxisSensor);
- UpdateUnusedSixInputIfNotEqual(ref lifo, ref currentNpad.JoyDualSixAxisSensor);
- UpdateUnusedSixInputIfNotEqual(ref lifo, ref currentNpad.JoyLeftSixAxisSensor);
- UpdateUnusedSixInputIfNotEqual(ref lifo, ref currentNpad.JoyRightSixAxisSensor);
- }
- if (!needUpdateRight && !isRightPair)
- {
- SixAxisSensorState emptyState = new SixAxisSensorState();
- emptyState.Attributes = SixAxisSensorAttribute.IsConnected;
- WriteNewSixInputEntry(ref currentNpad.JoyDualRightSixAxisSensor, ref emptyState);
- }
- return needUpdateRight;
- }
- private void UpdateDisconnectedInputSixAxis(PlayerIndex index)
- {
- ref NpadInternalState currentNpad = ref _device.Hid.SharedMemory.Npads[(int)index].InternalState;
- SixAxisSensorState newState = new SixAxisSensorState();
- newState.Attributes = SixAxisSensorAttribute.IsConnected;
- WriteNewSixInputEntry(ref currentNpad.FullKeySixAxisSensor, ref newState);
- WriteNewSixInputEntry(ref currentNpad.HandheldSixAxisSensor, ref newState);
- WriteNewSixInputEntry(ref currentNpad.JoyDualSixAxisSensor, ref newState);
- WriteNewSixInputEntry(ref currentNpad.JoyDualRightSixAxisSensor, ref newState);
- WriteNewSixInputEntry(ref currentNpad.JoyLeftSixAxisSensor, ref newState);
- WriteNewSixInputEntry(ref currentNpad.JoyRightSixAxisSensor, ref newState);
- }
- public void UpdateRumbleQueue(PlayerIndex index, Dictionary<byte, HidVibrationValue> dualVibrationValues)
- {
- if (RumbleQueues.TryGetValue(index, out ConcurrentQueue<(HidVibrationValue, HidVibrationValue)> currentQueue))
- {
- if (!dualVibrationValues.TryGetValue(0, out HidVibrationValue leftVibrationValue))
- {
- leftVibrationValue = _neutralVibrationValue;
- }
- if (!dualVibrationValues.TryGetValue(1, out HidVibrationValue rightVibrationValue))
- {
- rightVibrationValue = _neutralVibrationValue;
- }
- if (!LastVibrationValues.TryGetValue(index, out (HidVibrationValue, HidVibrationValue) dualVibrationValue) || !leftVibrationValue.Equals(dualVibrationValue.Item1) || !rightVibrationValue.Equals(dualVibrationValue.Item2))
- {
- currentQueue.Enqueue((leftVibrationValue, rightVibrationValue));
- LastVibrationValues[index] = (leftVibrationValue, rightVibrationValue);
- }
- }
- }
- public HidVibrationValue GetLastVibrationValue(PlayerIndex index, byte position)
- {
- if (!LastVibrationValues.TryGetValue(index, out (HidVibrationValue, HidVibrationValue) dualVibrationValue))
- {
- return _neutralVibrationValue;
- }
- return (position == 0) ? dualVibrationValue.Item1 : dualVibrationValue.Item2;
- }
- public ConcurrentQueue<(HidVibrationValue, HidVibrationValue)> GetRumbleQueue(PlayerIndex index)
- {
- if (!RumbleQueues.TryGetValue(index, out ConcurrentQueue<(HidVibrationValue, HidVibrationValue)> rumbleQueue))
- {
- rumbleQueue = new ConcurrentQueue<(HidVibrationValue, HidVibrationValue)>();
- _device.Hid.Npads.RumbleQueues[index] = rumbleQueue;
- }
- return rumbleQueue;
- }
- }
- }
|