A game about forced loneliness, made by TACStudios
1using System;
2using System.Collections.Generic;
3using Unity.Collections.LowLevel.Unsafe;
4using UnityEngine.InputSystem.LowLevel;
5using UnityEngine.InputSystem.Controls;
6using UnityEngine.InputSystem.Utilities;
7
8////TODO: recorded times are baked *external* times; reset touch when coming out of play mode
9
10////REVIEW: record velocity on touches? or add method to very easily get the data?
11
12////REVIEW: do we need to keep old touches around on activeTouches like the old UnityEngine touch API?
13
14namespace UnityEngine.InputSystem.EnhancedTouch
15{
16 /// <summary>
17 /// A high-level representation of a touch which automatically keeps track of a touch
18 /// over time.
19 /// </summary>
20 /// <remarks>
21 /// This API obsoletes the need for manually keeping tracking of touch IDs (<see cref="TouchControl.touchId"/>)
22 /// and touch phases (<see cref="TouchControl.phase"/>) in order to tell one touch apart from another.
23 ///
24 /// Also, this class protects against losing touches. If a touch is shorter-lived than a single input update,
25 /// <see cref="Touchscreen"/> may overwrite it with a new touch coming in in the same update whereas this class
26 /// will retain all changes that happened on the touchscreen in any particular update.
27 ///
28 /// The API makes a distinction between "fingers" and "touches". A touch refers to one contact state change event, that is, a
29 /// finger beginning to touch the screen (<see cref="TouchPhase.Began"/>), moving on the screen (<see cref="TouchPhase.Moved"/>),
30 /// or being lifted off the screen (<see cref="TouchPhase.Ended"/> or <see cref="TouchPhase.Canceled"/>).
31 /// A finger, on the other hand, always refers to the Nth contact on the screen.
32 ///
33 /// A Touch instance is a struct which only contains a reference to the actual data which is stored in unmanaged
34 /// memory.
35 /// </remarks>
36 [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1724:TypeNamesShouldNotMatchNamespaces")]
37 public struct Touch : IEquatable<Touch>
38 {
39 // The way this works is that at the core, it simply attaches one InputStateHistory per "<Touchscreen>/touch*"
40 // control and then presents a public API that crawls over the recorded touch history in various ways.
41
42 /// <summary>
43 /// Whether this touch record holds valid data.
44 /// </summary>
45 /// <value>If true, the data contained in the touch is valid.</value>
46 /// <remarks>
47 /// Touch data is stored in unmanaged memory as a circular input buffer. This means that when
48 /// the buffer runs out of capacity, older touch entries will get reused. When this happens,
49 /// existing <c>Touch</c> instances referring to the record become invalid.
50 ///
51 /// This property can be used to determine whether the record held on to by the <c>Touch</c>
52 /// instance is still valid.
53 ///
54 /// This property will be <c>false</c> for default-initialized <c>Touch</c> instances.
55 ///
56 /// Note that accessing most of the other properties on this struct when the touch is
57 /// invalid will trigger <c>InvalidOperationException</c>.
58 /// </remarks>
59 public bool valid => m_TouchRecord.valid;
60
61 /// <summary>
62 /// The finger used for the touch contact. Null only for default-initialized
63 /// instances of the struct.
64 /// </summary>
65 /// <value>Finger used for the touch contact.</value>
66 /// <seealso cref="activeFingers"/>
67 public Finger finger => m_Finger;
68
69 /// <summary>
70 /// Current phase of the touch.
71 /// </summary>
72 /// <value>Current phase of the touch.</value>
73 /// <remarks>
74 /// Every touch goes through a predefined cycle that starts with <see cref="TouchPhase.Began"/>,
75 /// then potentially <see cref="TouchPhase.Moved"/> and/or <see cref="TouchPhase.Stationary"/>,
76 /// and finally concludes with either <see cref="TouchPhase.Ended"/> or <see cref="TouchPhase.Canceled"/>.
77 ///
78 /// This property indicates where in the cycle the touch is.
79 /// </remarks>
80 /// <seealso cref="isInProgress"/>
81 /// <seealso cref="TouchControl.phase"/>
82 public TouchPhase phase => state.phase;
83
84 /// <summary>
85 /// Whether the touch has begun this frame, i.e. whether <see cref="phase"/> is <see cref="TouchPhase.Began"/>.
86 /// </summary>
87 /// <seealso cref="phase"/>
88 /// <seealso cref="ended"/>
89 /// <seealso cref="inProgress"/>
90 public bool began => phase == TouchPhase.Began;
91
92 /// <summary>
93 /// Whether the touch is currently in progress, i.e. whether <see cref="phase"/> is either
94 /// <see cref="TouchPhase.Moved"/>, <see cref="TouchPhase.Stationary"/>, or <see cref="TouchPhase.Began"/>.
95 /// </summary>
96 /// <seealso cref="phase"/>
97 /// <seealso cref="began"/>
98 /// <seealso cref="ended"/>
99 public bool inProgress => phase == TouchPhase.Moved || phase == TouchPhase.Stationary || phase == TouchPhase.Began;
100
101 /// <summary>
102 /// Whether the touch has ended this frame, i.e. whether <see cref="phase"/> is either
103 /// <see cref="TouchPhase.Ended"/> or <see cref="TouchPhase.Canceled"/>.
104 /// </summary>
105 /// <seealso cref="phase"/>
106 /// <seealso cref="began"/>
107 /// <seealso cref="isInProgress"/>
108 public bool ended => phase == TouchPhase.Ended || phase == TouchPhase.Canceled;
109
110 /// <summary>
111 /// Unique ID of the touch as (usually) assigned by the platform.
112 /// </summary>
113 /// <value>Unique, non-zero ID of the touch.</value>
114 /// <remarks>
115 /// Each touch contact that is made with the screen receives its own unique ID which is
116 /// normally assigned by the underlying platform.
117 ///
118 /// Note a platform may reuse touch IDs after their respective touches have finished.
119 /// This means that the guarantee of uniqueness is only made with respect to <see cref="activeTouches"/>.
120 ///
121 /// In particular, all touches in <see cref="history"/> will have the same ID whereas
122 /// touches in the a finger's <see cref="Finger.touchHistory"/> may end up having the same
123 /// touch ID even though constituting different physical touch contacts.
124 /// </remarks>
125 /// <seealso cref="TouchControl.touchId"/>
126 public int touchId => state.touchId;
127
128 /// <summary>
129 /// Normalized pressure of the touch against the touch surface.
130 /// </summary>
131 /// <value>Pressure level of the touch.</value>
132 /// <remarks>
133 /// Not all touchscreens are pressure-sensitive. If unsupported, this property will
134 /// always return 0.
135 ///
136 /// In general, touch pressure is supported on mobile platforms only.
137 ///
138 /// Note that it is possible for the value to go above 1 even though it is considered normalized. The reason is
139 /// that calibration on the system can put the maximum pressure point below the physically supported maximum value.
140 /// </remarks>
141 /// <seealso cref="TouchControl.pressure"/>
142 public float pressure => state.pressure;
143
144 /// <summary>
145 /// Screen-space radius of the touch.
146 /// </summary>
147 /// <value>Horizontal and vertical extents of the touch contact.</value>
148 /// <remarks>
149 /// If supported by the underlying device, this reports the size of the touch contact based on its
150 /// <see cref="screenPosition"/> center point. If not supported, this will be <c>default(Vector2)</c>.
151 /// </remarks>
152 /// <seealso cref="TouchControl.radius"/>
153 public Vector2 radius => state.radius;
154
155 /// <summary>
156 /// Time in seconds on the same timeline as <c>Time.realTimeSinceStartup</c> when the touch began.
157 /// </summary>
158 /// <value>Start time of the touch.</value>
159 /// <remarks>
160 /// This is the value of <see cref="InputEvent.time"/> when the touch started with
161 /// <see cref="phase"/> <see cref="TouchPhase.Began"/>.
162 /// </remarks>
163 /// <seealso cref="TouchControl.startTime"/>
164 public double startTime => state.startTime;
165
166 /// <summary>
167 /// Time in seconds on the same timeline as <c>Time.realTimeSinceStartup</c> when the touch record was
168 /// reported.
169 /// </summary>
170 /// <value>Time the touch record was reported.</value>
171 /// <remarks>
172 /// This is the value <see cref="InputEvent.time"/> of the event that signaled the current state
173 /// change for the touch.
174 /// </remarks>
175 public double time => m_TouchRecord.time;
176
177 /// <summary>
178 /// The touchscreen on which the touch occurred.
179 /// </summary>
180 /// <value>Touchscreen associated with the touch.</value>
181 public Touchscreen screen => finger.screen;
182
183 /// <summary>
184 /// Screen-space position of the touch.
185 /// </summary>
186 /// <value>Screen-space position of the touch.</value>
187 /// <seealso cref="TouchControl.position"/>
188 public Vector2 screenPosition => state.position;
189
190 /// <summary>
191 /// Screen-space position where the touch started.
192 /// </summary>
193 /// <value>Start position of the touch.</value>
194 /// <seealso cref="TouchControl.startPosition"/>
195 public Vector2 startScreenPosition => state.startPosition;
196
197 /// <summary>
198 /// Screen-space motion delta of the touch.
199 /// </summary>
200 /// <value>Screen-space motion delta of the touch.</value>
201 /// <remarks>
202 /// Note that deltas have behaviors attached to them different from most other
203 /// controls. See <see cref="Pointer.delta"/> for details.
204 /// </remarks>
205 /// <seealso cref="TouchControl.delta"/>
206 public Vector2 delta => state.delta;
207
208 /// <summary>
209 /// Number of times that the touch has been tapped in succession.
210 /// </summary>
211 /// <value>Indicates how many taps have been performed one after the other.</value>
212 /// <remarks>
213 /// Successive taps have to come within <see cref="InputSettings.multiTapDelayTime"/> for them
214 /// to increase the tap count. I.e. if a new tap finishes within that time after <see cref="startTime"/>
215 /// of the previous touch, the tap count is increased by one. If more than <see cref="InputSettings.multiTapDelayTime"/>
216 /// passes after a tap with no successive tap, the tap count is reset to zero.
217 /// </remarks>
218 /// <seealso cref="TouchControl.tapCount"/>
219 public int tapCount => state.tapCount;
220
221 /// <summary>
222 /// Whether the touch has performed a tap.
223 /// </summary>
224 /// <value>Indicates whether the touch has tapped the screen.</value>
225 /// <remarks>
226 /// A tap is defined as a touch that begins and ends within <see cref="InputSettings.defaultTapTime"/> and
227 /// stays within <see cref="InputSettings.tapRadius"/> of its <see cref="startScreenPosition"/>. If this
228 /// is the case for a touch, this button is set to 1 at the time the touch goes to <see cref="phase"/>
229 /// <see cref="TouchPhase.Ended"/>.
230 ///
231 /// Resets to 0 only when another touch is started on the control or when the control is reset.
232 /// </remarks>
233 /// <seealso cref="tapCount"/>
234 /// <seealso cref="InputSettings.defaultTapTime"/>
235 /// <seealso cref="TouchControl.tap"/>
236 public bool isTap => state.isTap;
237
238 /// <summary>
239 /// The index of the display containing the touch.
240 /// </summary>
241 /// <value>A zero based number representing the display index containing the touch.</value>
242 /// <seealso cref="TouchControl.displayIndex"/>
243 /// <seealso cref="Display"/>
244 public int displayIndex => state.displayIndex;
245
246 /// <summary>
247 /// Whether the touch is currently in progress, i.e. has a <see cref="phase"/> of
248 /// <see cref="TouchPhase.Began"/>, <see cref="TouchPhase.Moved"/>, or <see cref="TouchPhase.Stationary"/>.
249 /// </summary>
250 /// <value>Whether the touch is currently ongoing.</value>
251 public bool isInProgress
252 {
253 get
254 {
255 switch (phase)
256 {
257 case TouchPhase.Began:
258 case TouchPhase.Moved:
259 case TouchPhase.Stationary:
260 return true;
261 }
262 return false;
263 }
264 }
265
266 internal uint updateStepCount => state.updateStepCount;
267 internal uint uniqueId => extraData.uniqueId;
268
269 private unsafe ref TouchState state => ref *(TouchState*)m_TouchRecord.GetUnsafeMemoryPtr();
270 private unsafe ref ExtraDataPerTouchState extraData =>
271 ref *(ExtraDataPerTouchState*)m_TouchRecord.GetUnsafeExtraMemoryPtr();
272
273 /// <summary>
274 /// History for this specific touch.
275 /// </summary>
276 /// <remarks>
277 /// Unlike <see cref="Finger.touchHistory"/>, this gives the history of this touch only.
278 /// </remarks>
279 public TouchHistory history
280 {
281 get
282 {
283 if (!valid)
284 throw new InvalidOperationException("Touch is invalid");
285 return finger.GetTouchHistory(this);
286 }
287 }
288
289 /// <summary>
290 /// All touches that are either on-going as of the current frame or have ended in the current frame.
291 /// </summary>
292 /// <remarks>
293 /// A touch that begins in a frame will always have its phase set to <see cref="TouchPhase.Began"/> even
294 /// if there was also movement (or even an end/cancellation) for the touch in the same frame.
295 ///
296 /// A touch that begins and ends in the same frame will have its <see cref="TouchPhase.Began"/> surface
297 /// in that frame and then another entry with <see cref="TouchPhase.Ended"/> surface in the
298 /// <em>next</em> frame. This logic implies that there can be more active touches than concurrent touches
299 /// supported by the hardware/platform.
300 ///
301 /// A touch that begins and moves in the same frame will have its <see cref="TouchPhase.Began"/> surface
302 /// in that frame and then another entry with <see cref="TouchPhase.Moved"/> and the screen motion
303 /// surface in the <em>next</em> frame <em>except</em> if the touch also ended in the frame (in which
304 /// case <see cref="phase"/> will be <see cref="TouchPhase.Ended"/> instead of <see cref="TouchPhase.Moved"/>).
305 ///
306 /// Note that the touches reported by this API do <em>not</em> necessarily have to match the contents of
307 /// <see href="https://docs.unity3d.com/ScriptReference/Input-touches.html">UnityEngine.Input.touches</see>.
308 /// The reason for this is that the <c>UnityEngine.Input</c> API and the Input System API flush their input
309 /// queues at different points in time and may thus have a different view on available input. In particular,
310 /// the Input System event queue is flushed <em>later</em> in the frame than inputs for <c>UnityEngine.Input</c>
311 /// and may thus have newer inputs available. On Android, for example, touch input is gathered from a separate
312 /// UI thread and fed into the input system via a "background" event queue that can gather input asynchronously.
313 /// Due to this setup, touch events that will reach <c>UnityEngine.Input</c> only in the next frame may have
314 /// already reached the Input System.
315 ///
316 /// <example>
317 /// <code>
318 /// void Awake()
319 /// {
320 /// // Enable EnhancedTouch.
321 /// EnhancedTouchSupport.Enable();
322 /// }
323 ///
324 /// void Update()
325 /// {
326 /// foreach (var touch in Touch.activeTouches)
327 /// if (touch.began)
328 /// Debug.Log($"Touch {touch} started this frame");
329 /// else if (touch.ended)
330 /// Debug.Log($"Touch {touch} ended this frame");
331 /// }
332 /// </code>
333 /// </example>
334 /// </remarks>
335 /// <exception cref="InvalidOperationException"><c>EnhancedTouch</c> has not been enabled via <see cref="EnhancedTouchSupport.Enable"/>.</exception>
336 /// <seealso cref="activeFingers"/>
337 public static ReadOnlyArray<Touch> activeTouches
338 {
339 get
340 {
341 EnhancedTouchSupport.CheckEnabled();
342 // We lazily construct the array of active touches.
343 s_GlobalState.playerState.UpdateActiveTouches();
344 return new ReadOnlyArray<Touch>(s_GlobalState.playerState.activeTouches, 0, s_GlobalState.playerState.activeTouchCount);
345 }
346 }
347
348 /// <summary>
349 /// An array of all possible concurrent touch contacts, i.e. all concurrent touch contacts regardless of whether
350 /// they are currently active or not.
351 /// </summary>
352 /// <remarks>
353 /// For querying only active fingers, use <see cref="activeFingers"/>.
354 ///
355 /// The length of this array will always correspond to the maximum number of concurrent touches supported by the system.
356 /// Note that the actual number of physically supported concurrent touches as determined by the current hardware and
357 /// operating system may be lower than this number.
358 /// </remarks>
359 /// <exception cref="InvalidOperationException"><c>EnhancedTouch</c> has not been enabled via <see cref="EnhancedTouchSupport.Enable"/>.</exception>
360 /// <seealso cref="activeTouches"/>
361 /// <seealso cref="activeFingers"/>
362 public static ReadOnlyArray<Finger> fingers
363 {
364 get
365 {
366 EnhancedTouchSupport.CheckEnabled();
367 return new ReadOnlyArray<Finger>(s_GlobalState.playerState.fingers, 0, s_GlobalState.playerState.totalFingerCount);
368 }
369 }
370
371 /// <summary>
372 /// Set of currently active fingers, i.e. touch contacts that currently have an active touch (as defined by <see cref="activeTouches"/>).
373 /// </summary>
374 /// <exception cref="InvalidOperationException"><c>EnhancedTouch</c> has not been enabled via <see cref="EnhancedTouchSupport.Enable"/>.</exception>
375 /// <seealso cref="activeTouches"/>
376 /// <seealso cref="fingers"/>
377 public static ReadOnlyArray<Finger> activeFingers
378 {
379 get
380 {
381 EnhancedTouchSupport.CheckEnabled();
382 // We lazily construct the array of active fingers.
383 s_GlobalState.playerState.UpdateActiveFingers();
384 return new ReadOnlyArray<Finger>(s_GlobalState.playerState.activeFingers, 0, s_GlobalState.playerState.activeFingerCount);
385 }
386 }
387
388 /// <summary>
389 /// Return the set of <see cref="Touchscreen"/>s on which touch input is monitored.
390 /// </summary>
391 /// <exception cref="InvalidOperationException"><c>EnhancedTouch</c> has not been enabled via <see cref="EnhancedTouchSupport.Enable"/>.</exception>
392 public static IEnumerable<Touchscreen> screens
393 {
394 get
395 {
396 EnhancedTouchSupport.CheckEnabled();
397 return s_GlobalState.touchscreens;
398 }
399 }
400
401 /// <summary>
402 /// Event that is invoked when a finger touches a <see cref="Touchscreen"/>.
403 /// </summary>
404 /// <exception cref="InvalidOperationException"><c>EnhancedTouch</c> has not been enabled via <see cref="EnhancedTouchSupport.Enable"/>.</exception>
405 /// <seealso cref="onFingerUp"/>
406 /// <seealso cref="onFingerMove"/>
407 public static event Action<Finger> onFingerDown
408 {
409 add
410 {
411 EnhancedTouchSupport.CheckEnabled();
412 if (value == null)
413 throw new ArgumentNullException(nameof(value));
414 s_GlobalState.onFingerDown.AddCallback(value);
415 }
416 remove
417 {
418 EnhancedTouchSupport.CheckEnabled();
419 if (value == null)
420 throw new ArgumentNullException(nameof(value));
421 s_GlobalState.onFingerDown.RemoveCallback(value);
422 }
423 }
424
425 /// <summary>
426 /// Event that is invoked when a finger stops touching a <see cref="Touchscreen"/>.
427 /// </summary>
428 /// <exception cref="InvalidOperationException"><c>EnhancedTouch</c> has not been enabled via <see cref="EnhancedTouchSupport.Enable"/>.</exception>
429 /// <seealso cref="onFingerDown"/>
430 /// <seealso cref="onFingerMove"/>
431 public static event Action<Finger> onFingerUp
432 {
433 add
434 {
435 EnhancedTouchSupport.CheckEnabled();
436 if (value == null)
437 throw new ArgumentNullException(nameof(value));
438 s_GlobalState.onFingerUp.AddCallback(value);
439 }
440 remove
441 {
442 EnhancedTouchSupport.CheckEnabled();
443 if (value == null)
444 throw new ArgumentNullException(nameof(value));
445 s_GlobalState.onFingerUp.RemoveCallback(value);
446 }
447 }
448
449 /// <summary>
450 /// Event that is invoked when a finger that is in contact with a <see cref="Touchscreen"/> moves
451 /// on the screen.
452 /// </summary>
453 /// <exception cref="InvalidOperationException"><c>EnhancedTouch</c> has not been enabled via <see cref="EnhancedTouchSupport.Enable"/>.</exception>
454 /// <seealso cref="onFingerUp"/>
455 /// <seealso cref="onFingerDown"/>
456 public static event Action<Finger> onFingerMove
457 {
458 add
459 {
460 EnhancedTouchSupport.CheckEnabled();
461 if (value == null)
462 throw new ArgumentNullException(nameof(value));
463 s_GlobalState.onFingerMove.AddCallback(value);
464 }
465 remove
466 {
467 EnhancedTouchSupport.CheckEnabled();
468 if (value == null)
469 throw new ArgumentNullException(nameof(value));
470 s_GlobalState.onFingerMove.RemoveCallback(value);
471 }
472 }
473
474 /*
475 public static Action<Finger> onFingerTap
476 {
477 get { throw new NotImplementedException(); }
478 set { throw new NotImplementedException(); }
479 }
480 */
481
482 /// <summary>
483 /// The amount of history kept for each single touch.
484 /// </summary>
485 /// <remarks>
486 /// By default, this is zero meaning that no history information is kept for
487 /// touches. Setting this to <c>Int32.maxValue</c> will cause all history from
488 /// the beginning to the end of a touch being kept.
489 /// </remarks>
490 public static int maxHistoryLengthPerFinger
491 {
492 get => s_GlobalState.historyLengthPerFinger;
493
494 ////TODO
495 /*set { throw new NotImplementedException(); }*/
496 }
497
498 internal Touch(Finger finger, InputStateHistory<TouchState>.Record touchRecord)
499 {
500 m_Finger = finger;
501 m_TouchRecord = touchRecord;
502 }
503
504 public override string ToString()
505 {
506 if (!valid)
507 return "<None>";
508
509 return $"{{id={touchId} finger={finger.index} phase={phase} position={screenPosition} delta={delta} time={time}}}";
510 }
511
512 public bool Equals(Touch other)
513 {
514 return Equals(m_Finger, other.m_Finger) && m_TouchRecord.Equals(other.m_TouchRecord);
515 }
516
517 public override bool Equals(object obj)
518 {
519 return obj is Touch other && Equals(other);
520 }
521
522 public override int GetHashCode()
523 {
524 unchecked
525 {
526 return ((m_Finger != null ? m_Finger.GetHashCode() : 0) * 397) ^ m_TouchRecord.GetHashCode();
527 }
528 }
529
530 internal static void AddTouchscreen(Touchscreen screen)
531 {
532 Debug.Assert(!s_GlobalState.touchscreens.ContainsReference(screen), "Already added touchscreen");
533 s_GlobalState.touchscreens.AppendWithCapacity(screen, capacityIncrement: 5);
534
535 // Add finger tracking to states.
536 s_GlobalState.playerState.AddFingers(screen);
537#if UNITY_EDITOR
538 s_GlobalState.editorState.AddFingers(screen);
539#endif
540 }
541
542 internal static void RemoveTouchscreen(Touchscreen screen)
543 {
544 Debug.Assert(s_GlobalState.touchscreens.ContainsReference(screen), "Did not add touchscreen");
545
546 // Remove from list.
547 var index = s_GlobalState.touchscreens.IndexOfReference(screen);
548 s_GlobalState.touchscreens.RemoveAtWithCapacity(index);
549
550 // Remove fingers from states.
551 s_GlobalState.playerState.RemoveFingers(screen);
552#if UNITY_EDITOR
553 s_GlobalState.editorState.RemoveFingers(screen);
554#endif
555 }
556
557 ////TODO: only have this hooked when we actually need it
558 internal static void BeginUpdate()
559 {
560#if UNITY_EDITOR
561 if ((InputState.currentUpdateType == InputUpdateType.Editor && s_GlobalState.playerState.updateMask != InputUpdateType.Editor) ||
562 (InputState.currentUpdateType != InputUpdateType.Editor && s_GlobalState.playerState.updateMask == InputUpdateType.Editor))
563 {
564 // Either swap in editor state and retain currently active player state in s_EditorState
565 // or swap player state back in.
566 MemoryHelpers.Swap(ref s_GlobalState.playerState, ref s_GlobalState.editorState);
567 }
568#endif
569
570 // If we have any touches in activeTouches that are ended or canceled,
571 // we need to clear them in the next frame.
572 if (s_GlobalState.playerState.haveActiveTouchesNeedingRefreshNextUpdate)
573 s_GlobalState.playerState.haveBuiltActiveTouches = false;
574 }
575
576 private readonly Finger m_Finger;
577 internal InputStateHistory<TouchState>.Record m_TouchRecord;
578
579 /// <summary>
580 /// Holds global (static) touch state.
581 /// </summary>
582 internal struct GlobalState
583 {
584 internal InlinedArray<Touchscreen> touchscreens;
585 internal int historyLengthPerFinger;
586 internal CallbackArray<Action<Finger>> onFingerDown;
587 internal CallbackArray<Action<Finger>> onFingerMove;
588 internal CallbackArray<Action<Finger>> onFingerUp;
589
590 internal FingerAndTouchState playerState;
591#if UNITY_EDITOR
592 internal FingerAndTouchState editorState;
593#endif
594 }
595
596 private static GlobalState CreateGlobalState()
597 { // Convenient method since parameterized construction is default
598 return new GlobalState { historyLengthPerFinger = 64 };
599 }
600
601 internal static GlobalState s_GlobalState = CreateGlobalState();
602
603 internal static ISavedState SaveAndResetState()
604 {
605 // Save current state
606 var savedState = new SavedStructState<GlobalState>(
607 ref s_GlobalState,
608 (ref GlobalState state) => s_GlobalState = state,
609 () => { /* currently nothing to dispose */ });
610
611 // Reset global state
612 s_GlobalState = CreateGlobalState();
613
614 return savedState;
615 }
616
617 // In scenarios where we have to support multiple different types of input updates (e.g. in editor or in
618 // player when both dynamic and fixed input updates are enabled), we need more than one copy of touch state.
619 // We encapsulate the state in this struct so that we can easily swap it.
620 //
621 // NOTE: Finger instances are per state. This means that you will actually see different Finger instances for
622 // the same finger in two different update types.
623 [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA1001:TypesThatOwnDisposableFieldsShouldBeDisposable",
624 Justification = "Managed internally")]
625 internal struct FingerAndTouchState
626 {
627 public InputUpdateType updateMask;
628 public Finger[] fingers;
629 public Finger[] activeFingers;
630 public Touch[] activeTouches;
631 public int activeFingerCount;
632 public int activeTouchCount;
633 public int totalFingerCount;
634 public uint lastId;
635 public bool haveBuiltActiveTouches;
636 public bool haveActiveTouchesNeedingRefreshNextUpdate;
637
638 // `activeTouches` adds yet another view of input state that is different from "normal" recorded
639 // state history. In this view, touches become stationary in the next update and deltas reset
640 // between updates. We solve this by storing state separately for active touches. We *only* do
641 // so when `activeTouches` is actually queried meaning that `activeTouches` has no overhead if
642 // not used.
643 public InputStateHistory<TouchState> activeTouchState;
644
645 public void AddFingers(Touchscreen screen)
646 {
647 var touchCount = screen.touches.Count;
648 ArrayHelpers.EnsureCapacity(ref fingers, totalFingerCount, touchCount);
649 for (var i = 0; i < touchCount; ++i)
650 {
651 var finger = new Finger(screen, i, updateMask);
652 ArrayHelpers.AppendWithCapacity(ref fingers, ref totalFingerCount, finger);
653 }
654 }
655
656 public void RemoveFingers(Touchscreen screen)
657 {
658 var touchCount = screen.touches.Count;
659 for (var i = 0; i < fingers.Length; ++i)
660 {
661 if (fingers[i].screen != screen)
662 continue;
663
664 // Release unmanaged memory.
665 for (var n = 0; n < touchCount; ++n)
666 fingers[i + n].m_StateHistory.Dispose();
667
668 ////REVIEW: leave Fingers in place and reuse the instances?
669 ArrayHelpers.EraseSliceWithCapacity(ref fingers, ref totalFingerCount, i, touchCount);
670 break;
671 }
672
673 // Force rebuilding of active touches.
674 haveBuiltActiveTouches = false;
675 }
676
677 public void Destroy()
678 {
679 for (var i = 0; i < totalFingerCount; ++i)
680 fingers[i].m_StateHistory.Dispose();
681 activeTouchState?.Dispose();
682 activeTouchState = null;
683 }
684
685 public void UpdateActiveFingers()
686 {
687 ////TODO: do this only once per update per activeFingers getter
688
689 activeFingerCount = 0;
690 for (var i = 0; i < totalFingerCount; ++i)
691 {
692 var finger = fingers[i];
693 var lastTouch = finger.currentTouch;
694 if (lastTouch.valid)
695 ArrayHelpers.AppendWithCapacity(ref activeFingers, ref activeFingerCount, finger);
696 }
697 }
698
699 public unsafe void UpdateActiveTouches()
700 {
701 if (haveBuiltActiveTouches)
702 return;
703
704 // Clear activeTouches state.
705 if (activeTouchState == null)
706 {
707 activeTouchState = new InputStateHistory<TouchState>
708 {
709 extraMemoryPerRecord = UnsafeUtility.SizeOf<ExtraDataPerTouchState>()
710 };
711 }
712 else
713 {
714 activeTouchState.Clear();
715 activeTouchState.m_ControlCount = 0;
716 activeTouchState.m_Controls.Clear();
717 }
718 activeTouchCount = 0;
719 haveActiveTouchesNeedingRefreshNextUpdate = false;
720 var currentUpdateStepCount = InputUpdate.s_UpdateStepCount;
721
722 ////OPTIMIZE: Handle touchscreens that have no activity more efficiently
723 ////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
724 //// (instead of rebuild activeTouches from scratch each time, may be more useful to update it)
725
726 // Go through fingers and for each one, get the touches that were active this update.
727 for (var i = 0; i < totalFingerCount; ++i)
728 {
729 ref var finger = ref fingers[i];
730
731 // NOTE: Many of the operations here are inlined in order to not perform the same
732 // checks/computations repeatedly.
733
734 var history = finger.m_StateHistory;
735 var touchRecordCount = history.Count;
736 if (touchRecordCount == 0)
737 continue;
738
739 // We're walking newest-first through the touch history but want the resulting list of
740 // active touches to be oldest first (so that a record for an ended touch comes before
741 // a record of a new touch started on the same finger). To achieve that, we insert
742 // new touch entries for any finger always at the same index (i.e. we prepend rather
743 // than append).
744 var insertAt = activeTouchCount;
745
746 // Go back in time through the touch records on the finger and collect any touch
747 // active in the current frame. Note that this may yield *multiple* touches for the
748 // finger as there may be touches that have ended in the frame while in the same
749 // frame, a new touch was started.
750 var currentTouchId = 0;
751 var currentTouchState = default(TouchState*);
752 var touchRecordIndex = history.UserIndexToRecordIndex(touchRecordCount - 1); // Start with last record.
753 var touchRecordHeader = history.GetRecordUnchecked(touchRecordIndex);
754 var touchRecordSize = history.bytesPerRecord;
755 var extraMemoryOffset = touchRecordSize - history.extraMemoryPerRecord;
756 for (var n = 0; n < touchRecordCount; ++n)
757 {
758 if (n != 0)
759 {
760 --touchRecordIndex;
761 if (touchRecordIndex < 0)
762 {
763 // We're wrapping around so buffer must be full. Go to last record in buffer.
764 //touchRecordIndex = history.historyDepth - history.m_HeadIndex - 1;
765 touchRecordIndex = history.historyDepth - 1;
766 touchRecordHeader = history.GetRecordUnchecked(touchRecordIndex);
767 }
768 else
769 {
770 touchRecordHeader = (InputStateHistory.RecordHeader*)((byte*)touchRecordHeader - touchRecordSize);
771 }
772 }
773
774 // Skip if part of an ongoing touch we've already recorded.
775 var touchState = (TouchState*)touchRecordHeader->statePtrWithoutControlIndex; // History is tied to a single TouchControl.
776 var wasUpdatedThisFrame = touchState->updateStepCount == currentUpdateStepCount;
777 if (touchState->touchId == currentTouchId && !touchState->phase.IsEndedOrCanceled())
778 {
779 // If this is the Began record for the touch and that one happened in
780 // the current frame, we force the touch phase to Began.
781 if (wasUpdatedThisFrame && touchState->phase == TouchPhase.Began)
782 {
783 Debug.Assert(currentTouchState != null, "Must have current touch record at this point");
784
785 currentTouchState->phase = TouchPhase.Began;
786 currentTouchState->position = touchState->position;
787 currentTouchState->delta = default;
788
789 haveActiveTouchesNeedingRefreshNextUpdate = true;
790 }
791
792 // Need to continue here as there may still be Ended touches that need to
793 // be taken into account (as in, there may actually be multiple active touches
794 // for the same finger due to how the polling API works).
795 continue;
796 }
797
798 // If the touch is older than the current frame and it's a touch that has
799 // ended, we don't need to look further back into the history as anything
800 // coming before that will be equally outdated.
801 if (touchState->phase.IsEndedOrCanceled())
802 {
803 // An exception are touches that both began *and* ended in the previous frame.
804 // For these, we surface the Began in the previous update and the Ended in the
805 // current frame.
806 if (!(touchState->beganInSameFrame && touchState->updateStepCount == currentUpdateStepCount - 1) &&
807 !wasUpdatedThisFrame)
808 break;
809 }
810
811 // Make a copy of the touch so that we can modify data like deltas and phase.
812 // NOTE: Again, not using AddRecord() for speed.
813 // NOTE: Unlike `history`, `activeTouchState` stores control indices as each active touch
814 // will correspond to a different TouchControl.
815 var touchExtraState = (ExtraDataPerTouchState*)((byte*)touchRecordHeader + extraMemoryOffset);
816 var newRecordHeader = activeTouchState.AllocateRecord(out var newRecordIndex);
817 var newRecordState = (TouchState*)newRecordHeader->statePtrWithControlIndex;
818 var newRecordExtraState = (ExtraDataPerTouchState*)((byte*)newRecordHeader + activeTouchState.bytesPerRecord - UnsafeUtility.SizeOf<ExtraDataPerTouchState>());
819 newRecordHeader->time = touchRecordHeader->time;
820 newRecordHeader->controlIndex = ArrayHelpers.AppendWithCapacity(ref activeTouchState.m_Controls,
821 ref activeTouchState.m_ControlCount, finger.m_StateHistory.controls[0]);
822
823 UnsafeUtility.MemCpy(newRecordState, touchState, UnsafeUtility.SizeOf<TouchState>());
824 UnsafeUtility.MemCpy(newRecordExtraState, touchExtraState, UnsafeUtility.SizeOf<ExtraDataPerTouchState>());
825
826 // If the touch hasn't moved this frame, mark it stationary.
827 // EXCEPT: If we are looked at a Moved touch that also began in the same frame and that
828 // frame is the one immediately preceding us. In that case, we want to surface the Moved
829 // as if it happened this frame.
830 var phase = touchState->phase;
831 if ((phase == TouchPhase.Moved || phase == TouchPhase.Began) &&
832 !wasUpdatedThisFrame && !(phase == TouchPhase.Moved && touchState->beganInSameFrame && touchState->updateStepCount == currentUpdateStepCount - 1))
833 {
834 newRecordState->phase = TouchPhase.Stationary;
835 newRecordState->delta = default;
836 }
837 // If the touch wasn't updated this frame, zero out its delta.
838 else if (!wasUpdatedThisFrame && !touchState->beganInSameFrame)
839 {
840 newRecordState->delta = default;
841 }
842 else
843 {
844 // We want accumulated deltas only on activeTouches.
845 newRecordState->delta = newRecordExtraState->accumulatedDelta;
846 }
847
848 var newRecord = new InputStateHistory<TouchState>.Record(activeTouchState, newRecordIndex, newRecordHeader);
849 var newTouch = new Touch(finger, newRecord);
850
851 ArrayHelpers.InsertAtWithCapacity(ref activeTouches, ref activeTouchCount, insertAt, newTouch);
852
853 currentTouchId = touchState->touchId;
854 currentTouchState = newRecordState;
855
856 // For anything but stationary touches on the activeTouches list, we need a subsequent
857 // update in the next frame.
858 if (newTouch.phase != TouchPhase.Stationary)
859 haveActiveTouchesNeedingRefreshNextUpdate = true;
860 }
861 }
862
863 haveBuiltActiveTouches = true;
864 }
865 }
866
867 internal struct ExtraDataPerTouchState
868 {
869 public Vector2 accumulatedDelta;
870
871 public uint uniqueId; // Unique ID for touch *record* (i.e. multiple TouchStates having the same touchId will still each have a unique ID).
872
873 ////TODO
874 //public uint tapCount;
875 }
876 }
877}