File size: 20,867 Bytes
18a519f
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
using System;
using Unity.Collections.LowLevel.Unsafe;
using UnityEngine.InputSystem.LowLevel;
using UnityEngine.InputSystem.Utilities;

namespace UnityEngine.InputSystem
{
    internal partial class InputManager
    {
        // Indices correspond with those in m_Devices.
        internal StateChangeMonitorsForDevice[] m_StateChangeMonitors;
        private InlinedArray<StateChangeMonitorTimeout> m_StateChangeMonitorTimeouts;

        ////TODO: support combining monitors for bitfields
        public void AddStateChangeMonitor(InputControl control, IInputStateChangeMonitor monitor, long monitorIndex, uint groupIndex)
        {
            Debug.Assert(m_DevicesCount > 0);

            var device = control.device;
            var deviceIndex = device.m_DeviceIndex;
            Debug.Assert(deviceIndex != InputDevice.kInvalidDeviceIndex);

            // Allocate/reallocate monitor arrays, if necessary.
            // We lazy-sync it to array of devices.
            if (m_StateChangeMonitors == null)
                m_StateChangeMonitors = new StateChangeMonitorsForDevice[m_DevicesCount];
            else if (m_StateChangeMonitors.Length <= deviceIndex)
                Array.Resize(ref m_StateChangeMonitors, m_DevicesCount);

            // If we have removed monitors
            if (!isProcessingEvents && m_StateChangeMonitors[deviceIndex].needToCompactArrays)
                m_StateChangeMonitors[deviceIndex].CompactArrays();

            // Add record.
            m_StateChangeMonitors[deviceIndex].Add(control, monitor, monitorIndex, groupIndex);
        }

        private void RemoveStateChangeMonitors(InputDevice device)
        {
            if (m_StateChangeMonitors == null)
                return;

            var deviceIndex = device.m_DeviceIndex;
            Debug.Assert(deviceIndex != InputDevice.kInvalidDeviceIndex);

            if (deviceIndex >= m_StateChangeMonitors.Length)
                return;

            m_StateChangeMonitors[deviceIndex].Clear();

            // Clear timeouts pending on any control on the device.
            for (var i = 0; i < m_StateChangeMonitorTimeouts.length; ++i)
                if (m_StateChangeMonitorTimeouts[i].control?.device == device)
                    m_StateChangeMonitorTimeouts[i] = default;
        }

        public void RemoveStateChangeMonitor(InputControl control, IInputStateChangeMonitor monitor, long monitorIndex)
        {
            if (m_StateChangeMonitors == null)
                return;

            var device = control.device;
            var deviceIndex = device.m_DeviceIndex;

            // Ignore if device has already been removed.
            if (deviceIndex == InputDevice.kInvalidDeviceIndex)
                return;

            // Ignore if there are no state monitors set up for the device.
            if (deviceIndex >= m_StateChangeMonitors.Length)
                return;

            m_StateChangeMonitors[deviceIndex].Remove(monitor, monitorIndex, isProcessingEvents);

            // Remove pending timeouts on the monitor.
            for (var i = 0; i < m_StateChangeMonitorTimeouts.length; ++i)
                if (m_StateChangeMonitorTimeouts[i].monitor == monitor &&
                    m_StateChangeMonitorTimeouts[i].monitorIndex == monitorIndex)
                    m_StateChangeMonitorTimeouts[i] = default;
        }

        public void AddStateChangeMonitorTimeout(InputControl control, IInputStateChangeMonitor monitor, double time, long monitorIndex, int timerIndex)
        {
            m_StateChangeMonitorTimeouts.Append(
                new StateChangeMonitorTimeout
                {
                    control = control,
                    time = time,
                    monitor = monitor,
                    monitorIndex = monitorIndex,
                    timerIndex = timerIndex,
                });
        }

        public void RemoveStateChangeMonitorTimeout(IInputStateChangeMonitor monitor, long monitorIndex, int timerIndex)
        {
            var timeoutCount = m_StateChangeMonitorTimeouts.length;
            for (var i = 0; i < timeoutCount; ++i)
            {
                ////REVIEW: can we avoid the repeated array lookups without copying the struct out?
                if (ReferenceEquals(m_StateChangeMonitorTimeouts[i].monitor, monitor)
                    && m_StateChangeMonitorTimeouts[i].monitorIndex == monitorIndex
                    && m_StateChangeMonitorTimeouts[i].timerIndex == timerIndex)
                {
                    m_StateChangeMonitorTimeouts[i] = default;
                    break;
                }
            }
        }

