123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266 |
- using Unity.Collections.LowLevel.Unsafe;
- using UnityEngine.InputSystem.LowLevel;
- using UnityEngine.InputSystem.Utilities;
-
- namespace UnityEngine.InputSystem.EnhancedTouch
- {
- /// <summary>
- /// A source of touches (<see cref="Touch"/>).
- /// </summary>
- /// <remarks>
- /// Each <see cref="Touchscreen"/> has a limited number of fingers it supports corresponding to the total number of concurrent
- /// touches supported by the screen. Unlike a <see cref="Touch"/>, a <see cref="Finger"/> will stay the same and valid for the
- /// lifetime of its <see cref="Touchscreen"/>.
- ///
- /// Note that a Finger does not represent an actual physical finger in the world. That is, the same Finger instance might be used,
- /// for example, for a touch from the index finger at one point and then for a touch from the ring finger. Each Finger simply
- /// corresponds to the Nth touch on the given screen.
- /// </remarks>
- /// <seealso cref="Touch"/>
- [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA1001:TypesThatOwnDisposableFieldsShouldBeDisposable",
- Justification = "Holds on to internally managed memory which should not be disposed by the user.")]
- public class Finger
- {
- // This class stores pretty much all the data that is kept by the enhanced touch system. All
- // the finger and history tracking is found here.
-
- /// <summary>
- /// The screen that the finger is associated with.
- /// </summary>
- /// <value>Touchscreen associated with the touch.</value>
- public Touchscreen screen { get; }
-
- /// <summary>
- /// Index of the finger on <see cref="screen"/>. Each finger corresponds to the Nth touch on a screen.
- /// </summary>
- public int index { get; }
-
- /// <summary>
- /// Whether the finger is currently touching the screen.
- /// </summary>
- public bool isActive => currentTouch.valid;
-
- /// <summary>
- /// The current position of the finger on the screen or <c>default(Vector2)</c> if there is no
- /// ongoing touch.
- /// </summary>
- public Vector2 screenPosition
- {
- get
- {
- ////REVIEW: should this work off of currentTouch instead of lastTouch?
- var touch = lastTouch;
- if (!touch.valid)
- return default;
- return touch.screenPosition;
- }
- }
-
- ////REVIEW: should lastTouch and currentTouch have accumulated deltas? would that be confusing?
-
- /// <summary>
- /// The last touch that happened on the finger or <c>default(Touch)</c> (with <see cref="Touch.valid"/> being
- /// false) if no touch has been registered on the finger yet.
- /// </summary>
- /// <remarks>
- /// A given touch will be returned from this property for as long as no new touch has been started. As soon as a
- /// new touch is registered on the finger, the property switches to the new touch.
- /// </remarks>
- public Touch lastTouch
- {
- get
- {
- var count = m_StateHistory.Count;
- if (count == 0)
- return default;
- return new Touch(this, m_StateHistory[count - 1]);
- }
- }
-
- /// <summary>
- /// The currently ongoing touch for the finger or <c>default(Touch)</c> (with <see cref="Touch.valid"/> being false)
- /// if no touch is currently in progress on the finger.
- /// </summary>
- public Touch currentTouch
- {
- get
- {
- var touch = lastTouch;
- if (!touch.valid)
- return default;
- if (touch.isInProgress)
- return touch;
- // Ended touches stay current in the frame they ended in.
- if (touch.updateStepCount == InputUpdate.s_UpdateStepCount)
- return touch;
- return default;
- }
- }
-
- /// <summary>
- /// The full touch history of the finger.
- /// </summary>
- /// <remarks>
- /// The history is capped at <see cref="Touch.maxHistoryLengthPerFinger"/>. Once full, newer touch records will start
- /// overwriting older entries. Note that this means that a given touch will not trace all the way back to its beginning
- /// if it runs past the max history size.
- /// </remarks>
- public TouchHistory touchHistory => new TouchHistory(this, m_StateHistory);
-
- internal readonly InputStateHistory<TouchState> m_StateHistory;
-
- internal Finger(Touchscreen screen, int index, InputUpdateType updateMask)
- {
- this.screen = screen;
- this.index = index;
-
- // Set up history recording.
- m_StateHistory = new InputStateHistory<TouchState>(screen.touches[index])
- {
- historyDepth = Touch.maxHistoryLengthPerFinger,
- extraMemoryPerRecord = UnsafeUtility.SizeOf<Touch.ExtraDataPerTouchState>(),
- onRecordAdded = OnTouchRecorded,
- onShouldRecordStateChange = ShouldRecordTouch,
- updateMask = updateMask,
- };
- m_StateHistory.StartRecording();
-
- // record the current state if touch is already in progress
- if (screen.touches[index].isInProgress)
- m_StateHistory.RecordStateChange(screen.touches[index], screen.touches[index].value);
- }
-
- private static unsafe bool ShouldRecordTouch(InputControl control, double time, InputEventPtr eventPtr)
- {
- // We only want to record changes that come from events. We ignore internal state
- // changes that Touchscreen itself generates. This includes the resetting of deltas.
- if (!eventPtr.valid)
- return false;
- var eventType = eventPtr.type;
- if (eventType != StateEvent.Type && eventType != DeltaStateEvent.Type)
- return false;
-
- // Direct memory access for speed.
- var currentTouchState = (TouchState*)((byte*)control.currentStatePtr + control.stateBlock.byteOffset);
-
- // Touchscreen will record a button down and button up on a TouchControl when a tap occurs.
- // We only want to record the button down, not the button up.
- if (currentTouchState->isTapRelease)
- return false;
-
- return true;
- }
-
- private unsafe void OnTouchRecorded(InputStateHistory.Record record)
- {
- var recordIndex = record.recordIndex;
- var touchHeader = m_StateHistory.GetRecordUnchecked(recordIndex);
- var touchState = (TouchState*)touchHeader->statePtrWithoutControlIndex; // m_StateHistory is bound to a single TouchControl.
- touchState->updateStepCount = InputUpdate.s_UpdateStepCount;
-
- // Invalidate activeTouches.
- Touch.s_GlobalState.playerState.haveBuiltActiveTouches = false;
-
- // Record the extra data we maintain for each touch.
- var extraData = (Touch.ExtraDataPerTouchState*)((byte*)touchHeader + m_StateHistory.bytesPerRecord -
- UnsafeUtility.SizeOf<Touch.ExtraDataPerTouchState>());
- extraData->uniqueId = ++Touch.s_GlobalState.playerState.lastId;
-
- // We get accumulated deltas from Touchscreen. Store the accumulated
- // value and "unaccumulate" the value we store on delta.
- extraData->accumulatedDelta = touchState->delta;
- if (touchState->phase != TouchPhase.Began)
- {
- // Inlined (instead of just using record.previous) for speed. Bypassing
- // the safety checks here.
- if (recordIndex != m_StateHistory.m_HeadIndex)
- {
- var previousRecordIndex = recordIndex == 0 ? m_StateHistory.historyDepth - 1 : recordIndex - 1;
- var previousTouchHeader = m_StateHistory.GetRecordUnchecked(previousRecordIndex);
- var previousTouchState = (TouchState*)previousTouchHeader->statePtrWithoutControlIndex;
- touchState->delta -= previousTouchState->delta;
- touchState->beganInSameFrame = previousTouchState->beganInSameFrame &&
- previousTouchState->updateStepCount == touchState->updateStepCount;
- }
- }
- else
- {
- touchState->beganInSameFrame = true;
- }
-
- // Trigger callback.
- switch (touchState->phase)
- {
- case TouchPhase.Began:
- DelegateHelpers.InvokeCallbacksSafe(ref Touch.s_GlobalState.onFingerDown, this, "Touch.onFingerDown");
- break;
- case TouchPhase.Moved:
- DelegateHelpers.InvokeCallbacksSafe(ref Touch.s_GlobalState.onFingerMove, this, "Touch.onFingerMove");
- break;
- case TouchPhase.Ended:
- case TouchPhase.Canceled:
- DelegateHelpers.InvokeCallbacksSafe(ref Touch.s_GlobalState.onFingerUp, this, "Touch.onFingerUp");
- break;
- }
- }
-
- private unsafe Touch FindTouch(uint uniqueId)
- {
- Debug.Assert(uniqueId != default, "0 is not a valid ID");
- foreach (var record in m_StateHistory)
- {
- if (((Touch.ExtraDataPerTouchState*)record.GetUnsafeExtraMemoryPtrUnchecked())->uniqueId == uniqueId)
- return new Touch(this, record);
- }
-
- return default;
- }
-
- internal unsafe TouchHistory GetTouchHistory(Touch touch)
- {
- Debug.Assert(touch.finger == this);
-
- // If the touch is not pointing to our history, it's probably a touch we copied for
- // activeTouches. We know the unique ID of the touch so go and try to find the touch
- // in our history.
- var touchRecord = touch.m_TouchRecord;
- if (touchRecord.owner != m_StateHistory)
- {
- touch = FindTouch(touch.uniqueId);
- if (!touch.valid)
- return default;
- }
-
- var touchId = touch.touchId;
- var startIndex = touch.m_TouchRecord.index;
-
- // If the current touch isn't the beginning of the touch, search back through the
- // history for all touches belonging to the same contact.
- var count = 0;
- if (touch.phase != TouchPhase.Began)
- {
- for (var previousRecord = touch.m_TouchRecord.previous; previousRecord.valid; previousRecord = previousRecord.previous)
- {
- var touchState = (TouchState*)previousRecord.GetUnsafeMemoryPtr();
-
- // Stop if the touch doesn't belong to the same contact.
- if (touchState->touchId != touchId)
- break;
- ++count;
-
- // Stop if we've found the beginning of the touch.
- if (touchState->phase == TouchPhase.Began)
- break;
- }
- }
-
- if (count == 0)
- return default;
-
- // We don't want to include the touch we started with.
- --startIndex;
-
- return new TouchHistory(this, m_StateHistory, startIndex, count);
- }
- }
- }
|