Skip to content

CHANGE: Split Runtime and Editor code into separate folders to prepare for Module migration#2321

Open
jfreire-unity wants to merge 119 commits intodevelopfrom
joao/editor-runtime-split-for-module
Open

CHANGE: Split Runtime and Editor code into separate folders to prepare for Module migration#2321
jfreire-unity wants to merge 119 commits intodevelopfrom
joao/editor-runtime-split-for-module

Conversation

@jfreire-unity
Copy link
Copy Markdown
Collaborator

@jfreire-unity jfreire-unity commented Jan 12, 2026

Description

Background & Goal

NOTE 🤯: This PR is huge and hard to review, but a lot of the changes are moving files and folders around. I know it's not easy to navigate these huge changes so I tried my best to do a good PR description.

The Unity Input System package historically had UnityEditor.* references scattered throughout its runtime code, guarded by #if UNITY_EDITOR preprocessor directives, but still in the same files as runtime logic.

This was making the codebase hard to port into a Unity module in trunk (unity/unity), and it was also harder to reason about the Editor vs Standalone player boundary.

This PR tries to break the dependency of Editor references in Runtime code to help us migrate to a Unity module and does the following changes:

  • Zero UnityEditor.* references remain in the runtime source files.
  • All editor-specific logic has been moved to dedicated editor-side files or wired up via internal callback hooks.
  • The single Unity.InputSystem assembly is kept . The folder reorganisation (Runtime/ and Editor/ subfolders) is the externally visible result, making forward-ports and moving git history to trunk and eventual module migration easier. We decided not to introduce 2 assemblies as this would duplicate our upgrade path when projects upgrade to a Unity version using the new Input System module.

Ideally, we would have liked to also have a Runtime and Editor assembly.
That work was initially done but then we reverted it due to the last point above.

This PR was done with the help of agentic coding as well.


High-Level Changes

Area Change
Folder structure All runtime source moved to InputSystem/Runtime/, all editor source to InputSystem/Editor/. 1,201 files renamed (history preserved via git mv).
Assembly Unity.InputSystem.asmdef kept as single monolith. A Unity.InputSystem.Editor asmdef was prototyped and then rolled back — the folder split remains as foundation for a future module migration.
Runtime → Editor communication Replaced every direct UnityEditor.* call in runtime files with internal static callback fields (Action, Func). The Editor registers these at startup via [InitializeOnLoad]. In player builds these fields are null and all call sites are null-safe (?.Invoke()).
New file: InputSystemEditorInitializer.cs Central [InitializeOnLoad] class in Editor/ that owns all editor lifecycle concerns previously scattered across the runtime (play-mode changes, domain reload, asset tracking, analytics, Unity Remote, remoting, etc.).
New file: InputSystemState.cs Extracted domain-reload state (previously coupled to InputSystemObject and editor-specific paths) into its own runtime class to help with the dependency boundary.
Editor classes extracted 13 *Editor classes (Interaction editors, Composite editors, Processor editors, OnScreen editors) removed from runtime files and placed in dedicated files under Editor/.
Editor-only files relocated 11 files that were already fully wrapped in #if UNITY_EDITOR moved to Editor/ (e.g. PlayerInputEditor, UnityRemoteSupport, iOSPostProcessBuild).
Test fixtures updated InputTestRuntime, InputTestStateManager updated to reflect the new interface contracts (e.g. onPlayModeChanged: Action<int> instead of Action<PlayModeStateChange>).

Pattern used for these changes

The approach used throughout this PR is maybe worth understanding before reviewing individual files:

// Runtime side — declares an internal hook, never calls UnityEditor
internal static Action<InputActionAsset> s_OnActionsChanging;

// usage — null-safe, works in player (null) and editor (set)
s_OnActionsChanging?.Invoke(asset);

// Editor side — InputSystemEditorInitializer registers at load time
[InitializeOnLoad]
static class InputSystemEditorInitializer
{
    static InputSystemEditorInitializer()
    {
        InputSystem.s_OnActionsChanging = ValidateAndTrackActions;
        // ... all other hooks ...
    }
}

This pattern appears in: InputSystem.cs, InputManager.cs, NativeInputRuntime.cs, InputActionSetupExtensions.cs, InputActionAsset.cs, InputActionReference.cs, EnhancedTouchSupport.cs, RemoteInputPlayerConnection.cs, OnScreenStick.cs, TrackedPoseDriver.cs, InputSystemUIInputModule.cs.

⚠️ ❗❗❗ Feel free to let me know if you disagree with this approach and if you have a better one. I'm open to suggestions ❗❗❗ ⚠️


Review Guide Suggestions

1. InputSystem/Runtime/InputSystem.cs

  • Confirm using UnityEditor; is gone.
  • Check the four new internal hooks (s_OnSettingsChanged, s_OnActionsChanging, s_ShouldEnableActions, s_OnPlayModeChangeCallback) are used consistently and null-safely.
  • The #if UNITY_EDITOR block for OnPlayModeChange is intentional — it is a forwarding shim for tests that call it directly.

2. InputSystem/Runtime/NativeInputRuntime.cs

  • Previously contained direct EditorApplication.isPlaying, EditorApplication.isPaused, InternalEditorUtility.isApplicationActive, and EditorAnalytics calls.
  • Now uses plain internal bool fields (m_IsInPlayMode, m_IsEditorPaused, m_IsEditorActive) and callbacks (m_SendEditorAnalytic, m_SetUnityRemoteMessageHandler, etc.).
  • Verify the UpdateEditorState() method in InputSystemEditorInitializer is pumping those fields on EditorApplication.update.

3. InputSystem/Runtime/InputManager.cs

  • Replaced EditorApplication.isPausedm_Runtime.isEditorPaused (from IInputRuntime).
  • InputSystem.s_SystemObject references replaced with local m_ExitEditModeTime / m_EnterPlayModeTime fields.
  • ProjectWideActionsBuildProvider.actionsToIncludeInPlayerBuild replaced with s_GetProjectWideActions callback.

4. InputSystem/Runtime/IInputRuntime.cs

  • onPlayModeChanged changed from Action<PlayModeStateChange> to Action<int> — removes the UnityEditor enum from the interface.
  • Added bool isEditorPaused { get; }.

5. InputSystem/Editor/InputSystemEditorInitializer.cs (new file)

  • This is the single source of truth for all editor-runtime "wiring". Verify that the callback registered here have a corresponding field in the runtime.
  • Check play-mode lifecycle: OnEditorPlayModeStateChanged → sets m_IsInPlayMode, m_IsEditorPaused, dispatches DispatchPlayModeChange, etc.
  • Check domain-reload path: InputSystemState is loaded/restored here.

6. Extracted *Editor classes (Editor/Actions/Interactions/, Editor/Actions/Composites/, Editor/Controls/Processors/, Editor/Plugins/OnScreen/)

  • These were verbatim moves from their parent runtime files — no logic changes.
  • Maybe worth a quick sanity check that nothing is missing.

7. InputSystem/Runtime/Actions/InputActionReference.cs, InputActionAsset.cs, EnhancedTouchSupport.cs

  • Light-touch changes: direct AssetDatabase/AssemblyReloadEvents calls replaced with callback fields.
  • Confirm the callback fields are #if UNITY_EDITOR-guarded.

8. Test fixtures — Tests/TestFixture/

  • InputTestRuntime.cs: onPlayModeChanged is now Action<int> — make sure test call sites cast correctly.
  • InputTestStateManager.cs: there were some naming changes, so something to be aware.

9. Assembly / .asmdef files

  • Unity.InputSystem.asmdef should still reference a single assembly (no Editor split).
  • Downstream asmdefs (samples, test projects) should all still point to Unity.InputSystem only.
  • AssemblyInfo.cs: InternalsVisibleTo entries cover test assemblies — verify no entry was accidentally dropped.
  • A new .asmdef was created for InputSystemForUI.Editor.asmdef, since the previous folder containing InputSystemForUI.asmdef was split into Editor and Runtime. This is part of internal API so I don't see any breakages in this area when upgrading.

Testing status & QA

Built and run Samples on both Windows and macOS Play-mode and Standalone builds.

I recommend validating all the Samples work and do kind of the sames tests as we would do for a Release since this PR touches a lot of things.

Overall Product Risks

Please rate the potential complexity and halo effect from low to high for the reviewers. Note down potential risks to specific Editor branches if any.

  • Complexity: Medium (the main changes are straight forward but the fact that are so many changes maybe overwhelming to rewview)
  • Halo Effect: High

Comments to reviewers

Some sanity check guides:

  • Zero using UnityEditor; in any file under InputSystem/Runtime/
  • InputSystemEditorInitializer registers every callback that has a field on the runtime side
  • InputTestRuntime compiles and tests pass with the updated Action<int> signature
  • No asmdef downstream of Unity.InputSystem was accidentally wired to a now-deleted Unity.InputSystem.Editor assembly. Hopefully there are not left overs of this initial work.

One thing is not in place is a mechanism to guarantee we don't leak UnityEditor references in the Runtime folder. I'll follow up on that in another PR.

Checklist

Before review:

  • Changelog entry added.
    • Explains the change in Changed, Fixed, Added sections.
    • For API change contains an example snippet and/or migration example.
    • JIRA ticket linked, example (case %%). If it is a private issue, just add the case ID without a link.
    • Jira port for the next release set as "Resolved".
  • Tests added/changed, if applicable.
    • Functional tests Area_CanDoX, Area_CanDoX_EvenIfYIsTheCase, Area_WhenIDoX_AndYHappens_ThisIsTheResult.
    • Performance tests.
    • Integration tests.
  • Docs for new/changed API's.
    • Xmldoc cross references are set correctly.
    • Added explanation how the API works.
    • Usage code examples added.
    • The manual is updated, if needed.

During merge:

  • Commit message for squash-merge is prefixed with one of the list:
    • NEW: ___.
    • FIX: ___.
    • DOCS: ___.
    • CHANGE: ___.
    • RELEASE: 1.1.0-preview.3.

