test
This commit is contained in:
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: d1166ae938ecb42adaf8c4530372fb2b
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: c632b886b4d04e6b856720556b399ffe
|
||||
timeCreated: 1506762578
|
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 4313a2cc2049411789348ff4c952d89c
|
||||
timeCreated: 1512249379
|
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 9af5cfc54cf34eb1ac27c9c90a9a7440
|
||||
timeCreated: 1516846742
|
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 7333721cfa334ecdb55d0cb1a3386292
|
||||
timeCreated: 1628694001
|
@@ -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; }
|
||||
}
|
||||
}
|
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 02a54f9a4f584f5397cf3e9697372394
|
||||
timeCreated: 1506762438
|
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: bb39cf984021140a3adc2419327534a7
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 78260363757e4b02b9c6cc9949b4091a
|
||||
timeCreated: 1506761570
|
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: fa7997ba71d7b476799fab3d063b7e6c
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@@ -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
|
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 07a49c9088e54f1387f3bb8cbbe73e01
|
||||
timeCreated: 1627651217
|
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: caf772fd9b5b4840b7b0014d0616290d
|
||||
timeCreated: 1507682338
|
@@ -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;
|
||||
}
|
||||
}
|
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 8ccfc0b2d0da54246a3a9a3938b52fa4
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 5b23e3210ca74ea9ae4fe882b9bce278
|
||||
timeCreated: 1507691163
|
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: d9ce7842660d4bf89a86f378ecffb8f1
|
||||
timeCreated: 1506762514
|
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: dda83392ceab4fddbb9f3c7dd5edde62
|
||||
timeCreated: 1512187779
|
Reference in New Issue
Block a user