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}