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;
}
}
}