File size: 12,847 Bytes
18a519f
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
using System;
using System.Runtime.InteropServices;
using Unity.Collections.LowLevel.Unsafe;
using UnityEngine.InputSystem.Utilities;
using UnityEngineInternal.Input;

////REVIEW: can we get rid of the timestamp offsetting in the player and leave that complication for the editor only?

namespace UnityEngine.InputSystem.LowLevel
{
    /// <summary>
    /// A chunk of memory signaling a data transfer in the input system.
    /// </summary>
    /// <remarks>
    /// Input events are raw memory buffers akin to a byte array. For most uses of the input
    /// system, it is not necessary to be aware of the event stream in the background. Events
    /// are written to the internal event buffer by producers -- usually by the platform-specific
    /// backends sitting in the Unity runtime. Once per fixed or dynamic update (depending on
    /// what <see cref="InputSettings.updateMode"/> is set to), the input system then goes and
    /// flushes out the internal event buffer to process pending events.
    ///
    /// Events may signal general device-related occurrences (such as <see cref="DeviceConfigurationEvent"/>
    /// or <see cref="DeviceRemoveEvent"/>) or they may signal input activity. The latter kind of
    /// event is called "state events". In particular, these events are either <see cref="StateEvent"/>,
    /// only.
    ///
    /// Events are solely focused on input. To effect output on an input device (e.g. haptics
    /// effects), "commands" (see <see cref="InputDeviceCommand"/>) are used.
    ///
    /// Event processing can be listened to using <see cref="InputSystem.onEvent"/>. This callback
    /// will get triggered for each event as it is processed by the input system.
    ///
    /// Note that there is no "routing" mechanism for events, i.e. no mechanism by which the input
    /// system looks for a handler for a specific event. Instead, events represent low-level activity
    /// that the input system directly integrates into the state of its <see cref="InputDevice"/>
    /// instances.
    ///
    /// Each type of event is distinguished by its own <see cref="FourCC"/> type tag. The tag can
    /// be queried from the <see cref="type"/> property.
    ///
    /// Each event will receive a unique ID when queued to the internal event buffer. The ID can
    /// be queried using the <see cref="eventId"/> property. Over the lifetime of the input system,
    /// no two events will receive the same ID. If you repeatedly queue an event from the same
    /// memory buffer, each individual call of <see cref="InputSystem.QueueEvent"/> will result in
    /// its own unique event ID.
    ///
    /// All events are device-specific meaning that <see cref="deviceId"/> will always reference
    /// some device (which, however, may or may not translate to an <see cref="InputDevice"/>; that
    /// part depends on whether the input system was able to create an <see cref="InputDevice"/>
    /// based on the information received from the backend).
    /// </remarks>
    /// <seealso cref="InputEventPtr"/>
    // NOTE: This has to be layout compatible with native events.
    [StructLayout(LayoutKind.Explicit, Size = kBaseEventSize, Pack = 1)]
    public struct InputEvent
    {
        private const uint kHandledMask = 0x80000000;
        private const uint kIdMask = 0x7FFFFFFF;

        internal const int kBaseEventSize = NativeInputEvent.structSize;

        /// <summary>
        /// Default, invalid value for <see cref="eventId"/>. Upon being queued with
        /// <see cref="InputSystem.QueueEvent"/>, no event will receive this ID.
        /// </summary>
        public const int InvalidEventId = 0;

        internal const int kAlignment = 4;

        [FieldOffset(0)]
        private NativeInputEvent m_Event;

        /// <summary>
        /// Type code for the event.
        /// </summary>
        /// <remarks>
        /// Each type of event has its own unique FourCC tag. For example, state events (see <see cref="StateEvent"/>)
        /// are tagged with "STAT". The type tag for a specific type of event can be queried from its <c>Type</c>
        /// property (for example, <see cref="StateEvent.Type"/>).
        ///
        /// To check whether an event has a specific type tag, you can use <see cref="InputEventPtr.IsA{T}"/>.
        /// </remarks>
        public FourCC type
        {
            get => new FourCC((int)m_Event.type);
            set => m_Event.type = (NativeInputEventType)(int)value;
        }

