Maze / Library /PackageCache /com.unity.inputsystem@1.6.1 /InputSystem /Plugins /EnhancedTouch /Touch.cs
| using System; | |
| using System.Collections.Generic; | |
| using Unity.Collections.LowLevel.Unsafe; | |
| using UnityEngine.InputSystem.LowLevel; | |
| using UnityEngine.InputSystem.Controls; | |
| using UnityEngine.InputSystem.Utilities; | |
| ////TODO: recorded times are baked *external* times; reset touch when coming out of play mode | |
| ////REVIEW: record velocity on touches? or add method to very easily get the data? | |
| ////REVIEW: do we need to keep old touches around on activeTouches like the old UnityEngine touch API? | |
| namespace UnityEngine.InputSystem.EnhancedTouch | |
| { | |
| /// <summary> | |
| /// A high-level representation of a touch which automatically keeps track of a touch | |
| /// over time. | |
| /// </summary> | |
| /// <remarks> | |
| /// This API obsoletes the need for manually keeping tracking of touch IDs (<see cref="TouchControl.touchId"/>) | |
| /// and touch phases (<see cref="TouchControl.phase"/>) in order to tell one touch apart from another. | |
| /// | |
| /// Also, this class protects against losing touches. If a touch is shorter-lived than a single input update, | |
| /// <see cref="Touchscreen"/> may overwrite it with a new touch coming in in the same update whereas this class | |
| /// will retain all changes that happened on the touchscreen in any particular update. | |
| /// | |
| /// The API makes a distinction between "fingers" and "touches". A touch refers to one contact state change event, that is, a | |
| /// finger beginning to touch the screen (<see cref="TouchPhase.Began"/>), moving on the screen (<see cref="TouchPhase.Moved"/>), | |
| /// or being lifted off the screen (<see cref="TouchPhase.Ended"/> or <see cref="TouchPhase.Canceled"/>). | |
| /// A finger, on the other hand, always refers to the Nth contact on the screen. | |
| /// | |
| /// A Touch instance is a struct which only contains a reference to the actual data which is stored in unmanaged | |
| /// memory. | |
| /// </remarks> | |
| [] | |
| public struct Touch : IEquatable<Touch> | |
| { | |
| // The way this works is that at the core, it simply attaches one InputStateHistory per "<Touchscreen>/touch*" | |
| // control and then presents a public API that crawls over the recorded touch history in various ways. | |
| /// <summary> | |
| /// Whether this touch record holds valid data. | |
| /// </summary> | |
| /// <value>If true, the data contained in the touch is valid.</value> | |
| /// <remarks> | |
| /// Touch data is stored in unmanaged memory as a circular input buffer. This means that when | |
| /// the buffer runs out of capacity, older touch entries will get reused. When this happens, | |
| /// existing <c>Touch</c> instances referring to the record become invalid. | |
| /// | |
| /// This property can be used to determine whether the record held on to by the <c>Touch</c> | |
| /// instance is still valid. | |
| /// | |
| /// This property will be <c>false</c> for default-initialized <c>Touch</c> instances. | |
| /// | |
| /// Note that accessing most of the other properties on this struct when the touch is | |
| /// invalid will trigger <c>InvalidOperationException</c>. | |
| /// </remarks> | |
| public bool valid => m_TouchRecord.valid; | |
| /// <summary> | |
| /// The finger used for the touch contact. Null only for default-initialized | |
| /// instances of the struct. | |
| /// </summary> | |
| /// <value>Finger used for the touch contact.</value> | |
| /// <seealso cref="activeFingers"/> | |
| public Finger finger => m_Finger; | |
| /// <summary> | |
| /// Current phase of the touch. | |
| /// </summary> | |
| /// <value>Current phase of the touch.</value> | |
| /// <remarks> | |
| /// Every touch goes through a predefined cycle that starts with <see cref="TouchPhase.Began"/>, | |
| /// then potentially <see cref="TouchPhase.Moved"/> and/or <see cref="TouchPhase.Stationary"/>, | |
| /// and finally concludes with either <see cref="TouchPhase.Ended"/> or <see cref="TouchPhase.Canceled"/>. | |
| /// | |
| /// This property indicates where in the cycle the touch is. | |
| /// </remarks> | |
| /// <seealso cref="isInProgress"/> | |
| /// <seealso cref="TouchControl.phase"/> | |
| public TouchPhase phase => state.phase; | |
| /// <summary> | |
| /// Whether the touch has begun this frame, i.e. whether <see cref="phase"/> is <see cref="TouchPhase.Began"/>. | |
| /// </summary> | |
| /// <seealso cref="phase"/> | |
| /// <seealso cref="ended"/> | |
| /// <seealso cref="inProgress"/> | |
| public bool began => phase == TouchPhase.Began; | |
| /// <summary> | |
| /// Whether the touch is currently in progress, i.e. whether <see cref="phase"/> is either | |
| /// <see cref="TouchPhase.Moved"/>, <see cref="TouchPhase.Stationary"/>, or <see cref="TouchPhase.Began"/>. | |
| /// </summary> | |
| /// <seealso cref="phase"/> | |
| /// <seealso cref="began"/> | |
| /// <seealso cref="ended"/> | |
| public bool inProgress => phase == TouchPhase.Moved || phase == TouchPhase.Stationary || phase == TouchPhase.Began; | |
| /// <summary> | |
| /// Whether the touch has ended this frame, i.e. whether <see cref="phase"/> is either | |
| /// <see cref="TouchPhase.Ended"/> or <see cref="TouchPhase.Canceled"/>. | |
| /// </summary> | |
| /// <seealso cref="phase"/> | |
| /// <seealso cref="began"/> | |
| /// <seealso cref="isInProgress"/> | |
| public bool ended => phase == TouchPhase.Ended || phase == TouchPhase.Canceled; | |
| /// <summary> | |
| /// Unique ID of the touch as (usually) assigned by the platform. | |
| /// </summary> | |
| /// <value>Unique, non-zero ID of the touch.</value> | |
| /// <remarks> | |
| /// Each touch contact that is made with the screen receives its own unique ID which is | |
| /// normally assigned by the underlying platform. | |
| /// | |
| /// Note a platform may reuse touch IDs after their respective touches have finished. | |
| /// This means that the guarantee of uniqueness is only made with respect to <see cref="activeTouches"/>. | |
| /// | |
| /// In particular, all touches in <see cref="history"/> will have the same ID whereas | |
| /// touches in the a finger's <see cref="Finger.touchHistory"/> may end up having the same | |
| /// touch ID even though constituting different physical touch contacts. | |
| /// </remarks> | |
| /// <seealso cref="TouchControl.touchId"/> | |
| public int touchId => state.touchId; | |
| /// <summary> | |
| /// Normalized pressure of the touch against the touch surface. | |
| /// </summary> | |
| /// <value>Pressure level of the touch.</value> | |
| /// <remarks> | |
| /// Not all touchscreens are pressure-sensitive. If unsupported, this property will | |
| /// always return 0. | |
| /// | |
| /// In general, touch pressure is supported on mobile platforms only. | |
| /// | |
| /// Note that it is possible for the value to go above 1 even though it is considered normalized. The reason is | |
| /// that calibration on the system can put the maximum pressure point below the physically supported maximum value. | |
| /// </remarks> | |
| /// <seealso cref="TouchControl.pressure"/> | |
| public float pressure => state.pressure; | |
| /// <summary> | |
| /// Screen-space radius of the touch. | |
| /// </summary> | |
| /// <value>Horizontal and vertical extents of the touch contact.</value> | |
| /// <remarks> | |
| /// If supported by the underlying device, this reports the size of the touch contact based on its | |
| /// <see cref="screenPosition"/> center point. If not supported, this will be <c>default(Vector2)</c>. | |
| /// </remarks> | |
| /// <seealso cref="TouchControl.radius"/> | |
| public Vector2 radius => state.radius; | |
| /// <summary> | |
| /// Time in seconds on the same timeline as <c>Time.realTimeSinceStartup</c> when the touch began. | |
| /// </summary> | |
| /// <value>Start time of the touch.</value> | |
| /// <remarks> | |
| /// This is the value of <see cref="InputEvent.time"/> when the touch started with | |
| /// <see cref="phase"/> <see cref="TouchPhase.Began"/>. | |
| /// </remarks> | |
| /// <seealso cref="TouchControl.startTime"/> | |
| public double startTime => state.startTime; | |
| /// <summary> | |
| /// Time in seconds on the same timeline as <c>Time.realTimeSinceStartup</c> when the touch record was | |
| /// reported. | |
| /// </summary> | |
| /// <value>Time the touch record was reported.</value> | |
| /// <remarks> | |
| /// This is the value <see cref="InputEvent.time"/> of the event that signaled the current state | |
| /// change for the touch. | |
| /// </remarks> | |
| public double time => m_TouchRecord.time; | |
| /// <summary> | |
| /// The touchscreen on which the touch occurred. | |
| /// </summary> | |
| /// <value>Touchscreen associated with the touch.</value> | |
| public Touchscreen screen => finger.screen; | |
| /// <summary> | |
| /// Screen-space position of the touch. | |
| /// </summary> | |
| /// <value>Screen-space position of the touch.</value> | |
| /// <seealso cref="TouchControl.position"/> | |
| public Vector2 screenPosition => state.position; | |
| /// <summary> | |
| /// Screen-space position where the touch started. | |
| /// </summary> | |
| /// <value>Start position of the touch.</value> | |
| /// <seealso cref="TouchControl.startPosition"/> | |
| public Vector2 startScreenPosition => state.startPosition; | |
| /// <summary> | |
| /// Screen-space motion delta of the touch. | |
| /// </summary> | |
| /// <value>Screen-space motion delta of the touch.</value> | |
| /// <remarks> | |
| /// Note that deltas have behaviors attached to them different from most other | |
| /// controls. See <see cref="Pointer.delta"/> for details. | |
| /// </remarks> | |
| /// <seealso cref="TouchControl.delta"/> | |
| public Vector2 delta => state.delta; | |
| /// <summary> | |
| /// Number of times that the touch has been tapped in succession. | |
| /// </summary> | |
| /// <value>Indicates how many taps have been performed one after the other.</value> | |
| /// <remarks> | |
| /// Successive taps have to come within <see cref="InputSettings.multiTapDelayTime"/> for them | |
| /// to increase the tap count. I.e. if a new tap finishes within that time after <see cref="startTime"/> | |
| /// of the previous touch, the tap count is increased by one. If more than <see cref="InputSettings.multiTapDelayTime"/> | |
| /// passes after a tap with no successive tap, the tap count is reset to zero. | |
| /// </remarks> | |
| /// <seealso cref="TouchControl.tapCount"/> | |
| public int tapCount => state.tapCount; | |
| /// <summary> | |
| /// Whether the touch has performed a tap. | |
| /// </summary> | |
| /// <value>Indicates whether the touch has tapped the screen.</value> | |
| /// <remarks> | |
| /// A tap is defined as a touch that begins and ends within <see cref="InputSettings.defaultTapTime"/> and | |
| /// stays within <see cref="InputSettings.tapRadius"/> of its <see cref="startScreenPosition"/>. If this | |
| /// is the case for a touch, this button is set to 1 at the time the touch goes to <see cref="phase"/> | |
| /// <see cref="TouchPhase.Ended"/>. | |
| /// | |
| /// Resets to 0 only when another touch is started on the control or when the control is reset. | |
| /// </remarks> | |
| /// <seealso cref="tapCount"/> | |
| /// <seealso cref="InputSettings.defaultTapTime"/> | |
| /// <seealso cref="TouchControl.tap"/> | |
| public bool isTap => state.isTap; | |
| /// <summary> | |
| /// The index of the display containing the touch. | |
| /// </summary> | |
| /// <value>A zero based number representing the display index containing the touch.</value> | |
| /// <seealso cref="TouchControl.displayIndex"/> | |
| /// <seealso cref="Display"/> | |
| public int displayIndex => state.displayIndex; | |
| /// <summary> | |
| /// Whether the touch is currently in progress, i.e. has a <see cref="phase"/> of | |
| /// <see cref="TouchPhase.Began"/>, <see cref="TouchPhase.Moved"/>, or <see cref="TouchPhase.Stationary"/>. | |
| /// </summary> | |
| /// <value>Whether the touch is currently ongoing.</value> | |
| public bool isInProgress | |
| { | |
| get | |
| { | |
| switch (phase) | |
| { | |
| case TouchPhase.Began: | |
| case TouchPhase.Moved: | |
| case TouchPhase.Stationary: | |
| return true; | |
| } | |
| return false; | |
| } | |
| } | |
| internal uint updateStepCount => state.updateStepCount; | |
| internal uint uniqueId => extraData.uniqueId; | |
| private unsafe ref TouchState state => ref *(TouchState*)m_TouchRecord.GetUnsafeMemoryPtr(); | |
| private unsafe ref ExtraDataPerTouchState extraData => | |
| ref *(ExtraDataPerTouchState*)m_TouchRecord.GetUnsafeExtraMemoryPtr(); | |
| /// <summary> | |
| /// History for this specific touch. | |
| /// </summary> | |
| /// <remarks> | |
| /// Unlike <see cref="Finger.touchHistory"/>, this gives the history of this touch only. | |
| /// </remarks> | |
| public TouchHistory history | |
| { | |
| get | |
| { | |
| if (!valid) | |
| throw new InvalidOperationException("Touch is invalid"); | |
| return finger.GetTouchHistory(this); | |
| } | |
| } | |
| /// <summary> | |
| /// All touches that are either on-going as of the current frame or have ended in the current frame. | |
| /// </summary> | |
| /// <remarks> | |
| /// A touch that begins in a frame will always have its phase set to <see cref="TouchPhase.Began"/> even | |
| /// if there was also movement (or even an end/cancellation) for the touch in the same frame. | |
| /// | |
| /// A touch that begins and ends in the same frame will have its <see cref="TouchPhase.Began"/> surface | |
| /// in that frame and then another entry with <see cref="TouchPhase.Ended"/> surface in the | |
| /// <em>next</em> frame. This logic implies that there can be more active touches than concurrent touches | |
| /// supported by the hardware/platform. | |
| /// | |
| /// A touch that begins and moves in the same frame will have its <see cref="TouchPhase.Began"/> surface | |
| /// in that frame and then another entry with <see cref="TouchPhase.Moved"/> and the screen motion | |
| /// surface in the <em>next</em> frame <em>except</em> if the touch also ended in the frame (in which | |
| /// case <see cref="phase"/> will be <see cref="TouchPhase.Ended"/> instead of <see cref="TouchPhase.Moved"/>). | |
| /// | |
| /// Note that the touches reported by this API do <em>not</em> necessarily have to match the contents of | |
| /// <see href="https://docs.unity3d.com/ScriptReference/Input-touches.html">UnityEngine.Input.touches</see>. | |
| /// The reason for this is that the <c>UnityEngine.Input</c> API and the Input System API flush their input | |
| /// queues at different points in time and may thus have a different view on available input. In particular, | |
| /// the Input System event queue is flushed <em>later</em> in the frame than inputs for <c>UnityEngine.Input</c> | |
| /// and may thus have newer inputs available. On Android, for example, touch input is gathered from a separate | |
| /// UI thread and fed into the input system via a "background" event queue that can gather input asynchronously. | |
| /// Due to this setup, touch events that will reach <c>UnityEngine.Input</c> only in the next frame may have | |
| /// already reached the Input System. | |
| /// | |
| /// <example> | |
| /// <code> | |
| /// void Awake() | |
| /// { | |
| /// // Enable EnhancedTouch. | |
| /// EnhancedTouchSupport.Enable(); | |
| /// } | |
| /// | |
| /// void Update() | |
| /// { | |
| /// foreach (var touch in Touch.activeTouches) | |
| /// if (touch.began) | |
| /// Debug.Log($"Touch {touch} started this frame"); | |
| /// else if (touch.ended) | |
| /// Debug.Log($"Touch {touch} ended this frame"); | |
| /// } | |
| /// </code> | |
| /// </example> | |
| /// </remarks> | |
| /// <exception cref="InvalidOperationException"><c>EnhancedTouch</c> has not been enabled via <see cref="EnhancedTouchSupport.Enable"/>.</exception> | |
| /// <seealso cref="activeFingers"/> | |
| public static ReadOnlyArray<Touch> activeTouches | |
| { | |
| get | |
| { | |
| EnhancedTouchSupport.CheckEnabled(); | |
| // We lazily construct the array of active touches. | |
| s_GlobalState.playerState.UpdateActiveTouches(); | |
| return new ReadOnlyArray<Touch>(s_GlobalState.playerState.activeTouches, 0, s_GlobalState.playerState.activeTouchCount); | |
| } | |
| } | |
| /// <summary> | |
| /// An array of all possible concurrent touch contacts, i.e. all concurrent touch contacts regardless of whether | |
| /// they are currently active or not. | |
| /// </summary> | |
| /// <remarks> | |
| /// For querying only active fingers, use <see cref="activeFingers"/>. | |
| /// | |
| /// The length of this array will always correspond to the maximum number of concurrent touches supported by the system. | |
| /// Note that the actual number of physically supported concurrent touches as determined by the current hardware and | |
| /// operating system may be lower than this number. | |
| /// </remarks> | |
| /// <exception cref="InvalidOperationException"><c>EnhancedTouch</c> has not been enabled via <see cref="EnhancedTouchSupport.Enable"/>.</exception> | |
| /// <seealso cref="activeTouches"/> | |
| /// <seealso cref="activeFingers"/> | |
| public static ReadOnlyArray<Finger> fingers | |
| { | |
| get | |
| { | |
| EnhancedTouchSupport.CheckEnabled(); | |
| return new ReadOnlyArray<Finger>(s_GlobalState.playerState.fingers, 0, s_GlobalState.playerState.totalFingerCount); | |
| } | |
| } | |
| /// <summary> | |
| /// Set of currently active fingers, i.e. touch contacts that currently have an active touch (as defined by <see cref="activeTouches"/>). | |
| /// </summary> | |
| /// <exception cref="InvalidOperationException"><c>EnhancedTouch</c> has not been enabled via <see cref="EnhancedTouchSupport.Enable"/>.</exception> | |
| /// <seealso cref="activeTouches"/> | |
| /// <seealso cref="fingers"/> | |
| public static ReadOnlyArray<Finger> activeFingers | |
| { | |
| get | |
| { | |
| EnhancedTouchSupport.CheckEnabled(); | |
| // We lazily construct the array of active fingers. | |
| s_GlobalState.playerState.UpdateActiveFingers(); | |
| return new ReadOnlyArray<Finger>(s_GlobalState.playerState.activeFingers, 0, s_GlobalState.playerState.activeFingerCount); | |
| } | |
| } | |
| /// <summary> | |
| /// Return the set of <see cref="Touchscreen"/>s on which touch input is monitored. | |
| /// </summary> | |
| /// <exception cref="InvalidOperationException"><c>EnhancedTouch</c> has not been enabled via <see cref="EnhancedTouchSupport.Enable"/>.</exception> | |
| public static IEnumerable<Touchscreen> screens | |
| { | |
| get | |
| { | |
| EnhancedTouchSupport.CheckEnabled(); | |
| return s_GlobalState.touchscreens; | |
| } | |
| } | |
| /// <summary> | |
| /// Event that is invoked when a finger touches a <see cref="Touchscreen"/>. | |
| /// </summary> | |
| /// <exception cref="InvalidOperationException"><c>EnhancedTouch</c> has not been enabled via <see cref="EnhancedTouchSupport.Enable"/>.</exception> | |
| /// <seealso cref="onFingerUp"/> | |
| /// <seealso cref="onFingerMove"/> | |
| public static event Action<Finger> onFingerDown | |
| { | |
| add | |
| { | |
| EnhancedTouchSupport.CheckEnabled(); | |
| if (value == null) | |
| throw new ArgumentNullException(nameof(value)); | |
| s_GlobalState.onFingerDown.AddCallback(value); | |
| } | |
| remove | |
| { | |
| EnhancedTouchSupport.CheckEnabled(); | |
| if (value == null) | |
| throw new ArgumentNullException(nameof(value)); | |
| s_GlobalState.onFingerDown.RemoveCallback(value); | |
| } | |
| } | |
| /// <summary> | |
| /// Event that is invoked when a finger stops touching a <see cref="Touchscreen"/>. | |
| /// </summary> | |
| /// <exception cref="InvalidOperationException"><c>EnhancedTouch</c> has not been enabled via <see cref="EnhancedTouchSupport.Enable"/>.</exception> | |
| /// <seealso cref="onFingerDown"/> | |
| /// <seealso cref="onFingerMove"/> | |
| public static event Action<Finger> onFingerUp | |
| { | |
| add | |
| { | |
| EnhancedTouchSupport.CheckEnabled(); | |
| if (value == null) | |
| throw new ArgumentNullException(nameof(value)); | |
| s_GlobalState.onFingerUp.AddCallback(value); | |
| } | |
| remove | |
| { | |
| EnhancedTouchSupport.CheckEnabled(); | |
| if (value == null) | |
| throw new ArgumentNullException(nameof(value)); | |
| s_GlobalState.onFingerUp.RemoveCallback(value); | |
| } | |
| } | |
| /// <summary> | |
| /// Event that is invoked when a finger that is in contact with a <see cref="Touchscreen"/> moves | |
| /// on the screen. | |
| /// </summary> | |
| /// <exception cref="InvalidOperationException"><c>EnhancedTouch</c> has not been enabled via <see cref="EnhancedTouchSupport.Enable"/>.</exception> | |
| /// <seealso cref="onFingerUp"/> | |
| /// <seealso cref="onFingerDown"/> | |
| public static event Action<Finger> onFingerMove | |
| { | |
| add | |
| { | |
| EnhancedTouchSupport.CheckEnabled(); | |
| if (value == null) | |
| throw new ArgumentNullException(nameof(value)); | |
| s_GlobalState.onFingerMove.AddCallback(value); | |
| } | |
| remove | |
| { | |
| EnhancedTouchSupport.CheckEnabled(); | |
| if (value == null) | |
| throw new ArgumentNullException(nameof(value)); | |
| s_GlobalState.onFingerMove.RemoveCallback(value); | |
| } | |
| } | |
| /* | |
| public static Action<Finger> onFingerTap | |
| { | |
| get { throw new NotImplementedException(); } | |
| set { throw new NotImplementedException(); } | |
| } | |
| */ | |
| /// <summary> | |
| /// The amount of history kept for each single touch. | |
| /// </summary> | |
| /// <remarks> | |
| /// By default, this is zero meaning that no history information is kept for | |
| /// touches. Setting this to <c>Int32.maxValue</c> will cause all history from | |
| /// the beginning to the end of a touch being kept. | |
| /// </remarks> | |
| public static int maxHistoryLengthPerFinger | |
| { | |
| get => s_GlobalState.historyLengthPerFinger; | |
| ////TODO | |
| /*set { throw new NotImplementedException(); }*/ | |
| } | |
| internal Touch(Finger finger, InputStateHistory<TouchState>.Record touchRecord) | |
| { | |
| m_Finger = finger; | |
| m_TouchRecord = touchRecord; | |
| } | |
| public override string ToString() | |
| { | |
| if (!valid) | |
| return "<None>"; | |
| return $"{{id={touchId} finger={finger.index} phase={phase} position={screenPosition} delta={delta} time={time}}}"; | |
| } | |
| public bool Equals(Touch other) | |
| { | |
| return Equals(m_Finger, other.m_Finger) && m_TouchRecord.Equals(other.m_TouchRecord); | |
| } | |
| public override bool Equals(object obj) | |
| { | |
| return obj is Touch other && Equals(other); | |
| } | |
| public override int GetHashCode() | |
| { | |
| unchecked | |
| { | |
| return ((m_Finger != null ? m_Finger.GetHashCode() : 0) * 397) ^ m_TouchRecord.GetHashCode(); | |
| } | |
| } | |
| internal static void AddTouchscreen(Touchscreen screen) | |
| { | |
| Debug.Assert(!s_GlobalState.touchscreens.ContainsReference(screen), "Already added touchscreen"); | |
| s_GlobalState.touchscreens.AppendWithCapacity(screen, capacityIncrement: 5); | |
| // Add finger tracking to states. | |
| s_GlobalState.playerState.AddFingers(screen); | |
| s_GlobalState.editorState.AddFingers(screen); | |
| } | |
| internal static void RemoveTouchscreen(Touchscreen screen) | |
| { | |
| Debug.Assert(s_GlobalState.touchscreens.ContainsReference(screen), "Did not add touchscreen"); | |
| // Remove from list. | |
| var index = s_GlobalState.touchscreens.IndexOfReference(screen); | |
| s_GlobalState.touchscreens.RemoveAtWithCapacity(index); | |
| // Remove fingers from states. | |
| s_GlobalState.playerState.RemoveFingers(screen); | |
| s_GlobalState.editorState.RemoveFingers(screen); | |
| } | |
| ////TODO: only have this hooked when we actually need it | |
| internal static void BeginUpdate() | |
| { | |
| if ((InputState.currentUpdateType == InputUpdateType.Editor && s_GlobalState.playerState.updateMask != InputUpdateType.Editor) || | |
| (InputState.currentUpdateType != InputUpdateType.Editor && s_GlobalState.playerState.updateMask == InputUpdateType.Editor)) | |
| { | |
| // Either swap in editor state and retain currently active player state in s_EditorState | |
| // or swap player state back in. | |
| MemoryHelpers.Swap(ref s_GlobalState.playerState, ref s_GlobalState.editorState); | |
| } | |
| // If we have any touches in activeTouches that are ended or canceled, | |
| // we need to clear them in the next frame. | |
| if (s_GlobalState.playerState.haveActiveTouchesNeedingRefreshNextUpdate) | |
| s_GlobalState.playerState.haveBuiltActiveTouches = false; | |
| } | |
| private readonly Finger m_Finger; | |
| internal InputStateHistory<TouchState>.Record m_TouchRecord; | |
| /// <summary> | |
| /// Holds global (static) touch state. | |
| /// </summary> | |
| internal struct GlobalState | |
| { | |
| internal InlinedArray<Touchscreen> touchscreens; | |
| internal int historyLengthPerFinger; | |
| internal CallbackArray<Action<Finger>> onFingerDown; | |
| internal CallbackArray<Action<Finger>> onFingerMove; | |
| internal CallbackArray<Action<Finger>> onFingerUp; | |
| internal FingerAndTouchState playerState; | |
| internal FingerAndTouchState editorState; | |
| } | |
| private static GlobalState CreateGlobalState() | |
| { // Convenient method since parameterized construction is default | |
| return new GlobalState { historyLengthPerFinger = 64 }; | |
| } | |
| internal static GlobalState s_GlobalState = CreateGlobalState(); | |
| internal static ISavedState SaveAndResetState() | |
| { | |
| // Save current state | |
| var savedState = new SavedStructState<GlobalState>( | |
| ref s_GlobalState, | |
| (ref GlobalState state) => s_GlobalState = state, | |
| () => { /* currently nothing to dispose */ }); | |
| // Reset global state | |
| s_GlobalState = CreateGlobalState(); | |
| return savedState; | |
| } | |
| // In scenarios where we have to support multiple different types of input updates (e.g. in editor or in | |
| // player when both dynamic and fixed input updates are enabled), we need more than one copy of touch state. | |
| // We encapsulate the state in this struct so that we can easily swap it. | |
| // | |
| // NOTE: Finger instances are per state. This means that you will actually see different Finger instances for | |
| // the same finger in two different update types. | |
| [ | |
| ] | |
| internal struct FingerAndTouchState | |
| { | |
| public InputUpdateType updateMask; | |
| public Finger[] fingers; | |
| public Finger[] activeFingers; | |
| public Touch[] activeTouches; | |
| public int activeFingerCount; | |
| public int activeTouchCount; | |
| public int totalFingerCount; | |
| public uint lastId; | |
| public bool haveBuiltActiveTouches; | |
| public bool haveActiveTouchesNeedingRefreshNextUpdate; | |
| // `activeTouches` adds yet another view of input state that is different from "normal" recorded | |
| // state history. In this view, touches become stationary in the next update and deltas reset | |
| // between updates. We solve this by storing state separately for active touches. We *only* do | |
| // so when `activeTouches` is actually queried meaning that `activeTouches` has no overhead if | |
| // not used. | |
| public InputStateHistory<TouchState> activeTouchState; | |
| public void AddFingers(Touchscreen screen) | |
| { | |
| var touchCount = screen.touches.Count; | |
| ArrayHelpers.EnsureCapacity(ref fingers, totalFingerCount, touchCount); | |
| for (var i = 0; i < touchCount; ++i) | |
| { | |
| var finger = new Finger(screen, i, updateMask); | |
| ArrayHelpers.AppendWithCapacity(ref fingers, ref totalFingerCount, finger); | |
| } | |
| } | |
| public void RemoveFingers(Touchscreen screen) | |
| { | |
| var touchCount = screen.touches.Count; | |
| for (var i = 0; i < fingers.Length; ++i) | |
| { | |
| if (fingers[i].screen != screen) | |
| continue; | |
| // Release unmanaged memory. | |
| for (var n = 0; n < touchCount; ++n) | |
| fingers[i + n].m_StateHistory.Dispose(); | |
| ////REVIEW: leave Fingers in place and reuse the instances? | |
| ArrayHelpers.EraseSliceWithCapacity(ref fingers, ref totalFingerCount, i, touchCount); | |
| break; | |
| } | |
| // Force rebuilding of active touches. | |
| haveBuiltActiveTouches = false; | |
| } | |
| public void Destroy() | |
| { | |
| for (var i = 0; i < totalFingerCount; ++i) | |
| fingers[i].m_StateHistory.Dispose(); | |
| activeTouchState?.Dispose(); | |
| activeTouchState = null; | |
| } | |
| public void UpdateActiveFingers() | |
| { | |
| ////TODO: do this only once per update per activeFingers getter | |
| activeFingerCount = 0; | |
| for (var i = 0; i < totalFingerCount; ++i) | |
| { | |
| var finger = fingers[i]; | |
| var lastTouch = finger.currentTouch; | |
| if (lastTouch.valid) | |
| ArrayHelpers.AppendWithCapacity(ref activeFingers, ref activeFingerCount, finger); | |
| } | |
| } | |
| public unsafe void UpdateActiveTouches() | |
| { | |
| if (haveBuiltActiveTouches) | |
| return; | |
| // Clear activeTouches state. | |
| if (activeTouchState == null) | |
| { | |
| activeTouchState = new InputStateHistory<TouchState> | |
| { | |
| extraMemoryPerRecord = UnsafeUtility.SizeOf<ExtraDataPerTouchState>() | |
| }; | |
| } | |
| else | |
| { | |
| activeTouchState.Clear(); | |
| activeTouchState.m_ControlCount = 0; | |
| activeTouchState.m_Controls.Clear(); | |
| } | |
| activeTouchCount = 0; | |
| haveActiveTouchesNeedingRefreshNextUpdate = false; | |
| var currentUpdateStepCount = InputUpdate.s_UpdateStepCount; | |
| ////OPTIMIZE: Handle touchscreens that have no activity more efficiently | |
| ////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 | |
| //// (instead of rebuild activeTouches from scratch each time, may be more useful to update it) | |
| // Go through fingers and for each one, get the touches that were active this update. | |
| for (var i = 0; i < totalFingerCount; ++i) | |
| { | |
| ref var finger = ref fingers[i]; | |
| // NOTE: Many of the operations here are inlined in order to not perform the same | |
| // checks/computations repeatedly. | |
| var history = finger.m_StateHistory; | |
| var touchRecordCount = history.Count; | |
| if (touchRecordCount == 0) | |
| continue; | |
| // We're walking newest-first through the touch history but want the resulting list of | |
| // active touches to be oldest first (so that a record for an ended touch comes before | |
| // a record of a new touch started on the same finger). To achieve that, we insert | |
| // new touch entries for any finger always at the same index (i.e. we prepend rather | |
| // than append). | |
| var insertAt = activeTouchCount; | |
| // Go back in time through the touch records on the finger and collect any touch | |
| // active in the current frame. Note that this may yield *multiple* touches for the | |
| // finger as there may be touches that have ended in the frame while in the same | |
| // frame, a new touch was started. | |
| var currentTouchId = 0; | |
| var currentTouchState = default(TouchState*); | |
| var touchRecordIndex = history.UserIndexToRecordIndex(touchRecordCount - 1); // Start with last record. | |
| var touchRecordHeader = history.GetRecordUnchecked(touchRecordIndex); | |
| var touchRecordSize = history.bytesPerRecord; | |
| var extraMemoryOffset = touchRecordSize - history.extraMemoryPerRecord; | |
| for (var n = 0; n < touchRecordCount; ++n) | |
| { | |
| if (n != 0) | |
| { | |
| --touchRecordIndex; | |
| if (touchRecordIndex < 0) | |
| { | |
| // We're wrapping around so buffer must be full. Go to last record in buffer. | |
| //touchRecordIndex = history.historyDepth - history.m_HeadIndex - 1; | |
| touchRecordIndex = history.historyDepth - 1; | |
| touchRecordHeader = history.GetRecordUnchecked(touchRecordIndex); | |
| } | |
| else | |
| { | |
| touchRecordHeader = (InputStateHistory.RecordHeader*)((byte*)touchRecordHeader - touchRecordSize); | |
| } | |
| } | |
| // Skip if part of an ongoing touch we've already recorded. | |
| var touchState = (TouchState*)touchRecordHeader->statePtrWithoutControlIndex; // History is tied to a single TouchControl. | |
| var wasUpdatedThisFrame = touchState->updateStepCount == currentUpdateStepCount; | |
| if (touchState->touchId == currentTouchId && !touchState->phase.IsEndedOrCanceled()) | |
| { | |
| // If this is the Began record for the touch and that one happened in | |
| // the current frame, we force the touch phase to Began. | |
| if (wasUpdatedThisFrame && touchState->phase == TouchPhase.Began) | |
| { | |
| Debug.Assert(currentTouchState != null, "Must have current touch record at this point"); | |
| currentTouchState->phase = TouchPhase.Began; | |
| currentTouchState->position = touchState->position; | |
| currentTouchState->delta = default; | |
| haveActiveTouchesNeedingRefreshNextUpdate = true; | |
| } | |
| // Need to continue here as there may still be Ended touches that need to | |
| // be taken into account (as in, there may actually be multiple active touches | |
| // for the same finger due to how the polling API works). | |
| continue; | |
| } | |
| // If the touch is older than the current frame and it's a touch that has | |
| // ended, we don't need to look further back into the history as anything | |
| // coming before that will be equally outdated. | |
| if (touchState->phase.IsEndedOrCanceled()) | |
| { | |
| // An exception are touches that both began *and* ended in the previous frame. | |
| // For these, we surface the Began in the previous update and the Ended in the | |
| // current frame. | |
| if (!(touchState->beganInSameFrame && touchState->updateStepCount == currentUpdateStepCount - 1) && | |
| !wasUpdatedThisFrame) | |
| break; | |
| } | |
| // Make a copy of the touch so that we can modify data like deltas and phase. | |
| // NOTE: Again, not using AddRecord() for speed. | |
| // NOTE: Unlike `history`, `activeTouchState` stores control indices as each active touch | |
| // will correspond to a different TouchControl. | |
| var touchExtraState = (ExtraDataPerTouchState*)((byte*)touchRecordHeader + extraMemoryOffset); | |
| var newRecordHeader = activeTouchState.AllocateRecord(out var newRecordIndex); | |
| var newRecordState = (TouchState*)newRecordHeader->statePtrWithControlIndex; | |
| var newRecordExtraState = (ExtraDataPerTouchState*)((byte*)newRecordHeader + activeTouchState.bytesPerRecord - UnsafeUtility.SizeOf<ExtraDataPerTouchState>()); | |
| newRecordHeader->time = touchRecordHeader->time; | |
| newRecordHeader->controlIndex = ArrayHelpers.AppendWithCapacity(ref activeTouchState.m_Controls, | |
| ref activeTouchState.m_ControlCount, finger.m_StateHistory.controls[0]); | |
| UnsafeUtility.MemCpy(newRecordState, touchState, UnsafeUtility.SizeOf<TouchState>()); | |
| UnsafeUtility.MemCpy(newRecordExtraState, touchExtraState, UnsafeUtility.SizeOf<ExtraDataPerTouchState>()); | |
| // If the touch hasn't moved this frame, mark it stationary. | |
| // EXCEPT: If we are looked at a Moved touch that also began in the same frame and that | |
| // frame is the one immediately preceding us. In that case, we want to surface the Moved | |
| // as if it happened this frame. | |
| var phase = touchState->phase; | |
| if ((phase == TouchPhase.Moved || phase == TouchPhase.Began) && | |
| !wasUpdatedThisFrame && !(phase == TouchPhase.Moved && touchState->beganInSameFrame && touchState->updateStepCount == currentUpdateStepCount - 1)) | |
| { | |
| newRecordState->phase = TouchPhase.Stationary; | |
| newRecordState->delta = default; | |
| } | |
| // If the touch wasn't updated this frame, zero out its delta. | |
| else if (!wasUpdatedThisFrame && !touchState->beganInSameFrame) | |
| { | |
| newRecordState->delta = default; | |
| } | |
| else | |
| { | |
| // We want accumulated deltas only on activeTouches. | |
| newRecordState->delta = newRecordExtraState->accumulatedDelta; | |
| } | |
| var newRecord = new InputStateHistory<TouchState>.Record(activeTouchState, newRecordIndex, newRecordHeader); | |
| var newTouch = new Touch(finger, newRecord); | |
| ArrayHelpers.InsertAtWithCapacity(ref activeTouches, ref activeTouchCount, insertAt, newTouch); | |
| currentTouchId = touchState->touchId; | |
| currentTouchState = newRecordState; | |
| // For anything but stationary touches on the activeTouches list, we need a subsequent | |
| // update in the next frame. | |
| if (newTouch.phase != TouchPhase.Stationary) | |
| haveActiveTouchesNeedingRefreshNextUpdate = true; | |
| } | |
| } | |
| haveBuiltActiveTouches = true; | |
| } | |
| } | |
| internal struct ExtraDataPerTouchState | |
| { | |
| public Vector2 accumulatedDelta; | |
| public uint uniqueId; // Unique ID for touch *record* (i.e. multiple TouchStates having the same touchId will still each have a unique ID). | |
| ////TODO | |
| //public uint tapCount; | |
| } | |
| } | |
| } | |