        private void SortStateChangeMonitorsIfNecessary(int deviceIndex)
        {
            if (m_StateChangeMonitors != null && deviceIndex < m_StateChangeMonitors.Length &&
                m_StateChangeMonitors[deviceIndex].needToUpdateOrderingOfMonitors)
                m_StateChangeMonitors[deviceIndex].SortMonitorsByIndex();
        }

        public void SignalStateChangeMonitor(InputControl control, IInputStateChangeMonitor monitor)
        {
            var device = control.device;
            var deviceIndex = device.m_DeviceIndex;

            ref var monitorsForDevice = ref m_StateChangeMonitors[deviceIndex];
            for (var i = 0; i < monitorsForDevice.signalled.length; ++i)
            {
                SortStateChangeMonitorsIfNecessary(i);

                ref var listener = ref monitorsForDevice.listeners[i];
                if (listener.control == control && listener.monitor == monitor)
                    monitorsForDevice.signalled.SetBit(i);
            }
        }

        public unsafe void FireStateChangeNotifications()
        {
            var time = m_Runtime.currentTime;
            var count = Math.Min(m_StateChangeMonitors.LengthSafe(), m_DevicesCount);
            for (var i = 0; i < count; ++i)
                FireStateChangeNotifications(i, time, null);
        }

        // Record for a timeout installed on a state change monitor.
        private struct StateChangeMonitorTimeout
        {
            public InputControl control;
            public double time;
            public IInputStateChangeMonitor monitor;
            public long monitorIndex;
            public int timerIndex;
        }

        // Maps a single control to an action interested in the control. If
        // multiple actions are interested in the same control, we will end up
        // processing the control repeatedly but we assume this is the exception
        // and so optimize for the case where there's only one action going to
        // a control.
        //
        // Split into two structures to keep data needed only when there is an
        // actual value change out of the data we need for doing the scanning.
        internal struct StateChangeMonitorListener
        {
            public InputControl control;
            public IInputStateChangeMonitor monitor;
            public long monitorIndex;
            public uint groupIndex;
        }

        internal struct StateChangeMonitorsForDevice
        {
            public MemoryHelpers.BitRegion[] memoryRegions;
            public StateChangeMonitorListener[] listeners;
            public DynamicBitfield signalled;
            public bool needToUpdateOrderingOfMonitors;
            public bool needToCompactArrays;

            public int count => signalled.length;

            public void Add(InputControl control, IInputStateChangeMonitor monitor, long monitorIndex, uint groupIndex)
            {
                // NOTE: This method must only *append* to arrays. This way we can safely add data while traversing
                //       the arrays in FireStateChangeNotifications. Note that appending *may* mean that the arrays
                //       are switched to larger arrays.

                // Record listener.
                var listenerCount = signalled.length;
                ArrayHelpers.AppendWithCapacity(ref listeners, ref listenerCount,
                    new StateChangeMonitorListener
                    { monitor = monitor, monitorIndex = monitorIndex, groupIndex = groupIndex, control = control });

                // Record memory region.
                ref var controlStateBlock = ref control.m_StateBlock;
                var memoryRegionCount = signalled.length;
                ArrayHelpers.AppendWithCapacity(ref memoryRegions, ref memoryRegionCount,
                    new MemoryHelpers.BitRegion(controlStateBlock.byteOffset - control.device.stateBlock.byteOffset,
                        controlStateBlock.bitOffset, controlStateBlock.sizeInBits));

                signalled.SetLength(signalled.length + 1);

                needToUpdateOrderingOfMonitors = true;
            }

            public void Remove(IInputStateChangeMonitor monitor, long monitorIndex, bool deferRemoval)
            {
                if (listeners == null)
                    return;

                for (var i = 0; i < signalled.length; ++i)
                    if (ReferenceEquals(listeners[i].monitor, monitor) && listeners[i].monitorIndex == monitorIndex)
                    {
                        if (deferRemoval)
                        {
                            listeners[i] = default;
                            memoryRegions[i] = default;
                            signalled.ClearBit(i);
                            needToCompactArrays = true;
                        }
                        else
                        {
                            RemoveAt(i);
                        }

                        break;
                    }
            }

            public void Clear()
            {
                // We don't actually release memory we've potentially allocated but rather just reset
                // our count to zero.
                listeners.Clear(count);
                signalled.SetLength(0);

                needToCompactArrays = false;
            }

