diff --git a/Assets/Tests/InputSystem/CoreTests_Devices.cs b/Assets/Tests/InputSystem/CoreTests_Devices.cs index d75e58c842..a206997bab 100644 --- a/Assets/Tests/InputSystem/CoreTests_Devices.cs +++ b/Assets/Tests/InputSystem/CoreTests_Devices.cs @@ -3959,9 +3959,31 @@ public void Devices_CanListenForIMECompositionEvents() Assert.AreEqual(composition.ToString(), imeCompositionCharacters); }; - var inputEvent = IMECompositionEvent.Create(keyboard.deviceId, imeCompositionCharacters, + IMECompositionEvent.QueueEvent(keyboard.deviceId, imeCompositionCharacters, + InputRuntime.s_Instance.currentTime); + InputSystem.Update(); + + Assert.That(callbackWasCalled, Is.True); + } + + [Test] + [Category("Devices")] + public void Devices_CanReadEmptyIMECompositionEvents() + { + const string imeCompositionCharacters = ""; + var callbackWasCalled = false; + + var keyboard = InputSystem.AddDevice(); + keyboard.onIMECompositionChange += composition => + { + Assert.That(callbackWasCalled, Is.False); + callbackWasCalled = true; + Assert.AreEqual(composition.Count, 0); + Assert.AreEqual(composition.ToString(), imeCompositionCharacters); + }; + + IMECompositionEvent.QueueEvent(keyboard.deviceId, imeCompositionCharacters, InputRuntime.s_Instance.currentTime); - InputSystem.QueueEvent(ref inputEvent); InputSystem.Update(); Assert.That(callbackWasCalled, Is.True); diff --git a/Packages/com.unity.inputsystem/InputSystem/Events/IMECompositionEvent.cs b/Packages/com.unity.inputsystem/InputSystem/Events/IMECompositionEvent.cs index a9c886269f..399d4d5ff7 100644 --- a/Packages/com.unity.inputsystem/InputSystem/Events/IMECompositionEvent.cs +++ b/Packages/com.unity.inputsystem/InputSystem/Events/IMECompositionEvent.cs @@ -2,35 +2,106 @@ using System.Collections; using System.Collections.Generic; using System.Runtime.InteropServices; +using Unity.Collections; +using Unity.Collections.LowLevel.Unsafe; using UnityEngine.InputSystem.Utilities; namespace UnityEngine.InputSystem.LowLevel { + /// + /// Helper methods to convert NativeArray objects to C# strings. + /// + static class NativeArrayStringExtension + { + /// + /// Extension method to convert a NativeArray/ to a C# string. + /// + /// The NativeArray containing the string data. + /// A string representation of the Native Array character buffer. + public static unsafe string ToString(this NativeArray c) + { + return new string((char*)NativeArrayUnsafeUtility.GetUnsafePtr(c), 0, c.Length); + } + } + /// /// 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. /// - [StructLayout(LayoutKind.Explicit, Size = InputEvent.kBaseEventSize + sizeof(int) + (sizeof(char) * kIMECharBufferSize))] + [StructLayout(LayoutKind.Explicit, Size = InputEvent.kBaseEventSize + sizeof(int) + (sizeof(char)))] public struct IMECompositionEvent : IInputEventTypeInfo { - // These needs to match the native ImeCompositionStringInputEventData settings internal const int kIMECharBufferSize = 64; - public const int Type = 0x494D4553; + public const int Type = 0x494D4543; [FieldOffset(0)] public InputEvent baseEvent; [FieldOffset(InputEvent.kBaseEventSize)] - public IMECompositionString compositionString; + internal int length; + + [FieldOffset(InputEvent.kBaseEventSize + sizeof(int))] + internal char bufferStart; public FourCC typeStatic => Type; - public static IMECompositionEvent Create(int deviceId, string compositionString, double time) + internal IMECompositionString GetComposition() + { + var nativeArray = GetCharacters(); + var str = new IMECompositionString(nativeArray); + nativeArray.Dispose(); + return str; + } + + /// + /// Gets the IME characters packed into a variable sized NativeArray. + /// + /// + /// It is the callers responsibility to dispose of the NativeArray. + /// + /// The IME Characters for this event. + public unsafe NativeArray GetCharacters() + { + var characters = new NativeArray(length + sizeof(char), Allocator.Temp); + var ptr = NativeArrayUnsafeUtility.GetUnsafePtr(characters); + fixed(char* buffer = &bufferStart) + { + UnsafeUtility.MemCpy(ptr, buffer, length + sizeof(char)); + } + return characters; + } + + /// + /// Queues up an IME Composition Event. IME Event sizes are variable, and this simplifies the process of aligning up the Input Event information and actual IME composition string. + /// + /// ID of the device (see device. Will trigger call when processed. + /// The IME characters to be sent. This can be any length, or left blank to represent a resetting of the IME dialog. + /// The time in seconds, the event was generated at. This uses the same timeline as + public static void QueueEvent(int deviceId, string str, 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; + unsafe + { + int sizeInBytes = (InputEvent.kBaseEventSize + sizeof(int) + sizeof(char)) + (sizeof(char) * str.Length); + NativeArray eventBuffer = new NativeArray(sizeInBytes, Allocator.Temp, NativeArrayOptions.UninitializedMemory); + + byte* ptr = (byte*)NativeArrayUnsafeUtility.GetUnsafePtr(eventBuffer); + InputEvent* evt = (InputEvent*)ptr; + + *evt = new InputEvent(Type, sizeInBytes, deviceId, time); + ptr += InputEvent.kBaseEventSize; + + int* lengthPtr = (int*)ptr; + *lengthPtr = str.Length; + + ptr += sizeof(int); + + fixed(char* p = str) + { + UnsafeUtility.MemCpy(ptr, p, str.Length * sizeof(char)); + } + + InputSystem.QueueEvent(new InputEventPtr(evt)); + } } } @@ -111,6 +182,17 @@ public char this[int index] [FieldOffset(sizeof(int))] fixed char buffer[IMECompositionEvent.kIMECharBufferSize]; + public IMECompositionString(NativeArray characters) + { + int copySize = IMECompositionEvent.kIMECharBufferSize < characters.Length ? IMECompositionEvent.kIMECharBufferSize : characters.Length; + fixed(char* ptr = buffer) + { + void* arrayPtr = NativeArrayUnsafeUtility.GetUnsafePtr(characters); + UnsafeUtility.MemCpy(ptr, arrayPtr, copySize * sizeof(char)); + } + size = copySize; + } + public IMECompositionString(string characters) { if (string.IsNullOrEmpty(characters)) diff --git a/Packages/com.unity.inputsystem/InputSystem/InputManager.cs b/Packages/com.unity.inputsystem/InputSystem/InputManager.cs index 6bec8dce72..f804d78116 100644 --- a/Packages/com.unity.inputsystem/InputSystem/InputManager.cs +++ b/Packages/com.unity.inputsystem/InputSystem/InputManager.cs @@ -2803,7 +2803,7 @@ private unsafe void OnUpdate(InputUpdateType updateType, ref InputEventBuffer ev { var imeEventPtr = (IMECompositionEvent*)currentEventReadPtr; var textInputReceiver = device as ITextInputReceiver; - textInputReceiver?.OnIMECompositionChanged(imeEventPtr->compositionString); + textInputReceiver?.OnIMECompositionChanged(imeEventPtr->GetComposition()); break; } diff --git a/Packages/com.unity.inputsystem/InputSystem/Plugins/XR/Controls/PoseControl.cs b/Packages/com.unity.inputsystem/InputSystem/Plugins/XR/Controls/PoseControl.cs index 40587e75fe..f8b48b894d 100644 --- a/Packages/com.unity.inputsystem/InputSystem/Plugins/XR/Controls/PoseControl.cs +++ b/Packages/com.unity.inputsystem/InputSystem/Plugins/XR/Controls/PoseControl.cs @@ -33,7 +33,7 @@ public struct PoseState : IInputStateTypeInfo /// /// Constructor for PoseStates. - /// + /// /// Useful for creating PoseStates locally (not from ). /// /// Value to use for @@ -64,7 +64,7 @@ public PoseState(bool isTracked, TrackingState trackingState, Vector3 position, /// /// A Flags Enumeration specifying which other fields in the pose state are valid. /// - [FieldOffset(4), InputControl( displayName = "Tracking State", layout = "Integer")] + [FieldOffset(4), InputControl(displayName = "Tracking State", layout = "Integer")] public TrackingState trackingState; /// @@ -119,7 +119,7 @@ public PoseState(bool isTracked, TrackingState trackingState, Vector3 position, /// will not work correctly with a different memory layouts. Additional fields may /// be appended to the struct but what's there in the struct has to be located /// at exactly those memory addresses. - /// + /// /// For more information on tracking origins see . /// [Preserve, InputControlLayout(stateType = typeof(PoseState))]