A game about forced loneliness, made by TACStudios
1using System; 2using System.Collections.Generic; 3using Unity.Collections.LowLevel.Unsafe; 4using UnityEngine.InputSystem.LowLevel; 5using UnityEngine.InputSystem.Controls; 6using UnityEngine.InputSystem.Utilities; 7 8////TODO: recorded times are baked *external* times; reset touch when coming out of play mode 9 10////REVIEW: record velocity on touches? or add method to very easily get the data? 11 12////REVIEW: do we need to keep old touches around on activeTouches like the old UnityEngine touch API? 13 14namespace UnityEngine.InputSystem.EnhancedTouch 15{ 16 /// <summary> 17 /// A high-level representation of a touch which automatically keeps track of a touch 18 /// over time. 19 /// </summary> 20 /// <remarks> 21 /// This API obsoletes the need for manually keeping tracking of touch IDs (<see cref="TouchControl.touchId"/>) 22 /// and touch phases (<see cref="TouchControl.phase"/>) in order to tell one touch apart from another. 23 /// 24 /// Also, this class protects against losing touches. If a touch is shorter-lived than a single input update, 25 /// <see cref="Touchscreen"/> may overwrite it with a new touch coming in in the same update whereas this class 26 /// will retain all changes that happened on the touchscreen in any particular update. 27 /// 28 /// The API makes a distinction between "fingers" and "touches". A touch refers to one contact state change event, that is, a 29 /// finger beginning to touch the screen (<see cref="TouchPhase.Began"/>), moving on the screen (<see cref="TouchPhase.Moved"/>), 30 /// or being lifted off the screen (<see cref="TouchPhase.Ended"/> or <see cref="TouchPhase.Canceled"/>). 31 /// A finger, on the other hand, always refers to the Nth contact on the screen. 32 /// 33 /// A Touch instance is a struct which only contains a reference to the actual data which is stored in unmanaged 34 /// memory. 35 /// </remarks> 36 [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1724:TypeNamesShouldNotMatchNamespaces")] 37 public struct Touch : IEquatable<Touch> 38 { 39 // The way this works is that at the core, it simply attaches one InputStateHistory per "<Touchscreen>/touch*" 40 // control and then presents a public API that crawls over the recorded touch history in various ways. 41 42 /// <summary> 43 /// Whether this touch record holds valid data. 44 /// </summary> 45 /// <value>If true, the data contained in the touch is valid.</value> 46 /// <remarks> 47 /// Touch data is stored in unmanaged memory as a circular input buffer. This means that when 48 /// the buffer runs out of capacity, older touch entries will get reused. When this happens, 49 /// existing <c>Touch</c> instances referring to the record become invalid. 50 /// 51 /// This property can be used to determine whether the record held on to by the <c>Touch</c> 52 /// instance is still valid. 53 /// 54 /// This property will be <c>false</c> for default-initialized <c>Touch</c> instances. 55 /// 56 /// Note that accessing most of the other properties on this struct when the touch is 57 /// invalid will trigger <c>InvalidOperationException</c>. 58 /// </remarks> 59 public bool valid => m_TouchRecord.valid; 60 61 /// <summary> 62 /// The finger used for the touch contact. Null only for default-initialized 63 /// instances of the struct. 64 /// </summary> 65 /// <value>Finger used for the touch contact.</value> 66 /// <seealso cref="activeFingers"/> 67 public Finger finger => m_Finger; 68 69 /// <summary> 70 /// Current phase of the touch. 71 /// </summary> 72 /// <value>Current phase of the touch.</value> 73 /// <remarks> 74 /// Every touch goes through a predefined cycle that starts with <see cref="TouchPhase.Began"/>, 75 /// then potentially <see cref="TouchPhase.Moved"/> and/or <see cref="TouchPhase.Stationary"/>, 76 /// and finally concludes with either <see cref="TouchPhase.Ended"/> or <see cref="TouchPhase.Canceled"/>. 77 /// 78 /// This property indicates where in the cycle the touch is. 79 /// </remarks> 80 /// <seealso cref="isInProgress"/> 81 /// <seealso cref="TouchControl.phase"/> 82 public TouchPhase phase => state.phase; 83 84 /// <summary> 85 /// Whether the touch has begun this frame, i.e. whether <see cref="phase"/> is <see cref="TouchPhase.Began"/>. 86 /// </summary> 87 /// <seealso cref="phase"/> 88 /// <seealso cref="ended"/> 89 /// <seealso cref="inProgress"/> 90 public bool began => phase == TouchPhase.Began; 91 92 /// <summary> 93 /// Whether the touch is currently in progress, i.e. whether <see cref="phase"/> is either 94 /// <see cref="TouchPhase.Moved"/>, <see cref="TouchPhase.Stationary"/>, or <see cref="TouchPhase.Began"/>. 95 /// </summary> 96 /// <seealso cref="phase"/> 97 /// <seealso cref="began"/> 98 /// <seealso cref="ended"/> 99 public bool inProgress => phase == TouchPhase.Moved || phase == TouchPhase.Stationary || phase == TouchPhase.Began; 100 101 /// <summary> 102 /// Whether the touch has ended this frame, i.e. whether <see cref="phase"/> is either 103 /// <see cref="TouchPhase.Ended"/> or <see cref="TouchPhase.Canceled"/>. 104 /// </summary> 105 /// <seealso cref="phase"/> 106 /// <seealso cref="began"/> 107 /// <seealso cref="isInProgress"/> 108 public bool ended => phase == TouchPhase.Ended || phase == TouchPhase.Canceled; 109 110 /// <summary> 111 /// Unique ID of the touch as (usually) assigned by the platform. 112 /// </summary> 113 /// <value>Unique, non-zero ID of the touch.</value> 114 /// <remarks> 115 /// Each touch contact that is made with the screen receives its own unique ID which is 116 /// normally assigned by the underlying platform. 117 /// 118 /// Note a platform may reuse touch IDs after their respective touches have finished. 119 /// This means that the guarantee of uniqueness is only made with respect to <see cref="activeTouches"/>. 120 /// 121 /// In particular, all touches in <see cref="history"/> will have the same ID whereas 122 /// touches in the a finger's <see cref="Finger.touchHistory"/> may end up having the same 123 /// touch ID even though constituting different physical touch contacts. 124 /// </remarks> 125 /// <seealso cref="TouchControl.touchId"/> 126 public int touchId => state.touchId; 127 128 /// <summary> 129 /// Normalized pressure of the touch against the touch surface. 130 /// </summary> 131 /// <value>Pressure level of the touch.</value> 132 /// <remarks> 133 /// Not all touchscreens are pressure-sensitive. If unsupported, this property will 134 /// always return 0. 135 /// 136 /// In general, touch pressure is supported on mobile platforms only. 137 /// 138 /// Note that it is possible for the value to go above 1 even though it is considered normalized. The reason is 139 /// that calibration on the system can put the maximum pressure point below the physically supported maximum value. 140 /// </remarks> 141 /// <seealso cref="TouchControl.pressure"/> 142 public float pressure => state.pressure; 143 144 /// <summary> 145 /// Screen-space radius of the touch. 146 /// </summary> 147 /// <value>Horizontal and vertical extents of the touch contact.</value> 148 /// <remarks> 149 /// If supported by the underlying device, this reports the size of the touch contact based on its 150 /// <see cref="screenPosition"/> center point. If not supported, this will be <c>default(Vector2)</c>. 151 /// </remarks> 152 /// <seealso cref="TouchControl.radius"/> 153 public Vector2 radius => state.radius; 154 155 /// <summary> 156 /// Time in seconds on the same timeline as <c>Time.realTimeSinceStartup</c> when the touch began. 157 /// </summary> 158 /// <value>Start time of the touch.</value> 159 /// <remarks> 160 /// This is the value of <see cref="InputEvent.time"/> when the touch started with 161 /// <see cref="phase"/> <see cref="TouchPhase.Began"/>. 162 /// </remarks> 163 /// <seealso cref="TouchControl.startTime"/> 164 public double startTime => state.startTime; 165 166 /// <summary> 167 /// Time in seconds on the same timeline as <c>Time.realTimeSinceStartup</c> when the touch record was 168 /// reported. 169 /// </summary> 170 /// <value>Time the touch record was reported.</value> 171 /// <remarks> 172 /// This is the value <see cref="InputEvent.time"/> of the event that signaled the current state 173 /// change for the touch. 174 /// </remarks> 175 public double time => m_TouchRecord.time; 176 177 /// <summary> 178 /// The touchscreen on which the touch occurred. 179 /// </summary> 180 /// <value>Touchscreen associated with the touch.</value> 181 public Touchscreen screen => finger.screen; 182 183 /// <summary> 184 /// Screen-space position of the touch. 185 /// </summary> 186 /// <value>Screen-space position of the touch.</value> 187 /// <seealso cref="TouchControl.position"/> 188 public Vector2 screenPosition => state.position; 189 190 /// <summary> 191 /// Screen-space position where the touch started. 192 /// </summary> 193 /// <value>Start position of the touch.</value> 194 /// <seealso cref="TouchControl.startPosition"/> 195 public Vector2 startScreenPosition => state.startPosition; 196 197 /// <summary> 198 /// Screen-space motion delta of the touch. 199 /// </summary> 200 /// <value>Screen-space motion delta of the touch.</value> 201 /// <remarks> 202 /// Note that deltas have behaviors attached to them different from most other 203 /// controls. See <see cref="Pointer.delta"/> for details. 204 /// </remarks> 205 /// <seealso cref="TouchControl.delta"/> 206 public Vector2 delta => state.delta; 207 208 /// <summary> 209 /// Number of times that the touch has been tapped in succession. 210 /// </summary> 211 /// <value>Indicates how many taps have been performed one after the other.</value> 212 /// <remarks> 213 /// Successive taps have to come within <see cref="InputSettings.multiTapDelayTime"/> for them 214 /// to increase the tap count. I.e. if a new tap finishes within that time after <see cref="startTime"/> 215 /// of the previous touch, the tap count is increased by one. If more than <see cref="InputSettings.multiTapDelayTime"/> 216 /// passes after a tap with no successive tap, the tap count is reset to zero. 217 /// </remarks> 218 /// <seealso cref="TouchControl.tapCount"/> 219 public int tapCount => state.tapCount; 220 221 /// <summary> 222 /// Whether the touch has performed a tap. 223 /// </summary> 224 /// <value>Indicates whether the touch has tapped the screen.</value> 225 /// <remarks> 226 /// A tap is defined as a touch that begins and ends within <see cref="InputSettings.defaultTapTime"/> and 227 /// stays within <see cref="InputSettings.tapRadius"/> of its <see cref="startScreenPosition"/>. If this 228 /// is the case for a touch, this button is set to 1 at the time the touch goes to <see cref="phase"/> 229 /// <see cref="TouchPhase.Ended"/>. 230 /// 231 /// Resets to 0 only when another touch is started on the control or when the control is reset. 232 /// </remarks> 233 /// <seealso cref="tapCount"/> 234 /// <seealso cref="InputSettings.defaultTapTime"/> 235 /// <seealso cref="TouchControl.tap"/> 236 public bool isTap => state.isTap; 237 238 /// <summary> 239 /// The index of the display containing the touch. 240 /// </summary> 241 /// <value>A zero based number representing the display index containing the touch.</value> 242 /// <seealso cref="TouchControl.displayIndex"/> 243 /// <seealso cref="Display"/> 244 public int displayIndex => state.displayIndex; 245 246 /// <summary> 247 /// Whether the touch is currently in progress, i.e. has a <see cref="phase"/> of 248 /// <see cref="TouchPhase.Began"/>, <see cref="TouchPhase.Moved"/>, or <see cref="TouchPhase.Stationary"/>. 249 /// </summary> 250 /// <value>Whether the touch is currently ongoing.</value> 251 public bool isInProgress 252 { 253 get 254 { 255 switch (phase) 256 { 257 case TouchPhase.Began: 258 case TouchPhase.Moved: 259 case TouchPhase.Stationary: 260 return true; 261 } 262 return false; 263 } 264 } 265 266 internal uint updateStepCount => state.updateStepCount; 267 internal uint uniqueId => extraData.uniqueId; 268 269 private unsafe ref TouchState state => ref *(TouchState*)m_TouchRecord.GetUnsafeMemoryPtr(); 270 private unsafe ref ExtraDataPerTouchState extraData => 271 ref *(ExtraDataPerTouchState*)m_TouchRecord.GetUnsafeExtraMemoryPtr(); 272 273 /// <summary> 274 /// History for this specific touch. 275 /// </summary> 276 /// <remarks> 277 /// Unlike <see cref="Finger.touchHistory"/>, this gives the history of this touch only. 278 /// </remarks> 279 public TouchHistory history 280 { 281 get 282 { 283 if (!valid) 284 throw new InvalidOperationException("Touch is invalid"); 285 return finger.GetTouchHistory(this); 286 } 287 } 288 289 /// <summary> 290 /// All touches that are either on-going as of the current frame or have ended in the current frame. 291 /// </summary> 292 /// <remarks> 293 /// A touch that begins in a frame will always have its phase set to <see cref="TouchPhase.Began"/> even 294 /// if there was also movement (or even an end/cancellation) for the touch in the same frame. 295 /// 296 /// A touch that begins and ends in the same frame will have its <see cref="TouchPhase.Began"/> surface 297 /// in that frame and then another entry with <see cref="TouchPhase.Ended"/> surface in the 298 /// <em>next</em> frame. This logic implies that there can be more active touches than concurrent touches 299 /// supported by the hardware/platform. 300 /// 301 /// A touch that begins and moves in the same frame will have its <see cref="TouchPhase.Began"/> surface 302 /// in that frame and then another entry with <see cref="TouchPhase.Moved"/> and the screen motion 303 /// surface in the <em>next</em> frame <em>except</em> if the touch also ended in the frame (in which 304 /// case <see cref="phase"/> will be <see cref="TouchPhase.Ended"/> instead of <see cref="TouchPhase.Moved"/>). 305 /// 306 /// Note that the touches reported by this API do <em>not</em> necessarily have to match the contents of 307 /// <see href="https://docs.unity3d.com/ScriptReference/Input-touches.html">UnityEngine.Input.touches</see>. 308 /// The reason for this is that the <c>UnityEngine.Input</c> API and the Input System API flush their input 309 /// queues at different points in time and may thus have a different view on available input. In particular, 310 /// the Input System event queue is flushed <em>later</em> in the frame than inputs for <c>UnityEngine.Input</c> 311 /// and may thus have newer inputs available. On Android, for example, touch input is gathered from a separate 312 /// UI thread and fed into the input system via a "background" event queue that can gather input asynchronously. 313 /// Due to this setup, touch events that will reach <c>UnityEngine.Input</c> only in the next frame may have 314 /// already reached the Input System. 315 /// 316 /// <example> 317 /// <code> 318 /// void Awake() 319 /// { 320 /// // Enable EnhancedTouch. 321 /// EnhancedTouchSupport.Enable(); 322 /// } 323 /// 324 /// void Update() 325 /// { 326 /// foreach (var touch in Touch.activeTouches) 327 /// if (touch.began) 328 /// Debug.Log($"Touch {touch} started this frame"); 329 /// else if (touch.ended) 330 /// Debug.Log($"Touch {touch} ended this frame"); 331 /// } 332 /// </code> 333 /// </example> 334 /// </remarks> 335 /// <exception cref="InvalidOperationException"><c>EnhancedTouch</c> has not been enabled via <see cref="EnhancedTouchSupport.Enable"/>.</exception> 336 /// <seealso cref="activeFingers"/> 337 public static ReadOnlyArray<Touch> activeTouches 338 { 339 get 340 { 341 EnhancedTouchSupport.CheckEnabled(); 342 // We lazily construct the array of active touches. 343 s_GlobalState.playerState.UpdateActiveTouches(); 344 return new ReadOnlyArray<Touch>(s_GlobalState.playerState.activeTouches, 0, s_GlobalState.playerState.activeTouchCount); 345 } 346 } 347 348 /// <summary> 349 /// An array of all possible concurrent touch contacts, i.e. all concurrent touch contacts regardless of whether 350 /// they are currently active or not. 351 /// </summary> 352 /// <remarks> 353 /// For querying only active fingers, use <see cref="activeFingers"/>. 354 /// 355 /// The length of this array will always correspond to the maximum number of concurrent touches supported by the system. 356 /// Note that the actual number of physically supported concurrent touches as determined by the current hardware and 357 /// operating system may be lower than this number. 358 /// </remarks> 359 /// <exception cref="InvalidOperationException"><c>EnhancedTouch</c> has not been enabled via <see cref="EnhancedTouchSupport.Enable"/>.</exception> 360 /// <seealso cref="activeTouches"/> 361 /// <seealso cref="activeFingers"/> 362 public static ReadOnlyArray<Finger> fingers 363 { 364 get 365 { 366 EnhancedTouchSupport.CheckEnabled(); 367 return new ReadOnlyArray<Finger>(s_GlobalState.playerState.fingers, 0, s_GlobalState.playerState.totalFingerCount); 368 } 369 } 370 371 /// <summary> 372 /// Set of currently active fingers, i.e. touch contacts that currently have an active touch (as defined by <see cref="activeTouches"/>). 373 /// </summary> 374 /// <exception cref="InvalidOperationException"><c>EnhancedTouch</c> has not been enabled via <see cref="EnhancedTouchSupport.Enable"/>.</exception> 375 /// <seealso cref="activeTouches"/> 376 /// <seealso cref="fingers"/> 377 public static ReadOnlyArray<Finger> activeFingers 378 { 379 get 380 { 381 EnhancedTouchSupport.CheckEnabled(); 382 // We lazily construct the array of active fingers. 383 s_GlobalState.playerState.UpdateActiveFingers(); 384 return new ReadOnlyArray<Finger>(s_GlobalState.playerState.activeFingers, 0, s_GlobalState.playerState.activeFingerCount); 385 } 386 } 387 388 /// <summary> 389 /// Return the set of <see cref="Touchscreen"/>s on which touch input is monitored. 390 /// </summary> 391 /// <exception cref="InvalidOperationException"><c>EnhancedTouch</c> has not been enabled via <see cref="EnhancedTouchSupport.Enable"/>.</exception> 392 public static IEnumerable<Touchscreen> screens 393 { 394 get 395 { 396 EnhancedTouchSupport.CheckEnabled(); 397 return s_GlobalState.touchscreens; 398 } 399 } 400 401 /// <summary> 402 /// Event that is invoked when a finger touches a <see cref="Touchscreen"/>. 403 /// </summary> 404 /// <exception cref="InvalidOperationException"><c>EnhancedTouch</c> has not been enabled via <see cref="EnhancedTouchSupport.Enable"/>.</exception> 405 /// <seealso cref="onFingerUp"/> 406 /// <seealso cref="onFingerMove"/> 407 public static event Action<Finger> onFingerDown 408 { 409 add 410 { 411 EnhancedTouchSupport.CheckEnabled(); 412 if (value == null) 413 throw new ArgumentNullException(nameof(value)); 414 s_GlobalState.onFingerDown.AddCallback(value); 415 } 416 remove 417 { 418 EnhancedTouchSupport.CheckEnabled(); 419 if (value == null) 420 throw new ArgumentNullException(nameof(value)); 421 s_GlobalState.onFingerDown.RemoveCallback(value); 422 } 423 } 424 425 /// <summary> 426 /// Event that is invoked when a finger stops touching a <see cref="Touchscreen"/>. 427 /// </summary> 428 /// <exception cref="InvalidOperationException"><c>EnhancedTouch</c> has not been enabled via <see cref="EnhancedTouchSupport.Enable"/>.</exception> 429 /// <seealso cref="onFingerDown"/> 430 /// <seealso cref="onFingerMove"/> 431 public static event Action<Finger> onFingerUp 432 { 433 add 434 { 435 EnhancedTouchSupport.CheckEnabled(); 436 if (value == null) 437 throw new ArgumentNullException(nameof(value)); 438 s_GlobalState.onFingerUp.AddCallback(value); 439 } 440 remove 441 { 442 EnhancedTouchSupport.CheckEnabled(); 443 if (value == null) 444 throw new ArgumentNullException(nameof(value)); 445 s_GlobalState.onFingerUp.RemoveCallback(value); 446 } 447 } 448 449 /// <summary> 450 /// Event that is invoked when a finger that is in contact with a <see cref="Touchscreen"/> moves 451 /// on the screen. 452 /// </summary> 453 /// <exception cref="InvalidOperationException"><c>EnhancedTouch</c> has not been enabled via <see cref="EnhancedTouchSupport.Enable"/>.</exception> 454 /// <seealso cref="onFingerUp"/> 455 /// <seealso cref="onFingerDown"/> 456 public static event Action<Finger> onFingerMove 457 { 458 add 459 { 460 EnhancedTouchSupport.CheckEnabled(); 461 if (value == null) 462 throw new ArgumentNullException(nameof(value)); 463 s_GlobalState.onFingerMove.AddCallback(value); 464 } 465 remove 466 { 467 EnhancedTouchSupport.CheckEnabled(); 468 if (value == null) 469 throw new ArgumentNullException(nameof(value)); 470 s_GlobalState.onFingerMove.RemoveCallback(value); 471 } 472 } 473 474 /* 475 public static Action<Finger> onFingerTap 476 { 477 get { throw new NotImplementedException(); } 478 set { throw new NotImplementedException(); } 479 } 480 */ 481 482 /// <summary> 483 /// The amount of history kept for each single touch. 484 /// </summary> 485 /// <remarks> 486 /// By default, this is zero meaning that no history information is kept for 487 /// touches. Setting this to <c>Int32.maxValue</c> will cause all history from 488 /// the beginning to the end of a touch being kept. 489 /// </remarks> 490 public static int maxHistoryLengthPerFinger 491 { 492 get => s_GlobalState.historyLengthPerFinger; 493 494 ////TODO 495 /*set { throw new NotImplementedException(); }*/ 496 } 497 498 internal Touch(Finger finger, InputStateHistory<TouchState>.Record touchRecord) 499 { 500 m_Finger = finger; 501 m_TouchRecord = touchRecord; 502 } 503 504 public override string ToString() 505 { 506 if (!valid) 507 return "<None>"; 508 509 return $"{{id={touchId} finger={finger.index} phase={phase} position={screenPosition} delta={delta} time={time}}}"; 510 } 511 512 public bool Equals(Touch other) 513 { 514 return Equals(m_Finger, other.m_Finger) && m_TouchRecord.Equals(other.m_TouchRecord); 515 } 516 517 public override bool Equals(object obj) 518 { 519 return obj is Touch other && Equals(other); 520 } 521 522 public override int GetHashCode() 523 { 524 unchecked 525 { 526 return ((m_Finger != null ? m_Finger.GetHashCode() : 0) * 397) ^ m_TouchRecord.GetHashCode(); 527 } 528 } 529 530 internal static void AddTouchscreen(Touchscreen screen) 531 { 532 Debug.Assert(!s_GlobalState.touchscreens.ContainsReference(screen), "Already added touchscreen"); 533 s_GlobalState.touchscreens.AppendWithCapacity(screen, capacityIncrement: 5); 534 535 // Add finger tracking to states. 536 s_GlobalState.playerState.AddFingers(screen); 537#if UNITY_EDITOR 538 s_GlobalState.editorState.AddFingers(screen); 539#endif 540 } 541 542 internal static void RemoveTouchscreen(Touchscreen screen) 543 { 544 Debug.Assert(s_GlobalState.touchscreens.ContainsReference(screen), "Did not add touchscreen"); 545 546 // Remove from list. 547 var index = s_GlobalState.touchscreens.IndexOfReference(screen); 548 s_GlobalState.touchscreens.RemoveAtWithCapacity(index); 549 550 // Remove fingers from states. 551 s_GlobalState.playerState.RemoveFingers(screen); 552#if UNITY_EDITOR 553 s_GlobalState.editorState.RemoveFingers(screen); 554#endif 555 } 556 557 ////TODO: only have this hooked when we actually need it 558 internal static void BeginUpdate() 559 { 560#if UNITY_EDITOR 561 if ((InputState.currentUpdateType == InputUpdateType.Editor && s_GlobalState.playerState.updateMask != InputUpdateType.Editor) || 562 (InputState.currentUpdateType != InputUpdateType.Editor && s_GlobalState.playerState.updateMask == InputUpdateType.Editor)) 563 { 564 // Either swap in editor state and retain currently active player state in s_EditorState 565 // or swap player state back in. 566 MemoryHelpers.Swap(ref s_GlobalState.playerState, ref s_GlobalState.editorState); 567 } 568#endif 569 570 // If we have any touches in activeTouches that are ended or canceled, 571 // we need to clear them in the next frame. 572 if (s_GlobalState.playerState.haveActiveTouchesNeedingRefreshNextUpdate) 573 s_GlobalState.playerState.haveBuiltActiveTouches = false; 574 } 575 576 private readonly Finger m_Finger; 577 internal InputStateHistory<TouchState>.Record m_TouchRecord; 578 579 /// <summary> 580 /// Holds global (static) touch state. 581 /// </summary> 582 internal struct GlobalState 583 { 584 internal InlinedArray<Touchscreen> touchscreens; 585 internal int historyLengthPerFinger; 586 internal CallbackArray<Action<Finger>> onFingerDown; 587 internal CallbackArray<Action<Finger>> onFingerMove; 588 internal CallbackArray<Action<Finger>> onFingerUp; 589 590 internal FingerAndTouchState playerState; 591#if UNITY_EDITOR 592 internal FingerAndTouchState editorState; 593#endif 594 } 595 596 private static GlobalState CreateGlobalState() 597 { // Convenient method since parameterized construction is default 598 return new GlobalState { historyLengthPerFinger = 64 }; 599 } 600 601 internal static GlobalState s_GlobalState = CreateGlobalState(); 602 603 internal static ISavedState SaveAndResetState() 604 { 605 // Save current state 606 var savedState = new SavedStructState<GlobalState>( 607 ref s_GlobalState, 608 (ref GlobalState state) => s_GlobalState = state, 609 () => { /* currently nothing to dispose */ }); 610 611 // Reset global state 612 s_GlobalState = CreateGlobalState(); 613 614 return savedState; 615 } 616 617 // In scenarios where we have to support multiple different types of input updates (e.g. in editor or in 618 // player when both dynamic and fixed input updates are enabled), we need more than one copy of touch state. 619 // We encapsulate the state in this struct so that we can easily swap it. 620 // 621 // NOTE: Finger instances are per state. This means that you will actually see different Finger instances for 622 // the same finger in two different update types. 623 [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA1001:TypesThatOwnDisposableFieldsShouldBeDisposable", 624 Justification = "Managed internally")] 625 internal struct FingerAndTouchState 626 { 627 public InputUpdateType updateMask; 628 public Finger[] fingers; 629 public Finger[] activeFingers; 630 public Touch[] activeTouches; 631 public int activeFingerCount; 632 public int activeTouchCount; 633 public int totalFingerCount; 634 public uint lastId; 635 public bool haveBuiltActiveTouches; 636 public bool haveActiveTouchesNeedingRefreshNextUpdate; 637 638 // `activeTouches` adds yet another view of input state that is different from "normal" recorded 639 // state history. In this view, touches become stationary in the next update and deltas reset 640 // between updates. We solve this by storing state separately for active touches. We *only* do 641 // so when `activeTouches` is actually queried meaning that `activeTouches` has no overhead if 642 // not used. 643 public InputStateHistory<TouchState> activeTouchState; 644 645 public void AddFingers(Touchscreen screen) 646 { 647 var touchCount = screen.touches.Count; 648 ArrayHelpers.EnsureCapacity(ref fingers, totalFingerCount, touchCount); 649 for (var i = 0; i < touchCount; ++i) 650 { 651 var finger = new Finger(screen, i, updateMask); 652 ArrayHelpers.AppendWithCapacity(ref fingers, ref totalFingerCount, finger); 653 } 654 } 655 656 public void RemoveFingers(Touchscreen screen) 657 { 658 var touchCount = screen.touches.Count; 659 for (var i = 0; i < fingers.Length; ++i) 660 { 661 if (fingers[i].screen != screen) 662 continue; 663 664 // Release unmanaged memory. 665 for (var n = 0; n < touchCount; ++n) 666 fingers[i + n].m_StateHistory.Dispose(); 667 668 ////REVIEW: leave Fingers in place and reuse the instances? 669 ArrayHelpers.EraseSliceWithCapacity(ref fingers, ref totalFingerCount, i, touchCount); 670 break; 671 } 672 673 // Force rebuilding of active touches. 674 haveBuiltActiveTouches = false; 675 } 676 677 public void Destroy() 678 { 679 for (var i = 0; i < totalFingerCount; ++i) 680 fingers[i].m_StateHistory.Dispose(); 681 activeTouchState?.Dispose(); 682 activeTouchState = null; 683 } 684 685 public void UpdateActiveFingers() 686 { 687 ////TODO: do this only once per update per activeFingers getter 688 689 activeFingerCount = 0; 690 for (var i = 0; i < totalFingerCount; ++i) 691 { 692 var finger = fingers[i]; 693 var lastTouch = finger.currentTouch; 694 if (lastTouch.valid) 695 ArrayHelpers.AppendWithCapacity(ref activeFingers, ref activeFingerCount, finger); 696 } 697 } 698 699 public unsafe void UpdateActiveTouches() 700 { 701 if (haveBuiltActiveTouches) 702 return; 703 704 // Clear activeTouches state. 705 if (activeTouchState == null) 706 { 707 activeTouchState = new InputStateHistory<TouchState> 708 { 709 extraMemoryPerRecord = UnsafeUtility.SizeOf<ExtraDataPerTouchState>() 710 }; 711 } 712 else 713 { 714 activeTouchState.Clear(); 715 activeTouchState.m_ControlCount = 0; 716 activeTouchState.m_Controls.Clear(); 717 } 718 activeTouchCount = 0; 719 haveActiveTouchesNeedingRefreshNextUpdate = false; 720 var currentUpdateStepCount = InputUpdate.s_UpdateStepCount; 721 722 ////OPTIMIZE: Handle touchscreens that have no activity more efficiently 723 ////FIXME: This is sensitive to history size; we probably need to ensure that the Begans and Endeds/Canceleds of touches are always available to us 724 //// (instead of rebuild activeTouches from scratch each time, may be more useful to update it) 725 726 // Go through fingers and for each one, get the touches that were active this update. 727 for (var i = 0; i < totalFingerCount; ++i) 728 { 729 ref var finger = ref fingers[i]; 730 731 // NOTE: Many of the operations here are inlined in order to not perform the same 732 // checks/computations repeatedly. 733 734 var history = finger.m_StateHistory; 735 var touchRecordCount = history.Count; 736 if (touchRecordCount == 0) 737 continue; 738 739 // We're walking newest-first through the touch history but want the resulting list of 740 // active touches to be oldest first (so that a record for an ended touch comes before 741 // a record of a new touch started on the same finger). To achieve that, we insert 742 // new touch entries for any finger always at the same index (i.e. we prepend rather 743 // than append). 744 var insertAt = activeTouchCount; 745 746 // Go back in time through the touch records on the finger and collect any touch 747 // active in the current frame. Note that this may yield *multiple* touches for the 748 // finger as there may be touches that have ended in the frame while in the same 749 // frame, a new touch was started. 750 var currentTouchId = 0; 751 var currentTouchState = default(TouchState*); 752 var touchRecordIndex = history.UserIndexToRecordIndex(touchRecordCount - 1); // Start with last record. 753 var touchRecordHeader = history.GetRecordUnchecked(touchRecordIndex); 754 var touchRecordSize = history.bytesPerRecord; 755 var extraMemoryOffset = touchRecordSize - history.extraMemoryPerRecord; 756 for (var n = 0; n < touchRecordCount; ++n) 757 { 758 if (n != 0) 759 { 760 --touchRecordIndex; 761 if (touchRecordIndex < 0) 762 { 763 // We're wrapping around so buffer must be full. Go to last record in buffer. 764 //touchRecordIndex = history.historyDepth - history.m_HeadIndex - 1; 765 touchRecordIndex = history.historyDepth - 1; 766 touchRecordHeader = history.GetRecordUnchecked(touchRecordIndex); 767 } 768 else 769 { 770 touchRecordHeader = (InputStateHistory.RecordHeader*)((byte*)touchRecordHeader - touchRecordSize); 771 } 772 } 773 774 // Skip if part of an ongoing touch we've already recorded. 775 var touchState = (TouchState*)touchRecordHeader->statePtrWithoutControlIndex; // History is tied to a single TouchControl. 776 var wasUpdatedThisFrame = touchState->updateStepCount == currentUpdateStepCount; 777 if (touchState->touchId == currentTouchId && !touchState->phase.IsEndedOrCanceled()) 778 { 779 // If this is the Began record for the touch and that one happened in 780 // the current frame, we force the touch phase to Began. 781 if (wasUpdatedThisFrame && touchState->phase == TouchPhase.Began) 782 { 783 Debug.Assert(currentTouchState != null, "Must have current touch record at this point"); 784 785 currentTouchState->phase = TouchPhase.Began; 786 currentTouchState->position = touchState->position; 787 currentTouchState->delta = default; 788 789 haveActiveTouchesNeedingRefreshNextUpdate = true; 790 } 791 792 // Need to continue here as there may still be Ended touches that need to 793 // be taken into account (as in, there may actually be multiple active touches 794 // for the same finger due to how the polling API works). 795 continue; 796 } 797 798 // If the touch is older than the current frame and it's a touch that has 799 // ended, we don't need to look further back into the history as anything 800 // coming before that will be equally outdated. 801 if (touchState->phase.IsEndedOrCanceled()) 802 { 803 // An exception are touches that both began *and* ended in the previous frame. 804 // For these, we surface the Began in the previous update and the Ended in the 805 // current frame. 806 if (!(touchState->beganInSameFrame && touchState->updateStepCount == currentUpdateStepCount - 1) && 807 !wasUpdatedThisFrame) 808 break; 809 } 810 811 // Make a copy of the touch so that we can modify data like deltas and phase. 812 // NOTE: Again, not using AddRecord() for speed. 813 // NOTE: Unlike `history`, `activeTouchState` stores control indices as each active touch 814 // will correspond to a different TouchControl. 815 var touchExtraState = (ExtraDataPerTouchState*)((byte*)touchRecordHeader + extraMemoryOffset); 816 var newRecordHeader = activeTouchState.AllocateRecord(out var newRecordIndex); 817 var newRecordState = (TouchState*)newRecordHeader->statePtrWithControlIndex; 818 var newRecordExtraState = (ExtraDataPerTouchState*)((byte*)newRecordHeader + activeTouchState.bytesPerRecord - UnsafeUtility.SizeOf<ExtraDataPerTouchState>()); 819 newRecordHeader->time = touchRecordHeader->time; 820 newRecordHeader->controlIndex = ArrayHelpers.AppendWithCapacity(ref activeTouchState.m_Controls, 821 ref activeTouchState.m_ControlCount, finger.m_StateHistory.controls[0]); 822 823 UnsafeUtility.MemCpy(newRecordState, touchState, UnsafeUtility.SizeOf<TouchState>()); 824 UnsafeUtility.MemCpy(newRecordExtraState, touchExtraState, UnsafeUtility.SizeOf<ExtraDataPerTouchState>()); 825 826 // If the touch hasn't moved this frame, mark it stationary. 827 // EXCEPT: If we are looked at a Moved touch that also began in the same frame and that 828 // frame is the one immediately preceding us. In that case, we want to surface the Moved 829 // as if it happened this frame. 830 var phase = touchState->phase; 831 if ((phase == TouchPhase.Moved || phase == TouchPhase.Began) && 832 !wasUpdatedThisFrame && !(phase == TouchPhase.Moved && touchState->beganInSameFrame && touchState->updateStepCount == currentUpdateStepCount - 1)) 833 { 834 newRecordState->phase = TouchPhase.Stationary; 835 newRecordState->delta = default; 836 } 837 // If the touch wasn't updated this frame, zero out its delta. 838 else if (!wasUpdatedThisFrame && !touchState->beganInSameFrame) 839 { 840 newRecordState->delta = default; 841 } 842 else 843 { 844 // We want accumulated deltas only on activeTouches. 845 newRecordState->delta = newRecordExtraState->accumulatedDelta; 846 } 847 848 var newRecord = new InputStateHistory<TouchState>.Record(activeTouchState, newRecordIndex, newRecordHeader); 849 var newTouch = new Touch(finger, newRecord); 850 851 ArrayHelpers.InsertAtWithCapacity(ref activeTouches, ref activeTouchCount, insertAt, newTouch); 852 853 currentTouchId = touchState->touchId; 854 currentTouchState = newRecordState; 855 856 // For anything but stationary touches on the activeTouches list, we need a subsequent 857 // update in the next frame. 858 if (newTouch.phase != TouchPhase.Stationary) 859 haveActiveTouchesNeedingRefreshNextUpdate = true; 860 } 861 } 862 863 haveBuiltActiveTouches = true; 864 } 865 } 866 867 internal struct ExtraDataPerTouchState 868 { 869 public Vector2 accumulatedDelta; 870 871 public uint uniqueId; // Unique ID for touch *record* (i.e. multiple TouchStates having the same touchId will still each have a unique ID). 872 873 ////TODO 874 //public uint tapCount; 875 } 876 } 877}