A game about forced loneliness, made by TACStudios
1using System;
2using System.Runtime.InteropServices;
3using Unity.Collections;
4using Unity.Collections.LowLevel.Unsafe;
5using UnityEngine.InputSystem.Controls;
6using UnityEngine.InputSystem.Layouts;
7using UnityEngine.InputSystem.LowLevel;
8using UnityEngine.InputSystem.Utilities;
9using Unity.Profiling;
10
11////TODO: property that tells whether a Touchscreen is multi-touch capable
12
13////TODO: property that tells whether a Touchscreen supports pressure
14
15////TODO: add support for screen orientation
16
17////TODO: touch is hardwired to certain memory layouts ATM; either allow flexibility or make sure the layouts cannot be changed
18
19////TODO: startTimes are baked *external* times; reset touch when coming out of play mode
20
21////TODO: detect and diagnose touchId=0 events
22
23////REVIEW: where should we put handset vibration support? should that sit on the touchscreen class? be its own separate device?
24
25////REVIEW: Given that Touchscreen is no use for polling, should we remove Touchscreen.current?
26
27////REVIEW: Should Touchscreen reset individual TouchControls to default(TouchState) after a touch has ended? This would allow
28//// binding to a TouchControl as a whole and the action would correctly cancel if the touch ends
29
30namespace UnityEngine.InputSystem.LowLevel
31{
32 [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1028:EnumStorageShouldBeInt32", Justification = "byte to correspond to TouchState layout.")]
33 [Flags]
34 internal enum TouchFlags : byte
35 {
36 IndirectTouch = 1 << 0,
37
38 // NOTE: Leaving the first 3 bits for native.
39
40 PrimaryTouch = 1 << 3,
41 TapPress = 1 << 4,
42 TapRelease = 1 << 5,
43
44 // Indicates that the touch that established this primary touch has ended but that when
45 // it did, there were still other touches going on. We end the primary touch when the
46 // last touch leaves the screen.
47 OrphanedPrimaryTouch = 1 << 6,
48
49 // This is only used by EnhancedTouch to mark touch records that have begun in the same
50 // frame as the current touch record.
51 BeganInSameFrame = 1 << 7,
52 }
53
54 ////REVIEW: add timestamp directly to touch?
55 /// <summary>
56 /// State layout for a single touch.
57 /// </summary>
58 /// <remarks>
59 /// This is the low-level memory representation of a single touch, i.e the
60 /// way touches are internally transmitted and stored in the system. To update
61 /// touches on a <see cref="Touchscreen"/>, <see cref="StateEvent"/>s containing
62 /// TouchStates are sent to the screen.
63 /// </remarks>
64 /// <seealso cref="TouchControl"/>
65 /// <seealso cref="Touchscreen"/>
66 // IMPORTANT: Must match TouchInputState in native code.
67 [StructLayout(LayoutKind.Explicit, Size = kSizeInBytes)]
68 public struct TouchState : IInputStateTypeInfo
69 {
70 internal const int kSizeInBytes = 56;
71
72 /// <summary>
73 /// Memory format tag for TouchState.
74 /// </summary>
75 /// <value>Returns "TOUC".</value>
76 /// <seealso cref="InputStateBlock.format"/>
77 public static FourCC Format => new FourCC('T', 'O', 'U', 'C');
78
79 ////REVIEW: this should really be a uint
80 /// <summary>
81 /// Numeric ID of the touch.
82 /// </summary>
83 /// <value>Numeric ID of the touch.</value>
84 /// <remarks>
85 /// While a touch is ongoing, it must have a non-zero ID different from
86 /// all other ongoing touches. Starting with <see cref="TouchPhase.Began"/>
87 /// and ending with <see cref="TouchPhase.Ended"/> or <see cref="TouchPhase.Canceled"/>,
88 /// a touch is identified by its ID, i.e. a TouchState with the same ID
89 /// belongs to the same touch.
90 ///
91 /// After a touch has ended or been canceled, an ID can be reused.
92 /// </remarks>
93 /// <seealso cref="TouchControl.touchId"/>
94 [InputControl(displayName = "Touch ID", layout = "Integer", synthetic = true, dontReset = true)]
95 [FieldOffset(0)]
96 public int touchId;
97
98 /// <summary>
99 /// Screen-space position of the touch in pixels.
100 /// </summary>
101 /// <value>Screen-space position of the touch.</value>
102 /// <seealso cref="TouchControl.position"/>
103 [InputControl(displayName = "Position", dontReset = true)]
104 [FieldOffset(4)]
105 public Vector2 position;
106
107 /// <summary>
108 /// Screen-space motion delta of the touch in pixels.
109 /// </summary>
110 /// <value>Screen-space movement delta.</value>
111 /// <seealso cref="TouchControl.delta"/>
112 [InputControl(displayName = "Delta", layout = "Delta")]
113 [FieldOffset(12)]
114 public Vector2 delta;
115
116 /// <summary>
117 /// Pressure-level of the touch against the touchscreen.
118 /// </summary>
119 /// <value>Pressure of touch.</value>
120 /// <remarks>
121 /// The core range for this value is [0..1] with 1 indicating maximum pressure. Note, however,
122 /// that the actual value may go beyond 1 in practice. This is because the system will usually
123 /// define "maximum pressure" to be less than the physical maximum limit the hardware is capable
124 /// of reporting so that to achieve maximum pressure, one does not need to press as hard as
125 /// possible.
126 /// </remarks>
127 /// <seealso cref="TouchControl.pressure"/>
128 [InputControl(displayName = "Pressure", layout = "Axis")]
129 [FieldOffset(20)]
130 public float pressure;
131
132 /// <summary>
133 /// Radius of the touch print on the surface.
134 /// </summary>
135 /// <value>Touch extents horizontally and vertically.</value>
136 /// <remarks>
137 /// The touch radius is given in screen-space pixel coordinates along X and Y centered in the middle
138 /// of the touch. Note that not all screens and systems support radius detection on touches so this
139 /// value may be at <c>default</c> for an otherwise perfectly valid touch.
140 /// </remarks>
141 /// <seealso cref="TouchControl.radius"/>
142 [InputControl(displayName = "Radius")]
143 [FieldOffset(24)]
144 public Vector2 radius;
145
146 /// <summary>
147 /// <see cref="TouchPhase"/> value of the touch.
148 /// </summary>
149 /// <value>Current <see cref="TouchPhase"/>.</value>
150 /// <seealso cref="phase"/>
151 [InputControl(name = "phase", displayName = "Touch Phase", layout = "TouchPhase", synthetic = true)]
152 [InputControl(name = "press", displayName = "Touch Contact?", layout = "TouchPress", useStateFrom = "phase")]
153 [FieldOffset(32)]
154 public byte phaseId;
155
156 [InputControl(name = "tapCount", displayName = "Tap Count", layout = "Integer")]
157 [FieldOffset(33)]
158 public byte tapCount;
159
160 /// <summary>
161 /// The index of the display that was touched.
162 /// </summary>
163 [InputControl(name = "displayIndex", displayName = "Display Index", layout = "Integer")]
164 [FieldOffset(34)]
165 public byte displayIndex;
166
167 [InputControl(name = "indirectTouch", displayName = "Indirect Touch?", layout = "Button", bit = 0, synthetic = true)]
168 [InputControl(name = "tap", displayName = "Tap", layout = "Button", bit = 4)]
169 [FieldOffset(35)]
170 public byte flags;
171
172 // Need four bytes of alignment here for the startTime double. Using that for storing updateStepCounts.
173 // They aren't needed directly by Touchscreen but are used by EnhancedTouch and since we have the four
174 // bytes, may just as well use them instead of wasting them on padding.
175 [FieldOffset(36)]
176 internal uint updateStepCount;
177
178 // NOTE: The following data is NOT sent by native but rather data we add on the managed side to each touch.
179
180 /// <summary>
181 /// Time that the touch was started. Relative to <c>Time.realTimeSinceStartup</c>.
182 /// </summary>
183 /// <value>Time that the touch was started.</value>
184 /// <remarks>
185 /// This is set automatically by <see cref="Touchscreen"/> and does not need to be provided
186 /// by events sent to the touchscreen.
187 /// </remarks>
188 /// <seealso cref="InputEvent.time"/>
189 /// <seealso cref="TouchControl.startTime"/>
190 [InputControl(displayName = "Start Time", layout = "Double", synthetic = true)]
191 [FieldOffset(40)]
192 public double startTime; // In *external* time, i.e. currentTimeOffsetToRealtimeSinceStartup baked in.
193
194 /// <summary>
195 /// The position where the touch started.
196 /// </summary>
197 /// <value>Screen-space start position of the touch.</value>
198 /// <remarks>
199 /// This is set automatically by <see cref="Touchscreen"/> and does not need to be provided
200 /// by events sent to the touchscreen.
201 /// </remarks>
202 /// <seealso cref="TouchControl.startPosition"/>
203 [InputControl(displayName = "Start Position", synthetic = true)]
204 [FieldOffset(48)]
205 public Vector2 startPosition;
206
207 /// <summary>
208 /// Get or set the phase of the touch.
209 /// </summary>
210 /// <value>Phase of the touch.</value>
211 /// <seealso cref="TouchControl.phase"/>
212 public TouchPhase phase
213 {
214 get => (TouchPhase)phaseId;
215 set => phaseId = (byte)value;
216 }
217
218 public bool isNoneEndedOrCanceled => phase == TouchPhase.None || phase == TouchPhase.Ended ||
219 phase == TouchPhase.Canceled;
220 public bool isInProgress => phase == TouchPhase.Began || phase == TouchPhase.Moved ||
221 phase == TouchPhase.Stationary;
222
223 /// <summary>
224 /// Whether, after not having any touch contacts, this is part of the first touch contact that started.
225 /// </summary>
226 /// <remarks>
227 /// This flag will be set internally by <see cref="Touchscreen"/>. Generally, it is
228 /// not necessary to set this bit manually when feeding data to Touchscreens.
229 /// </remarks>
230 public bool isPrimaryTouch
231 {
232 get => (flags & (byte)TouchFlags.PrimaryTouch) != 0;
233 set
234 {
235 if (value)
236 flags |= (byte)TouchFlags.PrimaryTouch;
237 else
238 flags &= (byte)~TouchFlags.PrimaryTouch;
239 }
240 }
241
242 internal bool isOrphanedPrimaryTouch
243 {
244 get => (flags & (byte)TouchFlags.OrphanedPrimaryTouch) != 0;
245 set
246 {
247 if (value)
248 flags |= (byte)TouchFlags.OrphanedPrimaryTouch;
249 else
250 flags &= (byte)~TouchFlags.OrphanedPrimaryTouch;
251 }
252 }
253
254 public bool isIndirectTouch
255 {
256 get => (flags & (byte)TouchFlags.IndirectTouch) != 0;
257 set
258 {
259 if (value)
260 flags |= (byte)TouchFlags.IndirectTouch;
261 else
262 flags &= (byte)~TouchFlags.IndirectTouch;
263 }
264 }
265
266 public bool isTap
267 {
268 get => isTapPress;
269 set => isTapPress = value;
270 }
271
272 internal bool isTapPress
273 {
274 get => (flags & (byte)TouchFlags.TapPress) != 0;
275 set
276 {
277 if (value)
278 flags |= (byte)TouchFlags.TapPress;
279 else
280 flags &= (byte)~TouchFlags.TapPress;
281 }
282 }
283
284 internal bool isTapRelease
285 {
286 get => (flags & (byte)TouchFlags.TapRelease) != 0;
287 set
288 {
289 if (value)
290 flags |= (byte)TouchFlags.TapRelease;
291 else
292 flags &= (byte)~TouchFlags.TapRelease;
293 }
294 }
295
296 internal bool beganInSameFrame
297 {
298 get => (flags & (byte)TouchFlags.BeganInSameFrame) != 0;
299 set
300 {
301 if (value)
302 flags |= (byte)TouchFlags.BeganInSameFrame;
303 else
304 flags &= (byte)~TouchFlags.BeganInSameFrame;
305 }
306 }
307
308 /// <inheritdoc/>
309 public FourCC format => Format;
310
311 /// <summary>
312 /// Return a string representation of the state useful for debugging.
313 /// </summary>
314 /// <returns>A string representation of the touch state.</returns>
315 public override string ToString()
316 {
317 return $"{{ id={touchId} phase={phase} pos={position} delta={delta} pressure={pressure} radius={radius} primary={isPrimaryTouch} }}";
318 }
319 }
320
321 /// <summary>
322 /// Default state layout for touch devices.
323 /// </summary>
324 /// <remarks>
325 /// Combines multiple pointers each corresponding to a single contact.
326 ///
327 /// Normally, TODO (sending state events)
328 ///
329 /// All touches combine to quite a bit of state; ideally send delta events that update
330 /// only specific fingers.
331 ///
332 /// This is NOT used by native. Instead, the native runtime always sends individual touches (<see cref="TouchState"/>)
333 /// and leaves state management for a touchscreen as a whole to the managed part of the system.
334 /// </remarks>
335 [StructLayout(LayoutKind.Explicit, Size = MaxTouches * TouchState.kSizeInBytes)]
336 internal unsafe struct TouchscreenState : IInputStateTypeInfo
337 {
338 /// <summary>
339 /// Memory format tag for TouchscreenState.
340 /// </summary>
341 /// <value>Returns "TSCR".</value>
342 /// <seealso cref="InputStateBlock.format"/>
343 public static FourCC Format => new FourCC('T', 'S', 'C', 'R');
344
345 /// <summary>
346 /// Maximum number of touches that can be tracked at the same time.
347 /// </summary>
348 /// <value>Maximum number of concurrent touches.</value>
349 public const int MaxTouches = 10;
350
351 /// <summary>
352 /// Data for the touch that is deemed the "primary" touch at the moment.
353 /// </summary>
354 /// <remarks>
355 /// This touch duplicates touch data from whichever touch is deemed the primary touch at the moment.
356 /// When going from no fingers down to any finger down, the first finger to touch the screen is
357 /// deemed the "primary touch". It stays the primary touch until released. At that point, if any other
358 /// finger is still down, the next finger in <see cref="touchData"/> is
359 ///
360 /// Having this touch be its own separate state and own separate control allows actions to track the
361 /// state of the primary touch even if the touch moves from one finger to another in <see cref="touchData"/>.
362 /// </remarks>
363 [InputControl(name = "primaryTouch", displayName = "Primary Touch", layout = "Touch", synthetic = true)]
364 [InputControl(name = "primaryTouch/tap", usage = "PrimaryAction")]
365
366 // Add controls compatible with what Pointer expects and redirect their
367 // state to the state of touch0 so that this essentially becomes our
368 // pointer control.
369 // NOTE: Some controls from Pointer don't make sense for touch and we "park"
370 // them by assigning them invalid offsets (thus having automatic state
371 // layout put them at the end of our fixed state).
372 [InputControl(name = "position", useStateFrom = "primaryTouch/position")]
373 [InputControl(name = "delta", useStateFrom = "primaryTouch/delta", layout = "Delta")]
374 [InputControl(name = "pressure", useStateFrom = "primaryTouch/pressure")]
375 [InputControl(name = "radius", useStateFrom = "primaryTouch/radius")]
376 [InputControl(name = "press", useStateFrom = "primaryTouch/phase", layout = "TouchPress", synthetic = true, usages = new string[0])]
377 [InputControl(name = "displayIndex", useStateFrom = "primaryTouch/displayIndex", format = "BYTE")] // added format to override the Pointer's USHT value
378 [FieldOffset(0)]
379 public fixed byte primaryTouchData[TouchState.kSizeInBytes];
380
381 internal const int kTouchDataOffset = TouchState.kSizeInBytes;
382
383 [InputControl(layout = "Touch", name = "touch", displayName = "Touch", arraySize = MaxTouches)]
384 [FieldOffset(kTouchDataOffset)]
385 public fixed byte touchData[MaxTouches * TouchState.kSizeInBytes];
386
387 public TouchState* primaryTouch
388 {
389 get
390 {
391 fixed(byte* ptr = primaryTouchData)
392 return (TouchState*)ptr;
393 }
394 }
395
396 public TouchState* touches
397 {
398 get
399 {
400 fixed(byte* ptr = touchData)
401 return (TouchState*)ptr;
402 }
403 }
404
405 public FourCC format => Format;
406 }
407}
408
409namespace UnityEngine.InputSystem
410{
411 /// <summary>
412 /// Indicates where in its lifecycle a given touch is.
413 /// </summary>
414 public enum TouchPhase
415 {
416 ////REVIEW: Why have a separate None instead of just making this equivalent to either Ended or Canceled?
417 /// <summary>
418 /// No activity has been registered on the touch yet.
419 /// </summary>
420 /// <remarks>
421 /// A given touch state will generally not go back to None once there has been input for it. Meaning that
422 /// it generally indicates a default-initialized touch record.
423 /// </remarks>
424 None,
425
426 /// <summary>
427 /// A touch has just begun, i.e. a finger has touched the screen.. Only the first touch input in any given touch will have this phase.
428 /// </summary>
429 Began,
430
431 /// <summary>
432 /// An ongoing touch has changed position.
433 /// </summary>
434 Moved,
435
436 /// <summary>
437 /// An ongoing touch has just ended, i.e. the respective finger has been lifted off of the screen. Only the last touch input in a
438 /// given touch will have this phase.
439 /// </summary>
440 Ended,
441
442 /// <summary>
443 /// An ongoing touch has been cancelled, i.e. ended in a way other than through user interaction. This happens, for example, if
444 /// focus is moved away from the application while the touch is ongoing.
445 /// </summary>
446 Canceled,
447
448 /// <summary>
449 /// An ongoing touch has not been moved (not received any input) in a frame.
450 /// </summary>
451 /// <remarks>
452 /// This phase is not used by <see cref="Touchscreen"/>. This means that <see cref="TouchControl"/> will not generally
453 /// return this value for <see cref="TouchControl.phase"/>. It is, however, used by <see cref="UnityEngine.InputSystem.EnhancedTouch.Touch"/>.
454 /// </remarks>
455 Stationary,
456 }
457
458 /// <summary>
459 /// A multi-touch surface.
460 /// </summary>
461 /// <remarks>
462 /// Touchscreen is somewhat different from most other device implementations in that it does not usually
463 /// consume input in the form of a full device snapshot but rather consumes input sent to it in the form
464 /// of events containing a <see cref="TouchState"/> each. This is unusual as <see cref="TouchState"/>
465 /// uses a memory format different from <see cref="TouchState.Format"/>. However, when a <c>Touchscreen</c>
466 /// sees an event containing a <see cref="TouchState"/>, it will handle that event on a special code path.
467 ///
468 /// This allows <c>Touchscreen</c> to decide on its own which control in <see cref="touches"/> to store
469 /// a touch at and to perform things such as tap detection (see <see cref="TouchControl.tap"/> and
470 /// <see cref="TouchControl.tapCount"/>) and primary touch handling (see <see cref="primaryTouch"/>).
471 ///
472 /// <example>
473 /// <code>
474 /// // Create a touchscreen device.
475 /// var touchscreen = InputSystem.AddDevice<Touchscreen>();
476 ///
477 /// // Send a touch to the device.
478 /// InputSystem.QueueStateEvent(touchscreen,
479 /// new TouchState
480 /// {
481 /// phase = TouchPhase.Began,
482 /// // Must have a valid, non-zero touch ID. Touchscreen will not operate
483 /// // correctly if we don't set IDs properly.
484 /// touchId = 1,
485 /// position = new Vector2(123, 234),
486 /// // Delta will be computed by Touchscreen automatically.
487 /// });
488 /// </code>
489 /// </example>
490 ///
491 /// Note that this class presents a fairly low-level touch API. When working with touch from script code,
492 /// it is recommended to use the higher-level <see cref="EnhancedTouch.Touch"/> API instead.
493 /// </remarks>
494 [InputControlLayout(stateType = typeof(TouchscreenState), isGenericTypeOfDevice = true)]
495 public class Touchscreen : Pointer, IInputStateCallbackReceiver, IEventMerger, ICustomDeviceReset
496 {
497 /// <summary>
498 /// Synthetic control that has the data for the touch that is deemed the "primary" touch at the moment.
499 /// </summary>
500 /// <value>Control tracking the screen's primary touch.</value>
501 /// <remarks>
502 /// This touch duplicates touch data from whichever touch is deemed the primary touch at the moment.
503 /// When going from no fingers down to any finger down, the first finger to touch the screen is
504 /// deemed the "primary touch". It stays the primary touch until the last finger is released.
505 ///
506 /// Note that unlike the touch from which it originates, the primary touch will be kept ongoing for
507 /// as long as there is still a finger on the screen. Put another way, <see cref="TouchControl.phase"/>
508 /// of <c>primaryTouch</c> will only transition to <see cref="TouchPhase.Ended"/> once the last finger
509 /// has been lifted off the screen.
510 /// </remarks>
511 public TouchControl primaryTouch { get; protected set; }
512
513 /// <summary>
514 /// Array of all <see cref="TouchControl"/>s on the device.
515 /// </summary>
516 /// <value>All <see cref="TouchControl"/>s on the screen.</value>
517 /// <remarks>
518 /// By default, a touchscreen will allocate 10 touch controls. This can be changed
519 /// by modifying the "Touchscreen" layout itself or by derived layouts. In practice,
520 /// this means that this array will usually have a fixed length of 10 entries but
521 /// it may deviate from that.
522 /// </remarks>
523 public ReadOnlyArray<TouchControl> touches { get; protected set; }
524
525
526 static readonly ProfilerMarker k_TouchscreenUpdateMarker = new ProfilerMarker("Touchscreen.OnNextUpdate");
527 static readonly ProfilerMarker k_TouchAllocateMarker = new ProfilerMarker("TouchAllocate");
528
529 protected TouchControl[] touchControlArray
530 {
531 get => touches.m_Array;
532 set => touches = new ReadOnlyArray<TouchControl>(value);
533 }
534
535 /// <summary>
536 /// The touchscreen that was added or updated last or null if there is no
537 /// touchscreen connected to the system.
538 /// </summary>
539 /// <value>Current touch screen.</value>
540 public new static Touchscreen current { get; internal set; }
541
542 /// <inheritdoc />
543 public override void MakeCurrent()
544 {
545 base.MakeCurrent();
546 current = this;
547 }
548
549 /// <inheritdoc />
550 protected override void OnRemoved()
551 {
552 base.OnRemoved();
553 if (current == this)
554 current = null;
555 }
556
557 /// <inheritdoc />
558 protected override void FinishSetup()
559 {
560 base.FinishSetup();
561
562 primaryTouch = GetChildControl<TouchControl>("primaryTouch");
563
564 // Find out how many touch controls we have.
565 var touchControlCount = 0;
566 foreach (var child in children)
567 if (child is TouchControl)
568 ++touchControlCount;
569
570 // Keep primaryTouch out of array.
571 Debug.Assert(touchControlCount >= 1, "Should have found at least primaryTouch control");
572 if (touchControlCount >= 1)
573 --touchControlCount;
574
575 // Gather touch controls into array.
576 var touchArray = new TouchControl[touchControlCount];
577 var touchIndex = 0;
578 foreach (var child in children)
579 {
580 if (child == primaryTouch)
581 continue;
582
583 if (child is TouchControl control)
584 touchArray[touchIndex++] = control;
585 }
586
587 touches = new ReadOnlyArray<TouchControl>(touchArray);
588 }
589
590 // Touch has more involved state handling than most other devices. To not put touch allocation logic
591 // in all the various platform backends (i.e. see a touch with a certain ID coming in from the system
592 // and then having to decide *where* to store that inside of Touchscreen's state), we have backends
593 // send us individual touches ('TOUC') instead of whole Touchscreen snapshots ('TSRC'). Using
594 // IInputStateCallbackReceiver, Touchscreen then dynamically decides where to store the touch.
595 //
596 // Also, Touchscreen has bits of logic to automatically synthesize the state of controls it inherits
597 // from Pointer (such as "<Pointer>/press").
598 //
599 // NOTE: We do *NOT* make a effort here to prevent us from losing short-lived touches. This is different
600 // from the old input system where individual touches were not reused until the next frame. This meant
601 // that additional touches potentially had to be allocated in order to accommodate new touches coming
602 // in from the system.
603 //
604 // The rationale for *NOT* doing this is that:
605 //
606 // a) Actions don't need it. They observe every single state change and thus will not lose data
607 // even if it is short-lived (i.e. changes more than once in the same update).
608 // b) The higher-level Touch (EnhancedTouchSupport) API is provided to
609 // not only handle this scenario but also give a generally more flexible and useful touch API
610 // than writing code directly against Touchscreen.
611
612 protected new unsafe void OnNextUpdate()
613 {
614 k_TouchscreenUpdateMarker.Begin();
615
616 ////TODO: early out and skip crawling through touches if we didn't change state in the last update
617 //// (also obsoletes the need for the if() check below)
618 var statePtr = currentStatePtr;
619 var touchStatePtr = (TouchState*)((byte*)statePtr + stateBlock.byteOffset + TouchscreenState.kTouchDataOffset);
620 for (var i = 0; i < touches.Count; ++i, ++touchStatePtr)
621 {
622 // Reset delta.
623 if (touchStatePtr->delta != default)
624 InputState.Change(touches[i].delta, Vector2.zero);
625
626 // Reset tap count.
627 // NOTE: We are basing this on startTime rather than adding on end time of the last touch. The reason is
628 // that to do so we would have to add another record to keep track of timestamps for each touch. And
629 // since we know the maximum time that a tap can take, we have a reasonable estimate for when a prior
630 // tap must have ended.
631 if (touchStatePtr->tapCount > 0 && InputState.currentTime >= touchStatePtr->startTime + s_TapTime + s_TapDelayTime)
632 InputState.Change(touches[i].tapCount, (byte)0);
633 }
634
635 var primaryTouchState = (TouchState*)((byte*)statePtr + stateBlock.byteOffset);
636 if (primaryTouchState->delta != default)
637 InputState.Change(primaryTouch.delta, Vector2.zero);
638 if (primaryTouchState->tapCount > 0 && InputState.currentTime >= primaryTouchState->startTime + s_TapTime + s_TapDelayTime)
639 InputState.Change(primaryTouch.tapCount, (byte)0);
640
641 k_TouchscreenUpdateMarker.End();
642 }
643
644 /// <summary>
645 /// Called whenever a new state event is received.
646 /// </summary>
647 /// <param name="eventPtr"></param>
648 protected new unsafe void OnStateEvent(InputEventPtr eventPtr)
649 {
650 var eventType = eventPtr.type;
651
652 // We don't allow partial updates for TouchStates.
653 if (eventType == DeltaStateEvent.Type)
654 return;
655
656 // If it's not a single touch, just take the event state as is (will have to be TouchscreenState).
657 var stateEventPtr = StateEvent.FromUnchecked(eventPtr);
658 if (stateEventPtr->stateFormat != TouchState.Format)
659 {
660 InputState.Change(this, eventPtr);
661 return;
662 }
663
664 k_TouchAllocateMarker.Begin();
665
666 // For performance reasons, we read memory here directly rather than going through
667 // ReadValue() of the individual TouchControl children. This means that Touchscreen,
668 // unlike other devices, is hardwired to a single memory layout only.
669
670 var statePtr = currentStatePtr;
671 var currentTouchState = (TouchState*)((byte*)statePtr + touches[0].stateBlock.byteOffset);
672 var primaryTouchState = (TouchState*)((byte*)statePtr + primaryTouch.stateBlock.byteOffset);
673 var touchControlCount = touches.Count;
674
675 // Native does not send a full TouchState as we define it here. We have added some fields
676 // that we store internally. Make sure we don't read invalid memory here and copy only what
677 // we got.
678 TouchState newTouchState;
679 if (stateEventPtr->stateSizeInBytes == TouchState.kSizeInBytes)
680 {
681 newTouchState = *(TouchState*)stateEventPtr->state;
682 }
683 else
684 {
685 newTouchState = default;
686 UnsafeUtility.MemCpy(UnsafeUtility.AddressOf(ref newTouchState), stateEventPtr->state, stateEventPtr->stateSizeInBytes);
687 }
688
689 // Make sure we're not getting thrown off by noise on fields that we don't want to
690 // pick up from input.
691 newTouchState.tapCount = 0;
692 newTouchState.isTapPress = false;
693 newTouchState.isTapRelease = false;
694 newTouchState.updateStepCount = InputUpdate.s_UpdateStepCount;
695
696 ////REVIEW: The logic in here makes us inherently susceptible to the ordering of the touch events in the event
697 //// stream. I believe we have platforms (Android?) that send us touch events finger-by-finger (or touch-by-touch?)
698 //// rather than sorted by time. This will probably screw up the logic in here.
699
700 // If it's an ongoing touch, try to find the TouchState we have allocated to the touch
701 // previously.
702 var phase = newTouchState.phase;
703 if (phase != TouchPhase.Began)
704 {
705 var touchId = newTouchState.touchId;
706 for (var i = 0; i < touchControlCount; ++i)
707 {
708 if (currentTouchState[i].touchId == touchId)
709 {
710 // Preserve primary touch state.
711 var isPrimaryTouch = currentTouchState[i].isPrimaryTouch;
712 newTouchState.isPrimaryTouch = isPrimaryTouch;
713
714 // Compute delta if touch doesn't have one.
715 if (newTouchState.delta == default)
716 newTouchState.delta = newTouchState.position - currentTouchState[i].position;
717
718 // Accumulate delta.
719 newTouchState.delta += currentTouchState[i].delta;
720
721 // Keep start time and position.
722 newTouchState.startTime = currentTouchState[i].startTime;
723 newTouchState.startPosition = currentTouchState[i].startPosition;
724
725 // Detect taps.
726 var isTap = newTouchState.isNoneEndedOrCanceled &&
727 (eventPtr.time - newTouchState.startTime) <= s_TapTime &&
728 ////REVIEW: this only takes the final delta to start position into account, not the delta over the lifetime of the
729 //// touch; is this robust enough or do we need to make sure that we never move more than the tap radius
730 //// over the entire lifetime of the touch?
731 (newTouchState.position - newTouchState.startPosition).sqrMagnitude <= s_TapRadiusSquared;
732 if (isTap)
733 newTouchState.tapCount = (byte)(currentTouchState[i].tapCount + 1);
734 else
735 newTouchState.tapCount = currentTouchState[i].tapCount; // Preserve tap count; reset in OnCarryStateForward.
736
737 // Update primary touch.
738 if (isPrimaryTouch)
739 {
740 if (newTouchState.isNoneEndedOrCanceled)
741 {
742 ////REVIEW: also reset tapCounts here when tap delay time has expired on the touch?
743
744 newTouchState.isPrimaryTouch = false;
745
746 // Primary touch was ended. See if there are still other ongoing touches.
747 var haveOngoingTouch = false;
748 for (var n = 0; n < touchControlCount; ++n)
749 {
750 if (n == i)
751 continue;
752
753 if (currentTouchState[n].isInProgress)
754 {
755 haveOngoingTouch = true;
756 break;
757 }
758 }
759
760 if (!haveOngoingTouch)
761 {
762 // No, primary was the only ongoing touch. End it.
763
764 if (isTap)
765 TriggerTap(primaryTouch, ref newTouchState, eventPtr);
766 else
767 InputState.Change(primaryTouch, ref newTouchState, eventPtr: eventPtr);
768 }
769 else
770 {
771 // Yes, we have other touches going on. Make the primary touch an
772 // orphan and wait until the other touches are released.
773
774 var newPrimaryTouchState = newTouchState;
775 newPrimaryTouchState.phase = TouchPhase.Moved;
776 newPrimaryTouchState.isOrphanedPrimaryTouch = true;
777 InputState.Change(primaryTouch, ref newPrimaryTouchState, eventPtr: eventPtr);
778 }
779 }
780 else
781 {
782 // Primary touch was updated.
783 InputState.Change(primaryTouch, ref newTouchState, eventPtr: eventPtr);
784 }
785 }
786 else
787 {
788 // If it's not the primary touch but the touch has ended, see if we have an
789 // orphaned primary touch. If so, end it now.
790 if (newTouchState.isNoneEndedOrCanceled && primaryTouchState->isOrphanedPrimaryTouch)
791 {
792 var haveOngoingTouch = false;
793 for (var n = 0; n < touchControlCount; ++n)
794 {
795 if (n == i)
796 continue;
797
798 if (currentTouchState[n].isInProgress)
799 {
800 haveOngoingTouch = true;
801 break;
802 }
803 }
804
805 if (!haveOngoingTouch)
806 {
807 primaryTouchState->isOrphanedPrimaryTouch = false;
808 InputState.Change(primaryTouch.phase, (byte)TouchPhase.Ended);
809 }
810 }
811 }
812
813 if (isTap)
814 {
815 // Make tap button go down and up.
816 //
817 // NOTE: We do this here instead of right away up there when we detect the touch so
818 // that the state change notifications go together. First those for the primary
819 // touch, then the ones for the touch record itself.
820 TriggerTap(touches[i], ref newTouchState, eventPtr);
821 }
822 else
823 {
824 InputState.Change(touches[i], ref newTouchState, eventPtr: eventPtr);
825 }
826
827 k_TouchAllocateMarker.End();
828 return;
829 }
830 }
831
832 // Couldn't find an entry. Either it was a touch that we previously ran out of available
833 // entries for or it's an event sent out of sequence. Ignore the touch to be consistent.
834
835 k_TouchAllocateMarker.End();
836 return;
837 }
838
839 // It's a new touch. Try to find an unused TouchState.
840 for (var i = 0; i < touchControlCount; ++i, ++currentTouchState)
841 {
842 // NOTE: We're overwriting any ended touch immediately here. This means we immediately overwrite even
843 // if we still have other unused slots. What this gives us is a completely predictable touch #0..#N
844 // sequence (i.e. touch #N is only ever used if there are indeed #N concurrently touches). However,
845 // it does mean that we overwrite state aggressively. If you are not using actions or the higher-level
846 // Touch API, be aware of this!
847 if (currentTouchState->isNoneEndedOrCanceled)
848 {
849 newTouchState.delta = Vector2.zero;
850 newTouchState.startTime = eventPtr.time;
851 newTouchState.startPosition = newTouchState.position;
852
853 // Make sure we're not picking up noise sent from native.
854 newTouchState.isPrimaryTouch = false;
855 newTouchState.isOrphanedPrimaryTouch = false;
856 newTouchState.isTap = false;
857
858 // Tap counts are preserved from prior touches on the same finger.
859 newTouchState.tapCount = currentTouchState->tapCount;
860
861 // Make primary touch, if there's none currently.
862 if (primaryTouchState->isNoneEndedOrCanceled)
863 {
864 newTouchState.isPrimaryTouch = true;
865 InputState.Change(primaryTouch, ref newTouchState, eventPtr: eventPtr);
866 }
867
868 InputState.Change(touches[i], ref newTouchState, eventPtr: eventPtr);
869
870 k_TouchAllocateMarker.End();
871 return;
872 }
873 }
874
875 // We ran out of state and we don't want to stomp an existing ongoing touch.
876 // Drop this touch entirely.
877 // NOTE: Getting here means we're having fewer touch entries than the number of concurrent touches supported
878 // by the backend (or someone is simply sending us nonsense data).
879
880 k_TouchAllocateMarker.End();
881 }
882
883 void IInputStateCallbackReceiver.OnNextUpdate()
884 {
885 OnNextUpdate();
886 }
887
888 void IInputStateCallbackReceiver.OnStateEvent(InputEventPtr eventPtr)
889 {
890 OnStateEvent(eventPtr);
891 }
892
893 unsafe bool IInputStateCallbackReceiver.GetStateOffsetForEvent(InputControl control, InputEventPtr eventPtr, ref uint offset)
894 {
895 // This code goes back to the trickery we perform in OnStateEvent. We consume events in TouchState format
896 // instead of in TouchscreenState format. This means that the input system does not know how the state in those
897 // events correlates to the controls we have.
898 //
899 // This method is used to give the input system an offset based on which the input system can compute relative
900 // offsets into the state of eventPtr for controls that are part of the control hierarchy rooted at 'control'.
901
902 if (!eventPtr.IsA<StateEvent>())
903 return false;
904
905 var stateEventPtr = StateEvent.FromUnchecked(eventPtr);
906 if (stateEventPtr->stateFormat != TouchState.Format)
907 return false;
908
909 // If we get a null control and a TouchState event, all the system wants to know is what
910 // state offset to use to make sense of the event.
911 if (control == null)
912 {
913 // We can't say which specific touch this would go to (if any at all) without going through
914 // the same logic that we run through in OnStateEvent. For the sake of just being able to read
915 // out data from a touch event, it'd be enough to return the offset of *any* TouchControl here.
916 // But for the sake of being able to compare the data in an event to that in the Touchscreen,
917 // this would not be enough. Thus we make an attempt here at locating a touch record which *should*
918 // be receiving the event if it were to be processed by OnStateEvent.
919
920 var currentTouchState = (TouchState*)((byte*)currentStatePtr + touches[0].stateBlock.byteOffset);
921 var eventTouchState = (TouchState*)stateEventPtr->state;
922 var eventTouchId = eventTouchState->touchId;
923 var eventTouchPhase = eventTouchState->phase;
924
925 var touchControlCount = touches.Count;
926 for (var i = 0; i < touchControlCount; ++i)
927 {
928 var touch = ¤tTouchState[i];
929 if (touch->touchId == eventTouchId || (!touch->isInProgress && eventTouchPhase.IsActive()))
930 {
931 offset = primaryTouch.m_StateBlock.byteOffset + primaryTouch.m_StateBlock.alignedSizeInBytes - m_StateBlock.byteOffset +
932 (uint)(i * UnsafeUtility.SizeOf<TouchState>());
933 return true;
934 }
935 }
936
937 return false;
938 }
939
940 // The only controls we can read out from a TouchState event are those that are part of TouchControl
941 // (and part of this Touchscreen).
942 var touchControl = control.FindInParentChain<TouchControl>();
943 if (touchControl == null || touchControl.parent != this)
944 return false;
945
946 // We could allow *any* of the TouchControls on the Touchscreen here. We'd simply base the
947 // offset on the TouchControl of the 'control' we get as an argument.
948 //
949 // However, doing that would mean that all the TouchControls would map into the same input event.
950 // So when a piece of code like in InputUser goes and cycles through all controls to determine ones
951 // that have changed in an event, it would find that instead of a single touch position value changing,
952 // all of them would be changing from the same single event.
953 //
954 // For this reason, we lock things down to the primaryTouch control.
955
956 if (touchControl != primaryTouch)
957 return false;
958
959 offset = touchControl.stateBlock.byteOffset - m_StateBlock.byteOffset;
960 return true;
961 }
962
963 // Implement our own custom reset so that we can cancel touches instead of just wiping them
964 // with default state.
965 unsafe void ICustomDeviceReset.Reset()
966 {
967 var statePtr = currentStatePtr;
968
969 //// https://jira.unity3d.com/browse/ISX-930
970 ////TODO: Figure out a proper way to distinguish the source / reason for a state change.
971 //// What we're doing here is constructing an event solely for the purpose of Finger.ShouldRecordTouch() not
972 //// ignoring the state change like it does for delta resets.
973
974 using (var buffer = new NativeArray<byte>(StateEvent.GetEventSizeWithPayload<TouchState>(), Allocator.Temp))
975 {
976 var eventPtr = (StateEvent*)buffer.GetUnsafePtr();
977
978 eventPtr->baseEvent = new InputEvent(StateEvent.Type, buffer.Length, deviceId);
979
980 var primaryTouchState = (TouchState*)((byte*)statePtr + primaryTouch.stateBlock.byteOffset);
981 if (primaryTouchState->phase.IsActive())
982 {
983 UnsafeUtility.MemCpy(eventPtr->state, primaryTouchState, UnsafeUtility.SizeOf<TouchState>());
984 ((TouchState*)eventPtr->state)->phase = TouchPhase.Canceled;
985 InputState.Change(primaryTouch.phase, TouchPhase.Canceled, eventPtr: new InputEventPtr((InputEvent*)eventPtr));
986 }
987
988 var touchStates = (TouchState*)((byte*)statePtr + touches[0].stateBlock.byteOffset);
989 var touchCount = touches.Count;
990 for (var i = 0; i < touchCount; ++i)
991 {
992 if (touchStates[i].phase.IsActive())
993 {
994 UnsafeUtility.MemCpy(eventPtr->state, &touchStates[i], UnsafeUtility.SizeOf<TouchState>());
995 ((TouchState*)eventPtr->state)->phase = TouchPhase.Canceled;
996 InputState.Change(touches[i].phase, TouchPhase.Canceled, eventPtr: new InputEventPtr((InputEvent*)eventPtr));
997 }
998 }
999 }
1000 }
1001
1002 internal static unsafe bool MergeForward(InputEventPtr currentEventPtr, InputEventPtr nextEventPtr)
1003 {
1004 if (currentEventPtr.type != StateEvent.Type || nextEventPtr.type != StateEvent.Type)
1005 return false;
1006
1007 var currentEvent = StateEvent.FromUnchecked(currentEventPtr);
1008 var nextEvent = StateEvent.FromUnchecked(nextEventPtr);
1009
1010 if (currentEvent->stateFormat != TouchState.Format || nextEvent->stateFormat != TouchState.Format)
1011 return false;
1012
1013 var currentState = (TouchState*)currentEvent->state;
1014 var nextState = (TouchState*)nextEvent->state;
1015
1016 if (currentState->touchId != nextState->touchId || currentState->phaseId != nextState->phaseId || currentState->flags != nextState->flags)
1017 return false;
1018
1019 nextState->delta += currentState->delta;
1020
1021 return true;
1022 }
1023
1024 bool IEventMerger.MergeForward(InputEventPtr currentEventPtr, InputEventPtr nextEventPtr)
1025 {
1026 return MergeForward(currentEventPtr, nextEventPtr);
1027 }
1028
1029 // We can only detect taps on touch *release*. At which point it acts like a button that triggers and releases
1030 // in one operation.
1031 private static void TriggerTap(TouchControl control, ref TouchState state, InputEventPtr eventPtr)
1032 {
1033 ////REVIEW: we're updating the entire TouchControl here; we could update just the tap state using a delta event; problem
1034 //// is that the tap *down* still needs a full update on the state
1035
1036 // We don't increase tapCount here as we may be sending the tap from the same state to both the TouchControl
1037 // that got tapped and to primaryTouch.
1038
1039 // Press.
1040 state.isTapPress = true;
1041 state.isTapRelease = false;
1042 InputState.Change(control, ref state, eventPtr: eventPtr);
1043
1044 // Release.
1045 state.isTapPress = false;
1046 state.isTapRelease = true;
1047 InputState.Change(control, ref state, eventPtr: eventPtr);
1048 state.isTapRelease = false;
1049 }
1050
1051 internal static float s_TapTime;
1052 internal static float s_TapDelayTime;
1053 internal static float s_TapRadiusSquared;
1054 }
1055}