Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

NEW: ProfilerMarkers for measuring enabling/disabling of InputActions and for binding resolution. #2056

Merged
merged 9 commits into from
Dec 12, 2024
5 changes: 4 additions & 1 deletion Assets/Tests/InputSystem/CorePerformanceTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Copy link
Collaborator

Choose a reason for hiding this comment

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

please also add "InputAction.Disable" here, thanks

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

added in 539258b

};

[PrebuildSetup(typeof(ProjectWideActionsBuildSetup))]
Expand Down
2 changes: 2 additions & 0 deletions Assets/Tests/InputSystem/Plugins/UITests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1793,13 +1793,15 @@ 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
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

EventType.PointerMove was wrapped in #if UNITY_2021_2_OR_NEWER in its declaration.

Assert.That(scene.rightChildReceiver.events,
Has.Exactly(1).With.Property("type").EqualTo(EventType.PointerMove).And
.Matches((UICallbackReceiver.Event e) => e.pointerData.device == touchScreen).And
.Matches((UICallbackReceiver.Event e) => e.pointerData.touchId == 2).And
.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,
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 @@ -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

Expand Down
37 changes: 25 additions & 12 deletions Packages/com.unity.inputsystem/InputSystem/Actions/InputAction.cs
Original file line number Diff line number Diff line change
@@ -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;
Expand Down Expand Up @@ -681,6 +682,12 @@ public bool wantsInitialStateCheck
}
}

/// <summary>
/// ProfilerMarker for measuring the enabling/disabling of InputActions.
/// </summary>
static readonly ProfilerMarker k_InputActionEnableProfilerMarker = new ProfilerMarker("InputAction.Enable");
static readonly ProfilerMarker k_InputActionDisableProfilerMarker = new ProfilerMarker("InputAction.Disable");

/// <summary>
/// Construct an unnamed, free-standing action that is not part of any map or asset
/// and has no bindings. Bindings can be added with <see
Expand Down Expand Up @@ -899,18 +906,21 @@ public override string ToString()
/// <seealso cref="enabled"/>
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);
}
}

/// <summary>
Expand All @@ -928,10 +938,13 @@ public void Enable()
/// <seealso cref="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?
Expand Down
164 changes: 87 additions & 77 deletions Packages/com.unity.inputsystem/InputSystem/Actions/InputActionMap.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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?
Expand Down Expand Up @@ -313,6 +314,11 @@ public event Action<InputAction.CallbackContext> actionTriggered
remove => m_ActionCallbacks.RemoveCallback(value);
}

/// <summary>
/// ProfilerMarker to measure how long it takes to resolve bindings.
/// </summary>
static readonly ProfilerMarker k_ResolveBindingsProfilerMarker = new ProfilerMarker("InputActionMap.ResolveBindings");

/// <summary>
/// Construct an action map with default values.
/// </summary>
Expand Down Expand Up @@ -1299,100 +1305,104 @@ internal bool ResolveBindingsIfNecessary()
/// </remarks>
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<InputActionMap, ReadOnlyArray<InputActionMap>> 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<InputActionMap, ReadOnlyArray<InputActionMap>> 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<InputControl> 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<InputControl> 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();
}
}
}
Expand Down
Loading