aboutsummaryrefslogtreecommitdiff
path: root/Ryujinx.HLE/Ui/Input/NpadReader.cs
blob: a0d78d03afbfd7caa317b19a98280cad080d1d5f (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
using Ryujinx.HLE.HOS.Services.Hid.Types.SharedMemory.Common;
using Ryujinx.HLE.HOS.Services.Hid.Types.SharedMemory.Npad;

namespace Ryujinx.HLE.Ui.Input
{
    /// <summary>
    /// Class that converts Hid entries for the Npad into pressed / released events.
    /// </summary>
    class NpadReader
    {
        private readonly Switch _device;
        private NpadCommonState[] _lastStates;

        public event NpadButtonHandler NpadButtonUpEvent;
        public event NpadButtonHandler NpadButtonDownEvent;

        public NpadReader(Switch device)
        {
            _device = device;
            _lastStates = new NpadCommonState[_device.Hid.SharedMemory.Npads.Length];
        }

        public NpadButton GetCurrentButtonsOfNpad(int npadIndex)
        {
            return _lastStates[npadIndex].Buttons;
        }

        public NpadButton GetCurrentButtonsOfAllNpads()
        {
            NpadButton buttons = 0;

            foreach (var state in _lastStates)
            {
                buttons |= state.Buttons;
            }

            return buttons;
        }

        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;
            }
        }

        public void Update(bool supressEvents=false)
        {
            ref var npads = ref _device.Hid.SharedMemory.Npads;

            // Process each input individually.
            for (int npadIndex = 0; npadIndex < npads.Length; npadIndex++)
            {
                UpdateNpad(npadIndex, supressEvents);
            }
        }

        private void UpdateNpad(int npadIndex, bool supressEvents)
        {
            const int MaxEntries = 1024;

            ref var npadState = ref _device.Hid.SharedMemory.Npads[npadIndex];
            ref var lastEntry = ref _lastStates[npadIndex];

            var fullKeyEntries = GetCommonStateLifo(ref npadState.InternalState).ReadEntries(MaxEntries);

            int firstEntryNum;

            // Scan the LIFO for the first entry that is newer that what's already processed.
            for (firstEntryNum = fullKeyEntries.Length - 1; firstEntryNum >= 0 && fullKeyEntries[firstEntryNum].Object.SamplingNumber <= lastEntry.SamplingNumber; firstEntryNum--) ;

            if (firstEntryNum == -1)
            {
                return;
            }

            for (; firstEntryNum >= 0; firstEntryNum--)
            {
                var entry = fullKeyEntries[firstEntryNum];

                // The interval of valid entries should be contiguous.
                if (entry.SamplingNumber < lastEntry.SamplingNumber)
                {
                    break;
                }

                if (!supressEvents)
                {
                    ProcessNpadButtons(npadIndex, entry.Object.Buttons);
                }

                lastEntry = entry.Object;
            }
        }

        private void ProcessNpadButtons(int npadIndex, NpadButton buttons)
        {
            NpadButton lastButtons = _lastStates[npadIndex].Buttons;

            for (ulong buttonMask = 1; buttonMask != 0; buttonMask <<= 1)
            {
                NpadButton currentButton = (NpadButton)buttonMask & buttons;
                NpadButton lastButton = (NpadButton)buttonMask & lastButtons;

                if (lastButton != 0)
                {
                    if (currentButton == 0)
                    {
                        NpadButtonUpEvent?.Invoke(npadIndex, lastButton);
                    }
                }
                else
                {
                    if (currentButton != 0)
                    {
                        NpadButtonDownEvent?.Invoke(npadIndex, currentButton);
                    }
                }
            }
        }
    }
}