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();
}
}
}