            public void CompactArrays()
            {
                for (var i = count - 1; i >= 0; --i)
                {
                    var memoryRegion = memoryRegions[i];
                    if (memoryRegion.sizeInBits != 0)
                        continue;

                    RemoveAt(i);
                }

                needToCompactArrays = false;
            }

            private void RemoveAt(int i)
            {
                var numListeners = count;
                var numMemoryRegions = count;
                listeners.EraseAtWithCapacity(ref numListeners, i);
                memoryRegions.EraseAtWithCapacity(ref numMemoryRegions, i);
                signalled.SetLength(count - 1);
            }

            public void SortMonitorsByIndex()
            {
                // Insertion sort.
                for (var i = 1; i < signalled.length; ++i)
                {
                    for (var j = i; j > 0; --j)
                    {
                        // Sort by complexities only to keep the sort stable
                        // i.e. don't reverse the order of controls which have the same complexity
                        var firstComplexity = InputActionState.GetComplexityFromMonitorIndex(listeners[j - 1].monitorIndex);
                        var secondComplexity = InputActionState.GetComplexityFromMonitorIndex(listeners[j].monitorIndex);
                        if (firstComplexity >= secondComplexity)
                            break;

                        listeners.SwapElements(j, j - 1);
                        memoryRegions.SwapElements(j, j - 1);

                        // We can ignore the `signalled` array here as we call this method only
                        // when all monitors are in non-signalled state.
                    }
                }

                needToUpdateOrderingOfMonitors = false;
            }
        }

        // NOTE: 'newState' can be a subset of the full state stored at 'oldState'. In this case,
        //       'newStateOffsetInBytes' must give the offset into the full state and 'newStateSizeInBytes' must
        //       give the size of memory slice to be updated.
        private unsafe bool ProcessStateChangeMonitors(int deviceIndex, void* newStateFromEvent, void* oldStateOfDevice, uint newStateSizeInBytes, uint newStateOffsetInBytes)
        {
            if (m_StateChangeMonitors == null)
                return false;

            // We resize the monitor arrays only when someone adds to them so they
            // may be out of sync with the size of m_Devices.
            if (deviceIndex >= m_StateChangeMonitors.Length)
                return false;

            var memoryRegions = m_StateChangeMonitors[deviceIndex].memoryRegions;
            if (memoryRegions == null)
                return false; // No one cares about state changes on this device.

            var numMonitors = m_StateChangeMonitors[deviceIndex].count;
            var signalled = false;
            var signals = m_StateChangeMonitors[deviceIndex].signalled;
            var haveChangedSignalsBitfield = false;

            // For every memory region that overlaps what we got in the event, compare memory contents
            // between the old device state and what's in the event. If the contents different, the
            // respective state monitor signals.
            var newEventMemoryRegion = new MemoryHelpers.BitRegion(newStateOffsetInBytes, 0, newStateSizeInBytes * 8);
            for (var i = 0; i < numMonitors; ++i)
            {
                var memoryRegion = memoryRegions[i];

                // Check if the monitor record has been wiped in the meantime. If so, remove it.
                if (memoryRegion.sizeInBits == 0)
                {
                    ////REVIEW: Do we really care? It is nice that it's predictable this way but hardly a hard requirement
                    // NOTE: We're using EraseAtWithCapacity here rather than EraseAtByMovingTail to preserve
                    //       order which makes the order of callbacks somewhat more predictable.

                    var listenerCount = numMonitors;
                    var memoryRegionCount = numMonitors;
                    m_StateChangeMonitors[deviceIndex].listeners.EraseAtWithCapacity(ref listenerCount, i);
                    memoryRegions.EraseAtWithCapacity(ref memoryRegionCount, i);
                    signals.SetLength(numMonitors - 1);
                    haveChangedSignalsBitfield = true;
                    --numMonitors;
                    --i;
                    continue;
                }

                var overlap = newEventMemoryRegion.Overlap(memoryRegion);
                if (overlap.isEmpty || MemoryHelpers.Compare(oldStateOfDevice, (byte*)newStateFromEvent - newStateOffsetInBytes, overlap))
                    continue;

                signals.SetBit(i);
                haveChangedSignalsBitfield = true;
                signalled = true;
            }

            if (haveChangedSignalsBitfield)
                m_StateChangeMonitors[deviceIndex].signalled = signals;

            m_StateChangeMonitors[deviceIndex].needToCompactArrays = false;

            return signalled;
        }

