Skip to content
52 changes: 36 additions & 16 deletions Assets/Tests/InputSystem/CoreTests_Devices.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2648,7 +2648,7 @@ public void Devices_DeltaControlsResetBetweenUpdates(string layoutName, string c
[TestCase("Joystick", typeof(Joystick))]
[TestCase("Accelerometer", typeof(Accelerometer))]
[TestCase("Gyroscope", typeof(Gyroscope))]
public void Devices_CanCreateDevice(string layout, Type type)
public void Devices_CanCreateDevice(string layout, System.Type type)
{
var device = InputSystem.AddDevice(layout);

Expand Down Expand Up @@ -4130,25 +4130,45 @@ public void Devices_RemovingAndReaddingDevice_DoesNotAllocateMemory()
// Doesn't happen when a native backend reports a device.
var descriptionJson = description.ToJson();

Assert.That(() =>
{
Profiler.BeginSample(kProfilerRegion);
var recorder = Recorder.Get("GC.Alloc");
// The recorder was created enabled, which means it captured the creation of the Recorder object itself, etc.
// Disabling it flushes its data, so that we can retrieve the sample block count and have it correctly account
// for these initial allocations.
recorder.enabled = false;
#if !UNITY_WEBGL
recorder.FilterToCurrentThread();
#endif
recorder.enabled = true;

// "Plug" it back in.
deviceId = runtime.ReportNewInputDevice(descriptionJson);
InputSystem.Update();
Profiler.BeginSample(kProfilerRegion);

// "Unplug" device.
var removeEvent2 = DeviceRemoveEvent.Create(deviceId);
InputSystem.QueueEvent(ref removeEvent2);
InputSystem.Update();
// "Plug" it back in.
deviceId = runtime.ReportNewInputDevice(descriptionJson);
InputSystem.Update();

// "Plug" it back in.
runtime.ReportNewInputDevice(descriptionJson);
InputSystem.Update();
// "Unplug" device.
var removeEvent2 = DeviceRemoveEvent.Create(deviceId);
InputSystem.QueueEvent(ref removeEvent2);
InputSystem.Update();

Profiler.EndSample();
}, Is.Not.AllocatingGCMemory());
// "Plug" it back in.
runtime.ReportNewInputDevice(descriptionJson);
InputSystem.Update();

Profiler.EndSample();

recorder.enabled = false;
#if !UNITY_WEBGL
recorder.CollectFromAllThreads();
#endif

// We expect a single allocation for each call to ReportNewInputDevice when there is one disconnected device
//
int numberOfRepeats = 2;
int numberOfDisconnectedDevices = 1;
int numberOfCallsToReportNewInputDevicePerRun = 2;
int expectedAllocations = numberOfRepeats * numberOfDisconnectedDevices * numberOfCallsToReportNewInputDevicePerRun;
Assert.AreEqual(expectedAllocations, recorder.sampleBlockCount);
}

[Test]
Expand Down
1 change: 1 addition & 0 deletions Packages/com.unity.inputsystem/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ however, it has to be formatted properly to pass verification tests.
- Fixed Package compilation when Unity Analytics module is not enabled on 2022.3. [ISXB-996](https://issuetracker.unity3d.com/product/unity/issues/guid/ISXB-996)
- Fixed 'OnDrop' event not called when 'IPointerDownHandler' is also listened. [ISXB-1014](https://issuetracker.unity3d.com/product/unity/issues/guid/ISXB-1014)
- Fixed InputSystemUIInputModule calling pointer events on parent objects even when the "Send Pointer Hover To Parent" is off. [ISXB-586](https://issuetracker.unity3d.com/product/unity/issues/guid/ISXB-586)
- Improved performance of disconnected device activation (ISX-1450)

### Changed
- Use `ProfilerMarker` instead of `Profiler.BeginSample` and `Profiler.EndSample` when appropriate to enable recording of profiling data.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -380,14 +380,14 @@ public static InputDeviceDescription FromJson(string json)
};
}

internal static bool ComparePropertyToDeviceDescriptor(string propertyName, string propertyValue, string deviceDescriptor)
internal static bool ComparePropertyToDeviceDescriptor(string propertyName, JsonParser.JsonString propertyValue, string deviceDescriptor)
{
// We use JsonParser instead of JsonUtility.Parse in order to not allocate GC memory here.

var json = new JsonParser(deviceDescriptor);
if (!json.NavigateToProperty(propertyName))
{
if (string.IsNullOrEmpty(propertyValue))
if (propertyValue.text.isEmpty)
return true;
return false;
}
Expand Down
41 changes: 40 additions & 1 deletion Packages/com.unity.inputsystem/InputSystem/InputManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2534,6 +2534,45 @@ private void OnNativeDeviceDiscovered(int deviceId, string deviceDescriptor)
}
}

private JsonParser.JsonString MakeEscapedJsonString(string theString)
{
//
// When we create the device description from the (passed from native) deviceDescriptor string in OnNativeDeviceDiscovered()
// we remove any escape characters from the capabilties field when we do InputDeviceDescription.FromJson() - this decoded
// description is used to create the device.
//
// This means that the native and managed code can have slightly different representations of the capabilities field.
//
// Managed: description.capabilities string, unescaped
// eg "{"deviceName":"Oculus Quest", ..."
//
// Native: deviceDescriptor string, containing a Json encoded "capabilities" name/value pair represented by an escaped Json string
// eg "{\"deviceName\":\"Oculus Quest\", ..."
//
// To avoid a very costly escape-skipping character-by-character string comparison in JsonParser.Json.Equals() we
// reconstruct an escaped string and make an escaped JsonParser.JsonString and use that for the comparison instead.
//
var builder = new StringBuilder();
var length = theString.Length;
var hasEscapes = false;
for (var j = 0; j < length; ++j)
{
var ch = theString[j];
if (ch == '\\' || ch == '\"')
{
builder.Append('\\');
hasEscapes = true;
}
builder.Append(ch);
}
var jsonStringWithEscapes = new JsonParser.JsonString
{
text = builder.ToString(),
hasEscapes = hasEscapes
};
return jsonStringWithEscapes;
}

private InputDevice TryMatchDisconnectedDevice(string deviceDescriptor)
{
for (var i = 0; i < m_DisconnectedDevicesCount; ++i)
Expand All @@ -2552,7 +2591,7 @@ private InputDevice TryMatchDisconnectedDevice(string deviceDescriptor)
continue;
if (!InputDeviceDescription.ComparePropertyToDeviceDescriptor("type", description.deviceClass, deviceDescriptor))
continue;
if (!InputDeviceDescription.ComparePropertyToDeviceDescriptor("capabilities", description.capabilities, deviceDescriptor))
if (!InputDeviceDescription.ComparePropertyToDeviceDescriptor("capabilities", MakeEscapedJsonString(description.capabilities), deviceDescriptor))
continue;
if (!InputDeviceDescription.ComparePropertyToDeviceDescriptor("serial", description.serial, deviceDescriptor))
continue;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -260,6 +260,7 @@ public bool ParseStringValue(out JsonValue result)
else if (ch == '"')
{
++m_Position;

result = new JsonString
{
text = new Substring(m_Text, startIndex, m_Position - startIndex - 1),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -450,7 +450,7 @@ public bool ToBoolean(IFormatProvider provider = null)
case TypeCode.Single:
return !Mathf.Approximately(m_FloatValue, default);
case TypeCode.Double:
return NumberHelpers.Approximately(m_DoubleValue, default);
return !NumberHelpers.Approximately(m_DoubleValue, default);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bonus bug fix?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, I spotted a bug we didn't even know we had!

default:
return default;
}
Expand Down