Ei kuvausta
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

Touch.cs 41KB

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