        internal unsafe void FireStateChangeNotifications(int deviceIndex, double internalTime, InputEvent* eventPtr)
        {
            Debug.Assert(m_StateChangeMonitors != null);
            Debug.Assert(m_StateChangeMonitors.Length > deviceIndex);

            // NOTE: This method must be safe for mutating the state change monitor arrays from *within*
            //       NotifyControlStateChanged()! This includes all monitors for the device being wiped
            //       completely or arbitrary additions and removals having occurred.

            ref var signals = ref m_StateChangeMonitors[deviceIndex].signalled;
            ref var listeners = ref m_StateChangeMonitors[deviceIndex].listeners;
            var time = internalTime - InputRuntime.s_CurrentTimeOffsetToRealtimeSinceStartup;

            // If we don't have an event, gives us as dummy, invalid instance.
            // What matters is that InputEventPtr.valid is false for these.
            var tempEvent = new InputEvent(new FourCC('F', 'A', 'K', 'E'), InputEvent.kBaseEventSize, -1, internalTime);
            if (eventPtr == null)
                eventPtr = (InputEvent*)UnsafeUtility.AddressOf(ref tempEvent);

            // Call IStateChangeMonitor.NotifyControlStateChange for every monitor that is in
            // signalled state.
            eventPtr->handled = false;
            for (var i = 0; i < signals.length; ++i)
            {
                if (!signals.TestBit(i))
                    continue;

                var listener = listeners[i];
                try
                {
                    listener.monitor.NotifyControlStateChanged(listener.control, time, eventPtr,
                        listener.monitorIndex);
                }
                catch (Exception exception)
                {
                    Debug.LogError(
                        $"Exception '{exception.GetType().Name}' thrown from state change monitor '{listener.monitor.GetType().Name}' on '{listener.control}'");
                    Debug.LogException(exception);
                }

                // If the monitor signalled that it has processed the state change, reset all signalled
                // state monitors in the same group. This is what causes "SHIFT+B" to prevent "B" from
                // also triggering.
                if (eventPtr->handled)
                {
                    var groupIndex = listeners[i].groupIndex;
                    for (var n = i + 1; n < signals.length; ++n)
                    {
                        // NOTE: We restrict the preemption logic here to a single monitor. Otherwise,
                        //       we will have to require that group indices are stable *between*
                        //       monitors. Two separate InputActionStates, for example, would have to
                        //       agree on group indices that valid *between* the two states or we end
                        //       up preempting unrelated inputs.
                        //
                        //       Note that this implies there there is *NO* preemption between singleton
                        //       InputActions. This isn't intuitive.
                        if (listeners[n].groupIndex == groupIndex && listeners[n].monitor == listener.monitor)
                            signals.ClearBit(n);
                    }

                    // Need to reset it back to false as we may have more signalled state monitors that
                    // aren't in the same group (i.e. have independent inputs).
                    eventPtr->handled = false;
                }

                signals.ClearBit(i);
            }
        }

        private void ProcessStateChangeMonitorTimeouts()
        {
            if (m_StateChangeMonitorTimeouts.length == 0)
                return;

            // Go through the list and both trigger expired timers and remove any irrelevant
            // ones by compacting the array.
            // NOTE: We do not actually release any memory we may have allocated.
            var currentTime = m_Runtime.currentTime - InputRuntime.s_CurrentTimeOffsetToRealtimeSinceStartup;
            var remainingTimeoutCount = 0;
            for (var i = 0; i < m_StateChangeMonitorTimeouts.length; ++i)
            {
                // If we have reset this entry in RemoveStateChangeMonitorTimeouts(),
                // skip over it and let compaction get rid of it.
                if (m_StateChangeMonitorTimeouts[i].control == null)
                    continue;

                var timerExpirationTime = m_StateChangeMonitorTimeouts[i].time;
                if (timerExpirationTime <= currentTime)
                {
                    var timeout = m_StateChangeMonitorTimeouts[i];
                    timeout.monitor.NotifyTimerExpired(timeout.control,
                        currentTime, timeout.monitorIndex, timeout.timerIndex);

                    // Compaction will get rid of the entry.
                }
                else
                {
                    // Rather than repeatedly calling RemoveAt() and thus potentially
                    // moving the same data over and over again, we compact the array
                    // on the fly and move entries in the array down as needed.
                    if (i != remainingTimeoutCount)
                        m_StateChangeMonitorTimeouts[remainingTimeoutCount] = m_StateChangeMonitorTimeouts[i];
                    ++remainingTimeoutCount;
                }
            }

            m_StateChangeMonitorTimeouts.SetLength(remainingTimeoutCount);
        }
    }
}