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}