Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
10f19c9
FIX: InputActionReference returning incorrect action when action has …
ekcoh Sep 29, 2025
f38dd04
Updated change log.
ekcoh Sep 29, 2025
79c430d
Improvements to testing, refactoring of tests, bug fixes to InputActi…
ekcoh Sep 30, 2025
27b0311
Updated changelog
ekcoh Sep 30, 2025
7c5305a
FIX: RemoveActionMap does not remove contained actions. Additional bu…
ekcoh Sep 30, 2025
b02a52e
More bug fixes and test cases for input action reference
ekcoh Oct 1, 2025
0dbc8f5
Additional simplification, fixes and test cases. Added test assembly …
ekcoh Oct 2, 2025
651526e
Added more tests, code cleanup, refactored QA script to QA asset folder.
ekcoh Oct 3, 2025
b50c8c5
Merge branch 'develop' into isxb-1584-input-action-reference
ekcoh Oct 3, 2025
769d73c
Updated CHANGELOG
ekcoh Oct 3, 2025
58f9c28
Removed development comments from source file.
ekcoh Oct 3, 2025
5497481
Undo changes to AssetDatabaseUtils that were not needed in the end.
ekcoh Oct 3, 2025
de71adb
Merge branch 'develop' into isxb-1584-input-action-reference
ekcoh Oct 3, 2025
7611d03
xmldoc fixes
ekcoh Oct 3, 2025
8f826ca
Merge branch 'isxb-1584-input-action-reference' of github.com:Unity-T…
ekcoh Oct 3, 2025
6dd0f88
Replaced Conditional by preprocessor check since Conditional cannot w…
ekcoh Oct 5, 2025
22be817
Strengthened existing test by adding verification that reference is r…
ekcoh Oct 6, 2025
007412e
Corrected and updated inline doc for InputActionReference invalidation
ekcoh Oct 6, 2025
a146ecd
Simplified InputSystemProvider since there is no reason at all to use…
ekcoh Oct 6, 2025
3af6d81
Undo bug fix of RemoveActionMap which was correct and filed bug was A…
ekcoh Oct 6, 2025
a499b40
Improved inline comment
ekcoh Oct 6, 2025
4dfc4f4
Added missing guard to mimic inclusion of test based on PWA.
ekcoh Oct 6, 2025
8c008bc
Clarified in xmldoc that removing an action map still leaves all cont…
ekcoh Oct 6, 2025
b68353d
Merge branch 'develop' into isxb-1584-input-action-reference
ekcoh Oct 7, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 33 additions & 0 deletions Assets/Editor/DumpInputActionReferences.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
using System.Collections;
using System.Collections.Generic;
using System.Text;
using UnityEngine;
using UnityEngine.InputSystem;

internal static class DumpInputActionReferences
{
private static void DumpReferences(StringBuilder sb, string prefix, InputActionReference[] references)
{
sb.Append(prefix + ":\n");
foreach (var reference in references)
{
var s = reference.action != null ? "Yes" : "No";
sb.Append($"- {reference.name} (Resolved: {s}, Asset: {reference.asset})\n");
}
}

private static void DumpReferences()
{
var sb = new StringBuilder();
DumpReferences(sb, "Loaded objects", Object.FindObjectsByType<InputActionReference>(
FindObjectsInactive.Include, FindObjectsSortMode.InstanceID));
DumpReferences(sb, "All objects:", Resources.FindObjectsOfTypeAll<InputActionReference>());
Debug.Log(sb.ToString());
}

[UnityEditor.MenuItem("QA Tools/Dump Input Action References to Console", false, 100)]
private static void Dump()
{
DumpReferences();
}
}
Copy link
Collaborator

Choose a reason for hiding this comment

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

Should this be part of our Input Debugger interface instead?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Maybe, right now I just put it into our QA asset scripts since I did it to investigate bugs and felt it had more value to keep than removing it, but maybe someone would be interested in it. I would say there is no obvious user-value in it, but maybe for ourselves. However, you likely want snapshots and this was simple and sufficient to solve such problems. I do not intend to move it there as part of this PR at least.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

