diff --git a/Assets/Tests/InputSystem/CorePerformanceTests.cs b/Assets/Tests/InputSystem/CorePerformanceTests.cs index 5d7d8ac230..86026ed313 100644 --- a/Assets/Tests/InputSystem/CorePerformanceTests.cs +++ b/Assets/Tests/InputSystem/CorePerformanceTests.cs @@ -1113,7 +1113,10 @@ public void Performance_OptimizedControls_ReadingPose4kTimes(OptimizationTestTyp "InputSystem.onAfterUpdate", "PreUpdate.NewInputUpdate", "PreUpdate.InputForUIUpdate", - "FixedUpdate.NewInputFixedUpdate" + "FixedUpdate.NewInputFixedUpdate", + "InputAction.Disable", + "InputAction.Enable", + "InputActionMap.ResolveBindings" }; [PrebuildSetup(typeof(ProjectWideActionsBuildSetup))] diff --git a/Assets/Tests/InputSystem/Plugins/UITests.cs b/Assets/Tests/InputSystem/Plugins/UITests.cs index f098df98a6..1b94f75ab8 100644 --- a/Assets/Tests/InputSystem/Plugins/UITests.cs +++ b/Assets/Tests/InputSystem/Plugins/UITests.cs @@ -1793,6 +1793,7 @@ public IEnumerator UI_CanReleaseAndPressTouchesOnSameFrame() .Matches((UICallbackReceiver.Event e) => e.pointerData.pointerType == UIPointerType.Touch).And .Matches((UICallbackReceiver.Event e) => e.pointerData.position == secondPosition)); +#if UNITY_2021_2_OR_NEWER Assert.That(scene.rightChildReceiver.events, Has.Exactly(1).With.Property("type").EqualTo(EventType.PointerMove).And .Matches((UICallbackReceiver.Event e) => e.pointerData.device == touchScreen).And @@ -1800,6 +1801,7 @@ public IEnumerator UI_CanReleaseAndPressTouchesOnSameFrame() .Matches((UICallbackReceiver.Event e) => e.pointerData.pointerId == pointerIdTouch2).And .Matches((UICallbackReceiver.Event e) => e.pointerData.pointerType == UIPointerType.Touch).And .Matches((UICallbackReceiver.Event e) => e.pointerData.position == secondPosition)); +#endif // Pointer 3 Assert.That(scene.rightChildReceiver.events, diff --git a/Packages/com.unity.inputsystem/CHANGELOG.md b/Packages/com.unity.inputsystem/CHANGELOG.md index 21c891a88c..aab8c0ebe8 100644 --- a/Packages/com.unity.inputsystem/CHANGELOG.md +++ b/Packages/com.unity.inputsystem/CHANGELOG.md @@ -42,6 +42,7 @@ however, it has to be formatted properly to pass verification tests. ### Added - Added new API `InputSystem.settings.useIMGUIEditorForAssets` that should be used in custom `InputParameterEditor` that use both IMGUI and UI Toolkit. +- Added ProfilerMakers to `InputAction.Enable()` and `InputActionMap.ResolveBindings()` to enable gathering of profiling data. ## [1.11.2] - 2024-10-16 diff --git a/Packages/com.unity.inputsystem/InputSystem/Actions/InputAction.cs b/Packages/com.unity.inputsystem/InputSystem/Actions/InputAction.cs index c5fbf9f2ae..d4c1dd41d5 100644 --- a/Packages/com.unity.inputsystem/InputSystem/Actions/InputAction.cs +++ b/Packages/com.unity.inputsystem/InputSystem/Actions/InputAction.cs @@ -1,5 +1,6 @@ using System; using Unity.Collections.LowLevel.Unsafe; +using Unity.Profiling; using UnityEngine.InputSystem.Controls; using UnityEngine.InputSystem.LowLevel; using UnityEngine.InputSystem.Utilities; @@ -681,6 +682,12 @@ public bool wantsInitialStateCheck } } + /// + /// ProfilerMarker for measuring the enabling/disabling of InputActions. + /// + static readonly ProfilerMarker k_InputActionEnableProfilerMarker = new ProfilerMarker("InputAction.Enable"); + static readonly ProfilerMarker k_InputActionDisableProfilerMarker = new ProfilerMarker("InputAction.Disable"); + /// /// Construct an unnamed, free-standing action that is not part of any map or asset /// and has no bindings. Bindings can be added with public void Enable() { - if (enabled) - return; + using (k_InputActionEnableProfilerMarker.Auto()) + { + if (enabled) + return; - // For singleton actions, we create an internal-only InputActionMap - // private to the action. - var map = GetOrCreateActionMap(); + // For singleton actions, we create an internal-only InputActionMap + // private to the action. + var map = GetOrCreateActionMap(); - // First time we're enabled, find all controls. - map.ResolveBindingsIfNecessary(); + // First time we're enabled, find all controls. + map.ResolveBindingsIfNecessary(); - // Go live. - map.m_State.EnableSingleAction(this); + // Go live. + map.m_State.EnableSingleAction(this); + } } /// @@ -928,10 +938,13 @@ public void Enable() /// public void Disable() { - if (!enabled) - return; + using (k_InputActionDisableProfilerMarker.Auto()) + { + if (!enabled) + return; - m_ActionMap.m_State.DisableSingleAction(this); + m_ActionMap.m_State.DisableSingleAction(this); + } } ////REVIEW: is *not* cloning IDs here really the right thing to do? diff --git a/Packages/com.unity.inputsystem/InputSystem/Actions/InputActionMap.cs b/Packages/com.unity.inputsystem/InputSystem/Actions/InputActionMap.cs index 8918757d89..e5b5f2cb2f 100644 --- a/Packages/com.unity.inputsystem/InputSystem/Actions/InputActionMap.cs +++ b/Packages/com.unity.inputsystem/InputSystem/Actions/InputActionMap.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Linq; using Unity.Collections; +using Unity.Profiling; using UnityEngine.InputSystem.Utilities; ////REVIEW: given we have the global ActionPerformed callback, do we really need the per-map callback? @@ -313,6 +314,11 @@ public event Action actionTriggered remove => m_ActionCallbacks.RemoveCallback(value); } + /// + /// ProfilerMarker to measure how long it takes to resolve bindings. + /// + static readonly ProfilerMarker k_ResolveBindingsProfilerMarker = new ProfilerMarker("InputActionMap.ResolveBindings"); + /// /// Construct an action map with default values. /// @@ -1299,100 +1305,104 @@ internal bool ResolveBindingsIfNecessary() /// internal void ResolveBindings() { - // Make sure that if we trigger callbacks as part of disabling and re-enabling actions, - // we don't trigger a re-resolve while we're already resolving bindings. - using (InputActionRebindingExtensions.DeferBindingResolution()) + using (k_ResolveBindingsProfilerMarker.Auto()) { - // In case we have actions that are currently enabled, we temporarily retain the - // UnmanagedMemory of our InputActionState so that we can sync action states after - // we have re-resolved bindings. - var oldMemory = new InputActionState.UnmanagedMemory(); - try + // Make sure that if we trigger callbacks as part of disabling and re-enabling actions, + // we don't trigger a re-resolve while we're already resolving bindings. + using (InputActionRebindingExtensions.DeferBindingResolution()) { - OneOrMore> actionMaps; + // In case we have actions that are currently enabled, we temporarily retain the + // UnmanagedMemory of our InputActionState so that we can sync action states after + // we have re-resolved bindings. + var oldMemory = new InputActionState.UnmanagedMemory(); + try + { + OneOrMore> actionMaps; - // Start resolving. - var resolver = new InputBindingResolver(); + // Start resolving. + var resolver = new InputBindingResolver(); - // If we're part of an asset, we share state and thus binding resolution with - // all maps in the asset. - var needFullResolve = m_State == null; - if (m_Asset != null) - { - actionMaps = m_Asset.actionMaps; - Debug.Assert(actionMaps.Count > 0, "Asset referred to by action map does not have action maps"); + // If we're part of an asset, we share state and thus binding resolution with + // all maps in the asset. + var needFullResolve = m_State == null; + if (m_Asset != null) + { + actionMaps = m_Asset.actionMaps; + Debug.Assert(actionMaps.Count > 0, "Asset referred to by action map does not have action maps"); - // If there's a binding mask set on the asset, apply it. - resolver.bindingMask = m_Asset.m_BindingMask; + // If there's a binding mask set on the asset, apply it. + resolver.bindingMask = m_Asset.m_BindingMask; - foreach (var map in actionMaps) + foreach (var map in actionMaps) + { + needFullResolve |= map.bindingResolutionNeedsFullReResolve; + map.needToResolveBindings = false; + map.bindingResolutionNeedsFullReResolve = false; + map.controlsForEachActionInitialized = false; + } + } + else { - needFullResolve |= map.bindingResolutionNeedsFullReResolve; - map.needToResolveBindings = false; - map.bindingResolutionNeedsFullReResolve = false; - map.controlsForEachActionInitialized = false; + // Standalone action map (possibly a hidden one created for a singleton action). + // Gets its own private state. + + actionMaps = this; + needFullResolve |= bindingResolutionNeedsFullReResolve; + needToResolveBindings = false; + bindingResolutionNeedsFullReResolve = false; + controlsForEachActionInitialized = false; } - } - else - { - // Standalone action map (possibly a hidden one created for a singleton action). - // Gets its own private state. - - actionMaps = this; - needFullResolve |= bindingResolutionNeedsFullReResolve; - needToResolveBindings = false; - bindingResolutionNeedsFullReResolve = false; - controlsForEachActionInitialized = false; - } - // If we already have a state, re-use the arrays we have already allocated. - // NOTE: We will install the arrays on the very same InputActionState instance below. In the - // case where we didn't have to grow the arrays, we should end up with zero GC allocations - // here. - var hasEnabledActions = false; - InputControlList activeControls = default; - if (m_State != null) - { - // Grab a clone of the current memory. We clone because disabling all the actions - // in the map will alter the memory state and we want the state before we start - // touching it. - oldMemory = m_State.memory.Clone(); + // If we already have a state, re-use the arrays we have already allocated. + // NOTE: We will install the arrays on the very same InputActionState instance below. In the + // case where we didn't have to grow the arrays, we should end up with zero GC allocations + // here. + var hasEnabledActions = false; + InputControlList activeControls = default; + if (m_State != null) + { + // Grab a clone of the current memory. We clone because disabling all the actions + // in the map will alter the memory state and we want the state before we start + // touching it. + oldMemory = m_State.memory.Clone(); - m_State.PrepareForBindingReResolution(needFullResolve, ref activeControls, ref hasEnabledActions); + m_State.PrepareForBindingReResolution(needFullResolve, ref activeControls, ref hasEnabledActions); - // Reuse the arrays we have so that we can avoid managed memory allocations, if possible. - resolver.StartWithPreviousResolve(m_State, isFullResolve: needFullResolve); + // Reuse the arrays we have so that we can avoid managed memory allocations, if possible. + resolver.StartWithPreviousResolve(m_State, isFullResolve: needFullResolve); - // Throw away old memory. - m_State.memory.Dispose(); - } + // Throw away old memory. + m_State.memory.Dispose(); + } - // Resolve all maps in the asset. - foreach (var map in actionMaps) - resolver.AddActionMap(map); + // Resolve all maps in the asset. + foreach (var map in actionMaps) + resolver.AddActionMap(map); - // Install state. - if (m_State == null) - { - m_State = new InputActionState(); - m_State.Initialize(resolver); - } - else - { - m_State.ClaimDataFrom(resolver); + // Install state. + if (m_State == null) + { + m_State = new InputActionState(); + m_State.Initialize(resolver); + } + else + { + m_State.ClaimDataFrom(resolver); + } + + if (m_Asset != null) + { + foreach (var map in actionMaps) + map.m_State = m_State; + m_Asset.m_SharedStateForAllMaps = m_State; + } + + m_State.FinishBindingResolution(hasEnabledActions, oldMemory, activeControls, isFullResolve: needFullResolve); } - if (m_Asset != null) + finally { - foreach (var map in actionMaps) - map.m_State = m_State; - m_Asset.m_SharedStateForAllMaps = m_State; + oldMemory.Dispose(); } - - m_State.FinishBindingResolution(hasEnabledActions, oldMemory, activeControls, isFullResolve: needFullResolve); - } - finally - { - oldMemory.Dispose(); } } }