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("&lt;Gamepad&gt;/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>"&lt;Gamepad&gt;/leftStick/x"</c> and <c>"&lt;Gamepad&gt;/leftStick/left"</c> are 379 /// suitable picks, <c>"&lt;Gamepad&gt;/leftStick/x"</c> will be favored as it represents 380 /// input from an actual physical control whereas <c>"&lt;Gamepad&gt;/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}