A game about forced loneliness, made by TACStudios
1using System; 2using System.Runtime.InteropServices; 3using Unity.Collections; 4using Unity.Collections.LowLevel.Unsafe; 5using UnityEngine.InputSystem.Controls; 6using UnityEngine.InputSystem.Layouts; 7using UnityEngine.InputSystem.LowLevel; 8using UnityEngine.InputSystem.Utilities; 9using Unity.Profiling; 10 11////TODO: property that tells whether a Touchscreen is multi-touch capable 12 13////TODO: property that tells whether a Touchscreen supports pressure 14 15////TODO: add support for screen orientation 16 17////TODO: touch is hardwired to certain memory layouts ATM; either allow flexibility or make sure the layouts cannot be changed 18 19////TODO: startTimes are baked *external* times; reset touch when coming out of play mode 20 21////TODO: detect and diagnose touchId=0 events 22 23////REVIEW: where should we put handset vibration support? should that sit on the touchscreen class? be its own separate device? 24 25////REVIEW: Given that Touchscreen is no use for polling, should we remove Touchscreen.current? 26 27////REVIEW: Should Touchscreen reset individual TouchControls to default(TouchState) after a touch has ended? This would allow 28//// binding to a TouchControl as a whole and the action would correctly cancel if the touch ends 29 30namespace UnityEngine.InputSystem.LowLevel 31{ 32 [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1028:EnumStorageShouldBeInt32", Justification = "byte to correspond to TouchState layout.")] 33 [Flags] 34 internal enum TouchFlags : byte 35 { 36 IndirectTouch = 1 << 0, 37 38 // NOTE: Leaving the first 3 bits for native. 39 40 PrimaryTouch = 1 << 3, 41 TapPress = 1 << 4, 42 TapRelease = 1 << 5, 43 44 // Indicates that the touch that established this primary touch has ended but that when 45 // it did, there were still other touches going on. We end the primary touch when the 46 // last touch leaves the screen. 47 OrphanedPrimaryTouch = 1 << 6, 48 49 // This is only used by EnhancedTouch to mark touch records that have begun in the same 50 // frame as the current touch record. 51 BeganInSameFrame = 1 << 7, 52 } 53 54 ////REVIEW: add timestamp directly to touch? 55 /// <summary> 56 /// State layout for a single touch. 57 /// </summary> 58 /// <remarks> 59 /// This is the low-level memory representation of a single touch, i.e the 60 /// way touches are internally transmitted and stored in the system. To update 61 /// touches on a <see cref="Touchscreen"/>, <see cref="StateEvent"/>s containing 62 /// TouchStates are sent to the screen. 63 /// </remarks> 64 /// <seealso cref="TouchControl"/> 65 /// <seealso cref="Touchscreen"/> 66 // IMPORTANT: Must match TouchInputState in native code. 67 [StructLayout(LayoutKind.Explicit, Size = kSizeInBytes)] 68 public struct TouchState : IInputStateTypeInfo 69 { 70 internal const int kSizeInBytes = 56; 71 72 /// <summary> 73 /// Memory format tag for TouchState. 74 /// </summary> 75 /// <value>Returns "TOUC".</value> 76 /// <seealso cref="InputStateBlock.format"/> 77 public static FourCC Format => new FourCC('T', 'O', 'U', 'C'); 78 79 ////REVIEW: this should really be a uint 80 /// <summary> 81 /// Numeric ID of the touch. 82 /// </summary> 83 /// <value>Numeric ID of the touch.</value> 84 /// <remarks> 85 /// While a touch is ongoing, it must have a non-zero ID different from 86 /// all other ongoing touches. Starting with <see cref="TouchPhase.Began"/> 87 /// and ending with <see cref="TouchPhase.Ended"/> or <see cref="TouchPhase.Canceled"/>, 88 /// a touch is identified by its ID, i.e. a TouchState with the same ID 89 /// belongs to the same touch. 90 /// 91 /// After a touch has ended or been canceled, an ID can be reused. 92 /// </remarks> 93 /// <seealso cref="TouchControl.touchId"/> 94 [InputControl(displayName = "Touch ID", layout = "Integer", synthetic = true, dontReset = true)] 95 [FieldOffset(0)] 96 public int touchId; 97 98 /// <summary> 99 /// Screen-space position of the touch in pixels. 100 /// </summary> 101 /// <value>Screen-space position of the touch.</value> 102 /// <seealso cref="TouchControl.position"/> 103 [InputControl(displayName = "Position", dontReset = true)] 104 [FieldOffset(4)] 105 public Vector2 position; 106 107 /// <summary> 108 /// Screen-space motion delta of the touch in pixels. 109 /// </summary> 110 /// <value>Screen-space movement delta.</value> 111 /// <seealso cref="TouchControl.delta"/> 112 [InputControl(displayName = "Delta", layout = "Delta")] 113 [FieldOffset(12)] 114 public Vector2 delta; 115 116 /// <summary> 117 /// Pressure-level of the touch against the touchscreen. 118 /// </summary> 119 /// <value>Pressure of touch.</value> 120 /// <remarks> 121 /// The core range for this value is [0..1] with 1 indicating maximum pressure. Note, however, 122 /// that the actual value may go beyond 1 in practice. This is because the system will usually 123 /// define "maximum pressure" to be less than the physical maximum limit the hardware is capable 124 /// of reporting so that to achieve maximum pressure, one does not need to press as hard as 125 /// possible. 126 /// </remarks> 127 /// <seealso cref="TouchControl.pressure"/> 128 [InputControl(displayName = "Pressure", layout = "Axis")] 129 [FieldOffset(20)] 130 public float pressure; 131 132 /// <summary> 133 /// Radius of the touch print on the surface. 134 /// </summary> 135 /// <value>Touch extents horizontally and vertically.</value> 136 /// <remarks> 137 /// The touch radius is given in screen-space pixel coordinates along X and Y centered in the middle 138 /// of the touch. Note that not all screens and systems support radius detection on touches so this 139 /// value may be at <c>default</c> for an otherwise perfectly valid touch. 140 /// </remarks> 141 /// <seealso cref="TouchControl.radius"/> 142 [InputControl(displayName = "Radius")] 143 [FieldOffset(24)] 144 public Vector2 radius; 145 146 /// <summary> 147 /// <see cref="TouchPhase"/> value of the touch. 148 /// </summary> 149 /// <value>Current <see cref="TouchPhase"/>.</value> 150 /// <seealso cref="phase"/> 151 [InputControl(name = "phase", displayName = "Touch Phase", layout = "TouchPhase", synthetic = true)] 152 [InputControl(name = "press", displayName = "Touch Contact?", layout = "TouchPress", useStateFrom = "phase")] 153 [FieldOffset(32)] 154 public byte phaseId; 155 156 [InputControl(name = "tapCount", displayName = "Tap Count", layout = "Integer")] 157 [FieldOffset(33)] 158 public byte tapCount; 159 160 /// <summary> 161 /// The index of the display that was touched. 162 /// </summary> 163 [InputControl(name = "displayIndex", displayName = "Display Index", layout = "Integer")] 164 [FieldOffset(34)] 165 public byte displayIndex; 166 167 [InputControl(name = "indirectTouch", displayName = "Indirect Touch?", layout = "Button", bit = 0, synthetic = true)] 168 [InputControl(name = "tap", displayName = "Tap", layout = "Button", bit = 4)] 169 [FieldOffset(35)] 170 public byte flags; 171 172 // Need four bytes of alignment here for the startTime double. Using that for storing updateStepCounts. 173 // They aren't needed directly by Touchscreen but are used by EnhancedTouch and since we have the four 174 // bytes, may just as well use them instead of wasting them on padding. 175 [FieldOffset(36)] 176 internal uint updateStepCount; 177 178 // NOTE: The following data is NOT sent by native but rather data we add on the managed side to each touch. 179 180 /// <summary> 181 /// Time that the touch was started. Relative to <c>Time.realTimeSinceStartup</c>. 182 /// </summary> 183 /// <value>Time that the touch was started.</value> 184 /// <remarks> 185 /// This is set automatically by <see cref="Touchscreen"/> and does not need to be provided 186 /// by events sent to the touchscreen. 187 /// </remarks> 188 /// <seealso cref="InputEvent.time"/> 189 /// <seealso cref="TouchControl.startTime"/> 190 [InputControl(displayName = "Start Time", layout = "Double", synthetic = true)] 191 [FieldOffset(40)] 192 public double startTime; // In *external* time, i.e. currentTimeOffsetToRealtimeSinceStartup baked in. 193 194 /// <summary> 195 /// The position where the touch started. 196 /// </summary> 197 /// <value>Screen-space start position of the touch.</value> 198 /// <remarks> 199 /// This is set automatically by <see cref="Touchscreen"/> and does not need to be provided 200 /// by events sent to the touchscreen. 201 /// </remarks> 202 /// <seealso cref="TouchControl.startPosition"/> 203 [InputControl(displayName = "Start Position", synthetic = true)] 204 [FieldOffset(48)] 205 public Vector2 startPosition; 206 207 /// <summary> 208 /// Get or set the phase of the touch. 209 /// </summary> 210 /// <value>Phase of the touch.</value> 211 /// <seealso cref="TouchControl.phase"/> 212 public TouchPhase phase 213 { 214 get => (TouchPhase)phaseId; 215 set => phaseId = (byte)value; 216 } 217 218 public bool isNoneEndedOrCanceled => phase == TouchPhase.None || phase == TouchPhase.Ended || 219 phase == TouchPhase.Canceled; 220 public bool isInProgress => phase == TouchPhase.Began || phase == TouchPhase.Moved || 221 phase == TouchPhase.Stationary; 222 223 /// <summary> 224 /// Whether, after not having any touch contacts, this is part of the first touch contact that started. 225 /// </summary> 226 /// <remarks> 227 /// This flag will be set internally by <see cref="Touchscreen"/>. Generally, it is 228 /// not necessary to set this bit manually when feeding data to Touchscreens. 229 /// </remarks> 230 public bool isPrimaryTouch 231 { 232 get => (flags & (byte)TouchFlags.PrimaryTouch) != 0; 233 set 234 { 235 if (value) 236 flags |= (byte)TouchFlags.PrimaryTouch; 237 else 238 flags &= (byte)~TouchFlags.PrimaryTouch; 239 } 240 } 241 242 internal bool isOrphanedPrimaryTouch 243 { 244 get => (flags & (byte)TouchFlags.OrphanedPrimaryTouch) != 0; 245 set 246 { 247 if (value) 248 flags |= (byte)TouchFlags.OrphanedPrimaryTouch; 249 else 250 flags &= (byte)~TouchFlags.OrphanedPrimaryTouch; 251 } 252 } 253 254 public bool isIndirectTouch 255 { 256 get => (flags & (byte)TouchFlags.IndirectTouch) != 0; 257 set 258 { 259 if (value) 260 flags |= (byte)TouchFlags.IndirectTouch; 261 else 262 flags &= (byte)~TouchFlags.IndirectTouch; 263 } 264 } 265 266 public bool isTap 267 { 268 get => isTapPress; 269 set => isTapPress = value; 270 } 271 272 internal bool isTapPress 273 { 274 get => (flags & (byte)TouchFlags.TapPress) != 0; 275 set 276 { 277 if (value) 278 flags |= (byte)TouchFlags.TapPress; 279 else 280 flags &= (byte)~TouchFlags.TapPress; 281 } 282 } 283 284 internal bool isTapRelease 285 { 286 get => (flags & (byte)TouchFlags.TapRelease) != 0; 287 set 288 { 289 if (value) 290 flags |= (byte)TouchFlags.TapRelease; 291 else 292 flags &= (byte)~TouchFlags.TapRelease; 293 } 294 } 295 296 internal bool beganInSameFrame 297 { 298 get => (flags & (byte)TouchFlags.BeganInSameFrame) != 0; 299 set 300 { 301 if (value) 302 flags |= (byte)TouchFlags.BeganInSameFrame; 303 else 304 flags &= (byte)~TouchFlags.BeganInSameFrame; 305 } 306 } 307 308 /// <inheritdoc/> 309 public FourCC format => Format; 310 311 /// <summary> 312 /// Return a string representation of the state useful for debugging. 313 /// </summary> 314 /// <returns>A string representation of the touch state.</returns> 315 public override string ToString() 316 { 317 return $"{{ id={touchId} phase={phase} pos={position} delta={delta} pressure={pressure} radius={radius} primary={isPrimaryTouch} }}"; 318 } 319 } 320 321 /// <summary> 322 /// Default state layout for touch devices. 323 /// </summary> 324 /// <remarks> 325 /// Combines multiple pointers each corresponding to a single contact. 326 /// 327 /// Normally, TODO (sending state events) 328 /// 329 /// All touches combine to quite a bit of state; ideally send delta events that update 330 /// only specific fingers. 331 /// 332 /// This is NOT used by native. Instead, the native runtime always sends individual touches (<see cref="TouchState"/>) 333 /// and leaves state management for a touchscreen as a whole to the managed part of the system. 334 /// </remarks> 335 [StructLayout(LayoutKind.Explicit, Size = MaxTouches * TouchState.kSizeInBytes)] 336 internal unsafe struct TouchscreenState : IInputStateTypeInfo 337 { 338 /// <summary> 339 /// Memory format tag for TouchscreenState. 340 /// </summary> 341 /// <value>Returns "TSCR".</value> 342 /// <seealso cref="InputStateBlock.format"/> 343 public static FourCC Format => new FourCC('T', 'S', 'C', 'R'); 344 345 /// <summary> 346 /// Maximum number of touches that can be tracked at the same time. 347 /// </summary> 348 /// <value>Maximum number of concurrent touches.</value> 349 public const int MaxTouches = 10; 350 351 /// <summary> 352 /// Data for the touch that is deemed the "primary" touch at the moment. 353 /// </summary> 354 /// <remarks> 355 /// This touch duplicates touch data from whichever touch is deemed the primary touch at the moment. 356 /// When going from no fingers down to any finger down, the first finger to touch the screen is 357 /// deemed the "primary touch". It stays the primary touch until released. At that point, if any other 358 /// finger is still down, the next finger in <see cref="touchData"/> is 359 /// 360 /// Having this touch be its own separate state and own separate control allows actions to track the 361 /// state of the primary touch even if the touch moves from one finger to another in <see cref="touchData"/>. 362 /// </remarks> 363 [InputControl(name = "primaryTouch", displayName = "Primary Touch", layout = "Touch", synthetic = true)] 364 [InputControl(name = "primaryTouch/tap", usage = "PrimaryAction")] 365 366 // Add controls compatible with what Pointer expects and redirect their 367 // state to the state of touch0 so that this essentially becomes our 368 // pointer control. 369 // NOTE: Some controls from Pointer don't make sense for touch and we "park" 370 // them by assigning them invalid offsets (thus having automatic state 371 // layout put them at the end of our fixed state). 372 [InputControl(name = "position", useStateFrom = "primaryTouch/position")] 373 [InputControl(name = "delta", useStateFrom = "primaryTouch/delta", layout = "Delta")] 374 [InputControl(name = "pressure", useStateFrom = "primaryTouch/pressure")] 375 [InputControl(name = "radius", useStateFrom = "primaryTouch/radius")] 376 [InputControl(name = "press", useStateFrom = "primaryTouch/phase", layout = "TouchPress", synthetic = true, usages = new string[0])] 377 [InputControl(name = "displayIndex", useStateFrom = "primaryTouch/displayIndex", format = "BYTE")] // added format to override the Pointer's USHT value 378 [FieldOffset(0)] 379 public fixed byte primaryTouchData[TouchState.kSizeInBytes]; 380 381 internal const int kTouchDataOffset = TouchState.kSizeInBytes; 382 383 [InputControl(layout = "Touch", name = "touch", displayName = "Touch", arraySize = MaxTouches)] 384 [FieldOffset(kTouchDataOffset)] 385 public fixed byte touchData[MaxTouches * TouchState.kSizeInBytes]; 386 387 public TouchState* primaryTouch 388 { 389 get 390 { 391 fixed(byte* ptr = primaryTouchData) 392 return (TouchState*)ptr; 393 } 394 } 395 396 public TouchState* touches 397 { 398 get 399 { 400 fixed(byte* ptr = touchData) 401 return (TouchState*)ptr; 402 } 403 } 404 405 public FourCC format => Format; 406 } 407} 408 409namespace UnityEngine.InputSystem 410{ 411 /// <summary> 412 /// Indicates where in its lifecycle a given touch is. 413 /// </summary> 414 public enum TouchPhase 415 { 416 ////REVIEW: Why have a separate None instead of just making this equivalent to either Ended or Canceled? 417 /// <summary> 418 /// No activity has been registered on the touch yet. 419 /// </summary> 420 /// <remarks> 421 /// A given touch state will generally not go back to None once there has been input for it. Meaning that 422 /// it generally indicates a default-initialized touch record. 423 /// </remarks> 424 None, 425 426 /// <summary> 427 /// A touch has just begun, i.e. a finger has touched the screen.. Only the first touch input in any given touch will have this phase. 428 /// </summary> 429 Began, 430 431 /// <summary> 432 /// An ongoing touch has changed position. 433 /// </summary> 434 Moved, 435 436 /// <summary> 437 /// An ongoing touch has just ended, i.e. the respective finger has been lifted off of the screen. Only the last touch input in a 438 /// given touch will have this phase. 439 /// </summary> 440 Ended, 441 442 /// <summary> 443 /// An ongoing touch has been cancelled, i.e. ended in a way other than through user interaction. This happens, for example, if 444 /// focus is moved away from the application while the touch is ongoing. 445 /// </summary> 446 Canceled, 447 448 /// <summary> 449 /// An ongoing touch has not been moved (not received any input) in a frame. 450 /// </summary> 451 /// <remarks> 452 /// This phase is not used by <see cref="Touchscreen"/>. This means that <see cref="TouchControl"/> will not generally 453 /// return this value for <see cref="TouchControl.phase"/>. It is, however, used by <see cref="UnityEngine.InputSystem.EnhancedTouch.Touch"/>. 454 /// </remarks> 455 Stationary, 456 } 457 458 /// <summary> 459 /// A multi-touch surface. 460 /// </summary> 461 /// <remarks> 462 /// Touchscreen is somewhat different from most other device implementations in that it does not usually 463 /// consume input in the form of a full device snapshot but rather consumes input sent to it in the form 464 /// of events containing a <see cref="TouchState"/> each. This is unusual as <see cref="TouchState"/> 465 /// uses a memory format different from <see cref="TouchState.Format"/>. However, when a <c>Touchscreen</c> 466 /// sees an event containing a <see cref="TouchState"/>, it will handle that event on a special code path. 467 /// 468 /// This allows <c>Touchscreen</c> to decide on its own which control in <see cref="touches"/> to store 469 /// a touch at and to perform things such as tap detection (see <see cref="TouchControl.tap"/> and 470 /// <see cref="TouchControl.tapCount"/>) and primary touch handling (see <see cref="primaryTouch"/>). 471 /// 472 /// <example> 473 /// <code> 474 /// // Create a touchscreen device. 475 /// var touchscreen = InputSystem.AddDevice&lt;Touchscreen&gt;(); 476 /// 477 /// // Send a touch to the device. 478 /// InputSystem.QueueStateEvent(touchscreen, 479 /// new TouchState 480 /// { 481 /// phase = TouchPhase.Began, 482 /// // Must have a valid, non-zero touch ID. Touchscreen will not operate 483 /// // correctly if we don't set IDs properly. 484 /// touchId = 1, 485 /// position = new Vector2(123, 234), 486 /// // Delta will be computed by Touchscreen automatically. 487 /// }); 488 /// </code> 489 /// </example> 490 /// 491 /// Note that this class presents a fairly low-level touch API. When working with touch from script code, 492 /// it is recommended to use the higher-level <see cref="EnhancedTouch.Touch"/> API instead. 493 /// </remarks> 494 [InputControlLayout(stateType = typeof(TouchscreenState), isGenericTypeOfDevice = true)] 495 public class Touchscreen : Pointer, IInputStateCallbackReceiver, IEventMerger, ICustomDeviceReset 496 { 497 /// <summary> 498 /// Synthetic control that has the data for the touch that is deemed the "primary" touch at the moment. 499 /// </summary> 500 /// <value>Control tracking the screen's primary touch.</value> 501 /// <remarks> 502 /// This touch duplicates touch data from whichever touch is deemed the primary touch at the moment. 503 /// When going from no fingers down to any finger down, the first finger to touch the screen is 504 /// deemed the "primary touch". It stays the primary touch until the last finger is released. 505 /// 506 /// Note that unlike the touch from which it originates, the primary touch will be kept ongoing for 507 /// as long as there is still a finger on the screen. Put another way, <see cref="TouchControl.phase"/> 508 /// of <c>primaryTouch</c> will only transition to <see cref="TouchPhase.Ended"/> once the last finger 509 /// has been lifted off the screen. 510 /// </remarks> 511 public TouchControl primaryTouch { get; protected set; } 512 513 /// <summary> 514 /// Array of all <see cref="TouchControl"/>s on the device. 515 /// </summary> 516 /// <value>All <see cref="TouchControl"/>s on the screen.</value> 517 /// <remarks> 518 /// By default, a touchscreen will allocate 10 touch controls. This can be changed 519 /// by modifying the "Touchscreen" layout itself or by derived layouts. In practice, 520 /// this means that this array will usually have a fixed length of 10 entries but 521 /// it may deviate from that. 522 /// </remarks> 523 public ReadOnlyArray<TouchControl> touches { get; protected set; } 524 525 526 static readonly ProfilerMarker k_TouchscreenUpdateMarker = new ProfilerMarker("Touchscreen.OnNextUpdate"); 527 static readonly ProfilerMarker k_TouchAllocateMarker = new ProfilerMarker("TouchAllocate"); 528 529 protected TouchControl[] touchControlArray 530 { 531 get => touches.m_Array; 532 set => touches = new ReadOnlyArray<TouchControl>(value); 533 } 534 535 /// <summary> 536 /// The touchscreen that was added or updated last or null if there is no 537 /// touchscreen connected to the system. 538 /// </summary> 539 /// <value>Current touch screen.</value> 540 public new static Touchscreen current { get; internal set; } 541 542 /// <inheritdoc /> 543 public override void MakeCurrent() 544 { 545 base.MakeCurrent(); 546 current = this; 547 } 548 549 /// <inheritdoc /> 550 protected override void OnRemoved() 551 { 552 base.OnRemoved(); 553 if (current == this) 554 current = null; 555 } 556 557 /// <inheritdoc /> 558 protected override void FinishSetup() 559 { 560 base.FinishSetup(); 561 562 primaryTouch = GetChildControl<TouchControl>("primaryTouch"); 563 564 // Find out how many touch controls we have. 565 var touchControlCount = 0; 566 foreach (var child in children) 567 if (child is TouchControl) 568 ++touchControlCount; 569 570 // Keep primaryTouch out of array. 571 Debug.Assert(touchControlCount >= 1, "Should have found at least primaryTouch control"); 572 if (touchControlCount >= 1) 573 --touchControlCount; 574 575 // Gather touch controls into array. 576 var touchArray = new TouchControl[touchControlCount]; 577 var touchIndex = 0; 578 foreach (var child in children) 579 { 580 if (child == primaryTouch) 581 continue; 582 583 if (child is TouchControl control) 584 touchArray[touchIndex++] = control; 585 } 586 587 touches = new ReadOnlyArray<TouchControl>(touchArray); 588 } 589 590 // Touch has more involved state handling than most other devices. To not put touch allocation logic 591 // in all the various platform backends (i.e. see a touch with a certain ID coming in from the system 592 // and then having to decide *where* to store that inside of Touchscreen's state), we have backends 593 // send us individual touches ('TOUC') instead of whole Touchscreen snapshots ('TSRC'). Using 594 // IInputStateCallbackReceiver, Touchscreen then dynamically decides where to store the touch. 595 // 596 // Also, Touchscreen has bits of logic to automatically synthesize the state of controls it inherits 597 // from Pointer (such as "<Pointer>/press"). 598 // 599 // NOTE: We do *NOT* make a effort here to prevent us from losing short-lived touches. This is different 600 // from the old input system where individual touches were not reused until the next frame. This meant 601 // that additional touches potentially had to be allocated in order to accommodate new touches coming 602 // in from the system. 603 // 604 // The rationale for *NOT* doing this is that: 605 // 606 // a) Actions don't need it. They observe every single state change and thus will not lose data 607 // even if it is short-lived (i.e. changes more than once in the same update). 608 // b) The higher-level Touch (EnhancedTouchSupport) API is provided to 609 // not only handle this scenario but also give a generally more flexible and useful touch API 610 // than writing code directly against Touchscreen. 611 612 protected new unsafe void OnNextUpdate() 613 { 614 k_TouchscreenUpdateMarker.Begin(); 615 616 ////TODO: early out and skip crawling through touches if we didn't change state in the last update 617 //// (also obsoletes the need for the if() check below) 618 var statePtr = currentStatePtr; 619 var touchStatePtr = (TouchState*)((byte*)statePtr + stateBlock.byteOffset + TouchscreenState.kTouchDataOffset); 620 for (var i = 0; i < touches.Count; ++i, ++touchStatePtr) 621 { 622 // Reset delta. 623 if (touchStatePtr->delta != default) 624 InputState.Change(touches[i].delta, Vector2.zero); 625 626 // Reset tap count. 627 // NOTE: We are basing this on startTime rather than adding on end time of the last touch. The reason is 628 // that to do so we would have to add another record to keep track of timestamps for each touch. And 629 // since we know the maximum time that a tap can take, we have a reasonable estimate for when a prior 630 // tap must have ended. 631 if (touchStatePtr->tapCount > 0 && InputState.currentTime >= touchStatePtr->startTime + s_TapTime + s_TapDelayTime) 632 InputState.Change(touches[i].tapCount, (byte)0); 633 } 634 635 var primaryTouchState = (TouchState*)((byte*)statePtr + stateBlock.byteOffset); 636 if (primaryTouchState->delta != default) 637 InputState.Change(primaryTouch.delta, Vector2.zero); 638 if (primaryTouchState->tapCount > 0 && InputState.currentTime >= primaryTouchState->startTime + s_TapTime + s_TapDelayTime) 639 InputState.Change(primaryTouch.tapCount, (byte)0); 640 641 k_TouchscreenUpdateMarker.End(); 642 } 643 644 /// <summary> 645 /// Called whenever a new state event is received. 646 /// </summary> 647 /// <param name="eventPtr"></param> 648 protected new unsafe void OnStateEvent(InputEventPtr eventPtr) 649 { 650 var eventType = eventPtr.type; 651 652 // We don't allow partial updates for TouchStates. 653 if (eventType == DeltaStateEvent.Type) 654 return; 655 656 // If it's not a single touch, just take the event state as is (will have to be TouchscreenState). 657 var stateEventPtr = StateEvent.FromUnchecked(eventPtr); 658 if (stateEventPtr->stateFormat != TouchState.Format) 659 { 660 InputState.Change(this, eventPtr); 661 return; 662 } 663 664 k_TouchAllocateMarker.Begin(); 665 666 // For performance reasons, we read memory here directly rather than going through 667 // ReadValue() of the individual TouchControl children. This means that Touchscreen, 668 // unlike other devices, is hardwired to a single memory layout only. 669 670 var statePtr = currentStatePtr; 671 var currentTouchState = (TouchState*)((byte*)statePtr + touches[0].stateBlock.byteOffset); 672 var primaryTouchState = (TouchState*)((byte*)statePtr + primaryTouch.stateBlock.byteOffset); 673 var touchControlCount = touches.Count; 674 675 // Native does not send a full TouchState as we define it here. We have added some fields 676 // that we store internally. Make sure we don't read invalid memory here and copy only what 677 // we got. 678 TouchState newTouchState; 679 if (stateEventPtr->stateSizeInBytes == TouchState.kSizeInBytes) 680 { 681 newTouchState = *(TouchState*)stateEventPtr->state; 682 } 683 else 684 { 685 newTouchState = default; 686 UnsafeUtility.MemCpy(UnsafeUtility.AddressOf(ref newTouchState), stateEventPtr->state, stateEventPtr->stateSizeInBytes); 687 } 688 689 // Make sure we're not getting thrown off by noise on fields that we don't want to 690 // pick up from input. 691 newTouchState.tapCount = 0; 692 newTouchState.isTapPress = false; 693 newTouchState.isTapRelease = false; 694 newTouchState.updateStepCount = InputUpdate.s_UpdateStepCount; 695 696 ////REVIEW: The logic in here makes us inherently susceptible to the ordering of the touch events in the event 697 //// stream. I believe we have platforms (Android?) that send us touch events finger-by-finger (or touch-by-touch?) 698 //// rather than sorted by time. This will probably screw up the logic in here. 699 700 // If it's an ongoing touch, try to find the TouchState we have allocated to the touch 701 // previously. 702 var phase = newTouchState.phase; 703 if (phase != TouchPhase.Began) 704 { 705 var touchId = newTouchState.touchId; 706 for (var i = 0; i < touchControlCount; ++i) 707 { 708 if (currentTouchState[i].touchId == touchId) 709 { 710 // Preserve primary touch state. 711 var isPrimaryTouch = currentTouchState[i].isPrimaryTouch; 712 newTouchState.isPrimaryTouch = isPrimaryTouch; 713 714 // Compute delta if touch doesn't have one. 715 if (newTouchState.delta == default) 716 newTouchState.delta = newTouchState.position - currentTouchState[i].position; 717 718 // Accumulate delta. 719 newTouchState.delta += currentTouchState[i].delta; 720 721 // Keep start time and position. 722 newTouchState.startTime = currentTouchState[i].startTime; 723 newTouchState.startPosition = currentTouchState[i].startPosition; 724 725 // Detect taps. 726 var isTap = newTouchState.isNoneEndedOrCanceled && 727 (eventPtr.time - newTouchState.startTime) <= s_TapTime && 728 ////REVIEW: this only takes the final delta to start position into account, not the delta over the lifetime of the 729 //// touch; is this robust enough or do we need to make sure that we never move more than the tap radius 730 //// over the entire lifetime of the touch? 731 (newTouchState.position - newTouchState.startPosition).sqrMagnitude <= s_TapRadiusSquared; 732 if (isTap) 733 newTouchState.tapCount = (byte)(currentTouchState[i].tapCount + 1); 734 else 735 newTouchState.tapCount = currentTouchState[i].tapCount; // Preserve tap count; reset in OnCarryStateForward. 736 737 // Update primary touch. 738 if (isPrimaryTouch) 739 { 740 if (newTouchState.isNoneEndedOrCanceled) 741 { 742 ////REVIEW: also reset tapCounts here when tap delay time has expired on the touch? 743 744 newTouchState.isPrimaryTouch = false; 745 746 // Primary touch was ended. See if there are still other ongoing touches. 747 var haveOngoingTouch = false; 748 for (var n = 0; n < touchControlCount; ++n) 749 { 750 if (n == i) 751 continue; 752 753 if (currentTouchState[n].isInProgress) 754 { 755 haveOngoingTouch = true; 756 break; 757 } 758 } 759 760 if (!haveOngoingTouch) 761 { 762 // No, primary was the only ongoing touch. End it. 763 764 if (isTap) 765 TriggerTap(primaryTouch, ref newTouchState, eventPtr); 766 else 767 InputState.Change(primaryTouch, ref newTouchState, eventPtr: eventPtr); 768 } 769 else 770 { 771 // Yes, we have other touches going on. Make the primary touch an 772 // orphan and wait until the other touches are released. 773 774 var newPrimaryTouchState = newTouchState; 775 newPrimaryTouchState.phase = TouchPhase.Moved; 776 newPrimaryTouchState.isOrphanedPrimaryTouch = true; 777 InputState.Change(primaryTouch, ref newPrimaryTouchState, eventPtr: eventPtr); 778 } 779 } 780 else 781 { 782 // Primary touch was updated. 783 InputState.Change(primaryTouch, ref newTouchState, eventPtr: eventPtr); 784 } 785 } 786 else 787 { 788 // If it's not the primary touch but the touch has ended, see if we have an 789 // orphaned primary touch. If so, end it now. 790 if (newTouchState.isNoneEndedOrCanceled && primaryTouchState->isOrphanedPrimaryTouch) 791 { 792 var haveOngoingTouch = false; 793 for (var n = 0; n < touchControlCount; ++n) 794 { 795 if (n == i) 796 continue; 797 798 if (currentTouchState[n].isInProgress) 799 { 800 haveOngoingTouch = true; 801 break; 802 } 803 } 804 805 if (!haveOngoingTouch) 806 { 807 primaryTouchState->isOrphanedPrimaryTouch = false; 808 InputState.Change(primaryTouch.phase, (byte)TouchPhase.Ended); 809 } 810 } 811 } 812 813 if (isTap) 814 { 815 // Make tap button go down and up. 816 // 817 // NOTE: We do this here instead of right away up there when we detect the touch so 818 // that the state change notifications go together. First those for the primary 819 // touch, then the ones for the touch record itself. 820 TriggerTap(touches[i], ref newTouchState, eventPtr); 821 } 822 else 823 { 824 InputState.Change(touches[i], ref newTouchState, eventPtr: eventPtr); 825 } 826 827 k_TouchAllocateMarker.End(); 828 return; 829 } 830 } 831 832 // Couldn't find an entry. Either it was a touch that we previously ran out of available 833 // entries for or it's an event sent out of sequence. Ignore the touch to be consistent. 834 835 k_TouchAllocateMarker.End(); 836 return; 837 } 838 839 // It's a new touch. Try to find an unused TouchState. 840 for (var i = 0; i < touchControlCount; ++i, ++currentTouchState) 841 { 842 // NOTE: We're overwriting any ended touch immediately here. This means we immediately overwrite even 843 // if we still have other unused slots. What this gives us is a completely predictable touch #0..#N 844 // sequence (i.e. touch #N is only ever used if there are indeed #N concurrently touches). However, 845 // it does mean that we overwrite state aggressively. If you are not using actions or the higher-level 846 // Touch API, be aware of this! 847 if (currentTouchState->isNoneEndedOrCanceled) 848 { 849 newTouchState.delta = Vector2.zero; 850 newTouchState.startTime = eventPtr.time; 851 newTouchState.startPosition = newTouchState.position; 852 853 // Make sure we're not picking up noise sent from native. 854 newTouchState.isPrimaryTouch = false; 855 newTouchState.isOrphanedPrimaryTouch = false; 856 newTouchState.isTap = false; 857 858 // Tap counts are preserved from prior touches on the same finger. 859 newTouchState.tapCount = currentTouchState->tapCount; 860 861 // Make primary touch, if there's none currently. 862 if (primaryTouchState->isNoneEndedOrCanceled) 863 { 864 newTouchState.isPrimaryTouch = true; 865 InputState.Change(primaryTouch, ref newTouchState, eventPtr: eventPtr); 866 } 867 868 InputState.Change(touches[i], ref newTouchState, eventPtr: eventPtr); 869 870 k_TouchAllocateMarker.End(); 871 return; 872 } 873 } 874 875 // We ran out of state and we don't want to stomp an existing ongoing touch. 876 // Drop this touch entirely. 877 // NOTE: Getting here means we're having fewer touch entries than the number of concurrent touches supported 878 // by the backend (or someone is simply sending us nonsense data). 879 880 k_TouchAllocateMarker.End(); 881 } 882 883 void IInputStateCallbackReceiver.OnNextUpdate() 884 { 885 OnNextUpdate(); 886 } 887 888 void IInputStateCallbackReceiver.OnStateEvent(InputEventPtr eventPtr) 889 { 890 OnStateEvent(eventPtr); 891 } 892 893 unsafe bool IInputStateCallbackReceiver.GetStateOffsetForEvent(InputControl control, InputEventPtr eventPtr, ref uint offset) 894 { 895 // This code goes back to the trickery we perform in OnStateEvent. We consume events in TouchState format 896 // instead of in TouchscreenState format. This means that the input system does not know how the state in those 897 // events correlates to the controls we have. 898 // 899 // This method is used to give the input system an offset based on which the input system can compute relative 900 // offsets into the state of eventPtr for controls that are part of the control hierarchy rooted at 'control'. 901 902 if (!eventPtr.IsA<StateEvent>()) 903 return false; 904 905 var stateEventPtr = StateEvent.FromUnchecked(eventPtr); 906 if (stateEventPtr->stateFormat != TouchState.Format) 907 return false; 908 909 // If we get a null control and a TouchState event, all the system wants to know is what 910 // state offset to use to make sense of the event. 911 if (control == null) 912 { 913 // We can't say which specific touch this would go to (if any at all) without going through 914 // the same logic that we run through in OnStateEvent. For the sake of just being able to read 915 // out data from a touch event, it'd be enough to return the offset of *any* TouchControl here. 916 // But for the sake of being able to compare the data in an event to that in the Touchscreen, 917 // this would not be enough. Thus we make an attempt here at locating a touch record which *should* 918 // be receiving the event if it were to be processed by OnStateEvent. 919 920 var currentTouchState = (TouchState*)((byte*)currentStatePtr + touches[0].stateBlock.byteOffset); 921 var eventTouchState = (TouchState*)stateEventPtr->state; 922 var eventTouchId = eventTouchState->touchId; 923 var eventTouchPhase = eventTouchState->phase; 924 925 var touchControlCount = touches.Count; 926 for (var i = 0; i < touchControlCount; ++i) 927 { 928 var touch = &currentTouchState[i]; 929 if (touch->touchId == eventTouchId || (!touch->isInProgress && eventTouchPhase.IsActive())) 930 { 931 offset = primaryTouch.m_StateBlock.byteOffset + primaryTouch.m_StateBlock.alignedSizeInBytes - m_StateBlock.byteOffset + 932 (uint)(i * UnsafeUtility.SizeOf<TouchState>()); 933 return true; 934 } 935 } 936 937 return false; 938 } 939 940 // The only controls we can read out from a TouchState event are those that are part of TouchControl 941 // (and part of this Touchscreen). 942 var touchControl = control.FindInParentChain<TouchControl>(); 943 if (touchControl == null || touchControl.parent != this) 944 return false; 945 946 // We could allow *any* of the TouchControls on the Touchscreen here. We'd simply base the 947 // offset on the TouchControl of the 'control' we get as an argument. 948 // 949 // However, doing that would mean that all the TouchControls would map into the same input event. 950 // So when a piece of code like in InputUser goes and cycles through all controls to determine ones 951 // that have changed in an event, it would find that instead of a single touch position value changing, 952 // all of them would be changing from the same single event. 953 // 954 // For this reason, we lock things down to the primaryTouch control. 955 956 if (touchControl != primaryTouch) 957 return false; 958 959 offset = touchControl.stateBlock.byteOffset - m_StateBlock.byteOffset; 960 return true; 961 } 962 963 // Implement our own custom reset so that we can cancel touches instead of just wiping them 964 // with default state. 965 unsafe void ICustomDeviceReset.Reset() 966 { 967 var statePtr = currentStatePtr; 968 969 //// https://jira.unity3d.com/browse/ISX-930 970 ////TODO: Figure out a proper way to distinguish the source / reason for a state change. 971 //// What we're doing here is constructing an event solely for the purpose of Finger.ShouldRecordTouch() not 972 //// ignoring the state change like it does for delta resets. 973 974 using (var buffer = new NativeArray<byte>(StateEvent.GetEventSizeWithPayload<TouchState>(), Allocator.Temp)) 975 { 976 var eventPtr = (StateEvent*)buffer.GetUnsafePtr(); 977 978 eventPtr->baseEvent = new InputEvent(StateEvent.Type, buffer.Length, deviceId); 979 980 var primaryTouchState = (TouchState*)((byte*)statePtr + primaryTouch.stateBlock.byteOffset); 981 if (primaryTouchState->phase.IsActive()) 982 { 983 UnsafeUtility.MemCpy(eventPtr->state, primaryTouchState, UnsafeUtility.SizeOf<TouchState>()); 984 ((TouchState*)eventPtr->state)->phase = TouchPhase.Canceled; 985 InputState.Change(primaryTouch.phase, TouchPhase.Canceled, eventPtr: new InputEventPtr((InputEvent*)eventPtr)); 986 } 987 988 var touchStates = (TouchState*)((byte*)statePtr + touches[0].stateBlock.byteOffset); 989 var touchCount = touches.Count; 990 for (var i = 0; i < touchCount; ++i) 991 { 992 if (touchStates[i].phase.IsActive()) 993 { 994 UnsafeUtility.MemCpy(eventPtr->state, &touchStates[i], UnsafeUtility.SizeOf<TouchState>()); 995 ((TouchState*)eventPtr->state)->phase = TouchPhase.Canceled; 996 InputState.Change(touches[i].phase, TouchPhase.Canceled, eventPtr: new InputEventPtr((InputEvent*)eventPtr)); 997 } 998 } 999 } 1000 } 1001 1002 internal static unsafe bool MergeForward(InputEventPtr currentEventPtr, InputEventPtr nextEventPtr) 1003 { 1004 if (currentEventPtr.type != StateEvent.Type || nextEventPtr.type != StateEvent.Type) 1005 return false; 1006 1007 var currentEvent = StateEvent.FromUnchecked(currentEventPtr); 1008 var nextEvent = StateEvent.FromUnchecked(nextEventPtr); 1009 1010 if (currentEvent->stateFormat != TouchState.Format || nextEvent->stateFormat != TouchState.Format) 1011 return false; 1012 1013 var currentState = (TouchState*)currentEvent->state; 1014 var nextState = (TouchState*)nextEvent->state; 1015 1016 if (currentState->touchId != nextState->touchId || currentState->phaseId != nextState->phaseId || currentState->flags != nextState->flags) 1017 return false; 1018 1019 nextState->delta += currentState->delta; 1020 1021 return true; 1022 } 1023 1024 bool IEventMerger.MergeForward(InputEventPtr currentEventPtr, InputEventPtr nextEventPtr) 1025 { 1026 return MergeForward(currentEventPtr, nextEventPtr); 1027 } 1028 1029 // We can only detect taps on touch *release*. At which point it acts like a button that triggers and releases 1030 // in one operation. 1031 private static void TriggerTap(TouchControl control, ref TouchState state, InputEventPtr eventPtr) 1032 { 1033 ////REVIEW: we're updating the entire TouchControl here; we could update just the tap state using a delta event; problem 1034 //// is that the tap *down* still needs a full update on the state 1035 1036 // We don't increase tapCount here as we may be sending the tap from the same state to both the TouchControl 1037 // that got tapped and to primaryTouch. 1038 1039 // Press. 1040 state.isTapPress = true; 1041 state.isTapRelease = false; 1042 InputState.Change(control, ref state, eventPtr: eventPtr); 1043 1044 // Release. 1045 state.isTapPress = false; 1046 state.isTapRelease = true; 1047 InputState.Change(control, ref state, eventPtr: eventPtr); 1048 state.isTapRelease = false; 1049 } 1050 1051 internal static float s_TapTime; 1052 internal static float s_TapDelayTime; 1053 internal static float s_TapRadiusSquared; 1054 } 1055}