        /// <summary>
        /// Total size of the event in bytes.
        /// </summary>
        /// <value>Size of the event in bytes.</value>
        /// <remarks>
        /// Events are variable-size structs. This field denotes the total size of the event
        /// as stored in memory. This includes the full size of this struct and not just the
        /// "payload" of the event.
        ///
        /// <example>
        /// <code>
        /// // Store event in private buffer:
        /// unsafe byte[] CopyEventData(InputEventPtr eventPtr)
        /// {
        ///     var sizeInBytes = eventPtr.sizeInBytes;
        ///     var buffer = new byte[sizeInBytes];
        ///     fixed (byte* bufferPtr = buffer)
        ///     {
        ///         UnsafeUtility.MemCpy(new IntPtr(bufferPtr), eventPtr.data, sizeInBytes);
        ///     }
        ///     return buffer;
        /// }
        /// </code>
        /// </example>
        ///
        /// The maximum supported size of events is <c>ushort.MaxValue</c>, i.e. events cannot
        /// be larger than 64KB.
        /// </remarks>
        /// <exception cref="ArgumentException"><paramref name="value"/> exceeds <c>ushort.MaxValue</c>.</exception>
        public uint sizeInBytes
        {
            get => m_Event.sizeInBytes;
            set
            {
                if (value > ushort.MaxValue)
                    throw new ArgumentException("Maximum event size is " + ushort.MaxValue, nameof(value));
                m_Event.sizeInBytes = (ushort)value;
            }
        }

        /// <summary>
        /// Unique serial ID of the event.
        /// </summary>
        /// <remarks>
        /// Events are assigned running IDs when they are put on an event queue (see
        /// <see cref="InputSystem.QueueEvent"/>).
        /// </remarks>
        /// <seealso cref="InvalidEventId"/>
        public int eventId
        {
            get => (int)(m_Event.eventId & kIdMask);
            set => m_Event.eventId = value | (int)(m_Event.eventId & ~kIdMask);
        }

        /// <summary>
        /// ID of the device that the event is for.
        /// </summary>
        /// <remarks>
        /// Device IDs are allocated by the <see cref="IInputRuntime">runtime</see>. No two devices
        /// will receive the same ID over an application lifecycle regardless of whether the devices
        /// existed at the same time or not.
        /// </remarks>
        /// <seealso cref="InputDevice.deviceId"/>
        /// <seealso cref="InputSystem.GetDeviceById"/>
        /// <seealso cref="InputDevice.InvalidDeviceId"/>
        public int deviceId
        {
            get => m_Event.deviceId;
            set => m_Event.deviceId = (ushort)value;
        }

        /// <summary>
        /// Time that the event was generated at.
        /// </summary>
        /// <remarks>
        /// Times are in seconds and progress linearly in real-time. The timeline is the
        /// same as for <see cref="Time.realtimeSinceStartup"/>.
        ///
        /// Note that this implies that event times will reset in the editor every time you
        /// go into play mode. In effect, this can result in events appearing with negative
        /// timestamps (i.e. the event was generated before the current zero point for
        /// <see cref="Time.realtimeSinceStartup"/>).
        /// </remarks>
        public double time
        {
            get => m_Event.time - InputRuntime.s_CurrentTimeOffsetToRealtimeSinceStartup;
            set => m_Event.time = value + InputRuntime.s_CurrentTimeOffsetToRealtimeSinceStartup;
        }

        /// <summary>
        /// This is the raw input timestamp without the offset to <see cref="Time.realtimeSinceStartup"/>.
        /// </summary>
        /// <remarks>
        /// Internally, we always store all timestamps in "input time" which is relative to the native
        /// function GetTimeSinceStartup(). <see cref="IInputRuntime.currentTime"/> yields the current
        /// time on this timeline.
        /// </remarks>
        internal double internalTime
        {
            get => m_Event.time;
            set => m_Event.time = value;
        }

