IndieGame/client/Packages/com.unity.inputsystem@1.7.0/InputSystem/Events/InputEvent.cs

281 lines
12 KiB
C#
Raw Normal View History

2024-10-11 10:12:15 +08:00
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;
}
}
}