diff --git a/Assets/Tests/InputSystem/CoreTests_Actions.cs b/Assets/Tests/InputSystem/CoreTests_Actions.cs index c4ae3b7c5d..971297a1ae 100644 --- a/Assets/Tests/InputSystem/CoreTests_Actions.cs +++ b/Assets/Tests/InputSystem/CoreTests_Actions.cs @@ -5263,56 +5263,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); } } } @@ -5343,14 +5355,14 @@ 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) { #if UNITY_INPUT_SYSTEM_PROJECT_WIDE_ACTIONS // Exclude project-wide actions from this test InputSystem.actions?.Disable(); InputActionState.DestroyAllActionMapStates(); // Required for `onActionChange` to report correct number of changes #endif - + var actions = getActions(); var gamepad = InputSystem.AddDevice(); if (modification == Modification.AddDevice || modification == Modification.RemoveDevice) @@ -6080,12 +6092,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)); } @@ -6731,7 +6743,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 @@ -9023,7 +9035,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 @@ -11103,7 +11115,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))); } @@ -12123,7 +12135,7 @@ public void Actions_CompositeBindingResetWhenResetDeviceCalledWhileExecutingActi // Disable the Keyboard while action is being performed. // This simulates an "OnFocusLost" event occurring while processing the Action, e.g. when switching primary displays or moving the main window actionPerformed = true; - InputSystem.s_Manager.EnableOrDisableDevice(keyboard.device, false, InputManager.DeviceDisableScope.TemporaryWhilePlayerIsInBackground); + InputSystem.manager.EnableOrDisableDevice(keyboard.device, false, InputManager.DeviceDisableScope.TemporaryWhilePlayerIsInBackground); }; map.Enable(); @@ -12136,7 +12148,7 @@ public void Actions_CompositeBindingResetWhenResetDeviceCalledWhileExecutingActi Assert.IsTrue(actionPerformed); // Re enable the Keyboard (before keys are released) and execute Action again - InputSystem.s_Manager.EnableOrDisableDevice(keyboard.device, true, InputManager.DeviceDisableScope.TemporaryWhilePlayerIsInBackground); + InputSystem.manager.EnableOrDisableDevice(keyboard.device, true, InputManager.DeviceDisableScope.TemporaryWhilePlayerIsInBackground); actionPerformed = false; Release(keyboard.leftShiftKey); @@ -12155,7 +12167,7 @@ public void Actions_CompositeBindingResetWhenResetDeviceCalledWhileExecutingActi Release(keyboard.f1Key); // Re enable the Keyboard (after keys are released) and execute Action one more time - InputSystem.s_Manager.EnableOrDisableDevice(keyboard.device, true, InputManager.DeviceDisableScope.TemporaryWhilePlayerIsInBackground); + InputSystem.manager.EnableOrDisableDevice(keyboard.device, true, InputManager.DeviceDisableScope.TemporaryWhilePlayerIsInBackground); Press(keyboard.leftCtrlKey); Press(keyboard.leftShiftKey); @@ -12169,7 +12181,7 @@ public void Actions_CompositeBindingResetWhenResetDeviceCalledWhileExecutingActi Press(keyboard.f1Key); // Re enable the Keyboard (before keys are released) and verify Action isn't triggered when Key pressed first - InputSystem.s_Manager.EnableOrDisableDevice(keyboard.device, true, InputManager.DeviceDisableScope.TemporaryWhilePlayerIsInBackground); + InputSystem.manager.EnableOrDisableDevice(keyboard.device, true, InputManager.DeviceDisableScope.TemporaryWhilePlayerIsInBackground); Press(keyboard.f1Key); Press(keyboard.leftCtrlKey); diff --git a/Assets/Tests/InputSystem/CoreTests_Devices.cs b/Assets/Tests/InputSystem/CoreTests_Devices.cs index 0408664175..579e1f0389 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 0a74266c6b..c08b9cb650 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")); @@ -345,12 +346,13 @@ public void Editor_DomainReload_CanRemoveDevicesDuringDomainReload() Assert.That(InputSystem.devices, Has.Count.EqualTo(1)); 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 +360,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 +377,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 +2506,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] @@ -2926,15 +2928,15 @@ 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); - 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); // Won't get removed, just cleared. } [Test] diff --git a/Assets/Tests/InputSystem/CoreTests_Events.cs b/Assets/Tests/InputSystem/CoreTests_Events.cs index 6addd65598..df3367f98e 100644 --- a/Assets/Tests/InputSystem/CoreTests_Events.cs +++ b/Assets/Tests/InputSystem/CoreTests_Events.cs @@ -431,7 +431,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)); @@ -458,8 +458,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; @@ -475,19 +475,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)); 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 7fee57d947..dff74db724 100644 --- a/Assets/Tests/InputSystem/CoreTests_State.cs +++ b/Assets/Tests/InputSystem/CoreTests_State.cs @@ -1196,7 +1196,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 a36018f22b..4b9559160c 100644 --- a/Assets/Tests/InputSystem/Plugins/HIDTests.cs +++ b/Assets/Tests/InputSystem/Plugins/HIDTests.cs @@ -708,8 +708,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 e1cb63bf55..543b73639f 100644 --- a/Assets/Tests/InputSystem/Plugins/InputForUITests.cs +++ b/Assets/Tests/InputSystem/Plugins/InputForUITests.cs @@ -60,7 +60,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)) @@ -193,7 +193,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(); @@ -267,7 +267,7 @@ public void UIActionSubmit_FiresUISubmitEvents_FromInputsGamepadJoystickAndKeybo Update(); if (!useProjectWideActionsAsset) { - InputSystem.s_Manager.actions = null; + InputSystem.manager.actions = null; } Update(); @@ -304,7 +304,7 @@ public void UIActionCancel_FiresUICancelEvents_FromInputsGamepadAndKeyboard(bool Update(); if (!useProjectWideActionsAsset) { - InputSystem.s_Manager.actions = null; + InputSystem.manager.actions = null; } Update(); @@ -334,7 +334,7 @@ public void UIActionPoint_FiresUIPointEvents_FromInputsMousePenAndTouch(bool use Update(); if (!useProjectWideActionsAsset) { - InputSystem.s_Manager.actions = null; + InputSystem.manager.actions = null; } Update(); @@ -386,7 +386,7 @@ public void UIActionClick_FiresUIClickEvents_FromInputsMousePenAndTouch(bool use Update(); if (!useProjectWideActionsAsset) { - InputSystem.s_Manager.actions = null; + InputSystem.manager.actions = null; } Update(); @@ -471,7 +471,7 @@ public void UIActionScroll_FiresUIScrollEvents_FromInputMouse(bool useProjectWid Update(); if (!useProjectWideActionsAsset) { - InputSystem.s_Manager.actions = null; + InputSystem.manager.actions = null; } Update(); @@ -502,7 +502,7 @@ public void UIActionScroll_FiresUIScrollEvents_FromInputMouse(bool useProjectWid public void DefaultActions_ShouldNotGenerateAnyVerificationWarnings(bool useProjectWideActions) { if (!useProjectWideActions) - InputSystem.s_Manager.actions = null; + InputSystem.manager.actions = null; Update(); LogAssert.NoUnexpectedReceived(); } @@ -515,7 +515,7 @@ public void ActionsWithoutUIMap_ShouldGenerateWarnings() var asset = ProjectWideActionsAsset.CreateDefaultAssetAtPath(kAssetPath); asset.RemoveActionMap(asset.FindActionMap("UI", throwIfNotFound: true)); - InputSystem.s_Manager.actions = asset; + InputSystem.manager.actions = asset; Update(); var link = EditorHelpers.GetHyperlink(kAssetPath); @@ -551,7 +551,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); @@ -594,7 +594,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.")); @@ -619,7 +619,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, @@ -645,7 +645,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 598ce1cd2b..23b7aafd57 100644 --- a/Packages/com.unity.inputsystem/InputSystem/Actions/InputActionAsset.cs +++ b/Packages/com.unity.inputsystem/InputSystem/Actions/InputActionAsset.cs @@ -869,7 +869,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 d26eb8f251..d2ddfeddc4 100644 --- a/Packages/com.unity.inputsystem/InputSystem/Actions/InputActionMap.cs +++ b/Packages/com.unity.inputsystem/InputSystem/Actions/InputActionMap.cs @@ -809,8 +809,6 @@ private enum Flags BindingsForEachActionInitialized = 1 << 3, } - internal static int s_DeferBindingResolution; - internal struct DeviceArray { private bool m_HaveValue; @@ -1206,7 +1204,7 @@ internal bool LazyResolveBindings(bool fullResolve) needToResolveBindings = true; bindingResolutionNeedsFullReResolve |= fullResolve; - if (s_DeferBindingResolution > 0) + if (InputSystem.manager.areDeferredBindingsToResolve) return false; // Have to do it straight away. @@ -1225,7 +1223,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; } diff --git a/Packages/com.unity.inputsystem/InputSystem/Actions/InputActionRebindingExtensions.cs b/Packages/com.unity.inputsystem/InputSystem/Actions/InputActionRebindingExtensions.cs index bdf7888ceb..62a02b5cbe 100644 --- a/Packages/com.unity.inputsystem/InputSystem/Actions/InputActionRebindingExtensions.cs +++ b/Packages/com.unity.inputsystem/InputSystem/Actions/InputActionRebindingExtensions.cs @@ -2776,39 +2776,69 @@ 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; - } + 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(); + } + } + finally { - if (InputActionMap.s_DeferBindingResolution > 0) - --InputActionMap.s_DeferBindingResolution; - if (InputActionMap.s_DeferBindingResolution == 0) - InputActionState.DeferredResolutionOfBindings(); + --m_DeferredCount; } } + + + private int m_DeferredCount; } } diff --git a/Packages/com.unity.inputsystem/InputSystem/Actions/InputActionState.cs b/Packages/com.unity.inputsystem/InputSystem/Actions/InputActionState.cs index c931232931..7330ffcf4b 100644 --- a/Packages/com.unity.inputsystem/InputSystem/Actions/InputActionState.cs +++ b/Packages/com.unity.inputsystem/InputSystem/Actions/InputActionState.cs @@ -1120,7 +1120,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; @@ -1149,7 +1149,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; @@ -1225,7 +1225,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; } @@ -1234,7 +1234,7 @@ private void UnhookOnBeforeUpdate() if (!m_OnBeforeUpdateHooked) return; - InputSystem.s_Manager.onBeforeUpdate -= m_OnBeforeUpdateDelegate; + InputSystem.manager.onBeforeUpdate -= m_OnBeforeUpdateDelegate; m_OnBeforeUpdateHooked = false; } @@ -1267,7 +1267,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]; @@ -2102,7 +2102,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; @@ -2131,7 +2131,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. @@ -4237,6 +4237,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 @@ -4486,36 +4493,6 @@ internal static void OnDeviceChange(InputDevice device, InputDeviceChange change } } - internal static void DeferredResolutionOfBindings() - { - ++InputActionMap.s_DeferBindingResolution; - try - { - 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(); - } - } - 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/InputControlLayout.cs b/Packages/com.unity.inputsystem/InputSystem/Controls/InputControlLayout.cs index 501378afdc..ad8923044f 100644 --- a/Packages/com.unity.inputsystem/InputSystem/Controls/InputControlLayout.cs +++ b/Packages/com.unity.inputsystem/InputSystem/Controls/InputControlLayout.cs @@ -109,7 +109,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 cafc7392d4..4dca95d093 100644 --- a/Packages/com.unity.inputsystem/InputSystem/Devices/InputDevice.cs +++ b/Packages/com.unity.inputsystem/InputSystem/Devices/InputDevice.cs @@ -557,7 +557,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 c119cb05c6..748198f11b 100644 --- a/Packages/com.unity.inputsystem/InputSystem/Devices/Touchscreen.cs +++ b/Packages/com.unity.inputsystem/InputSystem/Devices/Touchscreen.cs @@ -534,6 +534,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() { @@ -624,14 +632,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); Profiler.EndSample(); @@ -720,11 +728,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 @@ -1044,8 +1052,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/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 6609f622f5..4468acd77a 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 @@ -421,7 +421,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 6086f7d605..a98a3dd7a6 100644 --- a/Packages/com.unity.inputsystem/InputSystem/Editor/Debugger/InputDebuggerWindow.cs +++ b/Packages/com.unity.inputsystem/InputSystem/Editor/Debugger/InputDebuggerWindow.cs @@ -212,9 +212,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() @@ -225,15 +225,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; } } @@ -311,7 +311,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 d38943abc8..0689b0fd88 100644 --- a/Packages/com.unity.inputsystem/InputSystem/Editor/Debugger/InputDeviceDebuggerWindow.cs +++ b/Packages/com.unity.inputsystem/InputSystem/Editor/Debugger/InputDeviceDebuggerWindow.cs @@ -356,9 +356,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 8376792d48..5f996a3610 100644 --- a/Packages/com.unity.inputsystem/InputSystem/Editor/Internal/InputStateWindow.cs +++ b/Packages/com.unity.inputsystem/InputSystem/Editor/Internal/InputStateWindow.cs @@ -152,7 +152,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 90ecf92f5c..3559418da1 100644 --- a/Packages/com.unity.inputsystem/InputSystem/Editor/UITKAssetEditor/Views/Selectors.cs +++ b/Packages/com.unity.inputsystem/InputSystem/Editor/UITKAssetEditor/Views/Selectors.cs @@ -277,7 +277,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 d154ec0c1b..f77005d49b 100644 --- a/Packages/com.unity.inputsystem/InputSystem/InputAnalytics.cs +++ b/Packages/com.unity.inputsystem/InputSystem/InputAnalytics.cs @@ -17,7 +17,7 @@ internal static class InputAnalytics public static void Initialize(InputManager manager) { - Debug.Assert(manager.m_Runtime != null); + Debug.Assert(manager.runtime != null); } public static void OnStartup(InputManager manager) @@ -60,8 +60,8 @@ public static void OnStartup(InputManager manager) data.old_enabled = EditorPlayerSettingHelpers.oldSystemBackendsEnabled; #endif - manager.m_Runtime.RegisterAnalyticsEvent(kEventStartup, 10, 100); - manager.m_Runtime.SendAnalyticsEvent(kEventStartup, data); + manager.runtime.RegisterAnalyticsEvent(kEventStartup, 10, 100); + manager.runtime.SendAnalyticsEvent(kEventStartup, data); } public static void OnShutdown(InputManager manager) @@ -77,8 +77,8 @@ public static void OnShutdown(InputManager manager) total_event_processing_time = (float)metrics.totalEventProcessingTime, }; - manager.m_Runtime.RegisterAnalyticsEvent(kEventShutdown, 10, 100); - manager.m_Runtime.SendAnalyticsEvent(kEventShutdown, data); + manager.runtime.RegisterAnalyticsEvent(kEventShutdown, 10, 100); + manager.runtime.SendAnalyticsEvent(kEventShutdown, data); } /// diff --git a/Packages/com.unity.inputsystem/InputSystem/InputManager.cs b/Packages/com.unity.inputsystem/InputSystem/InputManager.cs index 5bee4ac31a..5ec57297ff 100644 --- a/Packages/com.unity.inputsystem/InputSystem/InputManager.cs +++ b/Packages/com.unity.inputsystem/InputSystem/InputManager.cs @@ -16,6 +16,8 @@ #if UNITY_EDITOR using UnityEngine.InputSystem.Editor; +using UnityEngine.Tilemaps; + #endif #if UNITY_EDITOR @@ -55,13 +57,89 @@ 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; + + #if UNITY_INPUT_SYSTEM_PROJECT_WIDE_ACTIONS + newInstance.InitializeActions(); + #endif // UNITY_INPUT_SYSTEM_PROJECT_WIDE_ACTIONS + + 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(); + + #if UNITY_INPUT_SYSTEM_PROJECT_WIDE_ACTIONS + newInstance.ApplyActions(); + #endif + 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; public InputMetrics metrics { @@ -91,7 +169,6 @@ public InputSettings settings { get { - Debug.Assert(m_Settings != null); return m_Settings; } set @@ -99,6 +176,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; @@ -306,9 +387,6 @@ internal bool ShouldDrawWarningIconForBinding(string bindingPath) "InputSystem.ShouldDrawWarningIconForBinding"); } -#endif // UNITY_EDITOR - -#if UNITY_EDITOR private bool m_RunPlayerUpdatesInEditMode; /// @@ -323,7 +401,8 @@ public bool runPlayerUpdatesInEditMode get => m_RunPlayerUpdatesInEditMode; set => m_RunPlayerUpdatesInEditMode = value; } -#endif + +#endif // UNITY_EDITOR private bool gameIsPlaying => #if UNITY_EDITOR @@ -334,7 +413,7 @@ public bool runPlayerUpdatesInEditMode private bool gameHasFocus => #if UNITY_EDITOR - m_RunPlayerUpdatesInEditMode || m_HasFocus || gameShouldGetInputRegardlessOfFocus; + m_RunPlayerUpdatesInEditMode || m_HasFocus || gameShouldGetInputRegardlessOfFocus; #else m_HasFocus || gameShouldGetInputRegardlessOfFocus; #endif @@ -1769,55 +1848,16 @@ public void Update(InputUpdateType updateType) m_Runtime.Update(updateType); } - internal void Initialize(IInputRuntime runtime, InputSettings settings) - { - Debug.Assert(settings != null); - - m_Settings = settings; - -#if UNITY_INPUT_SYSTEM_PROJECT_WIDE_ACTIONS - InitializeActions(); -#endif // UNITY_INPUT_SYSTEM_PROJECT_WIDE_ACTIONS - InitializeData(); - InstallRuntime(runtime); - InstallGlobals(); - - ApplySettings(); - #if UNITY_INPUT_SYSTEM_PROJECT_WIDE_ACTIONS - ApplyActions(); - #endif - } - - 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. - } -#if UNITY_INPUT_SYSTEM_PROJECT_WIDE_ACTIONS + #if UNITY_INPUT_SYSTEM_PROJECT_WIDE_ACTIONS // 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) @@ -1828,10 +1868,9 @@ private void InitializeActions() break; } } -#endif // UNITY_EDITOR + #endif // UNITY_EDITOR } - -#endif // UNITY_INPUT_SYSTEM_PROJECT_WIDE_ACTIONS + #endif // UNITY_INPUT_SYSTEM_PROJECT_WIDE_ACTIONS internal void InitializeData() { @@ -1847,10 +1886,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 // Default polling frequency is 60 Hz. m_PollingFrequency = 60; @@ -2035,6 +2074,25 @@ 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; + [Serializable] internal struct AvailableDevice { @@ -2113,9 +2171,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; #if UNITY_INPUT_SYSTEM_PROJECT_WIDE_ACTIONS private InputActionAsset m_Actions; #endif @@ -2124,6 +2182,8 @@ internal struct AvailableDevice 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) @@ -2461,13 +2521,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; @@ -2500,7 +2560,7 @@ internal void SyncAllDevicesAfterEnteringPlayMode() SyncAllDevices(); } -#endif + #endif // UNITY_EDITOR private void WarnAboutDevicesFailingToRecreateAfterDomainReload() { @@ -2520,7 +2580,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) @@ -2564,6 +2624,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) @@ -2653,10 +2715,14 @@ internal void ApplySettings() } } - // 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; @@ -2680,9 +2746,8 @@ internal void ApplyActions() // Let listeners know. DelegateHelpers.InvokeCallbacksSafe(ref m_ActionsChangedListeners, "InputSystem.onActionsChange"); } - #endif - + internal unsafe long ExecuteGlobalCommand(ref TCommand command) where TCommand : struct, IInputDeviceCommandInfo { @@ -2844,7 +2909,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. @@ -2872,7 +2937,7 @@ private void OnPlayerLoopInitialization() InputStateBuffers.SwitchTo(m_StateBuffers, InputUpdate.s_LatestUpdateType); } -#endif + #endif // UNITY_EDITOR internal bool ShouldRunUpdate(InputUpdateType updateType) { @@ -2883,7 +2948,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 @@ -2892,7 +2957,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; } @@ -2991,21 +3056,21 @@ private unsafe void OnUpdate(InputUpdateType updateType, ref InputEventBuffer ev // Figure out if we can just flush the buffer and early out. var canFlushBuffer = false -#if UNITY_EDITOR + #if UNITY_EDITOR // If out of focus and runInBackground is off and ExactlyAsInPlayer is on, discard input. || (!gameHasFocus && m_Settings.editorInputBehaviorInPlayMode == InputSettings.EditorInputBehaviorInPlayMode.AllDeviceInputAlwaysGoesToGameView && (!m_Runtime.runInBackground || m_Settings.backgroundBehavior == InputSettings.BackgroundBehavior.ResetAndDisableAllDevices)) -#else + #else || (!gameHasFocus && !m_Runtime.runInBackground) -#endif + #endif ; var canEarlyOut = // Early out if there's no events to process. eventBuffer.eventCount == 0 || canFlushBuffer -#if UNITY_EDITOR + #if UNITY_EDITOR // If we're in the background and not supposed to process events in this update (but somehow // still ended up here), we're done. || ((!gameHasFocus || gameShouldGetInputRegardlessOfFocus) && @@ -3016,11 +3081,11 @@ private unsafe void OnUpdate(InputUpdateType updateType, ref InputEventBuffer ev // When the game is playing and has focus, we never process input in editor updates. All we // do is just switch to editor state buffers and then exit. || (gameIsPlaying && gameHasFocus && updateType == InputUpdateType.Editor)) -#endif + #endif ; -#if UNITY_EDITOR + #if UNITY_EDITOR var dropStatusEvents = false; if (!gameIsPlaying && gameShouldGetInputRegardlessOfFocus && (eventBuffer.sizeInBytes > (100 * 1024))) { @@ -3029,7 +3094,7 @@ private unsafe void OnUpdate(InputUpdateType updateType, ref InputEventBuffer ev canEarlyOut = false; dropStatusEvents = true; } -#endif + #endif if (canEarlyOut) { @@ -3093,7 +3158,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. @@ -3104,7 +3169,7 @@ private unsafe void OnUpdate(InputUpdateType updateType, ref InputEventBuffer ev continue; } -#endif + #endif // In the editor, we discard all input events that occur in-between exiting edit mode and having // entered play mode as otherwise we'll spill a bunch of UI events that have occurred while the @@ -3115,19 +3180,19 @@ private unsafe void OnUpdate(InputUpdateType updateType, ref InputEventBuffer ev // here such as throwing partial touches away and then letting the rest of a touch go through. // Could be that ultimately we need to issue a full reset of all devices at the beginning of // play mode in the editor. -#if UNITY_EDITOR + #if UNITY_EDITOR if ((currentEventType == StateEvent.Type || currentEventType == DeltaStateEvent.Type) && (updateType & InputUpdateType.Editor) == 0 && - InputSystem.s_SystemObject.exitEditModeTime > 0 && - currentEventTimeInternal >= InputSystem.s_SystemObject.exitEditModeTime && - (currentEventTimeInternal < InputSystem.s_SystemObject.enterPlayModeTime || - InputSystem.s_SystemObject.enterPlayModeTime == 0)) + InputSystem.domainStateManager.exitEditModeTime > 0 && + currentEventTimeInternal >= InputSystem.domainStateManager.exitEditModeTime && + (currentEventTimeInternal < InputSystem.domainStateManager.enterPlayModeTime || + InputSystem.domainStateManager.enterPlayModeTime == 0)) { m_InputEventStream.Advance(false); continue; } -#endif + #endif // If we're timeslicing, check if the event time is within limits. if (timesliceEvents && currentEventTimeInternal >= currentTime) @@ -3141,10 +3206,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; @@ -3152,7 +3217,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 @@ -3183,7 +3248,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. @@ -3193,12 +3258,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; @@ -3270,17 +3335,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) { Profiler.EndSample(); 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. @@ -3332,7 +3397,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 @@ -3363,9 +3428,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; } @@ -3381,9 +3446,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; @@ -3769,7 +3834,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 @@ -3783,7 +3848,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) @@ -3801,7 +3866,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 { @@ -3917,8 +3982,16 @@ internal void RestoreStateWithoutDevices(SerializedState state) m_Metrics = state.metrics; m_PollingFrequency = state.pollingFrequency; - 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); + m_Settings = state.settings; #if UNITY_INPUT_SYSTEM_PROJECT_WIDE_ACTIONS @@ -3941,6 +4014,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. /// @@ -4024,6 +4098,29 @@ internal void RestoreDevicesAfterDomainReload() Profiler.EndSample(); } + /// + /// 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: // @@ -4076,7 +4173,6 @@ private bool RestoreDeviceFromSavedState(ref DeviceState deviceState, InternedSt return true; } - #endif // UNITY_EDITOR || DEVELOPMENT_BUILD } } diff --git a/Packages/com.unity.inputsystem/InputSystem/InputSettings.cs b/Packages/com.unity.inputsystem/InputSystem/InputSettings.cs index c9cdafa369..245b374164 100644 --- a/Packages/com.unity.inputsystem/InputSystem/InputSettings.cs +++ b/Packages/com.unity.inputsystem/InputSystem/InputSettings.cs @@ -786,7 +786,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 c7f78b18bd..f87b4ce54a 100644 --- a/Packages/com.unity.inputsystem/InputSystem/InputSystem.cs +++ b/Packages/com.unity.inputsystem/InputSystem/InputSystem.cs @@ -80,9 +80,19 @@ namespace UnityEngine.InputSystem #if UNITY_EDITOR [InitializeOnLoad] #endif - public static partial class InputSystem { + static InputSystem() + { + GlobalInitialize(true); + } + + [RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.BeforeSceneLoad)] + private static void RuntimeInitialize() + { + GlobalInitialize(false); + } + #region Layouts /// @@ -2895,7 +2905,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. @@ -3100,9 +3110,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."); @@ -3110,7 +3119,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; @@ -3133,7 +3142,6 @@ public static event Action onActionsChange add => s_Manager.onActionsChange += value; remove => s_Manager.onActionsChange -= value; } - #endif // UNITY_INPUT_SYSTEM_PROJECT_WIDE_ACTIONS /// @@ -3419,8 +3427,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; } ////REVIEW: restrict metrics to editor and development builds? @@ -3430,13 +3438,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); @@ -3472,46 +3482,54 @@ 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) @@ -3522,56 +3540,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) { Profiler.BeginSample("InputSystem.InitializeInEditor"); - 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 + } + + 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(); - // 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; + // Restore remoting state. + s_RemoteConnection = s_DomainStateManager.systemState.remoteConnection; + SetUpRemoting(); + s_Remote.RestoreState(s_DomainStateManager.systemState.remotingState, s_Manager); - // Restore editor settings. - InputEditorUserSettings.s_Settings = s_SystemObject.systemState.userSettings; + // 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; - // Get rid of saved state. - s_SystemObject.systemState = new State(); + // 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; } #if UNITY_INPUT_SYSTEM_PROJECT_WIDE_ACTIONS @@ -3587,17 +3626,15 @@ internal static void InitializeInEditor(IInputRuntime runtime = null) } Debug.Assert(settings != null); - #if UNITY_EDITOR Debug.Assert(EditorUtility.InstanceIDToObject(settings.GetInstanceID()) != null, "InputSettings has lost its native object"); - #endif // If native backends for new input system aren't enabled, ask user whether we should // enable them (requires restart). We only ask once per session and don't ask when // running in batch mode. - if (!s_SystemObject.newInputBackendsCheckedAsEnabled && + if (!s_DomainStateManager.newInputBackendsCheckedAsEnabled && !EditorPlayerSettingHelpers.newSystemBackendsEnabled && - !s_Manager.m_Runtime.isInBatchMode) + !s_Manager.runtime.isInBatchMode) { const string dialogText = "This project is using the new input system package but the native platform backends for the new input system are not enabled in the player settings. " + "This means that no input from native devices will come through." + @@ -3609,13 +3646,13 @@ internal static void InitializeInEditor(IInputRuntime runtime = null) EditorHelpers.RestartEditorAndRecompileScripts(); } } - s_SystemObject.newInputBackendsCheckedAsEnabled = true; + s_DomainStateManager.newInputBackendsCheckedAsEnabled = true; -#if UNITY_INPUT_SYSTEM_PROJECT_WIDE_ACTIONS + #if UNITY_INPUT_SYSTEM_PROJECT_WIDE_ACTIONS // Make sure project wide input actions are enabled. // Note that this will always fail if entering play-mode within editor since not yet in play-mode. EnableActions(); -#endif + #endif RunInitialUpdate(); @@ -3629,15 +3666,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(); #if UNITY_INPUT_SYSTEM_PROJECT_WIDE_ACTIONS EnableActions(); @@ -3652,9 +3689,9 @@ internal static void OnPlayModeChange(PlayModeStateChange change) ////REVIEW: is there any other cleanup work we want to before? should we automatically nuke //// InputDevices that have been created with AddDevice<> during play mode? case PlayModeStateChange.EnteredEditMode: -#if UNITY_INPUT_SYSTEM_PROJECT_WIDE_ACTIONS + #if UNITY_INPUT_SYSTEM_PROJECT_WIDE_ACTIONS DisableActions(false); -#endif + #endif // Nuke all InputUsers. InputUser.ResetGlobals(); @@ -3663,34 +3700,20 @@ internal static void OnPlayModeChange(PlayModeStateChange change) InputActionState.DestroyAllActionMapStates(); // 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; } } - 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 @@ -3699,9 +3722,7 @@ private static void OnProjectChange() // Also, if the asset holding our current settings got deleted, switch back to a // 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 (EditorUtility.InstanceIDToObject(s_Manager.m_Settings.GetInstanceID()) == null) + if (EditorUtility.InstanceIDToObject(s_Manager.settings.GetInstanceID()) == null) { var newSettings = ScriptableObject.CreateInstance(); newSettings.hideFlags = HideFlags.HideAndDontSave; @@ -3709,59 +3730,62 @@ 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(); + InputSettings settings = null; - if (AssetDatabase.TryGetGUIDAndLocalFileIdentifier(asset, out string assetGuid, out long _) == false) - return; - - 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 -#if UNITY_INPUT_SYSTEM_PROJECT_WIDE_ACTIONS // && !UNITY_INCLUDE_TESTS + #if UNITY_INPUT_SYSTEM_PROJECT_WIDE_ACTIONS // This is the point where we initialise project-wide actions for the Player EnableActions(); -#endif + #endif } #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() + { + #if UNITY_INPUT_SYSTEM_PROJECT_WIDE_ACTIONS + // 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; + #endif // UNITY_INPUT_SYSTEM_PROJECT_WIDE_ACTIONS + } + + internal static void TestHook_EnableActions() + { + // Note this is too early for editor ! actions is not setup yet. + #if UNITY_INPUT_SYSTEM_PROJECT_WIDE_ACTIONS + EnableActions(); + #endif + } +#endif // UNITY_INCLUDE_TESTS + private static void RunInitialUpdate() { // Request an initial Update so that user methods such as Start and Awake @@ -3775,8 +3799,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 @@ -3834,215 +3874,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) - { - Profiler.BeginSample("InputSystem.Reset"); - -#if UNITY_INPUT_SYSTEM_PROJECT_WIDE_ACTIONS - // 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; -#endif // UNITY_INPUT_SYSTEM_PROJECT_WIDE_ACTIONS - - // 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,d - // 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, Editor Tests and Player Tests. - // Note this is too early for editor ! actions is not setup yet. - #if UNITY_INPUT_SYSTEM_PROJECT_WIDE_ACTIONS - EnableActions(); - #endif - - Profiler.EndSample(); - } - - /// - /// 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/InputSystemObject.cs.meta b/Packages/com.unity.inputsystem/InputSystem/InputSystemObject.cs.meta deleted file mode 100644 index 0a9818e38c..0000000000 --- a/Packages/com.unity.inputsystem/InputSystem/InputSystemObject.cs.meta +++ /dev/null @@ -1,3 +0,0 @@ -fileFormatVersion: 2 -guid: 5cdce2bffd1e49bda08b3db54a031207 -timeCreated: 1506825901 \ No newline at end of file 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/InputSystemStateManager.cs.meta b/Packages/com.unity.inputsystem/InputSystem/InputSystemStateManager.cs.meta new file mode 100644 index 0000000000..532aad0114 --- /dev/null +++ b/Packages/com.unity.inputsystem/InputSystem/InputSystemStateManager.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 3a1038634f66b98469a174a3e1a85190 \ No newline at end of file diff --git a/Packages/com.unity.inputsystem/InputSystem/InputSystemTestHooks.cs b/Packages/com.unity.inputsystem/InputSystem/InputSystemTestHooks.cs new file mode 100644 index 0000000000..3f2ed56f46 --- /dev/null +++ b/Packages/com.unity.inputsystem/InputSystem/InputSystemTestHooks.cs @@ -0,0 +1,98 @@ +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..83ac0bf722 --- /dev/null +++ b/Packages/com.unity.inputsystem/InputSystem/InputSystemTestHooks.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 1f95d379362f6c74ca1eb6368642199f \ No newline at end of file diff --git a/Packages/com.unity.inputsystem/InputSystem/NativeInputRuntime.cs b/Packages/com.unity.inputsystem/InputSystem/NativeInputRuntime.cs index 6bba685046..217df9bf88 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 ca4ff95ea4..d1b289178d 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 096f8c9659..169f3d34ed 100644 --- a/Packages/com.unity.inputsystem/InputSystem/Plugins/EnhancedTouch/Touch.cs +++ b/Packages/com.unity.inputsystem/InputSystem/Plugins/EnhancedTouch/Touch.cs @@ -587,18 +587,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() { @@ -609,7 +615,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 f8de763e49..d25132fc43 100644 --- a/Packages/com.unity.inputsystem/InputSystem/Plugins/EnhancedTouch/TouchSimulation.cs +++ b/Packages/com.unity.inputsystem/InputSystem/Plugins/EnhancedTouch/TouchSimulation.cs @@ -325,8 +325,8 @@ private unsafe void UpdateTouch(int touchIndex, int pointerIndex, TouchPhase pha if (phase == TouchPhase.Ended) { - touch.isTap = time - oldTouchState->startTime <= Touchscreen.s_TapTime && - (position - oldTouchState->startPosition).sqrMagnitude <= Touchscreen.s_TapRadiusSquared; + touch.isTap = time - oldTouchState->startTime <= Touchscreen.settings.tapTime && + (position - oldTouchState->startPosition).sqrMagnitude <= Touchscreen.settings.tapRadiusSquared; if (touch.isTap) ++touch.tapCount; } 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 0dbfc82e16..d3d522aa59 100644 --- a/Packages/com.unity.inputsystem/InputSystem/Plugins/PlayerInput/PlayerInput.cs +++ b/Packages/com.unity.inputsystem/InputSystem/Plugins/PlayerInput/PlayerInput.cs @@ -800,7 +800,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. @@ -814,7 +814,7 @@ public ReadOnlyArray devices /// /// public static bool isSinglePlayer => - s_AllActivePlayersCount <= 1 && + s_GlobalState.allActivePlayersCount <= 1 && (PlayerInputManager.instance == null || !PlayerInputManager.instance.joiningEnabled); /// @@ -996,9 +996,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; } @@ -1022,10 +1022,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; @@ -1051,11 +1051,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); } @@ -1083,13 +1083,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); @@ -1097,7 +1097,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 @@ -1108,13 +1108,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(); @@ -1180,18 +1180,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() { @@ -1202,8 +1225,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) { var oldActions = m_Actions; m_Actions = Instantiate(m_Actions); @@ -1394,10 +1417,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 { @@ -1411,15 +1434,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 { @@ -1443,13 +1466,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); @@ -1457,7 +1480,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()) { @@ -1475,10 +1498,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 { @@ -1531,7 +1554,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? @@ -1541,17 +1564,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); } } @@ -1572,16 +1595,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); } @@ -1611,15 +1634,14 @@ private void AssignPlayerIndex() } } - #if UNITY_EDITOR && UNITY_INPUT_SYSTEM_PROJECT_WIDE_ACTIONS +#if UNITY_EDITOR && UNITY_INPUT_SYSTEM_PROJECT_WIDE_ACTIONS void Reset() { // Set default actions to project wide actions. m_Actions = InputSystem.actions; // TODO Need to monitor changes? } - - #endif +#endif private void OnEnable() { @@ -1634,23 +1656,23 @@ private void OnEnable() } // Split-screen index defaults to player index. - if (s_InitSplitScreenIndex >= 0) + if (s_GlobalState.initSplitScreenIndex >= 0) m_SplitScreenIndex = splitScreenIndex; 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. @@ -1723,13 +1745,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(); @@ -1831,9 +1853,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) @@ -1845,9 +1867,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 2c2c557766..196c1f1de9 100644 --- a/Packages/com.unity.inputsystem/InputSystem/Plugins/PlayerInput/PlayerInputManager.cs +++ b/Packages/com.unity.inputsystem/InputSystem/Plugins/PlayerInput/PlayerInputManager.cs @@ -129,7 +129,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 /// @@ -432,7 +432,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); } @@ -458,7 +458,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); } @@ -508,12 +508,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; } } @@ -565,8 +565,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..108e0fa988 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; } } @@ -124,9 +145,7 @@ private static void OnUpdate() // Update controller state. api.RunFrame(); - // Check if we have any new controllers have appeared. - if (s_ConnectedControllers == null) - s_ConnectedControllers = new SteamHandle[STEAM_CONTROLLER_MAX_COUNT]; + // Check if we have any new controllers have appeared. 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 6a619301da..97704c9347 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 66c4f95bb1..c19026e397 100644 --- a/Packages/com.unity.inputsystem/InputSystem/Plugins/UI/InputSystemUIInputModule.cs +++ b/Packages/com.unity.inputsystem/InputSystem/Plugins/UI/InputSystemUIInputModule.cs @@ -1378,7 +1378,8 @@ public InputActionReference trackedDevicePosition 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 86caf47eb7..22cdfe3368 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 362e475669..f11035a7c0 100644 --- a/Packages/com.unity.inputsystem/InputSystem/Plugins/Users/InputUser.cs +++ b/Packages/com.unity.inputsystem/InputSystem/Plugins/Users/InputUser.cs @@ -1873,6 +1873,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 6647417093..29b30adc08 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 @@ -209,7 +209,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, @@ -234,7 +234,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); } /// @@ -256,7 +256,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) @@ -264,7 +264,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 e98443475f..49c02bc30f 100644 --- a/Packages/com.unity.inputsystem/InputSystem/State/InputStateHistory.cs +++ b/Packages/com.unity.inputsystem/InputSystem/State/InputStateHistory.cs @@ -115,7 +115,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..ccccc05afd --- /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 \ No newline at end of file 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..38c81f9be8 --- /dev/null +++ b/Packages/com.unity.inputsystem/InputSystem/Utilities/DirtyAssetTracker.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: fa1cedc2b36c5fc49a203b0e02a22d64 \ No newline at end of file diff --git a/Packages/com.unity.inputsystem/Tests/TestFixture/InputTestFixture.cs b/Packages/com.unity.inputsystem/Tests/TestFixture/InputTestFixture.cs index 14c3f6398f..e4f468946f 100644 --- a/Packages/com.unity.inputsystem/Tests/TestFixture/InputTestFixture.cs +++ b/Packages/com.unity.inputsystem/Tests/TestFixture/InputTestFixture.cs @@ -11,6 +11,8 @@ using UnityEngine.InputSystem.Utilities; using UnityEngine.TestTools; using UnityEngine.TestTools.Utils; +using UnityEngine.InputSystem.Users; + #if UNITY_EDITOR using UnityEditor; using UnityEngine.InputSystem.Editor; @@ -77,6 +79,8 @@ public virtual void Setup() { try { + m_StateManager = new InputTestStateManager(); + // Apparently, NUnit is reusing instances :( m_KeyInfos = default; m_IsUnityTest = default; @@ -92,7 +96,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. @@ -104,7 +108,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 @@ -120,7 +124,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(); @@ -175,7 +179,7 @@ public virtual void TearDown() try { #if DEVELOPMENT_BUILD || UNITY_EDITOR - InputSystem.Restore(); + m_StateManager.Restore(); #endif runtime.Dispose(); @@ -297,6 +301,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; @@ -898,19 +903,5 @@ public ActionConstraint AndThen(ActionConstraint constraint) return this; } } - - #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 } } 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..3126546de9 --- /dev/null +++ b/Packages/com.unity.inputsystem/Tests/TestFixture/InputTestStateManager.cs @@ -0,0 +1,154 @@ +using System; +using System.Collections.Generic; +using Unity.Collections; + +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 + { + 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) + { + Profiler.BeginSample("InputSystem.Reset"); + + 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(); + + Profiler.EndSample(); + } + + ////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..3242066427 --- /dev/null +++ b/Packages/com.unity.inputsystem/Tests/TestFixture/InputTestStateManager.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 90487f30114ceb14093b1cc699c8b303 \ No newline at end of file