diff --git a/Assets/Tests/InputSystem/CoreTests_Devices.cs b/Assets/Tests/InputSystem/CoreTests_Devices.cs index 0408664175..62b5a9232f 100644 --- a/Assets/Tests/InputSystem/CoreTests_Devices.cs +++ b/Assets/Tests/InputSystem/CoreTests_Devices.cs @@ -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); @@ -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] diff --git a/Packages/com.unity.inputsystem/CHANGELOG.md b/Packages/com.unity.inputsystem/CHANGELOG.md index c7f001bcc7..69b81d5db2 100644 --- a/Packages/com.unity.inputsystem/CHANGELOG.md +++ b/Packages/com.unity.inputsystem/CHANGELOG.md @@ -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. diff --git a/Packages/com.unity.inputsystem/InputSystem/Devices/InputDeviceDescription.cs b/Packages/com.unity.inputsystem/InputSystem/Devices/InputDeviceDescription.cs index 932f0af0d0..d2163d3c9e 100644 --- a/Packages/com.unity.inputsystem/InputSystem/Devices/InputDeviceDescription.cs +++ b/Packages/com.unity.inputsystem/InputSystem/Devices/InputDeviceDescription.cs @@ -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; } diff --git a/Packages/com.unity.inputsystem/InputSystem/InputManager.cs b/Packages/com.unity.inputsystem/InputSystem/InputManager.cs index e23e3f03d7..a46d595d47 100644 --- a/Packages/com.unity.inputsystem/InputSystem/InputManager.cs +++ b/Packages/com.unity.inputsystem/InputSystem/InputManager.cs @@ -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) @@ -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; diff --git a/Packages/com.unity.inputsystem/InputSystem/Utilities/JsonParser.cs b/Packages/com.unity.inputsystem/InputSystem/Utilities/JsonParser.cs index 64e6532ad1..47c565a821 100644 --- a/Packages/com.unity.inputsystem/InputSystem/Utilities/JsonParser.cs +++ b/Packages/com.unity.inputsystem/InputSystem/Utilities/JsonParser.cs @@ -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), diff --git a/Packages/com.unity.inputsystem/InputSystem/Utilities/PrimitiveValue.cs b/Packages/com.unity.inputsystem/InputSystem/Utilities/PrimitiveValue.cs index 960b6664d6..2f8627b456 100644 --- a/Packages/com.unity.inputsystem/InputSystem/Utilities/PrimitiveValue.cs +++ b/Packages/com.unity.inputsystem/InputSystem/Utilities/PrimitiveValue.cs @@ -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); default: return default; }