Skip to content
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