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 { /// /// A chunk of memory signaling a data transfer in the input system. /// /// /// 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 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 /// or ) or they may signal input activity. The latter kind of /// event is called "state events". In particular, these events are either , /// only. /// /// Events are solely focused on input. To effect output on an input device (e.g. haptics /// effects), "commands" (see ) are used. /// /// Event processing can be listened to using . 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 /// instances. /// /// Each type of event is distinguished by its own type tag. The tag can /// be queried from the property. /// /// Each event will receive a unique ID when queued to the internal event buffer. The ID can /// be queried using the 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 will result in /// its own unique event ID. /// /// All events are device-specific meaning that will always reference /// some device (which, however, may or may not translate to an ; that /// part depends on whether the input system was able to create an /// based on the information received from the backend). /// /// // 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; /// /// Default, invalid value for . Upon being queued with /// , no event will receive this ID. /// public const int InvalidEventId = 0; internal const int kAlignment = 4; [FieldOffset(0)] private NativeInputEvent m_Event; /// /// Type code for the event. /// /// /// Each type of event has its own unique FourCC tag. For example, state events (see ) /// are tagged with "STAT". The type tag for a specific type of event can be queried from its Type /// property (for example, ). /// /// To check whether an event has a specific type tag, you can use . /// public FourCC type { get => new FourCC((int)m_Event.type); set => m_Event.type = (NativeInputEventType)(int)value; } /// /// Total size of the event in bytes. /// /// Size of the event in bytes. /// /// 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. /// /// /// /// // 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; /// } /// /// /// /// The maximum supported size of events is ushort.MaxValue, i.e. events cannot /// be larger than 64KB. /// /// exceeds ushort.MaxValue. 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; } } /// /// Unique serial ID of the event. /// /// /// Events are assigned running IDs when they are put on an event queue (see /// ). /// /// public int eventId { get => (int)(m_Event.eventId & kIdMask); set => m_Event.eventId = value | (int)(m_Event.eventId & ~kIdMask); } /// /// ID of the device that the event is for. /// /// /// Device IDs are allocated by the runtime. No two devices /// will receive the same ID over an application lifecycle regardless of whether the devices /// existed at the same time or not. /// /// /// /// public int deviceId { get => m_Event.deviceId; set => m_Event.deviceId = (ushort)value; } /// /// Time that the event was generated at. /// /// /// Times are in seconds and progress linearly in real-time. The timeline is the /// same as for . /// /// 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 /// ). /// public double time { get => m_Event.time - InputRuntime.s_CurrentTimeOffsetToRealtimeSinceStartup; set => m_Event.time = value + InputRuntime.s_CurrentTimeOffsetToRealtimeSinceStartup; } /// /// This is the raw input timestamp without the offset to . /// /// /// Internally, we always store all timestamps in "input time" which is relative to the native /// function GetTimeSinceStartup(). yields the current /// time on this timeline. /// 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}"; } /// /// Get the next event after the given one. /// /// A valid event pointer. /// Pointer to the next event in memory. /// /// 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 ). /// /// 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); } /// /// Get the next event after the given one. Throw if that would point to invalid memory as indicated /// by the given memory buffer. /// /// A valid event pointer to an event inside . /// Event buffer in which to advance to the next event. /// Pointer to the next event. /// There are no more events in the given buffer. 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; } } }