A game about forced loneliness, made by TACStudios
1using System;
2using System.Collections;
3using System.Collections.Generic;
4using System.Runtime.CompilerServices;
5using System.Text;
6using Unity.Collections;
7using Unity.Collections.LowLevel.Unsafe;
8using UnityEngine.InputSystem.Controls;
9using UnityEngine.InputSystem.LowLevel;
10using UnityEngine.InputSystem.Utilities;
11
12////REVIEW: some of the stuff here is really low-level; should we move it into a separate static class inside of .LowLevel?
13
14namespace UnityEngine.InputSystem
15{
16 /// <summary>
17 /// Various extension methods for <see cref="InputControl"/>. Mostly low-level routines.
18 /// </summary>
19 public static class InputControlExtensions
20 {
21 /// <summary>
22 /// Find a control of the given type in control hierarchy of <paramref name="control"/>.
23 /// </summary>
24 /// <param name="control">Control whose parents to inspect.</param>
25 /// <typeparam name="TControl">Type of control to look for. Actual control type can be
26 /// subtype of this.</typeparam>
27 /// <remarks>The found control of type <typeparamref name="TControl"/> which may be either
28 /// <paramref name="control"/> itself or one of its parents. If no such control was found,
29 /// returns <c>null</c>.</remarks>
30 /// <exception cref="ArgumentNullException"><paramref name="control"/> is <c>null</c>.</exception>
31 public static TControl FindInParentChain<TControl>(this InputControl control)
32 where TControl : InputControl
33 {
34 if (control == null)
35 throw new ArgumentNullException(nameof(control));
36
37 for (var parent = control; parent != null; parent = parent.parent)
38 if (parent is TControl parentOfType)
39 return parentOfType;
40
41 return null;
42 }
43
44 ////REVIEW: This ist too high up in the class hierarchy; can be applied to any kind of control without it being readily apparent what exactly it means
45 /// <summary>
46 /// Check whether the given control is considered pressed according to the button press threshold.
47 /// </summary>
48 /// <param name="control">Control to check.</param>
49 /// <param name="buttonPressPoint">Optional custom button press point. If not supplied, <see cref="InputSettings.defaultButtonPressPoint"/>
50 /// is used.</param>
51 /// <returns>True if the actuation of the given control is high enough for it to be considered pressed.</returns>
52 /// <exception cref="ArgumentNullException"><paramref name="control"/> is <c>null</c>.</exception>
53 /// <remarks>
54 /// This method checks the actuation level of the control as <see cref="IsActuated"/> does. For <see cref="Controls.ButtonControl"/>s
55 /// and other <c>float</c> value controls, this will effectively check whether the float value of the control exceeds the button
56 /// point threshold. Note that if the control is an axis that can be both positive and negative, the press threshold works in
57 /// both directions, i.e. it can be crossed both in the positive direction and in the negative direction.
58 /// </remarks>
59 /// <seealso cref="IsActuated"/>
60 /// <seealso cref="InputSettings.defaultButtonPressPoint"/>
61 /// <seealso cref="InputSystem.settings"/>
62 public static bool IsPressed(this InputControl control, float buttonPressPoint = 0)
63 {
64 if (control == null)
65 throw new ArgumentNullException(nameof(control));
66 if (Mathf.Approximately(0, buttonPressPoint))
67 {
68 if (control is ButtonControl button)
69 buttonPressPoint = button.pressPointOrDefault;
70 else
71 buttonPressPoint = ButtonControl.s_GlobalDefaultButtonPressPoint;
72 }
73 return control.IsActuated(buttonPressPoint);
74 }
75
76 /// <summary>
77 /// Return true if the given control is actuated.
78 /// </summary>
79 /// <param name="control"></param>
80 /// <param name="threshold">Magnitude threshold that the control must match or exceed to be considered actuated.
81 /// An exception to this is the default value of zero. If threshold is zero, the control must have a magnitude
82 /// greater than zero.</param>
83 /// <returns></returns>
84 /// <remarks>
85 /// Actuation is defined as a control having a magnitude (<see cref="InputControl.EvaluateMagnitude()"/>
86 /// greater than zero or, if the control does not support magnitudes, has been moved from its default
87 /// state.
88 ///
89 /// In practice, this means that when actuated, a control will produce a value other than its default
90 /// value.
91 /// </remarks>
92 public static bool IsActuated(this InputControl control, float threshold = 0)
93 {
94 // First perform cheap memory check. If we're in default state, we don't
95 // need to invoke virtuals on the control.
96 if (control.CheckStateIsAtDefault())
97 return false;
98
99 // Check magnitude of actuation.
100 var magnitude = control.magnitude;
101 if (magnitude < 0)
102 {
103 // We know the control is not in default state but we also know it doesn't support
104 // magnitude. So, all we can say is that it is actuated. Not how much it is actuated.
105 //
106 // If we're looking for a specific threshold here, consider the control to always
107 // be under. But if not, consider it actuated "by virtue of not being in default state".
108 if (Mathf.Approximately(threshold, 0))
109 return true;
110 return false;
111 }
112
113 if (Mathf.Approximately(threshold, 0))
114 return magnitude > 0;
115
116 return magnitude >= threshold;
117 }
118
119 /// <summary>
120 /// Read the current value of the control and return it as an object.
121 /// </summary>
122 /// <returns></returns>
123 /// <remarks>
124 /// This method allocates GC memory and thus may cause garbage collection when used during gameplay.
125 ///
126 /// Use <seealso cref="ReadValueIntoBuffer"/> to read values generically without having to know the
127 /// specific value type of a control.
128 /// </remarks>
129 /// <seealso cref="ReadValueIntoBuffer"/>
130 /// <seealso cref="InputControl{TValue}.ReadValue"/>
131 public static unsafe object ReadValueAsObject(this InputControl control)
132 {
133 if (control == null)
134 throw new ArgumentNullException(nameof(control));
135
136 return control.ReadValueFromStateAsObject(control.currentStatePtr);
137 }
138
139 /// <summary>
140 /// Read the current, processed value of the control and store it into the given memory buffer.
141 /// </summary>
142 /// <param name="buffer">Buffer to store value in. Note that the value is not stored with the offset
143 /// found in <see cref="InputStateBlock.byteOffset"/> of the control's <see cref="InputControl.stateBlock"/>. It will
144 /// be stored directly at the given address.</param>
145 /// <param name="bufferSize">Size of the memory available at <paramref name="buffer"/> in bytes. Has to be
146 /// at least <see cref="InputControl.valueSizeInBytes"/>. If the size is smaller, nothing will be written to the buffer.</param>
147 /// <seealso cref="InputControl.valueSizeInBytes"/>
148 /// <seealso cref="InputControl.valueType"/>
149 /// <seealso cref="InputControl.ReadValueFromStateIntoBuffer"/>
150 public static unsafe void ReadValueIntoBuffer(this InputControl control, void* buffer, int bufferSize)
151 {
152 if (control == null)
153 throw new ArgumentNullException(nameof(control));
154 if (buffer == null)
155 throw new ArgumentNullException(nameof(buffer));
156
157 control.ReadValueFromStateIntoBuffer(control.currentStatePtr, buffer, bufferSize);
158 }
159
160 /// <summary>
161 /// Read the control's default value and return it as an object.
162 /// </summary>
163 /// <param name="control">Control to read default value from.</param>
164 /// <returns></returns>
165 /// <exception cref="ArgumentNullException"><paramref name="control"/> is null.</exception>
166 /// <remarks>
167 /// This method allocates GC memory and should thus not be used during normal gameplay.
168 /// </remarks>
169 /// <seealso cref="InputControl.hasDefaultState"/>
170 /// <seealso cref="InputControl.defaultStatePtr"/>
171 public static unsafe object ReadDefaultValueAsObject(this InputControl control)
172 {
173 if (control == null)
174 throw new ArgumentNullException(nameof(control));
175
176 return control.ReadValueFromStateAsObject(control.defaultStatePtr);
177 }
178
179 /// <summary>
180 /// Read the value for the given control from the given event.
181 /// </summary>
182 /// <param name="control">Control to read value for.</param>
183 /// <param name="inputEvent">Event to read value from. Must be a <see cref="StateEvent"/> or <see cref="DeltaStateEvent"/>.</param>
184 /// <typeparam name="TValue">Type of value to read.</typeparam>
185 /// <exception cref="ArgumentNullException"><paramref name="control"/> is <c>null</c>.</exception>
186 /// <exception cref="ArgumentException"><paramref name="inputEvent"/> is not a <see cref="StateEvent"/> or <see cref="DeltaStateEvent"/>.</exception>
187 /// <returns>The value for the given control as read out from the given event or <c>default(TValue)</c> if the given
188 /// event does not contain a value for the control (e.g. if it is a <see cref="DeltaStateEvent"/> not containing the relevant
189 /// portion of the device's state memory).</returns>
190 public static TValue ReadValueFromEvent<TValue>(this InputControl<TValue> control, InputEventPtr inputEvent)
191 where TValue : struct
192 {
193 if (control == null)
194 throw new ArgumentNullException(nameof(control));
195 if (!ReadValueFromEvent(control, inputEvent, out var value))
196 return default;
197 return value;
198 }
199
200 /// <summary>
201 /// Check if the given event contains a value for the given control and if so, read the value.
202 /// </summary>
203 /// <param name="control">Control to read value for.</param>
204 /// <param name="inputEvent">Input event. This must be a <see cref="StateEvent"/> or <see cref="DeltaStateEvent"/>.
205 /// Note that in the case of a <see cref="DeltaStateEvent"/>, the control may not actually be part of the event. In this
206 /// case, the method returns false and stores <c>default(TValue)</c> in <paramref name="value"/>.</param>
207 /// <param name="value">Variable that receives the control value.</param>
208 /// <typeparam name="TValue">Type of value to read.</typeparam>
209 /// <returns>True if the value has been successfully read from the event, false otherwise.</returns>
210 /// <exception cref="ArgumentNullException"><paramref name="control"/> is <c>null</c>.</exception>
211 /// <exception cref="ArgumentException"><paramref name="inputEvent"/> is not a <see cref="StateEvent"/> or <see cref="DeltaStateEvent"/>.</exception>
212 /// <seealso cref="ReadUnprocessedValueFromEvent{TValue}(InputControl{TValue},InputEventPtr)"/>
213 [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1021:AvoidOutParameters", MessageId = "2#")]
214 public static unsafe bool ReadValueFromEvent<TValue>(this InputControl<TValue> control, InputEventPtr inputEvent, out TValue value)
215 where TValue : struct
216 {
217 if (control == null)
218 throw new ArgumentNullException(nameof(control));
219
220 var statePtr = control.GetStatePtrFromStateEvent(inputEvent);
221 if (statePtr == null)
222 {
223 value = control.ReadDefaultValue();
224 return false;
225 }
226
227 value = control.ReadValueFromState(statePtr);
228 return true;
229 }
230
231 /// <summary>
232 /// Read the value of <paramref name="control"/> from the given <paramref name="inputEvent"/> without having to
233 /// know the specific value type of the control.
234 /// </summary>
235 /// <param name="control">Control to read the value for.</param>
236 /// <param name="inputEvent">An <see cref="StateEvent"/> or <see cref="DeltaStateEvent"/> to read the value from.</param>
237 /// <returns>The current value for the control or <c>null</c> if the control's value is not included
238 /// in the event.</returns>
239 /// <seealso cref="InputControl.ReadValueFromStateAsObject"/>
240 public static unsafe object ReadValueFromEventAsObject(this InputControl control, InputEventPtr inputEvent)
241 {
242 if (control == null)
243 throw new ArgumentNullException(nameof(control));
244
245 var statePtr = control.GetStatePtrFromStateEvent(inputEvent);
246 if (statePtr == null)
247 return control.ReadDefaultValueAsObject();
248
249 return control.ReadValueFromStateAsObject(statePtr);
250 }
251
252 public static TValue ReadUnprocessedValueFromEvent<TValue>(this InputControl<TValue> control, InputEventPtr eventPtr)
253 where TValue : struct
254 {
255 if (control == null)
256 throw new ArgumentNullException(nameof(control));
257
258 var result = default(TValue);
259 control.ReadUnprocessedValueFromEvent(eventPtr, out result);
260 return result;
261 }
262
263 [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1021:AvoidOutParameters", MessageId = "2#")]
264 public static unsafe bool ReadUnprocessedValueFromEvent<TValue>(this InputControl<TValue> control, InputEventPtr inputEvent, out TValue value)
265 where TValue : struct
266 {
267 if (control == null)
268 throw new ArgumentNullException(nameof(control));
269
270 var statePtr = control.GetStatePtrFromStateEvent(inputEvent);
271 if (statePtr == null)
272 {
273 value = control.ReadDefaultValue();
274 return false;
275 }
276
277 value = control.ReadUnprocessedValueFromState(statePtr);
278 return true;
279 }
280
281 ////REVIEW: this has the opposite argument order of WriteValueFromObjectIntoState; fix!
282 public static unsafe void WriteValueFromObjectIntoEvent(this InputControl control, InputEventPtr eventPtr, object value)
283 {
284 if (control == null)
285 throw new ArgumentNullException(nameof(control));
286
287 var statePtr = control.GetStatePtrFromStateEvent(eventPtr);
288 if (statePtr == null)
289 return;
290
291 control.WriteValueFromObjectIntoState(value, statePtr);
292 }
293
294 /// <summary>
295 /// Write the control's current value into <paramref name="statePtr"/>.
296 /// </summary>
297 /// <param name="control">Control to read the current value from and to store state for in <paramref name="statePtr"/>.</param>
298 /// <param name="statePtr">State to receive the control's value in its respective <see cref="InputControl.stateBlock"/>.</param>
299 /// <exception cref="ArgumentNullException"><paramref name="control"/> is null or <paramref name="statePtr"/> is null.</exception>
300 /// <remarks>
301 /// This method is equivalent to <see cref="InputControl{TValue}.WriteValueIntoState"/> except that one does
302 /// not have to know the value type of the given control.
303 /// </remarks>
304 /// <exception cref="NotSupportedException">The control does not support writing. This is the case, for
305 /// example, that compute values (such as the magnitude of a vector).</exception>
306 /// <seealso cref="InputControl{TValue}.WriteValueIntoState"/>
307 public static unsafe void WriteValueIntoState(this InputControl control, void* statePtr)
308 {
309 if (control == null)
310 throw new ArgumentNullException(nameof(control));
311 if (statePtr == null)
312 throw new ArgumentNullException(nameof(statePtr));
313
314 var valueSize = control.valueSizeInBytes;
315 var valuePtr = UnsafeUtility.Malloc(valueSize, 8, Allocator.Temp);
316 try
317 {
318 control.ReadValueFromStateIntoBuffer(control.currentStatePtr, valuePtr, valueSize);
319 control.WriteValueFromBufferIntoState(valuePtr, valueSize, statePtr);
320 }
321 finally
322 {
323 UnsafeUtility.Free(valuePtr, Allocator.Temp);
324 }
325 }
326
327 public static unsafe void WriteValueIntoState<TValue>(this InputControl control, TValue value, void* statePtr)
328 where TValue : struct
329 {
330 if (control == null)
331 throw new ArgumentNullException(nameof(control));
332
333 if (!(control is InputControl<TValue> controlOfType))
334 throw new ArgumentException(
335 $"Expecting control of type '{typeof(TValue).Name}' but got '{control.GetType().Name}'");
336
337 controlOfType.WriteValueIntoState(value, statePtr);
338 }
339
340 public static unsafe void WriteValueIntoState<TValue>(this InputControl<TValue> control, TValue value, void* statePtr)
341 where TValue : struct
342 {
343 if (control == null)
344 throw new ArgumentNullException(nameof(control));
345 if (statePtr == null)
346 throw new ArgumentNullException(nameof(statePtr));
347
348 var valuePtr = UnsafeUtility.AddressOf(ref value);
349 var valueSize = UnsafeUtility.SizeOf<TValue>();
350
351 control.WriteValueFromBufferIntoState(valuePtr, valueSize, statePtr);
352 }
353
354 public static unsafe void WriteValueIntoState<TValue>(this InputControl<TValue> control, void* statePtr)
355 where TValue : struct
356 {
357 if (control == null)
358 throw new ArgumentNullException(nameof(control));
359
360 control.WriteValueIntoState(control.ReadValue(), statePtr);
361 }
362
363 /// <summary>
364 ///
365 /// </summary>
366 /// <param name="state"></param>
367 /// <param name="value">Value for <paramref name="control"/> to write into <paramref name="state"/>.</param>
368 /// <typeparam name="TState"></typeparam>
369 /// <exception cref="ArgumentNullException"><paramref name="control"/> is null.</exception>
370 /// <exception cref="ArgumentException">Control's value does not fit within the memory of <paramref name="state"/>.</exception>
371 /// <exception cref="NotSupportedException"><paramref name="control"/> does not support writing.</exception>
372 public static unsafe void WriteValueIntoState<TValue, TState>(this InputControl<TValue> control, TValue value, ref TState state)
373 where TValue : struct
374 where TState : struct, IInputStateTypeInfo
375 {
376 if (control == null)
377 throw new ArgumentNullException(nameof(control));
378
379 // Make sure the control's state actually fits within the given state.
380 var sizeOfState = UnsafeUtility.SizeOf<TState>();
381 if (control.stateOffsetRelativeToDeviceRoot + control.m_StateBlock.alignedSizeInBytes >= sizeOfState)
382 throw new ArgumentException(
383 $"Control {control.path} with offset {control.stateOffsetRelativeToDeviceRoot} and size of {control.m_StateBlock.sizeInBits} bits is out of bounds for state of type {typeof(TState).Name} with size {sizeOfState}",
384 nameof(state));
385
386 // Write value.
387 var statePtr = (byte*)UnsafeUtility.AddressOf(ref state);
388 control.WriteValueIntoState(value, statePtr);
389 }
390
391 public static void WriteValueIntoEvent<TValue>(this InputControl control, TValue value, InputEventPtr eventPtr)
392 where TValue : struct
393 {
394 if (control == null)
395 throw new ArgumentNullException(nameof(control));
396 if (!eventPtr.valid)
397 throw new ArgumentNullException(nameof(eventPtr));
398
399 if (!(control is InputControl<TValue> controlOfType))
400 throw new ArgumentException(
401 $"Expecting control of type '{typeof(TValue).Name}' but got '{control.GetType().Name}'");
402
403 controlOfType.WriteValueIntoEvent(value, eventPtr);
404 }
405
406 public static unsafe void WriteValueIntoEvent<TValue>(this InputControl<TValue> control, TValue value, InputEventPtr eventPtr)
407 where TValue : struct
408 {
409 if (control == null)
410 throw new ArgumentNullException(nameof(control));
411 if (!eventPtr.valid)
412 throw new ArgumentNullException(nameof(eventPtr));
413
414 var statePtr = control.GetStatePtrFromStateEvent(eventPtr);
415 if (statePtr == null)
416 return;
417
418 control.WriteValueIntoState(value, statePtr);
419 }
420
421 /// <summary>
422 /// Copy the state of the device to the given memory buffer.
423 /// </summary>
424 /// <param name="device">An input device.</param>
425 /// <param name="buffer">Buffer to copy the state of the device to.</param>
426 /// <param name="bufferSizeInBytes">Size of <paramref name="buffer"/> in bytes.</param>
427 /// <exception cref="ArgumentException"><paramref name="bufferSizeInBytes"/> is less than or equal to 0.</exception>
428 /// <exception cref="ArgumentNullException"><paramref name="device"/> is <c>null</c>.</exception>
429 /// <remarks>
430 /// The method will copy however much fits into the given buffer. This means that if the state of the device
431 /// is larger than what fits into the buffer, not all of the device's state is copied.
432 /// </remarks>
433 /// <seealso cref="InputControl.stateBlock"/>
434 public static unsafe void CopyState(this InputDevice device, void* buffer, int bufferSizeInBytes)
435 {
436 if (device == null)
437 throw new ArgumentNullException(nameof(device));
438 if (bufferSizeInBytes <= 0)
439 throw new ArgumentException("bufferSizeInBytes must be positive", nameof(bufferSizeInBytes));
440
441 var stateBlock = device.m_StateBlock;
442 var sizeToCopy = Math.Min(bufferSizeInBytes, stateBlock.alignedSizeInBytes);
443
444 UnsafeUtility.MemCpy(buffer, (byte*)device.currentStatePtr + stateBlock.byteOffset, sizeToCopy);
445 }
446
447 /// <summary>
448 /// Copy the state of the device to the given struct.
449 /// </summary>
450 /// <param name="device">An input device.</param>
451 /// <param name="state">Struct to copy the state of the device into.</param>
452 /// <typeparam name="TState">A state struct type such as <see cref="MouseState"/>.</typeparam>
453 /// <exception cref="ArgumentException">The state format of <typeparamref name="TState"/> does not match
454 /// the state form of <paramref name="device"/>.</exception>
455 /// <exception cref="ArgumentNullException"><paramref name="device"/> is <c>null</c>.</exception>
456 /// <remarks>
457 /// This method will copy memory verbatim into the memory of the given struct. It will copy whatever
458 /// memory of the device fits into the given struct.
459 /// </remarks>
460 /// <seealso cref="InputControl.stateBlock"/>
461 public static unsafe void CopyState<TState>(this InputDevice device, out TState state)
462 where TState : struct, IInputStateTypeInfo
463 {
464 if (device == null)
465 throw new ArgumentNullException(nameof(device));
466
467 state = default;
468 if (device.stateBlock.format != state.format)
469 throw new ArgumentException(
470 $"Struct '{typeof(TState).Name}' has state format '{state.format}' which doesn't match device '{device}' with state format '{device.stateBlock.format}'",
471 nameof(TState));
472
473 var stateSize = UnsafeUtility.SizeOf<TState>();
474 var statePtr = UnsafeUtility.AddressOf(ref state);
475
476 device.CopyState(statePtr, stateSize);
477 }
478
479 /// <summary>
480 /// Check whether the memory of the given control is in its default state.
481 /// </summary>
482 /// <param name="control">An input control on a device that's been added to the system (see <see cref="InputDevice.added"/>).</param>
483 /// <returns>True if the state memory of the given control corresponds to the control's default.</returns>
484 /// <exception cref="ArgumentNullException"><paramref name="control"/> is <c>null</c>.</exception>
485 /// <remarks>
486 /// This method is a cheaper check than actually reading out the value from the control and checking whether it
487 /// is the same value as the default value. The method bypasses all value reading and simply performs a trivial
488 /// memory comparison of the control's current state memory to the default state memory stored for the control.
489 ///
490 /// Note that the default state for the memory of a control does not necessary need to be all zeroes. For example,
491 /// a stick axis may be stored as an unsigned 8-bit value with the memory state corresponding to a 0 value being 127.
492 /// </remarks>
493 /// <seealso cref="InputControl.stateBlock"/>
494 public static unsafe bool CheckStateIsAtDefault(this InputControl control)
495 {
496 if (control == null)
497 throw new ArgumentNullException(nameof(control));
498
499 return CheckStateIsAtDefault(control, control.currentStatePtr);
500 }
501
502 /// <summary>
503 /// Check if the given state corresponds to the default state of the control.
504 /// </summary>
505 /// <param name="control">Control to check the state for in <paramref name="statePtr"/>.</param>
506 /// <param name="statePtr">Pointer to a state buffer containing the <see cref="InputControl.stateBlock"/> for <paramref name="control"/>.</param>
507 /// <param name="maskPtr">If not null, only bits set to <c>false</c> (!) in the buffer will be taken into account. This can be used
508 /// to mask out noise, i.e. every bit that is set in the mask is considered to represent noise.</param>
509 /// <returns>True if the control/device is in its default state.</returns>
510 /// <remarks>
511 /// Note that default does not equate all zeroes. Stick axes, for example, that are stored as unsigned byte
512 /// values will have their resting position at 127 and not at 0. This is why we explicitly store default
513 /// state in a memory buffer instead of assuming zeroes.
514 /// </remarks>
515 /// <seealso cref="InputControl{TValue}.ReadDefaultValue"/>
516 public static unsafe bool CheckStateIsAtDefault(this InputControl control, void* statePtr, void* maskPtr = null)
517 {
518 if (control == null)
519 throw new ArgumentNullException(nameof(control));
520 if (statePtr == null)
521 throw new ArgumentNullException(nameof(statePtr));
522
523 return control.CompareState(statePtr, control.defaultStatePtr, maskPtr);
524 }
525
526 public static unsafe bool CheckStateIsAtDefaultIgnoringNoise(this InputControl control)
527 {
528 if (control == null)
529 throw new ArgumentNullException(nameof(control));
530
531 return control.CheckStateIsAtDefaultIgnoringNoise(control.currentStatePtr);
532 }
533
534 /// <summary>
535 /// Check if the given state corresponds to the default state of the control or has different state only
536 /// for parts marked as noise.
537 /// </summary>
538 /// <param name="control">Control to check the state for in <paramref name="statePtr"/>.</param>
539 /// <param name="statePtr">Pointer to a state buffer containing the <see cref="InputControl.stateBlock"/> for <paramref name="control"/>.</param>
540 /// <returns>True if the control/device is in its default state (ignoring any bits marked as noise).</returns>
541 /// <remarks>
542 /// Note that default does not equate all zeroes. Stick axes, for example, that are stored as unsigned byte
543 /// values will have their resting position at 127 and not at 0. This is why we explicitly store default
544 /// state in a memory buffer instead of assuming zeroes.
545 /// </remarks>
546 /// <seealso cref="InputControl{TValue}.ReadDefaultValue"/>
547 /// <seealso cref="InputControl.noisy"/>
548 /// <seealso cref="InputControl.noiseMaskPtr"/>
549 public static unsafe bool CheckStateIsAtDefaultIgnoringNoise(this InputControl control, void* statePtr)
550 {
551 if (control == null)
552 throw new ArgumentNullException(nameof(control));
553 if (statePtr == null)
554 throw new ArgumentNullException(nameof(statePtr));
555
556 return control.CheckStateIsAtDefault(statePtr, InputStateBuffers.s_NoiseMaskBuffer);
557 }
558
559 /// <summary>
560 /// Compare the control's current state to the state stored in <paramref name="statePtr"/>.
561 /// </summary>
562 /// <param name="statePtr">State memory containing the control's <see cref="InputControl.stateBlock"/>.</param>
563 /// <returns>True if </returns>
564 /// <seealso cref="InputControl.currentStatePtr"/>
565 /// <remarks>
566 /// This method ignores noise
567 ///
568 /// This method will not actually read values but will instead compare state directly as it is stored
569 /// in memory. <see cref="InputControl{TValue}.ReadValue"/> is not invoked and thus processors will
570 /// not be run.
571 /// </remarks>
572 public static unsafe bool CompareStateIgnoringNoise(this InputControl control, void* statePtr)
573 {
574 if (control == null)
575 throw new ArgumentNullException(nameof(control));
576 if (statePtr == null)
577 throw new ArgumentNullException(nameof(statePtr));
578
579 return control.CompareState(control.currentStatePtr, statePtr, control.noiseMaskPtr);
580 }
581
582 /// <summary>
583 /// Compare the control's stored state in <paramref name="firstStatePtr"/> to <paramref name="secondStatePtr"/>.
584 /// </summary>
585 /// <param name="firstStatePtr">Memory containing the control's <see cref="InputControl.stateBlock"/>.</param>
586 /// <param name="secondStatePtr">Memory containing the control's <see cref="InputControl.stateBlock"/></param>
587 /// <param name="maskPtr">Optional mask. If supplied, it will be used to mask the comparison between
588 /// <paramref name="firstStatePtr"/> and <paramref name="secondStatePtr"/> such that any bit not set in the
589 /// mask will be ignored even if different between the two states. This can be used, for example, to ignore
590 /// noise in the state (<see cref="InputControl.noiseMaskPtr"/>).</param>
591 /// <returns>True if the state is equivalent in both memory buffers.</returns>
592 /// <remarks>
593 /// Unlike <see cref="InputControl.CompareValue"/>, this method only compares raw memory state. If used on a stick, for example,
594 /// it may mean that this method returns false for two stick values that would compare equal using <see cref="InputControl.CompareValue"/>
595 /// (e.g. if both stick values fall below the deadzone).
596 /// </remarks>
597 /// <seealso cref="InputControl.CompareValue"/>
598 public static unsafe bool CompareState(this InputControl control, void* firstStatePtr, void* secondStatePtr, void* maskPtr = null)
599 {
600 ////REVIEW: for compound controls, do we want to go check leaves so as to not pick up on non-control noise in the state?
601 //// e.g. from HID input reports; or should we just leave that to maskPtr?
602
603 var firstPtr = (byte*)firstStatePtr + (int)control.m_StateBlock.byteOffset;
604 var secondPtr = (byte*)secondStatePtr + (int)control.m_StateBlock.byteOffset;
605 var mask = maskPtr != null ? (byte*)maskPtr + (int)control.m_StateBlock.byteOffset : null;
606
607 if (control.m_StateBlock.sizeInBits == 1)
608 {
609 // If we have a mask and the bit is set in the mask, the control is to be ignored
610 // and thus we consider it at default value.
611 if (mask != null && MemoryHelpers.ReadSingleBit(mask, control.m_StateBlock.bitOffset))
612 return true;
613
614 return MemoryHelpers.ReadSingleBit(secondPtr, control.m_StateBlock.bitOffset) ==
615 MemoryHelpers.ReadSingleBit(firstPtr, control.m_StateBlock.bitOffset);
616 }
617
618 return MemoryHelpers.MemCmpBitRegion(firstPtr, secondPtr,
619 control.m_StateBlock.bitOffset, control.m_StateBlock.sizeInBits, mask);
620 }
621
622 public static unsafe bool CompareState(this InputControl control, void* statePtr, void* maskPtr = null)
623 {
624 if (control == null)
625 throw new ArgumentNullException(nameof(control));
626 if (statePtr == null)
627 throw new ArgumentNullException(nameof(statePtr));
628
629 return control.CompareState(control.currentStatePtr, statePtr, maskPtr);
630 }
631
632 /// <summary>
633 /// Return true if the current value of <paramref name="control"/> is different to the one found
634 /// in <paramref name="statePtr"/>.
635 /// </summary>
636 /// <param name="control">Control whose state to compare to what is stored in <paramref name="statePtr"/>.</param>
637 /// <param name="statePtr">A block of input state memory containing the <see cref="InputControl.stateBlock"/>
638 /// of <paramref name="control."/></param>
639 /// <exception cref="ArgumentNullException"><paramref name="control"/> is <c>null</c> or <paramref name="statePtr"/>
640 /// is <c>null</c>.</exception>
641 /// <returns>True if the value of <paramref name="control"/> stored in <paramref name="statePtr"/> is different
642 /// compared to what <see cref="InputControl{T}.ReadValue"/> of the control returns.</returns>
643 public static unsafe bool HasValueChangeInState(this InputControl control, void* statePtr)
644 {
645 if (control == null)
646 throw new ArgumentNullException(nameof(control));
647 if (statePtr == null)
648 throw new ArgumentNullException(nameof(statePtr));
649
650 return control.CompareValue(control.currentStatePtr, statePtr);
651 }
652
653 /// <summary>
654 /// Return true if <paramref name="control"/> has a different value (from its current one) in
655 /// <paramref name="eventPtr"/>.
656 /// </summary>
657 /// <param name="control">An input control.</param>
658 /// <param name="eventPtr">An input event. Must be a <see cref="StateEvent"/> or <see cref="DeltaStateEvent"/>.</param>
659 /// <returns>True if <paramref name="eventPtr"/> contains a value for <paramref name="control"/> that is different
660 /// from the control's current value (see <see cref="InputControl{TValue}.ReadValue"/>).</returns>
661 /// <exception cref="ArgumentNullException"><paramref name="control"/> is <c>null</c> -or- <paramref name="eventPtr"/> is a <c>null</c> pointer (see <see cref="InputEventPtr.valid"/>).</exception>
662 /// <exception cref="ArgumentException"><paramref name="eventPtr"/> is not a <see cref="StateEvent"/> or <see cref="DeltaStateEvent"/>.</exception>
663 public static unsafe bool HasValueChangeInEvent(this InputControl control, InputEventPtr eventPtr)
664 {
665 if (control == null)
666 throw new ArgumentNullException(nameof(control));
667 if (!eventPtr.valid)
668 throw new ArgumentNullException(nameof(eventPtr));
669
670 var statePtr = control.GetStatePtrFromStateEvent(eventPtr);
671 if (statePtr == null)
672 return false;
673
674 return control.CompareValue(control.currentStatePtr, statePtr);
675 }
676
677 /// <summary>
678 /// Given a <see cref="StateEvent"/> or <see cref="DeltaStateEvent"/>, return the raw memory pointer that can
679 /// be used, for example, with <see cref="InputControl{T}.ReadValueFromState"/> to read out the value of <paramref name="control"/>
680 /// contained in the event.
681 /// </summary>
682 /// <param name="control">Control to access state for in the given state event.</param>
683 /// <param name="eventPtr">A <see cref="StateEvent"/> or <see cref="DeltaStateEvent"/> containing input state.</param>
684 /// <returns>A pointer that can be used with methods such as <see cref="InputControl{TValue}.ReadValueFromState"/> or <c>null</c>
685 /// if <paramref name="eventPtr"/> does not contain state for the given <paramref name="control"/>.</returns>
686 /// <exception cref="ArgumentNullException"><paramref name="control"/> is <c>null</c> -or- <paramref name="eventPtr"/> is invalid.</exception>
687 /// <exception cref="ArgumentException"><paramref name="eventPtr"/> is not a <see cref="StateEvent"/> or <see cref="DeltaStateEvent"/>.</exception>
688 /// <remarks>
689 /// Note that the given state event must have the same state format (see <see cref="InputStateBlock.format"/>) as the device
690 /// to which <paramref name="control"/> belongs. If this is not the case, the method will invariably return <c>null</c>.
691 ///
692 /// In practice, this means that the method cannot be used with touch events or, in general, with events sent to devices
693 /// that implement <see cref="IInputStateCallbackReceiver"/> (which <see cref="Touchscreen"/> does) except if the event
694 /// is in the same state format as the device. Touch events will generally be sent as state events containing only the
695 /// state of a single <see cref="Controls.TouchControl"/>, not the state of the entire <see cref="Touchscreen"/>.
696 /// </remarks>
697 public static unsafe void* GetStatePtrFromStateEvent(this InputControl control, InputEventPtr eventPtr)
698 {
699 if (control == null)
700 throw new ArgumentNullException(nameof(control));
701 if (!eventPtr.valid)
702 throw new ArgumentNullException(nameof(eventPtr));
703
704 return GetStatePtrFromStateEventUnchecked(control, eventPtr, eventPtr.type);
705 }
706
707 internal static unsafe void* GetStatePtrFromStateEventUnchecked(this InputControl control, InputEventPtr eventPtr, FourCC eventType)
708 {
709 uint stateOffset;
710 FourCC stateFormat;
711 uint stateSizeInBytes;
712 void* statePtr;
713
714 if (eventType == StateEvent.Type)
715 {
716 var stateEvent = StateEvent.FromUnchecked(eventPtr);
717
718 stateOffset = 0;
719 stateFormat = stateEvent->stateFormat;
720 stateSizeInBytes = stateEvent->stateSizeInBytes;
721 statePtr = stateEvent->state;
722 }
723 else if (eventType == DeltaStateEvent.Type)
724 {
725 var deltaEvent = DeltaStateEvent.FromUnchecked(eventPtr);
726
727 // If it's a delta event, we need to subtract the delta state offset if it's not set to the root of the device
728 stateOffset = deltaEvent->stateOffset;
729 stateFormat = deltaEvent->stateFormat;
730 stateSizeInBytes = deltaEvent->deltaStateSizeInBytes;
731 statePtr = deltaEvent->deltaState;
732 }
733 else
734 {
735 throw new ArgumentException($"Event must be a StateEvent or DeltaStateEvent but is a {eventType} instead",
736 nameof(eventPtr));
737 }
738
739 // Make sure we have a state event compatible with our device. The event doesn't
740 // have to be specifically for our device (we don't require device IDs to match) but
741 // the formats have to match and the size must be within range of what we're trying
742 // to read.
743 var device = control.device;
744 if (stateFormat != device.m_StateBlock.format)
745 {
746 // If the device is an IInputStateCallbackReceiver, there's a chance it actually recognizes
747 // the state format in the event and can correlate it to the state as found on the device.
748 if (!device.hasStateCallbacks ||
749 !((IInputStateCallbackReceiver)device).GetStateOffsetForEvent(control, eventPtr, ref stateOffset))
750 return null;
751 }
752
753 // Once a device has been added, global state buffer offsets are baked into control hierarchies.
754 // We need to unsubtract those offsets here.
755 // NOTE: If the given device has not actually been added to the system, the offset is simply 0
756 // and this is a harmless NOP.
757 stateOffset += device.m_StateBlock.byteOffset;
758
759 // Return null if state is out of range.
760 ref var controlStateBlock = ref control.m_StateBlock;
761 var controlOffset = (int)controlStateBlock.effectiveByteOffset - stateOffset;
762 if (controlOffset < 0 || controlOffset + controlStateBlock.alignedSizeInBytes > stateSizeInBytes)
763 return null;
764
765 return (byte*)statePtr - (int)stateOffset;
766 }
767
768 /// <summary>
769 /// Writes the default state of <paramref name="control"/> into <paramref name="eventPtr"/>.
770 /// </summary>
771 /// <param name="control">A control whose default state to write.</param>
772 /// <param name="eventPtr">A valid pointer to either a <see cref="StateEvent"/> or <see cref="DeltaStateEvent"/>.</param>
773 /// <exception cref="ArgumentNullException"><paramref name="control"/> is <c>null</c> -or- <paramref name="eventPtr"/> contains
774 /// a null pointer.</exception>
775 /// <exception cref="ArgumentException"><paramref name="eventPtr"/> is not a <see cref="StateEvent"/> or <see cref="DeltaStateEvent"/>.</exception>
776 /// <returns>True if the default state for <paramref name="control"/> was written to <paramref name="eventPtr"/>, false if the
777 /// given state or delta state event did not include memory for the given control.</returns>
778 /// <remarks>
779 /// Note that the default state of a control does not necessarily need to correspond to zero-initialized memory. For example, if
780 /// an axis control yields a range of [-1..1] and is stored as a signed 8-bit value, the default state will be 127, not 0.
781 ///
782 /// <example>
783 /// <code>
784 /// // Reset the left gamepad stick to its default state (which results in a
785 /// // value of (0,0).
786 /// using (StateEvent.From(Gamepad.all[0], out var eventPtr))
787 /// {
788 /// Gamepad.all[0].leftStick.ResetToDefaultStateInEvent(eventPtr);
789 /// InputSystem.QueueEvent(eventPtr);
790 /// }
791 /// </code>
792 /// </example>
793 /// </remarks>
794 /// <seealso cref="InputControl.defaultStatePtr"/>
795 public static unsafe bool ResetToDefaultStateInEvent(this InputControl control, InputEventPtr eventPtr)
796 {
797 if (control == null)
798 throw new ArgumentNullException(nameof(control));
799 if (!eventPtr.valid)
800 throw new ArgumentNullException(nameof(eventPtr));
801
802 var eventType = eventPtr.type;
803 if (eventType != StateEvent.Type && eventType != DeltaStateEvent.Type)
804 throw new ArgumentException("Given event is not a StateEvent or a DeltaStateEvent", nameof(eventPtr));
805
806 var statePtr = (byte*)control.GetStatePtrFromStateEvent(eventPtr);
807 if (statePtr == null)
808 return false;
809
810 var defaultStatePtr = (byte*)control.defaultStatePtr;
811 ref var stateBlock = ref control.m_StateBlock;
812 var offset = stateBlock.byteOffset;
813
814 MemoryHelpers.MemCpyBitRegion(statePtr + offset, defaultStatePtr + offset, stateBlock.bitOffset, stateBlock.sizeInBits);
815 return true;
816 }
817
818 /// <summary>
819 /// Queue a value change on the given <paramref name="control"/> which will be processed and take effect
820 /// in the next input update.
821 /// </summary>
822 /// <param name="control">Control to change the value of.</param>
823 /// <param name="value">New value for the control.</param>
824 /// <param name="time">Optional time at which the value change should take effect. If set, this will become
825 /// the <see cref="InputEvent.time"/> of the queued event. If the time is in the future and the update mode is
826 /// set to <see cref="InputSettings.UpdateMode.ProcessEventsInFixedUpdate"/>, the event will not be processed until
827 /// it falls within the time of an input update slice. Otherwise, the event will invariably be consumed in the
828 /// next input update (see <see cref="InputSystem.Update"/>).</param>
829 /// <typeparam name="TValue">Type of value.</typeparam>
830 /// <exception cref="ArgumentNullException"><paramref name="control"/> is null.</exception>
831 public static void QueueValueChange<TValue>(this InputControl<TValue> control, TValue value, double time = -1)
832 where TValue : struct
833 {
834 if (control == null)
835 throw new ArgumentNullException(nameof(control));
836
837 ////TODO: if it's not a bit-addressing control, send a delta state change only
838 using (StateEvent.From(control.device, out var eventPtr))
839 {
840 if (time >= 0)
841 eventPtr.time = time;
842 control.WriteValueIntoEvent(value, eventPtr);
843 InputSystem.QueueEvent(eventPtr);
844 }
845 }
846
847 /// <summary>
848 /// Modify <paramref name="newState"/> to write an accumulated value of the control
849 /// rather than the value currently found in the event.
850 /// </summary>
851 /// <param name="control">Control to perform the accumulation on.</param>
852 /// <param name="currentStatePtr">Memory containing the control's current state. See <see
853 /// cref="InputControl.currentStatePtr"/>.</param>
854 /// <param name="newState">Event containing the new state about to be written to the device.</param>
855 /// <exception cref="ArgumentNullException"><paramref name="control"/> is <c>null</c>.</exception>
856 /// <remarks>
857 /// This method reads the current, unprocessed value of the control from <see cref="InputControl.currentStatePtr"/>
858 /// and then adds it to the value of the control found in <paramref name="newState"/>.
859 ///
860 /// Note that the method does nothing if a value for the control is not contained in <paramref name="newState"/>.
861 /// This can be the case, for example, for <see cref="DeltaStateEvent"/>s.
862 /// </remarks>
863 /// <seealso cref="Pointer.delta"/>
864 public static unsafe void AccumulateValueInEvent(this InputControl<float> control, void* currentStatePtr, InputEventPtr newState)
865 {
866 if (control == null)
867 throw new ArgumentNullException(nameof(control));
868
869 if (!control.ReadUnprocessedValueFromEvent(newState, out var newValue))
870 return; // Value for the control not contained in the given event.
871
872 var oldValue = control.ReadUnprocessedValueFromState(currentStatePtr);
873 control.WriteValueIntoEvent(oldValue + newValue, newState);
874 }
875
876 internal static unsafe void AccumulateValueInEvent(this InputControl<Vector2> control, void* currentStatePtr, InputEventPtr newState)
877 {
878 if (control == null)
879 throw new ArgumentNullException(nameof(control));
880
881 if (!control.ReadUnprocessedValueFromEvent(newState, out var newValue))
882 return; // Value for the control not contained in the given event.
883
884 var oldDelta = control.ReadUnprocessedValueFromState(currentStatePtr);
885 control.WriteValueIntoEvent(oldDelta + newValue, newState);
886 }
887
888 public static void FindControlsRecursive<TControl>(this InputControl parent, IList<TControl> controls, Func<TControl, bool> predicate)
889 where TControl : InputControl
890 {
891 if (parent == null)
892 throw new ArgumentNullException(nameof(parent));
893 if (controls == null)
894 throw new ArgumentNullException(nameof(controls));
895 if (predicate == null)
896 throw new ArgumentNullException(nameof(predicate));
897 if (parent is TControl parentAsTControl && predicate(parentAsTControl))
898 controls.Add(parentAsTControl);
899
900 var children = parent.children;
901 var childCount = children.Count;
902 for (var i = 0; i < childCount; ++i)
903 {
904 var child = parent.children[i];
905 FindControlsRecursive(child, controls, predicate);
906 }
907 }
908
909 internal static string BuildPath(this InputControl control, string deviceLayout, StringBuilder builder = null)
910 {
911 if (control == null)
912 throw new ArgumentNullException(nameof(control));
913 if (string.IsNullOrEmpty(deviceLayout))
914 throw new ArgumentNullException(nameof(deviceLayout));
915
916 if (builder == null)
917 builder = new StringBuilder();
918
919 var device = control.device;
920
921 builder.Append('<');
922 builder.Append(deviceLayout.Escape("\\>", "\\>"));
923 builder.Append('>');
924
925 // Add usages of device, if any.
926 var deviceUsages = device.usages;
927 for (var i = 0; i < deviceUsages.Count; ++i)
928 {
929 builder.Append('{');
930 builder.Append(deviceUsages[i].ToString().Escape("\\}", "\\}"));
931 builder.Append('}');
932 }
933
934 builder.Append(InputControlPath.Separator);
935
936 // If any of the components contains a backslash, double it up as in control paths,
937 // these serve as escape characters.
938 var devicePath = device.path.Replace("\\", "\\\\");
939 var controlPath = control.path.Replace("\\", "\\\\");
940 builder.Append(controlPath, devicePath.Length + 1, controlPath.Length - devicePath.Length - 1);
941
942 return builder.ToString();
943 }
944
945 /// <summary>
946 /// Flags that control which controls are returned by <see cref="InputControlExtensions.EnumerateControls"/>.
947 /// </summary>
948 [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1714:Flags enums should have plural names", Justification = "False positive: `IgnoreControlsInDefaultState` is a plural form.")]
949 [Flags]
950 public enum Enumerate
951 {
952 /// <summary>
953 /// Ignore controls whose value is at default (see <see cref="InputControl{TValue}.ReadDefaultValue"/>).
954 /// </summary>
955 IgnoreControlsInDefaultState = 1 << 0,
956
957 /// <summary>
958 /// Ignore controls whose value is the same as their current value (see <see cref="InputControl{TValue}.ReadValue"/>).
959 /// </summary>
960 IgnoreControlsInCurrentState = 1 << 1,
961
962 /// <summary>
963 /// Include controls that are <see cref="InputControl.synthetic"/> and/or use state from other other controls (see
964 /// <see cref="Layouts.InputControlLayout.ControlItem.useStateFrom"/>). These are excluded by default.
965 /// </summary>
966 IncludeSyntheticControls = 1 << 2,
967
968 /// <summary>
969 /// Include noisy controls (see <see cref="InputControl.noisy"/>). These are excluded by default.
970 /// </summary>
971 IncludeNoisyControls = 1 << 3,
972
973 /// <summary>
974 /// For any leaf control returned by the enumeration, also return all the parent controls (see <see cref="InputControl.parent"/>)
975 /// in turn (but not the root <see cref="InputDevice"/> itself).
976 /// </summary>
977 IncludeNonLeafControls = 1 << 4,
978 }
979
980 /// <summary>
981 /// Go through the controls that have values in <paramref name="eventPtr"/>, apply the given filters, and return each
982 /// control one by one.
983 /// </summary>
984 /// <param name="eventPtr">An input event. Must be a <see cref="StateEvent"/> or <see cref="DeltaStateEvent"/>.</param>
985 /// <param name="flags">Filter settings that determine which controls to return.</param>
986 /// <param name="device">Input device from which to enumerate controls. If this is <c>null</c>, then the <see cref="InputEvent.deviceId"/>
987 /// from the given <paramref name="eventPtr"/> will be used to locate the device via <see cref="InputSystem.GetDeviceById"/>. If the device
988 /// cannot be found, an exception will be thrown. Note that type of device must match the state stored in the given event.</param>
989 /// <param name="magnitudeThreshold">If not zero, minimum actuation threshold (see <see cref="InputControl.EvaluateMagnitude()"/>) that
990 /// a control must reach (as per value in the given <paramref name="eventPtr"/>) in order for it to be included in the enumeration.</param>
991 /// <returns>An enumerator for the controls with values in <paramref name="eventPtr"/>.</returns>
992 /// <exception cref="ArgumentNullException"><paramref name="eventPtr"/> is a <c>null</c> pointer (see <see cref="InputEventPtr.valid"/>).</exception>
993 /// <exception cref="ArgumentException"><paramref name="eventPtr"/> is not a <see cref="StateEvent"/> and not a <see cref="DeltaStateEvent"/> -or-
994 /// <paramref name="device"/> is <c>null</c> and no device was found with a <see cref="InputDevice.deviceId"/> matching that of <see cref="InputEvent.deviceId"/>
995 /// found in <paramref name="eventPtr"/>.</exception>
996 /// <remarks>
997 /// This method is much more efficient than manually iterating over the controls of <paramref name="device"/> and locating
998 /// the ones that have changed in <paramref name="eventPtr"/>. See <see cref="InputEventControlEnumerator"/> for details.
999 ///
1000 /// This method will not allocate GC memory and can safely be used with <c>foreach</c> loops.
1001 /// </remarks>
1002 /// <seealso cref="InputEventControlEnumerator"/>
1003 /// <seealso cref="StateEvent"/>
1004 /// <seealso cref="DeltaStateEvent"/>
1005 /// <seealso cref="EnumerateChangedControls"/>
1006 public static InputEventControlCollection EnumerateControls(this InputEventPtr eventPtr, Enumerate flags, InputDevice device = null, float magnitudeThreshold = 0)
1007 {
1008 if (!eventPtr.valid)
1009 throw new ArgumentNullException(nameof(eventPtr), "Given event pointer must not be null");
1010
1011 var eventType = eventPtr.type;
1012 if (eventType != StateEvent.Type && eventType != DeltaStateEvent.Type)
1013 throw new ArgumentException($"Event must be a StateEvent or DeltaStateEvent but is a {eventType} instead", nameof(eventPtr));
1014
1015 // Look up device from event, if no device was supplied.
1016 if (device == null)
1017 {
1018 var deviceId = eventPtr.deviceId;
1019 device = InputSystem.GetDeviceById(deviceId);
1020 if (device == null)
1021 throw new ArgumentException($"Cannot find device with ID {deviceId} referenced by event", nameof(eventPtr));
1022 }
1023
1024 return new InputEventControlCollection { m_Device = device, m_EventPtr = eventPtr, m_Flags = flags, m_MagnitudeThreshold = magnitudeThreshold };
1025 }
1026
1027 /// <summary>
1028 /// Go through all controls in the given <paramref name="eventPtr"/> that have changed value.
1029 /// </summary>
1030 /// <param name="eventPtr">An input event. Must be a <see cref="StateEvent"/> or <see cref="DeltaStateEvent"/>.</param>
1031 /// <param name="device">Input device from which to enumerate controls. If this is <c>null</c>, then the <see cref="InputEvent.deviceId"/>
1032 /// from the given <paramref name="eventPtr"/> will be used to locate the device via <see cref="InputSystem.GetDeviceById"/>. If the device
1033 /// cannot be found, an exception will be thrown. Note that type of device must match the state stored in the given event.</param>
1034 /// <param name="magnitudeThreshold">If not zero, minimum actuation threshold (see <see cref="InputControl.EvaluateMagnitude()"/>) that
1035 /// a control must reach (as per value in the given <paramref name="eventPtr"/>) in order for it to be included in the enumeration.</param>
1036 /// <returns>An enumerator for the controls that have changed values in <paramref name="eventPtr"/>.</returns>
1037 /// <remarks>
1038 /// This method is a shorthand for calling <see cref="EnumerateControls"/> with <see cref="Enumerate.IgnoreControlsInCurrentState"/>.
1039 ///
1040 /// <example>
1041 /// <code>
1042 /// // Detect button presses.
1043 /// InputSystem.onEvent +=
1044 /// (eventPtr, device) =>
1045 /// {
1046 /// // Ignore anything that is not a state event.
1047 /// var eventType = eventPtr.type;
1048 /// if (eventType != StateEvent.Type && eventType != DeltaStateEvent.Type)
1049 /// return;
1050 ///
1051 /// // Find all changed controls actuated above the button press threshold.
1052 /// foreach (var control in eventPtr.EnumerateChangedControls
1053 /// (device: device, magnitudeThreshold: InputSystem.settings.defaultButtonPressThreshold))
1054 /// if (control is ButtonControl button)
1055 /// Debug.Log($"Button {button} was pressed");
1056 /// }
1057 /// </code>
1058 /// </example>
1059 /// </remarks>
1060 /// <seealso cref="InputSystem.onEvent"/>
1061 /// <seealso cref="EnumerateControls"/>
1062 /// <seealso cref="InputEventControlEnumerator"/>
1063 public static InputEventControlCollection EnumerateChangedControls(this InputEventPtr eventPtr, InputDevice device = null, float magnitudeThreshold = 0)
1064 {
1065 return eventPtr.EnumerateControls
1066 (Enumerate.IgnoreControlsInCurrentState, device, magnitudeThreshold);
1067 }
1068
1069 /// <summary>
1070 /// Return true if the given <paramref name="eventPtr"/> has any <see cref="Input"/>
1071 /// </summary>
1072 /// <param name="eventPtr">An event. Must be a <see cref="StateEvent"/> or <see cref="DeltaStateEvent"/>.</param>
1073 /// <param name="magnitude">The threshold value that a button must be actuated by to be considered pressed.</param>
1074 /// <param name="buttonControlsOnly">Whether the method should only consider button controls.</param>
1075 /// <returns></returns>
1076 /// <exception cref="ArgumentNullException"><paramref name="eventPtr"/> is a <c>null</c> pointer.</exception>
1077 /// <exception cref="ArgumentException"><paramref name="eventPtr"/> is not a <see cref="StateEvent"/> or <see cref="DeltaStateEvent"/> -or-
1078 /// the <see cref="InputDevice"/> referenced by the <see cref="InputEvent.deviceId"/> in the event cannot be found.</exception>
1079 /// <seealso cref="EnumerateChangedControls"/>
1080 /// <seealso cref="ButtonControl.isPressed"/>
1081 public static bool HasButtonPress(this InputEventPtr eventPtr, float magnitude = -1, bool buttonControlsOnly = true)
1082 {
1083 return eventPtr.GetFirstButtonPressOrNull(magnitude, buttonControlsOnly) != null;
1084 }
1085
1086 /// <summary>
1087 /// Get the first pressed button from the given event or null if the event doesn't contain a new button press.
1088 /// </summary>
1089 /// <param name="eventPtr">An event. Must be a <see cref="StateEvent"/> or <see cref="DeltaStateEvent"/>.</param>
1090 /// <param name="magnitude">The threshold value that a control must be actuated by (see
1091 /// <see cref="InputControl.EvaluateMagnitude()"/>) to be considered pressed. If not given, defaults to <see
1092 /// cref="InputSettings.defaultButtonPressPoint"/>.</param>
1093 /// <param name="buttonControlsOnly">Whether the method should only consider <see cref="ButtonControl"/>s. Otherwise,
1094 /// any <see cref="InputControl"/> that has an actuation (see <see cref="InputControl.EvaluateMagnitude()"/>) equal to
1095 /// or greater than the given <paramref name="magnitude"/> will be considered a pressed button. This is 'true' by
1096 /// default.</param>
1097 /// <returns>The control that was pressed.</returns>
1098 /// <exception cref="ArgumentNullException"><paramref name="eventPtr"/> is a <c>null</c> pointer.</exception>
1099 /// <exception cref="ArgumentException">The <see cref="InputDevice"/> referenced by the <see cref="InputEvent.deviceId"/> in the event cannot
1100 /// be found.</exception>
1101 /// <seealso cref="EnumerateChangedControls"/>
1102 /// <seealso cref="ButtonControl.isPressed"/>
1103 /// <remarks>Buttons will be evaluated in the order that they appear in the devices layout i.e. the bit position of each control
1104 /// in the devices state memory. For example, in the gamepad state, button north (bit position 4) will be evaluated before button
1105 /// east (bit position 5), so if both buttons were pressed in the given event, button north would be returned.
1106 /// Note that the function returns null if the <paramref name="eventPtr"/> is not a StateEvent or DeltaStateEvent.</remarks>
1107 public static InputControl GetFirstButtonPressOrNull(this InputEventPtr eventPtr, float magnitude = -1, bool buttonControlsOnly = true)
1108 {
1109 if (eventPtr.type != StateEvent.Type && eventPtr.type != DeltaStateEvent.Type)
1110 return null;
1111
1112 if (magnitude < 0)
1113 magnitude = InputSystem.settings.defaultButtonPressPoint;
1114
1115 foreach (var control in eventPtr.EnumerateControls(Enumerate.IgnoreControlsInDefaultState, magnitudeThreshold: magnitude))
1116 {
1117 if (buttonControlsOnly && !control.isButton)
1118 continue;
1119 return control;
1120 }
1121 return null;
1122 }
1123
1124 /// <summary>
1125 /// Enumerate all pressed buttons in the given event.
1126 /// </summary>
1127 /// <param name="eventPtr">An event. Must be a <see cref="StateEvent"/> or <see cref="DeltaStateEvent"/>.</param>
1128 /// <param name="magnitude">The threshold value that a button must be actuated by to be considered pressed.</param>
1129 /// <param name="buttonControlsOnly">Whether the method should only consider button controls.</param>
1130 /// <returns>An enumerable collection containing all buttons that were pressed in the given event.</returns>
1131 /// <exception cref="ArgumentNullException"><paramref name="eventPtr"/> is a <c>null</c> pointer.</exception>
1132 /// <exception cref="ArgumentException">The <see cref="InputDevice"/> referenced by the <see cref="InputEvent.deviceId"/> in the event cannot be found.</exception>
1133 /// <remarks>Returns an empty enumerable if the <paramref name="eventPtr"/> is not a <see cref="StateEvent"/> or <see cref="DeltaStateEvent"/>.</remarks>
1134 /// <seealso cref="EnumerateChangedControls"/>
1135 /// <seealso cref="ButtonControl.isPressed"/>
1136 public static IEnumerable<InputControl> GetAllButtonPresses(this InputEventPtr eventPtr, float magnitude = -1, bool buttonControlsOnly = true)
1137 {
1138 if (eventPtr.type != StateEvent.Type && eventPtr.type != DeltaStateEvent.Type)
1139 yield break;
1140
1141 if (magnitude < 0)
1142 magnitude = InputSystem.settings.defaultButtonPressPoint;
1143
1144 foreach (var control in eventPtr.EnumerateControls(Enumerate.IgnoreControlsInDefaultState, magnitudeThreshold: magnitude))
1145 {
1146 if (buttonControlsOnly && !control.isButton)
1147 continue;
1148 yield return control;
1149 }
1150 }
1151
1152 /// <summary>
1153 /// Allows iterating over the controls referenced by an <see cref="InputEvent"/> via <see cref="InputEventControlEnumerator"/>.
1154 /// </summary>
1155 /// <seealso cref="InputControlExtensions.EnumerateControls"/>
1156 /// <seealso cref="InputControlExtensions.EnumerateChangedControls"/>
1157 public struct InputEventControlCollection : IEnumerable<InputControl>
1158 {
1159 internal InputDevice m_Device;
1160 internal InputEventPtr m_EventPtr;
1161 internal Enumerate m_Flags;
1162 internal float m_MagnitudeThreshold;
1163
1164 /// <summary>
1165 /// The event being iterated over. A <see cref="StateEvent"/> or <see cref="DeltaStateEvent"/>.
1166 /// </summary>
1167 public InputEventPtr eventPtr => m_EventPtr;
1168
1169 /// <summary>
1170 /// Enumerate the controls in the event.
1171 /// </summary>
1172 /// <returns>An enumerator.</returns>
1173 public InputEventControlEnumerator GetEnumerator()
1174 {
1175 return new InputEventControlEnumerator(m_EventPtr, m_Device, m_Flags, m_MagnitudeThreshold);
1176 }
1177
1178 IEnumerator<InputControl> IEnumerable<InputControl>.GetEnumerator()
1179 {
1180 return GetEnumerator();
1181 }
1182
1183 IEnumerator IEnumerable.GetEnumerator()
1184 {
1185 return GetEnumerator();
1186 }
1187 }
1188
1189 /// <summary>
1190 /// Iterates over the controls in a <see cref="StateEvent"/> or <see cref="DeltaStateEvent"/>
1191 /// while optionally applying certain filtering criteria.
1192 /// </summary>
1193 /// <remarks>
1194 /// One problem with state events (that is, <see cref="StateEvent"/> and <see cref="DeltaStateEvent"/>)
1195 /// is that they contain raw blocks of memory which may contain state changes for arbitrary many
1196 /// controls on a device at the same time. Locating individual controls and determining which have
1197 /// changed state and how can thus be quite inefficient.
1198 ///
1199 /// This helper aims to provide an easy and efficient way to iterate over controls relevant to a
1200 /// given state event. Instead of iterating over the controls of a device looking for the ones
1201 /// relevant to a given event, enumeration is done the opposite by efficiently searching through
1202 /// the memory contained in an event and then mapping data found in the event back to controls
1203 /// on a given device.
1204 ///
1205 /// <example>
1206 /// <code>
1207 /// // Inefficient:
1208 /// foreach (var control in device.allControls)
1209 /// {
1210 /// // Skip control if it is noisy, synthetic, or not a leaf control.
1211 /// if (control.synthetic || control.noisy || control.children.Count > 0)
1212 /// continue;
1213 ///
1214 /// // Locate the control in the event.
1215 /// var statePtr = eventPtr.GetStatePtrFromStateEvent(eventPtr);
1216 /// if (statePtr == null)
1217 /// continue; // Control not included in event.
1218 ///
1219 /// // See if the control is actuated beyond a minimum threshold.
1220 /// if (control.EvaluateMagnitude(statePtr) < 0.001f)
1221 /// continue;
1222 ///
1223 /// Debug.Log($"Found actuated control {control}");
1224 /// }
1225 ///
1226 /// // Much more efficient:
1227 /// foreach (var control in eventPtr.EnumerateControls(
1228 /// InputControlExtensions.Enumerate.IgnoreControlsInDefaultState,
1229 /// device: device,
1230 /// magnitudeThreshold: 0.001f))
1231 /// {
1232 /// Debug.Log($"Found actuated control {control}");
1233 /// }
1234 /// </code>
1235 /// </example>
1236 /// </remarks>
1237 /// <seealso cref="InputControlExtensions.EnumerateControls"/>
1238 /// <seealso cref="InputControlExtensions.EnumerateChangedControls"/>
1239 public unsafe struct InputEventControlEnumerator : IEnumerator<InputControl>
1240 {
1241 private Enumerate m_Flags;
1242 private readonly InputDevice m_Device;
1243 private readonly uint[] m_StateOffsetToControlIndex;
1244 private readonly int m_StateOffsetToControlIndexLength;
1245 private readonly InputControl[] m_AllControls;
1246 private byte* m_DefaultState; // Already offset by device offset.
1247 private byte* m_CurrentState;
1248 private byte* m_NoiseMask; // Already offset by device offset.
1249 private InputEventPtr m_EventPtr;
1250 private InputControl m_CurrentControl;
1251 private int m_CurrentIndexInStateOffsetToControlIndexMap;
1252 private uint m_CurrentControlStateBitOffset;
1253 private byte* m_EventState;
1254 private uint m_CurrentBitOffset;
1255 private uint m_EndBitOffset;
1256 private float m_MagnitudeThreshold;
1257
1258 internal InputEventControlEnumerator(InputEventPtr eventPtr, InputDevice device, Enumerate flags, float magnitudeThreshold = 0)
1259 {
1260 Debug.Assert(eventPtr.valid, "eventPtr should be valid at this point");
1261 Debug.Assert(device != null, "Need to have valid device at this point");
1262
1263 m_Device = device;
1264 m_StateOffsetToControlIndex = device.m_StateOffsetToControlMap;
1265 m_StateOffsetToControlIndexLength = m_StateOffsetToControlIndex.LengthSafe();
1266 m_AllControls = device.m_ChildrenForEachControl;
1267 m_EventPtr = eventPtr;
1268 m_Flags = flags;
1269 m_CurrentControl = null;
1270 m_CurrentIndexInStateOffsetToControlIndexMap = default;
1271 m_CurrentControlStateBitOffset = 0;
1272 m_EventState = default;
1273 m_CurrentBitOffset = default;
1274 m_EndBitOffset = default;
1275 m_MagnitudeThreshold = magnitudeThreshold;
1276
1277 if ((flags & Enumerate.IncludeNoisyControls) == 0)
1278 m_NoiseMask = (byte*)device.noiseMaskPtr + device.m_StateBlock.byteOffset;
1279 else
1280 m_NoiseMask = default;
1281
1282 if ((flags & Enumerate.IgnoreControlsInDefaultState) != 0)
1283 m_DefaultState = (byte*)device.defaultStatePtr + device.m_StateBlock.byteOffset;
1284 else
1285 m_DefaultState = default;
1286
1287 if ((flags & Enumerate.IgnoreControlsInCurrentState) != 0)
1288 m_CurrentState = (byte*)device.currentStatePtr + device.m_StateBlock.byteOffset;
1289 else
1290 m_CurrentState = default;
1291
1292 Reset();
1293 }
1294
1295 private bool CheckDefault(uint numBits)
1296 {
1297 return MemoryHelpers.MemCmpBitRegion(m_EventState, m_DefaultState, m_CurrentBitOffset, numBits, m_NoiseMask);
1298 }
1299
1300 private bool CheckCurrent(uint numBits)
1301 {
1302 return MemoryHelpers.MemCmpBitRegion(m_EventState, m_CurrentState, m_CurrentBitOffset, numBits, m_NoiseMask);
1303 }
1304
1305 public bool MoveNext()
1306 {
1307 if (!m_EventPtr.valid)
1308 throw new ObjectDisposedException("Enumerator has already been disposed");
1309
1310 // If we are to include non-leaf controls and we have a current control, walk
1311 // up the tree until we reach the device.
1312 if (m_CurrentControl != null && (m_Flags & Enumerate.IncludeNonLeafControls) != 0)
1313 {
1314 var parent = m_CurrentControl.parent;
1315 if (parent != m_Device)
1316 {
1317 m_CurrentControl = parent;
1318 return true;
1319 }
1320 }
1321
1322 var ignoreDefault = m_DefaultState != null;
1323 var ignoreCurrent = m_CurrentState != null;
1324
1325 // Search for the next control that matches our filter criteria.
1326 while (true)
1327 {
1328 m_CurrentControl = null;
1329
1330 // If we are ignoring certain state values, try to skip over as much memory as we can.
1331 if (ignoreCurrent || ignoreDefault)
1332 {
1333 // If we are not byte-aligned, search whatever bits are left in the current byte.
1334 if ((m_CurrentBitOffset & 0x7) != 0)
1335 {
1336 var bitsLeftInByte = (m_CurrentBitOffset + 8) & 0x7;
1337 if ((ignoreCurrent && CheckCurrent(bitsLeftInByte))
1338 || (ignoreDefault && CheckDefault(bitsLeftInByte)))
1339 m_CurrentBitOffset += bitsLeftInByte;
1340 }
1341
1342 // Search byte by byte.
1343 while (m_CurrentBitOffset < m_EndBitOffset)
1344 {
1345 var byteOffset = m_CurrentBitOffset >> 3;
1346 var eventByte = m_EventState[byteOffset];
1347 var maskByte = m_NoiseMask != null ? m_NoiseMask[byteOffset] : 0xff;
1348
1349 if (ignoreCurrent)
1350 {
1351 var currentByte = m_CurrentState[byteOffset];
1352 if ((currentByte & maskByte) == (eventByte & maskByte))
1353 {
1354 m_CurrentBitOffset += 8;
1355 continue;
1356 }
1357 }
1358
1359 if (ignoreDefault)
1360 {
1361 var defaultByte = m_DefaultState[byteOffset];
1362 if ((defaultByte & maskByte) == (eventByte & maskByte))
1363 {
1364 m_CurrentBitOffset += 8;
1365 continue;
1366 }
1367 }
1368
1369 break;
1370 }
1371 }
1372
1373 // See if we've reached the end.
1374 if (m_CurrentBitOffset >= m_EndBitOffset
1375 || m_CurrentIndexInStateOffsetToControlIndexMap >= m_StateOffsetToControlIndexLength) // No more controls.
1376 return false;
1377
1378 // No, so find the control at the current bit offset.
1379 for (;
1380 m_CurrentIndexInStateOffsetToControlIndexMap < m_StateOffsetToControlIndexLength;
1381 ++m_CurrentIndexInStateOffsetToControlIndexMap)
1382 {
1383 InputDevice.DecodeStateOffsetToControlMapEntry(
1384 m_StateOffsetToControlIndex[m_CurrentIndexInStateOffsetToControlIndexMap],
1385 out var controlIndex,
1386 out var controlBitOffset,
1387 out var controlBitSize);
1388
1389 // If the control's bit region lies *before* the memory we're looking at,
1390 // skip it.
1391 if (controlBitOffset < m_CurrentControlStateBitOffset ||
1392 m_CurrentBitOffset >= (controlBitOffset + controlBitSize - m_CurrentControlStateBitOffset))
1393 continue;
1394
1395 // If the bit region we're looking at lies *before* the current control,
1396 // keep searching through memory.
1397 if ((controlBitOffset - m_CurrentControlStateBitOffset) >= m_CurrentBitOffset + 8)
1398 {
1399 // Jump to location of control.
1400 m_CurrentBitOffset = controlBitOffset - m_CurrentControlStateBitOffset;
1401 break;
1402 }
1403
1404 // If the control's bit region runs past of what we actually have (may be the case both
1405 // with delta events and normal state events), skip it.
1406 if (controlBitOffset + controlBitSize - m_CurrentControlStateBitOffset > m_EndBitOffset)
1407 continue;
1408
1409 // If the control is byte-aligned both in its start offset and its length,
1410 // we have what we're looking for.
1411 if ((controlBitOffset & 0x7) == 0 && (controlBitSize & 0x7) == 0)
1412 {
1413 m_CurrentControl = m_AllControls[controlIndex];
1414 }
1415 else
1416 {
1417 // Otherwise, we may need to check the bit region specifically for the control.
1418 if ((ignoreCurrent && MemoryHelpers.MemCmpBitRegion(m_EventState, m_CurrentState, controlBitOffset - m_CurrentControlStateBitOffset, controlBitSize, m_NoiseMask))
1419 || (ignoreDefault && MemoryHelpers.MemCmpBitRegion(m_EventState, m_DefaultState, controlBitOffset - m_CurrentControlStateBitOffset, controlBitSize, m_NoiseMask)))
1420 continue;
1421
1422 m_CurrentControl = m_AllControls[controlIndex];
1423 }
1424
1425 if ((m_Flags & Enumerate.IncludeNoisyControls) == 0 && m_CurrentControl.noisy)
1426 {
1427 m_CurrentControl = null;
1428 continue;
1429 }
1430
1431 if ((m_Flags & Enumerate.IncludeSyntheticControls) == 0)
1432 {
1433 var controlHasSharedState = (m_CurrentControl.m_ControlFlags &
1434 (InputControl.ControlFlags.UsesStateFromOtherControl |
1435 InputControl.ControlFlags.IsSynthetic)) != 0;
1436
1437 // Filter out synthetic and useStateFrom controls.
1438 if (controlHasSharedState)
1439 {
1440 m_CurrentControl = null;
1441 continue;
1442 }
1443 }
1444
1445 ++m_CurrentIndexInStateOffsetToControlIndexMap;
1446 break;
1447 }
1448
1449 if (m_CurrentControl != null)
1450 {
1451 // If we are the filter by magnitude, last check is to go let the control evaluate
1452 // its magnitude based on the data in the event and if it's too low, keep searching.
1453 if (m_MagnitudeThreshold != 0)
1454 {
1455 var statePtr = m_EventState - (m_CurrentControlStateBitOffset >> 3) - m_Device.m_StateBlock.byteOffset;
1456 var magnitude = m_CurrentControl.EvaluateMagnitude(statePtr);
1457 if (magnitude >= 0 && magnitude < m_MagnitudeThreshold)
1458 continue;
1459 }
1460
1461 return true;
1462 }
1463 }
1464 }
1465
1466 public void Reset()
1467 {
1468 if (!m_EventPtr.valid)
1469 throw new ObjectDisposedException("Enumerator has already been disposed");
1470
1471 var eventType = m_EventPtr.type;
1472 FourCC stateFormat;
1473 if (eventType == StateEvent.Type)
1474 {
1475 var stateEvent = StateEvent.FromUnchecked(m_EventPtr);
1476 m_EventState = (byte*)stateEvent->state;
1477 m_EndBitOffset = stateEvent->stateSizeInBytes * 8;
1478 m_CurrentBitOffset = 0;
1479 stateFormat = stateEvent->stateFormat;
1480 }
1481 else if (eventType == DeltaStateEvent.Type)
1482 {
1483 var deltaEvent = DeltaStateEvent.FromUnchecked(m_EventPtr);
1484 m_EventState = (byte*)deltaEvent->deltaState - deltaEvent->stateOffset; // We access m_EventState as if it contains a full state event.
1485 m_CurrentBitOffset = deltaEvent->stateOffset * 8;
1486 m_EndBitOffset = m_CurrentBitOffset + deltaEvent->deltaStateSizeInBytes * 8;
1487 stateFormat = deltaEvent->stateFormat;
1488 }
1489 else
1490 {
1491 throw new NotSupportedException($"Cannot iterate over controls in event of type '{eventType}'");
1492 }
1493
1494 m_CurrentIndexInStateOffsetToControlIndexMap = 0;
1495 m_CurrentControlStateBitOffset = 0;
1496 m_CurrentControl = null;
1497
1498 // If the state format of the event does not match that of the device,
1499 // we need to go through the IInputStateCallbackReceiver machinery to adapt.
1500 if (stateFormat != m_Device.m_StateBlock.format)
1501 {
1502 var stateOffset = 0u;
1503 if (m_Device.hasStateCallbacks &&
1504 ((IInputStateCallbackReceiver)m_Device).GetStateOffsetForEvent(null, m_EventPtr, ref stateOffset))
1505 {
1506 m_CurrentControlStateBitOffset = stateOffset * 8;
1507 if (m_CurrentState != null)
1508 m_CurrentState += stateOffset;
1509 if (m_DefaultState != null)
1510 m_DefaultState += stateOffset;
1511 if (m_NoiseMask != null)
1512 m_NoiseMask += stateOffset;
1513 }
1514 else
1515 {
1516 // https://fogbugz.unity3d.com/f/cases/1395648/
1517 if (m_Device is Touchscreen && m_EventPtr.IsA<StateEvent>() &&
1518 StateEvent.FromUnchecked(m_EventPtr)->stateFormat == TouchState.Format)
1519 {
1520 // if GetStateOffsetForEvent(null, ...) return false on touchscreen it means that
1521 // we don't have a free slot for incoming touch, so ignore it for now
1522 }
1523 else
1524 throw new InvalidOperationException(
1525 $"{eventType} event with state format {stateFormat} cannot be used with device '{m_Device}'");
1526 }
1527 }
1528
1529 // NOTE: We *could* run a CheckDefault() or even CheckCurrent() over the entire event here to rule
1530 // it out entirely. However, we don't do so based on the assumption that *in general* this will
1531 // only *add* time. Rationale:
1532 //
1533 // - We assume that it is very rare for devices to send events matching the state the device
1534 // already has (i.e. *entire* event is just == current state).
1535 // - We assume that it is less common than the opposite for devices to send StateEvents containing
1536 // nothing but default state. This happens frequently for the keyboard but is very uncommon for mice,
1537 // touchscreens, and gamepads (where the sticks will almost never be exactly at default).
1538 // - We assume that for DeltaStateEvents it is in fact quite common to contain only default state but
1539 // that since in most cases these will contain state for either a very small set of controls or even
1540 // just a single one, the work we do in MoveNext somewhat closely matches that we'd here with a CheckXXX()
1541 // call but that we'd add work to every DeltaStateEvent if we were to have the upfront comparison here.
1542 }
1543
1544 public void Dispose()
1545 {
1546 m_EventPtr = default;
1547 }
1548
1549 public InputControl Current => m_CurrentControl;
1550
1551 object IEnumerator.Current => Current;
1552 }
1553
1554 // Undocumented APIs. Meant to be used only by auto-generated, precompiled layouts.
1555 // These APIs exist solely to keep access to the various properties/fields internal
1556 // and only allow their contents to be modified in a controlled manner.
1557 #region Undocumented
1558
1559 public static ControlBuilder Setup(this InputControl control)
1560 {
1561 if (control == null)
1562 throw new ArgumentNullException(nameof(control));
1563 if (control.isSetupFinished)
1564 throw new InvalidOperationException($"The setup of {control} cannot be modified; control is already in use");
1565
1566 return new ControlBuilder { control = control };
1567 }
1568
1569 public static DeviceBuilder Setup(this InputDevice device, int controlCount, int usageCount, int aliasCount)
1570 {
1571 if (device == null)
1572 throw new ArgumentNullException(nameof(device));
1573 if (device.isSetupFinished)
1574 throw new InvalidOperationException($"The setup of {device} cannot be modified; control is already in use");
1575 if (controlCount < 1)
1576 throw new ArgumentOutOfRangeException(nameof(controlCount));
1577 if (usageCount < 0)
1578 throw new ArgumentOutOfRangeException(nameof(usageCount));
1579 if (aliasCount < 0)
1580 throw new ArgumentOutOfRangeException(nameof(aliasCount));
1581
1582 device.m_Device = device;
1583
1584 device.m_ChildrenForEachControl = new InputControl[controlCount];
1585
1586 if (usageCount > 0)
1587 {
1588 device.m_UsagesForEachControl = new InternedString[usageCount];
1589 device.m_UsageToControl = new InputControl[usageCount];
1590 }
1591 if (aliasCount > 0)
1592 device.m_AliasesForEachControl = new InternedString[aliasCount];
1593
1594 return new DeviceBuilder { device = device };
1595 }
1596
1597 public struct ControlBuilder
1598 {
1599 public InputControl control { get; internal set; }
1600
1601 [MethodImpl(MethodImplOptions.AggressiveInlining)]
1602 public ControlBuilder At(InputDevice device, int index)
1603 {
1604 #if UNITY_EDITOR || DEVELOPMENT_BUILD
1605 if (device == null)
1606 throw new ArgumentNullException(nameof(device));
1607 if (index < 0 || index >= device.m_ChildrenForEachControl.Length)
1608 throw new ArgumentOutOfRangeException(nameof(index));
1609 #endif
1610 device.m_ChildrenForEachControl[index] = control;
1611 control.m_Device = device;
1612 return this;
1613 }
1614
1615 [MethodImpl(MethodImplOptions.AggressiveInlining)]
1616 public ControlBuilder WithParent(InputControl parent)
1617 {
1618 #if UNITY_EDITOR || DEVELOPMENT_BUILD
1619 if (parent == null)
1620 throw new ArgumentNullException(nameof(parent));
1621 if (parent == control)
1622 throw new ArgumentException("Control cannot be its own parent", nameof(parent));
1623 #endif
1624 control.m_Parent = parent;
1625 return this;
1626 }
1627
1628 [MethodImpl(MethodImplOptions.AggressiveInlining)]
1629 public ControlBuilder WithName(string name)
1630 {
1631 #if UNITY_EDITOR || DEVELOPMENT_BUILD
1632 if (string.IsNullOrEmpty(name))
1633 throw new ArgumentNullException(nameof(name));
1634 #endif
1635 control.m_Name = new InternedString(name);
1636 return this;
1637 }
1638
1639 [MethodImpl(MethodImplOptions.AggressiveInlining)]
1640 public ControlBuilder WithDisplayName(string displayName)
1641 {
1642 #if UNITY_EDITOR || DEVELOPMENT_BUILD
1643 if (string.IsNullOrEmpty(displayName))
1644 throw new ArgumentNullException(nameof(displayName));
1645 #endif
1646 control.m_DisplayNameFromLayout = new InternedString(displayName);
1647 return this;
1648 }
1649
1650 [MethodImpl(MethodImplOptions.AggressiveInlining)]
1651 public ControlBuilder WithShortDisplayName(string shortDisplayName)
1652 {
1653 #if UNITY_EDITOR || DEVELOPMENT_BUILD
1654 if (string.IsNullOrEmpty(shortDisplayName))
1655 throw new ArgumentNullException(nameof(shortDisplayName));
1656 #endif
1657 control.m_ShortDisplayNameFromLayout = new InternedString(shortDisplayName);
1658 return this;
1659 }
1660
1661 [MethodImpl(MethodImplOptions.AggressiveInlining)]
1662 public ControlBuilder WithLayout(InternedString layout)
1663 {
1664 #if UNITY_EDITOR || DEVELOPMENT_BUILD
1665 if (layout.IsEmpty())
1666 throw new ArgumentException("Layout name cannot be empty", nameof(layout));
1667 #endif
1668 control.m_Layout = layout;
1669 return this;
1670 }
1671
1672 [MethodImpl(MethodImplOptions.AggressiveInlining)]
1673 public ControlBuilder WithUsages(int startIndex, int count)
1674 {
1675 #if UNITY_EDITOR || DEVELOPMENT_BUILD
1676 if (startIndex < 0 || startIndex >= control.device.m_UsagesForEachControl.Length)
1677 throw new ArgumentOutOfRangeException(nameof(startIndex));
1678 if (count < 0 || startIndex + count > control.device.m_UsagesForEachControl.Length)
1679 throw new ArgumentOutOfRangeException(nameof(count));
1680 #endif
1681 control.m_UsageStartIndex = startIndex;
1682 control.m_UsageCount = count;
1683 return this;
1684 }
1685
1686 [MethodImpl(MethodImplOptions.AggressiveInlining)]
1687 public ControlBuilder WithAliases(int startIndex, int count)
1688 {
1689 #if UNITY_EDITOR || DEVELOPMENT_BUILD
1690 if (startIndex < 0 || startIndex >= control.device.m_AliasesForEachControl.Length)
1691 throw new ArgumentOutOfRangeException(nameof(startIndex));
1692 if (count < 0 || startIndex + count > control.device.m_AliasesForEachControl.Length)
1693 throw new ArgumentOutOfRangeException(nameof(count));
1694 #endif
1695 control.m_AliasStartIndex = startIndex;
1696 control.m_AliasCount = count;
1697 return this;
1698 }
1699
1700 [MethodImpl(MethodImplOptions.AggressiveInlining)]
1701 public ControlBuilder WithChildren(int startIndex, int count)
1702 {
1703 #if UNITY_EDITOR || DEVELOPMENT_BUILD
1704 if (startIndex < 0 || startIndex >= control.device.m_ChildrenForEachControl.Length)
1705 throw new ArgumentOutOfRangeException(nameof(startIndex));
1706 if (count < 0 || startIndex + count > control.device.m_ChildrenForEachControl.Length)
1707 throw new ArgumentOutOfRangeException(nameof(count));
1708 #endif
1709 control.m_ChildStartIndex = startIndex;
1710 control.m_ChildCount = count;
1711 return this;
1712 }
1713
1714 [MethodImpl(MethodImplOptions.AggressiveInlining)]
1715 public ControlBuilder WithStateBlock(InputStateBlock stateBlock)
1716 {
1717 control.m_StateBlock = stateBlock;
1718 return this;
1719 }
1720
1721 [MethodImpl(MethodImplOptions.AggressiveInlining)]
1722 public ControlBuilder WithDefaultState(PrimitiveValue value)
1723 {
1724 control.m_DefaultState = value;
1725 control.m_Device.hasControlsWithDefaultState = true;
1726 return this;
1727 }
1728
1729 [MethodImpl(MethodImplOptions.AggressiveInlining)]
1730 public ControlBuilder WithMinAndMax(PrimitiveValue min, PrimitiveValue max)
1731 {
1732 control.m_MinValue = min;
1733 control.m_MaxValue = max;
1734 return this;
1735 }
1736
1737 [MethodImpl(MethodImplOptions.AggressiveInlining)]
1738 public ControlBuilder WithProcessor<TProcessor, TValue>(TProcessor processor)
1739 where TValue : struct
1740 where TProcessor : InputProcessor<TValue>
1741 {
1742 #if UNITY_EDITOR || DEVELOPMENT_BUILD
1743 if (processor == null)
1744 throw new ArgumentNullException(nameof(processor));
1745 #endif
1746 ////REVIEW: have a parameterized version of ControlBuilder<TValue> so we don't need the cast?
1747 ////TODO: size array to exact needed size before-hand
1748 ((InputControl<TValue>)control).m_ProcessorStack.Append(processor);
1749 return this;
1750 }
1751
1752 [MethodImpl(MethodImplOptions.AggressiveInlining)]
1753 public ControlBuilder IsNoisy(bool value)
1754 {
1755 control.noisy = value;
1756 return this;
1757 }
1758
1759 [MethodImpl(MethodImplOptions.AggressiveInlining)]
1760 public ControlBuilder IsSynthetic(bool value)
1761 {
1762 control.synthetic = value;
1763 return this;
1764 }
1765
1766 [MethodImpl(MethodImplOptions.AggressiveInlining)]
1767 public ControlBuilder DontReset(bool value)
1768 {
1769 control.dontReset = value;
1770 if (value)
1771 control.m_Device.hasDontResetControls = true;
1772 return this;
1773 }
1774
1775 [MethodImpl(MethodImplOptions.AggressiveInlining)]
1776 public ControlBuilder IsButton(bool value)
1777 {
1778 control.isButton = value;
1779 return this;
1780 }
1781
1782 [MethodImpl(MethodImplOptions.AggressiveInlining)]
1783 public void Finish()
1784 {
1785 control.isSetupFinished = true;
1786 }
1787 }
1788
1789 public struct DeviceBuilder
1790 {
1791 public InputDevice device { get; internal set; }
1792
1793 [MethodImpl(MethodImplOptions.AggressiveInlining)]
1794 public DeviceBuilder WithName(string name)
1795 {
1796 #if UNITY_EDITOR || DEVELOPMENT_BUILD
1797 if (string.IsNullOrEmpty(name))
1798 throw new ArgumentNullException(nameof(name));
1799 #endif
1800 device.m_Name = new InternedString(name);
1801 return this;
1802 }
1803
1804 [MethodImpl(MethodImplOptions.AggressiveInlining)]
1805 public DeviceBuilder WithDisplayName(string displayName)
1806 {
1807 #if UNITY_EDITOR || DEVELOPMENT_BUILD
1808 if (string.IsNullOrEmpty(displayName))
1809 throw new ArgumentNullException(nameof(displayName));
1810 #endif
1811 device.m_DisplayNameFromLayout = new InternedString(displayName);
1812 return this;
1813 }
1814
1815 [MethodImpl(MethodImplOptions.AggressiveInlining)]
1816 public DeviceBuilder WithShortDisplayName(string shortDisplayName)
1817 {
1818 #if UNITY_EDITOR || DEVELOPMENT_BUILD
1819 if (string.IsNullOrEmpty(shortDisplayName))
1820 throw new ArgumentNullException(nameof(shortDisplayName));
1821 #endif
1822 device.m_ShortDisplayNameFromLayout = new InternedString(shortDisplayName);
1823 return this;
1824 }
1825
1826 [MethodImpl(MethodImplOptions.AggressiveInlining)]
1827 public DeviceBuilder WithLayout(InternedString layout)
1828 {
1829 #if UNITY_EDITOR || DEVELOPMENT_BUILD
1830 if (layout.IsEmpty())
1831 throw new ArgumentException("Layout name cannot be empty", nameof(layout));
1832 #endif
1833 device.m_Layout = layout;
1834 return this;
1835 }
1836
1837 [MethodImpl(MethodImplOptions.AggressiveInlining)]
1838 public DeviceBuilder WithChildren(int startIndex, int count)
1839 {
1840 #if UNITY_EDITOR || DEVELOPMENT_BUILD
1841 if (startIndex < 0 || startIndex >= device.device.m_ChildrenForEachControl.Length)
1842 throw new ArgumentOutOfRangeException(nameof(startIndex));
1843 if (count < 0 || startIndex + count > device.device.m_ChildrenForEachControl.Length)
1844 throw new ArgumentOutOfRangeException(nameof(count));
1845 #endif
1846 device.m_ChildStartIndex = startIndex;
1847 device.m_ChildCount = count;
1848 return this;
1849 }
1850
1851 [MethodImpl(MethodImplOptions.AggressiveInlining)]
1852 public DeviceBuilder WithStateBlock(InputStateBlock stateBlock)
1853 {
1854 device.m_StateBlock = stateBlock;
1855 return this;
1856 }
1857
1858 [MethodImpl(MethodImplOptions.AggressiveInlining)]
1859 public DeviceBuilder IsNoisy(bool value)
1860 {
1861 device.noisy = value;
1862 return this;
1863 }
1864
1865 [MethodImpl(MethodImplOptions.AggressiveInlining)]
1866 public DeviceBuilder WithControlUsage(int controlIndex, InternedString usage, InputControl control)
1867 {
1868 #if UNITY_EDITOR || DEVELOPMENT_BUILD
1869 if (controlIndex < 0 || controlIndex >= device.m_UsagesForEachControl.Length)
1870 throw new ArgumentOutOfRangeException(nameof(controlIndex));
1871 if (usage.IsEmpty())
1872 throw new ArgumentException(nameof(usage));
1873 if (control == null)
1874 throw new ArgumentNullException(nameof(control));
1875 #endif
1876 device.m_UsagesForEachControl[controlIndex] = usage;
1877 device.m_UsageToControl[controlIndex] = control;
1878 return this;
1879 }
1880
1881 [MethodImpl(MethodImplOptions.AggressiveInlining)]
1882 public DeviceBuilder WithControlAlias(int controlIndex, InternedString alias)
1883 {
1884 #if UNITY_EDITOR || DEVELOPMENT_BUILD
1885 if (controlIndex < 0 || controlIndex >= device.m_AliasesForEachControl.Length)
1886 throw new ArgumentOutOfRangeException(nameof(controlIndex));
1887 if (alias.IsEmpty())
1888 throw new ArgumentException(nameof(alias));
1889 #endif
1890 device.m_AliasesForEachControl[controlIndex] = alias;
1891 return this;
1892 }
1893
1894 [MethodImpl(MethodImplOptions.AggressiveInlining)]
1895 public DeviceBuilder WithStateOffsetToControlIndexMap(uint[] map)
1896 {
1897 device.m_StateOffsetToControlMap = map;
1898 return this;
1899 }
1900
1901 [MethodImpl(MethodImplOptions.AggressiveInlining)]
1902 public unsafe DeviceBuilder WithControlTree(byte[] controlTreeNodes, ushort[] controlTreeIndicies)
1903 {
1904 var sizeOfNode = UnsafeUtility.SizeOf<InputDevice.ControlBitRangeNode>();
1905 var numNodes = controlTreeNodes.Length / sizeOfNode;
1906 device.m_ControlTreeNodes = new InputDevice.ControlBitRangeNode[numNodes];
1907 fixed(byte* nodePtr = controlTreeNodes)
1908 {
1909 for (var i = 0; i < numNodes; i++)
1910 {
1911 device.m_ControlTreeNodes[i] = *(InputDevice.ControlBitRangeNode*)(nodePtr + i * sizeOfNode);
1912 }
1913 }
1914
1915 device.m_ControlTreeIndices = controlTreeIndicies;
1916 return this;
1917 }
1918
1919 [MethodImpl(MethodImplOptions.AggressiveInlining)]
1920 public void Finish()
1921 {
1922 // Set up the list of just ButtonControls to quickly update press state.
1923 var i = 0;
1924 foreach (var control in device.allControls)
1925 {
1926 // Don't use .isButton here, since this can be called from tests with NULL controls
1927 if (control is ButtonControl)
1928 ++i;
1929 }
1930
1931 device.m_ButtonControlsCheckingPressState = new List<ButtonControl>(i);
1932 #if UNITY_2020_1_OR_NEWER
1933 device.m_UpdatedButtons = new HashSet<int>(i);
1934 #else
1935 // 2019 is too old to support setting HashSet capacity
1936 device.m_UpdatedButtons = new HashSet<int>();
1937 #endif
1938
1939 device.isSetupFinished = true;
1940 }
1941 }
1942
1943 #endregion
1944 }
1945}