- Move InputSystem/* to Runtime/ (preserving history)
- Move InputSystem/Editor to Editor/ (preserving history)
- Add meta files for new folder structure
- Follows Unity package layout conventions
- All file history preserved via git mv
Also exposes the required internals between assemblies.
This is done to avoid calling into Edito specific code. Instead, it will
be called if Editor code has registered a callback.
Due to refactoring, a lot of paths still contained the InputSystem folder path that no longer exists. We only have Editor and Runtime folders.
Comment thread Packages/com.unity.inputsystem/Editor/Plugins/HID/HIDSupportEditorInitializer.cs Outdated
@josepmariapujol-unity josepmariapujol-unity marked this pull request as ready for review January 30, 2026 10:37
@u-pr
Copy link
Copy Markdown
Contributor

u-pr Bot commented Jan 30, 2026

PR Reviewer Guide 🔍

Here are some key observations to aid the review process:

⏱️ Estimated effort to review: 4 🔵🔵🔵🔵⚪

The PR involves significant architectural changes separating Runtime and Editor assemblies, with many files added, moved, or modified, and contains a large amount of commented-out code that requires cleanup.
🏅 Score: 40

The PR is explicitly marked as DRAFT and contains a significant amount of code (implementation and tests) that is commented out rather than functioning, rendering the build potentially incomplete or untested.
🧪 PR contains tests
🔒 No security concerns identified
⚡ Recommended focus areas for review

Disabled Tests

The entire content of this test file (and others like UITests.cs partially) has been commented out. This significantly reduces test coverage and hides potential regressions introduced by the assembly split.

// using System;
// using System.Collections.Generic;
// using System.Linq;
// using NUnit.Framework;
// using UnityEngine;
// using UnityEngine.InputSystem;
// using UnityEngine.InputSystem.Controls;
// using UnityEngine.InputSystem.EnhancedTouch;
// using UnityEngine.InputSystem.LowLevel;
// using UnityEngine.InputSystem.Utilities;
// using UnityEngine.TestTools.Utils;
// using Touch = UnityEngine.InputSystem.EnhancedTouch.Touch;
// using Property = NUnit.Framework.PropertyAttribute;
// using TouchPhase = UnityEngine.InputSystem.TouchPhase;
//
// #if UNITY_EDITOR
// using UnityEngine.InputSystem.Editor;
// #endif
//
// internal class EnhancedTouchTests : CoreTestsFixture
// {
//     private TouchSimulation m_OldTouchSimulationInstance;
//
//     public override void Setup()
//     {
//         base.Setup();
//
//         // Disable() will not reset this so default initialize it here.
//         Touch.s_GlobalState.historyLengthPerFinger = 64;
//
//         if (!TestContext.CurrentContext.Test.Properties.ContainsKey("EnhancedTouchDisabled"))
//         {
//             InputSystem.AddDevice<Touchscreen>();
//             EnhancedTouchSupport.Enable();
//         }
//
//         // Make sure we don't run into interference with a TouchSimulation instance that may
//         // already be in place.
//         m_OldTouchSimulationInstance = TouchSimulation.s_Instance;
//         TouchSimulation.s_Instance = null;
//     }
//
//     public override void TearDown()
//     {
//         EnhancedTouchSupport.Disable();
//
//         // Make sure cleanup really did clean up.
//         Assert.That(Touch.s_GlobalState.touchscreens.length, Is.EqualTo(0));
//         Assert.That(Touch.s_GlobalState.playerState, Is.EqualTo(default(Touch.FingerAndTouchState)));
//         #if UNITY_EDITOR
//         Assert.That(Touch.s_GlobalState.editorState, Is.EqualTo(default(Touch.FingerAndTouchState)));
//         #endif
//
//         // Some state is kept alive in-between Disable/Enable. Manually clean it out.
//         Touch.s_GlobalState.onFingerDown = default;
//         Touch.s_GlobalState.onFingerUp = default;
//         Touch.s_GlobalState.onFingerMove = default;
//
//         TouchSimulation.Destroy();
//         TouchSimulation.s_Instance = m_OldTouchSimulationInstance;
//         m_OldTouchSimulationInstance = null;
//
//         base.TearDown();
//     }
//
//     [Test]
//     [Category("EnhancedTouch")]
//     [Property("EnhancedTouchDisabled", 1)]
//     public void EnhancedTouch_IsDisabledByDefault()
//     {
//         Assert.That(EnhancedTouchSupport.enabled, Is.False);
//     }
//
//     [Test]
//     [Category("EnhancedTouch")]
//     [Property("EnhancedTouchDisabled", 1)]
//     public void EnhancedTouch_ThrowsExceptionWhenNotEnabled()
//     {
//         Assert.That(() => Touch.activeFingers, Throws.InvalidOperationException);
//         Assert.That(() => Touch.activeTouches, Throws.InvalidOperationException);
//         Assert.That(() => Touch.fingers, Throws.InvalidOperationException);
//         Assert.That(() => Touch.screens, Throws.InvalidOperationException);
//     }
//
//     [Test]
//     [Category("EnhancedTouch")]
//     [Property("EnhancedTouchDisabled", 1)]
//     public void EnhancedTouch_CanBeDisabledAndEnabled()
//     {
//         InputSystem.AddDevice<Touchscreen>();
//
//         EnhancedTouchSupport.Enable();
//         Assert.That(EnhancedTouchSupport.enabled, Is.True);
//
//         EnhancedTouchSupport.Disable();
//         Assert.That(EnhancedTouchSupport.enabled, Is.False);
//     }
//
//     [Test]
//     [Category("EnhancedTouch")]
//     [Property("EnhancedTouchDisabled", 1)]
//     public void EnhancedTouch_CanBeDisabledAndEnabled_WithoutTouchscreenPresent()
//     {
//         EnhancedTouchSupport.Enable();
//         Assert.That(EnhancedTouchSupport.enabled, Is.True);
//
//         EnhancedTouchSupport.Disable();
//         Assert.That(EnhancedTouchSupport.enabled, Is.False);
//     }
//
//     // The following tests deal with the fact that the input system potentially maintains more than one view on input
//     // because it concurrently supports input state queries in fixed update, dynamic/manual update, and editor updates.
//     // For the touch system, this means that we have to potentially track several different versions of state, too.
//     // The tests here go through the various combinations and make sure that Touch captures information correctly.
//     #region Update Types / Modes
//
//     [Test]
//     [Category("EnhancedTouch")]
//     [TestCase(InputSettings.UpdateMode.ProcessEventsManually, InputUpdateType.Manual)]
//     [TestCase(InputSettings.UpdateMode.ProcessEventsInDynamicUpdate, InputUpdateType.Dynamic)]
//     [TestCase(InputSettings.UpdateMode.ProcessEventsInFixedUpdate, InputUpdateType.Fixed)]
//     public void EnhancedTouch_SupportsInputUpdateIn(InputSettings.UpdateMode updateMode, InputUpdateType updateType)
//     {
//         ResetTime();
//
//         InputSystem.settings.updateMode = updateMode;
//         runtime.currentTimeForFixedUpdate += Time.fixedDeltaTime;
//         BeginTouch(1, new Vector2(0.123f, 0.234f), queueEventOnly: true);
//         InputSystem.Update(updateType);
//
//         Assert.That(Touch.activeTouches, Has.Count.EqualTo(1));
//         Assert.That(Touch.activeTouches[0].screenPosition,
//             Is.EqualTo(new Vector2(0.123f, 0.234f)).Using(Vector2EqualityComparer.Instance));
//     }
//
//     #if UNITY_EDITOR
//     [Test]
//     [Category("EnhancedTouch")]
//     [TestCase(InputSettings.UpdateMode.ProcessEventsManually)]
//     [TestCase(InputSettings.UpdateMode.ProcessEventsInDynamicUpdate)]
//     [TestCase(InputSettings.UpdateMode.ProcessEventsInFixedUpdate)]
//     public void EnhancedTouch_SupportsEditorUpdates(InputSettings.UpdateMode updateMode)
//     {
//         ResetTime();
//
//         InputSystem.settings.editorInputBehaviorInPlayMode = default;
//
//         // To better observe that play mode and edit mode state is indeed independent and handled
//         // correctly, suppress resetting of the touch device when focus is lost to the player.
//         runtime.runInBackground = true;
//         SetCanRunInBackground(Touchscreen.current);
//
//         InputSystem.settings.updateMode = updateMode;
//         runtime.currentTimeForFixedUpdate += Time.fixedDeltaTime;
//         // Run one player update with data.
//         BeginTouch(1, new Vector2(0.123f, 0.234f));
//
//         Assert.That(Touch.activeTouches, Has.Count.EqualTo(1));
//
//         // And make sure we're not seeing the data in the editor.
//         runtime.PlayerFocusLost();
//         InputSystem.Update(InputUpdateType.Editor);
//
//         Assert.That(Touch.activeTouches, Is.Empty);
//
//         // Feed some data into editor state.
//         BeginTouch(2, new Vector2(0.234f, 0.345f), queueEventOnly: true);
//         InputSystem.Update(InputUpdateType.Editor);
//
//         Assert.That(Touch.activeTouches, Has.Count.EqualTo(1));
//         Assert.That(Touch.activeTouches[0].touchId, Is.EqualTo(2));
//
//         // Switch back to player.
//         runtime.PlayerFocusGained();
//         InputSystem.Update();
//
//         Assert.That(Touch.activeTouches, Has.Count.EqualTo(1));
//         Assert.That(Touch.activeTouches[0].touchId, Is.EqualTo(1));
//     }
//
//     #endif
//
//     #endregion
//
//     [Test]
//     [Category("EnhancedTouch")]
//     public void EnhancedTouch_CanGetActiveTouches()
//     {
//         // Begin and move in same frame.
//         BeginTouch(1, new Vector2(0.123f, 0.234f), queueEventOnly: true);
//         MoveTouch(1, new Vector2(0.234f, 0.345f), queueEventOnly: true);
//         // Begin only.
//         BeginTouch(2, new Vector2(0.345f, 0.456f), queueEventOnly: true);
//         // Begin, move, and end in same frame.
//         BeginTouch(3, new Vector2(0.456f, 0.567f), queueEventOnly: true);
//         MoveTouch(3, new Vector2(0.111f, 0.222f), queueEventOnly: true); // This one should get ignored.
//         EndTouch(3, new Vector2(0.567f, 0.678f), queueEventOnly: true);
//         // Begin only but reusing previous touch ID.
//         BeginTouch(3, new Vector2(0.678f, 0.789f), queueEventOnly: true);
//
//         InputSystem.Update();
//
//         Assert.That(Touch.activeTouches, Has.Count.EqualTo(4));
//
//         // When we begin and move a touch in the same frame, the phase should be Began, *NOT* Moved.
//         Assert.That(Touch.activeTouches, Has.Exactly(1)
//             .With.Property("touchId").EqualTo(1)
//             .And.With.Property("phase").EqualTo(TouchPhase.Began)
//             .And.With.Property("screenPosition").EqualTo(new Vector2(0.123f, 0.234f)).Using(Vector2EqualityComparer.Instance)
//             .And.With.Property("delta").EqualTo(default(Vector2)));
//
//         Assert.That(Touch.activeTouches, Has.Exactly(1)
//             .With.Property("touchId").EqualTo(2)
//             .And.With.Property("phase").EqualTo(TouchPhase.Began)
//             .And.With.Property("screenPosition").EqualTo(new Vector2(0.345f, 0.456f)).Using(Vector2EqualityComparer.Instance)
//             .And.With.Property("delta").EqualTo(default(Vector2)));
//
//         // A touch that begins and ends in the same frame, will see a Began in the current frame and a separate Ended in the next
//         // (even though there was no actual activity on the touch that frame).
//         Assert.That(Touch.activeTouches, Has.Exactly(1)
//             .With.Property("touchId").EqualTo(3)
//             .And.With.Property("phase").EqualTo(TouchPhase.Began)
//             .And.With.Property("screenPosition").EqualTo(new Vector2(0.456f, 0.567f)).Using(Vector2EqualityComparer.Instance)
//             .And.With.Property("delta").EqualTo(default(Vector2)));
//
//         Assert.That(Touch.activeTouches, Has.Exactly(1)
//             .With.Property("touchId").EqualTo(3)
//             .And.With.Property("phase").EqualTo(TouchPhase.Began)
//             .And.With.Property("screenPosition").EqualTo(new Vector2(0.678f, 0.789f)).Using(Vector2EqualityComparer.Instance)
//             .And.With.Property("delta").EqualTo(default(Vector2)));
//
//         InputSystem.Update();
//
//         Assert.That(Touch.activeTouches, Has.Count.EqualTo(4));
//
//         Assert.That(Touch.activeTouches, Has.Exactly(1)
//             .With.Property("touchId").EqualTo(1)
//             .And.With.Property("phase").EqualTo(TouchPhase.Moved)
//             .And.With.Property("screenPosition").EqualTo(new Vector2(0.234f, 0.345f)).Using(Vector2EqualityComparer.Instance)
//             .And.With.Property("delta").EqualTo(new Vector2(0.111f, 0.111f)).Using(Vector2EqualityComparer.Instance));
//
//         Assert.That(Touch.activeTouches, Has.Exactly(1)
//             .With.Property("touchId").EqualTo(2)
//             .And.With.Property("phase").EqualTo(TouchPhase.Stationary)
//             .And.With.Property("screenPosition").EqualTo(new Vector2(0.345f, 0.456f)).Using(Vector2EqualityComparer.Instance)
//             .And.With.Property("delta").EqualTo(default(Vector2)));
//
//         // Ended record for touch touch #3 that began and ended in previous frame.
//         Assert.That(Touch.activeTouches, Has.Exactly(1)
//             .With.Property("touchId").EqualTo(3)
//             .And.With.Property("phase").EqualTo(TouchPhase.Ended)
//             .And.With.Property("screenPosition").EqualTo(new Vector2(0.567f, 0.678f)).Using(Vector2EqualityComparer.Instance)
//             .And.With.Property("delta").EqualTo(new Vector2(0.111f, 0.111f)).Using(Vector2EqualityComparer.Instance));
//
//         Assert.That(Touch.activeTouches, Has.Exactly(1)
//             .With.Property("touchId").EqualTo(3)
//             .And.With.Property("phase").EqualTo(TouchPhase.Stationary)
//             .And.With.Property("screenPosition").EqualTo(new Vector2(0.678f, 0.789f)).Using(Vector2EqualityComparer.Instance)
//             .And.With.Property("delta").EqualTo(default(Vector2)));
//
//         InputSystem.Update();
//
//         Assert.That(Touch.activeTouches, Has.Count.EqualTo(3));
//
//         Assert.That(Touch.activeTouches, Has.Exactly(1)
//             .With.Property("touchId").EqualTo(1)
//             .And.With.Property("phase").EqualTo(TouchPhase.Stationary)
//             .And.With.Property("screenPosition").EqualTo(new Vector2(0.234f, 0.345f)).Using(Vector2EqualityComparer.Instance)
//             .And.With.Property("delta").EqualTo(default(Vector2)));
//
//         Assert.That(Touch.activeTouches, Has.Exactly(1)
//             .With.Property("touchId").EqualTo(2)
//             .And.With.Property("phase").EqualTo(TouchPhase.Stationary)
//             .And.With.Property("screenPosition").EqualTo(new Vector2(0.345f, 0.456f)).Using(Vector2EqualityComparer.Instance)
//             .And.With.Property("delta").EqualTo(default(Vector2)));
//
//         Assert.That(Touch.activeTouches, Has.Exactly(1)
//             .With.Property("touchId").EqualTo(3)
//             .And.With.Property("phase").EqualTo(TouchPhase.Stationary)
//             .And.With.Property("screenPosition").EqualTo(new Vector2(0.678f, 0.789f)).Using(Vector2EqualityComparer.Instance)
//             .And.With.Property("delta").EqualTo(default(Vector2)));
//
//         InputSystem.Update();
//
//         Assert.That(Touch.activeTouches, Has.Count.EqualTo(3));
//
//         Assert.That(Touch.activeTouches, Has.Exactly(1)
//             .With.Property("touchId").EqualTo(1)
//             .And.With.Property("phase").EqualTo(TouchPhase.Stationary)
//             .And.With.Property("screenPosition").EqualTo(new Vector2(0.234f, 0.345f)).Using(Vector2EqualityComparer.Instance)
//             .And.With.Property("delta").EqualTo(default(Vector2)));
//
//         Assert.That(Touch.activeTouches, Has.Exactly(1)
//             .With.Property("touchId").EqualTo(2)
//             .And.With.Property("phase").EqualTo(TouchPhase.Stationary)
//             .And.With.Property("screenPosition").EqualTo(new Vector2(0.345f, 0.456f)).Using(Vector2EqualityComparer.Instance)
//             .And.With.Property("delta").EqualTo(default(Vector2)));
//
//         Assert.That(Touch.activeTouches, Has.Exactly(1)
//             .With.Property("touchId").EqualTo(3)
//             .And.With.Property("phase").EqualTo(TouchPhase.Stationary)
//             .And.With.Property("screenPosition").EqualTo(new Vector2(0.678f, 0.789f)).Using(Vector2EqualityComparer.Instance)
//             .And.With.Property("delta").EqualTo(default(Vector2)));
//
//         EndTouch(3, new Vector2(0.111f, 0.222f), queueEventOnly: true);
//         EndTouch(2, new Vector2(0.222f, 0.333f), queueEventOnly: true);
//         EndTouch(1, new Vector2(0.333f, 0.444f), queueEventOnly: true);
//
//         InputSystem.Update();
//
//         Assert.That(Touch.activeTouches, Has.Count.EqualTo(3));
//
//         Assert.That(Touch.activeTouches, Has.Exactly(1)
//             .With.Property("touchId").EqualTo(1)
//             .And.With.Property("phase").EqualTo(TouchPhase.Ended)
//             .And.With.Property("screenPosition").EqualTo(new Vector2(0.333f, 0.444f)).Using(Vector2EqualityComparer.Instance)
//             .And.With.Property("delta").EqualTo(new Vector2(0.099f, 0.099f)).Using(Vector2EqualityComparer.Instance));
//
//         Assert.That(Touch.activeTouches, Has.Exactly(1)
//             .With.Property("touchId").EqualTo(2)
//             .And.With.Property("phase").EqualTo(TouchPhase.Ended)
//             .And.With.Property("screenPosition").EqualTo(new Vector2(0.222f, 0.333f)).Using(Vector2EqualityComparer.Instance)
//             .And.With.Property("delta").EqualTo(new Vector2(-0.123f, -0.123f)).Using(Vector2EqualityComparer.Instance));
//
//         Assert.That(Touch.activeTouches, Has.Exactly(1)
//             .With.Property("touchId").EqualTo(3)
//             .And.With.Property("phase").EqualTo(TouchPhase.Ended)
//             .And.With.Property("screenPosition").EqualTo(new Vector2(0.111f, 0.222f)).Using(Vector2EqualityComparer.Instance)
//             .And.With.Property("delta").EqualTo(new Vector2(-0.567f, -0.567f)).Using(Vector2EqualityComparer.Instance));
//
//         InputSystem.Update();
//
//         Assert.That(Touch.activeTouches, Is.Empty);
//     }
//
//     [Test]
//     [Category("EnhancedTouch")]
//     public void EnhancedTouch_DeltasInActiveTouchesAccumulateAndReset()
//     {
//         // Only Began in frame.
//         BeginTouch(1, new Vector2(0.111f, 0.222f), queueEventOnly: true);
//         // Began and Moved in same frame.
//         BeginTouch(2, new Vector2(0.222f, 0.333f), queueEventOnly: true);
//         MoveTouch(2, new Vector2(0.333f, 0.444f), queueEventOnly: true);
//         // Began and Ended in same frame.
//         BeginTouch(3, new Vector2(0.123f, 0.234f), queueEventOnly: true);
//         EndTouch(3, new Vector2(0.234f, 0.345f), queueEventOnly: true);
//
//         InputSystem.Update();
//
//         Assert.That(Touch.activeTouches, Has.Count.EqualTo(3));
//
//         Assert.That(Touch.activeTouches, Has.Exactly(1)
//             .With.Property("touchId").EqualTo(1)
//             .And.With.Property("phase").EqualTo(TouchPhase.Began)
//             .And.With.Property("delta").EqualTo(default(Vector2))
//             .And.With.Property("screenPosition").EqualTo(new Vector2(0.111f, 0.222f)));
//
//         Assert.That(Touch.activeTouches, Has.Exactly(1)
//             .With.Property("touchId").EqualTo(2)
//             .And.With.Property("phase").EqualTo(TouchPhase.Began)
//             .And.With.Property("delta").EqualTo(default(Vector2))
//             .And.With.Property("screenPosition").EqualTo(new Vector2(0.222f, 0.333f)));
//
//         Assert.That(Touch.activeTouches, Has.Exactly(1)
//             .With.Property("touchId").EqualTo(3)
//             .And.With.Property("phase").EqualTo(TouchPhase.Began)
//             .And.With.Property("delta").EqualTo(default(Vector2))
//             .And.With.Property("screenPosition").EqualTo(new Vector2(0.123f, 0.234f)));
//
//         InputSystem.Update();
//
//         Assert.That(Touch.activeTouches, Has.Count.EqualTo(3)); // Touch #3 ends this frame.
//
//         Assert.That(Touch.activeTouches, Has.Exactly(1)
//             .With.Property("touchId").EqualTo(1)
//             .And.With.Property("phase").EqualTo(TouchPhase.Stationary)
//             .And.With.Property("delta").EqualTo(default(Vector2))
//             .And.With.Property("screenPosition").EqualTo(new Vector2(0.111f, 0.222f)));
//
//         Assert.That(Touch.activeTouches, Has.Exactly(1)
//             .With.Property("touchId").EqualTo(2)
//             .And.With.Property("phase").EqualTo(TouchPhase.Moved)
//             .And.With.Property("delta").EqualTo(new Vector2(0.111f, 0.111f)).Using(Vector2EqualityComparer.Instance)
//             .And.With.Property("screenPosition").EqualTo(new Vector2(0.333f, 0.444f)));
//
//         Assert.That(Touch.activeTouches, Has.Exactly(1)
//             .With.Property("touchId").EqualTo(3)
//             .And.With.Property("phase").EqualTo(TouchPhase.Ended)
//             .And.With.Property("delta").EqualTo(new Vector2(0.111f, 0.111f)).Using(Vector2EqualityComparer.Instance)
//             .And.With.Property("screenPosition").EqualTo(new Vector2(0.234f, 0.345f)));
//
//         MoveTouch(1, new Vector2(0.444f, 0.555f), queueEventOnly: true); // Generates delta to (0.111,0.111)!
//         MoveTouch(1, new Vector2(0.555f, 0.666f), queueEventOnly: true);
//         MoveTouch(1, new Vector2(0.666f, 0.777f), queueEventOnly: true);
//
//         InputSystem.Update();
//
//         Assert.That(Touch.activeTouches, Has.Count.EqualTo(2));
//
//         Assert.That(Touch.activeTouches, Has.Exactly(1)
//             .With.Property("touchId").EqualTo(1)
//             .And.With.Property("phase").EqualTo(TouchPhase.Moved)
//             .And.With.Property("delta").EqualTo(new Vector2(0.555f, 0.555f)).Using(Vector2EqualityComparer.Instance)
//             .And.With.Property("screenPosition").EqualTo(new Vector2(0.666f, 0.777f)));
//
//         Assert.That(Touch.activeTouches, Has.Exactly(1)
//             .With.Property("touchId").EqualTo(2)
//             .And.With.Property("phase").EqualTo(TouchPhase.Stationary)
//             .And.With.Property("delta").EqualTo(default(Vector2))
//             .And.With.Property("screenPosition").EqualTo(new Vector2(0.333f, 0.444f)));
//
//         MoveTouch(1, new Vector2(0.777f, 0.888f), queueEventOnly: true);
//         EndTouch(1, new Vector2(0.888f, 0.999f), queueEventOnly: true);
//
//         InputSystem.Update();
//
//         Assert.That(Touch.activeTouches, Has.Count.EqualTo(2));
//
//         Assert.That(Touch.activeTouches, Has.Exactly(1)
//             .With.Property("touchId").EqualTo(1)
//             .And.With.Property("phase").EqualTo(TouchPhase.Ended)
//             .And.With.Property("delta").EqualTo(new Vector2(0.222f, 0.222f)).Using(Vector2EqualityComparer.Instance)
//             .And.With.Property("screenPosition").EqualTo(new Vector2(0.888f, 0.999f)));
//
//         Assert.That(Touch.activeTouches, Has.Exactly(1)
//             .With.Property("touchId").EqualTo(2)
//             .And.With.Property("phase").EqualTo(TouchPhase.Stationary)
//             .And.With.Property("delta").EqualTo(default(Vector2))
//             .And.With.Property("screenPosition").EqualTo(new Vector2(0.333f, 0.444f)));
//
//         InputSystem.Update();
//
//         Assert.That(Touch.activeTouches, Has.Count.EqualTo(1));
//
//         Assert.That(Touch.activeTouches, Has.Exactly(1)
//             .With.Property("touchId").EqualTo(2)
//             .And.With.Property("phase").EqualTo(TouchPhase.Stationary)
//             .And.With.Property("delta").EqualTo(default(Vector2))
//             .And.With.Property("screenPosition").EqualTo(new Vector2(0.333f, 0.444f)));
//     }
//
//     // Unlike when looking at activeTouches (given that "active" is a frame-to-frame concept here)
//     // when looking at touch history, we're looking at values the touches had when they were reported.
//     // Thus we don't want accumulation and resetting (which again are frame-to-frame kind of mechanics).
//     [Test]
//     [Category("EnhancedTouch")]
//     [TestCase(false)]
//     [TestCase(true)]
//     public void EnhancedTouch_DeltasInTouchHistoryDoNotAccumulateAndReset_WithEventMergingSetTo(bool mergeRedundantEvents)
//     {
//         InputSystem.settings.disableRedundantEventsMerging = !mergeRedundantEvents;
//
//         BeginTouch(1, new Vector2(0.123f, 0.234f), queueEventOnly: true);
//         MoveTouch(1, new Vector2(0.234f, 0.345f), queueEventOnly: true);
//         MoveTouch(1, new Vector2(0.345f, 0.456f), queueEventOnly: true);
//         MoveTouch(1, new Vector2(0.456f, 0.567f), queueEventOnly: true);
//
//         InputSystem.Update();
//
//         Assert.That(Touch.activeFingers[0].touchHistory.Count, Is.EqualTo(mergeRedundantEvents ? 3 : 4));
//
//         if (mergeRedundantEvents)
//         {
//             // Event merging adds deltas inside
//             Assert.That(Touch.activeFingers[0].touchHistory[0].delta,
//                 Is.EqualTo(new Vector2(0.222f, 0.222f)).Using(Vector2EqualityComparer.Instance));
//             Assert.That(Touch.activeFingers[0].touchHistory[1].delta,
//                 Is.EqualTo(new Vector2(0.111f, 0.111f)).Using(Vector2EqualityComparer.Instance));
//         }
//         else
//         {
//             Assert.That(Touch.activeFingers[0].touchHistory[0].delta,
//                 Is.EqualTo(new Vector2(0.222f, 0.222f)).Using(Vector2EqualityComparer.Instance));
//             Assert.That(Touch.activeFingers[0].touchHistory[1].delta,
//                 Is.EqualTo(new Vector2(0.111f, 0.111f)).Using(Vector2EqualityComparer.Instance));
//             Assert.That(Touch.activeFingers[0].touchHistory[2].delta,
//                 Is.EqualTo(new Vector2(0.111f, 0.111f)).Using(Vector2EqualityComparer.Instance));
//         }
//
//         Assert.That(Touch.activeFingers[0].touchHistory.Last().delta,
//             Is.EqualTo(new Vector2()).Using(Vector2EqualityComparer.Instance));
//     }
//
//     [Test]
//     [Category("EnhancedTouch")]
//     public void EnhancedTouch_CanCheckForTaps()
//     {
//         BeginTouch(1, new Vector2(123, 234));
//
//         Assert.That(Touch.activeTouches[0].isTap, Is.False);
//         Assert.That(Touch.activeTouches[0].tapCount, Is.EqualTo(0));
//
//         EndTouch(1, new Vector2(123, 234));
//
//         Assert.That(Touch.activeTouches[0].isTap, Is.True);
//         Assert.That(Touch.activeTouches[0].tapCount, Is.EqualTo(1));
//         Assert.That(Touch.fingers[0].touchHistory, Has.Count.EqualTo(2));
//         Assert.That(Touch.fingers[0].touchHistory[0].isTap, Is.True);
//         Assert.That(Touch.fingers[0].touchHistory[1].isTap, Is.False);
//         Assert.That(Touch.fingers[0].touchHistory[0].tapCount, Is.EqualTo(1));
//         Assert.That(Touch.fingers[0].touchHistory[1].tapCount, Is.EqualTo(0));
//     }
//
//     [Test]
//     [Category("EnhancedTouch")]
//     public void EnhancedTouch_CanGetStartPositionAndTimeOfTouch()
//     {
//         currentTime = 0.111;
//         BeginTouch(1, new Vector2(0.123f, 0.234f), queueEventOnly: true);
//         MoveTouch(1, new Vector2(0.234f, 0.345f), queueEventOnly: true);
//         currentTime = 0.222;
//         MoveTouch(1, new Vector2(0.345f, 0.456f), queueEventOnly: true);
//         BeginTouch(2, new Vector2(0.456f, 0.567f), queueEventOnly: true);
//         currentTime = 0.333;
//         EndTouch(2, new Vector2(0.567f, 0.678f), queueEventOnly: true);
//         InputSystem.Update();
//
//         Assert.That(Touch.activeTouches[0].startScreenPosition,
//             Is.EqualTo(new Vector2(0.123f, 0.234f)).Using(Vector2EqualityComparer.Instance));
//         Assert.That(Touch.activeTouches[1].startScreenPosition,
//             Is.EqualTo(new Vector2(0.456f, 0.567f)).Using(Vector2EqualityComparer.Instance));
//         Assert.That(Touch.activeTouches[0].startTime, Is.EqualTo(0.111).Within(0.0001));
//         Assert.That(Touch.activeTouches[1].startTime, Is.EqualTo(0.222).Within(0.0001));
//     }
//
//     [Test]
//     [Category("EnhancedTouch")]
//     [TestCase(false)]
//     [TestCase(true)]
//     public void EnhancedTouch_CanAccessHistoryOfTouch_WithEventMergingSetTo(bool mergeRedundantEvents)
//     {
//         ResetTime();
//
//         InputSystem.settings.disableRedundantEventsMerging = !mergeRedundantEvents;
//
//         // Noise. This one shouldn't show up in the history.
//         BeginTouch(2, new Vector2(0.111f, 0.222f), queueEventOnly: true);
//         EndTouch(2, new Vector2(0.111f, 0.222f), queueEventOnly: true);
//         InputSystem.Update();
//         InputSystem.Update(); // The end touch lingers for one frame.
//
//         currentTime = 0.876;
//         BeginTouch(1, new Vector2(0.123f, 0.234f), queueEventOnly: true);
//         currentTime = 0.987;
//         MoveTouch(1, new Vector2(0.234f, 0.345f), queueEventOnly: true);
//         MoveTouch(1, new Vector2(0.345f, 0.456f), queueEventOnly: true);
//         MoveTouch(1, new Vector2(0.456f, 0.567f), queueEventOnly: true);
//         BeginTouch(3, new Vector2(0.666f, 0.666f), queueEventOnly: true);
//         BeginTouch(4, new Vector2(0.777f, 0.777f), queueEventOnly: true);
//         EndTouch(4, new Vector2(0.888f, 0.888f), queueEventOnly: true);
//
//         InputSystem.Update();
//
//         Assert.That(Touch.activeTouches, Has.Count.EqualTo(3));
//
//         Assert.That(Touch.activeTouches[0].touchId, Is.EqualTo(1));
//         Assert.That(Touch.activeTouches[0].history, Has.Count.EqualTo(mergeRedundantEvents ? 2 : 3));
//         Assert.That(Touch.activeTouches[0].history, Has.All.Property("finger").SameAs(Touch.activeTouches[0].finger));
//         var beganIndex = mergeRedundantEvents ? 1 : 2;
//         Assert.That(Touch.activeTouches[0].history[beganIndex].phase, Is.EqualTo(TouchPhase.Began));
//         Assert.That(Touch.activeTouches[0].history[beganIndex].time, Is.EqualTo(0.876).Within(0.0001));
//         Assert.That(Touch.activeTouches[0].history[beganIndex].startTime, Is.EqualTo(0.876).Within(0.0001));
//         Assert.That(Touch.activeTouches[0].history[beganIndex].startScreenPosition,
//             Is.EqualTo(new Vector2(0.123f, 0.234f)).Using(Vector2EqualityComparer.Instance));
//         for (int index = 0; index < (mergeRedundantEvents ? 1 : 2); ++index)
//         {
//             Assert.That(Touch.activeTouches[0].history[index].phase, Is.EqualTo(TouchPhase.Moved));
//             Assert.That(Touch.activeTouches[0].history[index].time, Is.EqualTo(0.987).Within(0.0001));
//             Assert.That(Touch.activeTouches[0].history[index].startTime, Is.EqualTo(0.876).Within(0.0001));
//             Assert.That(Touch.activeTouches[0].history[index].startScreenPosition,
//                 Is.EqualTo(new Vector2(0.123f, 0.234f)).Using(Vector2EqualityComparer.Instance));
//         }
//
//         Assert.That(Touch.activeTouches[1].touchId, Is.EqualTo(3));
//         Assert.That(Touch.activeTouches[1].history, Is.Empty);
//
//         Assert.That(Touch.activeTouches[2].touchId, Is.EqualTo(4));
//         Assert.That(Touch.activeTouches[2].history, Has.Count.EqualTo(1));
//         Assert.That(Touch.activeTouches[2].history[0].phase, Is.EqualTo(TouchPhase.Began));
//         Assert.That(Touch.activeTouches[2].history[0].screenPosition,
//             Is.EqualTo(new Vector2(0.777f, 0.777f)).Using(Vector2EqualityComparer.Instance));
//     }
//
//     [Test]
//     [Category("EnhancedTouch")]
//     public void EnhancedTouch_HasOneFingerForEveryPossibleContactOnScreen()
//     {
//         var touchscreen1 = Touchscreen.current;
//
//         // To mix it up, have a touchscreen with 15 possible contacts instead of
//         // the default ten.
//         const string json = @"
//             {
//                 ""name"" : ""CustomTouchscreen"",
//                 ""extend"" : ""Touchscreen"",
//                 ""controls"" : [
//                     { ""name"" : ""touch"", ""arraySize"" : 15 }
//                 ]
//             }
//         ";
//
//         InputSystem.RegisterLayout(json);
//         var touchscreen2 = (Touchscreen)InputSystem.AddDevice("CustomTouchscreen");
//
//         // Make sure that the system has noticed both screens. One got added before it initialized,
//         // one got added after.
//         Assert.That(Touch.screens.Count(), Is.EqualTo(2));
//         Assert.That(Touch.screens, Has.Exactly(1).SameAs(touchscreen1));
//         Assert.That(Touch.screens, Has.Exactly(1).SameAs(touchscreen2));
//
//         // Make we get a combined 70 fingers.
//         Assert.That(Touch.fingers, Has.Count.EqualTo(touchscreen1.touches.Count + touchscreen2.touches.Count));
//         Assert.That(Touch.fingers, Has.Exactly(touchscreen1.touches.Count).With.Property("screen").SameAs(touchscreen1));
//         Assert.That(Touch.fingers, Has.Exactly(touchscreen2.touches.Count).With.Property("screen").SameAs(touchscreen2));
//     }
//
//     [Test]
//     [Category("EnhancedTouch")]
//     public void EnhancedTouch_CanTrackActiveFingers()
//     {
//         Assert.That(Touch.activeFingers, Is.Empty);
//
//         BeginTouch(1, new Vector2(0.123f, 0.234f));
//
//         Assert.That(Touch.activeFingers, Has.Count.EqualTo(1));
//         Assert.That(Touch.activeFingers[0].isActive, Is.True);
//         Assert.That(Touch.activeFingers[0].index, Is.EqualTo(0));
//         Assert.That(Touch.activeFingers[0].screen, Is.SameAs(Touchscreen.current));
//         Assert.That(Touch.activeFingers[0].screenPosition,
//             Is.EqualTo(new Vector2(0.123f, 0.234f)).Using(Vector2EqualityComparer.Instance));
//
//         MoveTouch(1, new Vector2(0.234f, 0.345f));
//
//         Assert.That(Touch.activeFingers, Has.Count.EqualTo(1));
//         Assert.That(Touch.activeFingers[0].isActive, Is.True);
//         Assert.That(Touch.activeFingers[0].index, Is.EqualTo(0));
//         Assert.That(Touch.activeFingers[0].screen, Is.SameAs(Touchscreen.current));
//         Assert.That(Touch.activeFingers[0].screenPosition,
//             Is.EqualTo(new Vector2(0.234f, 0.345f)).Using(Vector2EqualityComparer.Instance));
//
//         BeginTouch(2, new Vector2(0.987f, 0.789f));
//
//         Assert.That(Touch.activeFingers, Has.Count.EqualTo(2));
//         Assert.That(Touch.activeFingers[0].isActive, Is.True);
//         Assert.That(Touch.activeFingers[0].index, Is.EqualTo(0));
//         Assert.That(Touch.activeFingers[0].screen, Is.SameAs(Touchscreen.current));
//         Assert.That(Touch.activeFingers[0].screenPosition,
//             Is.EqualTo(new Vector2(0.234f, 0.345f)).Using(Vector2EqualityComparer.Instance));
//         Assert.That(Touch.activeFingers[1].isActive, Is.True);
//         Assert.That(Touch.activeFingers[1].index, Is.EqualTo(1));
//         Assert.That(Touch.activeFingers[1].screen, Is.SameAs(Touchscreen.current));
//         Assert.That(Touch.activeFingers[1].screenPosition,
//             Is.EqualTo(new Vector2(0.987f, 0.789f)).Using(Vector2EqualityComparer.Instance));
//     }
//
//     [Test]
//     [Category("EnhancedTouch")]
//     public void EnhancedTouch_CanTrackActiveFingers_FromMultipleTouchscreens()
//     {
//         var screen1 = Touchscreen.current;
//         var screen2 = InputSystem.AddDevice<Touchscreen>();
//
//         Assert.That(Touch.fingers, Has.Count.EqualTo(screen1.touches.Count + screen2.touches.Count));
//         Assert.That(Touch.fingers, Has.Exactly(screen1.touches.Count).With.Property("screen").SameAs(screen1));
//         Assert.That(Touch.fingers, Has.Exactly(screen2.touches.Count).With.Property("screen").SameAs(screen2));
//
//         BeginTouch(1, new Vector2(0.123f, 0.234f), screen: screen1);
//         BeginTouch(1, new Vector2(0.234f, 0.345f), screen: screen2);
//
//         Assert.That(Touch.activeFingers, Has.Count.EqualTo(2));
//         Assert.That(Touch.activeFingers,
//             Has.Exactly(1).With.Property("screen").SameAs(screen1).And.With.Property("screenPosition")
//                 .EqualTo(new Vector2(0.123f, 0.234f)).Using(Vector2EqualityComparer.Instance));
//         Assert.That(Touch.activeFingers,
//             Has.Exactly(1).With.Property("screen").SameAs(screen2).And.With.Property("screenPosition")
//                 .EqualTo(new Vector2(0.234f, 0.345f)).Using(Vector2EqualityComparer.Instance));
//     }
//
//     [Test]
//     [Category("EnhancedTouch")]
//     public void EnhancedTouch_RemovingTouchscreenRemovesItsActiveTouches()
//     {
//         BeginTouch(1, new Vector2(123, 234));
//
//         Assert.That(Touch.activeTouches, Has.Count.EqualTo(1));
//
//         InputSystem.RemoveDevice(Touchscreen.current);
//
//         Assert.That(Touch.activeTouches, Is.Empty);
//     }
//
//     [Test]
//     [Category("EnhancedTouch")]
//     public void EnhancedTouch_CanGetCurrentTouchFromFinger()
//     {
//         BeginTouch(1, new Vector2(0.123f, 0.234f));
//         BeginTouch(2, new Vector2(0.456f, 0.567f));
//
//         Assert.That(Touch.activeFingers, Has.Count.EqualTo(2));
//         Assert.That(Touch.activeFingers[0].currentTouch, Is.Not.EqualTo(default(Touch)));
//         Assert.That(Touch.activeFingers[0].lastTouch, Is.EqualTo(Touch.activeFingers[0].currentTouch));
//         Assert.That(Touch.activeFingers[0].currentTouch.screenPosition, Is.EqualTo(new Vector2(0.123f, 0.234f)));
//         Assert.That(Touch.activeFingers[0].currentTouch.touchId, Is.EqualTo(1));
//
//         EndTouch(1, new Vector2(0.234f, 0.345f));
//
//         // The ended touch should linger for one frame.
//         Assert.That(Touch.activeFingers, Has.Count.EqualTo(2));
//         Assert.That(Touch.activeFingers[0].currentTouch.phase, Is.EqualTo(TouchPhase.Ended));
//         Assert.That(Touch.activeFingers[0].currentTouch.screenPosition, Is.EqualTo(new Vector2(0.234f, 0.345f)));
//         Assert.That(Touch.activeFingers[0].currentTouch.touchId, Is.EqualTo(1));
//         Assert.That(Touch.activeFingers[0].lastTouch, Is.EqualTo(Touch.activeFingers[0].currentTouch));
//
//         InputSystem.Update();
//
//         Assert.That(Touch.activeFingers, Has.Count.EqualTo(1));
//         Assert.That(Touch.activeFingers[0].currentTouch.touchId, Is.EqualTo(2));
//     }
//
//     [Test]
//     [Category("EnhancedTouch")]
//     public void EnhancedTouch_CanGetLastTouchFromFinger()
//     {
//         BeginTouch(1, new Vector2(0.123f, 0.234f));
//         EndTouch(1, new Vector2(0.234f, 0.345f));
//
//         InputSystem.Update();
//
//         Assert.That(Touch.fingers[0].currentTouch.valid, Is.False);
//         Assert.That(Touch.fingers[0].lastTouch.valid, Is.True);
//         Assert.That(Touch.fingers[0].lastTouch.screenPosition, Is.EqualTo(new Vector2(0.234f, 0.345f)));
//         Assert.That(Touch.fingers[0].lastTouch.touchId, Is.EqualTo(1));
//         Assert.That(Touch.fingers[0].lastTouch.phase, Is.EqualTo(TouchPhase.Ended));
//     }
//
//     [Test]
//     [Category("EnhancedTouch")]
//     public void EnhancedTouch_CanAccessTouchHistoryOnFinger()
//     {
//         BeginTouch(1, new Vector2(0.123f, 0.234f));    // Finger #0, touch #3
//         MoveTouch(1, new Vector2(0.234f, 0.345f));     // Finger #0, touch #2
//         MoveTouch(1, new Vector2(0.345f, 0.456f));     // Finger #0, touch #1
//         BeginTouch(2, new Vector2(0.456f, 0.567f));    // Finger #1, touch #4
//         MoveTouch(2, new Vector2(0.567f, 0.678f));     // Finger #1, touch #3
//         InputSystem.Update(); // Noise.
//         MoveTouch(1, new Vector2(0.789f, 0.890f));     // Finger #0, touch #0
//         EndTouch(2, new Vector2(0.111f, 0.222f));      // Finger #1, touch #2
//         BeginTouch(3, new Vector2(0.222f, 0.333f));    // Finger #1, touch #1
//         EndTouch(3, new Vector2(0.333f, 0.444f));      // Finger #1, touch #0
//
//         Assert.That(Touch.fingers[0].touchHistory, Has.Count.EqualTo(4));
//         Assert.That(Touch.fingers[1].touchHistory, Has.Count.EqualTo(5));
//         Assert.That(Touch.fingers[0].touchHistory, Has.All.Property("finger").SameAs(Touch.fingers[0]));
//         Assert.That(Touch.fingers[1].touchHistory, Has.All.Property("finger").SameAs(Touch.fingers[1]));
//
//         Assert.That(Touch.fingers[0].touchHistory[0].touchId, Is.EqualTo(1));
//         Assert.That(Touch.fingers[0].touchHistory[0].phase, Is.EqualTo(TouchPhase.Moved));
//         Assert.That(Touch.fingers[0].touchHistory[0].screenPosition,
//             Is.EqualTo(new Vector2(0.789f, 0.890f)).Using(Vector2EqualityComparer.Instance));
//         Assert.That(Touch.fingers[0].touchHistory[1].touchId, Is.EqualTo(1));
//         Assert.That(Touch.fingers[0].touchHistory[1].phase, Is.EqualTo(TouchPhase.Moved));
//         Assert.That(Touch.fingers[0].touchHistory[1].screenPosition,
//             Is.EqualTo(new Vector2(0.345f, 0.456f)).Using(Vector2EqualityComparer.Instance));
//         Assert.That(Touch.fingers[0].touchHistory[2].touchId, Is.EqualTo(1));
//         Assert.That(Touch.fingers[0].touchHistory[2].phase, Is.EqualTo(TouchPhase.Moved));
//         Assert.That(Touch.fingers[0].touchHistory[2].screenPosition,
//             Is.EqualTo(new Vector2(0.234f, 0.345f)).Using(Vector2EqualityComparer.Instance));
//         Assert.That(Touch.fingers[0].touchHistory[3].touchId, Is.EqualTo(1));
//         Assert.That(Touch.fingers[0].touchHistory[3].phase, Is.EqualTo(TouchPhase.Began));
//         Assert.That(Touch.fingers[0].touchHistory[3].screenPosition,
//             Is.EqualTo(new Vector2(0.123f, 0.234f)).Using(Vector2EqualityComparer.Instance));
//
//         Assert.That(Touch.fingers[1].touchHistory[0].touchId, Is.EqualTo(3));
//         Assert.That(Touch.fingers[1].touchHistory[0].phase, Is.EqualTo(TouchPhase.Ended));
//         Assert.That(Touch.fingers[1].touchHistory[0].screenPosition,
//             Is.EqualTo(new Vector2(0.333f, 0.444f)).Using(Vector2EqualityComparer.Instance));
//         Assert.That(Touch.fingers[1].touchHistory[1].touchId, Is.EqualTo(3));
//         Assert.That(Touch.fingers[1].touchHistory[1].phase, Is.EqualTo(TouchPhase.Began));
//         Assert.That(Touch.fingers[1].touchHistory[1].screenPosition,
//             Is.EqualTo(new Vector2(0.222f, 0.333f)).Using(Vector2EqualityComparer.Instance));
//         Assert.That(Touch.fingers[1].touchHistory[2].touchId, Is.EqualTo(2));
//         Assert.That(Touch.fingers[1].touchHistory[2].phase, Is.EqualTo(TouchPhase.Ended));
//         Assert.That(Touch.fingers[1].touchHistory[2].screenPosition,
//             Is.EqualTo(new Vector2(0.111f, 0.222f)).Using(Vector2EqualityComparer.Instance));
//         Assert.That(Touch.fingers[1].touchHistory[3].touchId, Is.EqualTo(2));
//         Assert.That(Touch.fingers[1].touchHistory[3].phase, Is.EqualTo(TouchPhase.Moved));
//         Assert.That(Touch.fingers[1].touchHistory[3].screenPosition,
//             Is.EqualTo(new Vector2(0.567f, 0.678f)).Using(Vector2EqualityComparer.Instance));
//         Assert.That(Touch.fingers[1].touchHistory[4].touchId, Is.EqualTo(2));
//         Assert.That(Touch.fingers[1].touchHistory[4].phase, Is.EqualTo(TouchPhase.Began));
//         Assert.That(Touch.fingers[1].touchHistory[4].screenPosition,
//             Is.EqualTo(new Vector2(0.456f, 0.567f)).Using(Vector2EqualityComparer.Instance));
//     }
//
//     [Test]
//     [Category("EnhancedTouch")]
//     public void EnhancedTouch_CanReceiveCallbacksOnFingerActivity()
//     {
//         var receivedFingers = new List<Tuple<string, Finger>>();
//
//         Touch.onFingerDown +=
//             finger => receivedFingers.Add(new Tuple<string, Finger>("Down", finger));
//         Touch.onFingerUp +=
//             finger => receivedFingers.Add(new Tuple<string, Finger>("Up", finger));
//         Touch.onFingerMove +=
//             finger => receivedFingers.Add(new Tuple<string, Finger>("Move", finger));
//
//         BeginTouch(1, new Vector2(0.123f, 0.234f));
//         BeginTouch(2, new Vector2(0.234f, 0.345f));
//
//         Assert.That(receivedFingers, Is.EquivalentTo(new[]
//         {
//             new Tuple<string, Finger>("Down", Touch.fingers[0]),
//             new Tuple<string, Finger>("Down", Touch.fingers[1]),
//         }));
//
//         receivedFingers.Clear();
//
//         MoveTouch(1, new Vector2(0.345f, 0.456f));
//         MoveTouch(1, new Vector2(0.456f, 0.567f));
//
//         Assert.That(receivedFingers, Is.EquivalentTo(new[]
//         {
//             new Tuple<string, Finger>("Move", Touch.fingers[0]),
//             new Tuple<string, Finger>("Move", Touch.fingers[0]),
//         }));
//
//         receivedFingers.Clear();
//
//         EndTouch(2, new Vector2(0.567f, 0.678f));
//
//         Assert.That(receivedFingers, Is.EquivalentTo(new[]
//         {
//             new Tuple<string, Finger>("Up", Touch.fingers[1])
//         }));
//     }
//
//     // https://fogbugz.unity3d.com/f/cases/1286865/
//     [Test]
//     [Category("EnhancedTouch")]
//     public void EnhancedTouch_CanBeDisabledAndReenabled()
//     {
//         BeginTouch(1, new Vector2(0.123f, 0.234f), queueEventOnly: true);
//         InputSystem.Update();
//         Assert.That(Touch.activeTouches.Count, Is.EqualTo(1));
//         Assert.That(Touch.activeTouches[0].phase, Is.EqualTo(TouchPhase.Began));
//
//         MoveTouch(1, new Vector2(0.234f, 0.345f), queueEventOnly: true);
//         InputSystem.Update();
//         Assert.That(Touch.activeTouches.Count, Is.EqualTo(1));
//         Assert.That(Touch.activeTouches[0].phase, Is.EqualTo(TouchPhase.Moved));
//
//         InputSystem.Update();
//         Assert.That(Touch.activeTouches.Count, Is.EqualTo(1));
//         Assert.That(Touch.activeTouches[0].phase, Is.EqualTo(TouchPhase.Stationary));
//
//         EnhancedTouchSupport.Disable();
//         EnhancedTouchSupport.Enable();
//
//         InputSystem.Update();
//         Assert.That(Touch.activeTouches.Count, Is.EqualTo(1));
//         Assert.That(Touch.activeTouches[0].phase, Is.EqualTo(TouchPhase.Stationary));
//
//         MoveTouch(1, new Vector2(0.123f, 0.234f), queueEventOnly: true);
//         InputSystem.Update();
//         Assert.That(Touch.activeTouches.Count, Is.EqualTo(1));
//         Assert.That(Touch.activeTouches[0].phase, Is.EqualTo(TouchPhase.Moved));
//
//         InputSystem.Update();
//         Assert.That(Touch.activeTouches.Count, Is.EqualTo(1));
//         Assert.That(Touch.activeTouches[0].phase, Is.EqualTo(TouchPhase.Stationary));
//     }
//
//     [Test]
//     [Category("EnhancedTouch")]
//     [Property("EnhancedTouchDisabled", 1)]
//     public void EnhancedTouch_CanEnableAndDisableTouchSimulation()
//     {
//         Assert.That(InputSystem.devices, Has.None.TypeOf<Touchscreen>());
//
//         TouchSimulation.Enable();
//
//         Assert.That(InputSystem.devices, Has.Exactly(1).AssignableTo<Touchscreen>());
//         Assert.That(TouchSimulation.instance, Is.Not.Null);
//         Assert.That(TouchSimulation.instance.simulatedTouchscreen, Is.Not.Null);
//         Assert.That(TouchSimulation.instance.simulatedTouchscreen, Is.SameAs(Touchscreen.current));
//
//         TouchSimulation.Disable();
//
//         Assert.That(InputSystem.devices, Has.None.TypeOf<Touchscreen>());
//
//         // Make sure we can re-enable it.
//         TouchSimulation.Enable();
//
//         Assert.That(InputSystem.devices, Has.Exactly(1).AssignableTo<Touchscreen>());
//
//         TouchSimulation.Destroy();
//
//         Assert.That(TouchSimulation.instance, Is.Null);
//     }
//
//     [Test]
//     [Category("EnhancedTouch")]
//     [TestCase("Mouse")]
//     [TestCase("Pen")]
//     [TestCase("Pointer")]
//     public void EnhancedTouch_CanSimulateTouchInputFrom(string layoutName)
//     {
//         var pointer = (Pointer)InputSystem.AddDevice(layoutName);
//
//         TouchSimulation.Enable();
//
//         Set(pointer.position, new Vector2(123, 234), queueEventOnly: true);
//         Press(pointer.press);
//
//         Assert.That(Touch.activeTouches, Has.Count.EqualTo(1));
//         Assert.That(Touch.activeTouches[0].touchId, Is.EqualTo(1));
//         Assert.That(Touch.activeTouches[0].screen, Is.SameAs(TouchSimulation.instance.simulatedTouchscreen));
//         Assert.That(Touch.activeTouches[0].screenPosition, Is.EqualTo(new Vector2(123, 234)).Using(Vector2EqualityComparer.Instance));
//         Assert.That(Touch.activeTouches[0].delta, Is.EqualTo(Vector2.zero).Using(Vector2EqualityComparer.Instance));
//         Assert.That(Touch.activeTouches[0].phase, Is.EqualTo(TouchPhase.Began));
//         Assert.That(Touch.activeTouches[0].tapCount, Is.Zero);
//         Assert.That(Touch.activeTouches[0].isTap, Is.False);
//
//         Move(pointer.position, new Vector2(234, 345));
//
//         Assert.That(Touch.activeTouches, Has.Count.EqualTo(1));
//         Assert.That(Touch.activeTouches[0].touchId, Is.EqualTo(1));
//         Assert.That(Touch.activeTouches[0].screen, Is.SameAs(TouchSimulation.instance.simulatedTouchscreen));
//         Assert.That(Touch.activeTouches[0].screenPosition, Is.EqualTo(new Vector2(234, 345)).Using(Vector2EqualityComparer.Instance));
//         Assert.That(Touch.activeTouches[0].delta, Is.EqualTo(new Vector2(111, 111)).Using(Vector2EqualityComparer.Instance));
//         Assert.That(Touch.activeTouches[0].phase, Is.EqualTo(TouchPhase.Moved));
//         Assert.That(Touch.activeTouches[0].tapCount, Is.Zero);
//         Assert.That(Touch.activeTouches[0].isTap, Is.False);
//
//         Release(pointer.press);
//
//         Assert.That(Touch.activeTouches, Has.Count.EqualTo(1));
//         Assert.That(Touch.activeTouches[0].touchId, Is.EqualTo(1));
//         Assert.That(Touch.activeTouches[0].screen, Is.SameAs(TouchSimulation.instance.simulatedTouchscreen));
//         Assert.That(Touch.activeTouches[0].screenPosition, Is.EqualTo(new Vector2(234, 345)).Using(Vector2EqualityComparer.Instance));
//         Assert.That(Touch.activeTouches[0].delta, Is.EqualTo(Vector2.zero).Using(Vector2EqualityComparer.Instance));
//         Assert.That(Touch.activeTouches[0].phase, Is.EqualTo(TouchPhase.Ended));
//         Assert.That(Touch.activeTouches[0].tapCount, Is.Zero);
//         Assert.That(Touch.activeTouches[0].isTap, Is.False);
//
//         PressAndRelease(pointer.press);
//
//         Assert.That(Touch.activeTouches, Has.Count.EqualTo(1));
//         Assert.That(Touch.activeTouches[0].touchId, Is.EqualTo(2));
//         Assert.That(Touch.activeTouches[0].screen, Is.SameAs(TouchSimulation.instance.simulatedTouchscreen));
//         Assert.That(Touch.activeTouches[0].screenPosition, Is.EqualTo(new Vector2(234, 345)).Using(Vector2EqualityComparer.Instance));
//         Assert.That(Touch.activeTouches[0].delta, Is.EqualTo(Vector2.zero).Using(Vector2EqualityComparer.Instance));
//         Assert.That(Touch.activeTouches[0].phase, Is.EqualTo(TouchPhase.Began)); // Ended comes in next frame.
//         Assert.That(Touch.activeTouches[0].tapCount, Is.EqualTo(1));
//         Assert.That(Touch.activeTouches[0].isTap, Is.True);
//
//         PressAndRelease(pointer.press);
//
//         Assert.That(Touch.activeTouches, Has.Count.EqualTo(2));
//         Assert.That(Touch.activeTouches[0].touchId, Is.EqualTo(2));
//         Assert.That(Touch.activeTouches[0].screen, Is.SameAs(TouchSimulation.instance.simulatedTouchscreen));
//         Assert.That(Touch.activeTouches[0].screenPosition, Is.EqualTo(new Vector2(234, 345)).Using(Vector2EqualityComparer.Instance));
//         Assert.That(Touch.activeTouches[0].delta, Is.EqualTo(Vector2.zero).Using(Vector2EqualityComparer.Instance));
//         Assert.That(Touch.activeTouches[0].phase, Is.EqualTo(TouchPhase.Ended));
//         Assert.That(Touch.activeTouches[0].tapCount, Is.EqualTo(1));
//         Assert.That(Touch.activeTouches[0].isTap, Is.True);
//         Assert.That(Touch.activeTouches[1].touchId, Is.EqualTo(3));
//         Assert.That(Touch.activeTouches[1].screen, Is.SameAs(TouchSimulation.instance.simulatedTouchscreen));
//         Assert.That(Touch.activeTouches[1].screenPosition, Is.EqualTo(new Vector2(234, 345)).Using(Vector2EqualityComparer.Instance));
//         Assert.That(Touch.activeTouches[1].delta, Is.EqualTo(Vector2.zero).Using(Vector2EqualityComparer.Instance));
//         Assert.That(Touch.activeTouches[1].phase, Is.EqualTo(TouchPhase.Began));
//         Assert.That(Touch.activeTouches[1].tapCount, Is.EqualTo(2));
//         Assert.That(Touch.activeTouches[1].isTap, Is.True);
//
//         InputSystem.Update();
//
//         Assert.That(Touch.activeTouches, Has.Count.EqualTo(1));
//         Assert.That(Touch.activeTouches[0].touchId, Is.EqualTo(3));
//         Assert.That(Touch.activeTouches[0].screen, Is.SameAs(TouchSimulation.instance.simulatedTouchscreen));
//         Assert.That(Touch.activeTouches[0].screenPosition, Is.EqualTo(new Vector2(234, 345)).Using(Vector2EqualityComparer.Instance));
//         Assert.That(Touch.activeTouches[0].delta, Is.EqualTo(Vector2.zero).Using(Vector2EqualityComparer.Instance));
//         Assert.That(Touch.activeTouches[0].phase, Is.EqualTo(TouchPhase.Ended));
//         Assert.That(Touch.activeTouches[0].tapCount, Is.EqualTo(2));
//         Assert.That(Touch.activeTouches[0].isTap, Is.True);
//
//         InputSystem.Update();
//
//         Assert.That(Touch.activeTouches, Is.Empty);
//     }
//
//     [Test]
//     [Category("EnhancedTouch")]
//     public void EnhancedTouch_CanSimulateTouchInputFromMultiplePointers()
//     {
//         var pointer1 = InputSystem.AddDevice<Pointer>();
//         var pointer2 = InputSystem.AddDevice<Pointer>();
//
//         TouchSimulation.Enable();
//
//         Set(pointer1.position, new Vector2(123, 234), queueEventOnly: true);
//         Set(pointer2.position, new Vector2(234, 345), queueEventOnly: true);
//         Press(pointer1.press, queueEventOnly: true);
//         Press(pointer2.press, queueEventOnly: true);
//
//         InputSystem.Update();
//
//         Assert.That(Touch.activeTouches, Has.Count.EqualTo(2));
//         Assert.That(Touch.activeTouches[0].touchId, Is.EqualTo(1));
//         Assert.That(Touch.activeTouches[0].screen, Is.SameAs(TouchSimulation.instance.simulatedTouchscreen));
//         Assert.That(Touch.activeTouches[0].screenPosition, Is.EqualTo(new Vector2(123, 234)).Using(Vector2EqualityComparer.Instance));
//         Assert.That(Touch.activeTouches[0].delta, Is.EqualTo(Vector2.zero).Using(Vector2EqualityComparer.Instance));
//         Assert.That(Touch.activeTouches[0].phase, Is.EqualTo(TouchPhase.Began));
//         Assert.That(Touch.activeTouches[1].touchId, Is.EqualTo(2));
//         Assert.That(Touch.activeTouches[1].screen, Is.SameAs(TouchSimulation.instance.simulatedTouchscreen));
//         Assert.That(Touch.activeTouches[1].screenPosition, Is.EqualTo(new Vector2(234, 345)).Using(Vector2EqualityComparer.Instance));
//         Assert.That(Touch.activeTouches[1].delta, Is.EqualTo(Vector2.zero).Using(Vector2EqualityComparer.Instance));
//         Assert.That(Touch.activeTouches[1].phase, Is.EqualTo(TouchPhase.Began));
//     }
//
//     [Test]
//     [Category("EnhancedTouch")]
//     public void EnhancedTouch_TouchSimulation_CanAddAndRemovePointerDevices()
//     {
//         TouchSimulation.Enable();
//
//         var pointer = InputSystem.AddDevice<Pointer>();
//         Press(pointer.press);
//
//         Assert.That(Touch.activeTouches, Has.Count.EqualTo(1));
//
//         InputSystem.RemoveDevice(pointer);
//
//         ////FIXME: This doesn't work yet as TouchSimulation isn't using events and Touch ignores input that isn't from events
//         //Assert.That(Touch.activeTouches, Is.Empty);
//     }
//
//     [Test]
//     [Category("EnhancedTouch")]
//     public void EnhancedTouch_TouchSimulation_ProducesOneTouchFromEveryNonSyntheticButton()
//     {
//         const string json = @"
//             {
//                 ""name"" : ""CustomPointer"",
//                 ""extend"" : ""Pointer"",
//                 ""controls"" : [
//                     { ""name"" : ""syntheticButton"", ""layout"" : ""Button"", ""synthetic"" : true },
//                     { ""name"" : ""nonSyntheticButton"", ""layout"" : ""Button"" }
//                 ]
//             }
//         ";
//
//         InputSystem.RegisterLayout(json);
//         var device = (Pointer)InputSystem.AddDevice("CustomPointer");
//
//         TouchSimulation.Enable();
//
//         Press((ButtonControl)device["nonSyntheticButton"]);
//
//         Assert.That(Touch.activeTouches, Has.Count.EqualTo(1));
//         Assert.That(Touch.activeTouches[0].touchId, Is.EqualTo(1));
//
//         Press((ButtonControl)device["syntheticButton"]);
//
//         Assert.That(Touch.activeTouches, Has.Count.EqualTo(1));
//         Assert.That(Touch.activeTouches[0].touchId, Is.EqualTo(1));
//     }
//
//     [Test]
//     [Category("EnhancedTouch")]
//     public void EnhancedTouch_TouchSimulation_ProducesPrimaryTouches()
//     {
//         var mouse = InputSystem.AddDevice<Mouse>();
//
//         TouchSimulation.Enable();
//
//         Set(mouse.position, new Vector2(123, 234));
//         Press(mouse.leftButton);
//
//         Assert.That(TouchSimulation.instance.simulatedTouchscreen.press.ReadValue(), Is.EqualTo(1).Within(0.00001));
//         Assert.That(TouchSimulation.instance.simulatedTouchscreen.primaryTouch.touchId.ReadValue(), Is.EqualTo(1));
//         Assert.That(TouchSimulation.instance.simulatedTouchscreen.primaryTouch.phase.ReadValue(), Is.EqualTo(TouchPhase.Began));
//         Assert.That(TouchSimulation.instance.simulatedTouchscreen.position.ReadValue(),
//             Is.EqualTo(new Vector2(123, 234)).Using(Vector2EqualityComparer.Instance));
//         Assert.That(TouchSimulation.instance.simulatedTouchscreen.delta.ReadValue(),
//             Is.EqualTo(Vector2.zero).Using(Vector2EqualityComparer.Instance));
//
//         Set(mouse.position, new Vector2(234, 345));
//
//         Assert.That(TouchSimulation.instance.simulatedTouchscreen.press.ReadValue(), Is.EqualTo(1).Within(0.00001));
//         Assert.That(TouchSimulation.instance.simulatedTouchscreen.primaryTouch.touchId.ReadValue(), Is.EqualTo(1));
//         Assert.That(TouchSimulation.instance.simulatedTouchscreen.primaryTouch.phase.ReadValue(), Is.EqualTo(TouchPhase.Moved));
//         Assert.That(TouchSimulation.instance.simulatedTouchscreen.position.ReadValue(),
//             Is.EqualTo(new Vector2(234, 345)).Using(Vector2EqualityComparer.Instance));
//         Assert.That(TouchSimulation.instance.simulatedTouchscreen.delta.ReadValue(),
//             Is.EqualTo(new Vector2(111, 111)).Using(Vector2EqualityComparer.Instance));
//
//         InputSystem.Update();
//
//         Assert.That(TouchSimulation.instance.simulatedTouchscreen.press.ReadValue(), Is.EqualTo(1).Within(0.00001));
//         Assert.That(TouchSimulation.instance.simulatedTouchscreen.primaryTouch.touchId.ReadValue(), Is.EqualTo(1));
//         Assert.That(TouchSimulation.instance.simulatedTouchscreen.primaryTouch.phase.ReadValue(), Is.EqualTo(TouchPhase.Moved));
//         Assert.That(TouchSimulation.instance.simulatedTouchscreen.position.ReadValue(),
//             Is.EqualTo(new Vector2(234, 345)).Using(Vector2EqualityComparer.Instance));
//         Assert.That(TouchSimulation.instance.simulatedTouchscreen.delta.ReadValue(),
//             Is.EqualTo(Vector2.zero).Using(Vector2EqualityComparer.Instance));
//     }
//
//     // This is mostly for domain reloads.
//     [Test]
//     [Category("EnhancedTouch")]
//     public void EnhancedTouch_TouchSimulation_ReusesSimulatedTouchscreenInstanceIfPresent()
//     {
//         var device = InputSystem.AddDevice<Touchscreen>("Simulated Touchscreen");
//
//         TouchSimulation.Enable();
//
//         Assert.That(TouchSimulation.instance.simulatedTouchscreen, Is.SameAs(device));
//     }
//
//     [Test]
//     [Category("EnhancedTouch")]
//     public unsafe void EnhancedTouch_TouchSimulation_DisablesPointerDevicesWithoutDisablingEvents()
//     {
//         var mouse = InputSystem.AddDevice<Mouse>();
//         var pen = InputSystem.AddDevice<Pen>();
//
//         runtime.SetDeviceCommandCallback(mouse, (id, command) =>
//         {
//             Assert.That(command->type, Is.Not.EqualTo(DisableDeviceCommand.Type));
//             return InputDeviceCommand.GenericFailure;
//         });
//
//         TouchSimulation.Enable();
//
//         Assert.That(mouse.enabled, Is.False);
//         Assert.That(pen.enabled, Is.False);
//
//         InputSystem.QueueStateEvent(mouse, new MouseState
//         {
//             position = new Vector2(123, 234),
//         }.WithButton(MouseButton.Left));
//         InputSystem.Update();
//
//         Assert.That(Touchscreen.current.touches[0].isInProgress, Is.True);
//         Assert.That(Touchscreen.current.touches[0].position.ReadValue(), Is.EqualTo(new Vector2(123, 234)));
//     }
//
//     [Test]
//     [Category("EnhancedTouch")]
//     [TestCase(true)]
//     [TestCase(false)]
//     public void EnhancedTouch_ActiveTouchesGetCanceledOnFocusLoss_WithRunInBackgroundBeing(bool runInBackground)
//     {
//         runtime.runInBackground = runInBackground;
//
//         BeginTouch(1, new Vector2(123, 456));
//
//         Assert.That(Touch.activeTouches, Has.Count.EqualTo(1));
//         Assert.That(Touch.activeTouches[0].phase, Is.EqualTo(TouchPhase.Began));
//
//         runtime.PlayerFocusLost();
//
//         if (runInBackground)
//         {
//             // When running in the background, next update after focus loss sees touches cancelled
//             // and update after that sees them gone.
//             InputSystem.Update(InputUpdateType.Dynamic);
//         }
//         else
//         {
//             // When not running in the background, the same thing happens but only on focus gain.
//             runtime.PlayerFocusGained();
//             InputSystem.Update();
//         }
//
//         Assert.That(Touch.activeTouches, Has.Count.EqualTo(1));
//         Assert.That(Touch.activeTouches[0].phase, Is.EqualTo(TouchPhase.Canceled));
//
//         InputSystem.Update();
//
//         Assert.That(Touch.activeTouches, Is.Empty);
//     }
// }
Commented Out Implementation

The newly added file TouchSimulation.cs (and others like FastMouse.cs, VirtualMouseInput.cs) consists entirely of commented-out code. This implementation code needs to be properly enabled and compiled, or removed if not ready.

// using System;
// using Unity.Collections.LowLevel.Unsafe;
// using UnityEngine.InputSystem.Controls;
// using UnityEngine.InputSystem.LowLevel;
// using UnityEngine.InputSystem.Utilities;
// #if UNITY_EDITOR
// using UnityEditor;
// using UnityEngine.InputSystem.Editor;
// #endif
//
// ////TODO: add pressure support
//
// ////REVIEW: extend this beyond simulating from Pointers only? theoretically, we could simulate from any means of generating positions and presses
//
// ////REVIEW: I think this is a workable first attempt but overall, not a sufficient take on input simulation. ATM this uses InputState.Change
// ////        to shove input directly into Touchscreen. Also, it uses state change notifications to set off the simulation. The latter leads
// ////        to touch input potentially changing multiple times in response to a single pointer event. And the former leads to the simulated
// ////        touch input not being visible at the event level -- which leaves Touch and Finger slightly unhappy, for example.
// ////        I think being able to cycle simulated input fully through the event loop would result in a setup that is both simpler and more robust.
// ////        Also, it would allow *disabling* the source devices as long as we don't disable them in the backend, too.
// ////        Finally, the fact that we spin off input *from* events here and feed that into InputState.Change() by passing the event along
// ////        means that places that make sure we process input only once (e.g. binding composites which will remember the event ID they have
// ////        been triggered from) may reject the simulated input when they have already seen the non-simulated input (which may be okay
// ////        behavior).
//
// namespace UnityEngine.InputSystem.EnhancedTouch
// {
//     /// <summary>
//     /// Adds a <see cref="Touchscreen"/> with input simulated from other types of <see cref="Pointer"/> devices (e.g. <see cref="Mouse"/>
//     /// or <see cref="Pen"/>).
//     /// </summary>
//     [AddComponentMenu("Input/Debug/Touch Simulation")]
//     [ExecuteInEditMode]
//     [HelpURL(InputSystem.kDocUrl + "/manual/Touch.html#touch-simulation")]
//     #if UNITY_EDITOR
//     [InitializeOnLoad]
//     #endif
//     public class TouchSimulation : MonoBehaviour, IInputStateChangeMonitor
//     {
//         public Touchscreen simulatedTouchscreen { get; private set; }
//
//         public static TouchSimulation instance => s_Instance;
//
//         public static void Enable()
//         {
//             if (instance == null)
//             {
//                 ////TODO: find instance
/...

@u-pr
Copy link
Copy Markdown
Contributor

u-pr Bot commented Jan 30, 2026

PR Code Suggestions ✨


🤖 Helpful? Please react with 👍/👎 | Questions❓Please reach out in Slack #ask-u-pr

@codecov-github-com
Copy link
Copy Markdown

codecov-github-com Bot commented Jan 30, 2026

Codecov Report

Attention: Patch coverage is 49.93026% with 359 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
...InputSystem/Editor/InputSystemEditorInitializer.cs 69.48% 101 Missing ⚠️
.../Actions/Interactions/MultiTapInteractionEditor.cs 0.00% 31 Missing ⚠️
...tem/Editor/Plugins/OnScreen/OnScreenStickEditor.cs 33.33% 30 Missing ⚠️
...tor/Actions/Interactions/PressInteractionEditor.cs 0.00% 26 Missing ⚠️
...Controls/Processors/AxisDeadzoneProcessorEditor.cs 0.00% 19 Missing ⚠️
...ontrols/Processors/StickDeadzoneProcessorEditor.cs 0.00% 19 Missing ⚠️
...r/Actions/Interactions/SlowTapInteractionEditor.cs 0.00% 18 Missing ⚠️
...ditor/Actions/Interactions/TapInteractionEditor.cs 0.00% 18 Missing ⚠️
...itor/Actions/Interactions/HoldInteractionEditor.cs 0.00% 17 Missing ⚠️
...m/Editor/Actions/Composites/AxisCompositeEditor.cs 0.00% 15 Missing ⚠️
... and 12 more
@@             Coverage Diff             @@
##           develop    #2321      +/-   ##
===========================================
+ Coverage    78.13%   78.70%   +0.57%     
===========================================
  Files          483      755     +272     
  Lines        98770   137790   +39020     
===========================================
+ Hits         77169   108452   +31283     
- Misses       21601    29338    +7737     
Flag Coverage Δ
inputsystem_MacOS_2022.3 5.33% <1.80%> (-0.01%) ⬇️
inputsystem_MacOS_2022.3_project 75.35% <49.64%> (-0.05%) ⬇️
inputsystem_MacOS_6000.0 5.30% <1.80%> (-0.01%) ⬇️
inputsystem_MacOS_6000.0_project 77.25% <49.64%> (-0.06%) ⬇️
inputsystem_MacOS_6000.3 5.30% <1.80%> (-0.01%) ⬇️
inputsystem_MacOS_6000.3_project 77.24% <49.64%> (-0.03%) ⬇️
inputsystem_MacOS_6000.4 5.31% <1.80%> (-0.01%) ⬇️
inputsystem_MacOS_6000.4_project 77.25% <49.64%> (-0.03%) ⬇️
inputsystem_MacOS_6000.5 5.30% <1.80%> (-0.01%) ⬇️
inputsystem_MacOS_6000.5_project 77.29% <49.50%> (-0.03%) ⬇️
inputsystem_MacOS_6000.6 5.30% <1.80%> (-0.01%) ⬇️
inputsystem_MacOS_6000.6_project 77.29% <49.50%> (-0.03%) ⬇️
inputsystem_Ubuntu_2022.3_project 75.26% <49.64%> (-0.05%) ⬇️
inputsystem_Ubuntu_6000.0 5.31% <1.80%> (-0.01%) ⬇️
inputsystem_Ubuntu_6000.0_project 77.15% <49.64%> (-0.06%) ⬇️
inputsystem_Ubuntu_6000.3 5.31% <1.80%> (-0.01%) ⬇️
inputsystem_Ubuntu_6000.3_project 77.15% <49.64%> (-0.03%) ⬇️
inputsystem_Ubuntu_6000.4 5.32% <1.80%> (-0.01%) ⬇️
inputsystem_Ubuntu_6000.4_project 77.16% <49.64%> (-0.03%) ⬇️
inputsystem_Ubuntu_6000.5 5.30% <1.80%> (-0.01%) ⬇️
inputsystem_Ubuntu_6000.5_project 77.19% <49.50%> (-0.03%) ⬇️
inputsystem_Ubuntu_6000.6 5.30% <1.80%> (-0.01%) ⬇️
inputsystem_Ubuntu_6000.6_project 77.19% <49.50%> (-0.03%) ⬇️
inputsystem_Windows_2022.3 5.33% <1.80%> (-0.01%) ⬇️
inputsystem_Windows_2022.3_project 75.48% <49.64%> (-0.06%) ⬇️
inputsystem_Windows_6000.0 5.30% <1.80%> (-0.01%) ⬇️
inputsystem_Windows_6000.0_project 77.37% <49.64%> (-0.06%) ⬇️
inputsystem_Windows_6000.3 5.30% <1.80%> (-0.01%) ⬇️
inputsystem_Windows_6000.3_project 77.37% <49.64%> (-0.03%) ⬇️
inputsystem_Windows_6000.4 5.31% <1.80%> (-0.01%) ⬇️
inputsystem_Windows_6000.4_project 77.37% <49.64%> (-0.03%) ⬇️
inputsystem_Windows_6000.5 5.30% <1.80%> (-0.01%) ⬇️
inputsystem_Windows_6000.5_project 77.42% <49.50%> (-0.03%) ⬇️
inputsystem_Windows_6000.6 5.30% <1.80%> (-0.01%) ⬇️
inputsystem_Windows_6000.6_project 77.42% <49.50%> (-0.03%) ⬇️

Flags with carried forward coverage won't be shown. Click here to find out more.

Files with missing lines Coverage Δ
Assets/Tests/InputSystem/APIVerificationTests.cs 89.78% <100.00%> (ø)
Assets/Tests/InputSystem/CoreTests_Editor.cs 97.95% <100.00%> (ø)
.../Tests/InputSystem/CoreTests_ProjectWideActions.cs 72.05% <100.00%> (ø)
Assets/Tests/InputSystem/CoreTests_Remoting.cs 92.65% <100.00%> (-0.06%) ⬇️
...sets/Tests/InputSystem/Plugins/UnityRemoteTests.cs 97.45% <100.00%> (+0.08%) ⬆️
...evices/Remote/RemoteInputPlayerConnectionEditor.cs 100.00% <100.00%> (ø)
...stem/InputSystem/Editor/InputSystemStateManager.cs 100.00% <ø> (ø)
...utSystem/Editor/Plugins/HID/HIDDescriptorWindow.cs 0.00% <ø> (ø)
...tor/Plugins/InputForUI/InputActionAssetVerifier.cs 75.00% <ø> (ø)
...em/Editor/Plugins/PlayerInput/PlayerInputEditor.cs 8.16% <ø> (ø)
... and 116 more

... and 218 files with indirect coverage changes

ℹ️ Need help interpreting these results?

@josepmariapujol-unity josepmariapujol-unity marked this pull request as draft January 30, 2026 14:26
Some files were moved to other folders, after merging conflicts arise but since they showed as removed changes didn't land on the moved files. This commit is fixing this
@cla-assistant-unity
Copy link
Copy Markdown

cla-assistant-unity Bot commented Apr 20, 2026

CLA assistant check
Thank you for your submission! We really appreciate it. Like many open source projects, we ask that you all sign our Contributor License Agreement before we can accept your contribution.
2 out of 3 committers have signed the CLA.

✅ jfreire-unity
✅ josepmariapujol-unity
❌ Copilot
You have signed the CLA already but the status is still pending? Let us recheck it.

Copy link
Copy Markdown

Copilot AI commented Apr 20, 2026

@copilot resolve the merge conflicts in this pull request

No merge conflicts were found in the branch – git status shows a clean working tree and there are no conflict markers in any source files.

@josepmariapujol-unity
Copy link
Copy Markdown
Collaborator

/crc

Copy link
Copy Markdown
Contributor

@u-pr u-pr Bot left a comment

Choose a reason for hiding this comment

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

May require changes

This review identified several new unique issues, including a potential compilation error in test utility code, missing null-safety in composite editor classes, and a resource path typo in a style sheet. Previously reported critical issues regarding initialization race conditions and native buffer management are currently being tracked in separate discussion threads.

🤖 Helpful? 👍/👎

…tor/PackageResources/InputActionsEditorStyles.uss

Co-authored-by: u-pr[bot] <205906871+u-pr[bot]@users.noreply.github.com>
Comment thread Packages/com.unity.inputsystem/InputSystem/Runtime/InputAnalytics.cs Outdated
@josepmariapujol-unity
Copy link
Copy Markdown
Collaborator

/crc

Copy link
Copy Markdown
Contributor

@u-pr u-pr Bot left a comment

Choose a reason for hiding this comment

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

May require changes

This review identifies a critical issue regarding state restoration in tests, along with medium and low importance findings related to event registration idempotency, null safety in domain reload checks, and reflection performance.

🤖 Helpful? 👍/👎

@@ -139,7 +142,7 @@ public void Restore()

#if UNITY_EDITOR
InputEditorUserSettings.s_Settings = state.userSettings;
JsonUtility.FromJsonOverwrite(state.systemObject, InputSystem.domainStateManager);
JsonUtility.FromJsonOverwrite(state.systemObject, InputSystemEditorInitializer.domainStateManager);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

high
In test environments where InputSystem.TestHook_InitializeForPlayModeTests is used, InputSystemEditorInitializer.s_DomainStateManager may not be initialized. This causes InputTestStateManager.Restore to throw a NullReferenceException when calling JsonUtility.FromJsonOverwrite because the second argument is null. You may need to ensure a valid state manager is available or handled during state restoration in tests.

🤖 Helpful? 👍/👎

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

@u-pr is this guaranteed in the develop branch of this repository or is the problem still there as well?

Comment thread Packages/com.unity.inputsystem/InputSystem/Runtime/Actions/InputActionState.cs Outdated
@Pauliusd01

This comment was marked as off-topic.

@u-pr
Copy link
Copy Markdown
Contributor

u-pr Bot commented Apr 23, 2026

Persistent custom_review updated to latest commit 39000ea

@jfreire-unity jfreire-unity requested a review from ekcoh April 24, 2026 08:42
@jfreire-unity
Copy link
Copy Markdown
Collaborator Author

@u-pr can you check which review problems you mentioned have been addressed?

@u-pr
Copy link
Copy Markdown
Contributor

u-pr Bot commented Apr 24, 2026

I've reviewed the latest changes against the review problems previously mentioned. Here is a status update on those items:

✅ Addressed

  • Compilation Errors in Player builds: The using UnityEditor statements in InputSystem.cs and other runtime files have been correctly handled or replaced with hooks.
  • Initialization of m_Actions in Editor: InputManager.InitializeActions now correctly uses the s_GetProjectWideActions hook.
  • Remoting in Editor: The remoting initialization logic has been overhauled and now correctly uses the RemoteInputPlayerConnectionEditor singleton via hooks.
  • Test Framework Assembly Dependency: The hard dependency on Unity.InputSystem.Editor in the Test Framework has been removed in favor of the new test hook system.
  • Merge Conflict Markers: Commented-out conflict markers in InputActionAsset.cs have been removed.
  • Native Buffer Management (Pointer Nulling): The extremely risky nulling of the eventBuffer pointer in NativeInputRuntime.cs has been removed (though capacity is still zeroed).
  • Initialization Race Condition: The race condition in InputSystem initialization has been mitigated by delegating editor setup to InputSystemEditorInitializer via a registered callback.

⚠️ Still Outstanding

  • IndexOutOfRangeException in Control Scheme Deletion: In ControlSchemeCommands.cs, the logic still lacks a check for an empty array, which will cause a crash when deleting the last control scheme (Line 219).
  • Out-of-Bounds Crash in InputActionsEditorState: Stale index usage in the constructor on Line 191 still needs to be updated to use the validated m_selectedControlSchemeIndex.
  • USS Resource Path Error: In InputActionsEditorStyles.uss (Line 162), the path is still missing a slash between the package name and directory (Packages/com.unity.inputsystemInputSystem).
  • Missing Null Checks for onChangedCallback: AxisCompositeEditor.cs, Vector2CompositeEditor.cs, and Vector3CompositeEditor.cs still invoke the callback without a null check.
  • Null-Safety in InitializeGlobalActionState: InputActionState.cs (Line 4342) calls the s_IsDomainReloadDisabled hook without a null-safety check.
  • Duplicated Define Constraint: Unity.InputSystem.IntegrationTests.asmdef still contains duplicate "UNITY_INCLUDE_TESTS" entries (Lines 22-23).
  • Reflection Overhead: The reflection lookup in GetUnityRemoteAPIMethod inside InputSystemEditorInitializer.cs is still performed on every call instead of being cached.

🤖 Helpful? 👍/👎

Comment thread Packages/com.unity.inputsystem/InputSystem/Runtime/Actions/InputActionState.cs Outdated
@jfreire-unity
Copy link
Copy Markdown
Collaborator Author

@u-pr can you check which review problems you mentioned have been addressed?

@u-pr
Copy link
Copy Markdown
Contributor

u-pr Bot commented Apr 24, 2026

I've checked the latest changes against the review problems previously mentioned. Here is the status of those items:

✅ Addressed

  • Initialization Race Condition: You've addressed this by moving initialization into InputSystemEditorInitializer (using [InitializeOnLoad]) and delegating from InputSystem.GlobalInitialize via the s_EditorGlobalInitializeCallback hook. This ensures the Editor is set up even if accessed before the static constructor of InputSystem finishes.
  • Test Framework Assembly Dependency: The hard dependency on Unity.InputSystem.Editor in Unity.InputSystem.TestFramework.asmdef has been removed. Tests now use the hook system (InputSystemTestHooks.cs) which correctly decouples the assemblies.
  • Remoting in Editor: The remoting logic in InputSystem.cs has been updated to use the RemoteInputPlayerConnectionEditor singleton through the s_GetInstance hook, ensuring it survives domain reloads correctly.
  • Native Buffer Management (Pointer Nulling): The risky line eventBufferPtr->eventBuffer = (void*)0; has been removed from NativeInputRuntime.cs. However, note that capacityInBytes is still being zeroed (see below).
  • Merge Conflict Markers: The commented-out conflict markers in InputActionAsset.cs and elsewhere have been cleaned up.
  • Compilation Errors in Player Builds: The using UnityEditor statements have been removed from the runtime paths of InputSystem.cs and other core files.

⚠️ Still Outstanding

  • Native Buffer Capacity Reset: In NativeInputRuntime.cs (Line 102), eventBufferPtr->capacityInBytes = 0; is still present. As discussed, since the engine owns these buffers, zeroing the capacity when the count is zero might still lead to issues if the engine expects the capacity to remain valid for future events. You mentioned this would be reverted, but it remains in the current diff.
  • IndexOutOfRangeException in Control Scheme Deletion: In ControlSchemeCommands.cs (Line 219), the logic still lacks a check for when the last control scheme is deleted (array size 0). This will result in a call to GetArrayElementAtIndex(-1).
  • USS Resource Path Error: In InputActionsEditorStyles.uss (Line 162), the path is still missing a slash: resource('Packages/com.unity.inputsystemInputSystem/Editor/Icons/d_ChevronUp.png').
  • Missing Null Checks for onChangedCallback: The onChangedCallback in AxisCompositeEditor.cs (Line 34), Vector2CompositeEditor.cs, and Vector3CompositeEditor.cs is still invoked without a null check.
  • Null-Safety in InitializeGlobalActionState: In InputActionState.cs (Line 4342), the call InputSystem.s_IsDomainReloadDisabled() is not null-safe. Since execution order for RuntimeInitializeOnLoadMethod isn't guaranteed, this could crash if called before the Editor initializer registers the hook.
  • Duplicated Define Constraint: Unity.InputSystem.IntegrationTests.asmdef (Lines 22-23) still has duplicate `

🤖 Helpful? 👍/👎

@jfreire-unity
Copy link
Copy Markdown
Collaborator Author

/crc

Copy link
Copy Markdown
Contributor

@u-pr u-pr Bot left a comment

Choose a reason for hiding this comment

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

Great

I've consolidated the review results. Most critical issues have already been reported and discussed. Two additional minor items were found: a documentation typo and a suggestion for improving enum synchronization.

🤖 Helpful? 👍/👎

internal static Action<bool> s_SuppressAnalytics;

// API constants for analytics (matches Editor InputExitPlayModeAnalytic.Api enum)
internal enum Api
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

low
This Api enum appears to mirror the InputExitPlayModeAnalytic.Api enum in the Editor assembly. It might be helpful to add a cross-reference comment in both locations to ensure they remain in sync when new APIs are added to analytics tracking.

🤖 Helpful? 👍/👎

@@ -2985,23 +2963,25 @@ internal static bool ShouldDrawWarningIconForBinding(string bindingPath)
#region Actions


// This is called from InitializeInEditor() and InitializeInPlayer() to make sure
// This is called to make sure
// project-wide actions are all active in they are active in all of these MonoBehavior methods:
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

low
Minor typo in the comment: "active in they are active" likely should be "active so they are active".

🤖 Helpful? 👍/👎

…alInitialize()

Co-authored-by: u-pr[bot] <205906871+u-pr[bot]@users.noreply.github.com>
Copy link
Copy Markdown
Collaborator

@ekcoh ekcoh left a comment

Choose a reason for hiding this comment

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

Adding a first batch of comments and will resume from InputActionReference in the diff when I can review more.

[TestCase("Keyboard", "Devices/Precompiled/FastKeyboard.cs")]
[TestCase("Mouse", "Devices/Precompiled/FastMouse.cs")]
[TestCase("Touchscreen", "Devices/Precompiled/FastTouchscreen.cs")]
[TestCase("Keyboard", "InputSystem/Runtime/Devices/Precompiled/FastKeyboard.cs")]
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Why pass the InputSystem/Runtime/ when it seems to always be part of the arg? Either pass the full path or only pass the relevant part IMO since concatenated on line 220. Also would prefer Path.Combine but maybe its irrelevant.


currentTime = 1;
InputSystem.OnPlayModeChange(PlayModeStateChange.ExitingEditMode);
InputSystemEditorInitializer.OnPlayModeChange(PlayModeStateChange.ExitingEditMode);
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Doesn't have to be addressed by this PR, but I believe we should make sure we have a ticket filed on replacing these "faked play-mode state transitions" with real ones when this already sits in a proper editor test?

UnityEngine.TestTools.LogAssert.Expect(LogType.Error, "ScriptableSingleton already exists. Did you query the singleton in a constructor?");
UnityEngine.TestTools.LogAssert.Expect(LogType.Error, "ScriptableSingleton already exists. Did you query the singleton in a constructor?");
// Ensure the singleton is initialized deterministically in editor.
_ = RemoteInputPlayerConnection.instance;
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Exactly, most commonly seen in in inline lambdas though. This looks very odd. Why do we need for this "singleton" to instantiate on this row? It seems like it's created below. Is there some brittle design that might need addressing here?


namespace UnityEngine.InputSystem.Processors
{
#if UNITY_EDITOR
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Shouldn't this #if and matching #endif be enclosing all the content of this file?


namespace UnityEngine.InputSystem.Processors
{
#if UNITY_EDITOR
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

I would recommend enclosing all the content of this file with this #if, and move corresponding #endif to the end as well.

private CustomOrDefaultSetting m_MinSetting;
private CustomOrDefaultSetting m_MaxSetting;
}
#endif
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

IMO, it would be best to enclose all content, other files do this.

private CustomOrDefaultSetting m_MinSetting;
private CustomOrDefaultSetting m_MaxSetting;
}
#endif
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

This #endif can terminate the file

@@ -216,13 +216,13 @@ public static Command DeleteSelectedControlScheme()
if (indexOfArrayElement > serializedArray.arraySize - 1)
return state.With(
selectedControlSchemeIndex: serializedArray.arraySize - 1,
selectedControlScheme: new InputControlScheme(serializedArray.GetArrayElementAtIndex(serializedArray.arraySize - 1)), selectedDeviceRequirementIndex: -1);
selectedControlScheme: InputControlSchemeEditorExtensions.FromSerializedProperty(serializedArray.GetArrayElementAtIndex(serializedArray.arraySize - 1)), selectedDeviceRequirementIndex: -1);
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

I would suggest making a separate bug ticket on this specific issue and related fix, then fix it directly after landing this.

@@ -8,31 +8,6 @@

namespace UnityEngine.InputSystem
{
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

I notice this structure was previously included in EITHER editor OR development builds. To me it's not obvious why that would be, is to be able to access remoting or something else? Just want to make sure this won't make a regression and there might be reasoning from your side around this?

@@ -895,10 +894,14 @@ IEnumerator IEnumerable.GetEnumerator()
internal void MarkAsDirty()
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Hmmm... who calls this? Given that the function is a no-op outside editor builds I wonder if this should only be tracked in the editor context, e.g. list of InputActionAsset references which are dirty instead of doing it this way.

Copy link
Copy Markdown
Collaborator

@ekcoh ekcoh left a comment

Choose a reason for hiding this comment

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

I think InputActionReference need some work. Will post again when I have reviewed this whole PR.

// residing as a sub-asset within a .inputactions asset.
// This is not needed for players since scriptable objects aren't serialized back from within a player.
if (!CanSetReference(this))
if (!CanSetReference())
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

I would suggest that we do this differently. It seems whole CheckImmutableReference is really only relevant for editor use cases so it could move to editor code. That eliminates the need for those function objects. Instead, pass a single function object instead that is only called (if its set) at line where call to CheckImmutableReference() happens, then this is fully separated between editor and player right?

{
#if UNITY_EDITOR
RegisterApiUsage(UnityEngine.InputSystem.Editor.InputExitPlayModeAnalytic.Api.AddBinding);
RegisterAnalyticsApi((int)Api.AddBinding);
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Why are we casting this (and following places to int?)


namespace UnityEngine.InputSystem
{
#if UNITY_EDITOR || DEVELOPMENT_BUILD
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

I wonder if this is all needed, shouldn't this structure be stripped if not referenced in a release build?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

8 participants