File size: 11,646 Bytes
18a519f
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
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);
        }
    }
}