        ////FIXME: this API isn't consistent; time seems to be internalTime whereas time property is external time
        public InputEvent(FourCC type, int sizeInBytes, int deviceId, double time = -1)
        {
            if (time < 0)
                time = InputRuntime.s_Instance.currentTime;

            m_Event.type = (NativeInputEventType)(int)type;
            m_Event.sizeInBytes = (ushort)sizeInBytes;
            m_Event.deviceId = (ushort)deviceId;
            m_Event.time = time;
            m_Event.eventId = InvalidEventId;
        }

        // We internally use bits inside m_EventId as flags. IDs are linearly counted up by the
        // native input system starting at 1 so we have plenty room.
        // NOTE: The native system assigns IDs when events are queued so if our handled flag
        //       will implicitly get overwritten. Having events go back to unhandled state
        //       when they go on the queue makes sense in itself, though, so this is fine.
        public bool handled
        {
            get => (m_Event.eventId & kHandledMask) == kHandledMask;
            set
            {
                if (value)
                    m_Event.eventId = (int)(m_Event.eventId | kHandledMask);
                else
                    m_Event.eventId = (int)(m_Event.eventId & ~kHandledMask);
            }
        }

        public override string ToString()
        {
            return $"id={eventId} type={type} device={deviceId} size={sizeInBytes} time={time}";
        }

        /// <summary>
        /// Get the next event after the given one.
        /// </summary>
        /// <param name="currentPtr">A valid event pointer.</param>
        /// <returns>Pointer to the next event in memory.</returns>
        /// <remarks>
        /// This method applies no checks and must only be called if there is an event following the
        /// given one. Also, the size of the given event must be 100% as the method will simply
        /// take the size and advance the given pointer by it (and aligning it to <see cref="kAlignment"/>).
        /// </remarks>
        /// <seealso cref="GetNextInMemoryChecked"/>
        internal static unsafe InputEvent* GetNextInMemory(InputEvent* currentPtr)
        {
            Debug.Assert(currentPtr != null, "Event pointer must not be NULL");
            var alignedSizeInBytes = currentPtr->sizeInBytes.AlignToMultipleOf(kAlignment);
            return (InputEvent*)((byte*)currentPtr + alignedSizeInBytes);
        }

        /// <summary>
        /// Get the next event after the given one. Throw if that would point to invalid memory as indicated
        /// by the given memory buffer.
        /// </summary>
        /// <param name="currentPtr">A valid event pointer to an event inside <paramref name="buffer"/>.</param>
        /// <param name="buffer">Event buffer in which to advance to the next event.</param>
        /// <returns>Pointer to the next event.</returns>
        /// <exception cref="InvalidOperationException">There are no more events in the given buffer.</exception>
        internal static unsafe InputEvent* GetNextInMemoryChecked(InputEvent* currentPtr, ref InputEventBuffer buffer)
        {
            Debug.Assert(currentPtr != null, "Event pointer must not be NULL");

            var alignedSizeInBytes = currentPtr->sizeInBytes.AlignToMultipleOf(kAlignment);
            var nextPtr = (InputEvent*)((byte*)currentPtr + alignedSizeInBytes);

            if (!buffer.Contains(nextPtr))
                throw new InvalidOperationException(
                    $"Event '{new InputEventPtr(currentPtr)}' is last event in given buffer with size {buffer.sizeInBytes}");

            return nextPtr;
        }

        public static unsafe bool Equals(InputEvent* first, InputEvent* second)
        {
            if (first == second)
                return true;
            if (first == null || second == null)
                return false;

            if (first->m_Event.sizeInBytes != second->m_Event.sizeInBytes)
                return false;

            return UnsafeUtility.MemCmp(first, second, first->m_Event.sizeInBytes) == 0;
        }
    }
}