A game about forced loneliness, made by TACStudios
1using System; 2using System.Runtime.InteropServices; 3using Unity.Collections.LowLevel.Unsafe; 4using UnityEngine.InputSystem.Utilities; 5using UnityEngineInternal.Input; 6 7////REVIEW: can we get rid of the timestamp offsetting in the player and leave that complication for the editor only? 8 9namespace UnityEngine.InputSystem.LowLevel 10{ 11 /// <summary> 12 /// A chunk of memory signaling a data transfer in the input system. 13 /// </summary> 14 /// <remarks> 15 /// Input events are raw memory buffers akin to a byte array. For most uses of the input 16 /// system, it is not necessary to be aware of the event stream in the background. Events 17 /// are written to the internal event buffer by producers -- usually by the platform-specific 18 /// backends sitting in the Unity runtime. Once per fixed or dynamic update (depending on 19 /// what <see cref="InputSettings.updateMode"/> is set to), the input system then goes and 20 /// flushes out the internal event buffer to process pending events. 21 /// 22 /// Events may signal general device-related occurrences (such as <see cref="DeviceConfigurationEvent"/> 23 /// or <see cref="DeviceRemoveEvent"/>) or they may signal input activity. The latter kind of 24 /// event is called "state events". In particular, these events are either <see cref="StateEvent"/>, 25 /// only. 26 /// 27 /// Events are solely focused on input. To effect output on an input device (e.g. haptics 28 /// effects), "commands" (see <see cref="InputDeviceCommand"/>) are used. 29 /// 30 /// Event processing can be listened to using <see cref="InputSystem.onEvent"/>. This callback 31 /// will get triggered for each event as it is processed by the input system. 32 /// 33 /// Note that there is no "routing" mechanism for events, i.e. no mechanism by which the input 34 /// system looks for a handler for a specific event. Instead, events represent low-level activity 35 /// that the input system directly integrates into the state of its <see cref="InputDevice"/> 36 /// instances. 37 /// 38 /// Each type of event is distinguished by its own <see cref="FourCC"/> type tag. The tag can 39 /// be queried from the <see cref="type"/> property. 40 /// 41 /// Each event will receive a unique ID when queued to the internal event buffer. The ID can 42 /// be queried using the <see cref="eventId"/> property. Over the lifetime of the input system, 43 /// no two events will receive the same ID. If you repeatedly queue an event from the same 44 /// memory buffer, each individual call of <see cref="InputSystem.QueueEvent"/> will result in 45 /// its own unique event ID. 46 /// 47 /// All events are device-specific meaning that <see cref="deviceId"/> will always reference 48 /// some device (which, however, may or may not translate to an <see cref="InputDevice"/>; that 49 /// part depends on whether the input system was able to create an <see cref="InputDevice"/> 50 /// based on the information received from the backend). 51 /// </remarks> 52 /// <seealso cref="InputEventPtr"/> 53 // NOTE: This has to be layout compatible with native events. 54 [StructLayout(LayoutKind.Explicit, Size = kBaseEventSize, Pack = 1)] 55 public struct InputEvent 56 { 57 private const uint kHandledMask = 0x80000000; 58 private const uint kIdMask = 0x7FFFFFFF; 59 60 internal const int kBaseEventSize = NativeInputEvent.structSize; 61 62 /// <summary> 63 /// Default, invalid value for <see cref="eventId"/>. Upon being queued with 64 /// <see cref="InputSystem.QueueEvent"/>, no event will receive this ID. 65 /// </summary> 66 public const int InvalidEventId = 0; 67 68 internal const int kAlignment = 4; 69 70 [FieldOffset(0)] 71 private NativeInputEvent m_Event; 72 73 /// <summary> 74 /// Type code for the event. 75 /// </summary> 76 /// <remarks> 77 /// Each type of event has its own unique FourCC tag. For example, state events (see <see cref="StateEvent"/>) 78 /// are tagged with "STAT". The type tag for a specific type of event can be queried from its <c>Type</c> 79 /// property (for example, <see cref="StateEvent.Type"/>). 80 /// 81 /// To check whether an event has a specific type tag, you can use <see cref="InputEventPtr.IsA{T}"/>. 82 /// </remarks> 83 public FourCC type 84 { 85 get => new FourCC((int)m_Event.type); 86 set => m_Event.type = (NativeInputEventType)(int)value; 87 } 88 89 /// <summary> 90 /// Total size of the event in bytes. 91 /// </summary> 92 /// <value>Size of the event in bytes.</value> 93 /// <remarks> 94 /// Events are variable-size structs. This field denotes the total size of the event 95 /// as stored in memory. This includes the full size of this struct and not just the 96 /// "payload" of the event. 97 /// 98 /// <example> 99 /// <code> 100 /// // Store event in private buffer: 101 /// unsafe byte[] CopyEventData(InputEventPtr eventPtr) 102 /// { 103 /// var sizeInBytes = eventPtr.sizeInBytes; 104 /// var buffer = new byte[sizeInBytes]; 105 /// fixed (byte* bufferPtr = buffer) 106 /// { 107 /// UnsafeUtility.MemCpy(new IntPtr(bufferPtr), eventPtr.data, sizeInBytes); 108 /// } 109 /// return buffer; 110 /// } 111 /// </code> 112 /// </example> 113 /// 114 /// The maximum supported size of events is <c>ushort.MaxValue</c>, i.e. events cannot 115 /// be larger than 64KB. 116 /// </remarks> 117 /// <exception cref="ArgumentException"><paramref name="value"/> exceeds <c>ushort.MaxValue</c>.</exception> 118 public uint sizeInBytes 119 { 120 get => m_Event.sizeInBytes; 121 set 122 { 123 if (value > ushort.MaxValue) 124 throw new ArgumentException("Maximum event size is " + ushort.MaxValue, nameof(value)); 125 m_Event.sizeInBytes = (ushort)value; 126 } 127 } 128 129 /// <summary> 130 /// Unique serial ID of the event. 131 /// </summary> 132 /// <remarks> 133 /// Events are assigned running IDs when they are put on an event queue (see 134 /// <see cref="InputSystem.QueueEvent"/>). 135 /// </remarks> 136 /// <seealso cref="InvalidEventId"/> 137 public int eventId 138 { 139 get => (int)(m_Event.eventId & kIdMask); 140 set => m_Event.eventId = value | (int)(m_Event.eventId & ~kIdMask); 141 } 142 143 /// <summary> 144 /// ID of the device that the event is for. 145 /// </summary> 146 /// <remarks> 147 /// Device IDs are allocated by the <see cref="IInputRuntime">runtime</see>. No two devices 148 /// will receive the same ID over an application lifecycle regardless of whether the devices 149 /// existed at the same time or not. 150 /// </remarks> 151 /// <seealso cref="InputDevice.deviceId"/> 152 /// <seealso cref="InputSystem.GetDeviceById"/> 153 /// <seealso cref="InputDevice.InvalidDeviceId"/> 154 public int deviceId 155 { 156 get => m_Event.deviceId; 157 set => m_Event.deviceId = (ushort)value; 158 } 159 160 /// <summary> 161 /// Time that the event was generated at. 162 /// </summary> 163 /// <remarks> 164 /// Times are in seconds and progress linearly in real-time. The timeline is the 165 /// same as for <see cref="Time.realtimeSinceStartup"/>. 166 /// 167 /// Note that this implies that event times will reset in the editor every time you 168 /// go into play mode. In effect, this can result in events appearing with negative 169 /// timestamps (i.e. the event was generated before the current zero point for 170 /// <see cref="Time.realtimeSinceStartup"/>). 171 /// </remarks> 172 public double time 173 { 174 get => m_Event.time - InputRuntime.s_CurrentTimeOffsetToRealtimeSinceStartup; 175 set => m_Event.time = value + InputRuntime.s_CurrentTimeOffsetToRealtimeSinceStartup; 176 } 177 178 /// <summary> 179 /// This is the raw input timestamp without the offset to <see cref="Time.realtimeSinceStartup"/>. 180 /// </summary> 181 /// <remarks> 182 /// Internally, we always store all timestamps in "input time" which is relative to the native 183 /// function GetTimeSinceStartup(). <see cref="IInputRuntime.currentTime"/> yields the current 184 /// time on this timeline. 185 /// </remarks> 186 internal double internalTime 187 { 188 get => m_Event.time; 189 set => m_Event.time = value; 190 } 191 192 ////FIXME: this API isn't consistent; time seems to be internalTime whereas time property is external time 193 public InputEvent(FourCC type, int sizeInBytes, int deviceId, double time = -1) 194 { 195 if (time < 0) 196 time = InputRuntime.s_Instance.currentTime; 197 198 m_Event.type = (NativeInputEventType)(int)type; 199 m_Event.sizeInBytes = (ushort)sizeInBytes; 200 m_Event.deviceId = (ushort)deviceId; 201 m_Event.time = time; 202 m_Event.eventId = InvalidEventId; 203 } 204 205 // We internally use bits inside m_EventId as flags. IDs are linearly counted up by the 206 // native input system starting at 1 so we have plenty room. 207 // NOTE: The native system assigns IDs when events are queued so if our handled flag 208 // will implicitly get overwritten. Having events go back to unhandled state 209 // when they go on the queue makes sense in itself, though, so this is fine. 210 public bool handled 211 { 212 get => (m_Event.eventId & kHandledMask) == kHandledMask; 213 set 214 { 215 if (value) 216 m_Event.eventId = (int)(m_Event.eventId | kHandledMask); 217 else 218 m_Event.eventId = (int)(m_Event.eventId & ~kHandledMask); 219 } 220 } 221 222 public override string ToString() 223 { 224 return $"id={eventId} type={type} device={deviceId} size={sizeInBytes} time={time}"; 225 } 226 227 /// <summary> 228 /// Get the next event after the given one. 229 /// </summary> 230 /// <param name="currentPtr">A valid event pointer.</param> 231 /// <returns>Pointer to the next event in memory.</returns> 232 /// <remarks> 233 /// This method applies no checks and must only be called if there is an event following the 234 /// given one. Also, the size of the given event must be 100% as the method will simply 235 /// take the size and advance the given pointer by it (and aligning it to <see cref="kAlignment"/>). 236 /// </remarks> 237 /// <seealso cref="GetNextInMemoryChecked"/> 238 internal static unsafe InputEvent* GetNextInMemory(InputEvent* currentPtr) 239 { 240 Debug.Assert(currentPtr != null, "Event pointer must not be NULL"); 241 var alignedSizeInBytes = currentPtr->sizeInBytes.AlignToMultipleOf(kAlignment); 242 return (InputEvent*)((byte*)currentPtr + alignedSizeInBytes); 243 } 244 245 /// <summary> 246 /// Get the next event after the given one. Throw if that would point to invalid memory as indicated 247 /// by the given memory buffer. 248 /// </summary> 249 /// <param name="currentPtr">A valid event pointer to an event inside <paramref name="buffer"/>.</param> 250 /// <param name="buffer">Event buffer in which to advance to the next event.</param> 251 /// <returns>Pointer to the next event.</returns> 252 /// <exception cref="InvalidOperationException">There are no more events in the given buffer.</exception> 253 internal static unsafe InputEvent* GetNextInMemoryChecked(InputEvent* currentPtr, ref InputEventBuffer buffer) 254 { 255 Debug.Assert(currentPtr != null, "Event pointer must not be NULL"); 256 257 var alignedSizeInBytes = currentPtr->sizeInBytes.AlignToMultipleOf(kAlignment); 258 var nextPtr = (InputEvent*)((byte*)currentPtr + alignedSizeInBytes); 259 260 if (!buffer.Contains(nextPtr)) 261 throw new InvalidOperationException( 262 $"Event '{new InputEventPtr(currentPtr)}' is last event in given buffer with size {buffer.sizeInBytes}"); 263 264 return nextPtr; 265 } 266 267 public static unsafe bool Equals(InputEvent* first, InputEvent* second) 268 { 269 if (first == second) 270 return true; 271 if (first == null || second == null) 272 return false; 273 274 if (first->m_Event.sizeInBytes != second->m_Event.sizeInBytes) 275 return false; 276 277 return UnsafeUtility.MemCmp(first, second, first->m_Event.sizeInBytes) == 0; 278 } 279 } 280}