A game about forced loneliness, made by TACStudios
1using System;
2using System.Collections.Generic;
3using System.Diagnostics;
4using System.Runtime.CompilerServices;
5using UnityEngine.InputSystem.Controls;
6using UnityEngine.InputSystem.LowLevel;
7using UnityEngine.InputSystem.Utilities;
8using Unity.Collections.LowLevel.Unsafe;
9using UnityEngine.InputSystem.Layouts;
10
11////REVIEW: should EvaluateMagnitude() be called EvaluateActuation() or something similar?
12
13////REVIEW: as soon as we gain the ability to have blittable type constraints, InputControl<TValue> should be constrained such
14
15////REVIEW: Reading and writing is asymmetric. Writing does not involve processors, reading does.
16
17////REVIEW: While the arrays used by controls are already nicely centralized on InputDevice, InputControls still
18//// hold a bunch of reference data that requires separate scanning. Can we move *all* reference data to arrays
19//// on InputDevice and make InputControls reference-free? Most challenging thing probably is getting rid of
20//// the InputDevice reference itself.
21
22////REVIEW: how do we do stuff like smoothing over time?
23
24////TODO: allow easier access to the default state such that you can easily create a state event containing only default state
25
26////TODO: come up with a way where we do ReadValue on the most common forms/setups of controls and not have any virtual method dispatch but
27//// rather go with minimal overhead directly to reading out memory
28//// (this should at least cover FLT, single BIT, and INT controls; and should be able to apply the common transformations
29//// as per AxisControl)
30
31namespace UnityEngine.InputSystem
32{
33 /// <summary>
34 /// A typed and named source of input values in a hierarchy of controls.
35 /// </summary>
36 /// <remarks>
37 /// Controls can have children which in turn may have children. At the root of the child
38 /// hierarchy is always an <see cref="InputDevice"/> (which themselves are InputControls).
39 ///
40 /// Controls can be looked up by their <see cref="path"/> (see <see cref="InputControlPath.TryFindControl"/>).
41 ///
42 /// Each control must have a unique <see cref="name"/> within the <see cref="children"/> of
43 /// its <see cref="parent"/>. Multiple names can be assigned to controls using aliases (see
44 /// <see cref="aliases"/>). Name lookup is case-insensitive.
45 ///
46 /// For display purposes, a control may have a separate <see cref="displayName"/>. This name
47 /// will usually correspond to what the control is caused on the actual underlying hardware.
48 /// For example, on an Xbox gamepad, the control with the name "buttonSouth" will have a display
49 /// name of "A". Controls that have very long display names may also have a <see cref="shortDisplayName"/>.
50 /// This is the case for the "Left Button" on the <see cref="Mouse"/>, for example, which is
51 /// commonly abbreviated "LMB".
52 ///
53 /// In addition to names, a control may have usages associated with it (see <see cref="usages"/>).
54 /// A usage indicates how a control is meant to be used. For example, a button can be assigned
55 /// the "PrimaryAction" usage to indicate it is the primary action button the device. Within a
56 /// device, usages have to be unique. See <see cref="CommonUsages"/> for a list of standardized usages.
57 ///
58 /// Controls do not actually store values. Instead, every control receives an <see cref="InputStateBlock"/>
59 /// which, after the control's device has been added to the system, is used to read out values
60 /// from the device's backing store. This backing store is referred to as "state" in the API
61 /// as opposed to "values" which represent the data resulting from reading state. The format that
62 /// each control stores state in is specific to the control. It can vary not only between controls
63 /// of different types but also between controls of the same type. An <see cref="AxisControl"/>,
64 /// for example, can be stored as a float or as a byte or in a number of other formats. <see cref="stateBlock"/>
65 /// identifies both where the control stores its state as well as the format it stores it in.
66 ///
67 /// Controls are generally not created directly but are created internally by the input system
68 /// from data known as "layouts" (see <see cref="InputControlLayout"/>). Each such layout describes
69 /// the setup of a specific hierarchy of controls. The system internally maintains a registry of
70 /// layouts and produces devices and controls from them as needed. The layout that a control has
71 /// been created from can be queried using <see cref="layout"/>. For most purposes, the intricacies
72 /// of the control layout mechanisms can be ignored and it is sufficient to know the names of a
73 /// small set of common device layouts such as "Keyboard", "Mouse", "Gamepad", and "Touchscreen".
74 ///
75 /// Each control has a single, fixed value type. The type can be queried at runtime using
76 /// <see cref="valueType"/>. Most types of controls are derived from <see cref="InputControl{TValue}"/>
77 /// which has APIs specific to the type of value of the control (e.g. <see cref="InputControl{TValue}.ReadValue()"/>.
78 ///
79 /// The following example demonstrates various common operations performed on input controls:
80 ///
81 /// <example>
82 /// <code>
83 /// // Look up dpad/up control on current gamepad.
84 /// var dpadUpControl = Gamepad.current["dpad/up"];
85 ///
86 /// // Look up the back button on the current gamepad.
87 /// var backButton = Gamepad.current["{Back}"];
88 ///
89 /// // Look up all dpad/up controls on all gamepads in the system.
90 /// using (var controls = InputSystem.FindControls("<Gamepad>/dpad/up"))
91 /// Debug.Log($"Found {controls.Count} controls");
92 ///
93 /// // Display the value of all controls on the current gamepad.
94 /// foreach (var control in Gamepad.current.allControls)
95 /// Debug.Log(controls.ReadValueAsObject());
96 ///
97 /// // Track the value of the left stick on the current gamepad over time.
98 /// var leftStickHistory = new InputStateHistory(Gamepad.current.leftStick);
99 /// leftStickHistory.Enable();
100 /// </code>
101 /// </example>
102 /// <example>
103 /// </example>
104 /// </remarks>
105 /// <see cref="InputControl{TValue}"/>
106 /// <seealso cref="InputDevice"/>
107 /// <seealso cref="InputControlPath"/>
108 /// <seealso cref="InputStateBlock"/>
109 [DebuggerDisplay("{DebuggerDisplay(),nq}")]
110 public abstract class InputControl
111 {
112 /// <summary>
113 /// The name of the control, i.e. the final name part in its path.
114 /// </summary>
115 /// <remarks>
116 /// Names of controls must be unique within the context of their parent.
117 ///
118 /// Note that this is the name of the control as assigned internally (like "buttonSouth")
119 /// and not necessarily a good display name. Use <see cref="displayName"/> for
120 /// getting more readable names for display purposes (where available).
121 ///
122 /// Lookup of names is case-insensitive.
123 ///
124 /// This is set from the name of the control in the layout.
125 /// </remarks>
126 /// <seealso cref="path"/>
127 /// <seealso cref="aliases"/>
128 /// <seealso cref="InputControlAttribute.name"/>
129 /// <seealso cref="InputControlLayout.ControlItem.name"/>
130 public string name => m_Name;
131
132 ////TODO: protect against empty strings
133 /// <summary>
134 /// The text to display as the name of the control.
135 /// </summary>
136 /// <remarks>
137 /// Note that the display name of a control may change over time. For example, when changing
138 /// from a QWERTY keyboard layout to an AZERTY keyboard layout, the "q" key (which will keep
139 /// that <see cref="name"/>) will change its display name from "q" to "a".
140 ///
141 /// By default, a control's display name will come from its layout. If it is not assigned
142 /// a display name there, the display name will default to <see cref="name"/>. However, specific
143 /// controls may override this behavior. <see cref="KeyControl"/>, for example, will set the
144 /// display name to the actual key name corresponding to the current keyboard layout.
145 ///
146 /// For nested controls, the display name will include the display names of all parent controls,
147 /// i.e. the display name will fully identify the control on the device. For example, the display
148 /// name for the left D-Pad button on a gamepad is "D-Pad Left" and not just "Left".
149 /// </remarks>
150 /// <seealso cref="shortDisplayName"/>
151 public string displayName
152 {
153 get
154 {
155 RefreshConfigurationIfNeeded();
156 if (m_DisplayName != null)
157 return m_DisplayName;
158 if (m_DisplayNameFromLayout != null)
159 return m_DisplayNameFromLayout;
160 return m_Name;
161 }
162 // This is not public as a domain reload will wipe the change. This should really
163 // come from the control itself *if* the control wants to have a custom display name
164 // not driven by its layout.
165 protected set => m_DisplayName = value;
166 }
167
168 /// <summary>
169 /// An alternate, abbreviated <see cref="displayName"/> (for example "LMB" instead of "Left Button").
170 /// </summary>
171 /// <remarks>
172 /// If the control has no abbreviated version, this will be null. Note that this behavior is different
173 /// from <see cref="displayName"/> which will fall back to <see cref="name"/> if no display name has
174 /// been assigned to the control.
175 ///
176 /// For nested controls, the short display name will include the short display names of all parent controls,
177 /// that is, the display name will fully identify the control on the device. For example, the display
178 /// name for the left D-Pad button on a gamepad is "D-Pad \u2190" and not just "\u2190". Note that if a parent
179 /// control has no short name, its long name will be used instead.
180 /// </remarks>
181 /// <seealso cref="displayName"/>
182 public string shortDisplayName
183 {
184 get
185 {
186 RefreshConfigurationIfNeeded();
187 if (m_ShortDisplayName != null)
188 return m_ShortDisplayName;
189 if (m_ShortDisplayNameFromLayout != null)
190 return m_ShortDisplayNameFromLayout;
191 return null;
192 }
193 protected set => m_ShortDisplayName = value;
194 }
195
196 /// <summary>
197 /// Full path all the way from the root.
198 /// </summary>
199 /// <remarks>
200 /// This will always be the "effective" path of the control, i.e. it will not contain
201 /// elements such as usages (<c>"{Back}"</c>) and other elements that can be part of
202 /// control paths used for matching. Instead, this property will always be a simple
203 /// linear ordering of names leading from the device at the top to the control with each
204 /// element being separated by a forward slash (<c>/</c>).
205 ///
206 /// Allocates on first hit. Paths are not created until someone asks for them.
207 ///
208 /// <example>
209 /// Example: "/gamepad/leftStick/x"
210 /// </example>
211 /// </remarks>
212 /// <seealso cref="InputControlPath"/>
213 public string path
214 {
215 get
216 {
217 if (m_Path == null)
218 m_Path = InputControlPath.Combine(m_Parent, m_Name);
219 return m_Path;
220 }
221 }
222
223 /// <summary>
224 /// Layout the control is based on.
225 /// </summary>
226 /// <remarks>
227 /// This is the layout name rather than a reference to an <see cref="InputControlLayout"/> as
228 /// we only create layout instances during device creation and treat them
229 /// as temporaries in general so as to not waste heap space during normal operation.
230 /// </remarks>
231 public string layout => m_Layout;
232
233 /// <summary>
234 /// Semicolon-separated list of variants of the control layout or "default".
235 /// </summary>
236 /// <example>
237 /// "Lefty" when using the "Lefty" gamepad layout.
238 /// </example>
239 public string variants => m_Variants;
240
241 /// <summary>
242 /// The device that this control is a part of.
243 /// </summary>
244 /// <remarks>
245 /// This is the root of the control hierarchy. For the device at the root, this
246 /// will point to itself.
247 /// </remarks>
248 /// <seealso cref="InputDevice.allControls"/>
249 public InputDevice device => m_Device;
250
251 /// <summary>
252 /// The immediate parent of the control or null if the control has no parent
253 /// (which, once fully constructed) will only be the case for InputDevices).
254 /// </summary>
255 /// <seealso cref="children"/>
256 public InputControl parent => m_Parent;
257
258 /// <summary>
259 /// List of immediate children.
260 /// </summary>
261 /// <remarks>
262 /// Does not allocate.
263 /// </remarks>
264 /// <seealso cref="parent"/>
265 public ReadOnlyArray<InputControl> children =>
266 new ReadOnlyArray<InputControl>(m_Device.m_ChildrenForEachControl, m_ChildStartIndex, m_ChildCount);
267
268 /// <summary>
269 /// List of usage tags associated with the control.
270 /// </summary>
271 /// <remarks>
272 /// Usages apply "semantics" to a control. Whereas the name of a control identifies a particular
273 /// "endpoint" within the control hierarchy, the usages of a control identify particular roles
274 /// of specific control. A simple example is <see cref="CommonUsages.Back"/> which identifies a
275 /// control generally used to move backwards in the navigation history of a UI. On a keyboard,
276 /// it is the escape key that generally fulfills this role whereas on a gamepad, it is generally
277 /// the "B" / "Circle" button. Some devices may not have a control that generally fulfills this
278 /// function and thus may not have any control with the "Back" usage.
279 ///
280 /// By looking up controls by usage rather than by name, it is possible to locate the correct
281 /// control to use for certain standardized situation without having to know the particulars of
282 /// the device or platform.
283 ///
284 /// <example>
285 /// <code>
286 /// // Bind to any control which is tagged with the "Back" usage on any device.
287 /// var backAction = new InputAction(binding: "*/{Back}");
288 /// </code>
289 /// </example>
290 ///
291 /// Note that usages on devices work slightly differently than usages of controls on devices.
292 /// They are also queried through this property but unlike the usages of controls, the set of
293 /// usages of a device can be changed dynamically as the role of the device changes. For details,
294 /// see <see cref="InputSystem.SetDeviceUsage(InputDevice,string)"/>. Controls, on the other hand,
295 /// can currently only be assigned usages through layouts (<see cref="InputControlAttribute.usage"/>
296 /// or <see cref="InputControlAttribute.usages"/>).
297 /// </remarks>
298 /// <seealso cref="InputControlAttribute.usage"/>
299 /// <seealso cref="InputControlAttribute.usages"/>
300 /// <seealso cref="InputSystem.SetDeviceUsage(InputDevice,string)"/>
301 /// <seealso cref="InputSystem.AddDeviceUsage(InputDevice,string)"/>
302 /// <seealso cref="InputSystem.RemoveDeviceUsage(InputDevice,string)"/>
303 /// <seealso cref="CommonUsages"/>
304 public ReadOnlyArray<InternedString> usages =>
305 new ReadOnlyArray<InternedString>(m_Device.m_UsagesForEachControl, m_UsageStartIndex, m_UsageCount);
306
307 // List of alternate names for the control.
308 public ReadOnlyArray<InternedString> aliases =>
309 new ReadOnlyArray<InternedString>(m_Device.m_AliasesForEachControl, m_AliasStartIndex, m_AliasCount);
310
311 // Information about where the control stores its state.
312 public InputStateBlock stateBlock => m_StateBlock;
313
314 /// <summary>
315 /// Whether the control is considered noisy.
316 /// </summary>
317 /// <value>True if the control produces noisy input.</value>
318 /// <remarks>
319 /// A control is considered "noisy" if it produces different values without necessarily requiring user
320 /// interaction. A good example are sensors (see <see cref="Sensor"/>). For example, the PS4 controller
321 /// which has a gyroscope sensor built into the device. Whereas sticks and buttons on the device require
322 /// user interaction to produce non-default values, the gyro will produce varying values even if the
323 /// device just sits there without user interaction.
324 ///
325 /// The value of this property is determined by the layout (<see cref="InputControlLayout"/>) that the
326 /// control has been built from.
327 ///
328 /// Note that for devices (<see cref="InputDevice"/>) this property is true if any control on the device
329 /// is marked as noisy.
330 ///
331 /// The primary effect of being noise is on <see cref="InputDevice.MakeCurrent"/> and
332 /// on interactive rebinding (see <see cref="InputActionRebindingExtensions.RebindingOperation"/>).
333 /// However, being noisy also affects automatic resetting of controls that happens when the application
334 /// loses focus. While other controls are reset to their default value (except if <c>Application.runInBackground</c>
335 /// is true and the device the control belongs to is marked as <see cref="InputDevice.canRunInBackground"/>),
336 /// noisy controls will not be reset but rather remain at their current value. This is based on the assumption
337 /// that noisy controls most often represent sensor values and snapping the last sampling value back to default
338 /// will usually have undesirable effects on an application's simulation logic.
339 /// </remarks>
340 /// <seealso cref="InputControlLayout.ControlItem.isNoisy"/>
341 /// <seealso cref="InputControlAttribute.noisy"/>
342 public bool noisy
343 {
344 get => (m_ControlFlags & ControlFlags.IsNoisy) != 0;
345 internal set
346 {
347 if (value)
348 {
349 m_ControlFlags |= ControlFlags.IsNoisy;
350 // Making a control noisy makes all its children noisy.
351 var list = children;
352 for (var i = 0; i < list.Count; ++i)
353 {
354 if (null != list[i])
355 list[i].noisy = true;
356 }
357 }
358 else
359 m_ControlFlags &= ~ControlFlags.IsNoisy;
360 }
361 }
362
363 /// <summary>
364 /// Whether the control is considered synthetic.
365 /// </summary>
366 /// <value>True if the control does not represent an actual physical control on the device.</value>
367 /// <remarks>
368 /// A control is considered "synthetic" if it does not correspond to an actual, physical control on the
369 /// device. An example for this is <see cref="Keyboard.anyKey"/> or the up/down/left/right buttons added
370 /// by <see cref="StickControl"/>.
371 ///
372 /// The value of this property is determined by the layout (<see cref="InputControlLayout"/>) that the
373 /// control has been built from.
374 ///
375 /// The primary effect of being synthetic is in interactive rebinding (see
376 /// <see cref="InputActionRebindingExtensions.RebindingOperation"/>) where non-synthetic
377 /// controls will be favored over synthetic ones. This means, for example, that if both
378 /// <c>"<Gamepad>/leftStick/x"</c> and <c>"<Gamepad>/leftStick/left"</c> are
379 /// suitable picks, <c>"<Gamepad>/leftStick/x"</c> will be favored as it represents
380 /// input from an actual physical control whereas <c>"<Gamepad>/leftStick/left"</c>
381 /// represents input from a made-up control. If, however, the "left" button is the only
382 /// viable pick, it will be accepted.
383 /// </remarks>
384 /// <seealso cref="InputControlLayout.ControlItem.isSynthetic"/>
385 /// <seealso cref="InputControlAttribute.synthetic"/>
386 public bool synthetic
387 {
388 get => (m_ControlFlags & ControlFlags.IsSynthetic) != 0;
389 internal set
390 {
391 if (value)
392 m_ControlFlags |= ControlFlags.IsSynthetic;
393 else
394 m_ControlFlags &= ~ControlFlags.IsSynthetic;
395 }
396 }
397
398 /// <summary>
399 /// Fetch a control from the control's hierarchy by name.
400 /// </summary>
401 /// <remarks>
402 /// Note that path matching is case-insensitive.
403 /// </remarks>
404 /// <example>
405 /// <code>
406 /// gamepad["leftStick"] // Returns Gamepad.leftStick
407 /// gamepad["leftStick/x"] // Returns Gamepad.leftStick.x
408 /// gamepad["{PrimaryAction}"] // Returns the control with PrimaryAction usage, that is, Gamepad.aButton
409 /// </code>
410 /// </example>
411 /// <exception cref="KeyNotFoundException"><paramref name="path"/> cannot be found.</exception>
412 /// <seealso cref="InputControlPath"/>
413 /// <seealso cref="path"/>
414 /// <seealso cref="TryGetChildControl"/>
415 public InputControl this[string path]
416 {
417 get
418 {
419 var control = InputControlPath.TryFindChild(this, path);
420 if (control == null)
421 throw new KeyNotFoundException(
422 $"Cannot find control '{path}' as child of '{this}'");
423 return control;
424 }
425 }
426
427 /// <summary>
428 /// Returns the underlying value type of this control.
429 /// </summary>
430 /// <value>Type of values produced by the control.</value>
431 /// <remarks>
432 /// This is the type of values that are returned when reading the current value of a control
433 /// or when reading a value of a control from an event.
434 /// </remarks>
435 /// <seealso cref="valueSizeInBytes"/>
436 /// <seealso cref="ReadValueFromStateAsObject"/>
437 public abstract Type valueType { get; }
438
439 /// <summary>
440 /// Size in bytes of values that the control returns.
441 /// </summary>
442 /// <seealso cref="valueType"/>
443 public abstract int valueSizeInBytes { get; }
444
445 /// <summary>
446 /// Compute an absolute, normalized magnitude value that indicates the extent to which the control
447 /// is actuated. Shortcut for <see cref="EvaluateMagnitude()"/>.
448 /// </summary>
449 /// <returns>Amount of actuation of the control or -1 if it cannot be determined.</returns>
450 /// <seealso cref="EvaluateMagnitude(void*)"/>
451 /// <seealso cref="EvaluateMagnitude()"/>
452 public float magnitude => EvaluateMagnitude();
453
454 /// <summary>
455 /// Return a string representation of the control useful for debugging.
456 /// </summary>
457 /// <returns>A string representation of the control.</returns>
458 public override string ToString()
459 {
460 return $"{layout}:{path}";
461 }
462
463 private string DebuggerDisplay()
464 {
465 // If the device hasn't been added, don't try to read the control's value.
466 if (!device.added)
467 return ToString();
468
469 // ReadValueAsObject might throw. Revert to just ToString() in that case.
470 try
471 {
472 return $"{layout}:{path}={this.ReadValueAsObject()}";
473 }
474 catch (Exception)
475 {
476 return ToString();
477 }
478 }
479
480 ////REVIEW: The -1 behavior seems bad; probably better to just return 1 for controls that do not support finer levels of actuation
481 /// <summary>
482 /// Compute an absolute, normalized magnitude value that indicates the extent to which the control
483 /// is actuated.
484 /// </summary>
485 /// <returns>Amount of actuation of the control or -1 if it cannot be determined.</returns>
486 /// <remarks>
487 /// Magnitudes do not make sense for all types of controls. For example, for a control that represents
488 /// an enumeration of values (such as <see cref="TouchPhaseControl"/>), there is no meaningful
489 /// linear ordering of values (one could derive a linear ordering through the actual enum values but
490 /// their assignment may be entirely arbitrary; it is unclear whether a state of <see cref="TouchPhase.Canceled"/>
491 /// has a higher or lower "magnitude" as a state of <see cref="TouchPhase.Began"/>).
492 ///
493 /// Controls that have no meaningful magnitude will return -1 when calling this method. Any negative
494 /// return value should be considered an invalid value.
495 /// </remarks>
496 /// <seealso cref="EvaluateMagnitude(void*)"/>
497 public unsafe float EvaluateMagnitude()
498 {
499 return EvaluateMagnitude(currentStatePtr);
500 }
501
502 /// <summary>
503 /// Compute an absolute, normalized magnitude value that indicates the extent to which the control
504 /// is actuated in the given state.
505 /// </summary>
506 /// <param name="statePtr">State containing the control's <see cref="stateBlock"/>.</param>
507 /// <returns>Amount of actuation of the control or -1 if it cannot be determined.</returns>
508 /// <seealso cref="EvaluateMagnitude()"/>
509 /// <seealso cref="stateBlock"/>
510 public virtual unsafe float EvaluateMagnitude(void* statePtr)
511 {
512 return -1;
513 }
514
515 public abstract unsafe object ReadValueFromBufferAsObject(void* buffer, int bufferSize);
516
517 /// <summary>
518 /// Read the control's final, processed value from the given state and return the value as an object.
519 /// </summary>
520 /// <param name="statePtr"></param>
521 /// <returns>The control's value as stored in <paramref name="statePtr"/>.</returns>
522 /// <remarks>
523 /// This method allocates GC memory and should not be used during normal gameplay operation.
524 /// </remarks>
525 /// <exception cref="ArgumentNullException"><paramref name="statePtr"/> is null.</exception>
526 /// <seealso cref="ReadValueFromStateIntoBuffer"/>
527 public abstract unsafe object ReadValueFromStateAsObject(void* statePtr);
528
529 /// <summary>
530 /// Read the control's final, processed value from the given state and store it in the given buffer.
531 /// </summary>
532 /// <param name="statePtr">State to read the value for the control from.</param>
533 /// <param name="bufferPtr">Buffer to store the value in.</param>
534 /// <param name="bufferSize">Size of <paramref name="bufferPtr"/> in bytes. Must be at least <see cref="valueSizeInBytes"/>.
535 /// If it is smaller, <see cref="ArgumentException"/> will be thrown.</param>
536 /// <exception cref="ArgumentNullException"><paramref name="statePtr"/> is null, or <paramref name="bufferPtr"/> is null.</exception>
537 /// <exception cref="ArgumentException"><paramref name="bufferSize"/> is smaller than <see cref="valueSizeInBytes"/>.</exception>
538 /// <seealso cref="ReadValueFromStateAsObject"/>
539 /// <seealso cref="WriteValueFromBufferIntoState"/>
540 public abstract unsafe void ReadValueFromStateIntoBuffer(void* statePtr, void* bufferPtr, int bufferSize);
541
542 /// <summary>
543 /// Read a value from the given memory and store it as state.
544 /// </summary>
545 /// <param name="bufferPtr">Memory containing value.</param>
546 /// <param name="bufferSize">Size of <paramref name="bufferPtr"/> in bytes. Must be at least <see cref="valueSizeInBytes"/>.</param>
547 /// <param name="statePtr">State containing the control's <see cref="stateBlock"/>. Will receive the state
548 /// as converted from the given value.</param>
549 /// <remarks>
550 /// Writing values will NOT apply processors to the given value. This can mean that when reading a value
551 /// from a control after it has been written to its state, the resulting value differs from what was
552 /// written.
553 /// </remarks>
554 /// <exception cref="NotSupportedException">The control does not support writing. This is the case, for
555 /// example, that compute values (such as the magnitude of a vector).</exception>
556 /// <seealso cref="ReadValueFromStateIntoBuffer"/>
557 /// <seealso cref="WriteValueFromObjectIntoState"/>
558 public virtual unsafe void WriteValueFromBufferIntoState(void* bufferPtr, int bufferSize, void* statePtr)
559 {
560 throw new NotSupportedException(
561 $"Control '{this}' does not support writing");
562 }
563
564 /// <summary>
565 /// Read a value object and store it as state in the given memory.
566 /// </summary>
567 /// <param name="value">Value for the control.</param>
568 /// <param name="statePtr">State containing the control's <see cref="stateBlock"/>. Will receive
569 /// the state state as converted from the given value.</param>
570 /// <remarks>
571 /// Writing values will NOT apply processors to the given value. This can mean that when reading a value
572 /// from a control after it has been written to its state, the resulting value differs from what was
573 /// written.
574 /// </remarks>
575 /// <exception cref="NotSupportedException">The control does not support writing. This is the case, for
576 /// example, that compute values (such as the magnitude of a vector).</exception>
577 /// <seealso cref="WriteValueFromBufferIntoState"/>
578 public virtual unsafe void WriteValueFromObjectIntoState(object value, void* statePtr)
579 {
580 throw new NotSupportedException(
581 $"Control '{this}' does not support writing");
582 }
583
584 /// <summary>
585 /// Compare the value of the control as read from <paramref name="firstStatePtr"/> to that read from
586 /// <paramref name="secondStatePtr"/> and return true if they are equal.
587 /// </summary>
588 /// <param name="firstStatePtr">Memory containing the control's <see cref="stateBlock"/>.</param>
589 /// <param name="secondStatePtr">Memory containing the control's <see cref="stateBlock"/></param>
590 /// <returns>True if the value of the control is equal in both <paramref name="firstStatePtr"/> and
591 /// <paramref name="secondStatePtr"/>.</returns>
592 /// <remarks>
593 /// Unlike <see cref="CompareValue"/>, this method will have to do more than just compare the memory
594 /// for the control in the two state buffers. It will have to read out state for the control and run
595 /// the full processing machinery for the control to turn the state into a final, processed value.
596 /// CompareValue is thus more costly than <see cref="CompareValue"/>.
597 ///
598 /// This method will apply epsilons (<see cref="Mathf.Epsilon"/>) when comparing floats.
599 /// </remarks>
600 /// <seealso cref="CompareValue"/>
601 public abstract unsafe bool CompareValue(void* firstStatePtr, void* secondStatePtr);
602
603 /// <summary>
604 /// Try to find a child control matching the given path.
605 /// </summary>
606 /// <param name="path">A control path. See <see cref="InputControlPath"/>.</param>
607 /// <returns>The first direct or indirect child control that matches the given <paramref name="path"/>
608 /// or null if no control was found to match.</returns>
609 /// <exception cref="ArgumentNullException"><paramref name="path"/> is <c>null</c> or empty.</exception>
610 /// <remarks>
611 /// Note that if the given path matches multiple child controls, only the first control
612 /// encountered in the search will be returned.
613 ///
614 /// <example>
615 /// <code>
616 /// // Returns the leftStick control of the current gamepad.
617 /// Gamepad.current.TryGetChildControl("leftStick");
618 ///
619 /// // Returns the X axis control of the leftStick on the current gamepad.
620 /// Gamepad.current.TryGetChildControl("leftStick/x");
621 ///
622 /// // Returns the first control ending with "stick" in its name. Note that it
623 /// // undetermined whether this is leftStick or rightStick (or even another stick
624 /// // added by the given gamepad).
625 /// Gamepad.current.TryGetChildControl("*stick");
626 /// </code>
627 /// </example>
628 ///
629 /// This method is equivalent to calling <see cref="InputControlPath.TryFindChild"/>.
630 /// </remarks>
631 public InputControl TryGetChildControl(string path)
632 {
633 if (string.IsNullOrEmpty(path))
634 throw new ArgumentNullException(nameof(path));
635 return InputControlPath.TryFindChild(this, path);
636 }
637
638 public TControl TryGetChildControl<TControl>(string path)
639 where TControl : InputControl
640 {
641 if (string.IsNullOrEmpty(path))
642 throw new ArgumentNullException(nameof(path));
643
644 var control = TryGetChildControl(path);
645 if (control == null)
646 return null;
647
648 var controlOfType = control as TControl;
649 if (controlOfType == null)
650 throw new InvalidOperationException(
651 $"Expected control '{path}' to be of type '{typeof(TControl).Name}' but is of type '{control.GetType().Name}' instead!");
652
653 return controlOfType;
654 }
655
656 public InputControl GetChildControl(string path)
657 {
658 if (string.IsNullOrEmpty(path))
659 throw new ArgumentNullException(nameof(path));
660
661 var control = TryGetChildControl(path);
662 if (control == null)
663 throw new ArgumentException($"Cannot find input control '{MakeChildPath(path)}'", nameof(path));
664
665 return control;
666 }
667
668 public TControl GetChildControl<TControl>(string path)
669 where TControl : InputControl
670 {
671 var control = GetChildControl(path);
672
673 if (!(control is TControl controlOfType))
674 throw new ArgumentException(
675 $"Expected control '{path}' to be of type '{typeof(TControl).Name}' but is of type '{control.GetType().Name}' instead!", nameof(path));
676
677 return controlOfType;
678 }
679
680 protected InputControl()
681 {
682 // Set defaults for state block setup. Subclasses may override.
683 m_StateBlock.byteOffset = InputStateBlock.AutomaticOffset; // Request automatic layout by default.
684 }
685
686 /// <summary>
687 /// Perform final initialization tasks after the control hierarchy has been put into place.
688 /// </summary>
689 /// <remarks>
690 /// This method can be overridden to perform control- or device-specific setup work. The most
691 /// common use case is for looking up child controls and storing them in local getters.
692 ///
693 /// <example>
694 /// <code>
695 /// public class MyDevice : InputDevice
696 /// {
697 /// public ButtonControl button { get; private set; }
698 /// public AxisControl axis { get; private set; }
699 ///
700 /// protected override void OnFinishSetup()
701 /// {
702 /// // Cache controls in getters.
703 /// button = GetChildControl("button");
704 /// axis = GetChildControl("axis");
705 /// }
706 /// }
707 /// </code>
708 /// </example>
709 /// </remarks>
710 protected virtual void FinishSetup()
711 {
712 }
713
714 /// <summary>
715 /// Call <see cref="RefreshConfiguration"/> if the configuration has in the interim been invalidated
716 /// by a <see cref="DeviceConfigurationEvent"/>.
717 /// </summary>
718 /// <remarks>
719 /// This method is only relevant if you are implementing your own devices or new
720 /// types of controls which are fetching configuration data from the devices (such
721 /// as <see cref="KeyControl"/> which is fetching display names for individual keys
722 /// from the underlying platform).
723 ///
724 /// This method should be called if you are accessing cached data set up by
725 /// <see cref="RefreshConfiguration"/>.
726 ///
727 /// <example>
728 /// <code>
729 /// // Let's say your device has an associated orientation which it can be held with
730 /// // and you want to surface both as a property and as a usage on the device.
731 /// // Whenever your backend code detects a change in orientation, it should send
732 /// // a DeviceConfigurationEvent to your device to signal that the configuration
733 /// // of the device has changed. You can then implement RefreshConfiguration() to
734 /// // read out and update the device orientation on the managed InputDevice instance.
735 /// public class MyDevice : InputDevice
736 /// {
737 /// public enum Orientation
738 /// {
739 /// Horizontal,
740 /// Vertical,
741 /// }
742 ///
743 /// private Orientation m_Orientation;
744 /// public Orientation orientation
745 /// {
746 /// get
747 /// {
748 /// // Call RefreshOrientation if the configuration of the device has been
749 /// // invalidated since last time we initialized m_Orientation.
750 /// RefreshConfigurationIfNeeded();
751 /// return m_Orientation;
752 /// }
753 /// }
754 /// protected override void RefreshConfiguration()
755 /// {
756 /// // Fetch the current orientation from the backend. How you do this
757 /// // depends on your device. Using DeviceCommands is one way.
758 /// var fetchOrientationCommand = new FetchOrientationCommand();
759 /// ExecuteCommand(ref fetchOrientationCommand);
760 /// m_Orientation = fetchOrientation;
761 ///
762 /// // Reflect the orientation on the device.
763 /// switch (m_Orientation)
764 /// {
765 /// case Orientation.Vertical:
766 /// InputSystem.RemoveDeviceUsage(this, s_Horizontal);
767 /// InputSystem.AddDeviceUsage(this, s_Vertical);
768 /// break;
769 ///
770 /// case Orientation.Horizontal:
771 /// InputSystem.RemoveDeviceUsage(this, s_Vertical);
772 /// InputSystem.AddDeviceUsage(this, s_Horizontal);
773 /// break;
774 /// }
775 /// }
776 ///
777 /// private static InternedString s_Vertical = new InternedString("Vertical");
778 /// private static InternedString s_Horizontal = new InternedString("Horizontal");
779 /// }
780 /// </code>
781 /// </example>
782 /// </remarks>
783 /// <seealso cref="RefreshConfiguration"/>
784 protected void RefreshConfigurationIfNeeded()
785 {
786 if (!isConfigUpToDate)
787 {
788 RefreshConfiguration();
789 isConfigUpToDate = true;
790 }
791 }
792
793 protected virtual void RefreshConfiguration()
794 {
795 }
796
797 ////TODO: drop protected access
798 protected internal InputStateBlock m_StateBlock;
799
800 ////REVIEW: shouldn't these sit on the device?
801 protected internal unsafe void* currentStatePtr => InputStateBuffers.GetFrontBufferForDevice(GetDeviceIndex());
802
803 protected internal unsafe void* previousFrameStatePtr => InputStateBuffers.GetBackBufferForDevice(GetDeviceIndex());
804
805 protected internal unsafe void* defaultStatePtr => InputStateBuffers.s_DefaultStateBuffer;
806
807 /// <summary>
808 /// Return the memory that holds the noise mask for the control.
809 /// </summary>
810 /// <value>Noise bit mask for the control.</value>
811 /// <remarks>
812 /// Like with all state blocks, the specific memory block for the control is found at the memory
813 /// region specified by <see cref="stateBlock"/>.
814 ///
815 /// The noise mask can be overlaid as a bit mask over the state for the control. When doing so, all state
816 /// that is noise will be masked out whereas all state that isn't will come through unmodified. In other words,
817 /// any bit that is set in the noise mask indicates that the corresponding bit in the control's state memory
818 /// is noise.
819 /// </remarks>
820 /// <seealso cref="noisy"/>
821 protected internal unsafe void* noiseMaskPtr => InputStateBuffers.s_NoiseMaskBuffer;
822
823 /// <summary>
824 /// The offset of this control's state relative to its device root.
825 /// </summary>
826 /// <remarks>
827 /// Once a device has been added to the system, its state block will get allocated
828 /// in the global state buffers and the offset of the device's state block will
829 /// get baked into all of the controls on the device. This property always returns
830 /// the "unbaked" offset.
831 /// </remarks>
832 protected internal uint stateOffsetRelativeToDeviceRoot
833 {
834 get
835 {
836 var deviceStateOffset = device.m_StateBlock.byteOffset;
837 Debug.Assert(deviceStateOffset <= m_StateBlock.byteOffset);
838 return m_StateBlock.byteOffset - deviceStateOffset;
839 }
840 }
841
842 // This data is initialized by InputDeviceBuilder.
843 internal InternedString m_Name;
844 internal string m_Path;
845 internal string m_DisplayName; // Display name set by the control itself (may be null).
846 internal string m_DisplayNameFromLayout; // Display name coming from layout (may be null).
847 internal string m_ShortDisplayName; // Short display name set by the control itself (may be null).
848 internal string m_ShortDisplayNameFromLayout; // Short display name coming from layout (may be null).
849 internal InternedString m_Layout;
850 internal InternedString m_Variants;
851 internal InputDevice m_Device;
852 internal InputControl m_Parent;
853 internal int m_UsageCount;
854 internal int m_UsageStartIndex;
855 internal int m_AliasCount;
856 internal int m_AliasStartIndex;
857 internal int m_ChildCount;
858 internal int m_ChildStartIndex;
859 internal ControlFlags m_ControlFlags;
860
861 // Value caching
862 // These values will be set to true during state updates if the control has actually changed value.
863 // Set to true initially so default state will be returned on the first call
864 internal bool m_CachedValueIsStale = true;
865 internal bool m_UnprocessedCachedValueIsStale = true;
866
867 ////REVIEW: store these in arrays in InputDevice instead?
868 internal PrimitiveValue m_DefaultState;
869 internal PrimitiveValue m_MinValue;
870 internal PrimitiveValue m_MaxValue;
871
872 internal FourCC m_OptimizedControlDataType;
873
874 /// <summary>
875 /// For some types of control you can safely read/write state memory directly
876 /// which is much faster than calling ReadUnprocessedValueFromState/WriteValueIntoState.
877 /// This method returns a type that you can use for reading/writing the control directly,
878 /// or it returns InputStateBlock.kFormatInvalid if it's not possible for this type of control.
879 /// </summary>
880 /// <remarks>
881 /// For example, AxisControl might be a "float" in state memory, and if no processing is applied during reading (e.g. no invert/scale/etc),
882 /// then you could read it as float in memory directly without calling ReadUnprocessedValueFromState, which is faster.
883 /// Additionally, if you have a Vector3Control which uses 3 AxisControls as consecutive floats in memory,
884 /// you can cast the Vector3Control state memory directly to Vector3 without calling ReadUnprocessedValueFromState on x/y/z axes.
885 ///
886 /// The value returned for any given control is computed automatically by the Input System, when the control's setup configuration changes. <see cref="InputControl.CalculateOptimizedControlDataType"/>
887 /// There are some parameter changes which don't trigger a configuration change (such as the clamp, invert, normalize, and scale parameters on AxisControl),
888 /// so if you modify these, the optimized data type is not automatically updated. In this situation, you should manually update it by calling <see cref="InputControl.ApplyParameterChanges"/>.
889 /// </remarks>
890 public FourCC optimizedControlDataType => m_OptimizedControlDataType;
891
892 /// <summary>
893 /// Calculates and returns a optimized data type that can represent a control's value in memory directly.
894 /// The value then is cached in <see cref="InputControl.optimizedControlDataType"/>.
895 /// This method is for internal use only, you should not call this from your own code.
896 /// </summary>
897 protected virtual FourCC CalculateOptimizedControlDataType()
898 {
899 return InputStateBlock.kFormatInvalid;
900 }
901
902 /// <summary>
903 /// Apply built-in parameters changes (e.g. <see cref="AxisControl.invert"/>, others), recompute <see cref="InputControl.optimizedControlDataType"/> for impacted controls and clear cached value.
904 /// </summary>
905 /// <remarks>
906 /// </remarks>
907 public void ApplyParameterChanges()
908 {
909 // First we go through all children of our own hierarchy
910 SetOptimizedControlDataTypeRecursively();
911
912 // Then we go through all parents up to the root, because our own change might influence their optimization status
913 // e.g. let's say we have a tree where root is Vector3 and children are three AxisControl
914 // And user is calling this method on AxisControl which goes from Float to NotOptimized.
915 // Then we need to also transition Vector3 to NotOptimized as well.
916
917 var currentParent = parent;
918 while (currentParent != null)
919 {
920 currentParent.SetOptimizedControlDataType();
921 currentParent = currentParent.parent;
922 }
923
924 // Also use this method to mark cached values as stale
925 MarkAsStaleRecursively();
926 }
927
928 [MethodImpl(MethodImplOptions.AggressiveInlining)]
929 private void SetOptimizedControlDataType()
930 {
931 // setting check need to be inline so we clear optimizations if setting is disabled after the fact
932 m_OptimizedControlDataType = InputSystem.s_Manager.optimizedControlsFeatureEnabled
933 ? CalculateOptimizedControlDataType()
934 : (FourCC)InputStateBlock.kFormatInvalid;
935 }
936
937 [MethodImpl(MethodImplOptions.AggressiveInlining)]
938 internal void SetOptimizedControlDataTypeRecursively()
939 {
940 // Need to go depth-first because CalculateOptimizedControlDataType might depend on computed values of children
941 if (m_ChildCount > 0)
942 {
943 foreach (var inputControl in children)
944 inputControl.SetOptimizedControlDataTypeRecursively();
945 }
946
947 SetOptimizedControlDataType();
948 }
949
950 // This function exists to warn users to start using ApplyParameterChanges for edge cases that were previously not intentionally supported,
951 // where control properties suddenly change underneath us without us anticipating that.
952 // This is mainly to AxisControl fields being public and capable of changing at any time even if we were not anticipated such a usage pattern.
953 // Also it's not clear if InputControl.stateBlock.format can potentially change at any time, likely not.
954 [MethodImpl(MethodImplOptions.AggressiveInlining)]
955 // Only do this check in and editor in hope that it will be sufficient to catch any misuse during development.
956 // It is not done in debug builds because it has a performance cost and it will show up when profiled.
957 [Conditional("UNITY_EDITOR")]
958 internal void EnsureOptimizationTypeHasNotChanged()
959 {
960 if (!InputSystem.s_Manager.optimizedControlsFeatureEnabled)
961 return;
962
963 var currentOptimizedControlDataType = CalculateOptimizedControlDataType();
964 if (currentOptimizedControlDataType != optimizedControlDataType)
965 {
966 Debug.LogError(
967 $"Control '{name}' / '{path}' suddenly changed optimization state due to either format " +
968 $"change or control parameters change (was '{optimizedControlDataType}' but became '{currentOptimizedControlDataType}'), " +
969 "this hinders control hot path optimization, please call control.ApplyParameterChanges() " +
970 "after the changes to the control to fix this error.");
971
972 // Automatically fix the issue
973 // Note this function is only executed in the editor
974 m_OptimizedControlDataType = currentOptimizedControlDataType;
975 }
976
977 if (m_ChildCount > 0)
978 {
979 foreach (var inputControl in children)
980 inputControl.EnsureOptimizationTypeHasNotChanged();
981 }
982 }
983
984 [Flags]
985 internal enum ControlFlags
986 {
987 ConfigUpToDate = 1 << 0,
988 IsNoisy = 1 << 1,
989 IsSynthetic = 1 << 2,
990 IsButton = 1 << 3,
991 DontReset = 1 << 4,
992 SetupFinished = 1 << 5, // Can't be modified once this is set.
993 UsesStateFromOtherControl = 1 << 6,
994 }
995
996 internal bool isSetupFinished
997 {
998 get => (m_ControlFlags & ControlFlags.SetupFinished) == ControlFlags.SetupFinished;
999 set
1000 {
1001 if (value)
1002 m_ControlFlags |= ControlFlags.SetupFinished;
1003 else
1004 m_ControlFlags &= ~ControlFlags.SetupFinished;
1005 }
1006 }
1007
1008 internal bool isButton
1009 {
1010 get => (m_ControlFlags & ControlFlags.IsButton) == ControlFlags.IsButton;
1011 set
1012 {
1013 if (value)
1014 m_ControlFlags |= ControlFlags.IsButton;
1015 else
1016 m_ControlFlags &= ~ControlFlags.IsButton;
1017 }
1018 }
1019
1020 internal bool isConfigUpToDate
1021 {
1022 get => (m_ControlFlags & ControlFlags.ConfigUpToDate) == ControlFlags.ConfigUpToDate;
1023 set
1024 {
1025 if (value)
1026 m_ControlFlags |= ControlFlags.ConfigUpToDate;
1027 else
1028 m_ControlFlags &= ~ControlFlags.ConfigUpToDate;
1029 }
1030 }
1031
1032 internal bool dontReset
1033 {
1034 get => (m_ControlFlags & ControlFlags.DontReset) == ControlFlags.DontReset;
1035 set
1036 {
1037 if (value)
1038 m_ControlFlags |= ControlFlags.DontReset;
1039 else
1040 m_ControlFlags &= ~ControlFlags.DontReset;
1041 }
1042 }
1043
1044 internal bool usesStateFromOtherControl
1045 {
1046 get => (m_ControlFlags & ControlFlags.UsesStateFromOtherControl) == ControlFlags.UsesStateFromOtherControl;
1047 set
1048 {
1049 if (value)
1050 m_ControlFlags |= ControlFlags.UsesStateFromOtherControl;
1051 else
1052 m_ControlFlags &= ~ControlFlags.UsesStateFromOtherControl;
1053 }
1054 }
1055
1056 internal bool hasDefaultState => !m_DefaultState.isEmpty;
1057
1058 // This method exists only to not slap the internal interaction on all overrides of
1059 // FinishSetup().
1060 internal void CallFinishSetupRecursive()
1061 {
1062 var list = children;
1063 for (var i = 0; i < list.Count; ++i)
1064 list[i].CallFinishSetupRecursive();
1065 FinishSetup();
1066 SetOptimizedControlDataTypeRecursively();
1067 }
1068
1069 internal string MakeChildPath(string path)
1070 {
1071 if (this is InputDevice)
1072 return path;
1073 return $"{this.path}/{path}";
1074 }
1075
1076 internal void BakeOffsetIntoStateBlockRecursive(uint offset)
1077 {
1078 m_StateBlock.byteOffset += offset;
1079
1080 var list = children;
1081 for (var i = 0; i < list.Count; ++i)
1082 list[i].BakeOffsetIntoStateBlockRecursive(offset);
1083 }
1084
1085 internal int GetDeviceIndex()
1086 {
1087 var deviceIndex = m_Device.m_DeviceIndex;
1088 if (deviceIndex == InputDevice.kInvalidDeviceIndex)
1089 throw new InvalidOperationException(
1090 $"Cannot query value of control '{path}' before '{device.name}' has been added to system!");
1091 return deviceIndex;
1092 }
1093
1094 internal bool IsValueConsideredPressed(float value)
1095 {
1096 if (isButton)
1097 return ((ButtonControl)this).IsValueConsideredPressed(value);
1098 return value >= ButtonControl.s_GlobalDefaultButtonPressPoint;
1099 }
1100
1101 internal virtual void AddProcessor(object first)
1102 {
1103 }
1104
1105 internal void MarkAsStale()
1106 {
1107 m_CachedValueIsStale = true;
1108 m_UnprocessedCachedValueIsStale = true;
1109 }
1110
1111 internal void MarkAsStaleRecursively()
1112 {
1113 MarkAsStale();
1114
1115 foreach (var inputControl in children)
1116 {
1117 inputControl.MarkAsStale();
1118 if (inputControl is ButtonControl buttonControl)
1119 {
1120 // If everything is becoming stale, update all press states so we can reevaluate
1121 buttonControl.UpdateWasPressed();
1122 #if UNITY_EDITOR
1123 buttonControl.UpdateWasPressedEditor();
1124 #endif
1125 }
1126 }
1127 }
1128
1129 #if UNITY_EDITOR
1130 internal virtual IEnumerable<object> GetProcessors()
1131 {
1132 yield return null;
1133 }
1134
1135 #endif
1136 }
1137
1138 /// <summary>
1139 /// Base class for input controls with a specific value type.
1140 /// </summary>
1141 /// <typeparam name="TValue">Type of value captured by the control. Note that this does not mean
1142 /// that the control has to store data in the given value format. A control that captures float
1143 /// values, for example, may be stored in state as byte values instead.</typeparam>
1144 public abstract class InputControl<TValue> : InputControl
1145 where TValue : struct
1146 {
1147 public override Type valueType => typeof(TValue);
1148
1149 public override int valueSizeInBytes => UnsafeUtility.SizeOf<TValue>();
1150
1151 /// <summary>
1152 /// Returns the current value of the control after processors have been applied.
1153 /// </summary>
1154 /// <returns>The controls current value.</returns>
1155 /// <remarks>
1156 /// This can only be called on devices that have been added to the system (<see cref="InputDevice.added"/>).
1157 ///
1158 /// If internal feature "USE_READ_VALUE_CACHING" is enabled, then this property implements caching
1159 /// to avoid applying processors when the underlying control has not changed.
1160 /// With this in mind, be aware of processors that use global state, such as the <see cref="Processors.AxisDeadzoneProcessor"/>.
1161 /// Unless the control unprocessed value has been changed, input system settings changed or <see cref="InputControl.ApplyParameterChanges()"/> invoked,
1162 /// the processors will not run and calls to <see cref="value"/> will return the same result as previous calls.
1163 ///
1164 /// If a processor requires to be run on every read, override <see cref="InputProcessor.cachingPolicy"/> property
1165 /// in the processor and set it to <see cref="InputProcessor.CachingPolicy.EvaluateOnEveryRead"/>.
1166 ///
1167 /// To improve debugging try setting "PARANOID_READ_VALUE_CACHING_CHECKS" internal feature flag to check if cache value is still consistent.
1168 ///
1169 /// Also note that this property returns the result as ref readonly. If custom control states are in use, i.e.
1170 /// any controls not shipped with the Input System package, be careful of accidental defensive copies
1171 /// <see href="https://docs.microsoft.com/en-us/dotnet/csharp/write-safe-efficient-code#avoid-defensive-copies"/>.
1172 /// </remarks>
1173 /// <seealso cref="ReadValue"/>
1174 public ref readonly TValue value
1175 {
1176 get
1177 {
1178#if UNITY_EDITOR
1179 if (InputUpdate.s_LatestUpdateType.IsEditorUpdate())
1180 return ref ReadStateInEditor();
1181#endif
1182
1183 if (
1184 // if feature is disabled we re-evaluate every call
1185 !InputSystem.s_Manager.readValueCachingFeatureEnabled
1186 // if cached value is stale we re-evaluate and clear the flag
1187 || m_CachedValueIsStale
1188 // if a processor in stack needs to be re-evaluated, but unprocessedValue is still can be cached
1189 || evaluateProcessorsEveryRead
1190 )
1191 {
1192 m_CachedValue = ProcessValue(unprocessedValue);
1193 m_CachedValueIsStale = false;
1194 }
1195#if DEBUG
1196 else if (InputSystem.s_Manager.paranoidReadValueCachingChecksEnabled)
1197 {
1198 var oldUnprocessedValue = m_UnprocessedCachedValue;
1199 var newUnprocessedValue = unprocessedValue;
1200 var currentProcessedValue = ProcessValue(newUnprocessedValue);
1201
1202 if (CompareValue(ref newUnprocessedValue, ref oldUnprocessedValue))
1203 {
1204 // don't warn if unprocessedValue caching failed
1205 m_CachedValue = currentProcessedValue;
1206 }
1207 else if (CompareValue(ref currentProcessedValue, ref m_CachedValue))
1208 {
1209 // processors are not behaving as expected if unprocessedValue stays the same but processedValue changed
1210 var namesList = new List<string>();
1211 foreach (var inputProcessor in m_ProcessorStack)
1212 namesList.Add(inputProcessor.ToString());
1213 var names = string.Join(", ", namesList);
1214 Debug.LogError(
1215 "Cached processed value unexpectedly became outdated due to InputProcessor's returning a different value, " +
1216 $"new value '{currentProcessedValue}' old value '{m_CachedValue}', current processors are: {names}. " +
1217 "If your processor need to be recomputed on every read please add \"public override CachingPolicy cachingPolicy => CachingPolicy.EvaluateOnEveryRead;\" to the processor.");
1218 m_CachedValue = currentProcessedValue;
1219 }
1220 }
1221#endif
1222
1223 return ref m_CachedValue;
1224 }
1225 }
1226
1227 internal unsafe ref readonly TValue unprocessedValue
1228 {
1229 get
1230 {
1231#if UNITY_EDITOR
1232 if (InputUpdate.s_LatestUpdateType.IsEditorUpdate())
1233 return ref ReadUnprocessedStateInEditor();
1234#endif
1235 // Case ISXB-606
1236 // If an object reference has the underlying object deleted then a device can go
1237 // away which means that the underlying state buffers will have been resized.
1238 //
1239 // The currentStatePtr accessor uses GetDeviceIndex() to index into the state
1240 // buffers but this index can then be out of bounds.
1241 //
1242 // InputStateBuffers.Get{Front,Back}Buffer() now check for the requested index being
1243 // in-bounds and return null if not - check that here to avoid null derefence later.
1244 //
1245 if (currentStatePtr == null)
1246 {
1247 return ref m_UnprocessedCachedValue;
1248 }
1249
1250 if (
1251 // if feature is disabled we re-evaluate every call
1252 !InputSystem.s_Manager.readValueCachingFeatureEnabled
1253 // if cached value is stale we re-evaluate and clear the flag
1254 || m_UnprocessedCachedValueIsStale
1255 )
1256 {
1257 m_UnprocessedCachedValue = ReadUnprocessedValueFromState(currentStatePtr);
1258 m_UnprocessedCachedValueIsStale = false;
1259 }
1260#if DEBUG
1261 else if (InputSystem.s_Manager.paranoidReadValueCachingChecksEnabled)
1262 {
1263 var currentUnprocessedValue = ReadUnprocessedValueFromState(currentStatePtr);
1264 if (CompareValue(ref currentUnprocessedValue, ref m_UnprocessedCachedValue))
1265 {
1266 Debug.LogError($"Cached unprocessed value unexpectedly became outdated for unknown reason, new value '{currentUnprocessedValue}' old value '{m_UnprocessedCachedValue}'.");
1267 m_UnprocessedCachedValue = currentUnprocessedValue;
1268 }
1269 }
1270#endif
1271
1272 return ref m_UnprocessedCachedValue;
1273 }
1274 }
1275
1276 /// <summary>
1277 /// Returns the current value of the control after processors have been applied.
1278 /// </summary>
1279 /// <returns>The controls current value.</returns>
1280 /// <remarks>
1281 /// This can only be called on devices that have been added to the system (<see cref="InputDevice.added"/>).
1282 ///
1283 /// If internal feature "USE_READ_VALUE_CACHING" is enabled, then this property implements caching
1284 /// to avoid applying processors when the underlying control has not changed.
1285 /// With this in mind, be aware of processors that use global state, such as the <see cref="Processors.AxisDeadzoneProcessor"/>.
1286 /// Unless the control unprocessed value has been changed, input system settings changed or <see cref="InputControl.ApplyParameterChanges()"/> invoked,
1287 /// the processors will not run and calls to <see cref="value"/> will return the same result as previous calls.
1288 ///
1289 /// If a processor requires to be run on every read, override <see cref="InputProcessor.cachingPolicy"/> property
1290 /// in the processor and set it to <see cref="InputProcessor.CachingPolicy.EvaluateOnEveryRead"/>.
1291 ///
1292 /// To improve debugging try setting "PARANOID_READ_VALUE_CACHING_CHECKS" internal feature flag to check if cache value is still consistent.
1293 /// <see href="https://docs.microsoft.com/en-us/dotnet/csharp/write-safe-efficient-code#avoid-defensive-copies"/>.
1294 /// </remarks>
1295 /// <seealso cref="value"/>
1296 public TValue ReadValue()
1297 {
1298 return value;
1299 }
1300
1301 ////REVIEW: is 'frame' really the best wording here?
1302 /// <summary>
1303 /// Get the control's value from the previous frame (<see cref="InputControl.previousFrameStatePtr"/>).
1304 /// </summary>
1305 /// <returns>The control's value in the previous frame.</returns>
1306 public TValue ReadValueFromPreviousFrame()
1307 {
1308 unsafe
1309 {
1310 return ReadValueFromState(previousFrameStatePtr);
1311 }
1312 }
1313
1314 /// <summary>
1315 /// Get the control's default value.
1316 /// </summary>
1317 /// <returns>The control's default value.</returns>
1318 /// <remarks>
1319 /// This is not necessarily equivalent to <c>default(TValue)</c>. A control's default value is determined
1320 /// by reading its value from the default state (<see cref="InputControl.defaultStatePtr"/>) which in turn
1321 /// is determined from settings in the control's registered layout (<see cref="InputControlLayout.ControlItem.defaultState"/>).
1322 /// </remarks>
1323 public TValue ReadDefaultValue()
1324 {
1325 unsafe
1326 {
1327 return ReadValueFromState(defaultStatePtr);
1328 }
1329 }
1330
1331 public unsafe TValue ReadValueFromState(void* statePtr)
1332 {
1333 if (statePtr == null)
1334 throw new ArgumentNullException(nameof(statePtr));
1335 return ProcessValue(ReadUnprocessedValueFromState(statePtr));
1336 }
1337
1338 /// <summary>
1339 /// Read value from provided <paramref name="statePtr"/> and apply processors. Try cache result if possible.
1340 /// </summary>
1341 /// <param name="statePtr">State pointer to read from.</param>
1342 /// <returns>The controls current value.</returns>
1343 /// <remarks>
1344 /// If <paramref name="statePtr"/> is "currentStatePtr", then read will be done via <see cref="value"/> property to improve performance.
1345 /// </remarks>
1346 /// <seealso cref="value"/>
1347 public unsafe TValue ReadValueFromStateWithCaching(void* statePtr)
1348 {
1349 return statePtr == currentStatePtr ? value : ReadValueFromState(statePtr);
1350 }
1351
1352 /// <summary>
1353 /// Read value from provided <paramref name="statePtr"/>. Try cache result if possible.
1354 /// </summary>
1355 /// <param name="statePtr">State pointer to read from.</param>
1356 /// <returns>The controls current value.</returns>
1357 /// <remarks>
1358 /// If <paramref name="statePtr"/> is "currentStatePtr", then read will be done via <see cref="unprocessedValue"/> property to improve performance.
1359 /// </remarks>
1360 /// <seealso cref="value"/>
1361 public unsafe TValue ReadUnprocessedValueFromStateWithCaching(void* statePtr)
1362 {
1363 return statePtr == currentStatePtr ? unprocessedValue : ReadUnprocessedValueFromState(statePtr);
1364 }
1365
1366 public TValue ReadUnprocessedValue()
1367 {
1368 return unprocessedValue;
1369 }
1370
1371 public abstract unsafe TValue ReadUnprocessedValueFromState(void* statePtr);
1372
1373 /// <inheritdoc />
1374 public override unsafe object ReadValueFromStateAsObject(void* statePtr)
1375 {
1376 return ReadValueFromState(statePtr);
1377 }
1378
1379 /// <inheritdoc />
1380 public override unsafe void ReadValueFromStateIntoBuffer(void* statePtr, void* bufferPtr, int bufferSize)
1381 {
1382 if (statePtr == null)
1383 throw new ArgumentNullException(nameof(statePtr));
1384 if (bufferPtr == null)
1385 throw new ArgumentNullException(nameof(bufferPtr));
1386
1387 var numBytes = UnsafeUtility.SizeOf<TValue>();
1388 if (bufferSize < numBytes)
1389 throw new ArgumentException(
1390 $"bufferSize={bufferSize} < sizeof(TValue)={numBytes}", nameof(bufferSize));
1391
1392 var value = ReadValueFromState(statePtr);
1393 var valuePtr = UnsafeUtility.AddressOf(ref value);
1394
1395 UnsafeUtility.MemCpy(bufferPtr, valuePtr, numBytes);
1396 }
1397
1398 public override unsafe void WriteValueFromBufferIntoState(void* bufferPtr, int bufferSize, void* statePtr)
1399 {
1400 if (bufferPtr == null)
1401 throw new ArgumentNullException(nameof(bufferPtr));
1402 if (statePtr == null)
1403 throw new ArgumentNullException(nameof(statePtr));
1404
1405 var numBytes = UnsafeUtility.SizeOf<TValue>();
1406 if (bufferSize < numBytes)
1407 throw new ArgumentException(
1408 $"bufferSize={bufferSize} < sizeof(TValue)={numBytes}", nameof(bufferSize));
1409
1410 // C# won't let us use a pointer to a generically defined type. Work
1411 // around this by using UnsafeUtility.
1412 var value = default(TValue);
1413 var valuePtr = UnsafeUtility.AddressOf(ref value);
1414 UnsafeUtility.MemCpy(valuePtr, bufferPtr, numBytes);
1415
1416 WriteValueIntoState(value, statePtr);
1417 }
1418
1419 /// <inheritdoc />
1420 public override unsafe void WriteValueFromObjectIntoState(object value, void* statePtr)
1421 {
1422 if (statePtr == null)
1423 throw new ArgumentNullException(nameof(statePtr));
1424 if (value == null)
1425 throw new ArgumentNullException(nameof(value));
1426
1427 // If value is not of expected type, try to convert.
1428 if (!(value is TValue))
1429 value = Convert.ChangeType(value, typeof(TValue));
1430
1431 var valueOfType = (TValue)value;
1432 WriteValueIntoState(valueOfType, statePtr);
1433 }
1434
1435 public virtual unsafe void WriteValueIntoState(TValue value, void* statePtr)
1436 {
1437 ////REVIEW: should we be able to even tell from layouts which controls support writing and which don't?
1438
1439 throw new NotSupportedException(
1440 $"Control '{this}' does not support writing");
1441 }
1442
1443 /// <inheritdoc />
1444 public override unsafe object ReadValueFromBufferAsObject(void* buffer, int bufferSize)
1445 {
1446 if (buffer == null)
1447 throw new ArgumentNullException(nameof(buffer));
1448
1449 var valueSize = UnsafeUtility.SizeOf<TValue>();
1450 if (bufferSize < valueSize)
1451 throw new ArgumentException(
1452 $"Expecting buffer of at least {valueSize} bytes for value of type {typeof(TValue).Name} but got buffer of only {bufferSize} bytes instead",
1453 nameof(bufferSize));
1454
1455 var value = default(TValue);
1456 var valuePtr = UnsafeUtility.AddressOf(ref value);
1457 UnsafeUtility.MemCpy(valuePtr, buffer, valueSize);
1458
1459 return value;
1460 }
1461
1462 private static unsafe bool CompareValue(ref TValue firstValue, ref TValue secondValue)
1463 {
1464 var firstValuePtr = UnsafeUtility.AddressOf(ref firstValue);
1465 var secondValuePtr = UnsafeUtility.AddressOf(ref secondValue);
1466
1467 // NOTE: We're comparing raw memory of processed values here (which are guaranteed to be structs or
1468 // primitives), not state. Means we don't have to take bits into account here.
1469
1470 return UnsafeUtility.MemCmp(firstValuePtr, secondValuePtr, UnsafeUtility.SizeOf<TValue>()) != 0;
1471 }
1472
1473 public override unsafe bool CompareValue(void* firstStatePtr, void* secondStatePtr)
1474 {
1475 ////REVIEW: should we first compare state here? if there's no change in state, there can be no change in value and we can skip the rest
1476
1477 var firstValue = ReadValueFromState(firstStatePtr);
1478 var secondValue = ReadValueFromState(secondStatePtr);
1479
1480 return CompareValue(ref firstValue, ref secondValue);
1481 }
1482
1483 [MethodImpl(MethodImplOptions.AggressiveInlining)]
1484 public TValue ProcessValue(TValue value)
1485 {
1486 ProcessValue(ref value);
1487 return value;
1488 }
1489
1490 /// <summary>
1491 /// Applies all control processors to the passed value.
1492 /// </summary>
1493 /// <param name="value"></param>
1494 /// <remarks>
1495 /// Use this overload when your state struct is large to avoid creating copies of the state.
1496 /// </remarks>
1497 [MethodImpl(MethodImplOptions.AggressiveInlining)]
1498 public void ProcessValue(ref TValue value)
1499 {
1500 if (m_ProcessorStack.length <= 0)
1501 return;
1502
1503 value = m_ProcessorStack.firstValue.Process(value, this);
1504 if (m_ProcessorStack.additionalValues == null)
1505 return;
1506
1507 for (var i = 0; i < m_ProcessorStack.length - 1; ++i)
1508 value = m_ProcessorStack.additionalValues[i].Process(value, this);
1509 }
1510
1511 internal InlinedArray<InputProcessor<TValue>> m_ProcessorStack;
1512
1513 private TValue m_CachedValue;
1514 private TValue m_UnprocessedCachedValue;
1515
1516 #if UNITY_EDITOR
1517 [MethodImpl(MethodImplOptions.AggressiveInlining)]
1518 private unsafe ref readonly TValue ReadStateInEditor()
1519 {
1520 // we don't use cached values during editor updates because editor updates cause controls to look at a
1521 // different block of state memory, and since the cached values are from the play mode memory, we'd
1522 // end up returning the wrong values.
1523 m_EditorValue = ReadValueFromState(currentStatePtr);
1524 return ref m_EditorValue;
1525 }
1526
1527 [MethodImpl(MethodImplOptions.AggressiveInlining)]
1528 private unsafe ref readonly TValue ReadUnprocessedStateInEditor()
1529 {
1530 m_UnprocessedEditorValue = ReadUnprocessedValueFromState(currentStatePtr);
1531 return ref m_UnprocessedEditorValue;
1532 }
1533
1534 // these fields are just to work with the fact that the 'value' property is ref readonly, so we
1535 // need somewhere with a known lifetime to store these so they can be returned by ref.
1536 private TValue m_EditorValue;
1537 private TValue m_UnprocessedEditorValue;
1538 #endif
1539
1540 // Only layouts are allowed to modify the processor stack.
1541 internal TProcessor TryGetProcessor<TProcessor>()
1542 where TProcessor : InputProcessor<TValue>
1543 {
1544 if (m_ProcessorStack.length > 0)
1545 {
1546 if (m_ProcessorStack.firstValue is TProcessor processor)
1547 return processor;
1548 if (m_ProcessorStack.additionalValues != null)
1549 for (var i = 0; i < m_ProcessorStack.length - 1; ++i)
1550 if (m_ProcessorStack.additionalValues[i] is TProcessor result)
1551 return result;
1552 }
1553 return default;
1554 }
1555
1556 internal override void AddProcessor(object processor)
1557 {
1558 if (!(processor is InputProcessor<TValue> processorOfType))
1559 throw new ArgumentException(
1560 $"Cannot add processor of type '{processor.GetType().Name}' to control of type '{GetType().Name}'", nameof(processor));
1561 m_ProcessorStack.Append(processorOfType);
1562 }
1563
1564 #if UNITY_EDITOR
1565 internal override IEnumerable<object> GetProcessors()
1566 {
1567 foreach (var processor in m_ProcessorStack)
1568 yield return processor;
1569 }
1570
1571 #endif
1572
1573 internal bool evaluateProcessorsEveryRead = false;
1574
1575 protected override void FinishSetup()
1576 {
1577 foreach (var processor in m_ProcessorStack)
1578 if (processor.cachingPolicy == InputProcessor.CachingPolicy.EvaluateOnEveryRead)
1579 evaluateProcessorsEveryRead = true;
1580
1581 base.FinishSetup();
1582 }
1583
1584 internal InputProcessor<TValue>[] processors => m_ProcessorStack.ToArray();
1585 }
1586}