This commit is contained in:
2025-01-17 13:10:42 +01:00
commit 4536213c91
15115 changed files with 1442174 additions and 0 deletions

View File

@@ -0,0 +1,143 @@
using System;
using System.Runtime.InteropServices;
using UnityEngine.InputSystem.Utilities;
////REVIEW: move this inside InputActionTrace?
namespace UnityEngine.InputSystem.LowLevel
{
/// <summary>
/// A variable-size event that captures the triggering of an action.
/// </summary>
/// <remarks>
/// Action events capture fully processed values only.
///
/// This struct is internal as the data it stores requires having access to <see cref="InputActionState"/>.
/// Public access is meant to go through <see cref="InputActionTrace"/> which provides a wrapper around
/// action events in the form of <see cref="InputActionTrace.ActionEventPtr"/>.
/// </remarks>
[StructLayout(LayoutKind.Explicit, Size = InputEvent.kBaseEventSize + 16 + 1)]
internal unsafe struct ActionEvent : IInputEventTypeInfo
{
public static FourCC Type => new FourCC('A', 'C', 'T', 'N');
////REVIEW: should we decouple this from InputEvent? we get deviceId which we don't really have a use for
[FieldOffset(0)] public InputEvent baseEvent;
[FieldOffset(InputEvent.kBaseEventSize + 0)] private ushort m_ControlIndex;
[FieldOffset(InputEvent.kBaseEventSize + 2)] private ushort m_BindingIndex;
[FieldOffset(InputEvent.kBaseEventSize + 4)] private ushort m_InteractionIndex;
[FieldOffset(InputEvent.kBaseEventSize + 6)] private byte m_StateIndex;
[FieldOffset(InputEvent.kBaseEventSize + 7)] private byte m_Phase;
[FieldOffset(InputEvent.kBaseEventSize + 8)] private double m_StartTime;
[FieldOffset(InputEvent.kBaseEventSize + 16)] public fixed byte m_ValueData[1]; // Variable-sized.
public double startTime
{
get => m_StartTime;
set => m_StartTime = value;
}
public InputActionPhase phase
{
get => (InputActionPhase)m_Phase;
set => m_Phase = (byte)value;
}
public byte* valueData
{
get
{
fixed(byte* data = m_ValueData)
{
return data;
}
}
}
public int valueSizeInBytes => (int)baseEvent.sizeInBytes - InputEvent.kBaseEventSize - 16;
public int stateIndex
{
get => m_StateIndex;
set
{
Debug.Assert(value >= 0 && value <= byte.MaxValue);
if (value < 0 || value > byte.MaxValue)
throw new NotSupportedException("State count cannot exceed byte.MaxValue");
m_StateIndex = (byte)value;
}
}
public int controlIndex
{
get => m_ControlIndex;
set
{
Debug.Assert(value >= 0 && value <= ushort.MaxValue);
if (value < 0 || value > ushort.MaxValue)
throw new NotSupportedException("Control count cannot exceed ushort.MaxValue");
m_ControlIndex = (ushort)value;
}
}
public int bindingIndex
{
get => m_BindingIndex;
set
{
Debug.Assert(value >= 0 && value <= ushort.MaxValue);
if (value < 0 || value > ushort.MaxValue)
throw new NotSupportedException("Binding count cannot exceed ushort.MaxValue");
m_BindingIndex = (ushort)value;
}
}
public int interactionIndex
{
get
{
if (m_InteractionIndex == ushort.MaxValue)
return InputActionState.kInvalidIndex;
return m_InteractionIndex;
}
set
{
Debug.Assert(value == InputActionState.kInvalidIndex || (value >= 0 && value < ushort.MaxValue));
if (value == InputActionState.kInvalidIndex)
m_InteractionIndex = ushort.MaxValue;
else
{
if (value < 0 || value >= ushort.MaxValue)
throw new NotSupportedException("Interaction count cannot exceed ushort.MaxValue-1");
m_InteractionIndex = (ushort)value;
}
}
}
public InputEventPtr ToEventPtr()
{
fixed(ActionEvent* ptr = &this)
{
return new InputEventPtr((InputEvent*)ptr);
}
}
public FourCC typeStatic => Type;
public static int GetEventSizeWithValueSize(int valueSizeInBytes)
{
return InputEvent.kBaseEventSize + 16 + valueSizeInBytes;
}
public static ActionEvent* From(InputEventPtr ptr)
{
if (!ptr.valid)
throw new ArgumentNullException(nameof(ptr));
if (!ptr.IsA<ActionEvent>())
throw new InvalidCastException($"Cannot cast event with type '{ptr.type}' into ActionEvent");
return (ActionEvent*)ptr.data;
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: d1166ae938ecb42adaf8c4530372fb2b
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,105 @@
using System;
using System.Runtime.InteropServices;
using Unity.Collections;
using Unity.Collections.LowLevel.Unsafe;
using UnityEngine.InputSystem.Utilities;
namespace UnityEngine.InputSystem.LowLevel
{
/// <summary>
/// Partial state update for an input device.
/// </summary>
/// <remarks>
/// Avoids having to send a full state memory snapshot when only a small
/// part of the state has changed.
/// </remarks>
[StructLayout(LayoutKind.Explicit, Pack = 1, Size = InputEvent.kBaseEventSize + 9)]
public unsafe struct DeltaStateEvent : IInputEventTypeInfo
{
public const int Type = 0x444C5441; // 'DLTA'
[FieldOffset(0)]
public InputEvent baseEvent;
[FieldOffset(InputEvent.kBaseEventSize)]
public FourCC stateFormat;
[FieldOffset(InputEvent.kBaseEventSize + 4)]
public uint stateOffset;
[FieldOffset(InputEvent.kBaseEventSize + 8)]
internal fixed byte stateData[1]; // Variable-sized.
public uint deltaStateSizeInBytes => baseEvent.sizeInBytes - (InputEvent.kBaseEventSize + 8);
public void* deltaState
{
get
{
fixed(byte* data = stateData)
{
return data;
}
}
}
public FourCC typeStatic => Type;
public InputEventPtr ToEventPtr()
{
fixed(DeltaStateEvent * ptr = &this)
{
return new InputEventPtr((InputEvent*)ptr);
}
}
public static DeltaStateEvent* From(InputEventPtr ptr)
{
if (!ptr.valid)
throw new ArgumentNullException(nameof(ptr));
if (!ptr.IsA<DeltaStateEvent>())
throw new InvalidCastException($"Cannot cast event with type '{ptr.type}' into DeltaStateEvent");
return FromUnchecked(ptr);
}
internal static DeltaStateEvent* FromUnchecked(InputEventPtr ptr)
{
return (DeltaStateEvent*)ptr.data;
}
public static NativeArray<byte> From(InputControl control, out InputEventPtr eventPtr, Allocator allocator = Allocator.Temp)
{
if (control == null)
throw new ArgumentNullException(nameof(control));
var device = control.device;
if (!device.added)
throw new ArgumentException($"Device for control '{control}' has not been added to system",
nameof(control));
ref var deviceStateBlock = ref device.m_StateBlock;
ref var controlStateBlock = ref control.m_StateBlock;
var stateFormat = deviceStateBlock.format; // The event is sent against the *device* so that's the state format we use.
var stateSize = 0u;
if (controlStateBlock.bitOffset != 0)
stateSize = (controlStateBlock.bitOffset + controlStateBlock.sizeInBits + 7) / 8;
else
stateSize = controlStateBlock.alignedSizeInBytes;
var stateOffset = controlStateBlock.byteOffset;
var statePtr = (byte*)control.currentStatePtr + (int)stateOffset;
var eventSize = InputEvent.kBaseEventSize + sizeof(int) * 2 + stateSize;
var buffer = new NativeArray<byte>((int)eventSize.AlignToMultipleOf(4), allocator);
var stateEventPtr = (DeltaStateEvent*)buffer.GetUnsafePtr();
stateEventPtr->baseEvent = new InputEvent(Type, (int)eventSize, device.deviceId, InputRuntime.s_Instance.currentTime);
stateEventPtr->stateFormat = stateFormat;
stateEventPtr->stateOffset = controlStateBlock.byteOffset - deviceStateBlock.byteOffset; // Make offset relative to device.
UnsafeUtility.MemCpy(stateEventPtr->deltaState, statePtr, stateSize);
eventPtr = stateEventPtr->ToEventPtr();
return buffer;
}
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: c632b886b4d04e6b856720556b399ffe
timeCreated: 1506762578

View File

@@ -0,0 +1,40 @@
using System.Runtime.InteropServices;
using UnityEngine.InputSystem.Utilities;
////REVIEW: should this have optional data that identifies *what* has changed?
namespace UnityEngine.InputSystem.LowLevel
{
/// <summary>
/// Indicates that the configuration of a device has changed.
/// </summary>
/// <seealso cref="InputSystem.QueueConfigChangeEvent"/>
/// <seealso cref="InputDevice.OnConfigurationChanged"/>
[StructLayout(LayoutKind.Explicit, Size = InputEvent.kBaseEventSize)]
public struct DeviceConfigurationEvent : IInputEventTypeInfo
{
public const int Type = 0x44434647;
[FieldOffset(0)]
public InputEvent baseEvent;
////REVIEW: have some kind of payload that allows indicating what changed in the config?
public FourCC typeStatic => Type;
public unsafe InputEventPtr ToEventPtr()
{
fixed(DeviceConfigurationEvent * ptr = &this)
{
return new InputEventPtr((InputEvent*)ptr);
}
}
public static DeviceConfigurationEvent Create(int deviceId, double time)
{
var inputEvent = new DeviceConfigurationEvent();
inputEvent.baseEvent = new InputEvent(Type, InputEvent.kBaseEventSize, deviceId, time);
return inputEvent;
}
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 4313a2cc2049411789348ff4c952d89c
timeCreated: 1512249379

View File

@@ -0,0 +1,41 @@
using System.Runtime.InteropServices;
using UnityEngine.InputSystem.Utilities;
namespace UnityEngine.InputSystem.LowLevel
{
/// <summary>
/// Notifies about the removal of an input device.
/// </summary>
/// <remarks>
/// Device that got removed is the one identified by <see cref="InputEvent.deviceId"/>
/// of <see cref="baseEvent"/>.
/// </remarks>
[StructLayout(LayoutKind.Explicit, Size = InputEvent.kBaseEventSize)]
public struct DeviceRemoveEvent : IInputEventTypeInfo
{
public const int Type = 0x4452454D;
/// <summary>
/// Common event data.
/// </summary>
[FieldOffset(0)]
public InputEvent baseEvent;
public FourCC typeStatic => Type;
public unsafe InputEventPtr ToEventPtr()
{
fixed(DeviceRemoveEvent * ptr = &this)
{
return new InputEventPtr((InputEvent*)ptr);
}
}
public static DeviceRemoveEvent Create(int deviceId, double time = -1)
{
var inputEvent =
new DeviceRemoveEvent {baseEvent = new InputEvent(Type, InputEvent.kBaseEventSize, deviceId, time)};
return inputEvent;
}
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 9af5cfc54cf34eb1ac27c9c90a9a7440
timeCreated: 1516846742

View File

@@ -0,0 +1,37 @@
using System.Runtime.InteropServices;
using UnityEngine.InputSystem.Utilities;
namespace UnityEngine.InputSystem.LowLevel
{
/// <summary>
/// Event that causes the state of an <see cref="InputDevice"/> to be reset (see <see cref="InputSystem.ResetDevice"/>).
/// </summary>
/// <seealso cref="InputSystem.ResetDevice"/>
[StructLayout(LayoutKind.Explicit, Size = InputEvent.kBaseEventSize)]
public struct DeviceResetEvent : IInputEventTypeInfo
{
public const int Type = 0x44525354; // DRST
/// <summary>
/// Common event data.
/// </summary>
[FieldOffset(0)]
public InputEvent baseEvent;
/// <summary>
/// Whether to also reset <see cref="Layouts.InputControlAttribute.dontReset"/> controls.
/// </summary>
[FieldOffset(InputDeviceCommand.kBaseCommandSize)]
public bool hardReset;
public FourCC typeStatic => Type;
public static DeviceResetEvent Create(int deviceId, bool hardReset = false, double time = -1)
{
var inputEvent =
new DeviceResetEvent {baseEvent = new InputEvent(Type, InputEvent.kBaseEventSize, deviceId, time)};
inputEvent.hardReset = hardReset;
return inputEvent;
}
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 7333721cfa334ecdb55d0cb1a3386292
timeCreated: 1628694001

View File

@@ -0,0 +1,15 @@
using UnityEngine.InputSystem.Utilities;
namespace UnityEngine.InputSystem.LowLevel
{
// Allows retrieving information about event types from an instance of the type.
// As structs can always be default instantiated, this allows us to get data on the struct
// from an instance of the struct without having to go through vtable dispatches.
/// <summary>
/// Interface implemented by all input event structs which reports the data format identifier of the command.
/// </summary>
public interface IInputEventTypeInfo
{
FourCC typeStatic { get; }
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 02a54f9a4f584f5397cf3e9697372394
timeCreated: 1506762438

View File

@@ -0,0 +1,146 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using UnityEngine.InputSystem.Utilities;
namespace UnityEngine.InputSystem.LowLevel
{
/// <summary>
/// A specialized event that contains the current IME Composition string, if IME is enabled and active.
/// This event contains the entire current string to date, and once a new composition is submitted will send a blank string event.
/// </summary>
[StructLayout(LayoutKind.Explicit, Size = InputEvent.kBaseEventSize + sizeof(int) + (sizeof(char) * kIMECharBufferSize))]
public struct IMECompositionEvent : IInputEventTypeInfo
{
// These needs to match the native ImeCompositionStringInputEventData settings
internal const int kIMECharBufferSize = 64;
public const int Type = 0x494D4553;
[FieldOffset(0)]
public InputEvent baseEvent;
[FieldOffset(InputEvent.kBaseEventSize)]
public IMECompositionString compositionString;
public FourCC typeStatic => Type;
public static IMECompositionEvent Create(int deviceId, string compositionString, double time)
{
var inputEvent = new IMECompositionEvent();
inputEvent.baseEvent = new InputEvent(Type, InputEvent.kBaseEventSize + sizeof(int) + (sizeof(char) * kIMECharBufferSize), deviceId, time);
inputEvent.compositionString = new IMECompositionString(compositionString);
return inputEvent;
}
}
/// <summary>
/// A struct representing an string of characters generated by an IME for text input.
/// </summary>
/// <remarks>
/// This is the internal representation of character strings in the event stream. It is exposed to user content through the
/// <see cref="ITextInputReceiver.OnIMECompositionChanged"/> method. It can easily be converted to a normal C# string using
/// <see cref="ToString"/>, but is exposed as the raw struct to avoid allocating memory by default.
/// </remarks>
[StructLayout(LayoutKind.Explicit, Size = sizeof(int) + sizeof(char) * LowLevel.IMECompositionEvent.kIMECharBufferSize)]
public unsafe struct IMECompositionString : IEnumerable<char>
{
internal struct Enumerator : IEnumerator<char>
{
IMECompositionString m_CompositionString;
char m_CurrentCharacter;
int m_CurrentIndex;
public Enumerator(IMECompositionString compositionString)
{
m_CompositionString = compositionString;
m_CurrentCharacter = '\0';
m_CurrentIndex = -1;
}
public bool MoveNext()
{
int size = m_CompositionString.Count;
m_CurrentIndex++;
if (m_CurrentIndex == size)
return false;
fixed(char* ptr = m_CompositionString.buffer)
{
m_CurrentCharacter = *(ptr + m_CurrentIndex);
}
return true;
}
public void Reset()
{
m_CurrentIndex = -1;
}
public void Dispose()
{
}
public char Current => m_CurrentCharacter;
object IEnumerator.Current => Current;
}
public int Count => size;
public char this[int index]
{
get
{
if (index >= Count || index < 0)
throw new ArgumentOutOfRangeException(nameof(index));
fixed(char* ptr = buffer)
{
return *(ptr + index);
}
}
}
[FieldOffset(0)]
int size;
[FieldOffset(sizeof(int))]
fixed char buffer[IMECompositionEvent.kIMECharBufferSize];
public IMECompositionString(string characters)
{
if (string.IsNullOrEmpty(characters))
{
size = 0;
return;
}
Debug.Assert(characters.Length < IMECompositionEvent.kIMECharBufferSize);
size = characters.Length;
for (var i = 0; i < size; i++)
buffer[i] = characters[i];
}
public override string ToString()
{
fixed(char* ptr = buffer)
{
return new string(ptr, 0, size);
}
}
public IEnumerator<char> GetEnumerator()
{
return new Enumerator(this);
}
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: bb39cf984021140a3adc2419327534a7
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,280 @@
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;
}
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 78260363757e4b02b9c6cc9949b4091a
timeCreated: 1506761570

View File

@@ -0,0 +1,424 @@
using System;
using System.Collections;
using System.Collections.Generic;
using Unity.Collections;
using Unity.Collections.LowLevel.Unsafe;
using UnityEngine.InputSystem.Utilities;
////TODO: batch append method
////TODO: switch to NativeArray long length (think we have it in Unity 2018.3)
////REVIEW: can we get rid of kBufferSizeUnknown and force size to always be known? (think this would have to wait until
//// the native changes have landed in 2018.3)
namespace UnityEngine.InputSystem.LowLevel
{
/// <summary>
/// A buffer of raw memory holding a sequence of <see cref="InputEvent">input events</see>.
/// </summary>
/// <remarks>
/// Note that event buffers are not thread-safe. It is not safe to write events to the buffer
/// concurrently from multiple threads. It is, however, safe to traverse the contents of an
/// existing buffer from multiple threads as long as it is not mutated at the same time.
/// </remarks>
public unsafe struct InputEventBuffer : IEnumerable<InputEventPtr>, IDisposable, ICloneable
{
public const long BufferSizeUnknown = -1;
/// <summary>
/// Total number of events in the buffer.
/// </summary>
/// <value>Number of events currently in the buffer.</value>
public int eventCount => m_EventCount;
/// <summary>
/// Size of the used portion of the buffer in bytes. Use <see cref="capacityInBytes"/> to
/// get the total allocated size.
/// </summary>
/// <value>Used size of buffer in bytes.</value>
/// <remarks>
/// If the size is not known, returns <see cref="BufferSizeUnknown"/>.
///
/// Note that the size does not usually correspond to <see cref="eventCount"/> times <c>sizeof(InputEvent)</c>.
/// as <see cref="InputEvent"/> instances are variable in size.
/// </remarks>
public long sizeInBytes => m_SizeInBytes;
/// <summary>
/// Total size of allocated memory in bytes. This value minus <see cref="sizeInBytes"/> is the
/// spare capacity of the buffer. Will never be less than <see cref="sizeInBytes"/>.
/// </summary>
/// <value>Size of allocated memory in bytes.</value>
/// <remarks>
/// A buffer's capacity determines how much event data can be written to the buffer before it has to be
/// reallocated.
/// </remarks>
public long capacityInBytes
{
get
{
if (!m_Buffer.IsCreated)
return 0;
return m_Buffer.Length;
}
}
/// <summary>
/// The raw underlying memory buffer.
/// </summary>
/// <value>Underlying buffer of unmanaged memory.</value>
public NativeArray<byte> data => m_Buffer;
/// <summary>
/// Pointer to the first event in the buffer.
/// </summary>
/// <value>Pointer to first event in buffer.</value>
public InputEventPtr bufferPtr
{
// When using ConvertExistingDataToNativeArray, the NativeArray isn't getting a "safety handle" (seems like a bug)
// and calling GetUnsafeReadOnlyPtr() will result in a NullReferenceException. Get the pointer without checks here.
get { return (InputEvent*)NativeArrayUnsafeUtility.GetUnsafeBufferPointerWithoutChecks(m_Buffer); }
}
/// <summary>
/// Construct an event buffer using the given memory block containing <see cref="InputEvent"/>s.
/// </summary>
/// <param name="eventPtr">A buffer containing <paramref name="eventCount"/> number of input events. The
/// individual events in the buffer are variable-sized (depending on the type of each event).</param>
/// <param name="eventCount">The number of events in <paramref name="eventPtr"/>. Can be zero.</param>
/// <param name="sizeInBytes">Total number of bytes of event data in the memory block pointed to by <paramref name="eventPtr"/>.
/// If -1 (default), the size of the actual event data in the buffer is considered unknown and has to be determined by walking
/// <paramref name="eventCount"/> number of events (due to the variable size of each event).</param>
/// <param name="capacityInBytes">The total size of the memory block allocated at <paramref name="eventPtr"/>. If this
/// is larger than <paramref name="sizeInBytes"/>, additional events can be appended to the buffer until the capacity
/// is exhausted. If this is -1 (default), the capacity is considered unknown and no additional events can be
/// appended to the buffer.</param>
/// <exception cref="ArgumentException"><paramref name="eventPtr"/> is <c>null</c> and <paramref name="eventCount"/> is not zero
/// -or- <paramref name="capacityInBytes"/> is less than <paramref name="sizeInBytes"/>.</exception>
public InputEventBuffer(InputEvent* eventPtr, int eventCount, int sizeInBytes = -1, int capacityInBytes = -1)
: this()
{
if (eventPtr == null && eventCount != 0)
throw new ArgumentException("eventPtr is NULL but eventCount is != 0", nameof(eventCount));
if (capacityInBytes != 0 && capacityInBytes < sizeInBytes)
throw new ArgumentException($"capacity({capacityInBytes}) cannot be smaller than size({sizeInBytes})",
nameof(capacityInBytes));
if (eventPtr != null)
{
if (capacityInBytes < 0)
capacityInBytes = sizeInBytes;
m_Buffer = NativeArrayUnsafeUtility.ConvertExistingDataToNativeArray<byte>(eventPtr,
capacityInBytes > 0 ? capacityInBytes : 0, Allocator.None);
m_SizeInBytes = sizeInBytes >= 0 ? sizeInBytes : BufferSizeUnknown;
m_EventCount = eventCount;
m_WeOwnTheBuffer = false;
}
}
/// <summary>
/// Construct an event buffer using the array containing <see cref="InputEvent"/>s.
/// </summary>
/// <param name="buffer">A native array containing <paramref name="eventCount"/> number of input events. The
/// individual events in the buffer are variable-sized (depending on the type of each event).</param>
/// <param name="eventCount">The number of events in <paramref name="buffer"/>. Can be zero.</param>
/// <param name="sizeInBytes">Total number of bytes of event data in the <paramref cref="buffer"/>.
/// If -1 (default), the size of the actual event data in <paramref name="buffer"/> is considered unknown and has to be determined by walking
/// <paramref name="eventCount"/> number of events (due to the variable size of each event).</param>
/// <param name="transferNativeArrayOwnership">If true, ownership of the <c>NativeArray</c> given by <paramref name="buffer"/> is
/// transferred to the <c>InputEventBuffer</c>. Calling <see cref="Dispose"/> will deallocate the array. Also, <see cref="AllocateEvent"/>
/// may re-allocate the array.</param>
/// <exception cref="ArgumentException"><paramref name="buffer"/> has no memory allocated but <paramref name="eventCount"/> is not zero.</exception>
/// <exception cref="ArgumentOutOfRangeException"><paramref name="sizeInBytes"/> is greater than the total length allocated for
/// <paramref name="buffer"/>.</exception>
public InputEventBuffer(NativeArray<byte> buffer, int eventCount, int sizeInBytes = -1, bool transferNativeArrayOwnership = false)
{
if (eventCount > 0 && !buffer.IsCreated)
throw new ArgumentException("buffer has no data but eventCount is > 0", nameof(eventCount));
if (sizeInBytes > buffer.Length)
throw new ArgumentOutOfRangeException(nameof(sizeInBytes));
m_Buffer = buffer;
m_WeOwnTheBuffer = transferNativeArrayOwnership;
m_SizeInBytes = sizeInBytes >= 0 ? sizeInBytes : buffer.Length;
m_EventCount = eventCount;
}
/// <summary>
/// Append a new event to the end of the buffer by copying the event from <paramref name="eventPtr"/>.
/// </summary>
/// <param name="eventPtr">Data of the event to store in the buffer. This will be copied in full as
/// per <see cref="InputEvent.sizeInBytes"/> found in the event's header.</param>
/// <param name="capacityIncrementInBytes">If the buffer needs to be reallocated to accommodate the event, number of
/// bytes to grow the buffer by.</param>
/// <param name="allocator">If the buffer needs to be reallocated to accommodate the event, the type of allocation to
/// use.</param>
/// <exception cref="ArgumentNullException"><paramref name="eventPtr"/> is <c>null</c>.</exception>
/// <remarks>
/// If the buffer's current capacity (see <see cref="capacityInBytes"/>) is smaller than <see cref="InputEvent.sizeInBytes"/>
/// of the given event, the buffer will be reallocated.
/// </remarks>
public void AppendEvent(InputEvent* eventPtr, int capacityIncrementInBytes = 2048, Allocator allocator = Allocator.Persistent)
{
if (eventPtr == null)
throw new ArgumentNullException(nameof(eventPtr));
// Allocate space.
var eventSizeInBytes = eventPtr->sizeInBytes;
var destinationPtr = AllocateEvent((int)eventSizeInBytes, capacityIncrementInBytes, allocator);
// Copy event.
UnsafeUtility.MemCpy(destinationPtr, eventPtr, eventSizeInBytes);
}
/// <summary>
/// Make space for an event of <paramref name="sizeInBytes"/> bytes and return a pointer to
/// the memory for the event.
/// </summary>
/// <param name="sizeInBytes">Number of bytes to make available for the event including the event header (see <see cref="InputEvent"/>).</param>
/// <param name="capacityIncrementInBytes">If the buffer needs to be reallocated to accommodate the event, number of
/// bytes to grow the buffer by.</param>
/// <param name="allocator">If the buffer needs to be reallocated to accommodate the event, the type of allocation to
/// use.</param>
/// <returns>A pointer to a block of memory in <see cref="bufferPtr"/>. Store the event data here.</returns>
/// <exception cref="ArgumentException"><paramref name="sizeInBytes"/> is less than the size needed for the
/// header of an <see cref="InputEvent"/>. Will automatically be aligned to a multiple of 4.</exception>
/// <remarks>
/// Only <see cref="InputEvent.sizeInBytes"/> is initialized by this method. No other fields from the event's
/// header are touched.
///
/// The event will be appended to the buffer after the last event currently in the buffer (if any).
/// </remarks>
public InputEvent* AllocateEvent(int sizeInBytes, int capacityIncrementInBytes = 2048, Allocator allocator = Allocator.Persistent)
{
if (sizeInBytes < InputEvent.kBaseEventSize)
throw new ArgumentException(
$"sizeInBytes must be >= sizeof(InputEvent) == {InputEvent.kBaseEventSize} (was {sizeInBytes})",
nameof(sizeInBytes));
var alignedSizeInBytes = sizeInBytes.AlignToMultipleOf(InputEvent.kAlignment);
// See if we need to enlarge our buffer.
var necessaryCapacity = m_SizeInBytes + alignedSizeInBytes;
var currentCapacity = capacityInBytes;
if (currentCapacity < necessaryCapacity)
{
// Yes, so reallocate.
var newCapacity = necessaryCapacity.AlignToMultipleOf(capacityIncrementInBytes);
if (newCapacity > int.MaxValue)
throw new NotImplementedException("NativeArray long support");
var newBuffer =
new NativeArray<byte>((int)newCapacity, allocator, NativeArrayOptions.ClearMemory);
if (m_Buffer.IsCreated)
{
UnsafeUtility.MemCpy(newBuffer.GetUnsafePtr(),
NativeArrayUnsafeUtility.GetUnsafeBufferPointerWithoutChecks(m_Buffer),
this.sizeInBytes);
if (m_WeOwnTheBuffer)
m_Buffer.Dispose();
}
m_Buffer = newBuffer;
m_WeOwnTheBuffer = true;
}
var eventPtr = (InputEvent*)((byte*)NativeArrayUnsafeUtility.GetUnsafeBufferPointerWithoutChecks(m_Buffer) + m_SizeInBytes);
eventPtr->sizeInBytes = (uint)sizeInBytes;
m_SizeInBytes += alignedSizeInBytes;
++m_EventCount;
return eventPtr;
}
/// <summary>
/// Whether the given event pointer refers to data within the event buffer.
/// </summary>
/// <param name="eventPtr"></param>
/// <returns></returns>
/// <remarks>
/// Note that this method does NOT check whether the given pointer points to an actual
/// event in the buffer. It solely performs a pointer out-of-bounds check.
///
/// Also note that if the size of the memory buffer is unknown (<see cref="BufferSizeUnknown"/>,
/// only a lower-bounds check is performed.
/// </remarks>
public bool Contains(InputEvent* eventPtr)
{
if (eventPtr == null)
return false;
if (sizeInBytes == 0)
return false;
var bufferPtr = NativeArrayUnsafeUtility.GetUnsafeBufferPointerWithoutChecks(data);
if (eventPtr < bufferPtr)
return false;
if (sizeInBytes != BufferSizeUnknown && eventPtr >= (byte*)bufferPtr + sizeInBytes)
return false;
return true;
}
public void Reset()
{
m_EventCount = 0;
if (m_SizeInBytes != BufferSizeUnknown)
m_SizeInBytes = 0;
}
/// <summary>
/// Advance the read position to the next event in the buffer, preserving or not preserving the
/// current event depending on <paramref name="leaveEventInBuffer"/>.
/// </summary>
/// <param name="currentReadPos"></param>
/// <param name="currentWritePos"></param>
/// <param name="numEventsRetainedInBuffer"></param>
/// <param name="numRemainingEvents"></param>
/// <param name="leaveEventInBuffer"></param>
/// <remarks>
/// This method MUST ONLY BE CALLED if the current event has been fully processed. If the at <paramref name="currentWritePos"/>
/// is smaller than the current event, then this method will OVERWRITE parts or all of the current event.
/// </remarks>
internal void AdvanceToNextEvent(ref InputEvent* currentReadPos,
ref InputEvent* currentWritePos, ref int numEventsRetainedInBuffer,
ref int numRemainingEvents, bool leaveEventInBuffer)
{
Debug.Assert(currentReadPos >= currentWritePos, "Current write position is beyond read position");
// Get new read position *before* potentially moving the current event so that we don't
// end up overwriting the data we need to find the next event in memory.
var newReadPos = currentReadPos;
if (numRemainingEvents > 1)
{
// Don't perform safety check in non-debug builds.
#if UNITY_EDITOR || DEVELOPMENT_BUILD
newReadPos = InputEvent.GetNextInMemoryChecked(currentReadPos, ref this);
#else
newReadPos = InputEvent.GetNextInMemory(currentReadPos);
#endif
}
// If the current event should be left in the buffer, advance write position.
if (leaveEventInBuffer)
{
Debug.Assert(Contains(currentWritePos), "Current write position should be contained in buffer");
// Move down in buffer if read and write pos have deviated from each other.
var numBytes = currentReadPos->sizeInBytes;
if (currentReadPos != currentWritePos)
UnsafeUtility.MemMove(currentWritePos, currentReadPos, numBytes);
currentWritePos = (InputEvent*)((byte*)currentWritePos + numBytes.AlignToMultipleOf(4));
++numEventsRetainedInBuffer;
}
currentReadPos = newReadPos;
--numRemainingEvents;
}
public IEnumerator<InputEventPtr> GetEnumerator()
{
return new Enumerator(this);
}
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
public void Dispose()
{
// Nothing to do if we don't actually own the memory.
if (!m_WeOwnTheBuffer)
return;
Debug.Assert(m_Buffer.IsCreated, "Buffer has not been created");
m_Buffer.Dispose();
m_WeOwnTheBuffer = false;
m_SizeInBytes = 0;
m_EventCount = 0;
}
public InputEventBuffer Clone()
{
var clone = new InputEventBuffer();
if (m_Buffer.IsCreated)
{
clone.m_Buffer = new NativeArray<byte>(m_Buffer.Length, Allocator.Persistent);
clone.m_Buffer.CopyFrom(m_Buffer);
clone.m_WeOwnTheBuffer = true;
}
clone.m_SizeInBytes = m_SizeInBytes;
clone.m_EventCount = m_EventCount;
return clone;
}
object ICloneable.Clone()
{
return Clone();
}
private NativeArray<byte> m_Buffer;
private long m_SizeInBytes;
private int m_EventCount;
private bool m_WeOwnTheBuffer; ////FIXME: what we really want is access to NativeArray's allocator label
private struct Enumerator : IEnumerator<InputEventPtr>
{
private readonly InputEvent* m_Buffer;
private readonly int m_EventCount;
private InputEvent* m_CurrentEvent;
private int m_CurrentIndex;
public Enumerator(InputEventBuffer buffer)
{
m_Buffer = buffer.bufferPtr;
m_EventCount = buffer.m_EventCount;
m_CurrentEvent = null;
m_CurrentIndex = 0;
}
public bool MoveNext()
{
if (m_CurrentIndex == m_EventCount)
return false;
if (m_CurrentEvent == null)
{
m_CurrentEvent = m_Buffer;
return m_CurrentEvent != null;
}
Debug.Assert(m_CurrentEvent != null, "Current event must not be null");
++m_CurrentIndex;
if (m_CurrentIndex == m_EventCount)
return false;
m_CurrentEvent = InputEvent.GetNextInMemory(m_CurrentEvent);
return true;
}
public void Reset()
{
m_CurrentEvent = null;
m_CurrentIndex = 0;
}
public void Dispose()
{
}
public InputEventPtr Current => m_CurrentEvent;
object IEnumerator.Current => Current;
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: fa7997ba71d7b476799fab3d063b7e6c
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,150 @@
using System;
using UnityEngine.InputSystem.Utilities;
#pragma warning disable CA2225
namespace UnityEngine.InputSystem.LowLevel
{
/// <summary>
/// Wraps around mechanisms for listening in on the <see cref="InputEvent"/> stream made
/// available through <see cref="InputSystem.onEvent"/>.
/// </summary>
/// <remarks>
/// This struct can be used to add (<see cref="op_Addition"/>) or remove (<see cref="op_Subtraction"/>)
/// callbacks directly to/from the event pipeline.
///
/// Alternatively, it can be used as an <c>IObservable</c> to <see cref="Subscribe"/> observers to
/// the event stream. See <see cref="Observable"/> for extension methods to set up various observer
/// mechanisms.
///
/// <example>
/// <code>
/// InputSystem.onEvent
/// .ForDevice(Mouse.current)
/// .Call(evt =>
/// {
/// foreach (var control in evt.EnumerateChangedControls())
/// Debug.Log($"Control {control} on mouse has changed value");
/// });
/// </code>
/// </example>
/// </remarks>
/// <seealso cref="InputSystem.onEvent"/>
public struct InputEventListener : IObservable<InputEventPtr>
{
internal static ObserverState s_ObserverState;
/// <summary>
/// Add a delegate to be called for each <see cref="InputEvent"/> that is processed by the Input System.
/// </summary>
/// <param name="_"></param>
/// <param name="callback">A callback to call for each event.</param>
/// <returns>The same listener instance.</returns>
/// <exception cref="ArgumentNullException"><paramref name="callback"/> is <c>null</c>.</exception>
/// <remarks>
/// <example>
/// <code>
/// InputSystem.onEvent +=
/// (eventPtr, device) =>
/// {
/// Debug.Log($"Event for {device}");
/// };
/// </code>
/// </example>
/// </remarks>
/// <seealso cref="InputSystem.onEvent"/>
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA1801:ReviewUnusedParameters", MessageId = "_", Justification = "Keep this for future implementation")]
public static InputEventListener operator+(InputEventListener _, Action<InputEventPtr, InputDevice> callback)
{
if (callback == null)
throw new ArgumentNullException(nameof(callback));
lock (InputSystem.s_Manager)
InputSystem.s_Manager.onEvent += callback;
return default;
}
/// <summary>
/// Remove a delegate from <see cref="InputEvent"/>.
/// </summary>
/// <param name="_"></param>
/// <param name="callback">A callback that was previously installed on <see cref="InputSystem.onEvent"/>.</param>
/// <returns>The same listener instance.</returns>
/// <exception cref="ArgumentNullException"><paramref name="callback"/> is <c>null</c>.</exception>
/// <remarks>
/// <example>
/// <code>
/// InputSystem.onEvent -= myDelegate;
/// </code>
/// </example>
/// </remarks>
/// <seealso cref="InputSystem.onEvent"/>
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA1801:ReviewUnusedParameters", MessageId = "_", Justification = "Keep this for future implementation")]
public static InputEventListener operator-(InputEventListener _, Action<InputEventPtr, InputDevice> callback)
{
if (callback == null)
throw new ArgumentNullException(nameof(callback));
lock (InputSystem.s_Manager)
InputSystem.s_Manager.onEvent -= callback;
return default;
}
/// <summary>
/// Subscribe an observer to the event pump.
/// </summary>
/// <param name="observer">Observer to be notified for each event.</param>
/// <returns>A handle to dispose of the subscription.</returns>
/// <remarks>
/// The easiest way to subscribe is via the extension methods in <see cref="Observable"/>.
/// <example>
/// <code>
/// // Subscribe.
/// var subscription = InputSystem.onEvent.Call(e => Debug.Log("Event"));
///
/// // Unsubscribe.
/// subscription.Dispose();
/// </code>
/// </example>
/// </remarks>
public IDisposable Subscribe(IObserver<InputEventPtr> observer)
{
if (s_ObserverState == null)
s_ObserverState = new ObserverState();
if (s_ObserverState.observers.length == 0)
InputSystem.s_Manager.onEvent += s_ObserverState.onEventDelegate;
s_ObserverState.observers.AppendWithCapacity(observer);
return new DisposableObserver { observer = observer };
}
internal class ObserverState
{
public InlinedArray<IObserver<InputEventPtr>> observers;
public Action<InputEventPtr, InputDevice> onEventDelegate;
public ObserverState()
{
onEventDelegate =
(eventPtr, device) =>
{
for (var i = observers.length - 1; i >= 0; --i)
observers[i].OnNext(eventPtr);
};
}
}
private class DisposableObserver : IDisposable
{
public IObserver<InputEventPtr> observer;
public void Dispose()
{
var index = s_ObserverState.observers.IndexOfReference(observer);
if (index >= 0)
s_ObserverState.observers.RemoveAtWithCapacity(index);
if (s_ObserverState.observers.length == 0)
InputSystem.s_Manager.onEvent -= s_ObserverState.onEventDelegate;
}
}
}
}
#pragma warning restore CA2225

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 07a49c9088e54f1387f3bb8cbbe73e01
timeCreated: 1627651217

View File

@@ -0,0 +1,273 @@
using System;
using UnityEngine.InputSystem.Utilities;
////REVIEW: nuke this and force raw pointers on all code using events?
namespace UnityEngine.InputSystem.LowLevel
{
/// <summary>
/// Pointer to an <see cref="InputEvent"/>. Makes it easier to work with InputEvents and hides
/// the unsafe operations necessary to work with them.
/// </summary>
/// <remarks>
/// Note that event pointers generally refer to event buffers that are continually reused. This means
/// that event pointers should not be held on to. Instead, to hold onto event data, manually copy
/// an event to a buffer.
/// </remarks>
public unsafe struct InputEventPtr : IEquatable<InputEventPtr>
{
// C# does not allow us to have pointers to structs that have managed data members. Since
// this can't be guaranteed for generic type parameters, they can't be used with pointers.
// This is why we cannot make InputEventPtr generic or have a generic method that returns
// a pointer to a specific type of event.
private readonly InputEvent* m_EventPtr;
/// <summary>
/// Initialize the pointer to refer to the given event.
/// </summary>
/// <param name="eventPtr">Pointer to an event. Can be <c>null</c>.</param>
public InputEventPtr(InputEvent* eventPtr)
{
m_EventPtr = eventPtr;
}
/// <summary>
/// Whether the pointer is not <c>null</c>.
/// </summary>
/// <value>True if the struct refers to an event.</value>
public bool valid => m_EventPtr != null;
/// <summary>
/// Whether the event is considered "handled" and should not be processed further.
/// </summary>
/// <remarks>
/// This is used in two ways. Setting it from inside <see cref="InputSystem.onEvent"/> will
/// cause the event to not be processed further. If it is a <see cref="StateEvent"/> or
/// <see cref="DeltaStateEvent"/>, the <see cref="InputDevice"/> targeted by the event will
/// not receive the state change.
///
/// Setting this flag from inside a state change monitor (see <see cref="InputState.AddChangeMonitor(InputControl,IInputStateChangeMonitor,long,uint)"/>)
/// will prevent other monitors on the same <see cref="IInputStateChangeMonitor"/> not receiving the state change.
/// </remarks>
/// <exception cref="InvalidOperationException">The event pointer instance is not <see cref="valid"/>.</exception>
public bool handled
{
get
{
if (!valid)
return false;
return m_EventPtr->handled;
}
set
{
if (!valid)
throw new InvalidOperationException("The InputEventPtr is not valid.");
m_EventPtr->handled = value;
}
}
public int id
{
get
{
if (!valid)
return 0;
return m_EventPtr->eventId;
}
set
{
if (!valid)
throw new InvalidOperationException("The InputEventPtr is not valid.");
m_EventPtr->eventId = value;
}
}
public FourCC type
{
get
{
if (!valid)
return new FourCC();
return m_EventPtr->type;
}
}
public uint sizeInBytes
{
get
{
if (!valid)
return 0;
return m_EventPtr->sizeInBytes;
}
}
public int deviceId
{
get
{
if (!valid)
return InputDevice.InvalidDeviceId;
return m_EventPtr->deviceId;
}
set
{
if (!valid)
throw new InvalidOperationException("The InputEventPtr is not valid.");
m_EventPtr->deviceId = value;
}
}
public double time
{
get => valid ? m_EventPtr->time : 0.0;
set
{
if (!valid)
throw new InvalidOperationException("The InputEventPtr is not valid.");
m_EventPtr->time = value;
}
}
internal double internalTime
{
get => valid ? m_EventPtr->internalTime : 0.0;
set
{
if (!valid)
throw new InvalidOperationException("The InputEventPtr is not valid.");
m_EventPtr->internalTime = value;
}
}
public InputEvent* data => m_EventPtr;
// The stateFormat, stateSizeInBytes, and stateOffset properties are very
// useful for debugging.
internal FourCC stateFormat
{
get
{
var eventType = type;
if (eventType == StateEvent.Type)
return StateEvent.FromUnchecked(this)->stateFormat;
if (eventType == DeltaStateEvent.Type)
return DeltaStateEvent.FromUnchecked(this)->stateFormat;
throw new InvalidOperationException("Event must be a StateEvent or DeltaStateEvent but is " + this);
}
}
internal uint stateSizeInBytes
{
get
{
if (IsA<StateEvent>())
return StateEvent.From(this)->stateSizeInBytes;
if (IsA<DeltaStateEvent>())
return DeltaStateEvent.From(this)->deltaStateSizeInBytes;
throw new InvalidOperationException("Event must be a StateEvent or DeltaStateEvent but is " + this);
}
}
internal uint stateOffset
{
get
{
if (IsA<DeltaStateEvent>())
return DeltaStateEvent.From(this)->stateOffset;
throw new InvalidOperationException("Event must be a DeltaStateEvent but is " + this);
}
}
public bool IsA<TOtherEvent>()
where TOtherEvent : struct, IInputEventTypeInfo
{
if (m_EventPtr == null)
return false;
// NOTE: Important to say `default` instead of `new TOtherEvent()` here. The latter will result in a call to
// `Activator.CreateInstance` on Mono and thus allocate GC memory.
TOtherEvent otherEvent = default;
return m_EventPtr->type == otherEvent.typeStatic;
}
// NOTE: It is your responsibility to know *if* there actually another event following this one in memory.
public InputEventPtr Next()
{
if (!valid)
return new InputEventPtr();
return new InputEventPtr(InputEvent.GetNextInMemory(m_EventPtr));
}
public override string ToString()
{
if (!valid)
return "null";
// il2cpp has a bug which makes builds fail if this is written as 'return m_EventPtr->ToString()'.
// Gives an error about "trying to constrain an invalid type".
// Writing it as a two-step operation like here makes it build cleanly.
var eventPtr = *m_EventPtr;
return eventPtr.ToString();
}
/// <summary>
/// Return the plain pointer wrapped around by the struct.
/// </summary>
/// <returns>A plain pointer. Can be <c>null</c>.</returns>
public InputEvent* ToPointer()
{
return this;
}
public bool Equals(InputEventPtr other)
{
return m_EventPtr == other.m_EventPtr || InputEvent.Equals(m_EventPtr, other.m_EventPtr);
}
public override bool Equals(object obj)
{
if (ReferenceEquals(null, obj))
return false;
return obj is InputEventPtr ptr && Equals(ptr);
}
public override int GetHashCode()
{
return unchecked((int)(long)m_EventPtr);
}
public static bool operator==(InputEventPtr left, InputEventPtr right)
{
return left.m_EventPtr == right.m_EventPtr;
}
public static bool operator!=(InputEventPtr left, InputEventPtr right)
{
return left.m_EventPtr != right.m_EventPtr;
}
public static implicit operator InputEventPtr(InputEvent* eventPtr)
{
return new InputEventPtr(eventPtr);
}
public static InputEventPtr From(InputEvent* eventPtr)
{
return new InputEventPtr(eventPtr);
}
public static implicit operator InputEvent*(InputEventPtr eventPtr)
{
return eventPtr.data;
}
// Make annoying Microsoft code analyzer happy.
public static InputEvent* FromInputEventPtr(InputEventPtr eventPtr)
{
return eventPtr.data;
}
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: caf772fd9b5b4840b7b0014d0616290d
timeCreated: 1507682338

View File

@@ -0,0 +1,181 @@
using Unity.Collections;
using Unity.Collections.LowLevel.Unsafe;
namespace UnityEngine.InputSystem.LowLevel
{
/// <summary>
/// The input event stream is a combination of the input event buffer passed from native code and an
/// append buffer that is owned by the managed side. Events queued during update are added to the
/// append buffer. To calling code, the two buffers look like a single coherent stream of events.
/// Calling Advance will first step through the events from the native side, followed by any events
/// that have been appended.
/// </summary>
internal unsafe struct InputEventStream
{
public bool isOpen => m_IsOpen;
public int remainingEventCount => m_RemainingNativeEventCount + m_RemainingAppendEventCount;
/// <summary>
/// How many events were left in the native buffer during reading.
/// </summary>
public int numEventsRetainedInBuffer => m_NumEventsRetainedInBuffer;
public InputEvent* currentEventPtr => m_RemainingNativeEventCount > 0
? m_CurrentNativeEventReadPtr
: (m_RemainingAppendEventCount > 0 ? m_CurrentAppendEventReadPtr : null);
public uint numBytesRetainedInBuffer =>
(uint)((byte*)m_CurrentNativeEventWritePtr -
(byte*)NativeArrayUnsafeUtility
.GetUnsafeBufferPointerWithoutChecks(m_NativeBuffer.data));
public InputEventStream(ref InputEventBuffer eventBuffer, int maxAppendedEvents)
{
m_CurrentNativeEventWritePtr = m_CurrentNativeEventReadPtr =
(InputEvent*)NativeArrayUnsafeUtility.GetUnsafeBufferPointerWithoutChecks(eventBuffer.data);
m_NativeBuffer = eventBuffer;
m_RemainingNativeEventCount = m_NativeBuffer.eventCount;
m_NumEventsRetainedInBuffer = 0;
m_CurrentAppendEventReadPtr = m_CurrentAppendEventWritePtr = default;
m_AppendBuffer = default;
m_RemainingAppendEventCount = 0;
m_MaxAppendedEvents = maxAppendedEvents;
m_IsOpen = true;
}
public void Close(ref InputEventBuffer eventBuffer)
{
// If we have retained events, update event count and buffer size. If not, just reset.
if (m_NumEventsRetainedInBuffer > 0)
{
var bufferPtr = NativeArrayUnsafeUtility.GetUnsafeBufferPointerWithoutChecks(m_NativeBuffer.data);
Debug.Assert((byte*)m_CurrentNativeEventWritePtr > (byte*)bufferPtr);
var newBufferSize = (byte*)m_CurrentNativeEventWritePtr - (byte*)bufferPtr;
m_NativeBuffer = new InputEventBuffer((InputEvent*)bufferPtr, m_NumEventsRetainedInBuffer, (int)newBufferSize,
(int)m_NativeBuffer.capacityInBytes);
}
else
{
m_NativeBuffer.Reset();
}
if (m_AppendBuffer.data.IsCreated)
m_AppendBuffer.Dispose();
eventBuffer = m_NativeBuffer;
m_IsOpen = false;
}
public void CleanUpAfterException()
{
if (!isOpen)
return;
m_NativeBuffer.Reset();
if (m_AppendBuffer.data.IsCreated)
m_AppendBuffer.Dispose();
m_IsOpen = false;
}
public void Write(InputEvent* eventPtr)
{
if (m_AppendBuffer.eventCount >= m_MaxAppendedEvents)
{
Debug.LogError($"Maximum number of queued events exceeded. Set the '{nameof(InputSettings.maxQueuedEventsPerUpdate)}' " +
$"setting to a higher value if you need to queue more events than this. " +
$"Current limit is '{m_MaxAppendedEvents}'.");
return;
}
var wasAlreadyCreated = m_AppendBuffer.data.IsCreated;
var oldBufferPtr = (byte*)m_AppendBuffer.bufferPtr.data;
m_AppendBuffer.AppendEvent(eventPtr, allocator: Allocator.Temp);
if (!wasAlreadyCreated)
{
m_CurrentAppendEventWritePtr = m_CurrentAppendEventReadPtr =
(InputEvent*)NativeArrayUnsafeUtility.GetUnsafeBufferPointerWithoutChecks(m_AppendBuffer.data);
}
else
{
// AppendEvent can reallocate the buffer if it needs more space, so make sure the read and write pointers
// point to the equivalent places in the new buffer.
var newBufferPtr = (byte*)m_AppendBuffer.bufferPtr.data;
if (oldBufferPtr != newBufferPtr)
{
var currentWriteOffset = (byte*)m_CurrentAppendEventWritePtr - oldBufferPtr;
var currentReadOffset = (byte*)m_CurrentAppendEventReadPtr - oldBufferPtr;
m_CurrentAppendEventWritePtr = (InputEvent*)(newBufferPtr + currentWriteOffset);
m_CurrentAppendEventReadPtr = (InputEvent*)(newBufferPtr + currentReadOffset);
}
}
m_RemainingAppendEventCount++;
}
public InputEvent* Advance(bool leaveEventInBuffer)
{
if (m_RemainingNativeEventCount > 0)
{
m_NativeBuffer.AdvanceToNextEvent(ref m_CurrentNativeEventReadPtr, ref m_CurrentNativeEventWritePtr,
ref m_NumEventsRetainedInBuffer, ref m_RemainingNativeEventCount, leaveEventInBuffer);
}
else if (m_RemainingAppendEventCount > 0)
{
var numEventRetained = 0;
m_AppendBuffer.AdvanceToNextEvent(ref m_CurrentAppendEventReadPtr, ref m_CurrentAppendEventWritePtr,
ref numEventRetained, ref m_RemainingAppendEventCount, false);
}
return currentEventPtr;
}
/// <summary>
/// Peeks next event in the stream
/// </summary>
public InputEvent* Peek()
{
// Advance will go to next event in m_NativeBuffer
if (m_RemainingNativeEventCount > 1)
return InputEvent.GetNextInMemory(m_CurrentNativeEventReadPtr);
// Advance will decrement m_RemainingNativeEventCount to 0
// and currentEventPtr will point to m_CurrentAppendEventReadPtr if any
if (m_RemainingNativeEventCount == 1)
return m_RemainingAppendEventCount > 0 ? m_CurrentAppendEventReadPtr : null;
// Advance will go to next event in m_AppendBuffer
if (m_RemainingAppendEventCount > 1)
return InputEvent.GetNextInMemory(m_CurrentAppendEventReadPtr);
// No next event
return null;
}
private InputEventBuffer m_NativeBuffer;
private InputEvent* m_CurrentNativeEventReadPtr;
private InputEvent* m_CurrentNativeEventWritePtr;
private int m_RemainingNativeEventCount;
private readonly int m_MaxAppendedEvents;
// During Update, new events that are queued will be added to the append buffer
private InputEventBuffer m_AppendBuffer;
private InputEvent* m_CurrentAppendEventReadPtr;
private InputEvent* m_CurrentAppendEventWritePtr;
private int m_RemainingAppendEventCount;
// When timeslicing events or in before-render updates, we may be leaving events in the buffer
// for later processing. We do this by compacting the event buffer and moving events down such
// that the events we leave in the buffer form one contiguous chunk of memory at the beginning
// of the buffer.
private int m_NumEventsRetainedInBuffer;
private bool m_IsOpen;
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 8ccfc0b2d0da54246a3a9a3938b52fa4
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 5b23e3210ca74ea9ae4fe882b9bce278
timeCreated: 1507691163

View File

@@ -0,0 +1,197 @@
using System;
using System.Runtime.InteropServices;
using Unity.Collections;
using UnityEngine.InputSystem.Utilities;
using Unity.Collections.LowLevel.Unsafe;
namespace UnityEngine.InputSystem.LowLevel
{
/// <summary>
/// A complete state snapshot for an entire input device.
/// </summary>
/// <remarks>
/// This is a variable-sized event.
/// </remarks>
[StructLayout(LayoutKind.Explicit, Size = InputEvent.kBaseEventSize + 4 + kStateDataSizeToSubtract, Pack = 1)]
public unsafe struct StateEvent : IInputEventTypeInfo
{
public const int Type = 0x53544154; // 'STAT'
internal const int kStateDataSizeToSubtract = 1;
[FieldOffset(0)]
public InputEvent baseEvent;
/// <summary>
/// Type code for the state stored in the event.
/// </summary>
[FieldOffset(InputEvent.kBaseEventSize)]
public FourCC stateFormat;
[FieldOffset(InputEvent.kBaseEventSize + sizeof(int))]
internal fixed byte stateData[kStateDataSizeToSubtract]; // Variable-sized.
public uint stateSizeInBytes => baseEvent.sizeInBytes - (InputEvent.kBaseEventSize + sizeof(int));
public void* state
{
get
{
fixed(byte* data = stateData)
{
return data;
}
}
}
public InputEventPtr ToEventPtr()
{
fixed(StateEvent * ptr = &this)
{
return new InputEventPtr((InputEvent*)ptr);
}
}
public FourCC typeStatic => Type;
/// <summary>
/// Retrieve the state stored in the event.
/// </summary>
/// <typeparam name="TState">Type of state expected to be stored in the event. <see cref="IInputStateTypeInfo.format"/>
/// must match <see cref="stateFormat"/>.</typeparam>
/// <returns>Copy of the state stored in the event.</returns>
/// <exception cref="InvalidOperationException"><see cref="stateFormat"/> does not match <see cref="IInputStateTypeInfo.format"/>
/// of <typeparamref name="TState"/>.</exception>
/// <remarks>
/// The event may contain less or more data than what is found in the struct. Only the data found in the event
/// is copied. The remainder of the struct is left at default values.
/// </remarks>
/// <seealso cref="GetState{T}(InputEventPtr)"/>
public TState GetState<TState>()
where TState : struct, IInputStateTypeInfo
{
var result = default(TState);
if (stateFormat != result.format)
throw new InvalidOperationException($"Expected state format '{result.format}' but got '{stateFormat}' instead");
UnsafeUtility.MemCpy(UnsafeUtility.AddressOf(ref result), state, Math.Min(stateSizeInBytes, UnsafeUtility.SizeOf<TState>()));
return result;
}
/// <summary>
/// Retrieve the state stored in the event.
/// </summary>
/// <typeparam name="TState">Type of state expected to be stored in the event. <see cref="IInputStateTypeInfo.format"/>
/// must match <see cref="stateFormat"/>.</typeparam>
/// <param name="ptr">A pointer to an input event. The pointer is checked for <c>null</c> and
/// for whether the type of event it refers to is indeed a StateEvent.</param>
/// <returns>Copy of the state stored in the event.</returns>
/// <remarks>
/// The event may contain less or more data than what is found in the struct. Only the data found in the event
/// is copied. The remainder of the struct is left at default values.
/// </remarks>
/// <exception cref="InvalidOperationException"><see cref="stateFormat"/> does not match <see cref="IInputStateTypeInfo.format"/>
/// of <typeparamref name="TState"/>.</exception>
/// <exception cref="ArgumentNullException"><paramref name="ptr"/> is <c>default(InputEventPtr)</c>.</exception>
/// <exception cref="InvalidCastException"><paramref name="ptr"/> does not refer to a StateEvent.</exception>
/// <seealso cref="GetState{T}()"/>
public static TState GetState<TState>(InputEventPtr ptr)
where TState : struct, IInputStateTypeInfo
{
return From(ptr)->GetState<TState>();
}
public static int GetEventSizeWithPayload<TState>()
where TState : struct
{
return UnsafeUtility.SizeOf<TState>() + InputEvent.kBaseEventSize + sizeof(int);
}
/// <summary>
/// Return the given <see cref="InputEventPtr"/> as a StateEvent pointer.
/// </summary>
/// <param name="ptr">A pointer to an input event. The pointer is checked for <c>null</c> and
/// for whether the type of event it refers to is indeed a StateEvent.</param>
/// <returns>Pointer <paramref name="ptr"/> converted to a StateEvent pointer.</returns>
/// <exception cref="ArgumentNullException"><paramref name="ptr"/> is <c>default(InputEventPtr)</c>.</exception>
/// <exception cref="InvalidCastException"><paramref name="ptr"/> does not refer to a StateEvent.</exception>
public static StateEvent* From(InputEventPtr ptr)
{
if (!ptr.valid)
throw new ArgumentNullException(nameof(ptr));
if (!ptr.IsA<StateEvent>())
throw new InvalidCastException($"Cannot cast event with type '{ptr.type}' into StateEvent");
return FromUnchecked(ptr);
}
internal static StateEvent* FromUnchecked(InputEventPtr ptr)
{
return (StateEvent*)ptr.data;
}
/// <summary>
/// Read the current state of <paramref name="device"/> and create a state event from it.
/// </summary>
/// <param name="device">Device to grab the state from. Must be a device that has been added to the system.</param>
/// <param name="eventPtr">Receives a pointer to the newly created state event.</param>
/// <param name="allocator">Which native allocator to allocate memory for the event from. By default, the buffer is
/// allocated as temporary memory (<see cref="Allocator.Temp"/>. Note that this means the buffer will not be valid
/// past the current frame. Use <see cref="Allocator.Persistent"/> if the buffer for the state event is meant to
/// persist for longer.</param>
/// <returns>Buffer of unmanaged memory allocated for the event.</returns>
/// <exception cref="ArgumentException"><paramref name="device"/> has not been added to the system.</exception>
/// <exception cref="ArgumentNullException"><paramref name="device"/> is <c>null</c>.</exception>
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1021:AvoidOutParameters", MessageId = "1#")]
public static NativeArray<byte> From(InputDevice device, out InputEventPtr eventPtr, Allocator allocator = Allocator.Temp)
{
return From(device, out eventPtr, allocator, useDefaultState: false);
}
/// <summary>
/// Create a state event for the given <paramref name="device"/> and copy the default state of the device
/// into the event.
/// </summary>
/// <param name="device">Device to create a state event for. Must be a device that has been added to the system.</param>
/// <param name="eventPtr">Receives a pointer to the newly created state event.</param>
/// <param name="allocator">Which native allocator to allocate memory for the event from. By default, the buffer is
/// allocated as temporary memory (<see cref="Allocator.Temp"/>. Note that this means the buffer will not be valid
/// past the current frame. Use <see cref="Allocator.Persistent"/> if the buffer for the state event is meant to
/// persist for longer.</param>
/// <returns>Buffer of unmanaged memory allocated for the event.</returns>
/// <exception cref="ArgumentException"><paramref name="device"/> has not been added to the system.</exception>
/// <exception cref="ArgumentNullException"><paramref name="device"/> is <c>null</c>.</exception>
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1021:AvoidOutParameters", MessageId = "1#")]
public static NativeArray<byte> FromDefaultStateFor(InputDevice device, out InputEventPtr eventPtr, Allocator allocator = Allocator.Temp)
{
return From(device, out eventPtr, allocator, useDefaultState: true);
}
private static NativeArray<byte> From(InputDevice device, out InputEventPtr eventPtr, Allocator allocator, bool useDefaultState)
{
if (device == null)
throw new ArgumentNullException(nameof(device));
if (!device.added)
throw new ArgumentException($"Device '{device}' has not been added to system",
nameof(device));
var stateFormat = device.m_StateBlock.format;
var stateSize = device.m_StateBlock.alignedSizeInBytes;
var stateOffset = device.m_StateBlock.byteOffset;
var statePtr = (byte*)(useDefaultState ? device.defaultStatePtr : device.currentStatePtr) + (int)stateOffset;
var eventSize = InputEvent.kBaseEventSize + sizeof(int) + stateSize;
var buffer = new NativeArray<byte>((int)eventSize.AlignToMultipleOf(4), allocator);
var stateEventPtr = (StateEvent*)buffer.GetUnsafePtr();
stateEventPtr->baseEvent = new InputEvent(Type, (int)eventSize, device.deviceId, InputRuntime.s_Instance.currentTime);
stateEventPtr->stateFormat = stateFormat;
UnsafeUtility.MemCpy(stateEventPtr->state, statePtr, stateSize);
eventPtr = stateEventPtr->ToEventPtr();
return buffer;
}
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: d9ce7842660d4bf89a86f378ecffb8f1
timeCreated: 1506762514

View File

@@ -0,0 +1,65 @@
using System;
using System.Runtime.InteropServices;
using UnityEngine.InputSystem.Utilities;
namespace UnityEngine.InputSystem.LowLevel
{
/// <summary>
/// A single character text input event.
/// </summary>
/// <remarks>
/// Text input does not fit the control-based input model well and thus is
/// represented as its own form of input. A device that is capable of receiving
/// text input (such as <see cref="Keyboard"/>) receives text input events
/// and should implement <see cref="ITextInputReceiver"/> in order for the
/// input system to be able to relay these events to the device.
/// </remarks>
[StructLayout(LayoutKind.Explicit, Size = InputEvent.kBaseEventSize + 4)]
public struct TextEvent : IInputEventTypeInfo
{
public const int Type = 0x54455854;
[FieldOffset(0)]
public InputEvent baseEvent;
/// <summary>
/// Character in UTF-32 encoding.
/// </summary>
[FieldOffset(InputEvent.kBaseEventSize)]
public int character;
public FourCC typeStatic => Type;
public static unsafe TextEvent* From(InputEventPtr eventPtr)
{
if (!eventPtr.valid)
throw new ArgumentNullException(nameof(eventPtr));
if (!eventPtr.IsA<TextEvent>())
throw new InvalidCastException(string.Format("Cannot cast event with type '{0}' into TextEvent",
eventPtr.type));
return (TextEvent*)eventPtr.data;
}
public static TextEvent Create(int deviceId, char character, double time = -1)
{
////TODO: detect and throw when if character is surrogate
var inputEvent = new TextEvent
{
baseEvent = new InputEvent(Type, InputEvent.kBaseEventSize + 4, deviceId, time),
character = character
};
return inputEvent;
}
public static TextEvent Create(int deviceId, int character, double time = -1)
{
var inputEvent = new TextEvent
{
baseEvent = new InputEvent(Type, InputEvent.kBaseEventSize + 4, deviceId, time),
character = character
};
return inputEvent;
}
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: dda83392ceab4fddbb9f3c7dd5edde62
timeCreated: 1512187779