diff --git a/Assets/Tests/InputSystem/CoreTests_Actions.cs b/Assets/Tests/InputSystem/CoreTests_Actions.cs index adf5f74c45..513b8a256f 100644 --- a/Assets/Tests/InputSystem/CoreTests_Actions.cs +++ b/Assets/Tests/InputSystem/CoreTests_Actions.cs @@ -5523,56 +5523,68 @@ public class ModificationCases : IEnumerable [Preserve] public ModificationCases() {} + private static readonly Modification[] ModificationAppliesToSingleActionMap = + { + Modification.AddBinding, + Modification.RemoveBinding, + Modification.ModifyBinding, + Modification.ApplyBindingOverride, + Modification.AddAction, + Modification.RemoveAction, + Modification.ChangeBindingMask, + Modification.AddDevice, + Modification.RemoveDevice, + Modification.AddDeviceGlobally, + Modification.RemoveDeviceGlobally, + // Excludes: AddMap, RemoveMap + }; + + private static readonly Modification[] ModificationAppliesToSingletonAction = + { + Modification.AddBinding, + Modification.RemoveBinding, + Modification.ModifyBinding, + Modification.ApplyBindingOverride, + Modification.AddDeviceGlobally, + Modification.RemoveDeviceGlobally, + }; + public IEnumerator GetEnumerator() { - bool ModificationAppliesToSingletonAction(Modification modification) + // NOTE: This executes *outside* of our test fixture during test discovery. + + // We cannot directly create the InputAction objects within GetEnumerator() because the underlying + // asset object might be invalid by the time the tests are actually run. + // + // That is, NUnit TestCases are generated once when the Assembly is loaded and will persist until it's unloaded, + // meaning they'll never be recreated without a Domain Reload. However, since InputActionAsset is a ScriptableObject, + // it could be deleted or otherwise invalidated between test case creation and actual test execution. + // + // So, instead we'll create a delegate to create the Actions object as the parameter for each test case, allowing + // the test case to create an Actions object itself when it actually runs. { - switch (modification) + var actionsFromAsset = new Func(() => new DefaultInputActions().asset); + foreach (var value in Enum.GetValues(typeof(Modification))) { - case Modification.AddBinding: - case Modification.RemoveBinding: - case Modification.ModifyBinding: - case Modification.ApplyBindingOverride: - case Modification.AddDeviceGlobally: - case Modification.RemoveDeviceGlobally: - return true; + yield return new TestCaseData(value, actionsFromAsset); } - return false; } - bool ModificationAppliesToSingleActionMap(Modification modification) { - switch (modification) + var actionMap = new Func(CreateMap); + foreach (var value in Enum.GetValues(typeof(Modification))) { - case Modification.AddMap: - case Modification.RemoveMap: - return false; + if (ModificationAppliesToSingleActionMap.Contains((Modification)value)) + yield return new TestCaseData(value, actionMap); } - return true; } - // NOTE: This executes *outside* of our test fixture during test discovery. - - // Creates a matrix of all permutations of Modifications combined with assets, maps, and singleton actions. - foreach (var func in new Func[] { () => new DefaultInputActions().asset, CreateMap, CreateSingletonAction }) { + var singletonMap = new Func(CreateSingletonAction); foreach (var value in Enum.GetValues(typeof(Modification))) { - var actions = func(); - if (actions is InputActionMap map) - { - if (map.m_SingletonAction != null) - { - if (!ModificationAppliesToSingletonAction((Modification)value)) - continue; - } - else if (!ModificationAppliesToSingleActionMap((Modification)value)) - { - continue; - } - } - - yield return new TestCaseData(value, actions); + if (ModificationAppliesToSingletonAction.Contains((Modification)value)) + yield return new TestCaseData(value, singletonMap); } } } @@ -5603,12 +5615,13 @@ private InputActionMap CreateSingletonAction() [Test] [Category("Actions")] [TestCaseSource(typeof(ModificationCases))] - public void Actions_CanHandleModification(Modification modification, IInputActionCollection2 actions) + public void Actions_CanHandleModification(Modification modification, Func getActions) { // Exclude project-wide actions from this test InputSystem.actions?.Disable(); InputActionState.DestroyAllActionMapStates(); // Required for `onActionChange` to report correct number of changes + var actions = getActions(); var gamepad = InputSystem.AddDevice(); if (modification == Modification.AddDevice || modification == Modification.RemoveDevice) @@ -6338,12 +6351,12 @@ public void Actions_AddingSameProcessorTwice_DoesntImpactUIHideState() InputSystem.RegisterProcessor(); Assert.That(InputSystem.TryGetProcessor("ConstantFloat1Test"), Is.Not.EqualTo(null)); - bool hide = InputSystem.s_Manager.processors.ShouldHideInUI("ConstantFloat1Test"); + bool hide = InputSystem.manager.processors.ShouldHideInUI("ConstantFloat1Test"); Assert.That(hide, Is.EqualTo(false)); InputSystem.RegisterProcessor(); // Check we haven't caused this to alias with itself and cause it to be hidden in the UI - hide = InputSystem.s_Manager.processors.ShouldHideInUI("ConstantFloat1Test"); + hide = InputSystem.manager.processors.ShouldHideInUI("ConstantFloat1Test"); Assert.That(hide, Is.EqualTo(false)); } @@ -6979,7 +6992,7 @@ public void Actions_RegisteringExistingInteractionUnderNewName_CreatesAlias() { InputSystem.RegisterInteraction("TestTest"); - Assert.That(InputSystem.s_Manager.interactions.aliases.Contains(new InternedString("TestTest"))); + Assert.That(InputSystem.manager.interactions.aliases.Contains(new InternedString("TestTest"))); } #endif // UNITY_EDITOR @@ -9298,7 +9311,7 @@ public void Actions_RegisteringExistingCompositeUnderNewName_CreatesAlias() { InputSystem.RegisterBindingComposite("TestTest"); - Assert.That(InputSystem.s_Manager.composites.aliases.Contains(new InternedString("TestTest"))); + Assert.That(InputSystem.manager.composites.aliases.Contains(new InternedString("TestTest"))); } #endif // UNITY_EDITOR @@ -11606,7 +11619,7 @@ public void Actions_DisablingAllActions_RemovesAllTheirStateMonitors() // Not the most elegant test as we reach into internals here but with the // current API, it's not possible to enumerate monitors from outside. - Assert.That(InputSystem.s_Manager.m_StateChangeMonitors, + Assert.That(InputSystem.manager.m_StateChangeMonitors, Has.All.Matches( (InputManager.StateChangeMonitorsForDevice x) => x.memoryRegions.All(r => r.sizeInBits == 0))); } diff --git a/Assets/Tests/InputSystem/CoreTests_Analytics.cs b/Assets/Tests/InputSystem/CoreTests_Analytics.cs index a86829a939..8cb5f822cf 100644 --- a/Assets/Tests/InputSystem/CoreTests_Analytics.cs +++ b/Assets/Tests/InputSystem/CoreTests_Analytics.cs @@ -409,7 +409,6 @@ public void Analytics_ShouldReportBuildAnalytics_WhenNotHavingSettingsAsset() { CollectAnalytics(InputBuildAnalytic.kEventName); - var storedSettings = InputSystem.s_Manager.settings; InputSettings defaultSettings = null; try @@ -461,7 +460,6 @@ public void Analytics_ShouldReportBuildAnalytics_WhenNotHavingSettingsAsset() } finally { - InputSystem.s_Manager.settings = storedSettings; if (defaultSettings != null) Object.DestroyImmediate(defaultSettings); } @@ -473,7 +471,6 @@ public void Analytics_ShouldReportBuildAnalytics_WhenHavingSettingsAssetWithCust { CollectAnalytics(InputBuildAnalytic.kEventName); - var storedSettings = InputSystem.s_Manager.settings; InputSettings customSettings = null; try @@ -555,7 +552,6 @@ public void Analytics_ShouldReportBuildAnalytics_WhenHavingSettingsAssetWithCust } finally { - InputSystem.s_Manager.settings = storedSettings; if (customSettings != null) Object.DestroyImmediate(customSettings); } diff --git a/Assets/Tests/InputSystem/CoreTests_Devices.cs b/Assets/Tests/InputSystem/CoreTests_Devices.cs index 580352dad2..d75cfeed1a 100644 --- a/Assets/Tests/InputSystem/CoreTests_Devices.cs +++ b/Assets/Tests/InputSystem/CoreTests_Devices.cs @@ -572,11 +572,11 @@ public void Devices_AddingDeviceThatUsesBeforeRenderUpdates_CausesBeforeRenderUp InputSystem.RegisterLayout(deviceJson); - Assert.That(InputSystem.s_Manager.updateMask & InputUpdateType.BeforeRender, Is.EqualTo((InputUpdateType)0)); + Assert.That(InputSystem.manager.updateMask & InputUpdateType.BeforeRender, Is.EqualTo((InputUpdateType)0)); InputSystem.AddDevice("CustomGamepad"); - Assert.That(InputSystem.s_Manager.updateMask & InputUpdateType.BeforeRender, Is.EqualTo(InputUpdateType.BeforeRender)); + Assert.That(InputSystem.manager.updateMask & InputUpdateType.BeforeRender, Is.EqualTo(InputUpdateType.BeforeRender)); } [Test] @@ -596,15 +596,15 @@ public void Devices_RemovingLastDeviceThatUsesBeforeRenderUpdates_CausesBeforeRe var device1 = InputSystem.AddDevice("CustomGamepad"); var device2 = InputSystem.AddDevice("CustomGamepad"); - Assert.That(InputSystem.s_Manager.updateMask & InputUpdateType.BeforeRender, Is.EqualTo(InputUpdateType.BeforeRender)); + Assert.That(InputSystem.manager.updateMask & InputUpdateType.BeforeRender, Is.EqualTo(InputUpdateType.BeforeRender)); InputSystem.RemoveDevice(device1); - Assert.That(InputSystem.s_Manager.updateMask & InputUpdateType.BeforeRender, Is.EqualTo(InputUpdateType.BeforeRender)); + Assert.That(InputSystem.manager.updateMask & InputUpdateType.BeforeRender, Is.EqualTo(InputUpdateType.BeforeRender)); InputSystem.RemoveDevice(device2); - Assert.That(InputSystem.s_Manager.updateMask & InputUpdateType.BeforeRender, Is.EqualTo((InputUpdateType)0)); + Assert.That(InputSystem.manager.updateMask & InputUpdateType.BeforeRender, Is.EqualTo((InputUpdateType)0)); } private class TestDeviceReceivingAddAndRemoveNotification : Mouse diff --git a/Assets/Tests/InputSystem/CoreTests_Editor.cs b/Assets/Tests/InputSystem/CoreTests_Editor.cs index 273d9b1bda..fdd75eaaf5 100644 --- a/Assets/Tests/InputSystem/CoreTests_Editor.cs +++ b/Assets/Tests/InputSystem/CoreTests_Editor.cs @@ -147,11 +147,11 @@ public void Editor_CanSaveAndRestoreState() }.ToJson()); InputSystem.Update(); - InputSystem.SaveAndReset(); + m_StateManager.SaveAndReset(false, null); Assert.That(InputSystem.devices, Has.Count.EqualTo(0)); - InputSystem.Restore(); + m_StateManager.Restore(); Assert.That(InputSystem.devices, Has.Exactly(1).With.Property("layout").EqualTo("MyDevice").And.TypeOf()); @@ -165,6 +165,7 @@ public void Editor_CanSaveAndRestoreState() Assert.That(unsupportedDevices[0].interfaceName, Is.EqualTo("Test")); } +#if !ENABLE_CORECLR // onFindLayoutForDevice allows dynamically injecting new layouts into the system that // are custom-tailored at runtime for the discovered device. Make sure that our domain // reload can restore these. @@ -195,12 +196,12 @@ public void Editor_DomainReload_CanRestoreDevicesBuiltWithDynamicallyGeneratedLa Assert.That(InputSystem.devices, Has.Exactly(1).TypeOf()); - InputSystem.SaveAndReset(); + m_StateManager.SaveAndReset(false, null); Assert.That(InputSystem.devices, Is.Empty); - var state = InputSystem.GetSavedState(); - var manager = InputSystem.s_Manager; + var state = m_StateManager.GetSavedState(); + var manager = InputSystem.manager; manager.m_SavedAvailableDevices = state.managerState.availableDevices; manager.m_SavedDeviceStates = state.managerState.devices; @@ -209,7 +210,7 @@ public void Editor_DomainReload_CanRestoreDevicesBuiltWithDynamicallyGeneratedLa Assert.That(InputSystem.devices, Has.Exactly(1).TypeOf()); - InputSystem.Restore(); + m_StateManager.Restore(); } [Test] @@ -219,7 +220,7 @@ public void Editor_DomainReload_PreservesUsagesOnDevices() var device = InputSystem.AddDevice(); InputSystem.SetDeviceUsage(device, CommonUsages.LeftHand); - SimulateDomainReload(); + InputSystem.TestHook_SimulateDomainReload(runtime); var newDevice = InputSystem.devices[0]; @@ -239,7 +240,7 @@ public void Editor_DomainReload_PreservesEnabledState() Assert.That(device.enabled, Is.False); - SimulateDomainReload(); + InputSystem.TestHook_SimulateDomainReload(runtime); var newDevice = InputSystem.devices[0]; @@ -252,7 +253,7 @@ public void Editor_DomainReload_InputSystemInitializationCausesDevicesToBeRecrea { InputSystem.AddDevice(); - SimulateDomainReload(); + InputSystem.TestHook_SimulateDomainReload(runtime); Assert.That(InputSystem.devices, Has.Count.EqualTo(1)); Assert.That(InputSystem.devices[0], Is.TypeOf()); @@ -289,7 +290,7 @@ public void Editor_DomainReload_CustomDevicesAreRestoredAsLayoutsBecomeAvailable InputSystem.RegisterLayout(kLayout); InputSystem.AddDevice("CustomDevice"); - SimulateDomainReload(); + InputSystem.TestHook_SimulateDomainReload(runtime); Assert.That(InputSystem.devices, Is.Empty); @@ -310,7 +311,7 @@ public void Editor_DomainReload_RetainsUnsupportedDevices() }); InputSystem.Update(); - SimulateDomainReload(); + InputSystem.TestHook_SimulateDomainReload(runtime); Assert.That(InputSystem.GetUnsupportedDevices(), Has.Count.EqualTo(1)); Assert.That(InputSystem.GetUnsupportedDevices()[0].interfaceName, Is.EqualTo("SomethingUnknown")); @@ -346,11 +347,13 @@ public void Editor_DomainReload_CanRemoveDevicesDuringDomainReload() Assert.That(InputSystem.devices[0], Is.AssignableTo()); } +#endif // !ENABLE_CORECLR + [Test] [Category("Editor")] public void Editor_RestoringStateWillCleanUpEventHooks() { - InputSystem.SaveAndReset(); + m_StateManager.SaveAndReset(false, null); var receivedOnEvent = 0; var receivedOnDeviceChange = 0; @@ -358,7 +361,7 @@ public void Editor_RestoringStateWillCleanUpEventHooks() InputSystem.onEvent += (e, d) => ++ receivedOnEvent; InputSystem.onDeviceChange += (c, d) => ++ receivedOnDeviceChange; - InputSystem.Restore(); + m_StateManager.Restore(); var device = InputSystem.AddDevice("Gamepad"); InputSystem.QueueStateEvent(device, new GamepadState()); @@ -375,8 +378,8 @@ public void Editor_RestoringStateWillRestoreObjectsOfLayoutBuilder() var builder = new TestLayoutBuilder {layoutToLoad = "Gamepad"}; InputSystem.RegisterLayoutBuilder(() => builder.DoIt(), "TestLayout"); - InputSystem.SaveAndReset(); - InputSystem.Restore(); + m_StateManager.SaveAndReset(false, null); + m_StateManager.Restore(); var device = InputSystem.AddDevice("TestLayout"); @@ -2504,7 +2507,7 @@ public void TODO_Editor_SettingsModifiedInPlayMode_AreRestoredWhenReEnteringEdit [Category("Editor")] public void Editor_AlwaysKeepsEditorUpdatesEnabled() { - Assert.That(InputSystem.s_Manager.updateMask & InputUpdateType.Editor, Is.EqualTo(InputUpdateType.Editor)); + Assert.That(InputSystem.manager.updateMask & InputUpdateType.Editor, Is.EqualTo(InputUpdateType.Editor)); } [Test] @@ -2961,16 +2964,16 @@ public void Editor_LeavingPlayMode_DestroysAllActionStates() action.Enable(); Assert.That(InputActionState.s_GlobalState.globalList.length, Is.EqualTo(1)); - Assert.That(InputSystem.s_Manager.m_StateChangeMonitors.Length, Is.GreaterThan(0)); - Assert.That(InputSystem.s_Manager.m_StateChangeMonitors[0].count, Is.EqualTo(1)); + Assert.That(InputSystem.manager.m_StateChangeMonitors.Length, Is.GreaterThan(0)); + Assert.That(InputSystem.manager.m_StateChangeMonitors[0].count, Is.EqualTo(1)); // Exit play mode. InputSystem.OnPlayModeChange(PlayModeStateChange.ExitingPlayMode); InputSystem.OnPlayModeChange(PlayModeStateChange.EnteredEditMode); Assert.That(InputActionState.s_GlobalState.globalList.length, Is.Zero); - // Won't get removed, just cleared. - Assert.That(InputSystem.s_Manager.m_StateChangeMonitors[0].listeners[0].control, Is.Null); + // Won't get removed, just cleared. + Assert.That(InputSystem.manager.m_StateChangeMonitors[0].listeners[0].control, Is.Null); } [Test] diff --git a/Assets/Tests/InputSystem/CoreTests_Events.cs b/Assets/Tests/InputSystem/CoreTests_Events.cs index 4034353352..7960e6f934 100644 --- a/Assets/Tests/InputSystem/CoreTests_Events.cs +++ b/Assets/Tests/InputSystem/CoreTests_Events.cs @@ -471,7 +471,7 @@ public void Events_CanSwitchToFullyManualUpdates() #if UNITY_EDITOR // Edit mode updates shouldn't have been disabled in editor. - Assert.That(InputSystem.s_Manager.updateMask & InputUpdateType.Editor, Is.Not.Zero); + Assert.That(InputSystem.manager.updateMask & InputUpdateType.Editor, Is.Not.Zero); #endif InputSystem.QueueStateEvent(mouse, new MouseState().WithButton(MouseButton.Left)); @@ -498,8 +498,8 @@ public void Events_CanSwitchToProcessingInFixedUpdates() Assert.That(InputSystem.settings.updateMode, Is.EqualTo(InputSettings.UpdateMode.ProcessEventsInFixedUpdate)); Assert.That(receivedOnChange, Is.True); - Assert.That(InputSystem.s_Manager.updateMask & InputUpdateType.Fixed, Is.EqualTo(InputUpdateType.Fixed)); - Assert.That(InputSystem.s_Manager.updateMask & InputUpdateType.Dynamic, Is.EqualTo(InputUpdateType.None)); + Assert.That(InputSystem.manager.updateMask & InputUpdateType.Fixed, Is.EqualTo(InputUpdateType.Fixed)); + Assert.That(InputSystem.manager.updateMask & InputUpdateType.Dynamic, Is.EqualTo(InputUpdateType.None)); InputSystem.QueueStateEvent(mouse, new MouseState().WithButton(MouseButton.Left)); runtime.currentTimeForFixedUpdate += Time.fixedDeltaTime; @@ -515,19 +515,19 @@ public void Events_CanSwitchToProcessingInFixedUpdates() [Category("Events")] public void Events_ShouldRunUpdate_AppliesUpdateMask() { - InputSystem.s_Manager.updateMask = InputUpdateType.Dynamic; + InputSystem.manager.updateMask = InputUpdateType.Dynamic; Assert.That(runtime.onShouldRunUpdate.Invoke(InputUpdateType.Dynamic)); Assert.That(!runtime.onShouldRunUpdate.Invoke(InputUpdateType.Fixed)); Assert.That(!runtime.onShouldRunUpdate.Invoke(InputUpdateType.Manual)); - InputSystem.s_Manager.updateMask = InputUpdateType.Manual; + InputSystem.manager.updateMask = InputUpdateType.Manual; Assert.That(!runtime.onShouldRunUpdate.Invoke(InputUpdateType.Dynamic)); Assert.That(!runtime.onShouldRunUpdate.Invoke(InputUpdateType.Fixed)); Assert.That(runtime.onShouldRunUpdate.Invoke(InputUpdateType.Manual)); - InputSystem.s_Manager.updateMask = InputUpdateType.Default; + InputSystem.manager.updateMask = InputUpdateType.Default; Assert.That(runtime.onShouldRunUpdate.Invoke(InputUpdateType.Dynamic)); Assert.That(runtime.onShouldRunUpdate.Invoke(InputUpdateType.Fixed)); @@ -1261,20 +1261,20 @@ public void Events_CanPreventEventsFromBeingProcessed() public void EventHandledPolicy_ShouldReflectUserSetting() { // Assert default setting - Assert.That(InputSystem.s_Manager.inputEventHandledPolicy, Is.EqualTo(InputEventHandledPolicy.SuppressStateUpdates)); + Assert.That(InputSystem.manager.inputEventHandledPolicy, Is.EqualTo(InputEventHandledPolicy.SuppressStateUpdates)); // Assert policy can be changed - InputSystem.s_Manager.inputEventHandledPolicy = InputEventHandledPolicy.SuppressActionEventNotifications; - Assert.That(InputSystem.s_Manager.inputEventHandledPolicy, Is.EqualTo(InputEventHandledPolicy.SuppressActionEventNotifications)); + InputSystem.manager.inputEventHandledPolicy = InputEventHandledPolicy.SuppressActionEventNotifications; + Assert.That(InputSystem.manager.inputEventHandledPolicy, Is.EqualTo(InputEventHandledPolicy.SuppressActionEventNotifications)); // Assert policy can be changed back - InputSystem.s_Manager.inputEventHandledPolicy = InputEventHandledPolicy.SuppressStateUpdates; - Assert.That(InputSystem.s_Manager.inputEventHandledPolicy, Is.EqualTo(InputEventHandledPolicy.SuppressStateUpdates)); + InputSystem.manager.inputEventHandledPolicy = InputEventHandledPolicy.SuppressStateUpdates; + Assert.That(InputSystem.manager.inputEventHandledPolicy, Is.EqualTo(InputEventHandledPolicy.SuppressStateUpdates)); // Assert setting property to an invalid value throws exception and do not have side-effects Assert.Throws(() => - InputSystem.s_Manager.inputEventHandledPolicy = (InputEventHandledPolicy)123456); - Assert.That(InputSystem.s_Manager.inputEventHandledPolicy, Is.EqualTo(InputEventHandledPolicy.SuppressStateUpdates)); + InputSystem.manager.inputEventHandledPolicy = (InputEventHandledPolicy)123456); + Assert.That(InputSystem.manager.inputEventHandledPolicy, Is.EqualTo(InputEventHandledPolicy.SuppressStateUpdates)); } class SuppressedActionEventData @@ -1323,7 +1323,7 @@ public void Events_ShouldRespectHandledPolicyUponUpdateAndSuppressedPressTransit int[] expectedStarted, int[] expectedPerformed, int[] expectedCancelled) { // Update setting to match desired scenario - InputSystem.s_Manager.inputEventHandledPolicy = policy; + InputSystem.manager.inputEventHandledPolicy = policy; var seesControlChangesUnderSuppression = policy == InputEventHandledPolicy.SuppressActionEventNotifications; // Use a boxed boolean to allow lambda to capture reference. diff --git a/Assets/Tests/InputSystem/CoreTests_Remoting.cs b/Assets/Tests/InputSystem/CoreTests_Remoting.cs index e8eb988e0a..e44f3db236 100644 --- a/Assets/Tests/InputSystem/CoreTests_Remoting.cs +++ b/Assets/Tests/InputSystem/CoreTests_Remoting.cs @@ -297,7 +297,7 @@ public void Remote_CanConnectInputSystemsOverEditorPlayerConnection() connectionToPlayer.Bind(fakeEditorConnection, true); // Bind a local remote on the player side. - var local = new InputRemoting(InputSystem.s_Manager); + var local = new InputRemoting(InputSystem.manager); local.Subscribe(connectionToEditor); connectionToEditor.Subscribe(local); @@ -477,17 +477,13 @@ private class FakeRemote : IDisposable public FakeRemote() { runtime = new InputTestRuntime(); - var manager = new InputManager(); - manager.m_Settings = ScriptableObject.CreateInstance(); - manager.InitializeData(); - manager.InstallRuntime(runtime); - manager.ApplySettings(); + var manager = InputManager.CreateAndInitialize(runtime, null, true); - local = new InputRemoting(InputSystem.s_Manager); + local = new InputRemoting(InputSystem.manager); remote = new InputRemoting(manager); var remoteInstaller = new GlobalsInstallerObserver(manager); - var localInstaller = new GlobalsInstallerObserver(InputSystem.s_Manager); + var localInstaller = new GlobalsInstallerObserver(InputSystem.manager); // The installers will ensure the globals environment is prepared right before // the receiver processes the message. There are some static fields, such as @@ -505,14 +501,12 @@ public FakeRemote() public void SwitchToRemoteState() { - InputSystem.s_Manager = remoteManager; - InputStateBuffers.SwitchTo(remoteManager.m_StateBuffers, remoteManager.defaultUpdateType); + InputSystem.TestHook_SwitchToDifferentInputManager(remoteManager); } public void SwitchToLocalState() { - InputSystem.s_Manager = localManager; - InputStateBuffers.SwitchTo(localManager.m_StateBuffers, localManager.defaultUpdateType); + InputSystem.TestHook_SwitchToDifferentInputManager(localManager); } public void Dispose() @@ -524,8 +518,8 @@ public void Dispose() } if (remoteManager != null) { - Object.Destroy(remoteManager.m_Settings); - remoteManager.Destroy(); + Object.Destroy(remoteManager.settings); + remoteManager.Dispose(); } } } diff --git a/Assets/Tests/InputSystem/CoreTests_State.cs b/Assets/Tests/InputSystem/CoreTests_State.cs index 8bc38bf728..12c185d9ab 100644 --- a/Assets/Tests/InputSystem/CoreTests_State.cs +++ b/Assets/Tests/InputSystem/CoreTests_State.cs @@ -1235,7 +1235,7 @@ public void State_FixedUpdatesAreDisabledByDefault() { Assert.That(InputSystem.settings.updateMode, Is.EqualTo(InputSettings.UpdateMode.ProcessEventsInDynamicUpdate)); Assert.That(runtime.onShouldRunUpdate(InputUpdateType.Fixed), Is.False); - Assert.That(InputSystem.s_Manager.updateMask & InputUpdateType.Fixed, Is.EqualTo(InputUpdateType.None)); + Assert.That(InputSystem.manager.updateMask & InputUpdateType.Fixed, Is.EqualTo(InputUpdateType.None)); } [Test] diff --git a/Assets/Tests/InputSystem/Plugins/HIDTests.cs b/Assets/Tests/InputSystem/Plugins/HIDTests.cs index 9b51d0aa7e..8b9de84ecc 100644 --- a/Assets/Tests/InputSystem/Plugins/HIDTests.cs +++ b/Assets/Tests/InputSystem/Plugins/HIDTests.cs @@ -814,8 +814,8 @@ public void Devices_HIDDescriptorSurvivesReload() }.ToJson()); InputSystem.Update(); - InputSystem.SaveAndReset(); - InputSystem.Restore(); + m_StateManager.SaveAndReset(false, null); + m_StateManager.Restore(); var hid = (HID)InputSystem.devices.First(x => x is HID); diff --git a/Assets/Tests/InputSystem/Plugins/InputForUITests.cs b/Assets/Tests/InputSystem/Plugins/InputForUITests.cs index c279d7a157..21bbc89767 100644 --- a/Assets/Tests/InputSystem/Plugins/InputForUITests.cs +++ b/Assets/Tests/InputSystem/Plugins/InputForUITests.cs @@ -62,7 +62,7 @@ public override void TearDown() EventProvider.ClearMockProvider(); m_InputForUIEvents.Clear(); - InputSystem.s_Manager.actions = storedActions; + InputSystem.manager.actions = storedActions; #if UNITY_EDITOR if (File.Exists(kAssetPath)) @@ -195,7 +195,7 @@ public void UIActionNavigation_FiresUINavigationEvents_FromInputsGamepadJoystick // Remove the project-wide actions asset in play mode and player. // It will call InputSystem.onActionChange and re-set InputSystemProvider.actionAsset // This the case where no project-wide actions asset is available in the project. - InputSystem.s_Manager.actions = null; + InputSystem.manager.actions = null; } Update(); @@ -269,7 +269,7 @@ public void UIActionSubmit_FiresUISubmitEvents_FromInputsGamepadJoystickAndKeybo Update(); if (!useProjectWideActionsAsset) { - InputSystem.s_Manager.actions = null; + InputSystem.manager.actions = null; } Update(); @@ -306,7 +306,7 @@ public void UIActionCancel_FiresUICancelEvents_FromInputsGamepadAndKeyboard(bool Update(); if (!useProjectWideActionsAsset) { - InputSystem.s_Manager.actions = null; + InputSystem.manager.actions = null; } Update(); @@ -336,7 +336,7 @@ public void UIActionPoint_FiresUIPointEvents_FromInputsMousePenAndTouch(bool use Update(); if (!useProjectWideActionsAsset) { - InputSystem.s_Manager.actions = null; + InputSystem.manager.actions = null; } Update(); @@ -388,7 +388,7 @@ public void UIActionClick_FiresUIClickEvents_FromInputsMousePenAndTouch(bool use Update(); if (!useProjectWideActionsAsset) { - InputSystem.s_Manager.actions = null; + InputSystem.manager.actions = null; } Update(); @@ -475,7 +475,7 @@ public void UIActionScroll_FiresUIScrollEvents_FromInputMouse(bool useProjectWid Update(); if (!useProjectWideActionsAsset) { - InputSystem.s_Manager.actions = null; + InputSystem.manager.actions = null; } Update(); @@ -530,7 +530,7 @@ public void UIActionScroll_ReceivesNormalizedScrollWheelDelta(float scrollWheelD public void DefaultActions_ShouldNotGenerateAnyVerificationWarnings(bool useProjectWideActions) { if (!useProjectWideActions) - InputSystem.s_Manager.actions = null; + InputSystem.manager.actions = null; Update(); LogAssert.NoUnexpectedReceived(); } @@ -542,7 +542,7 @@ public void ActionsWithoutUIMap_ShouldNotGenerateWarnings() var asset = ProjectWideActionsAsset.CreateDefaultAssetAtPath(kAssetPath); asset.RemoveActionMap(asset.FindActionMap("UI", throwIfNotFound: true)); - InputSystem.s_Manager.actions = asset; + InputSystem.manager.actions = asset; Update(); LogAssert.NoUnexpectedReceived(); @@ -556,7 +556,7 @@ public void ActionsWithUIMap_MissingActions_ShouldGenerateWarnings() asset.RemoveActionMap(asset.FindActionMap("UI", throwIfNotFound: true)); asset.AddActionMap(new InputActionMap("UI")); // An empty UI map should log warnings. - InputSystem.s_Manager.actions = asset; + InputSystem.manager.actions = asset; Update(); var link = EditorHelpers.GetHyperlink(kAssetPath); @@ -589,7 +589,7 @@ public void ActionMapWithNonExistentRequiredAction_ShouldGenerateWarning(string var action = asset.FindAction(actionPath); action.Rename("Other"); - InputSystem.s_Manager.actions = asset; + InputSystem.manager.actions = asset; Update(); //var link = AssetDatabase.GetAssetPath()//EditorHelpers.GetHyperlink(kAssetPath); @@ -632,7 +632,7 @@ public void ActionMapWithUnboundRequiredAction_ShouldGenerateWarning(string acti asset.AddActionMap(newMap); - InputSystem.s_Manager.actions = asset; + InputSystem.manager.actions = asset; Update(); LogAssert.Expect(LogType.Warning, new Regex($"^InputAction with path '{actionPath}' in asset \"{kAssetPath}\" do not have any configured bindings.")); @@ -657,7 +657,7 @@ public void ActionWithUnexpectedActionType_ShouldGenerateWarning(string actionPa var expectedType = action.type; action.m_Type = unexpectedType; // change directly via internals for now - InputSystem.s_Manager.actions = asset; + InputSystem.manager.actions = asset; Update(); LogAssert.Expect(LogType.Warning, @@ -683,7 +683,7 @@ public void ActionWithDifferentExpectedControlType_ShouldGenerateWarning(string var expectedControlType = action.expectedControlType; action.expectedControlType = unexpectedControlType; - InputSystem.s_Manager.actions = asset; + InputSystem.manager.actions = asset; Update(); LogAssert.Expect(LogType.Warning, diff --git a/Assets/Tests/InputSystem/Plugins/SteamTests.cs b/Assets/Tests/InputSystem/Plugins/SteamTests.cs index 09a1039bc1..2298dfc71a 100644 --- a/Assets/Tests/InputSystem/Plugins/SteamTests.cs +++ b/Assets/Tests/InputSystem/Plugins/SteamTests.cs @@ -36,13 +36,7 @@ public override void Setup() public override void TearDown() { base.TearDown(); - m_SteamAPI = null; - - SteamSupport.s_API = null; - SteamSupport.s_InputDevices = null; - SteamSupport.s_ConnectedControllers = null; - SteamSupport.s_InputDeviceCount = 0; - SteamSupport.s_HooksInstalled = false; + SteamSupport.Shutdown(); } [Test] diff --git a/Packages/com.unity.inputsystem/InputSystem/Actions/InputActionAsset.cs b/Packages/com.unity.inputsystem/InputSystem/Actions/InputActionAsset.cs index bf91ff49f4..2af430ca5e 100644 --- a/Packages/com.unity.inputsystem/InputSystem/Actions/InputActionAsset.cs +++ b/Packages/com.unity.inputsystem/InputSystem/Actions/InputActionAsset.cs @@ -895,7 +895,7 @@ IEnumerator IEnumerable.GetEnumerator() internal void MarkAsDirty() { #if UNITY_EDITOR - InputSystem.TrackDirtyInputActionAsset(this); + DirtyAssetTracker.TrackDirtyInputActionAsset(this); #endif } diff --git a/Packages/com.unity.inputsystem/InputSystem/Actions/InputActionMap.cs b/Packages/com.unity.inputsystem/InputSystem/Actions/InputActionMap.cs index 0375f5865d..a07f286f7a 100644 --- a/Packages/com.unity.inputsystem/InputSystem/Actions/InputActionMap.cs +++ b/Packages/com.unity.inputsystem/InputSystem/Actions/InputActionMap.cs @@ -324,7 +324,6 @@ public event Action actionTriggered /// public InputActionMap() { - s_NeedToResolveBindings = true; } /// @@ -816,9 +815,6 @@ private enum Flags BindingsForEachActionInitialized = 1 << 3, } - internal static int s_DeferBindingResolution; - internal static bool s_NeedToResolveBindings; - internal struct DeviceArray { private bool m_HaveValue; @@ -1202,9 +1198,6 @@ internal bool LazyResolveBindings(bool fullResolve) m_ControlsForEachAction = null; controlsForEachActionInitialized = false; - // Indicate that there is at least one action map that has a change - s_NeedToResolveBindings = true; - // If we haven't had to resolve bindings yet, we can wait until when we // actually have to. if (m_State == null) @@ -1218,7 +1211,10 @@ internal bool LazyResolveBindings(bool fullResolve) needToResolveBindings = true; bindingResolutionNeedsFullReResolve |= fullResolve; - if (s_DeferBindingResolution > 0) + // Indicate that there is at least one action map that has a change + InputSystem.manager.bindingsNeedResolving = true; + + if (InputSystem.manager.areDeferredBindingsToResolve) return false; // Have to do it straight away. @@ -1237,7 +1233,7 @@ internal bool ResolveBindingsIfNecessary() { if (m_State != null && m_State.isProcessingControlStateChange) { - Debug.Assert(s_DeferBindingResolution > 0, "While processing control state changes, binding resolution should be suppressed"); + Debug.Assert(InputSystem.manager.areDeferredBindingsToResolve, "While processing control state changes, binding resolution should be suppressed"); return false; } @@ -1998,9 +1994,6 @@ public void OnBeforeSerialize() /// public void OnAfterDeserialize() { - // Indicate that there is at least one action map that has a change - s_NeedToResolveBindings = true; - m_State = null; m_MapIndexInState = InputActionState.kInvalidIndex; m_EnabledActionsCount = 0; diff --git a/Packages/com.unity.inputsystem/InputSystem/Actions/InputActionRebindingExtensions.cs b/Packages/com.unity.inputsystem/InputSystem/Actions/InputActionRebindingExtensions.cs index 2f4ebf946e..402ba34639 100644 --- a/Packages/com.unity.inputsystem/InputSystem/Actions/InputActionRebindingExtensions.cs +++ b/Packages/com.unity.inputsystem/InputSystem/Actions/InputActionRebindingExtensions.cs @@ -2147,8 +2147,8 @@ public RebindingOperation Start() m_StartTime = InputState.currentTime; - m_SavedInputEventHandledPolicy = InputSystem.s_Manager.inputEventHandledPolicy; - InputSystem.s_Manager.inputEventHandledPolicy = m_TargetInputEventHandledPolicy; + m_SavedInputEventHandledPolicy = InputSystem.manager.inputEventHandledPolicy; + InputSystem.manager.inputEventHandledPolicy = m_TargetInputEventHandledPolicy; if (m_WaitSecondsAfterMatch > 0 || m_Timeout > 0) { @@ -2655,7 +2655,7 @@ private void ResetAfterMatchCompleted() UnhookOnEvent(); UnhookOnAfterUpdate(); - InputSystem.s_Manager.inputEventHandledPolicy = m_SavedInputEventHandledPolicy; + InputSystem.manager.inputEventHandledPolicy = m_SavedInputEventHandledPolicy; } private void ThrowIfRebindInProgress() @@ -2831,39 +2831,73 @@ public static RebindingOperation PerformInteractiveRebinding(this InputAction ac return rebind; } + internal static DeferBindingResolutionContext DeferBindingResolution() + { + return InputSystem.manager.DeferBindingResolution(); + } + } + + internal sealed class DeferBindingResolutionContext : IDisposable + { + public int deferredCount => m_DeferredCount; + + public void Acquire() + { + ++m_DeferredCount; + } + + public void Release() + { + if (m_DeferredCount > 0 && --m_DeferredCount == 0) + ExecuteDeferredResolutionOfBindings(); + } + /// - /// Temporarily suspend immediate re-resolution of bindings. + /// Allows usage within using() blocks, i.e. we need a "Release" method to match "Acquire", but we also want + /// to implement IDisposable so instance are automatically cleaned up when exiting a using() block. /// - /// - /// When changing control setups, it may take multiple steps to get to the final setup but each individual - /// step may trigger bindings to be resolved again in order to update controls on actions (see ). - /// Using this struct, this can be avoided and binding resolution can be deferred to after the whole operation - /// is complete and the final binding setup is in place. - /// - internal static DeferBindingResolutionWrapper DeferBindingResolution() + public void Dispose() { - if (s_DeferBindingResolutionWrapper == null) - s_DeferBindingResolutionWrapper = new DeferBindingResolutionWrapper(); - s_DeferBindingResolutionWrapper.Acquire(); - return s_DeferBindingResolutionWrapper; + Release(); } - private static DeferBindingResolutionWrapper s_DeferBindingResolutionWrapper; - - internal class DeferBindingResolutionWrapper : IDisposable + private void ExecuteDeferredResolutionOfBindings() { - public void Acquire() + ++m_DeferredCount; + try { - ++InputActionMap.s_DeferBindingResolution; - } + if (bindingsNeedResolving) + { + ref var globalList = ref InputActionState.s_GlobalState.globalList; - public void Dispose() + for (var i = 0; i < globalList.length; ++i) + { + var handle = globalList[i]; + + var state = handle.IsAllocated ? (InputActionState)handle.Target : null; + if (state == null) + { + // Stale entry in the list. State has already been reclaimed by GC. Remove it. + if (handle.IsAllocated) + globalList[i].Free(); + globalList.RemoveAtWithCapacity(i); + --i; + continue; + } + + for (var n = 0; n < state.totalMapCount; ++n) + state.maps[n].ResolveBindingsIfNecessary(); + } + bindingsNeedResolving = false; + } + } + finally { - if (InputActionMap.s_DeferBindingResolution > 0) - --InputActionMap.s_DeferBindingResolution; - if (InputActionMap.s_DeferBindingResolution == 0) - InputActionState.DeferredResolutionOfBindings(); + --m_DeferredCount; } } + + private int m_DeferredCount; + public bool bindingsNeedResolving; // TODO Revisit: Was previously static, but based on changes by Alex+Tim might no longer be needed?! } } diff --git a/Packages/com.unity.inputsystem/InputSystem/Actions/InputActionState.cs b/Packages/com.unity.inputsystem/InputSystem/Actions/InputActionState.cs index 82d6fe1082..4ec8359dad 100644 --- a/Packages/com.unity.inputsystem/InputSystem/Actions/InputActionState.cs +++ b/Packages/com.unity.inputsystem/InputSystem/Actions/InputActionState.cs @@ -1143,7 +1143,7 @@ private void EnableControls(int mapIndex, int controlStartIndex, int numControls "Control start index out of range"); Debug.Assert(controlStartIndex + numControls <= totalControlCount, "Control range out of bounds"); - var manager = InputSystem.s_Manager; + var manager = InputSystem.manager; for (var i = 0; i < numControls; ++i) { var controlIndex = controlStartIndex + i; @@ -1172,7 +1172,7 @@ private void DisableControls(int mapIndex, int controlStartIndex, int numControl "Control start index out of range"); Debug.Assert(controlStartIndex + numControls <= totalControlCount, "Control range out of bounds"); - var manager = InputSystem.s_Manager; + var manager = InputSystem.manager; for (var i = 0; i < numControls; ++i) { var controlIndex = controlStartIndex + i; @@ -1248,7 +1248,7 @@ private void HookOnBeforeUpdate() if (m_OnBeforeUpdateDelegate == null) m_OnBeforeUpdateDelegate = OnBeforeInitialUpdate; - InputSystem.s_Manager.onBeforeUpdate += m_OnBeforeUpdateDelegate; + InputSystem.manager.onBeforeUpdate += m_OnBeforeUpdateDelegate; m_OnBeforeUpdateHooked = true; } @@ -1257,7 +1257,7 @@ private void UnhookOnBeforeUpdate() if (!m_OnBeforeUpdateHooked) return; - InputSystem.s_Manager.onBeforeUpdate -= m_OnBeforeUpdateDelegate; + InputSystem.manager.onBeforeUpdate -= m_OnBeforeUpdateDelegate; m_OnBeforeUpdateHooked = false; } @@ -1290,7 +1290,7 @@ private void OnBeforeInitialUpdate() // Go through all binding states and for every binding that needs an initial state check, // go through all bound controls and for each one that isn't in its default state, pretend // that the control just got actuated. - var manager = InputSystem.s_Manager; + var manager = InputSystem.manager; for (var bindingIndex = 0; bindingIndex < totalBindingCount; ++bindingIndex) { ref var bindingState = ref bindingStates[bindingIndex]; @@ -1526,7 +1526,7 @@ private void ProcessControlStateChange(int mapIndex, int controlIndex, int bindi // Check if we should suppress interaction processing notifications m_Suppressed = (eventPtr != null) && eventPtr.handled && - InputSystem.s_Manager.inputEventHandledPolicy == InputEventHandledPolicy.SuppressActionEventNotifications; + InputSystem.manager.inputEventHandledPolicy == InputEventHandledPolicy.SuppressActionEventNotifications; // Check if we have multiple concurrent actuations on the same action. This may lead us // to ignore certain inputs (e.g. when we get an input of lesser magnitude while already having @@ -2115,7 +2115,7 @@ internal void StartTimeout(float seconds, ref TriggerState trigger) Debug.Assert(trigger.controlIndex >= 0 && trigger.controlIndex < totalControlCount, "Control index out of range"); Debug.Assert(trigger.interactionIndex >= 0 && trigger.interactionIndex < totalInteractionCount, "Interaction index out of range"); - var manager = InputSystem.s_Manager; + var manager = InputSystem.manager; var currentTime = trigger.time; var control = controls[trigger.controlIndex]; var interactionIndex = trigger.interactionIndex; @@ -2144,7 +2144,7 @@ private void StopTimeout(int interactionIndex) ref var interactionState = ref interactionStates[interactionIndex]; - var manager = InputSystem.s_Manager; + var manager = InputSystem.manager; manager.RemoveStateChangeMonitorTimeout(this, interactionState.timerMonitorIndex, interactionIndex); // Update state. @@ -4296,6 +4296,13 @@ internal struct GlobalState internal static GlobalState s_GlobalState; + [RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.BeforeSceneLoad)] + private static void InitializeGlobalActionState() + { + ResetGlobals(); + s_GlobalState = default; + } + internal static ISavedState SaveAndResetState() { // Save current state @@ -4545,40 +4552,6 @@ internal static void OnDeviceChange(InputDevice device, InputDeviceChange change } } - internal static void DeferredResolutionOfBindings() - { - ++InputActionMap.s_DeferBindingResolution; - try - { - if (InputActionMap.s_NeedToResolveBindings) - { - for (var i = 0; i < s_GlobalState.globalList.length; ++i) - { - var handle = s_GlobalState.globalList[i]; - - var state = handle.IsAllocated ? (InputActionState)handle.Target : null; - if (state == null) - { - // Stale entry in the list. State has already been reclaimed by GC. Remove it. - if (handle.IsAllocated) - s_GlobalState.globalList[i].Free(); - s_GlobalState.globalList.RemoveAtWithCapacity(i); - --i; - continue; - } - - for (var n = 0; n < state.totalMapCount; ++n) - state.maps[n].ResolveBindingsIfNecessary(); - } - InputActionMap.s_NeedToResolveBindings = false; - } - } - finally - { - --InputActionMap.s_DeferBindingResolution; - } - } - internal static void DisableAllActions() { for (var i = 0; i < s_GlobalState.globalList.length; ++i) diff --git a/Packages/com.unity.inputsystem/InputSystem/Controls/InputControl.cs b/Packages/com.unity.inputsystem/InputSystem/Controls/InputControl.cs index 4ea6943039..cca6b2e07c 100644 --- a/Packages/com.unity.inputsystem/InputSystem/Controls/InputControl.cs +++ b/Packages/com.unity.inputsystem/InputSystem/Controls/InputControl.cs @@ -1071,7 +1071,7 @@ public void ApplyParameterChanges() private void SetOptimizedControlDataType() { // setting check need to be inline so we clear optimizations if setting is disabled after the fact - m_OptimizedControlDataType = InputSystem.s_Manager.optimizedControlsFeatureEnabled + m_OptimizedControlDataType = InputSystem.manager.optimizedControlsFeatureEnabled ? CalculateOptimizedControlDataType() : (FourCC)InputStateBlock.kFormatInvalid; } @@ -1099,7 +1099,7 @@ internal void SetOptimizedControlDataTypeRecursively() [Conditional("UNITY_EDITOR")] internal void EnsureOptimizationTypeHasNotChanged() { - if (!InputSystem.s_Manager.optimizedControlsFeatureEnabled) + if (!InputSystem.manager.optimizedControlsFeatureEnabled) return; var currentOptimizedControlDataType = CalculateOptimizedControlDataType(); @@ -1326,7 +1326,7 @@ public ref readonly TValue value if ( // if feature is disabled we re-evaluate every call - !InputSystem.s_Manager.readValueCachingFeatureEnabled + !InputSystem.manager.readValueCachingFeatureEnabled // if cached value is stale we re-evaluate and clear the flag || m_CachedValueIsStale // if a processor in stack needs to be re-evaluated, but unprocessedValue is still can be cached @@ -1337,7 +1337,7 @@ public ref readonly TValue value m_CachedValueIsStale = false; } #if DEBUG - else if (InputSystem.s_Manager.paranoidReadValueCachingChecksEnabled) + else if (InputSystem.manager.paranoidReadValueCachingChecksEnabled) { var oldUnprocessedValue = m_UnprocessedCachedValue; var newUnprocessedValue = unprocessedValue; @@ -1393,7 +1393,7 @@ internal unsafe ref readonly TValue unprocessedValue if ( // if feature is disabled we re-evaluate every call - !InputSystem.s_Manager.readValueCachingFeatureEnabled + !InputSystem.manager.readValueCachingFeatureEnabled // if cached value is stale we re-evaluate and clear the flag || m_UnprocessedCachedValueIsStale ) @@ -1402,7 +1402,7 @@ internal unsafe ref readonly TValue unprocessedValue m_UnprocessedCachedValueIsStale = false; } #if DEBUG - else if (InputSystem.s_Manager.paranoidReadValueCachingChecksEnabled) + else if (InputSystem.manager.paranoidReadValueCachingChecksEnabled) { var currentUnprocessedValue = ReadUnprocessedValueFromState(currentStatePtr); if (CompareValue(ref currentUnprocessedValue, ref m_UnprocessedCachedValue)) diff --git a/Packages/com.unity.inputsystem/InputSystem/Controls/InputControlLayout.cs b/Packages/com.unity.inputsystem/InputSystem/Controls/InputControlLayout.cs index 3f36895354..7a5be95c37 100644 --- a/Packages/com.unity.inputsystem/InputSystem/Controls/InputControlLayout.cs +++ b/Packages/com.unity.inputsystem/InputSystem/Controls/InputControlLayout.cs @@ -60,7 +60,7 @@ public delegate string InputDeviceFindControlLayoutDelegate(ref InputDeviceDescr /// public class InputControlLayout { - private static InternedString s_DefaultVariant = new InternedString("Default"); + private static readonly InternedString s_DefaultVariant = new InternedString("Default"); public static InternedString DefaultVariant => s_DefaultVariant; public const string VariantSeparator = ";"; diff --git a/Packages/com.unity.inputsystem/InputSystem/Devices/InputDevice.cs b/Packages/com.unity.inputsystem/InputSystem/Devices/InputDevice.cs index 49c78673c4..a86f838357 100644 --- a/Packages/com.unity.inputsystem/InputSystem/Devices/InputDevice.cs +++ b/Packages/com.unity.inputsystem/InputSystem/Devices/InputDevice.cs @@ -606,7 +606,7 @@ public unsafe long ExecuteCommand(ref TCommand command) var commandPtr = (InputDeviceCommand*)UnsafeUtility.AddressOf(ref command); // Give callbacks first shot. - var manager = InputSystem.s_Manager; + var manager = InputSystem.manager; manager.m_DeviceCommandCallbacks.LockForChanges(); for (var i = 0; i < manager.m_DeviceCommandCallbacks.length; ++i) { diff --git a/Packages/com.unity.inputsystem/InputSystem/Devices/Touchscreen.cs b/Packages/com.unity.inputsystem/InputSystem/Devices/Touchscreen.cs index 3463559f82..beb0422f54 100644 --- a/Packages/com.unity.inputsystem/InputSystem/Devices/Touchscreen.cs +++ b/Packages/com.unity.inputsystem/InputSystem/Devices/Touchscreen.cs @@ -520,6 +520,14 @@ protected TouchControl[] touchControlArray /// Current touch screen. public new static Touchscreen current { get; internal set; } + /// + /// The current global settings for Touchscreen devices. + /// + /// + /// These are cached values taken from . + /// + internal static TouchscreenSettings settings { get; set; } + /// public override void MakeCurrent() { @@ -609,14 +617,14 @@ protected override void FinishSetup() // that to do so we would have to add another record to keep track of timestamps for each touch. And // since we know the maximum time that a tap can take, we have a reasonable estimate for when a prior // tap must have ended. - if (touchStatePtr->tapCount > 0 && InputState.currentTime >= touchStatePtr->startTime + s_TapTime + s_TapDelayTime) + if (touchStatePtr->tapCount > 0 && InputState.currentTime >= touchStatePtr->startTime + settings.tapTime + settings.tapDelayTime) InputState.Change(touches[i].tapCount, (byte)0); } var primaryTouchState = (TouchState*)((byte*)statePtr + stateBlock.byteOffset); if (primaryTouchState->delta != default) InputState.Change(primaryTouch.delta, Vector2.zero); - if (primaryTouchState->tapCount > 0 && InputState.currentTime >= primaryTouchState->startTime + s_TapTime + s_TapDelayTime) + if (primaryTouchState->tapCount > 0 && InputState.currentTime >= primaryTouchState->startTime + settings.tapTime + settings.tapDelayTime) InputState.Change(primaryTouch.tapCount, (byte)0); k_TouchscreenUpdateMarker.End(); @@ -705,11 +713,11 @@ protected override void FinishSetup() // Detect taps. var isTap = newTouchState.isNoneEndedOrCanceled && - (eventPtr.time - newTouchState.startTime) <= s_TapTime && + (eventPtr.time - newTouchState.startTime) <= settings.tapTime && ////REVIEW: this only takes the final delta to start position into account, not the delta over the lifetime of the //// touch; is this robust enough or do we need to make sure that we never move more than the tap radius //// over the entire lifetime of the touch? - (newTouchState.position - newTouchState.startPosition).sqrMagnitude <= s_TapRadiusSquared; + (newTouchState.position - newTouchState.startPosition).sqrMagnitude <= settings.tapRadiusSquared; if (isTap) newTouchState.tapCount = (byte)(currentTouchState[i].tapCount + 1); else @@ -1029,8 +1037,16 @@ private static void TriggerTap(TouchControl control, ref TouchState state, Input state.isTapRelease = false; } - internal static float s_TapTime; - internal static float s_TapDelayTime; - internal static float s_TapRadiusSquared; + private static TouchscreenSettings s_Settings; + } + + /// + /// Cached settings retrieved from . + /// + internal struct TouchscreenSettings + { + public float tapTime; + public float tapDelayTime; + public float tapRadiusSquared; } } diff --git a/Packages/com.unity.inputsystem/InputSystem/Editor/Analytics/InputActionsEditorSessionAnalytic.cs b/Packages/com.unity.inputsystem/InputSystem/Editor/Analytics/InputActionsEditorSessionAnalytic.cs index a966aa18a0..3b6686e078 100644 --- a/Packages/com.unity.inputsystem/InputSystem/Editor/Analytics/InputActionsEditorSessionAnalytic.cs +++ b/Packages/com.unity.inputsystem/InputSystem/Editor/Analytics/InputActionsEditorSessionAnalytic.cs @@ -223,7 +223,7 @@ private bool ImplicitFocus() private double m_FocusStart; private double m_SessionStart; - private static IInputRuntime runtime => InputSystem.s_Manager.m_Runtime; + private static IInputRuntime runtime => InputSystem.manager.runtime; private bool hasFocus => !double.IsNaN(m_FocusStart); private bool hasSession => !double.IsNaN(m_SessionStart); // Returns current time since startup. Note that IInputRuntime explicitly defines in interface that diff --git a/Packages/com.unity.inputsystem/InputSystem/Editor/Analytics/InputBuildAnalytic.cs b/Packages/com.unity.inputsystem/InputSystem/Editor/Analytics/InputBuildAnalytic.cs index dd719aa8f3..61a8dbe747 100644 --- a/Packages/com.unity.inputsystem/InputSystem/Editor/Analytics/InputBuildAnalytic.cs +++ b/Packages/com.unity.inputsystem/InputSystem/Editor/Analytics/InputBuildAnalytic.cs @@ -376,7 +376,7 @@ internal class ReportProcessor : IPostprocessBuildWithReport public void OnPostprocessBuild(BuildReport report) { - InputSystem.s_Manager?.m_Runtime?.SendAnalytic(new InputBuildAnalytic(report)); + InputSystem.manager?.runtime?.SendAnalytic(new InputBuildAnalytic(report)); } } } diff --git a/Packages/com.unity.inputsystem/InputSystem/Editor/AssetEditor/InputActionPropertiesView.cs b/Packages/com.unity.inputsystem/InputSystem/Editor/AssetEditor/InputActionPropertiesView.cs index 093283868f..d6bf5e4759 100644 --- a/Packages/com.unity.inputsystem/InputSystem/Editor/AssetEditor/InputActionPropertiesView.cs +++ b/Packages/com.unity.inputsystem/InputSystem/Editor/AssetEditor/InputActionPropertiesView.cs @@ -81,7 +81,7 @@ protected override void DrawGeneralProperties() private void BuildControlTypeList() { var types = new List(); - var allLayouts = InputSystem.s_Manager.m_Layouts; + var allLayouts = InputSystem.manager.m_Layouts; foreach (var layoutName in allLayouts.layoutTypes.Keys) { if (EditorInputControlLayoutCache.TryGetLayout(layoutName).hideInUI) diff --git a/Packages/com.unity.inputsystem/InputSystem/Editor/ControlPicker/InputControlPickerDropdown.cs b/Packages/com.unity.inputsystem/InputSystem/Editor/ControlPicker/InputControlPickerDropdown.cs index b8f29118a6..ae2ff1e9a4 100644 --- a/Packages/com.unity.inputsystem/InputSystem/Editor/ControlPicker/InputControlPickerDropdown.cs +++ b/Packages/com.unity.inputsystem/InputSystem/Editor/ControlPicker/InputControlPickerDropdown.cs @@ -47,7 +47,7 @@ public void SetExpectedControlLayout(string expectedControlLayout) m_ExpectedControlType = typeof(InputDevice); else m_ExpectedControlType = !string.IsNullOrEmpty(expectedControlLayout) - ? InputSystem.s_Manager.m_Layouts.GetControlTypeForLayout(new InternedString(expectedControlLayout)) + ? InputSystem.manager.m_Layouts.GetControlTypeForLayout(new InternedString(expectedControlLayout)) : null; // If the layout is for a device, automatically switch to device @@ -417,7 +417,7 @@ private bool LayoutMatchesExpectedControlLayoutFilter(string layout) if (m_ExpectedControlType == null) return true; - var layoutType = InputSystem.s_Manager.m_Layouts.GetControlTypeForLayout(new InternedString(layout)); + var layoutType = InputSystem.manager.m_Layouts.GetControlTypeForLayout(new InternedString(layout)); return m_ExpectedControlType.IsAssignableFrom(layoutType); } diff --git a/Packages/com.unity.inputsystem/InputSystem/Editor/Debugger/InputDebuggerWindow.cs b/Packages/com.unity.inputsystem/InputSystem/Editor/Debugger/InputDebuggerWindow.cs index a2f04acd35..3f0d86afb7 100644 --- a/Packages/com.unity.inputsystem/InputSystem/Editor/Debugger/InputDebuggerWindow.cs +++ b/Packages/com.unity.inputsystem/InputSystem/Editor/Debugger/InputDebuggerWindow.cs @@ -218,9 +218,9 @@ private static void ResetDevice(InputDevice device, bool hard) { var playerUpdateType = InputDeviceDebuggerWindow.DetermineUpdateTypeToShow(device); var currentUpdateType = InputState.currentUpdateType; - InputStateBuffers.SwitchTo(InputSystem.s_Manager.m_StateBuffers, playerUpdateType); + InputStateBuffers.SwitchTo(InputSystem.manager.m_StateBuffers, playerUpdateType); InputSystem.ResetDevice(device, alsoResetDontResetControls: hard); - InputStateBuffers.SwitchTo(InputSystem.s_Manager.m_StateBuffers, currentUpdateType); + InputStateBuffers.SwitchTo(InputSystem.manager.m_StateBuffers, currentUpdateType); } private static void ToggleAddDevicesNotSupportedByProject() @@ -231,15 +231,15 @@ private static void ToggleAddDevicesNotSupportedByProject() private void ToggleDiagnosticMode() { - if (InputSystem.s_Manager.m_Diagnostics != null) + if (InputSystem.manager.m_Diagnostics != null) { - InputSystem.s_Manager.m_Diagnostics = null; + InputSystem.manager.m_Diagnostics = null; } else { if (m_Diagnostics == null) m_Diagnostics = new InputDiagnostics(); - InputSystem.s_Manager.m_Diagnostics = m_Diagnostics; + InputSystem.manager.m_Diagnostics = m_Diagnostics; } } @@ -319,7 +319,7 @@ private void DrawToolbarGUI() menu.AddItem(Contents.addDevicesNotSupportedByProjectContent, InputEditorUserSettings.addDevicesNotSupportedByProject, ToggleAddDevicesNotSupportedByProject); - menu.AddItem(Contents.diagnosticsModeContent, InputSystem.s_Manager.m_Diagnostics != null, + menu.AddItem(Contents.diagnosticsModeContent, InputSystem.manager.m_Diagnostics != null, ToggleDiagnosticMode); menu.AddItem(Contents.touchSimulationContent, InputEditorUserSettings.simulateTouch, ToggleTouchSimulation); diff --git a/Packages/com.unity.inputsystem/InputSystem/Editor/Debugger/InputDeviceDebuggerWindow.cs b/Packages/com.unity.inputsystem/InputSystem/Editor/Debugger/InputDeviceDebuggerWindow.cs index f62d6a0ef9..b5eca8c5ab 100644 --- a/Packages/com.unity.inputsystem/InputSystem/Editor/Debugger/InputDeviceDebuggerWindow.cs +++ b/Packages/com.unity.inputsystem/InputSystem/Editor/Debugger/InputDeviceDebuggerWindow.cs @@ -469,9 +469,9 @@ private void RefreshControlTreeValues() m_InputUpdateTypeShownInControlTree = DetermineUpdateTypeToShow(m_Device); var currentUpdateType = InputState.currentUpdateType; - InputStateBuffers.SwitchTo(InputSystem.s_Manager.m_StateBuffers, m_InputUpdateTypeShownInControlTree); + InputStateBuffers.SwitchTo(InputSystem.manager.m_StateBuffers, m_InputUpdateTypeShownInControlTree); m_ControlTree.RefreshControlValues(); - InputStateBuffers.SwitchTo(InputSystem.s_Manager.m_StateBuffers, currentUpdateType); + InputStateBuffers.SwitchTo(InputSystem.manager.m_StateBuffers, currentUpdateType); } [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA1801:ReviewUnusedParameters", MessageId = "device", Justification = "Keep this for future implementation")] diff --git a/Packages/com.unity.inputsystem/InputSystem/Editor/EditorInputControlLayoutCache.cs b/Packages/com.unity.inputsystem/InputSystem/Editor/EditorInputControlLayoutCache.cs index 81dac4b311..7800d5ea06 100644 --- a/Packages/com.unity.inputsystem/InputSystem/Editor/EditorInputControlLayoutCache.cs +++ b/Packages/com.unity.inputsystem/InputSystem/Editor/EditorInputControlLayoutCache.cs @@ -246,7 +246,7 @@ internal static void Clear() // If our layout data is outdated, rescan all the layouts in the system. private static void Refresh() { - var manager = InputSystem.s_Manager; + var manager = InputSystem.manager; if (manager.m_LayoutRegistrationVersion == s_LayoutRegistrationVersion) return; diff --git a/Packages/com.unity.inputsystem/InputSystem/Editor/Internal/InputStateWindow.cs b/Packages/com.unity.inputsystem/InputSystem/Editor/Internal/InputStateWindow.cs index cc3c01de14..7e4a5fd7a3 100644 --- a/Packages/com.unity.inputsystem/InputSystem/Editor/Internal/InputStateWindow.cs +++ b/Packages/com.unity.inputsystem/InputSystem/Editor/Internal/InputStateWindow.cs @@ -176,7 +176,7 @@ private unsafe void PollBuffersFromControl(InputControl control, bool selectBuff private static unsafe void* TryGetDeviceState(InputDevice device, BufferSelector selector) { - var manager = InputSystem.s_Manager; + var manager = InputSystem.manager; var deviceIndex = device.m_DeviceIndex; switch (selector) diff --git a/Packages/com.unity.inputsystem/InputSystem/Editor/Settings/InputEditorUserSettings.cs b/Packages/com.unity.inputsystem/InputSystem/Editor/Settings/InputEditorUserSettings.cs index d2d3527cdb..108f3a36d1 100644 --- a/Packages/com.unity.inputsystem/InputSystem/Editor/Settings/InputEditorUserSettings.cs +++ b/Packages/com.unity.inputsystem/InputSystem/Editor/Settings/InputEditorUserSettings.cs @@ -87,7 +87,7 @@ internal struct SerializedState private static void OnChange() { Save(); - InputSystem.s_Manager.ApplySettings(); + InputSystem.manager.ApplySettings(); } internal static void Load() diff --git a/Packages/com.unity.inputsystem/InputSystem/Editor/UITKAssetEditor/Views/Selectors.cs b/Packages/com.unity.inputsystem/InputSystem/Editor/UITKAssetEditor/Views/Selectors.cs index 30fb76edae..9c639541ac 100644 --- a/Packages/com.unity.inputsystem/InputSystem/Editor/UITKAssetEditor/Views/Selectors.cs +++ b/Packages/com.unity.inputsystem/InputSystem/Editor/UITKAssetEditor/Views/Selectors.cs @@ -287,7 +287,7 @@ public static IEnumerable GetCompositePartOptions(string bindingName, st public static IEnumerable BuildControlTypeList(InputActionType selectedActionType) { - var allLayouts = InputSystem.s_Manager.m_Layouts; + var allLayouts = InputSystem.manager.m_Layouts; // "Any" is always in first position (index 0) yield return "Any"; diff --git a/Packages/com.unity.inputsystem/InputSystem/Events/InputEventListener.cs b/Packages/com.unity.inputsystem/InputSystem/Events/InputEventListener.cs index 6ffb0bbfdd..d9f1354738 100644 --- a/Packages/com.unity.inputsystem/InputSystem/Events/InputEventListener.cs +++ b/Packages/com.unity.inputsystem/InputSystem/Events/InputEventListener.cs @@ -57,8 +57,8 @@ public struct InputEventListener : IObservable { if (callback == null) throw new ArgumentNullException(nameof(callback)); - lock (InputSystem.s_Manager) - InputSystem.s_Manager.onEvent += callback; + lock (InputSystem.manager) + InputSystem.manager.onEvent += callback; return default; } @@ -82,8 +82,8 @@ public struct InputEventListener : IObservable { if (callback == null) throw new ArgumentNullException(nameof(callback)); - lock (InputSystem.s_Manager) - InputSystem.s_Manager.onEvent -= callback; + lock (InputSystem.manager) + InputSystem.manager.onEvent -= callback; return default; } @@ -110,7 +110,7 @@ public IDisposable Subscribe(IObserver observer) s_ObserverState = new ObserverState(); if (s_ObserverState.observers.length == 0) - InputSystem.s_Manager.onEvent += s_ObserverState.onEventDelegate; + InputSystem.manager.onEvent += s_ObserverState.onEventDelegate; s_ObserverState.observers.AppendWithCapacity(observer); return new DisposableObserver { observer = observer }; @@ -142,7 +142,7 @@ public void Dispose() if (index >= 0) s_ObserverState.observers.RemoveAtWithCapacity(index); if (s_ObserverState.observers.length == 0) - InputSystem.s_Manager.onEvent -= s_ObserverState.onEventDelegate; + InputSystem.manager.onEvent -= s_ObserverState.onEventDelegate; } } } diff --git a/Packages/com.unity.inputsystem/InputSystem/InputAnalytics.cs b/Packages/com.unity.inputsystem/InputSystem/InputAnalytics.cs index 7593ebacf4..03a296a09e 100644 --- a/Packages/com.unity.inputsystem/InputSystem/InputAnalytics.cs +++ b/Packages/com.unity.inputsystem/InputSystem/InputAnalytics.cs @@ -55,17 +55,17 @@ public interface IInputAnalytic public static void Initialize(InputManager manager) { - Debug.Assert(manager.m_Runtime != null); + Debug.Assert(manager.runtime != null); } public static void OnStartup(InputManager manager) { - manager.m_Runtime.SendAnalytic(new StartupEventAnalytic(manager)); + manager.runtime.SendAnalytic(new StartupEventAnalytic(manager)); } public static void OnShutdown(InputManager manager) { - manager.m_Runtime.SendAnalytic(new ShutdownEventDataAnalytic(manager)); + manager.runtime.SendAnalytic(new ShutdownEventDataAnalytic(manager)); } /// @@ -278,7 +278,7 @@ internal static class AnalyticExtensions { internal static void Send(this TSource analytic) where TSource : InputAnalytics.IInputAnalytic { - InputSystem.s_Manager?.m_Runtime?.SendAnalytic(analytic); + InputSystem.manager?.runtime?.SendAnalytic(analytic); } } } diff --git a/Packages/com.unity.inputsystem/InputSystem/InputManager.cs b/Packages/com.unity.inputsystem/InputSystem/InputManager.cs index 1edf9ad90f..fd06c3ea9a 100644 --- a/Packages/com.unity.inputsystem/InputSystem/InputManager.cs +++ b/Packages/com.unity.inputsystem/InputSystem/InputManager.cs @@ -22,6 +22,8 @@ #if UNITY_EDITOR using UnityEngine.InputSystem.Editor; +using UnityEngine.Tilemaps; + #endif #if UNITY_EDITOR @@ -45,13 +47,88 @@ namespace UnityEngine.InputSystem /// /// Manages devices, layouts, and event processing. /// - internal partial class InputManager + internal partial class InputManager : IDisposable { + private InputManager() {} + + public static InputManager CreateAndInitialize(IInputRuntime runtime, InputSettings settings, bool fakeManagerForRemotingTests = false) + { + var newInstance = new InputManager(); + + // Not directly used by InputManager, but we need a single instance that's used in a variety of places without a static field + newInstance.m_DeferBindingResolutionContext = new DeferBindingResolutionContext(); + + // If settings object wasn't provided, create a temporary settings object for now + if (settings == null) + { + settings = ScriptableObject.CreateInstance(); + settings.hideFlags = HideFlags.HideAndDontSave; + } + newInstance.m_Settings = settings; + + newInstance.InitializeActions(); + + newInstance.InitializeData(); + newInstance.InstallRuntime(runtime); + + // For remoting tests, we need to create a "fake manager" that simulates a remote endpoint. + // In this case don't install globals as this will corrupt the "local" manager state. + if (!fakeManagerForRemotingTests) + newInstance.InstallGlobals(); + + newInstance.ApplySettings(); + + newInstance.ApplyActions(); + + newInstance.bindingsNeedResolving = true; + return newInstance; + } + + #region Dispose implementation + protected virtual void Dispose(bool disposing) + { + if (!disposedValue) + { + if (disposing) + { + // Notify devices are being removed but don't actually removed them; no point when disposing + for (var i = 0; i < m_DevicesCount; ++i) + m_Devices[i].NotifyRemoved(); + + m_StateBuffers.FreeAll(); + UninstallGlobals(); + + // If we're still holding the "temporary" settings object make sure to delete it + if (m_Settings != null && m_Settings.hideFlags == HideFlags.HideAndDontSave) + Object.DestroyImmediate(m_Settings); + + // Project-wide Actions are never temporary so we do not destroy them. + } + + disposedValue = true; + } + } + + ~InputManager() + { + Dispose(false); + } + + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + private bool disposedValue; + #endregion + public ReadOnlyArray devices => new ReadOnlyArray(m_Devices, 0, m_DevicesCount); public TypeTable processors => m_Processors; public TypeTable interactions => m_Interactions; public TypeTable composites => m_Composites; + internal IInputRuntime runtime => m_Runtime; static readonly ProfilerMarker k_InputUpdateProfilerMarker = new ProfilerMarker("InputUpdate"); static readonly ProfilerMarker k_InputTryFindMatchingControllerMarker = new ProfilerMarker("InputSystem.TryFindMatchingControlLayout"); @@ -97,7 +174,6 @@ public InputSettings settings { get { - Debug.Assert(m_Settings != null); return m_Settings; } set @@ -105,6 +181,10 @@ public InputSettings settings if (value == null) throw new ArgumentNullException(nameof(value)); + // Delete the "temporary" settings if necessary + if (m_Settings != null && m_Settings.hideFlags == HideFlags.HideAndDontSave) + ScriptableObject.DestroyImmediate(m_Settings); + if (m_Settings == value) return; @@ -373,9 +453,6 @@ internal bool ShouldDrawWarningIconForBinding(string bindingPath) "InputSystem.ShouldDrawWarningIconForBinding"); } -#endif // UNITY_EDITOR - -#if UNITY_EDITOR private bool m_RunPlayerUpdatesInEditMode; /// @@ -390,7 +467,8 @@ public bool runPlayerUpdatesInEditMode get => m_RunPlayerUpdatesInEditMode; set => m_RunPlayerUpdatesInEditMode = value; } -#endif + +#endif // UNITY_EDITOR private bool gameIsPlaying => #if UNITY_EDITOR @@ -1840,50 +1918,14 @@ public void Update(InputUpdateType updateType) m_Runtime.Update(updateType); } - internal void Initialize(IInputRuntime runtime, InputSettings settings) - { - Debug.Assert(settings != null); - - m_Settings = settings; - - InitializeActions(); - InitializeData(); - InstallRuntime(runtime); - InstallGlobals(); - - ApplySettings(); - ApplyActions(); - } - - internal void Destroy() - { - // There isn't really much of a point in removing devices but we still - // want to clear out any global state they may be keeping. So just tell - // the devices that they got removed without actually removing them. - for (var i = 0; i < m_DevicesCount; ++i) - m_Devices[i].NotifyRemoved(); - - // Free all state memory. - m_StateBuffers.FreeAll(); - - // Uninstall globals. - UninstallGlobals(); - - // Destroy settings if they are temporary. - if (m_Settings != null && m_Settings.hideFlags == HideFlags.HideAndDontSave) - Object.DestroyImmediate(m_Settings); - - // Project-wide Actions are never temporary so we do not destroy them. - } - // Initialize project-wide actions: // - In editor (edit mode or play-mode) we always use the editor build preferences persisted setting. // - In player build we always attempt to find a preloaded asset. private void InitializeActions() { -#if UNITY_EDITOR + #if UNITY_EDITOR m_Actions = ProjectWideActionsBuildProvider.actionsToIncludeInPlayerBuild; -#else + #else m_Actions = null; var candidates = Resources.FindObjectsOfTypeAll(); foreach (var candidate in candidates) @@ -1894,7 +1936,7 @@ private void InitializeActions() break; } } -#endif // UNITY_EDITOR + #endif // UNITY_EDITOR } internal void InitializeData() @@ -1911,10 +1953,10 @@ internal void InitializeData() // can manually turn off one of them to optimize operation. m_UpdateMask = InputUpdateType.Dynamic | InputUpdateType.Fixed; m_HasFocus = Application.isFocused; -#if UNITY_EDITOR + #if UNITY_EDITOR m_EditorIsActive = true; m_UpdateMask |= InputUpdateType.Editor; -#endif + #endif m_ScrollDeltaBehavior = InputSettings.ScrollDeltaBehavior.UniformAcrossAllPlatforms; @@ -2089,6 +2131,47 @@ internal bool RegisterCustomTypes() return true; // Signal that custom types were extracted and registered. } + private static string GetRegisteredTypeDefaultName(Type type, string name, string suffix) + { + // Default name to name of type without suffix. + if (!string.IsNullOrEmpty(name)) + return name; + name = type.Name; + if (name.EndsWith(suffix)) + name = name.Substring(0, name.Length - suffix.Length); + return name; + } + + internal void RegisterProcessor(Type type, string name = null) + { + if (type == null) throw new ArgumentNullException(nameof(type)); + name = GetRegisteredTypeDefaultName(type, name, "Processor"); + + // Flush out any precompiled layout depending on the processor. + var precompiledLayouts = m_Layouts.precompiledLayouts; + foreach (var key in new List(precompiledLayouts.Keys)) // Need to keep key list stable while iterating; ToList() for some reason not available with .NET Standard 2.0 on Mono. + { + if (StringHelpers.CharacterSeparatedListsHaveAtLeastOneCommonElement(precompiledLayouts[key].metadata, name, ';')) + m_Layouts.precompiledLayouts.Remove(key); + } + + processors.AddTypeRegistration(name, type); + } + + public void RegisterInteraction(Type type, string name = null) + { + if (type == null) throw new ArgumentNullException(nameof(type)); + name = GetRegisteredTypeDefaultName(type, name, "Interaction"); + interactions.AddTypeRegistration(name, type); + } + + public void RegisterBindingComposite(Type type, string name = null) + { + if (type == null) throw new ArgumentNullException(nameof(type)); + name = GetRegisteredTypeDefaultName(type, name, "Composite"); + composites.AddTypeRegistration(name, type); + } + internal void InstallRuntime(IInputRuntime runtime) { if (m_Runtime != null) @@ -2184,6 +2267,30 @@ internal void UninstallGlobals() } } + /// + /// Acquires a temporary "lock" to suspend immediate re-resolution of bindings. + /// + /// + /// When changing control setups, it may take multiple steps to get to the final setup but each individual + /// step may trigger bindings to be resolved again in order to update controls on actions (see ). + /// Using Acquire/Release semantics via the returned context object, binding resolution can be deferred until the entire operation + /// is complete and the final binding setup is in place. + /// + /// NOTE: Returned DeferBindingResolutionContext object is used globally for all ActionMaps. + /// + internal DeferBindingResolutionContext DeferBindingResolution() + { + m_DeferBindingResolutionContext.Acquire(); + return m_DeferBindingResolutionContext; + } + + internal bool areDeferredBindingsToResolve => m_DeferBindingResolutionContext.deferredCount > 0; + public bool bindingsNeedResolving + { + get => m_DeferBindingResolutionContext.bindingsNeedResolving; + set => m_DeferBindingResolutionContext.bindingsNeedResolving = value; + } + [Serializable] internal struct AvailableDevice { @@ -2267,9 +2374,9 @@ internal struct AvailableDevice private bool m_HaveSentStartupAnalytics; #endif - internal IInputRuntime m_Runtime; - internal InputMetrics m_Metrics; - internal InputSettings m_Settings; + private IInputRuntime m_Runtime; + private InputMetrics m_Metrics; + private InputSettings m_Settings; // Extract as booleans (from m_Settings) because feature check is in the hot path @@ -2303,6 +2410,8 @@ internal bool paranoidReadValueCachingChecksEnabled internal IInputDiagnostics m_Diagnostics; #endif + private DeferBindingResolutionContext m_DeferBindingResolutionContext; + ////REVIEW: Make it so that device names *always* have a number appended? (i.e. Gamepad1, Gamepad2, etc. instead of Gamepad, Gamepad1, etc) private void MakeDeviceNameUnique(InputDevice device) @@ -2688,13 +2797,13 @@ private void InstallBeforeUpdateHookIfNecessary() private void RestoreDevicesAfterDomainReloadIfNecessary() { - #if UNITY_EDITOR + #if UNITY_EDITOR && !ENABLE_CORECLR if (m_SavedDeviceStates != null) RestoreDevicesAfterDomainReload(); #endif } -#if UNITY_EDITOR + #if UNITY_EDITOR private void SyncAllDevicesWhenEditorIsActivated() { var isActive = m_Runtime.isEditorActive; @@ -2727,7 +2836,7 @@ internal void SyncAllDevicesAfterEnteringPlayMode() SyncAllDevices(); } -#endif + #endif // UNITY_EDITOR private void WarnAboutDevicesFailingToRecreateAfterDomainReload() { @@ -2747,7 +2856,7 @@ private void WarnAboutDevicesFailingToRecreateAfterDomainReload() // At this point, we throw the device states away and forget about // what we had before the domain reload. m_SavedDeviceStates = null; - #endif + #endif // UNITY_EDITOR } private void OnBeforeUpdate(InputUpdateType updateType) @@ -2791,6 +2900,8 @@ private void OnBeforeUpdate(InputUpdateType updateType) /// internal void ApplySettings() { + Debug.Assert(m_Settings != null); + // Sync update mask. var newUpdateMask = InputUpdateType.Editor; if ((m_UpdateMask & InputUpdateType.BeforeRender) != 0) @@ -2880,10 +2991,14 @@ internal void ApplySettings() m_ParanoidReadValueCachingChecksEnabled = m_Settings.IsFeatureEnabled((InputFeatureNames.kParanoidReadValueCachingChecks)); } - // Cache some values. - Touchscreen.s_TapTime = settings.defaultTapTime; - Touchscreen.s_TapDelayTime = settings.multiTapDelayTime; - Touchscreen.s_TapRadiusSquared = settings.tapRadius * settings.tapRadius; + // Cache Touch specific settings to Touchscreen + Touchscreen.settings = new TouchscreenSettings + { + tapTime = settings.defaultTapTime, + tapDelayTime = settings.multiTapDelayTime, + tapRadiusSquared = settings.tapRadius * settings.tapRadius + }; + // Extra clamp here as we can't tell what we're getting from serialized data. ButtonControl.s_GlobalDefaultButtonPressPoint = Mathf.Clamp(settings.defaultButtonPressPoint, ButtonControl.kMinButtonPressPoint, float.MaxValue); ButtonControl.s_GlobalDefaultButtonReleaseThreshold = settings.buttonReleaseThreshold; @@ -3056,7 +3171,7 @@ internal void OnFocusChanged(bool focus) m_HasFocus = focus; } -#if UNITY_EDITOR + #if UNITY_EDITOR internal void LeavePlayMode() { // Reenable all devices and reset their play mode state. @@ -3084,7 +3199,7 @@ private void OnPlayerLoopInitialization() InputStateBuffers.SwitchTo(m_StateBuffers, InputUpdate.s_LatestUpdateType); } -#endif + #endif // UNITY_EDITOR internal bool ShouldRunUpdate(InputUpdateType updateType) { @@ -3095,7 +3210,7 @@ internal bool ShouldRunUpdate(InputUpdateType updateType) var mask = m_UpdateMask; -#if UNITY_EDITOR + #if UNITY_EDITOR // If the player isn't running, the only thing we run is editor updates, except if // explicitly overriden via `runUpdatesInEditMode`. // NOTE: This means that in edit mode (outside of play mode) we *never* switch to player @@ -3104,7 +3219,7 @@ internal bool ShouldRunUpdate(InputUpdateType updateType) // it will see gamepad inputs going to the editor and respond to them. if (!gameIsPlaying && updateType != InputUpdateType.Editor && !runPlayerUpdatesInEditMode) return false; -#endif + #endif // UNITY_EDITOR return (updateType & mask) != 0; } @@ -3274,7 +3389,7 @@ private unsafe void OnUpdate(InputUpdateType updateType, ref InputEventBuffer ev var currentEventTimeInternal = currentEventReadPtr->internalTime; var currentEventType = currentEventReadPtr->type; -#if UNITY_EDITOR + #if UNITY_EDITOR if (dropStatusEvents) { // If the type here is a status event, ask advance not to leave the event in the buffer. Otherwise, leave it there. @@ -3292,7 +3407,7 @@ private unsafe void OnUpdate(InputUpdateType updateType, ref InputEventBuffer ev m_InputEventStream.Advance(false); continue; } -#endif + #endif // If we're timeslicing, check if the event time is within limits. if (timesliceEvents && currentEventTimeInternal >= currentTime) @@ -3306,10 +3421,10 @@ private unsafe void OnUpdate(InputUpdateType updateType, ref InputEventBuffer ev device = TryGetDeviceById(currentEventReadPtr->deviceId); if (device == null) { -#if UNITY_EDITOR + #if UNITY_EDITOR ////TODO: see if this is a device we haven't created and if so, just ignore m_Diagnostics?.OnCannotFindDeviceForEvent(new InputEventPtr(currentEventReadPtr)); -#endif + #endif m_InputEventStream.Advance(false); continue; @@ -3317,7 +3432,7 @@ private unsafe void OnUpdate(InputUpdateType updateType, ref InputEventBuffer ev // In the editor, we may need to bump events from editor updates into player updates // and vice versa. -#if UNITY_EDITOR + #if UNITY_EDITOR if (isPlaying && !gameHasFocus) { if (m_Settings.editorInputBehaviorInPlayMode == InputSettings.EditorInputBehaviorInPlayMode @@ -3348,7 +3463,7 @@ private unsafe void OnUpdate(InputUpdateType updateType, ref InputEventBuffer ev } } } -#endif + #endif // UNITY_EDITOR // If device is disabled, we let the event through only in certain cases. // Removal and configuration change events should always be processed. @@ -3358,12 +3473,12 @@ private unsafe void OnUpdate(InputUpdateType updateType, ref InputEventBuffer ev (device.m_DeviceFlags & (InputDevice.DeviceFlags.DisabledInRuntime | InputDevice.DeviceFlags.DisabledWhileInBackground)) != 0) { -#if UNITY_EDITOR + #if UNITY_EDITOR // If the device is disabled in the backend, getting events for them // is something that indicates a problem in the backend so diagnose. if ((device.m_DeviceFlags & InputDevice.DeviceFlags.DisabledInRuntime) != 0) m_Diagnostics?.OnEventForDisabledDevice(currentEventReadPtr, device); -#endif + #endif m_InputEventStream.Advance(false); continue; @@ -3435,17 +3550,17 @@ private unsafe void OnUpdate(InputUpdateType updateType, ref InputEventBuffer ev // Give the device a chance to do something with data before we propagate it to event listeners. if (device.hasEventPreProcessor) { -#if UNITY_EDITOR + #if UNITY_EDITOR var eventSizeBeforePreProcessor = currentEventReadPtr->sizeInBytes; -#endif + #endif var shouldProcess = ((IEventPreProcessor)device).PreProcessEvent(currentEventReadPtr); -#if UNITY_EDITOR + #if UNITY_EDITOR if (currentEventReadPtr->sizeInBytes > eventSizeBeforePreProcessor) { k_InputUpdateProfilerMarker.End(); throw new AccessViolationException($"'{device}'.PreProcessEvent tries to grow an event from {eventSizeBeforePreProcessor} bytes to {currentEventReadPtr->sizeInBytes} bytes, this will potentially corrupt events after the current event and/or cause out-of-bounds memory access."); } -#endif + #endif if (!shouldProcess) { // Skip event if PreProcessEvent considers it to be irrelevant. @@ -3498,7 +3613,7 @@ private unsafe void OnUpdate(InputUpdateType updateType, ref InputEventBuffer ev if (currentEventTimeInternal < device.m_LastUpdateTimeInternal && !(deviceIsStateCallbackReceiver && device.stateBlock.format != eventPtr.stateFormat)) { -#if UNITY_EDITOR + #if UNITY_EDITOR m_Diagnostics?.OnEventTimestampOutdated(new InputEventPtr(currentEventReadPtr), device); #elif UNITY_ANDROID // Android keyboards can send events out of order: Holding down a key will send multiple @@ -3529,9 +3644,9 @@ private unsafe void OnUpdate(InputUpdateType updateType, ref InputEventBuffer ev // If the state format doesn't match, ignore the event. if (device.stateBlock.format != eventPtr.stateFormat) { -#if UNITY_EDITOR + #if UNITY_EDITOR m_Diagnostics?.OnEventFormatMismatch(currentEventReadPtr, device); -#endif + #endif break; } @@ -3547,9 +3662,9 @@ private unsafe void OnUpdate(InputUpdateType updateType, ref InputEventBuffer ev // Only events should. If running play mode updates in editor, we want to defer to the play mode // callbacks to set the last update time to avoid dropping events only processed by the editor state. if (device.m_LastUpdateTimeInternal <= eventPtr.internalTime -#if UNITY_EDITOR + #if UNITY_EDITOR && !(updateType == InputUpdateType.Editor && runPlayerUpdatesInEditMode) -#endif + #endif ) device.m_LastUpdateTimeInternal = eventPtr.internalTime; @@ -3794,10 +3909,10 @@ private bool ShouldDiscardEditModeTransitionEvent(FourCC eventType, double event { return (eventType == StateEvent.Type || eventType == DeltaStateEvent.Type) && (updateType & InputUpdateType.Editor) == 0 && - InputSystem.s_SystemObject.exitEditModeTime > 0 && - eventTime >= InputSystem.s_SystemObject.exitEditModeTime && - (eventTime < InputSystem.s_SystemObject.enterPlayModeTime || - InputSystem.s_SystemObject.enterPlayModeTime == 0); + InputSystem.domainStateManager.exitEditModeTime > 0 && + eventTime >= InputSystem.domainStateManager.exitEditModeTime && + (eventTime < InputSystem.domainStateManager.enterPlayModeTime || + InputSystem.domainStateManager.enterPlayModeTime == 0); } /// @@ -3869,7 +3984,7 @@ private void ResetCurrentProcessedEventBytesForDevices() [Conditional("UNITY_EDITOR")] void CheckAllDevicesOptimizedControlsHaveValidState() { - if (!InputSystem.s_Manager.m_OptimizedControlsFeatureEnabled) + if (!InputSystem.manager.m_OptimizedControlsFeatureEnabled) return; foreach (var device in devices) @@ -4026,7 +4141,7 @@ internal unsafe bool UpdateState(InputDevice device, InputUpdateType updateType, { // Update the pressed/not pressed state of all buttons that have changed this update // With enough ButtonControls being checked, it's faster to find out which have actually changed rather than test all. - if (InputSystem.s_Manager.m_ReadValueCachingFeatureEnabled || device.m_UseCachePathForButtonPresses) + if (InputSystem.manager.m_ReadValueCachingFeatureEnabled || device.m_UseCachePathForButtonPresses) { foreach (var button in device.m_UpdatedButtons) { @@ -4103,7 +4218,7 @@ private unsafe void WriteStateChange(InputStateBuffers.DoubleBuffers buffers, in // If we have enough ButtonControls being checked for wasPressedThisFrame/wasReleasedThisFrame, // use this path to find out which have actually changed here. - if (InputSystem.s_Manager.m_ReadValueCachingFeatureEnabled || m_Devices[deviceIndex].m_UseCachePathForButtonPresses) + if (InputSystem.manager.m_ReadValueCachingFeatureEnabled || m_Devices[deviceIndex].m_UseCachePathForButtonPresses) { // if the buffers have just been flipped, and we're doing a full state update, then the state from the // previous update is now in the back buffer, and we should be comparing to that when checking what @@ -4133,7 +4248,7 @@ private bool FlipBuffersForDeviceIfNecessary(InputDevice device, InputUpdateType return false; } -#if UNITY_EDITOR + #if UNITY_EDITOR ////REVIEW: should this use the editor update ticks as quasi-frame-boundaries? // Updates go to the editor only if the game isn't playing or does not have focus. // Otherwise we fall through to the logic that flips for the *next* dynamic and @@ -4147,7 +4262,7 @@ private bool FlipBuffersForDeviceIfNecessary(InputDevice device, InputUpdateType m_StateBuffers.m_EditorStateBuffers.SwapBuffers(device.m_DeviceIndex); return true; } -#endif + #endif // Flip buffers if we haven't already for this frame. if (device.m_CurrentUpdateStepCount != InputUpdate.s_UpdateStepCount) @@ -4165,7 +4280,7 @@ private bool FlipBuffersForDeviceIfNecessary(InputDevice device, InputUpdateType // Stuff everything that we want to survive a domain reload into // a m_SerializedState. -#if UNITY_EDITOR || DEVELOPMENT_BUILD + #if UNITY_EDITOR || DEVELOPMENT_BUILD [Serializable] internal struct DeviceState { @@ -4289,7 +4404,14 @@ internal void RestoreStateWithoutDevices(SerializedState state) #endif m_InputEventHandledPolicy = state.inputEventHandledPolicy; - if (m_Settings != null) + // Cached settings might be null if the ScriptableObject was destroyed; create new default instance in this case. + if (state.settings == null) + { + state.settings = ScriptableObject.CreateInstance(); + state.settings.hideFlags = HideFlags.HideAndDontSave; // Hide from the project Hierarchy and Scene + } + + if (m_Settings != null && m_Settings != state.settings) Object.DestroyImmediate(m_Settings); settings = state.settings; @@ -4312,6 +4434,7 @@ internal void RestoreStateWithoutDevices(SerializedState state) internal DeviceState[] m_SavedDeviceStates; internal AvailableDevice[] m_SavedAvailableDevices; +#if !ENABLE_CORECLR /// /// Recreate devices based on the devices we had before a domain reload. /// @@ -4395,6 +4518,30 @@ internal void RestoreDevicesAfterDomainReload() k_InputRestoreDevicesAfterReloadMarker.End(); } + /// + /// Notifies all devices of removal to better cleanup data when using SimulateDomainReload test hook + /// + /// + /// Devices maintain their own list of Devices within static fields, updated via NotifyAdded and NotifyRemoved overrides. + /// These fields are reset during a real DR, but not so when we "simulate" causing them to report incorrect values when + /// queried via direct APIs, e.g. Gamepad.all. So, to mitigate this we'll call NotifyRemove during this scenario. + /// + internal void TestHook_RemoveDevicesForSimulatedDomainReload() + { + if (m_Devices == null) + return; + + foreach (var device in m_Devices) + { + if (device == null) + break; + + device.NotifyRemoved(); + } + } + +#endif // !ENABLE_CORECLR + // We have two general types of devices we need to care about when recreating devices // after domain reloads: // diff --git a/Packages/com.unity.inputsystem/InputSystem/InputSettings.cs b/Packages/com.unity.inputsystem/InputSystem/InputSettings.cs index 03a81d2150..710ea77238 100644 --- a/Packages/com.unity.inputsystem/InputSystem/InputSettings.cs +++ b/Packages/com.unity.inputsystem/InputSystem/InputSettings.cs @@ -775,7 +775,7 @@ internal bool IsFeatureEnabled(string featureName) internal void OnChange() { if (InputSystem.settings == this) - InputSystem.s_Manager.ApplySettings(); + InputSystem.manager.ApplySettings(); } internal const int s_OldUnsupportedFixedAndDynamicUpdateSetting = 0; diff --git a/Packages/com.unity.inputsystem/InputSystem/InputSystem.cs b/Packages/com.unity.inputsystem/InputSystem/InputSystem.cs index cd155adeda..76e705c1e7 100644 --- a/Packages/com.unity.inputsystem/InputSystem/InputSystem.cs +++ b/Packages/com.unity.inputsystem/InputSystem/InputSystem.cs @@ -59,13 +59,22 @@ namespace UnityEngine.InputSystem #if UNITY_EDITOR [InitializeOnLoad] #endif - public static partial class InputSystem { #if UNITY_EDITOR static readonly ProfilerMarker k_InputInitializeInEditorMarker = new ProfilerMarker("InputSystem.InitializeInEditor"); #endif - static readonly ProfilerMarker k_InputResetMarker = new ProfilerMarker("InputSystem.Reset"); + + static InputSystem() + { + GlobalInitialize(true); + } + + [RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.BeforeSceneLoad)] + private static void RuntimeInitialize() + { + GlobalInitialize(false); + } #region Layouts @@ -856,26 +865,7 @@ public static bool IsFirstLayoutBasedOnSecond(string firstLayoutName, string sec /// public static void RegisterProcessor(Type type, string name = null) { - if (type == null) - throw new ArgumentNullException(nameof(type)); - - // Default name to name of type without Processor suffix. - if (string.IsNullOrEmpty(name)) - { - name = type.Name; - if (name.EndsWith("Processor")) - name = name.Substring(0, name.Length - "Processor".Length); - } - - // Flush out any precompiled layout depending on the processor. - var precompiledLayouts = s_Manager.m_Layouts.precompiledLayouts; - foreach (var key in new List(precompiledLayouts.Keys)) // Need to keep key list stable while iterating; ToList() for some reason not available with .NET Standard 2.0 on Mono. - { - if (StringHelpers.CharacterSeparatedListsHaveAtLeastOneCommonElement(precompiledLayouts[key].metadata, name, ';')) - s_Manager.m_Layouts.precompiledLayouts.Remove(key); - } - - s_Manager.processors.AddTypeRegistration(name, type); + s_Manager.RegisterProcessor(type, name); } /// @@ -2879,7 +2869,7 @@ public static InputSettings settings if (value == null) throw new ArgumentNullException(nameof(value)); - if (s_Manager.m_Settings == value) + if (s_Manager.settings == value) return; // In the editor, we keep track of the settings asset through EditorBuildSettings. @@ -3077,9 +3067,8 @@ public static InputActionAsset actions var current = s_Manager.actions; if (ReferenceEquals(current, value)) return; - var valueIsNotNull = value != null; -#if UNITY_EDITOR + #if UNITY_EDITOR // Do not allow assigning non-persistent assets (pure in-memory objects) if (valueIsNotNull && !EditorUtility.IsPersistent(value)) throw new ArgumentException($"Assigning a non-persistent {nameof(InputActionAsset)} to this property is not allowed. The assigned asset need to be persisted on disc inside the /Assets folder."); @@ -3087,7 +3076,7 @@ public static InputActionAsset actions // Track reference to enable including it in built Players, note that it will discard any non-persisted // object reference ProjectWideActionsBuildProvider.actionsToIncludeInPlayerBuild = value; -#endif // UNITY_EDITOR + #endif // UNITY_EDITOR // Update underlying value s_Manager.actions = value; @@ -3111,7 +3100,6 @@ public static event Action onActionsChange remove => s_Manager.onActionsChange -= value; } - /// /// Event that is signalled when the state of enabled actions in the system changes or /// when actions are triggered. @@ -3213,17 +3201,7 @@ public static event Action onActionChange /// public static void RegisterInteraction(Type type, string name = null) { - if (type == null) - throw new ArgumentNullException(nameof(type)); - - if (string.IsNullOrEmpty(name)) - { - name = type.Name; - if (name.EndsWith("Interaction")) - name = name.Substring(0, name.Length - "Interaction".Length); - } - - s_Manager.interactions.AddTypeRegistration(name, type); + s_Manager.RegisterInteraction(type, name); } /// @@ -3282,17 +3260,7 @@ public static IEnumerable ListInteractions() /// public static void RegisterBindingComposite(Type type, string name) { - if (type == null) - throw new ArgumentNullException(nameof(type)); - - if (string.IsNullOrEmpty(name)) - { - name = type.Name; - if (name.EndsWith("Composite")) - name = name.Substring(0, name.Length - "Composite".Length); - } - - s_Manager.composites.AddTypeRegistration(name, type); + s_Manager.RegisterBindingComposite(type, name); } /// @@ -3395,8 +3363,8 @@ public static int ListEnabledActions(List actions) /// The boolean value to set to public static bool runInBackground { - get => s_Manager.m_Runtime.runInBackground; - set => s_Manager.m_Runtime.runInBackground = value; + get => s_Manager.runtime.runInBackground; + set => s_Manager.runtime.runInBackground = value; } #if UNITY_INPUT_SYSTEM_PLATFORM_SCROLL_DELTA @@ -3412,13 +3380,15 @@ public static bool runInBackground /// Up-to-date metrics on input system activity. public static InputMetrics metrics => s_Manager.metrics; - internal static InputManager s_Manager; - internal static InputRemoting s_Remote; + internal static InputManager manager => s_Manager; + private static InputManager s_Manager; + private static InputRemoting s_Remote; #if DEVELOPMENT_BUILD || UNITY_EDITOR - internal static RemoteInputPlayerConnection s_RemoteConnection; + private static RemoteInputPlayerConnection s_RemoteConnection; + internal static RemoteInputPlayerConnection remoteConnection => s_RemoteConnection; - private static void SetUpRemoting() + internal static void SetUpRemoting() { Debug.Assert(s_Manager != null); @@ -3454,46 +3424,55 @@ private static void SetUpRemotingInternal() #if !UNITY_EDITOR private static bool ShouldEnableRemoting() { -#if UNITY_INCLUDE_TESTS - var isRunningTests = true; -#else - var isRunningTests = false; -#endif - if (isRunningTests) - return false; // Don't remote while running tests. + #if UNITY_INCLUDE_TESTS + return false; // Don't remote while running tests. + #endif + return true; } - #endif + #endif //!UNITY_EDITOR #endif // DEVELOPMENT_BUILD || UNITY_EDITOR // The rest here is internal stuff to manage singletons, survive domain reloads, // and to support the reset ability for tests. - static InputSystem() + + private static bool IsDomainReloadDisabledForPlayMode() { - #if UNITY_EDITOR - InitializeInEditor(); - #else - InitializeInPlayer(); + #if UNITY_EDITOR && !ENABLE_CORECLR + if (!EditorSettings.enterPlayModeOptionsEnabled || (EditorSettings.enterPlayModeOptions & EnterPlayModeOptions.DisableDomainReload) == 0) + return false; #endif + return true; } - ////FIXME: Unity is not calling this method if it's inside an #if block that is not - //// visible to the editor; that shouldn't be the case - [RuntimeInitializeOnLoadMethod(loadType: RuntimeInitializeLoadType.SubsystemRegistration)] - private static void RunInitializeInPlayer() + private static void GlobalInitialize(bool calledFromCtor) { - // We're using this method just to make sure the class constructor is called - // so we don't need any code in here. When the engine calls this method, the - // class constructor will be run if it hasn't been run already. + // This method is called twice: once from the static ctor and again from RuntimeInitialize(). + // We handle the calls differently for the Editor and Player. - // IL2CPP has a bug that causes the class constructor to not be run when - // the RuntimeInitializeOnLoadMethod is invoked. So we need an explicit check - // here until that is fixed (case 1014293). - #if !UNITY_EDITOR - if (s_Manager == null) - InitializeInPlayer(); - #endif +#if UNITY_EDITOR + // If Domain Reloads are enabled, InputSystem is initialized via the ctor and we can ignore + // the second call from "Runtime", otherwise (DRs are disabled) the ctor isn't fired, so we + // must initialize via the Runtime call. + + if (calledFromCtor || IsDomainReloadDisabledForPlayMode()) + { + InitializeInEditor(calledFromCtor); + } +#else + // In the Player, simply initialize InputSystem from the ctor and then execute the initial update + // from the second call. This saves us from needing another RuntimeInitializeOnLoad attribute. + + if (calledFromCtor) + { + InitializeInPlayer(null, true); + } + else + { + RunInitialUpdate(); + } +#endif // UNITY_EDITOR } // Initialization is triggered by accessing InputSystem. Some parts (like InputActions) @@ -3504,56 +3483,77 @@ internal static void EnsureInitialized() } #if UNITY_EDITOR - internal static InputSystemObject s_SystemObject; - internal static void InitializeInEditor(IInputRuntime runtime = null) + // ISX-1860 - #ifdef out Domain Reload specific functionality from CoreCLR + private static InputSystemStateManager s_DomainStateManager; + internal static InputSystemStateManager domainStateManager => s_DomainStateManager; + + internal static void InitializeInEditor(bool calledFromCtor, IInputRuntime runtime = null) { k_InputInitializeInEditorMarker.Begin(); - Reset(runtime: runtime); + bool globalReset = calledFromCtor || !IsDomainReloadDisabledForPlayMode(); - var existingSystemObjects = Resources.FindObjectsOfTypeAll(); - if (existingSystemObjects != null && existingSystemObjects.Length > 0) + // We must initialize a new InputManager object first thing since other parts + // of the init flow depend on it. + if (globalReset) { - ////FIXME: does not preserve action map state + if (s_Manager != null) + s_Manager.Dispose(); - // We're coming back out of a domain reload. We're restoring part of the - // InputManager state here but we're still waiting from layout registrations - // that happen during domain initialization. + // Settings object should get set by an actual InputSettings asset. + s_Manager = InputManager.CreateAndInitialize(runtime ?? NativeInputRuntime.instance, null); + s_Manager.runtime.onPlayModeChanged = OnPlayModeChange; + s_Manager.runtime.onProjectChange = OnProjectChange; - s_SystemObject = existingSystemObjects[0]; - s_Manager.RestoreStateWithoutDevices(s_SystemObject.systemState.managerState); - InputDebuggerWindow.ReviveAfterDomainReload(); + InputEditorUserSettings.s_Settings = new InputEditorUserSettings.SerializedState(); - // Restore remoting state. - s_RemoteConnection = s_SystemObject.systemState.remoteConnection; - SetUpRemoting(); - s_Remote.RestoreState(s_SystemObject.systemState.remotingState, s_Manager); + #if !UNITY_DISABLE_DEFAULT_INPUT_PLUGIN_INITIALIZATION + InputSystem.PerformDefaultPluginInitialization(); + #endif + } - // Get manager to restore devices on first input update. By that time we - // should have all (possibly updated) layout information in place. - s_Manager.m_SavedDeviceStates = s_SystemObject.systemState.managerState.devices; - s_Manager.m_SavedAvailableDevices = s_SystemObject.systemState.managerState.availableDevices; + var existingSystemStateManagers = Resources.FindObjectsOfTypeAll(); + if (existingSystemStateManagers != null && existingSystemStateManagers.Length > 0) + { + if (globalReset) + { + ////FIXME: does not preserve action map state + + // If we're coming back out of a domain reload. We're restoring part of the + // InputManager state here but we're still waiting from layout registrations + // that happen during domain initialization. + + s_DomainStateManager = existingSystemStateManagers[0]; + s_Manager.RestoreStateWithoutDevices(s_DomainStateManager.systemState.managerState); + InputDebuggerWindow.ReviveAfterDomainReload(); - // Restore editor settings. - InputEditorUserSettings.s_Settings = s_SystemObject.systemState.userSettings; + // Restore remoting state. + s_RemoteConnection = s_DomainStateManager.systemState.remoteConnection; + SetUpRemoting(); + s_Remote.RestoreState(s_DomainStateManager.systemState.remotingState, s_Manager); - // Get rid of saved state. - s_SystemObject.systemState = new State(); + // Get s_Manager to restore devices on first input update. By that time we + // should have all (possibly updated) layout information in place. + s_Manager.m_SavedDeviceStates = s_DomainStateManager.systemState.managerState.devices; + s_Manager.m_SavedAvailableDevices = s_DomainStateManager.systemState.managerState.availableDevices; + + // Restore editor settings. + InputEditorUserSettings.s_Settings = s_DomainStateManager.systemState.userSettings; + + // Get rid of saved state. + s_DomainStateManager.systemState = new InputSystemState(); + } } else { - s_SystemObject = ScriptableObject.CreateInstance(); - s_SystemObject.hideFlags = HideFlags.HideAndDontSave; + s_DomainStateManager = ScriptableObject.CreateInstance(); + s_DomainStateManager.hideFlags = HideFlags.HideAndDontSave; // See if we have a remembered settings object. - if (EditorBuildSettings.TryGetConfigObject(InputSettingsProvider.kEditorBuildSettingsConfigKey, - out InputSettings settingsAsset)) + if (EditorBuildSettings.TryGetConfigObject(InputSettingsProvider.kEditorBuildSettingsConfigKey, out InputSettings settingsAsset)) { - if (s_Manager.m_Settings.hideFlags == HideFlags.HideAndDontSave) - ScriptableObject.DestroyImmediate(s_Manager.m_Settings); - s_Manager.m_Settings = settingsAsset; - s_Manager.ApplySettings(); + s_Manager.settings = settingsAsset; } // See if we have a saved actions object @@ -3583,7 +3583,7 @@ internal static void InitializeInEditor(IInputRuntime runtime = null) private static void ShowRestartWarning() { - if (!s_SystemObject.newInputBackendsCheckedAsEnabled && + if (!s_DomainStateManager.newInputBackendsCheckedAsEnabled && !EditorPlayerSettingHelpers.newSystemBackendsEnabled && !Application.isBatchMode) { @@ -3597,7 +3597,7 @@ private static void ShowRestartWarning() EditorHelpers.RestartEditorAndRecompileScripts(); } } - s_SystemObject.newInputBackendsCheckedAsEnabled = true; + s_DomainStateManager.newInputBackendsCheckedAsEnabled = true; EditorApplication.delayCall -= ShowRestartWarning; } @@ -3608,15 +3608,15 @@ internal static void OnPlayModeChange(PlayModeStateChange change) switch (change) { case PlayModeStateChange.ExitingEditMode: - s_SystemObject.settings = JsonUtility.ToJson(settings); - s_SystemObject.exitEditModeTime = InputRuntime.s_Instance.currentTime; - s_SystemObject.enterPlayModeTime = 0; + s_DomainStateManager.settings = JsonUtility.ToJson(settings); + s_DomainStateManager.exitEditModeTime = InputRuntime.s_Instance.currentTime; + s_DomainStateManager.enterPlayModeTime = 0; // InputSystem.actions is not setup yet break; case PlayModeStateChange.EnteredPlayMode: - s_SystemObject.enterPlayModeTime = InputRuntime.s_Instance.currentTime; + s_DomainStateManager.enterPlayModeTime = InputRuntime.s_Instance.currentTime; s_Manager.SyncAllDevicesAfterEnteringPlayMode(); break; @@ -3641,29 +3641,15 @@ internal static void OnPlayModeChange(PlayModeStateChange change) InputActionReference.InvalidateAll(); // Restore settings. - if (!string.IsNullOrEmpty(s_SystemObject.settings)) + if (!string.IsNullOrEmpty(s_DomainStateManager.settings)) { - JsonUtility.FromJsonOverwrite(s_SystemObject.settings, settings); - s_SystemObject.settings = null; + JsonUtility.FromJsonOverwrite(s_DomainStateManager.settings, settings); + s_DomainStateManager.settings = null; settings.OnChange(); } - // reload input action assets marked as dirty from disk - if (s_TrackedDirtyAssets == null) - return; - - foreach (var assetGuid in s_TrackedDirtyAssets) - { - var assetPath = AssetDatabase.GUIDToAssetPath(assetGuid); - - if (string.IsNullOrEmpty(assetPath)) - continue; - - AssetDatabase.ImportAsset(assetPath, ImportAssetOptions.ForceUpdate); - } - - s_TrackedDirtyAssets.Clear(); - + // reload input assets marked as dirty from disk + DirtyAssetTracker.ReloadDirtyAssets(); break; } } @@ -3678,7 +3664,7 @@ public static bool HasNativeObject(Object obj) #endif } - private static void OnProjectChange() + internal static void OnProjectChange() { ////TODO: use dirty count to find whether settings have actually changed // May have added, removed, moved, or renamed settings asset. Force a refresh @@ -3689,7 +3675,7 @@ private static void OnProjectChange() // temporary settings object. // NOTE: We access m_Settings directly here to make sure we're not running into asserts // from the settings getter checking it has a valid object. - if (!HasNativeObject(s_Manager.m_Settings)) + if (!HasNativeObject(s_Manager.settings)) { var newSettings = ScriptableObject.CreateInstance(); newSettings.hideFlags = HideFlags.HideAndDontSave; @@ -3697,49 +3683,27 @@ private static void OnProjectChange() } } - private static HashSet s_TrackedDirtyAssets; - - /// - /// Keep track of InputActionAsset assets that you want to re-load on exiting Play mode. This is useful because - /// some user actions, such as adding a new input binding at runtime, change the in-memory representation of the - /// input action asset and those changes survive when exiting Play mode. If you re-open an Input - /// Action Asset in the Editor that has been changed this way, you see the new bindings that have been added - /// during Play mode which you might not typically want to happen. - /// - /// You can avoid this by force re-loading from disk any asset that has been marked as dirty. - /// - /// - internal static void TrackDirtyInputActionAsset(InputActionAsset asset) +#else // UNITY_EDITOR + internal static void InitializeInPlayer(IInputRuntime runtime, bool loadSettingsAsset) { - if (s_TrackedDirtyAssets == null) - s_TrackedDirtyAssets = new HashSet(); - - if (AssetDatabase.TryGetGUIDAndLocalFileIdentifier(asset, out string assetGuid, out long _) == false) - return; + InputSettings settings = null; - s_TrackedDirtyAssets.Add(assetGuid); - } - -#else - private static void InitializeInPlayer(IInputRuntime runtime = null, InputSettings settings = null) - { - if (settings == null) - settings = Resources.FindObjectsOfTypeAll().FirstOrDefault() ?? ScriptableObject.CreateInstance(); + if (loadSettingsAsset) + settings = Resources.FindObjectsOfTypeAll().FirstOrDefault(); // No domain reloads in the player so we don't need to look for existing // instances. - s_Manager = new InputManager(); - s_Manager.Initialize(runtime ?? NativeInputRuntime.instance, settings); + s_Manager = InputManager.CreateAndInitialize(runtime ?? NativeInputRuntime.instance, settings); -#if !UNITY_DISABLE_DEFAULT_INPUT_PLUGIN_INITIALIZATION + #if !UNITY_DISABLE_DEFAULT_INPUT_PLUGIN_INITIALIZATION PerformDefaultPluginInitialization(); -#endif + #endif // Automatically enable remoting in development players. -#if DEVELOPMENT_BUILD + #if DEVELOPMENT_BUILD if (ShouldEnableRemoting()) SetUpRemoting(); -#endif + #endif // This is the point where we initialise project-wide actions for the Player EnableActions(); @@ -3747,7 +3711,29 @@ private static void InitializeInPlayer(IInputRuntime runtime = null, InputSettin #endif // UNITY_EDITOR - [RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.BeforeSceneLoad)] +#if UNITY_INCLUDE_TESTS + // + // We cannot define UNITY_INPUT_SYSTEM_PROJECT_WIDE_ACTIONS within the Test-Framework assembly, and + // so this hook is needed; it's called from InputTestStateManager.Reset(). + // + internal static void TestHook_DisableActions() + { + // Note that in a test setup we might enter reset with project-wide actions already enabled but the + // reset itself has pushed the action system state on the state stack. To avoid action state memory + // problems we disable actions here and also request asset to be marked dirty and reimported. + DisableActions(triggerSetupChanged: true); + if (s_Manager != null) + s_Manager.actions = null; + } + + internal static void TestHook_EnableActions() + { + // Note this is too early for editor ! actions is not setup yet. + EnableActions(); + } + +#endif // UNITY_INCLUDE_TESTS + private static void RunInitialUpdate() { // Request an initial Update so that user methods such as Start and Awake @@ -3761,8 +3747,24 @@ private static void RunInitialUpdate() } #if !UNITY_DISABLE_DEFAULT_INPUT_PLUGIN_INITIALIZATION - private static void PerformDefaultPluginInitialization() + + #if UNITY_EDITOR + // Plug-ins must only be initialized once, since many of them use static fields. + // When Domain Reloads are disabled, we must guard against this method being called a second time. + private static bool s_PluginsInitialized = false; + #endif + + internal static void PerformDefaultPluginInitialization() { + #if UNITY_EDITOR + if (s_PluginsInitialized) + { + Debug.Assert(false, "Attempted to re-initialize InputSystem Plugins!"); + return; + } + s_PluginsInitialized = true; + #endif + UISupport.Initialize(); #if UNITY_EDITOR || UNITY_STANDALONE || UNITY_WSA || UNITY_ANDROID || UNITY_IOS || UNITY_TVOS || UNITY_VISIONOS @@ -3819,212 +3821,5 @@ private static void PerformDefaultPluginInitialization() } #endif // UNITY_DISABLE_DEFAULT_INPUT_PLUGIN_INITIALIZATION - - // For testing, we want the ability to push/pop system state even in the player. - // However, we don't want it in release players. -#if DEVELOPMENT_BUILD || UNITY_EDITOR - /// - /// Return the input system to its default state. - /// - private static void Reset(bool enableRemoting = false, IInputRuntime runtime = null) - { - k_InputResetMarker.Begin(); - - // Note that in a test setup we might enter reset with project-wide actions already enabled but the - // reset itself has pushed the action system state on the state stack. To avoid action state memory - // problems we disable actions here and also request asset to be marked dirty and reimported. - DisableActions(triggerSetupChanged: true); - if (s_Manager != null) - s_Manager.actions = null; - - // Some devices keep globals. Get rid of them by pretending the devices - // are removed. - if (s_Manager != null) - { - foreach (var device in s_Manager.devices) - device.NotifyRemoved(); - - s_Manager.UninstallGlobals(); - } - - // Create temporary settings. In the tests, this is all we need. But outside of tests, - // this should get replaced with an actual InputSettings asset. - var settings = ScriptableObject.CreateInstance(); - settings.hideFlags = HideFlags.HideAndDontSave; - - #if UNITY_EDITOR - s_Manager = new InputManager(); - s_Manager.Initialize( - runtime: runtime ?? NativeInputRuntime.instance, - settings: settings); - - s_Manager.m_Runtime.onPlayModeChanged = OnPlayModeChange; - s_Manager.m_Runtime.onProjectChange = OnProjectChange; - - InputEditorUserSettings.s_Settings = new InputEditorUserSettings.SerializedState(); - - if (enableRemoting) - SetUpRemoting(); - - #if !UNITY_DISABLE_DEFAULT_INPUT_PLUGIN_INITIALIZATION - PerformDefaultPluginInitialization(); - #endif - - #else - InitializeInPlayer(runtime, settings); - #endif - - Mouse.s_PlatformMouseDevice = null; - - InputEventListener.s_ObserverState = default; - InputUser.ResetGlobals(); - EnhancedTouchSupport.Reset(); - - // This is the point where we initialise project-wide actions for the Editor Play-mode, Editor Tests and Player Tests. - EnableActions(); - - k_InputResetMarker.End(); - } - - /// - /// Destroy the current setup of the input system. - /// - /// - /// > [!NOTE] - /// > This also de-allocates data we're keeping in unmanaged memory! - /// - private static void Destroy() - { - // NOTE: Does not destroy InputSystemObject. We want to destroy input system - // state repeatedly during tests but we want to not create InputSystemObject - // over and over. - s_Manager.Destroy(); - if (s_RemoteConnection != null) - Object.DestroyImmediate(s_RemoteConnection); - #if UNITY_EDITOR - EditorInputControlLayoutCache.Clear(); - InputDeviceDebuggerWindow.s_OnToolbarGUIActions.Clear(); - InputEditorUserSettings.s_Settings = new InputEditorUserSettings.SerializedState(); - #endif - - s_Manager = null; - s_RemoteConnection = null; - s_Remote = null; - } - - /// - /// Snapshot of the state used by the input system. - /// - /// - /// Can be taken across domain reloads. - /// - [Serializable] - internal struct State - { - [NonSerialized] public InputManager manager; - [NonSerialized] public InputRemoting remote; - [SerializeField] public RemoteInputPlayerConnection remoteConnection; - [SerializeField] public InputManager.SerializedState managerState; - [SerializeField] public InputRemoting.SerializedState remotingState; - #if UNITY_EDITOR - [SerializeField] public InputEditorUserSettings.SerializedState userSettings; - [SerializeField] public string systemObject; - #endif - ////TODO: make these saved states capable of surviving domain reloads - [NonSerialized] public ISavedState inputActionState; - [NonSerialized] public ISavedState touchState; - [NonSerialized] public ISavedState inputUserState; - } - - private static Stack s_SavedStateStack; - - internal static State GetSavedState() - { - return s_SavedStateStack.Peek(); - } - - /// - /// Push the current state of the input system onto a stack and - /// reset the system to its default state. - /// - /// - /// The save stack is not able to survive domain reloads. It is intended solely - /// for use in tests. - /// - internal static void SaveAndReset(bool enableRemoting = false, IInputRuntime runtime = null) - { - if (s_SavedStateStack == null) - s_SavedStateStack = new Stack(); - - ////FIXME: does not preserve global state in InputActionState - ////TODO: preserve InputUser state - ////TODO: preserve EnhancedTouchSupport state - - s_SavedStateStack.Push(new State - { - manager = s_Manager, - remote = s_Remote, - remoteConnection = s_RemoteConnection, - managerState = s_Manager.SaveState(), - remotingState = s_Remote?.SaveState() ?? new InputRemoting.SerializedState(), - #if UNITY_EDITOR - userSettings = InputEditorUserSettings.s_Settings, - systemObject = JsonUtility.ToJson(s_SystemObject), - #endif - inputActionState = InputActionState.SaveAndResetState(), - touchState = EnhancedTouch.Touch.SaveAndResetState(), - inputUserState = InputUser.SaveAndResetState() - }); - - Reset(enableRemoting, runtime ?? InputRuntime.s_Instance); // Keep current runtime. - } - - ////FIXME: this method doesn't restore things like InputDeviceDebuggerWindow.onToolbarGUI - /// - /// Restore the state of the system from the last state pushed with . - /// - internal static void Restore() - { - Debug.Assert(s_SavedStateStack != null && s_SavedStateStack.Count > 0); - - // Load back previous state. - var state = s_SavedStateStack.Pop(); - - state.inputUserState.StaticDisposeCurrentState(); - state.touchState.StaticDisposeCurrentState(); - state.inputActionState.StaticDisposeCurrentState(); - - // Nuke what we have. - Destroy(); - - state.inputUserState.RestoreSavedState(); - state.touchState.RestoreSavedState(); - state.inputActionState.RestoreSavedState(); - - s_Manager = state.manager; - s_Remote = state.remote; - s_RemoteConnection = state.remoteConnection; - - InputUpdate.Restore(state.managerState.updateState); - - s_Manager.InstallRuntime(s_Manager.m_Runtime); - s_Manager.InstallGlobals(); - s_Manager.ApplySettings(); - - #if UNITY_EDITOR - InputEditorUserSettings.s_Settings = state.userSettings; - JsonUtility.FromJsonOverwrite(state.systemObject, s_SystemObject); - #endif - - // Get devices that keep global lists (like Gamepad) to re-initialize them - // by pretending the devices have been added. - foreach (var device in devices) - { - device.NotifyAdded(); - device.MakeCurrent(); - } - } - -#endif } } diff --git a/Packages/com.unity.inputsystem/InputSystem/InputSystemObject.cs b/Packages/com.unity.inputsystem/InputSystem/InputSystemObject.cs deleted file mode 100644 index 4bb0ddc027..0000000000 --- a/Packages/com.unity.inputsystem/InputSystem/InputSystemObject.cs +++ /dev/null @@ -1,37 +0,0 @@ -#if UNITY_EDITOR -using UnityEngine.InputSystem.Editor; - -namespace UnityEngine.InputSystem -{ - /// - /// A hidden, internal object we put in the editor to bundle input system state - /// and help us survive domain reloads. - /// - /// - /// Player doesn't need this stuff because there's no domain reloads to survive. - /// - internal class InputSystemObject : ScriptableObject, ISerializationCallbackReceiver - { - [SerializeField] public InputSystem.State systemState; - [SerializeField] public bool newInputBackendsCheckedAsEnabled; - [SerializeField] public string settings; - [SerializeField] public double exitEditModeTime; - [SerializeField] public double enterPlayModeTime; - - public void OnBeforeSerialize() - { - // Save current system state. - systemState.manager = InputSystem.s_Manager; - systemState.remote = InputSystem.s_Remote; - systemState.remoteConnection = InputSystem.s_RemoteConnection; - systemState.managerState = InputSystem.s_Manager.SaveState(); - systemState.remotingState = InputSystem.s_Remote.SaveState(); - systemState.userSettings = InputEditorUserSettings.s_Settings; - } - - public void OnAfterDeserialize() - { - } - } -} -#endif // UNITY_EDITOR diff --git a/Packages/com.unity.inputsystem/InputSystem/InputSystemStateManager.cs b/Packages/com.unity.inputsystem/InputSystem/InputSystemStateManager.cs new file mode 100644 index 0000000000..0c2decc009 --- /dev/null +++ b/Packages/com.unity.inputsystem/InputSystem/InputSystemStateManager.cs @@ -0,0 +1,99 @@ +using System; +using UnityEngine.InputSystem; +using UnityEngine; +#if UNITY_EDITOR +using UnityEngine.InputSystem.Editor; +#endif +using UnityEngine.InputSystem.Utilities; + +namespace UnityEngine.InputSystem +{ +#if UNITY_EDITOR || DEVELOPMENT_BUILD + /// + /// Snapshot of the state used by the input system. + /// + /// + /// Can be taken across domain reloads. + /// + [Serializable] + internal struct InputSystemState + { + [NonSerialized] public InputManager manager; + [NonSerialized] public InputRemoting remote; + [SerializeField] public RemoteInputPlayerConnection remoteConnection; + [SerializeField] public InputManager.SerializedState managerState; + [SerializeField] public InputRemoting.SerializedState remotingState; +#if UNITY_EDITOR + [SerializeField] public InputEditorUserSettings.SerializedState userSettings; + [SerializeField] public string systemObject; +#endif + ////TODO: make these saved states capable of surviving domain reloads + [NonSerialized] public ISavedState inputActionState; + [NonSerialized] public ISavedState touchState; + [NonSerialized] public ISavedState inputUserState; + } + + // ISX-1860 - #ifdef out Domain Reload specific functionality from CoreCLR +#if UNITY_EDITOR + /// + /// A hidden, internal object we put in the editor to bundle input system state + /// and help us survive domain reloads. + /// + /// + /// Player doesn't need this stuff because there's no domain reloads to survive, and + /// also doesn't have domain reloads. + /// + internal class InputSystemStateManager : ScriptableObject, ISerializationCallbackReceiver + { + /// + /// References the "core" input state that must survive domain reloads. + /// + [SerializeField] public InputSystemState systemState; + + /// + /// Triggers Editor restart when enabling NewInput back-ends. + /// + [SerializeField] public bool newInputBackendsCheckedAsEnabled; + + /// + /// Saves and restores InputSettings across domain reloads + /// + /// + /// InputSettings are serialized to JSON which this string holds. + /// + [SerializeField] public string settings; + + /// + /// Timestamp retrieved from InputRuntime.currentTime when exiting EditMode. + /// + /// + /// All input events occurring between exiting EditMode and entering PlayMode are discarded. + /// + [SerializeField] public double exitEditModeTime; + + /// + /// Timestamp retrieved from InputRuntime.currentTime when entering PlayMode. + /// + /// + /// All input events occurring between exiting EditMode and entering PlayMode are discarded. + /// + [SerializeField] public double enterPlayModeTime; + + public void OnBeforeSerialize() + { + // Save current system state. + systemState.manager = InputSystem.manager; + systemState.remote = InputSystem.remoting; + systemState.remoteConnection = InputSystem.remoteConnection; + systemState.managerState = InputSystem.manager.SaveState(); + systemState.remotingState = InputSystem.remoting.SaveState(); + systemState.userSettings = InputEditorUserSettings.s_Settings; + } + + public void OnAfterDeserialize() + { + } + } +#endif // UNITY_EDITOR +#endif // UNITY_EDITOR || DEVELOPMENT_BUILD +} diff --git a/Packages/com.unity.inputsystem/InputSystem/InputSystemObject.cs.meta b/Packages/com.unity.inputsystem/InputSystem/InputSystemStateManager.cs.meta similarity index 83% rename from Packages/com.unity.inputsystem/InputSystem/InputSystemObject.cs.meta rename to Packages/com.unity.inputsystem/InputSystem/InputSystemStateManager.cs.meta index 2d5639d7bb..1237781b72 100644 --- a/Packages/com.unity.inputsystem/InputSystem/InputSystemObject.cs.meta +++ b/Packages/com.unity.inputsystem/InputSystem/InputSystemStateManager.cs.meta @@ -1,5 +1,5 @@ fileFormatVersion: 2 -guid: 5cdce2bffd1e49bda08b3db54a031207 +guid: 3a1038634f66b98469a174a3e1a85190 MonoImporter: externalObjects: {} serializedVersion: 2 diff --git a/Packages/com.unity.inputsystem/InputSystem/InputSystemTestHooks.cs b/Packages/com.unity.inputsystem/InputSystem/InputSystemTestHooks.cs new file mode 100644 index 0000000000..69274b2d08 --- /dev/null +++ b/Packages/com.unity.inputsystem/InputSystem/InputSystemTestHooks.cs @@ -0,0 +1,99 @@ +using System; +using System.Collections.Generic; +#if UNITY_EDITOR +using UnityEngine.InputSystem.Editor; +#endif +using UnityEngine.InputSystem.LowLevel; + +#if UNITY_EDITOR || UNITY_INCLUDE_TESTS + +namespace UnityEngine.InputSystem +{ + /// + /// Extension of class to provide test-specific functionality + /// + public static partial class InputSystem + { +#if UNITY_EDITOR + internal static void TestHook_InitializeForPlayModeTests(bool enableRemoting, IInputRuntime runtime) + { + s_Manager = InputManager.CreateAndInitialize(runtime, null); + + s_Manager.runtime.onPlayModeChanged = InputSystem.OnPlayModeChange; + s_Manager.runtime.onProjectChange = InputSystem.OnProjectChange; + + InputEditorUserSettings.s_Settings = new InputEditorUserSettings.SerializedState(); + + if (enableRemoting) + InputSystem.SetUpRemoting(); + +#if !UNITY_DISABLE_DEFAULT_INPUT_PLUGIN_INITIALIZATION + // Reset the flag so can re-initialize Plugins between tests. + InputSystem.s_PluginsInitialized = false; + InputSystem.PerformDefaultPluginInitialization(); +#endif + } + +#if !ENABLE_CORECLR + internal static void TestHook_SimulateDomainReload(IInputRuntime runtime) + { + // This quite invasive goes into InputSystem internals. Unfortunately, we + // have no proper way of simulating domain reloads ATM. So we directly call various + // internal methods here in a sequence similar to what we'd get during a domain reload. + // Since we're faking it, pass 'true' for calledFromCtor param. + + // Need to notify devices of removal so their static fields are cleaned up + InputSystem.s_Manager.TestHook_RemoveDevicesForSimulatedDomainReload(); + + InputSystem.s_DomainStateManager.OnBeforeSerialize(); + InputSystem.s_DomainStateManager = null; + InputSystem.s_Manager = null; // Do NOT Dispose()! The native memory cannot be freed as it's reference by saved state + InputSystem.s_PluginsInitialized = false; + InputSystem.InitializeInEditor(true, runtime); + } + +#endif +#endif // UNITY_EDITOR + + /// + /// Destroy the current setup of the input system. + /// + /// + /// NOTE: This also de-allocates data we're keeping in unmanaged memory! + /// + internal static void TestHook_DestroyAndReset() + { + // NOTE: Does not destroy InputSystemObject. We want to destroy input system + // state repeatedly during tests but we want to not create InputSystemObject + // over and over. + InputSystem.manager.Dispose(); + if (InputSystem.s_RemoteConnection != null) + Object.DestroyImmediate(InputSystem.s_RemoteConnection); + +#if UNITY_EDITOR + EditorInputControlLayoutCache.Clear(); + InputDeviceDebuggerWindow.s_OnToolbarGUIActions.Clear(); + InputEditorUserSettings.s_Settings = new InputEditorUserSettings.SerializedState(); +#endif + + InputSystem.s_Manager = null; + InputSystem.s_RemoteConnection = null; + InputSystem.s_Remote = null; + } + + internal static void TestHook_RestoreFromSavedState(InputSystemState savedState) + { + s_Manager = savedState.manager; + s_Remote = savedState.remote; + s_RemoteConnection = savedState.remoteConnection; + } + + internal static void TestHook_SwitchToDifferentInputManager(InputManager otherManager) + { + s_Manager = otherManager; + InputStateBuffers.SwitchTo(otherManager.m_StateBuffers, otherManager.defaultUpdateType); + } + } +} + +#endif // UNITY_EDITOR || UNITY_INCLUDE_TESTS diff --git a/Packages/com.unity.inputsystem/InputSystem/InputSystemTestHooks.cs.meta b/Packages/com.unity.inputsystem/InputSystem/InputSystemTestHooks.cs.meta new file mode 100644 index 0000000000..fa0bcfd8fe --- /dev/null +++ b/Packages/com.unity.inputsystem/InputSystem/InputSystemTestHooks.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 1f95d379362f6c74ca1eb6368642199f +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.unity.inputsystem/InputSystem/NativeInputRuntime.cs b/Packages/com.unity.inputsystem/InputSystem/NativeInputRuntime.cs index 6ee7e59f1f..13fc987173 100644 --- a/Packages/com.unity.inputsystem/InputSystem/NativeInputRuntime.cs +++ b/Packages/com.unity.inputsystem/InputSystem/NativeInputRuntime.cs @@ -20,7 +20,26 @@ namespace UnityEngine.InputSystem.LowLevel /// internal class NativeInputRuntime : IInputRuntime { - public static readonly NativeInputRuntime instance = new NativeInputRuntime(); + private static NativeInputRuntime s_Instance; + + // Private ctor exists to enforce Singleton pattern + private NativeInputRuntime() {} + + /// + /// Employ the Singleton pattern for this class and initialize a new instance on first use. + /// + /// + /// This property is typically used to initialize InputManager and isn't used afterwards, i.e. there's + /// no perf impact to the null check. + /// + public static NativeInputRuntime instance + { + get + { + s_Instance ??= new NativeInputRuntime(); + return s_Instance; + } + } public int AllocateDeviceId() { diff --git a/Packages/com.unity.inputsystem/InputSystem/Plugins/DualShock/DualShockGamepadHID.cs b/Packages/com.unity.inputsystem/InputSystem/Plugins/DualShock/DualShockGamepadHID.cs index c3b8314c26..eda2f4c037 100644 --- a/Packages/com.unity.inputsystem/InputSystem/Plugins/DualShock/DualShockGamepadHID.cs +++ b/Packages/com.unity.inputsystem/InputSystem/Plugins/DualShock/DualShockGamepadHID.cs @@ -585,7 +585,7 @@ public unsafe void OnStateEvent(InputEventPtr eventPtr) || newState->buttons2 != currentState->buttons2; if (!actuated) - InputSystem.s_Manager.DontMakeCurrentlyUpdatingDeviceCurrent(); + InputSystem.manager.DontMakeCurrentlyUpdatingDeviceCurrent(); } InputState.Change(this, eventPtr); @@ -675,8 +675,8 @@ public DualSenseHIDInputReport ToHIDInputReport() [StructLayout(LayoutKind.Explicit)] internal struct DualSenseHIDMinimalInputReport { - public static int ExpectedSize1 = 10; - public static int ExpectedSize2 = 78; + public const int ExpectedSize1 = 10; + public const int ExpectedSize2 = 78; [FieldOffset(0)] public byte reportId; [FieldOffset(1)] public byte leftStickX; @@ -920,7 +920,7 @@ public unsafe void OnStateEvent(InputEventPtr eventPtr) || newState->buttons3 != currentState->buttons3; if (!actuatedOrChanged) - InputSystem.s_Manager.DontMakeCurrentlyUpdatingDeviceCurrent(); + InputSystem.manager.DontMakeCurrentlyUpdatingDeviceCurrent(); } InputState.Change(this, eventPtr); diff --git a/Packages/com.unity.inputsystem/InputSystem/Plugins/EnhancedTouch/EnhancedTouchSupport.cs b/Packages/com.unity.inputsystem/InputSystem/Plugins/EnhancedTouch/EnhancedTouchSupport.cs index 92437b307f..b3bdc45b9d 100644 --- a/Packages/com.unity.inputsystem/InputSystem/Plugins/EnhancedTouch/EnhancedTouchSupport.cs +++ b/Packages/com.unity.inputsystem/InputSystem/Plugins/EnhancedTouch/EnhancedTouchSupport.cs @@ -63,10 +63,7 @@ public static class EnhancedTouchSupport /// Whether enhanced touch support is currently enabled. /// /// True if EnhancedTouch support has been enabled. - public static bool enabled => s_Enabled > 0; - - private static int s_Enabled; - private static InputSettings.UpdateMode s_UpdateMode; + public static bool enabled => Touch.s_GlobalState.enhancedTouchEnabled > 0; /// /// Enable enhanced touch support. @@ -82,8 +79,8 @@ public static class EnhancedTouchSupport /// public static void Enable() { - ++s_Enabled; - if (s_Enabled > 1) + ++Touch.s_GlobalState.enhancedTouchEnabled; + if (Touch.s_GlobalState.enhancedTouchEnabled > 1) return; InputSystem.onDeviceChange += OnDeviceChange; @@ -107,8 +104,8 @@ public static void Disable() { if (!enabled) return; - --s_Enabled; - if (s_Enabled > 0) + --Touch.s_GlobalState.enhancedTouchEnabled; + if (Touch.s_GlobalState.enhancedTouchEnabled > 0) return; InputSystem.onDeviceChange -= OnDeviceChange; @@ -131,7 +128,7 @@ internal static void Reset() Touch.s_GlobalState.editorState.Destroy(); Touch.s_GlobalState.editorState = default; #endif - s_Enabled = 0; + Touch.s_GlobalState.enhancedTouchEnabled = 0; } private static void SetUpState() @@ -141,7 +138,7 @@ private static void SetUpState() Touch.s_GlobalState.editorState.updateMask = InputUpdateType.Editor; #endif - s_UpdateMode = InputSystem.settings.updateMode; + Touch.s_GlobalState.enhancedTouchUpdateMode = InputSystem.settings.updateMode; foreach (var device in InputSystem.devices) OnDeviceChange(device, InputDeviceChange.Added); @@ -186,7 +183,7 @@ private static void OnDeviceChange(InputDevice device, InputDeviceChange change) private static void OnSettingsChange() { var currentUpdateMode = InputSystem.settings.updateMode; - if (s_UpdateMode == currentUpdateMode) + if (Touch.s_GlobalState.enhancedTouchUpdateMode == currentUpdateMode) return; TearDownState(); SetUpState(); diff --git a/Packages/com.unity.inputsystem/InputSystem/Plugins/EnhancedTouch/Touch.cs b/Packages/com.unity.inputsystem/InputSystem/Plugins/EnhancedTouch/Touch.cs index 32b02471ac..700f46a214 100644 --- a/Packages/com.unity.inputsystem/InputSystem/Plugins/EnhancedTouch/Touch.cs +++ b/Packages/com.unity.inputsystem/InputSystem/Plugins/EnhancedTouch/Touch.cs @@ -709,18 +709,24 @@ internal struct GlobalState internal CallbackArray> onFingerMove; internal CallbackArray> onFingerUp; + // Used by EnhancedTouchSupport but placed here to consolidate static fields + internal int enhancedTouchEnabled; + internal InputSettings.UpdateMode enhancedTouchUpdateMode; + internal FingerAndTouchState playerState; #if UNITY_EDITOR internal FingerAndTouchState editorState; #endif } - private static GlobalState CreateGlobalState() - { // Convenient method since parameterized construction is default - return new GlobalState { historyLengthPerFinger = 64 }; - } + internal static GlobalState s_GlobalState; - internal static GlobalState s_GlobalState = CreateGlobalState(); + [RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.BeforeSceneLoad)] + private static void InitializeGlobalTouchState() + { + // Touch GlobalState doesn't require Dispose operations + s_GlobalState = new GlobalState { historyLengthPerFinger = 64 }; + } internal static ISavedState SaveAndResetState() { @@ -731,7 +737,7 @@ internal static ISavedState SaveAndResetState() () => { /* currently nothing to dispose */ }); // Reset global state - s_GlobalState = CreateGlobalState(); + InitializeGlobalTouchState(); return savedState; } diff --git a/Packages/com.unity.inputsystem/InputSystem/Plugins/EnhancedTouch/TouchSimulation.cs b/Packages/com.unity.inputsystem/InputSystem/Plugins/EnhancedTouch/TouchSimulation.cs index 108e83e5a4..2d99d80fd7 100644 --- a/Packages/com.unity.inputsystem/InputSystem/Plugins/EnhancedTouch/TouchSimulation.cs +++ b/Packages/com.unity.inputsystem/InputSystem/Plugins/EnhancedTouch/TouchSimulation.cs @@ -317,6 +317,20 @@ private unsafe void UpdateTouch(int touchIndex, int pointerIndex, TouchPhase pha else { touch.touchId = m_TouchIds[touchIndex]; + /*touch.touchId = oldTouchState->touchId; // TODO: Follow-up develop: touch.touchId = m_TouchIds[touchIndex]; + touch.isPrimaryTouch = m_PrimaryTouchIndex == touchIndex; + touch.delta = position - oldTouchState->position; + touch.startPosition = oldTouchState->startPosition; + touch.startTime = oldTouchState->startTime; + touch.tapCount = oldTouchState->tapCount; + + if (phase == TouchPhase.Ended) + { + touch.isTap = time - oldTouchState->startTime <= Touchscreen.settings.tapTime && + (position - oldTouchState->startPosition).sqrMagnitude <= Touchscreen.settings.tapRadiusSquared; + if (touch.isTap) + ++touch.tapCount; + }*/ } //NOTE: Processing these events still happen in the current frame. diff --git a/Packages/com.unity.inputsystem/InputSystem/Plugins/HID/HIDSupport.cs b/Packages/com.unity.inputsystem/InputSystem/Plugins/HID/HIDSupport.cs index 29341e26d8..842d96bb42 100644 --- a/Packages/com.unity.inputsystem/InputSystem/Plugins/HID/HIDSupport.cs +++ b/Packages/com.unity.inputsystem/InputSystem/Plugins/HID/HIDSupport.cs @@ -94,7 +94,7 @@ public static ReadOnlyArray supportedHIDUsages s_SupportedHIDUsages = value.ToArray(); // Add HIDs we now support. - InputSystem.s_Manager.AddAvailableDevicesThatAreNowRecognized(); + InputSystem.manager.AddAvailableDevicesThatAreNowRecognized(); // Remove HIDs we no longer support. for (var i = 0; i < InputSystem.devices.Count; ++i) diff --git a/Packages/com.unity.inputsystem/InputSystem/Plugins/PlayerInput/PlayerInput.cs b/Packages/com.unity.inputsystem/InputSystem/Plugins/PlayerInput/PlayerInput.cs index ff25fe676a..6f588ede57 100644 --- a/Packages/com.unity.inputsystem/InputSystem/Plugins/PlayerInput/PlayerInput.cs +++ b/Packages/com.unity.inputsystem/InputSystem/Plugins/PlayerInput/PlayerInput.cs @@ -886,7 +886,7 @@ public ReadOnlyArray devices /// /// /// - public static ReadOnlyArray all => new ReadOnlyArray(s_AllActivePlayers, 0, s_AllActivePlayersCount); + public static ReadOnlyArray all => new ReadOnlyArray(s_GlobalState.allActivePlayers, 0, s_GlobalState.allActivePlayersCount); /// /// Whether PlayerInput operates in single-player mode. @@ -900,7 +900,7 @@ public ReadOnlyArray devices /// This is controlled by . /// public static bool isSinglePlayer => - s_AllActivePlayersCount <= 1 && + s_GlobalState.allActivePlayersCount <= 1 && (PlayerInputManager.instance == null || !PlayerInputManager.instance.joiningEnabled); /// @@ -1144,9 +1144,9 @@ public void SwitchCurrentActionMap(string mapNameOrId) /// public static PlayerInput GetPlayerByIndex(int playerIndex) { - for (var i = 0; i < s_AllActivePlayersCount; ++i) - if (s_AllActivePlayers[i].playerIndex == playerIndex) - return s_AllActivePlayers[i]; + for (var i = 0; i < s_GlobalState.allActivePlayersCount; ++i) + if (s_GlobalState.allActivePlayers[i].playerIndex == playerIndex) + return s_GlobalState.allActivePlayers[i]; return null; } @@ -1171,10 +1171,10 @@ public static PlayerInput FindFirstPairedToDevice(InputDevice device) if (device == null) throw new ArgumentNullException(nameof(device)); - for (var i = 0; i < s_AllActivePlayersCount; ++i) + for (var i = 0; i < s_GlobalState.allActivePlayersCount; ++i) { - if (ReadOnlyArrayExtensions.ContainsReference(s_AllActivePlayers[i].devices, device)) - return s_AllActivePlayers[i]; + if (ReadOnlyArrayExtensions.ContainsReference(s_GlobalState.allActivePlayers[i].devices, device)) + return s_GlobalState.allActivePlayers[i]; } return null; @@ -1208,11 +1208,11 @@ public static PlayerInput Instantiate(GameObject prefab, int playerIndex = -1, s throw new ArgumentNullException(nameof(prefab)); // Set initialization data. - s_InitPlayerIndex = playerIndex; - s_InitSplitScreenIndex = splitScreenIndex; - s_InitControlScheme = controlScheme; + s_GlobalState.initPlayerIndex = playerIndex; + s_GlobalState.initSplitScreenIndex = splitScreenIndex; + s_GlobalState.initControlScheme = controlScheme; if (pairWithDevice != null) - ArrayHelpers.AppendWithCapacity(ref s_InitPairWithDevices, ref s_InitPairWithDevicesCount, pairWithDevice); + ArrayHelpers.AppendWithCapacity(ref s_GlobalState.initPairWithDevices, ref s_GlobalState.initPairWithDevicesCount, pairWithDevice); return DoInstantiate(prefab); } @@ -1246,13 +1246,13 @@ public static PlayerInput Instantiate(GameObject prefab, int playerIndex = -1, s throw new ArgumentNullException(nameof(prefab)); // Set initialization data. - s_InitPlayerIndex = playerIndex; - s_InitSplitScreenIndex = splitScreenIndex; - s_InitControlScheme = controlScheme; + s_GlobalState.initPlayerIndex = playerIndex; + s_GlobalState.initSplitScreenIndex = splitScreenIndex; + s_GlobalState.initControlScheme = controlScheme; if (pairWithDevices != null) { for (var i = 0; i < pairWithDevices.Length; ++i) - ArrayHelpers.AppendWithCapacity(ref s_InitPairWithDevices, ref s_InitPairWithDevicesCount, pairWithDevices[i]); + ArrayHelpers.AppendWithCapacity(ref s_GlobalState.initPairWithDevices, ref s_GlobalState.initPairWithDevicesCount, pairWithDevices[i]); } return DoInstantiate(prefab); @@ -1260,7 +1260,7 @@ public static PlayerInput Instantiate(GameObject prefab, int playerIndex = -1, s private static PlayerInput DoInstantiate(GameObject prefab) { - var destroyIfDeviceSetupUnsuccessful = s_DestroyIfDeviceSetupUnsuccessful; + var destroyIfDeviceSetupUnsuccessful = s_GlobalState.destroyIfDeviceSetupUnsuccessful; GameObject instance; try @@ -1271,13 +1271,13 @@ private static PlayerInput DoInstantiate(GameObject prefab) finally { // Reset init data. - s_InitPairWithDevicesCount = 0; - if (s_InitPairWithDevices != null) - Array.Clear(s_InitPairWithDevices, 0, s_InitPairWithDevicesCount); - s_InitControlScheme = null; - s_InitPlayerIndex = -1; - s_InitSplitScreenIndex = -1; - s_DestroyIfDeviceSetupUnsuccessful = false; + s_GlobalState.initPairWithDevicesCount = 0; + if (s_GlobalState.initPairWithDevices != null) + Array.Clear(s_GlobalState.initPairWithDevices, 0, s_GlobalState.initPairWithDevicesCount); + s_GlobalState.initControlScheme = null; + s_GlobalState.initPlayerIndex = -1; + s_GlobalState.initSplitScreenIndex = -1; + s_GlobalState.destroyIfDeviceSetupUnsuccessful = false; } var playerInput = instance.GetComponentInChildren(); @@ -1343,18 +1343,41 @@ private static PlayerInput DoInstantiate(GameObject prefab) [NonSerialized] private Action m_DeviceChangeDelegate; [NonSerialized] private bool m_OnDeviceChangeHooked; - internal static int s_AllActivePlayersCount; - internal static PlayerInput[] s_AllActivePlayers; - private static Action s_UserChangeDelegate; + /// + /// Holds global (static) Player data + /// + internal struct GlobalState + { + public int allActivePlayersCount; + public PlayerInput[] allActivePlayers; + public Action userChangeDelegate; + + // The following information is used when the next PlayerInput component is enabled. + + public int initPairWithDevicesCount; + public InputDevice[] initPairWithDevices; + public int initPlayerIndex; + public int initSplitScreenIndex; + public string initControlScheme; + public bool destroyIfDeviceSetupUnsuccessful; + } + private static GlobalState s_GlobalState; - // The following information is used when the next PlayerInput component is enabled. + // For sanity purposes, GlobalState is private with properties accessing specific fields + internal static int allActivePlayersCount => s_GlobalState.allActivePlayersCount; + internal static PlayerInput[] allActivePlayers => s_GlobalState.allActivePlayers; + internal static bool destroyIfDeviceSetupUnsuccessful { get; set; } - private static int s_InitPairWithDevicesCount; - private static InputDevice[] s_InitPairWithDevices; - private static int s_InitPlayerIndex = -1; - private static int s_InitSplitScreenIndex = -1; - private static string s_InitControlScheme; - internal static bool s_DestroyIfDeviceSetupUnsuccessful; + [RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.BeforeSceneLoad)] + private static void InitializeGlobalPlayerState() + { + // Touch GlobalState doesn't require Dispose operations + s_GlobalState = new PlayerInput.GlobalState + { + initPlayerIndex = -1, + initSplitScreenIndex = -1 + }; + } private void InitializeActions() { @@ -1365,8 +1388,8 @@ private void InitializeActions() // Check if we need to duplicate our actions by looking at all other players. If any // has the same actions, duplicate. - for (var i = 0; i < s_AllActivePlayersCount; ++i) - if (s_AllActivePlayers[i].m_Actions == m_Actions && s_AllActivePlayers[i] != this) + for (var i = 0; i < s_GlobalState.allActivePlayersCount; ++i) + if (s_GlobalState.allActivePlayers[i].m_Actions == m_Actions && s_GlobalState.allActivePlayers[i] != this) { CopyActionAssetAndApplyBindingOverrides(); break; @@ -1564,10 +1587,10 @@ private void AssignUserAndDevices() { // If we have devices we are meant to pair with, do so. Otherwise, don't // do anything as we don't know what kind of input to look for. - if (s_InitPairWithDevicesCount > 0) + if (s_GlobalState.initPairWithDevicesCount > 0) { - for (var i = 0; i < s_InitPairWithDevicesCount; ++i) - m_InputUser = InputUser.PerformPairingWithDevice(s_InitPairWithDevices[i], m_InputUser); + for (var i = 0; i < s_GlobalState.initPairWithDevicesCount; ++i) + m_InputUser = InputUser.PerformPairingWithDevice(s_GlobalState.initPairWithDevices[i], m_InputUser); } else { @@ -1581,15 +1604,15 @@ private void AssignUserAndDevices() // If we have control schemes, try to find the one we should use. if (m_Actions.controlSchemes.Count > 0) { - if (!string.IsNullOrEmpty(s_InitControlScheme)) + if (!string.IsNullOrEmpty(s_GlobalState.initControlScheme)) { // We've been given a control scheme to initialize this. Try that one and // that one only. Might mean we end up with missing devices. - var controlScheme = m_Actions.FindControlScheme(s_InitControlScheme); + var controlScheme = m_Actions.FindControlScheme(s_GlobalState.initControlScheme); if (controlScheme == null) { - Debug.LogError($"No control scheme '{s_InitControlScheme}' in '{m_Actions}'", this); + Debug.LogError($"No control scheme '{s_GlobalState.initControlScheme}' in '{m_Actions}'", this); } else { @@ -1613,13 +1636,13 @@ private void AssignUserAndDevices() // If we did not end up with a usable scheme by now but we've been given devices to pair with, // search for a control scheme matching the given devices. - if (s_InitPairWithDevicesCount > 0 && (!m_InputUser.valid || m_InputUser.controlScheme == null)) + if (s_GlobalState.initPairWithDevicesCount > 0 && (!m_InputUser.valid || m_InputUser.controlScheme == null)) { // The devices we've been given may not be all the devices required to satisfy a given control scheme so we // want to pick any one control scheme that is the best match for the devices we have regardless of whether // we'll need additional devices. TryToActivateControlScheme will take care of that. var controlScheme = InputControlScheme.FindControlSchemeForDevices( - new ReadOnlyArray(s_InitPairWithDevices, 0, s_InitPairWithDevicesCount), m_Actions.controlSchemes, + new ReadOnlyArray(s_GlobalState.initPairWithDevices, 0, s_GlobalState.initPairWithDevicesCount), m_Actions.controlSchemes, allowUnsuccesfulMatch: true); if (controlScheme != null) TryToActivateControlScheme(controlScheme.Value); @@ -1627,7 +1650,7 @@ private void AssignUserAndDevices() // If we don't have a working control scheme by now and we haven't been instructed to use // one specific control scheme, try each one in the asset one after the other until we // either find one we can use or run out of options. - else if ((!m_InputUser.valid || m_InputUser.controlScheme == null) && string.IsNullOrEmpty(s_InitControlScheme)) + else if ((!m_InputUser.valid || m_InputUser.controlScheme == null) && string.IsNullOrEmpty(s_GlobalState.initControlScheme)) { using (var availableDevices = InputUser.GetUnpairedInputDevices()) { @@ -1653,10 +1676,10 @@ private void AssignUserAndDevices() // device is present that matches the binding and that isn't used by any other player, we'll // pair to the player. - if (s_InitPairWithDevicesCount > 0) + if (s_GlobalState.initPairWithDevicesCount > 0) { - for (var i = 0; i < s_InitPairWithDevicesCount; ++i) - m_InputUser = InputUser.PerformPairingWithDevice(s_InitPairWithDevices[i], m_InputUser); + for (var i = 0; i < s_GlobalState.initPairWithDevicesCount; ++i) + m_InputUser = InputUser.PerformPairingWithDevice(s_GlobalState.initPairWithDevices[i], m_InputUser); } else { @@ -1709,7 +1732,7 @@ private bool TryToActivateControlScheme(InputControlScheme controlScheme) ////FIXME: this will fall apart if account management is involved and a user needs to log in on device first // Pair any devices we may have been given. - if (s_InitPairWithDevicesCount > 0) + if (s_GlobalState.initPairWithDevicesCount > 0) { ////REVIEW: should AndPairRemainingDevices() require that there is at least one existing //// device paired to the user that is usable with the given control scheme? @@ -1719,17 +1742,17 @@ private bool TryToActivateControlScheme(InputControlScheme controlScheme) // we have the player grab all the devices in s_InitPairWithDevices along with a control // scheme that fits none of them and then AndPairRemainingDevices() supplying the devices // actually needed by the control scheme. - for (var i = 0; i < s_InitPairWithDevicesCount; ++i) + for (var i = 0; i < s_GlobalState.initPairWithDevicesCount; ++i) { - var device = s_InitPairWithDevices[i]; + var device = s_GlobalState.initPairWithDevices[i]; if (!controlScheme.SupportsDevice(device)) return false; } // We're good. Give the devices to the user. - for (var i = 0; i < s_InitPairWithDevicesCount; ++i) + for (var i = 0; i < s_GlobalState.initPairWithDevicesCount; ++i) { - var device = s_InitPairWithDevices[i]; + var device = s_GlobalState.initPairWithDevices[i]; m_InputUser = InputUser.PerformPairingWithDevice(device, m_InputUser); } } @@ -1750,16 +1773,16 @@ private bool TryToActivateControlScheme(InputControlScheme controlScheme) private void AssignPlayerIndex() { - if (s_InitPlayerIndex != -1) - m_PlayerIndex = s_InitPlayerIndex; + if (s_GlobalState.initPlayerIndex != -1) + m_PlayerIndex = s_GlobalState.initPlayerIndex; else { var minPlayerIndex = int.MaxValue; var maxPlayerIndex = int.MinValue; - for (var i = 0; i < s_AllActivePlayersCount; ++i) + for (var i = 0; i < s_GlobalState.allActivePlayersCount; ++i) { - var playerIndex = s_AllActivePlayers[i].playerIndex; + var playerIndex = s_GlobalState.allActivePlayers[i].playerIndex; minPlayerIndex = Math.Min(minPlayerIndex, playerIndex); maxPlayerIndex = Math.Max(maxPlayerIndex, playerIndex); } @@ -1789,7 +1812,7 @@ private void AssignPlayerIndex() } } - #if UNITY_EDITOR +#if UNITY_EDITOR void Reset() { // Set default actions to project wide actions. @@ -1797,7 +1820,7 @@ void Reset() // TODO Need to monitor changes? } - #endif +#endif private void OnEnable() { @@ -1812,23 +1835,23 @@ private void OnEnable() } // Split-screen index defaults to player index. - if (s_InitSplitScreenIndex >= 0) - m_SplitScreenIndex = s_InitSplitScreenIndex; + if (s_GlobalState.initSplitScreenIndex >= 0) + m_SplitScreenIndex = s_GlobalState.initSplitScreenIndex; else m_SplitScreenIndex = playerIndex; // Add to global list and sort it by player index. - ArrayHelpers.AppendWithCapacity(ref s_AllActivePlayers, ref s_AllActivePlayersCount, this); - for (var i = 1; i < s_AllActivePlayersCount; ++i) - for (var j = i; j > 0 && s_AllActivePlayers[j - 1].playerIndex > s_AllActivePlayers[j].playerIndex; --j) - s_AllActivePlayers.SwapElements(j, j - 1); + ArrayHelpers.AppendWithCapacity(ref s_GlobalState.allActivePlayers, ref s_GlobalState.allActivePlayersCount, this); + for (var i = 1; i < s_GlobalState.allActivePlayersCount; ++i) + for (var j = i; j > 0 && s_GlobalState.allActivePlayers[j - 1].playerIndex > s_GlobalState.allActivePlayers[j].playerIndex; --j) + s_GlobalState.allActivePlayers.SwapElements(j, j - 1); // If it's the first player, hook into user change notifications. - if (s_AllActivePlayersCount == 1) + if (s_GlobalState.allActivePlayersCount == 1) { - if (s_UserChangeDelegate == null) - s_UserChangeDelegate = OnUserChange; - InputUser.onChange += s_UserChangeDelegate; + if (s_GlobalState.userChangeDelegate == null) + s_GlobalState.userChangeDelegate = OnUserChange; + InputUser.onChange += s_GlobalState.userChangeDelegate; } // In single player, set up for automatic device switching. @@ -1901,13 +1924,13 @@ private void OnDisable() m_Enabled = false; // Remove from global list. - var index = ArrayHelpers.IndexOfReference(s_AllActivePlayers, this, s_AllActivePlayersCount); + var index = ArrayHelpers.IndexOfReference(s_GlobalState.allActivePlayers, this, s_GlobalState.allActivePlayersCount); if (index != -1) - ArrayHelpers.EraseAtWithCapacity(s_AllActivePlayers, ref s_AllActivePlayersCount, index); + ArrayHelpers.EraseAtWithCapacity(s_GlobalState.allActivePlayers, ref s_GlobalState.allActivePlayersCount, index); // Unhook from change notifications if we're the last player. - if (s_AllActivePlayersCount == 0 && s_UserChangeDelegate != null) - InputUser.onChange -= s_UserChangeDelegate; + if (s_GlobalState.allActivePlayersCount == 0 && s_GlobalState.userChangeDelegate != null) + InputUser.onChange -= s_GlobalState.userChangeDelegate; StopListeningForUnpairedDeviceActivity(); StopListeningForDeviceChanges(); @@ -2033,9 +2056,9 @@ private static void OnUserChange(InputUser user, InputUserChange change, InputDe { case InputUserChange.DeviceLost: case InputUserChange.DeviceRegained: - for (var i = 0; i < s_AllActivePlayersCount; ++i) + for (var i = 0; i < s_GlobalState.allActivePlayersCount; ++i) { - var player = s_AllActivePlayers[i]; + var player = s_GlobalState.allActivePlayers[i]; if (player.m_InputUser == user) { if (change == InputUserChange.DeviceLost) @@ -2047,9 +2070,9 @@ private static void OnUserChange(InputUser user, InputUserChange change, InputDe break; case InputUserChange.ControlsChanged: - for (var i = 0; i < s_AllActivePlayersCount; ++i) + for (var i = 0; i < s_GlobalState.allActivePlayersCount; ++i) { - var player = s_AllActivePlayers[i]; + var player = s_GlobalState.allActivePlayers[i]; if (player.m_InputUser == user) player.HandleControlsChanged(); } diff --git a/Packages/com.unity.inputsystem/InputSystem/Plugins/PlayerInput/PlayerInputManager.cs b/Packages/com.unity.inputsystem/InputSystem/Plugins/PlayerInput/PlayerInputManager.cs index fea0837a79..476f984213 100644 --- a/Packages/com.unity.inputsystem/InputSystem/Plugins/PlayerInput/PlayerInputManager.cs +++ b/Packages/com.unity.inputsystem/InputSystem/Plugins/PlayerInput/PlayerInputManager.cs @@ -135,7 +135,7 @@ public bool splitScreen /// /// This count corresponds to all instances that are currently enabled. /// - public int playerCount => PlayerInput.s_AllActivePlayersCount; + public int playerCount => PlayerInput.allActivePlayersCount; ////FIXME: this needs to be settable /// @@ -444,7 +444,7 @@ public PlayerInput JoinPlayer(int playerIndex = -1, int splitScreenIndex = -1, s if (!CheckIfPlayerCanJoin(playerIndex)) return null; - PlayerInput.s_DestroyIfDeviceSetupUnsuccessful = true; + PlayerInput.destroyIfDeviceSetupUnsuccessful = true; return PlayerInput.Instantiate(m_PlayerPrefab, playerIndex: playerIndex, splitScreenIndex: splitScreenIndex, controlScheme: controlScheme, pairWithDevice: pairWithDevice); } @@ -470,7 +470,7 @@ public PlayerInput JoinPlayer(int playerIndex = -1, int splitScreenIndex = -1, s if (!CheckIfPlayerCanJoin(playerIndex)) return null; - PlayerInput.s_DestroyIfDeviceSetupUnsuccessful = true; + PlayerInput.destroyIfDeviceSetupUnsuccessful = true; return PlayerInput.Instantiate(m_PlayerPrefab, playerIndex: playerIndex, splitScreenIndex: splitScreenIndex, controlScheme: controlScheme, pairWithDevices: pairWithDevices); } @@ -520,12 +520,12 @@ private bool CheckIfPlayerCanJoin(int playerIndex = -1) // If we have a player index, make sure it's unique. if (playerIndex != -1) { - for (var i = 0; i < PlayerInput.s_AllActivePlayersCount; ++i) - if (PlayerInput.s_AllActivePlayers[i].playerIndex == playerIndex) + for (var i = 0; i < PlayerInput.allActivePlayersCount; ++i) + if (PlayerInput.allActivePlayers[i].playerIndex == playerIndex) { Debug.LogError( - $"Player index #{playerIndex} is already taken by player {PlayerInput.s_AllActivePlayers[i]}", - PlayerInput.s_AllActivePlayers[i]); + $"Player index #{playerIndex} is already taken by player {PlayerInput.allActivePlayers[i]}", + PlayerInput.allActivePlayers[i]); return false; } } @@ -577,8 +577,8 @@ private void OnEnable() } // Join all players already in the game. - for (var i = 0; i < PlayerInput.s_AllActivePlayersCount; ++i) - NotifyPlayerJoined(PlayerInput.s_AllActivePlayers[i]); + for (var i = 0; i < PlayerInput.allActivePlayersCount; ++i) + NotifyPlayerJoined(PlayerInput.allActivePlayers[i]); if (m_AllowJoining) EnableJoining(); diff --git a/Packages/com.unity.inputsystem/InputSystem/Plugins/Steam/SteamSupport.cs b/Packages/com.unity.inputsystem/InputSystem/Plugins/Steam/SteamSupport.cs index 357b65676e..c84dee3b92 100644 --- a/Packages/com.unity.inputsystem/InputSystem/Plugins/Steam/SteamSupport.cs +++ b/Packages/com.unity.inputsystem/InputSystem/Plugins/Steam/SteamSupport.cs @@ -27,7 +27,7 @@ public static ISteamControllerAPI api set { s_API = value; - InstallHooks(s_API != null); + InstallControllerUpdateHooks(s_API != null); } } @@ -38,11 +38,19 @@ internal static ISteamControllerAPI GetAPIAndRequireItToBeSet() return s_API; } - internal static SteamHandle[] s_ConnectedControllers; - internal static SteamController[] s_InputDevices; - internal static int s_InputDeviceCount; - internal static bool s_HooksInstalled; - internal static ISteamControllerAPI s_API; + /// + /// Returns if the controller Update event handlers have been set or not. + /// + /// + /// The s_ConnectedControllers array is allocated in response to setting event handlers and + /// so it can double as our "is installed" flag. + /// + private static bool updateHooksInstalled => s_ConnectedControllers != null; + + private static SteamHandle[] s_ConnectedControllers; + private static SteamController[] s_InputDevices; + private static int s_InputDeviceCount; + private static ISteamControllerAPI s_API; private const int STEAM_CONTROLLER_MAX_COUNT = 16; @@ -54,22 +62,35 @@ public static void Initialize() // We use this as a base layout. InputSystem.RegisterLayout(); - if (api != null) - InstallHooks(true); + InstallControllerUpdateHooks(s_API != null); } - private static void InstallHooks(bool state) + /// + /// Disable Steam controller API support and reset the state. + /// + internal static void Shutdown() { - Debug.Assert(api != null); - if (state && !s_HooksInstalled) + InstallControllerUpdateHooks(false); + + s_API = null; + s_InputDevices = null; + s_InputDeviceCount = 0; + } + + private static void InstallControllerUpdateHooks(bool state) + { + Debug.Assert(api != null || !state); + if (state && !updateHooksInstalled) { + s_ConnectedControllers = new SteamHandle[STEAM_CONTROLLER_MAX_COUNT]; InputSystem.onBeforeUpdate += OnUpdate; InputSystem.onActionChange += OnActionChange; } - else if (!state && s_HooksInstalled) + else if (!state && updateHooksInstalled) { InputSystem.onBeforeUpdate -= OnUpdate; InputSystem.onActionChange -= OnActionChange; + s_ConnectedControllers = null; } } @@ -125,8 +146,6 @@ private static void OnUpdate() api.RunFrame(); // Check if we have any new controllers have appeared. - if (s_ConnectedControllers == null) - s_ConnectedControllers = new SteamHandle[STEAM_CONTROLLER_MAX_COUNT]; var numConnectedControllers = api.GetConnectedControllers(s_ConnectedControllers); for (var i = 0; i < numConnectedControllers; ++i) { diff --git a/Packages/com.unity.inputsystem/InputSystem/Plugins/Switch/SwitchProControllerHID.cs b/Packages/com.unity.inputsystem/InputSystem/Plugins/Switch/SwitchProControllerHID.cs index 6f5024c875..a37007b504 100644 --- a/Packages/com.unity.inputsystem/InputSystem/Plugins/Switch/SwitchProControllerHID.cs +++ b/Packages/com.unity.inputsystem/InputSystem/Plugins/Switch/SwitchProControllerHID.cs @@ -253,7 +253,7 @@ public unsafe void OnStateEvent(InputEventPtr eventPtr) || newState->buttons2 != currentState->buttons2; if (!actuated) - InputSystem.s_Manager.DontMakeCurrentlyUpdatingDeviceCurrent(); + InputSystem.manager.DontMakeCurrentlyUpdatingDeviceCurrent(); } InputState.Change(this, eventPtr); diff --git a/Packages/com.unity.inputsystem/InputSystem/Plugins/UI/InputSystemUIInputModule.cs b/Packages/com.unity.inputsystem/InputSystem/Plugins/UI/InputSystemUIInputModule.cs index 07275570be..adba24a25e 100644 --- a/Packages/com.unity.inputsystem/InputSystem/Plugins/UI/InputSystemUIInputModule.cs +++ b/Packages/com.unity.inputsystem/InputSystem/Plugins/UI/InputSystemUIInputModule.cs @@ -1540,7 +1540,8 @@ static void ResetDefaultActions() /// public void AssignDefaultActions() { - if (defaultActions == null) + // Without Domain Reloads, the InputActionAsset could be "null" even if defaultActions is valid + if (defaultActions == null || defaultActions.asset == null) { defaultActions = new DefaultInputActions(); } diff --git a/Packages/com.unity.inputsystem/InputSystem/Plugins/UI/InputSystemUIInputModuleEditor.cs b/Packages/com.unity.inputsystem/InputSystem/Plugins/UI/InputSystemUIInputModuleEditor.cs index a160f2c9c0..e3dc4688d1 100644 --- a/Packages/com.unity.inputsystem/InputSystem/Plugins/UI/InputSystemUIInputModuleEditor.cs +++ b/Packages/com.unity.inputsystem/InputSystem/Plugins/UI/InputSystemUIInputModuleEditor.cs @@ -13,6 +13,7 @@ namespace UnityEngine.InputSystem.UI.Editor [InitializeOnLoad] internal class InputSystemUIInputModuleEditor : UnityEditor.Editor { + // ISX-1966 - It's unclear if this initializer will work correctly with CoreCLR and needs to be investigated. static InputSystemUIInputModuleEditor() { #if UNITY_6000_0_OR_NEWER && ENABLE_INPUT_SYSTEM diff --git a/Packages/com.unity.inputsystem/InputSystem/Plugins/Users/InputUser.cs b/Packages/com.unity.inputsystem/InputSystem/Plugins/Users/InputUser.cs index d2e2ccfb3a..80f0c1c246 100644 --- a/Packages/com.unity.inputsystem/InputSystem/Plugins/Users/InputUser.cs +++ b/Packages/com.unity.inputsystem/InputSystem/Plugins/Users/InputUser.cs @@ -1878,6 +1878,13 @@ private struct GlobalState private static GlobalState s_GlobalState; + [RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.BeforeSceneLoad)] + private static void InitializeGlobalUserState() + { + ResetGlobals(); + s_GlobalState = default; + } + internal static ISavedState SaveAndResetState() { // Save current state and provide an opaque interface to restore it diff --git a/Packages/com.unity.inputsystem/InputSystem/Plugins/iOS/iOSSupport.cs b/Packages/com.unity.inputsystem/InputSystem/Plugins/iOS/iOSSupport.cs index 981bac4fad..d2975cf469 100644 --- a/Packages/com.unity.inputsystem/InputSystem/Plugins/iOS/iOSSupport.cs +++ b/Packages/com.unity.inputsystem/InputSystem/Plugins/iOS/iOSSupport.cs @@ -53,7 +53,7 @@ public static void Initialize() InputSystem.RegisterLayout(); // Don't add devices for InputTestRuntime // TODO: Maybe there should be a better place for adding device from C# - if (InputSystem.s_Manager.m_Runtime is NativeInputRuntime) + if (InputSystem.manager.runtime is NativeInputRuntime) { if (iOSStepCounter.IsAvailable()) InputSystem.AddDevice(); diff --git a/Packages/com.unity.inputsystem/InputSystem/State/InputState.cs b/Packages/com.unity.inputsystem/InputSystem/State/InputState.cs index 787b5df377..69bae00f6b 100644 --- a/Packages/com.unity.inputsystem/InputSystem/State/InputState.cs +++ b/Packages/com.unity.inputsystem/InputSystem/State/InputState.cs @@ -44,8 +44,8 @@ public static class InputState /// public static event Action onChange { - add => InputSystem.s_Manager.onDeviceStateChange += value; - remove => InputSystem.s_Manager.onDeviceStateChange -= value; + add => InputSystem.manager.onDeviceStateChange += value; + remove => InputSystem.manager.onDeviceStateChange -= value; } public static unsafe void Change(InputDevice device, InputEventPtr eventPtr, InputUpdateType updateType = default) @@ -65,7 +65,7 @@ public static unsafe void Change(InputDevice device, InputEventPtr eventPtr, Inp else { #if UNITY_EDITOR - InputSystem.s_Manager.m_Diagnostics?.OnEventFormatMismatch(eventPtr, device); + InputSystem.manager.m_Diagnostics?.OnEventFormatMismatch(eventPtr, device); #endif return; } @@ -75,8 +75,8 @@ public static unsafe void Change(InputDevice device, InputEventPtr eventPtr, Inp $"State format {stateFormat} from event does not match state format {device.stateBlock.format} of device {device}", nameof(eventPtr)); - InputSystem.s_Manager.UpdateState(device, eventPtr, - updateType != default ? updateType : InputSystem.s_Manager.defaultUpdateType); + InputSystem.manager.UpdateState(device, eventPtr, + updateType != default ? updateType : InputSystem.manager.defaultUpdateType); } /// @@ -122,8 +122,8 @@ public static unsafe void Change(InputControl control, ref TState state, var statePtr = UnsafeUtility.AddressOf(ref state); var stateOffset = control.stateBlock.byteOffset - device.stateBlock.byteOffset; - InputSystem.s_Manager.UpdateState(device, - updateType != default ? updateType : InputSystem.s_Manager.defaultUpdateType, statePtr, stateOffset, + InputSystem.manager.UpdateState(device, + updateType != default ? updateType : InputSystem.manager.defaultUpdateType, statePtr, stateOffset, (uint)stateSize, eventPtr.valid ? eventPtr.internalTime @@ -207,7 +207,7 @@ public static void AddChangeMonitor(InputControl control, IInputStateChangeMonit if (!control.device.added) throw new ArgumentException($"Device for control '{control}' has not been added to system"); - InputSystem.s_Manager.AddStateChangeMonitor(control, monitor, monitorIndex, groupIndex); + InputSystem.manager.AddStateChangeMonitor(control, monitor, monitorIndex, groupIndex); } public static IInputStateChangeMonitor AddChangeMonitor(InputControl control, @@ -232,7 +232,7 @@ public static void RemoveChangeMonitor(InputControl control, IInputStateChangeMo if (monitor == null) throw new ArgumentNullException(nameof(monitor)); - InputSystem.s_Manager.RemoveStateChangeMonitor(control, monitor, monitorIndex); + InputSystem.manager.RemoveStateChangeMonitor(control, monitor, monitorIndex); } /// @@ -253,7 +253,7 @@ public static void AddChangeMonitorTimeout(InputControl control, IInputStateChan if (monitor == null) throw new ArgumentNullException(nameof(monitor)); - InputSystem.s_Manager.AddStateChangeMonitorTimeout(control, monitor, time, monitorIndex, timerIndex); + InputSystem.manager.AddStateChangeMonitorTimeout(control, monitor, time, monitorIndex, timerIndex); } public static void RemoveChangeMonitorTimeout(IInputStateChangeMonitor monitor, long monitorIndex = -1, int timerIndex = -1) @@ -261,7 +261,7 @@ public static void RemoveChangeMonitorTimeout(IInputStateChangeMonitor monitor, if (monitor == null) throw new ArgumentNullException(nameof(monitor)); - InputSystem.s_Manager.RemoveStateChangeMonitorTimeout(monitor, monitorIndex, timerIndex); + InputSystem.manager.RemoveStateChangeMonitorTimeout(monitor, monitorIndex, timerIndex); } private class StateChangeMonitorDelegate : IInputStateChangeMonitor diff --git a/Packages/com.unity.inputsystem/InputSystem/State/InputStateHistory.cs b/Packages/com.unity.inputsystem/InputSystem/State/InputStateHistory.cs index b70dc2ae6c..5b09c32737 100644 --- a/Packages/com.unity.inputsystem/InputSystem/State/InputStateHistory.cs +++ b/Packages/com.unity.inputsystem/InputSystem/State/InputStateHistory.cs @@ -128,7 +128,7 @@ public int extraMemoryPerRecord /// public InputUpdateType updateMask { - get => m_UpdateMask ?? InputSystem.s_Manager.updateMask & ~InputUpdateType.Editor; + get => m_UpdateMask ?? InputSystem.manager.updateMask & ~InputUpdateType.Editor; set { if (value == InputUpdateType.None) diff --git a/Packages/com.unity.inputsystem/InputSystem/Utilities/DirtyAssetTracker.cs b/Packages/com.unity.inputsystem/InputSystem/Utilities/DirtyAssetTracker.cs new file mode 100644 index 0000000000..5586d4741e --- /dev/null +++ b/Packages/com.unity.inputsystem/InputSystem/Utilities/DirtyAssetTracker.cs @@ -0,0 +1,44 @@ +#if UNITY_EDITOR +using System.Collections.Generic; +using UnityEditor; + +namespace UnityEngine.InputSystem.Utilities +{ + internal static class DirtyAssetTracker + { + /// + /// Keep track of InputActionAsset assets that you want to re-load. This is useful because some user actions, + /// such as adding a new input binding at runtime, change the in-memory representation of the input action asset and + /// those changes survive when exiting Play mode. If you re-open an Input Action Asset in the Editor that has been changed + /// this way, you see the new bindings that have been added during Play mode which you might not typically want to happen. + /// + /// You can avoid this by force re-loading from disk any asset that has been marked as dirty. + /// + /// + public static void TrackDirtyInputActionAsset(InputActionAsset asset) + { + if (AssetDatabase.TryGetGUIDAndLocalFileIdentifier(asset, out string assetGuid, out long _) == false) + return; + + s_TrackedDirtyAssets.Add(assetGuid); + } + + public static void ReloadDirtyAssets() + { + foreach (var assetGuid in s_TrackedDirtyAssets) + { + var assetPath = AssetDatabase.GUIDToAssetPath(assetGuid); + + if (string.IsNullOrEmpty(assetPath)) + continue; + + AssetDatabase.ImportAsset(assetPath, ImportAssetOptions.ForceUpdate); + } + + s_TrackedDirtyAssets.Clear(); + } + + private static HashSet s_TrackedDirtyAssets = new HashSet(); + } +} +#endif diff --git a/Packages/com.unity.inputsystem/InputSystem/Utilities/DirtyAssetTracker.cs.meta b/Packages/com.unity.inputsystem/InputSystem/Utilities/DirtyAssetTracker.cs.meta new file mode 100644 index 0000000000..a1ef7afaae --- /dev/null +++ b/Packages/com.unity.inputsystem/InputSystem/Utilities/DirtyAssetTracker.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: fa1cedc2b36c5fc49a203b0e02a22d64 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.unity.inputsystem/Tests/TestFixture/InputTestFixture.cs b/Packages/com.unity.inputsystem/Tests/TestFixture/InputTestFixture.cs index 5364844376..d551102500 100644 --- a/Packages/com.unity.inputsystem/Tests/TestFixture/InputTestFixture.cs +++ b/Packages/com.unity.inputsystem/Tests/TestFixture/InputTestFixture.cs @@ -15,6 +15,7 @@ #if UNITY_6000_5_OR_NEWER using UnityEngine.Assemblies; #endif +using UnityEngine.InputSystem.Users; #if UNITY_EDITOR using UnityEditor; @@ -91,6 +92,8 @@ public virtual void Setup() { try { + m_StateManager = new InputTestStateManager(); + // Apparently, NUnit is reusing instances :( m_KeyInfos = default; m_IsUnityTest = default; @@ -106,7 +109,7 @@ public virtual void Setup() // Push current input system state on stack. #if DEVELOPMENT_BUILD || UNITY_EDITOR - InputSystem.SaveAndReset(enableRemoting: false, runtime: runtime); + m_StateManager.SaveAndReset(false, runtime); #endif // Override the editor messing with logic like canRunInBackground and focus and // make it behave like in the player. @@ -118,7 +121,7 @@ public virtual void Setup() // so turn them off. #if UNITY_EDITOR if (Application.isPlaying && IsUnityTest()) - InputSystem.s_Manager.m_UpdateMask &= ~InputUpdateType.Editor; + InputSystem.manager.m_UpdateMask &= ~InputUpdateType.Editor; #endif // We use native collections in a couple places. We when leak them, we want to know where exactly @@ -134,7 +137,7 @@ public virtual void Setup() NativeInputRuntime.instance.onUpdate = (InputUpdateType updateType, ref InputEventBuffer buffer) => { - if (InputSystem.s_Manager.ShouldRunUpdate(updateType)) + if (InputSystem.manager.ShouldRunUpdate(updateType)) InputSystem.Update(updateType); // We ignore any input coming from native. buffer.Reset(); @@ -189,7 +192,7 @@ public virtual void TearDown() try { #if DEVELOPMENT_BUILD || UNITY_EDITOR - InputSystem.Restore(); + m_StateManager.Restore(); #endif runtime.Dispose(); @@ -315,6 +318,7 @@ public static void AssertStickValues(StickControl stick, Vector2 stickValue, flo Assert.That(stick.right.ReadUnprocessedValue(), Is.EqualTo(right).Within(0.0001), "Incorrect 'right' value"); } + internal InputTestStateManager m_StateManager; private Dictionary> m_KeyInfos; private bool m_Initialized; @@ -998,20 +1002,6 @@ public ActionConstraint AndThen(ActionConstraint constraint) } } - #if UNITY_EDITOR - internal void SimulateDomainReload() - { - // This quite invasively goes into InputSystem internals. Unfortunately, we - // have no proper way of simulating domain reloads ATM. So we directly call various - // internal methods here in a sequence similar to what we'd get during a domain reload. - - InputSystem.s_SystemObject.OnBeforeSerialize(); - InputSystem.s_SystemObject = null; - InputSystem.InitializeInEditor(runtime); - } - - #endif - private static void CheckValidity(InputDevice device, InputControl control) { if (!device.added) @@ -1023,7 +1013,7 @@ private static void CheckValidity(InputDevice device, InputControl control) // Guards against a device from another scope being used. This is a direct way to evaluate whether // the device is associated with the current manager state or not since device state isn't consistently // pushed/popped in the current design. - var manager = InputSystem.s_Manager; + var manager = InputSystem.manager; if (manager == null || !manager.HasDevice(device)) { throw new ArgumentException($"Control '{control}' does not have any associated state. " + diff --git a/Packages/com.unity.inputsystem/Tests/TestFixture/InputTestStateManager.cs b/Packages/com.unity.inputsystem/Tests/TestFixture/InputTestStateManager.cs new file mode 100644 index 0000000000..6c357c5038 --- /dev/null +++ b/Packages/com.unity.inputsystem/Tests/TestFixture/InputTestStateManager.cs @@ -0,0 +1,156 @@ +using System; +using System.Collections.Generic; +using Unity.Collections; +using Unity.Profiling; +using UnityEngine.InputSystem; +using UnityEngine.InputSystem.LowLevel; +using UnityEngine.InputSystem.Users; +using UnityEngine.Profiling; +using UnityEngine.InputSystem.EnhancedTouch; + +#if UNITY_EDITOR +using UnityEditor; +using UnityEngine.InputSystem.Editor; +#endif + +namespace UnityEngine.InputSystem +{ + /// + /// Provides functions for saving and restore the InputSystem state across tests and domain reloads. + /// + internal class InputTestStateManager + { + static readonly ProfilerMarker k_InputResetMarker = new ProfilerMarker("InputSystem.Reset"); + + public InputSystemState GetSavedState() + { + return m_SavedStateStack.Peek(); + } + + /// + /// Push the current state of the input system onto a stack and + /// reset the system to its default state. + /// + /// + /// The save stack is not able to survive domain reloads. It is intended solely + /// for use in tests. + /// + public void SaveAndReset(bool enableRemoting, IInputRuntime runtime) + { + ////FIXME: does not preserve global state in InputActionState + ////TODO: preserve InputUser state + ////TODO: preserve EnhancedTouchSupport state + + m_SavedStateStack.Push(new InputSystemState + { + manager = InputSystem.manager, + remote = InputSystem.remoting, + remoteConnection = InputSystem.remoteConnection, + managerState = InputSystem.manager.SaveState(), + remotingState = InputSystem.remoting?.SaveState() ?? new InputRemoting.SerializedState(), +#if UNITY_EDITOR + userSettings = InputEditorUserSettings.s_Settings, + systemObject = JsonUtility.ToJson(InputSystem.domainStateManager), +#endif + inputActionState = InputActionState.SaveAndResetState(), + touchState = EnhancedTouch.Touch.SaveAndResetState(), + inputUserState = InputUser.SaveAndResetState() + }); + + Reset(enableRemoting, runtime ?? InputRuntime.s_Instance); // Keep current runtime. + } + + /// + /// Return the input system to its default state. + /// + public void Reset(bool enableRemoting, IInputRuntime runtime) + { + k_InputResetMarker.Begin(); + + InputSystem.TestHook_DisableActions(); + + // Some devices keep globals. Get rid of them by pretending the devices + // are removed. + if (InputSystem.manager != null) + { + foreach (var device in InputSystem.manager.devices) + device.NotifyRemoved(); + + InputSystem.manager.UninstallGlobals(); + } + +#if UNITY_EDITOR + // Perform special initialization for running Editor tests + InputSystem.TestHook_InitializeForPlayModeTests(enableRemoting, runtime); +#else + // For Player tests we can use the normal initialization + InputSystem.InitializeInPlayer(runtime, false); +#endif // UNITY_EDITOR + + Mouse.s_PlatformMouseDevice = null; + + InputEventListener.s_ObserverState = default; + InputUser.ResetGlobals(); + EnhancedTouchSupport.Reset(); + + InputSystem.TestHook_EnableActions(); + + k_InputResetMarker.End(); + } + + ////FIXME: this method doesn't restore things like InputDeviceDebuggerWindow.onToolbarGUI + /// + /// Restore the state of the system from the last state pushed with . + /// + public void Restore() + { + Debug.Assert(m_SavedStateStack.Count > 0); + + // Load back previous state. + var state = m_SavedStateStack.Pop(); + + state.inputUserState.StaticDisposeCurrentState(); + state.touchState.StaticDisposeCurrentState(); + state.inputActionState.StaticDisposeCurrentState(); + + InputSystem.TestHook_DestroyAndReset(); + + state.inputUserState.RestoreSavedState(); + state.touchState.RestoreSavedState(); + state.inputActionState.RestoreSavedState(); + + InputSystem.TestHook_RestoreFromSavedState(state); + InputUpdate.Restore(state.managerState.updateState); + + InputSystem.manager.InstallRuntime(InputSystem.manager.runtime); + InputSystem.manager.InstallGlobals(); + + // IMPORTANT + // If InputManager was using the "temporary" settings object, then it'll have been deleted during Reset() + // and the saved Manager settings state will also be null, since it's a ScriptableObject. + // In this case we manually create and set new temp settings object. + if (InputSystem.manager.settings == null) + { + var tmpSettings = ScriptableObject.CreateInstance(); + tmpSettings.hideFlags = HideFlags.HideAndDontSave; + InputSystem.manager.settings = tmpSettings; + } + else InputSystem.manager.ApplySettings(); + +#if UNITY_EDITOR + InputEditorUserSettings.s_Settings = state.userSettings; + JsonUtility.FromJsonOverwrite(state.systemObject, InputSystem.domainStateManager); +#endif + + // Get devices that keep global lists (like Gamepad) to re-initialize them + // by pretending the devices have been added. + foreach (var device in InputSystem.devices) + { + device.NotifyAdded(); + device.MakeCurrent(); + } + } + + private Stack m_SavedStateStack = new Stack(); + } +} diff --git a/Packages/com.unity.inputsystem/Tests/TestFixture/InputTestStateManager.cs.meta b/Packages/com.unity.inputsystem/Tests/TestFixture/InputTestStateManager.cs.meta new file mode 100644 index 0000000000..7b62c616ec --- /dev/null +++ b/Packages/com.unity.inputsystem/Tests/TestFixture/InputTestStateManager.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 90487f30114ceb14093b1cc699c8b303 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: