No Description
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.

Finger.cs 11KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266
  1. using Unity.Collections.LowLevel.Unsafe;
  2. using UnityEngine.InputSystem.LowLevel;
  3. using UnityEngine.InputSystem.Utilities;
  4. namespace UnityEngine.InputSystem.EnhancedTouch
  5. {
  6. /// <summary>
  7. /// A source of touches (<see cref="Touch"/>).
  8. /// </summary>
  9. /// <remarks>
  10. /// Each <see cref="Touchscreen"/> has a limited number of fingers it supports corresponding to the total number of concurrent
  11. /// touches supported by the screen. Unlike a <see cref="Touch"/>, a <see cref="Finger"/> will stay the same and valid for the
  12. /// lifetime of its <see cref="Touchscreen"/>.
  13. ///
  14. /// Note that a Finger does not represent an actual physical finger in the world. That is, the same Finger instance might be used,
  15. /// for example, for a touch from the index finger at one point and then for a touch from the ring finger. Each Finger simply
  16. /// corresponds to the Nth touch on the given screen.
  17. /// </remarks>
  18. /// <seealso cref="Touch"/>
  19. [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA1001:TypesThatOwnDisposableFieldsShouldBeDisposable",
  20. Justification = "Holds on to internally managed memory which should not be disposed by the user.")]
  21. public class Finger
  22. {
  23. // This class stores pretty much all the data that is kept by the enhanced touch system. All
  24. // the finger and history tracking is found here.
  25. /// <summary>
  26. /// The screen that the finger is associated with.
  27. /// </summary>
  28. /// <value>Touchscreen associated with the touch.</value>
  29. public Touchscreen screen { get; }
  30. /// <summary>
  31. /// Index of the finger on <see cref="screen"/>. Each finger corresponds to the Nth touch on a screen.
  32. /// </summary>
  33. public int index { get; }
  34. /// <summary>
  35. /// Whether the finger is currently touching the screen.
  36. /// </summary>
  37. public bool isActive => currentTouch.valid;
  38. /// <summary>
  39. /// The current position of the finger on the screen or <c>default(Vector2)</c> if there is no
  40. /// ongoing touch.
  41. /// </summary>
  42. public Vector2 screenPosition
  43. {
  44. get
  45. {
  46. ////REVIEW: should this work off of currentTouch instead of lastTouch?
  47. var touch = lastTouch;
  48. if (!touch.valid)
  49. return default;
  50. return touch.screenPosition;
  51. }
  52. }
  53. ////REVIEW: should lastTouch and currentTouch have accumulated deltas? would that be confusing?
  54. /// <summary>
  55. /// The last touch that happened on the finger or <c>default(Touch)</c> (with <see cref="Touch.valid"/> being
  56. /// false) if no touch has been registered on the finger yet.
  57. /// </summary>
  58. /// <remarks>
  59. /// A given touch will be returned from this property for as long as no new touch has been started. As soon as a
  60. /// new touch is registered on the finger, the property switches to the new touch.
  61. /// </remarks>
  62. public Touch lastTouch
  63. {
  64. get
  65. {
  66. var count = m_StateHistory.Count;
  67. if (count == 0)
  68. return default;
  69. return new Touch(this, m_StateHistory[count - 1]);
  70. }
  71. }
  72. /// <summary>
  73. /// The currently ongoing touch for the finger or <c>default(Touch)</c> (with <see cref="Touch.valid"/> being false)
  74. /// if no touch is currently in progress on the finger.
  75. /// </summary>
  76. public Touch currentTouch
  77. {
  78. get
  79. {
  80. var touch = lastTouch;
  81. if (!touch.valid)
  82. return default;
  83. if (touch.isInProgress)
  84. return touch;
  85. // Ended touches stay current in the frame they ended in.
  86. if (touch.updateStepCount == InputUpdate.s_UpdateStepCount)
  87. return touch;
  88. return default;
  89. }
  90. }
  91. /// <summary>
  92. /// The full touch history of the finger.
  93. /// </summary>
  94. /// <remarks>
  95. /// The history is capped at <see cref="Touch.maxHistoryLengthPerFinger"/>. Once full, newer touch records will start
  96. /// overwriting older entries. Note that this means that a given touch will not trace all the way back to its beginning
  97. /// if it runs past the max history size.
  98. /// </remarks>
  99. public TouchHistory touchHistory => new TouchHistory(this, m_StateHistory);
  100. internal readonly InputStateHistory<TouchState> m_StateHistory;
  101. internal Finger(Touchscreen screen, int index, InputUpdateType updateMask)
  102. {
  103. this.screen = screen;
  104. this.index = index;
  105. // Set up history recording.
  106. m_StateHistory = new InputStateHistory<TouchState>(screen.touches[index])
  107. {
  108. historyDepth = Touch.maxHistoryLengthPerFinger,
  109. extraMemoryPerRecord = UnsafeUtility.SizeOf<Touch.ExtraDataPerTouchState>(),
  110. onRecordAdded = OnTouchRecorded,
  111. onShouldRecordStateChange = ShouldRecordTouch,
  112. updateMask = updateMask,
  113. };
  114. m_StateHistory.StartRecording();
  115. // record the current state if touch is already in progress
  116. if (screen.touches[index].isInProgress)
  117. m_StateHistory.RecordStateChange(screen.touches[index], screen.touches[index].value);
  118. }
  119. private static unsafe bool ShouldRecordTouch(InputControl control, double time, InputEventPtr eventPtr)
  120. {
  121. // We only want to record changes that come from events. We ignore internal state
  122. // changes that Touchscreen itself generates. This includes the resetting of deltas.
  123. if (!eventPtr.valid)
  124. return false;
  125. var eventType = eventPtr.type;
  126. if (eventType != StateEvent.Type && eventType != DeltaStateEvent.Type)
  127. return false;
  128. // Direct memory access for speed.
  129. var currentTouchState = (TouchState*)((byte*)control.currentStatePtr + control.stateBlock.byteOffset);
  130. // Touchscreen will record a button down and button up on a TouchControl when a tap occurs.
  131. // We only want to record the button down, not the button up.
  132. if (currentTouchState->isTapRelease)
  133. return false;
  134. return true;
  135. }
  136. private unsafe void OnTouchRecorded(InputStateHistory.Record record)
  137. {
  138. var recordIndex = record.recordIndex;
  139. var touchHeader = m_StateHistory.GetRecordUnchecked(recordIndex);
  140. var touchState = (TouchState*)touchHeader->statePtrWithoutControlIndex; // m_StateHistory is bound to a single TouchControl.
  141. touchState->updateStepCount = InputUpdate.s_UpdateStepCount;
  142. // Invalidate activeTouches.
  143. Touch.s_GlobalState.playerState.haveBuiltActiveTouches = false;
  144. // Record the extra data we maintain for each touch.
  145. var extraData = (Touch.ExtraDataPerTouchState*)((byte*)touchHeader + m_StateHistory.bytesPerRecord -
  146. UnsafeUtility.SizeOf<Touch.ExtraDataPerTouchState>());
  147. extraData->uniqueId = ++Touch.s_GlobalState.playerState.lastId;
  148. // We get accumulated deltas from Touchscreen. Store the accumulated
  149. // value and "unaccumulate" the value we store on delta.
  150. extraData->accumulatedDelta = touchState->delta;
  151. if (touchState->phase != TouchPhase.Began)
  152. {
  153. // Inlined (instead of just using record.previous) for speed. Bypassing
  154. // the safety checks here.
  155. if (recordIndex != m_StateHistory.m_HeadIndex)
  156. {
  157. var previousRecordIndex = recordIndex == 0 ? m_StateHistory.historyDepth - 1 : recordIndex - 1;
  158. var previousTouchHeader = m_StateHistory.GetRecordUnchecked(previousRecordIndex);
  159. var previousTouchState = (TouchState*)previousTouchHeader->statePtrWithoutControlIndex;
  160. touchState->delta -= previousTouchState->delta;
  161. touchState->beganInSameFrame = previousTouchState->beganInSameFrame &&
  162. previousTouchState->updateStepCount == touchState->updateStepCount;
  163. }
  164. }
  165. else
  166. {
  167. touchState->beganInSameFrame = true;
  168. }
  169. // Trigger callback.
  170. switch (touchState->phase)
  171. {
  172. case TouchPhase.Began:
  173. DelegateHelpers.InvokeCallbacksSafe(ref Touch.s_GlobalState.onFingerDown, this, "Touch.onFingerDown");
  174. break;
  175. case TouchPhase.Moved:
  176. DelegateHelpers.InvokeCallbacksSafe(ref Touch.s_GlobalState.onFingerMove, this, "Touch.onFingerMove");
  177. break;
  178. case TouchPhase.Ended:
  179. case TouchPhase.Canceled:
  180. DelegateHelpers.InvokeCallbacksSafe(ref Touch.s_GlobalState.onFingerUp, this, "Touch.onFingerUp");
  181. break;
  182. }
  183. }
  184. private unsafe Touch FindTouch(uint uniqueId)
  185. {
  186. Debug.Assert(uniqueId != default, "0 is not a valid ID");
  187. foreach (var record in m_StateHistory)
  188. {
  189. if (((Touch.ExtraDataPerTouchState*)record.GetUnsafeExtraMemoryPtrUnchecked())->uniqueId == uniqueId)
  190. return new Touch(this, record);
  191. }
  192. return default;
  193. }
  194. internal unsafe TouchHistory GetTouchHistory(Touch touch)
  195. {
  196. Debug.Assert(touch.finger == this);
  197. // If the touch is not pointing to our history, it's probably a touch we copied for
  198. // activeTouches. We know the unique ID of the touch so go and try to find the touch
  199. // in our history.
  200. var touchRecord = touch.m_TouchRecord;
  201. if (touchRecord.owner != m_StateHistory)
  202. {
  203. touch = FindTouch(touch.uniqueId);
  204. if (!touch.valid)
  205. return default;
  206. }
  207. var touchId = touch.touchId;
  208. var startIndex = touch.m_TouchRecord.index;
  209. // If the current touch isn't the beginning of the touch, search back through the
  210. // history for all touches belonging to the same contact.
  211. var count = 0;
  212. if (touch.phase != TouchPhase.Began)
  213. {
  214. for (var previousRecord = touch.m_TouchRecord.previous; previousRecord.valid; previousRecord = previousRecord.previous)
  215. {
  216. var touchState = (TouchState*)previousRecord.GetUnsafeMemoryPtr();
  217. // Stop if the touch doesn't belong to the same contact.
  218. if (touchState->touchId != touchId)
  219. break;
  220. ++count;
  221. // Stop if we've found the beginning of the touch.
  222. if (touchState->phase == TouchPhase.Began)
  223. break;
  224. }
  225. }
  226. if (count == 0)
  227. return default;
  228. // We don't want to include the touch we started with.
  229. --startIndex;
  230. return new TouchHistory(this, m_StateHistory, startIndex, count);
  231. }
  232. }
  233. }