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