However its handy to be able to see which InputActionReferences exist in memory and on disc as serialised SOs

11 changes: 11 additions & 0 deletions Assets/Editor/DumpInputActionReferences.cs.meta

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

76 changes: 76 additions & 0 deletions Assets/Tests/InputSystem.Editor/EditorPrefsTestUtils.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
using UnityEditor;

namespace Tests.InputSystem.Editor
{
/// <summary>
/// Utility to simplify editor tests with respect to editor preferences.
/// </summary>
internal static class EditorPrefsTestUtils
{
private const string EnterPlayModeOptionsEnabledKey = "EnterPlayModeOptionsEnabled";
private const string EnterPlayModeOptionsKey = "EnterPlayModeOptions";

private static bool _savedEnterPlayModeOptionsEnabled;
private static int _savedEnterPlayModeOptions;

/// <summary>
/// Call this from a tests SetUp routine to save editor preferences so they can be restored after the test.
/// </summary>
public static void SaveEditorPrefs()
{
_savedEnterPlayModeOptionsEnabled = EditorPrefs.GetBool(EnterPlayModeOptionsEnabledKey, false);
_savedEnterPlayModeOptions = EditorPrefs.GetInt(EnterPlayModeOptionsKey, (int)EnterPlayModeOptions.None);
}

/// <summary>
/// Call this from a tests TearDown routine to restore editor preferences to the state it had before the test.
/// </summary>
/// <remarks>Note that if domain reloads have not been disabled and you have a domain reload mid-test,
/// this utility will fail to restore editor preferences since the saved data will be lost.</remarks>
public static void RestoreEditorPrefs()
{
EditorPrefs.SetBool(EnterPlayModeOptionsEnabledKey, _savedEnterPlayModeOptionsEnabled);
EditorPrefs.SetInt(EnterPlayModeOptionsKey, _savedEnterPlayModeOptions);
}

/// <summary>
/// Returns whether domain reloads are disabled.
/// </summary>
/// <returns>true if domain reloads have been disabled, else false.</returns>
public static bool IsDomainReloadsDisabled()
{
return EditorPrefs.GetBool(EnterPlayModeOptionsEnabledKey, false) &&
(EditorPrefs.GetInt(EnterPlayModeOptionsKey, (int)EnterPlayModeOptions.None) &
(int)EnterPlayModeOptions.DisableDomainReload) != 0;
}

/// <summary>
/// Returns whether scene reloads are disabled.
/// </summary>
/// <returns>true if scene reloads have been disabled, else false.</returns>
public static bool IsSceneReloadsDisabled()
{
return EditorPrefs.GetBool(EnterPlayModeOptionsEnabledKey, false) &&
(EditorPrefs.GetInt(EnterPlayModeOptionsKey, (int)EnterPlayModeOptions.None) &
(int)EnterPlayModeOptions.DisableSceneReload) != 0;
}

/// <summary>
/// Call this from within a test to temporarily enable domain reload.
/// </summary>
public static void EnableDomainReload()
{
EditorPrefs.SetBool(EnterPlayModeOptionsEnabledKey, false);
}

Check warning on line 64 in Assets/Tests/InputSystem.Editor/EditorPrefsTestUtils.cs

View check run for this annotation

Codecov GitHub.com / codecov/patch

Assets/Tests/InputSystem.Editor/EditorPrefsTestUtils.cs#L62-L64

Added lines #L62 - L64 were not covered by tests

/// <summary>
/// Call this from within a test to temporarily disable domain reload (and scene reloads).
/// </summary>
public static void DisableDomainReload()
{
EditorPrefs.SetInt(EnterPlayModeOptionsKey, (int)(EnterPlayModeOptions.DisableDomainReload |
EnterPlayModeOptions.DisableSceneReload));
EditorPrefs.SetBool(EnterPlayModeOptionsEnabledKey, true);
}
}
}
3 changes: 3 additions & 0 deletions Assets/Tests/InputSystem.Editor/EditorPrefsTestUtils.cs.meta

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

