Skip to content

Commit 219e951

Browse files
StayTalmDmytro Ivanov
authored andcommitted
Using variable size IME events
1 parent 3342f7e commit 219e951

File tree

4 files changed

+163
-56
lines changed

4 files changed

+163
-56
lines changed

Assets/Tests/InputSystem/CoreTests_Devices.cs

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5235,9 +5235,31 @@ public void Devices_CanListenForIMECompositionEvents()
52355235
Assert.AreEqual(composition.ToString(), imeCompositionCharacters);
52365236
};
52375237

5238-
var inputEvent = IMECompositionEvent.Create(keyboard.deviceId, imeCompositionCharacters,
5238+
IMECompositionEventVariableSize.QueueEvent(keyboard.deviceId, imeCompositionCharacters,
5239+
InputRuntime.s_Instance.currentTime);
5240+
InputSystem.Update();
5241+
5242+
Assert.That(callbackWasCalled, Is.True);
5243+
}
5244+
5245+
[Test]
5246+
[Category("Devices")]
5247+
public void Devices_CanReadEmptyIMECompositionEvents()
5248+
{
5249+
const string imeCompositionCharacters = "";
5250+
var callbackWasCalled = false;
5251+
5252+
var keyboard = InputSystem.AddDevice<Keyboard>();
5253+
keyboard.onIMECompositionChange += composition =>
5254+
{
5255+
Assert.That(callbackWasCalled, Is.False);
5256+
callbackWasCalled = true;
5257+
Assert.AreEqual(composition.Count, 0);
5258+
Assert.AreEqual(composition.ToString(), imeCompositionCharacters);
5259+
};
5260+
5261+
IMECompositionEventVariableSize.QueueEvent(keyboard.deviceId, imeCompositionCharacters,
52395262
InputRuntime.s_Instance.currentTime);
5240-
InputSystem.QueueEvent(ref inputEvent);
52415263
InputSystem.Update();
52425264

52435265
Assert.That(callbackWasCalled, Is.True);

Packages/com.unity.inputsystem/InputSystem/Devices/ITextInputReceiver.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ namespace UnityEngine.InputSystem.LowLevel
88
/// input through <see cref="TextEvent"/>.
99
/// </remarks>
1010
/// <seealso cref="TextEvent"/>
11-
/// <seealso cref="IMECompositionEvent"/>
11+
/// <seealso cref="IMECompositionEventVariableSize"/>
1212
public interface ITextInputReceiver
1313
{
1414
/// <summary>
@@ -32,7 +32,7 @@ public interface ITextInputReceiver
3232
/// Called when an IME composition is in-progress or finished.
3333
/// </summary>
3434
/// <param name="compositionString">The current composition.</param>
35-
/// <seealso cref="IMECompositionEvent"/>
35+
/// <seealso cref="IMECompositionEventVariableSize"/>
3636
/// <seealso cref="Keyboard.onIMECompositionChange"/>
3737
/// <remarks>
3838
/// The method will be repeatedly called with the current string while composition is in progress.

Packages/com.unity.inputsystem/InputSystem/Events/IMECompositionEvent.cs

Lines changed: 134 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -2,16 +2,15 @@
22
using System.Collections;
33
using System.Collections.Generic;
44
using System.Runtime.InteropServices;
5+
using Unity.Collections;
6+
using Unity.Collections.LowLevel.Unsafe;
57
using UnityEngine.InputSystem.Utilities;
68

79
namespace UnityEngine.InputSystem.LowLevel
810
{
9-
/// <summary>
10-
/// A specialized event that contains the current IME Composition string, if IME is enabled and active.
11-
/// This event contains the entire current string to date, and once a new composition is submitted will send a blank string event.
12-
/// </summary>
11+
[Obsolete("IMECompositionEvent is obsolete, please use IMECompositionEventVariableSize")]
1312
[StructLayout(LayoutKind.Explicit, Size = InputEvent.kBaseEventSize + sizeof(int) + (sizeof(char) * kIMECharBufferSize))]
14-
public struct IMECompositionEvent : IInputEventTypeInfo
13+
public unsafe struct IMECompositionEvent : IInputEventTypeInfo
1514
{
1615
// These needs to match the native ImeCompositionStringInputEventData settings
1716
internal const int kIMECharBufferSize = 64;
@@ -21,19 +20,95 @@ public struct IMECompositionEvent : IInputEventTypeInfo
2120
public InputEvent baseEvent;
2221

2322
[FieldOffset(InputEvent.kBaseEventSize)]
24-
public IMECompositionString compositionString;
23+
private int length;
24+
25+
[FieldOffset(InputEvent.kBaseEventSize + sizeof(int))]
26+
private fixed char buffer[kIMECharBufferSize];
27+
28+
public IMECompositionString compositionString
29+
{
30+
get
31+
{
32+
fixed(char* ptr = buffer)
33+
return new IMECompositionString(ptr, length);
34+
}
35+
}
2536

2637
public FourCC typeStatic => Type;
2738

2839
public static IMECompositionEvent Create(int deviceId, string compositionString, double time)
2940
{
3041
var inputEvent = new IMECompositionEvent();
3142
inputEvent.baseEvent = new InputEvent(Type, InputEvent.kBaseEventSize + sizeof(int) + (sizeof(char) * kIMECharBufferSize), deviceId, time);
32-
inputEvent.compositionString = new IMECompositionString(compositionString);
43+
inputEvent.length = compositionString.Length > kIMECharBufferSize ? kIMECharBufferSize : compositionString.Length;
44+
fixed(char* dst = compositionString)
45+
fixed(char* src = compositionString)
46+
UnsafeUtility.MemCpy(dst, src, inputEvent.length * sizeof(char));
3347
return inputEvent;
3448
}
3549
}
3650

51+
/// <summary>
52+
/// A specialized event that contains the current IME Composition string, if IME is enabled and active.
53+
/// This event contains the entire current string to date, and once a new composition is submitted will send a blank string event.
54+
/// </summary>
55+
[StructLayout(LayoutKind.Explicit, Size = InputEvent.kBaseEventSize + sizeof(int))]
56+
public struct IMECompositionEventVariableSize : IInputEventTypeInfo
57+
{
58+
// Before we had 0x494D4553 which corresponds to ImeCompositionStringInputEventData fixed size event with 64 character payload.
59+
// 0x494D4543 corresponds to ImeCompositionInputEventData and is a different event which provides variable size array of characters after the event.
60+
public const int Type = 0x494D4543;
61+
62+
[FieldOffset(0)]
63+
public InputEvent baseEvent;
64+
65+
[FieldOffset(InputEvent.kBaseEventSize)]
66+
internal int length;
67+
68+
internal static unsafe char* GetCharsPtr(IMECompositionEventVariableSize* ev)
69+
{
70+
return (char*)((byte*)ev + InputEvent.kBaseEventSize + sizeof(int));
71+
}
72+
73+
public FourCC typeStatic => Type;
74+
75+
/// <summary>
76+
/// Returns composition string for the given event.
77+
/// </summary>
78+
/// <param name="ev">Pointer to the event.</param>
79+
/// <returns></returns>
80+
public static unsafe IMECompositionString GetIMECompositionString(IMECompositionEventVariableSize* ev)
81+
{
82+
return new IMECompositionString(GetCharsPtr(ev), ev->length);
83+
}
84+
85+
/// <summary>
86+
/// 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.
87+
/// </summary>
88+
/// <param name="deviceId">ID of the device (see <see cref="InputDevice.deviceId") to which the composition event should be sent to. Should be an <see cref="ITextInputReceiver"/> device. Will trigger <see cref="ITextInputReceiver.OnIMECompositionChanged"/> call when processed.</param>
89+
/// <param name="str">The IME characters to be sent. This can be any length, or left blank to represent a resetting of the IME dialog.</param>
90+
/// <param name="time">The time in seconds, the event was generated at. This uses the same timeline as <see cref="Time.realtimeSinceStartup"/></param>
91+
public static unsafe void QueueEvent(int deviceId, string str, double time)
92+
{
93+
var sizeInBytes = (InputEvent.kBaseEventSize + sizeof(int)) + sizeof(char) * str.Length;
94+
var eventBuffer = new NativeArray<byte>(sizeInBytes, Allocator.Temp, NativeArrayOptions.UninitializedMemory);
95+
96+
var ev = (IMECompositionEventVariableSize*)eventBuffer.GetUnsafePtr();
97+
98+
ev->baseEvent = new InputEvent(Type, sizeInBytes, deviceId, time);
99+
ev->length = str.Length;
100+
101+
if (str.Length > 0)
102+
fixed(char* p = str)
103+
UnsafeUtility.MemCpy(GetCharsPtr(ev), p, str.Length * sizeof(char));
104+
105+
InputSystem.QueueEvent(new InputEventPtr((InputEvent*)ev));
106+
107+
eventBuffer.Dispose();
108+
}
109+
}
110+
111+
//// TODO for v2 remove and replace with just string.
37112
/// <summary>
38113
/// A struct representing an string of characters generated by an IME for text input.
39114
/// </summary>
@@ -42,36 +117,31 @@ public static IMECompositionEvent Create(int deviceId, string compositionString,
42117
/// <see cref="ITextInputReceiver.OnIMECompositionChanged"/> method. It can easily be converted to a normal C# string using
43118
/// <see cref="ToString"/>, but is exposed as the raw struct to avoid allocating memory by default.
44119
/// </remarks>
45-
[StructLayout(LayoutKind.Explicit, Size = sizeof(int) + sizeof(char) * LowLevel.IMECompositionEvent.kIMECharBufferSize)]
46120
public unsafe struct IMECompositionString : IEnumerable<char>
47121
{
48-
internal struct Enumerator : IEnumerator<char>
122+
private const int kLegacyIMEEventCharBufferSize = 64;
123+
124+
private readonly string m_ManagedString;
125+
private readonly int m_Size;
126+
private fixed char m_FixedBuffer[kLegacyIMEEventCharBufferSize];
127+
128+
private struct FixedBufferEnumerator : IEnumerator<char>
49129
{
50-
IMECompositionString m_CompositionString;
51-
char m_CurrentCharacter;
52-
int m_CurrentIndex;
130+
private IMECompositionString m_CompositionString;
131+
private int m_CurrentIndex;
53132

54-
public Enumerator(IMECompositionString compositionString)
133+
public FixedBufferEnumerator(IMECompositionString compositionString)
55134
{
56135
m_CompositionString = compositionString;
57-
m_CurrentCharacter = '\0';
58136
m_CurrentIndex = -1;
59137
}
60138

61139
public bool MoveNext()
62140
{
63-
int size = m_CompositionString.Count;
64-
65-
m_CurrentIndex++;
66-
67-
if (m_CurrentIndex == size)
141+
if (m_CurrentIndex + 1 >= m_CompositionString.Count)
68142
return false;
69143

70-
fixed(char* ptr = m_CompositionString.buffer)
71-
{
72-
m_CurrentCharacter = *(ptr + m_CurrentIndex);
73-
}
74-
144+
m_CurrentIndex++;
75145
return true;
76146
}
77147

@@ -84,58 +154,73 @@ public void Dispose()
84154
{
85155
}
86156

87-
public char Current => m_CurrentCharacter;
157+
public char Current => m_CompositionString[m_CurrentIndex];
88158

89159
object IEnumerator.Current => Current;
90160
}
91161

92-
public int Count => size;
162+
public int Count => m_Size;
93163

94164
public char this[int index]
95165
{
96166
get
97167
{
168+
if (m_ManagedString != null)
169+
return m_ManagedString[index];
170+
98171
if (index >= Count || index < 0)
99172
throw new ArgumentOutOfRangeException(nameof(index));
100173

101-
fixed(char* ptr = buffer)
102-
{
103-
return *(ptr + index);
104-
}
174+
return m_FixedBuffer[index];
105175
}
106176
}
107177

108-
[FieldOffset(0)]
109-
int size;
110-
111-
[FieldOffset(sizeof(int))]
112-
fixed char buffer[IMECompositionEvent.kIMECharBufferSize];
113-
114-
public IMECompositionString(string characters)
178+
public IMECompositionString(char* characters, int length)
115179
{
116-
if (string.IsNullOrEmpty(characters))
180+
// only allocate string if we can't fit into fixed buffer
181+
if (length <= kLegacyIMEEventCharBufferSize)
182+
{
183+
m_ManagedString = null;
184+
m_Size = length;
185+
if (m_Size > 0)
186+
{
187+
Debug.Assert(characters != null);
188+
fixed(char* dst = m_FixedBuffer)
189+
UnsafeUtility.MemCpy(dst, characters, m_Size * sizeof(char));
190+
}
191+
}
192+
else
117193
{
118-
size = 0;
119-
return;
194+
m_ManagedString = new string(characters, 0, length);
195+
m_Size = length;
120196
}
197+
}
121198

122-
Debug.Assert(characters.Length < IMECompositionEvent.kIMECharBufferSize);
123-
size = characters.Length;
124-
for (var i = 0; i < size; i++)
125-
buffer[i] = characters[i];
199+
public IMECompositionString(string characters)
200+
{
201+
// string is already allocated on the heap, so reuse it
202+
m_ManagedString = characters;
203+
m_Size = characters.Length;
126204
}
127205

128206
public override string ToString()
129207
{
130-
fixed(char* ptr = buffer)
131-
{
132-
return new string(ptr, 0, size);
133-
}
208+
if (m_Size == 0)
209+
return string.Empty;
210+
211+
if (m_ManagedString != null)
212+
return m_ManagedString;
213+
214+
fixed(char* ptr = m_FixedBuffer)
215+
return new string(ptr, 0, m_Size);
134216
}
135217

136218
public IEnumerator<char> GetEnumerator()
137219
{
138-
return new Enumerator(this);
220+
if (m_ManagedString != null)
221+
return m_ManagedString.GetEnumerator();
222+
223+
return new FixedBufferEnumerator(this);
139224
}
140225

141226
IEnumerator IEnumerable.GetEnumerator()

Packages/com.unity.inputsystem/InputSystem/InputManager.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3252,11 +3252,11 @@ private unsafe void OnUpdate(InputUpdateType updateType, ref InputEventBuffer ev
32523252
break;
32533253
}
32543254

3255-
case IMECompositionEvent.Type:
3255+
case IMECompositionEventVariableSize.Type:
32563256
{
3257-
var imeEventPtr = (IMECompositionEvent*)currentEventReadPtr;
3257+
var imeEventPtr = (IMECompositionEventVariableSize*)currentEventReadPtr;
32583258
var textInputReceiver = device as ITextInputReceiver;
3259-
textInputReceiver?.OnIMECompositionChanged(imeEventPtr->compositionString);
3259+
textInputReceiver?.OnIMECompositionChanged(IMECompositionEventVariableSize.GetIMECompositionString(imeEventPtr));
32603260
break;
32613261
}
32623262

0 commit comments

Comments
 (0)