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