197 changes: 197 additions & 0 deletions Assets/Tests/InputSystem.Editor/InputActionReferenceEditorTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,197 @@
using System;
using System.Collections;
using System.Linq;
using NUnit.Framework;
using Tests.InputSystem.Editor;
using UnityEditor;
using UnityEditor.SceneManagement;
using UnityEngine;
using UnityEngine.InputSystem;
using UnityEngine.InputSystem.Editor;
using UnityEngine.SceneManagement;
using UnityEngine.TestTools;
using Object = UnityEngine.Object;

/// <summary>
/// Editor tests for <see cref="UnityEngine.InputSystem.InputActionReference"/>.
/// </summary>
/// <remarks>
/// This test need fixed asset paths since mid-test domain reloads would otherwise discard data.
///
/// Be aware that if you get failures in editor tests that switch between play mode and edit mode via coroutines
/// you might get misleading stack traces that indicate errors in different places than they actually happen.
/// At least this have been observered for exception stack traces.
/// </remarks>
internal class InputActionReferenceEditorTestsWithScene
{
private const string TestCategory = "Editor";

private Scene m_Scene;

private const string assetPath = "Assets/__InputActionReferenceEditorTests.inputactions";
private const string dummyPath = "Assets/__InputActionReferenceEditorTestsDummy.asset";
private const string scenePath = "Assets/__InputActionReferenceEditorTestsScene.unity";

private void CreateAsset()
{
var asset = ScriptableObject.CreateInstance<InputActionAsset>();

var map1 = new InputActionMap("map1");
map1.AddAction("action1");
map1.AddAction("action2");
asset.AddActionMap(map1);

System.IO.File.WriteAllText(assetPath, asset.ToJson());
Object.DestroyImmediate(asset);
AssetDatabase.ImportAsset(assetPath);
}

[SetUp]
public void SetUp()
{
// This looks odd, but when we yield into play mode from our test coroutine we may get a domain reload
// (depending on editor preferences) which will trigger another SetUp() mid-test.
if (Application.isPlaying)
return;

EditorPrefsTestUtils.SaveEditorPrefs();

m_Scene = EditorSceneManager.NewScene(NewSceneSetup.EmptyScene);
CreateAsset();

var go = new GameObject("Root");
var behaviour = go.AddComponent<InputActionBehaviour>();
var reference = InputActionImporter.LoadInputActionReferencesFromAsset(assetPath).First(
r => "action1".Equals(r.action.name));
behaviour.referenceAsField = reference;
behaviour.referenceAsReference = reference;

TestUtils.SaveScene(m_Scene, scenePath);
}

[TearDown]
public void TearDown()
{
// This looks odd, but when we yield into play mode from our test coroutine we may get a domain reload
// (depending on editor preferences) which will trigger another TearDown() mid-test.
if (Application.isPlaying)
return;

Check warning on line 78 in Assets/Tests/InputSystem.Editor/InputActionReferenceEditorTests.cs

View check run for this annotation

Codecov GitHub.com / codecov/patch

Assets/Tests/InputSystem.Editor/InputActionReferenceEditorTests.cs#L78

Added line #L78 was not covered by tests

EditorPrefsTestUtils.RestoreEditorPrefs();

// Close scene
EditorSceneManager.CloseScene(m_Scene, true);

// Clean-up
AssetDatabase.DeleteAsset(dummyPath);
AssetDatabase.DeleteAsset(assetPath);
AssetDatabase.DeleteAsset(scenePath);
}

private void DisableDomainReloads()
{
// Assumes off before running tests.
Debug.Assert(!EditorPrefsTestUtils.IsDomainReloadsDisabled());
Debug.Assert(!EditorPrefsTestUtils.IsSceneReloadsDisabled());

// Safe to store since state wouldn't be reset by domain reload.
EditorPrefsTestUtils.DisableDomainReload();
}

private static InputActionBehaviour GetBehaviour() => Object.FindObjectOfType<InputActionBehaviour>();
private static InputActionAsset GetAsset() => AssetDatabase.LoadAssetAtPath<InputActionAsset>(assetPath);

// For unclear reason, NUnit fails to assert throwing exceptions after transition into play-mode.
// So until that can be sorted out, we do it manually (in the same way) ourselves.
private static void AssertThrows<T>(Action action) where T : Exception
{
var exceptionThrown = false;
try
{
action();
}

Check warning on line 112 in Assets/Tests/InputSystem.Editor/InputActionReferenceEditorTests.cs

View check run for this annotation

Codecov GitHub.com / codecov/patch

Assets/Tests/InputSystem.Editor/InputActionReferenceEditorTests.cs#L112

Added line #L112 was not covered by tests
catch (InvalidOperationException)
{
exceptionThrown = true;
}
Assert.IsTrue(exceptionThrown, $"Expected exception of type {typeof(T)} to be thrown but it was not.");
}

private static bool[] _disableDomainReloadsValues = new bool[] { false, true };

[UnityTest]
[Category(TestCategory)]
[Description("https://issuetracker.unity3d.com/product/unity/issues/guid/ISXB-1584")]
public IEnumerator ReferenceSetInPlaymodeShouldBeRestored_WhenExitingPlaymode(
[ValueSource(nameof(_disableDomainReloadsValues))] bool disableDomainReloads)
{
if (disableDomainReloads)
DisableDomainReloads();

// Edit-mode section
{
// Sanity check our initial edit-mode state
var obj = GetBehaviour();
Assert.That(obj.referenceAsField.action, Is.SameAs(GetAsset().FindAction("map1/action1")));
Assert.That(obj.referenceAsReference.action, Is.SameAs(GetAsset().FindAction("map1/action1")));

// Enter play-mode (This will lead to domain reload by default).
yield return new EnterPlayMode();
}

// Play-mode section
{
var obj = GetBehaviour();
var editModeAction = GetAsset().FindAction("map1/action1");
var playModeAction = GetAsset().FindAction("map1/action2");

// Make sure our action reference is consistent in play-mode
Assert.That(obj.referenceAsField.action, Is.SameAs(editModeAction));

// ISXB-1584: Attempting assignment of persisted input action reference in play-mode in editor.
// Rationale: We cannot allow this since it would corrupt the source asset since changes applied to SO
// mapped to an asset isn't reverted when exiting play-mode.
//
// Here we would like to do:
// Assert.Throws<InvalidOperationException>(() => obj.reference.Set(null));
//
// But we can't since it would fail with NullReferenceException.
// It turns out that because of the domain reload / Unity’s internal serialization quirks, the obj is
// sometimes null inside the lambda when NUnit captures it for execution. So to work around this we
// instead do the same kind of check manually for now which doesn't seem to have this problem.
//
// It is odd since NUnit does basically does the same thing (apart from wrapping the lambda as a
// TestDelegate). So the WHY for this problem remains unclear for now.
AssertThrows<InvalidOperationException>(() => obj.referenceAsField.Set(playModeAction));
AssertThrows<InvalidOperationException>(() => obj.referenceAsReference.Set(editModeAction));

// Make sure there were no side-effects.
Assert.That(obj.referenceAsField.action, Is.SameAs(editModeAction));
Assert.That(obj.referenceAsReference.action, Is.SameAs(editModeAction));

// Correct usage is to use a run-time assigned input action reference instead. It is up to the user
// to decide whether this reference should additionally be persisted (which is possible by saving it to
// and asset, or by using SerializeReference).
obj.referenceAsField = InputActionReference.Create(playModeAction);
obj.referenceAsReference = InputActionReference.Create(playModeAction);

// Makes sure we have the expected reference.
Assert.That(obj.referenceAsField.action, Is.SameAs(playModeAction));
Assert.That(obj.referenceAsReference.action, Is.SameAs(playModeAction));

// Exit play-mode (This will lead to domain reload by default).
yield return new ExitPlayMode();
}

// Edit-mode section
{
// Make sure our reference is back to its initial edit mode state
var obj = GetBehaviour();
var editModeAction = GetAsset().FindAction("map1/action1");
Assert.That(obj.referenceAsField.action, Is.SameAs(editModeAction));
Assert.That(obj.referenceAsReference.action, Is.SameAs(editModeAction));
}

yield return null;
}
}

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading