diff --git a/Packages/com.unity.inputsystem/InputSystem/Runtime/IInputRuntime.cs b/Packages/com.unity.inputsystem/InputSystem/Runtime/IInputRuntime.cs index 86b9886612..ab95d38d40 100644 --- a/Packages/com.unity.inputsystem/InputSystem/Runtime/IInputRuntime.cs +++ b/Packages/com.unity.inputsystem/InputSystem/Runtime/IInputRuntime.cs @@ -74,6 +74,12 @@ internal unsafe interface IInputRuntime /// command sent to the device. long DeviceCommand(int deviceId, InputDeviceCommand* commandPtr); + /// + /// Initialize the focus state based on current conditions of the application. + /// + /// + void InitializeFocusState(); + /// /// Set delegate to be called on input updates. /// @@ -113,11 +119,15 @@ internal unsafe interface IInputRuntime /// /// Set delegate to call when the application changes focus. /// - /// + /// Action onPlayerFocusChanged { get; set; } #endif + /// + /// Flags indicating various focus states for the application and editor. + /// FocusFlags focusState { get; set; } + /// /// Is true when the player or game view has focus. /// diff --git a/Packages/com.unity.inputsystem/InputSystem/Runtime/InputManager.LegacyFocusHandling.cs b/Packages/com.unity.inputsystem/InputSystem/Runtime/InputManager.LegacyFocusHandling.cs index f4c781bdf4..6c4b8d9fbb 100644 --- a/Packages/com.unity.inputsystem/InputSystem/Runtime/InputManager.LegacyFocusHandling.cs +++ b/Packages/com.unity.inputsystem/InputSystem/Runtime/InputManager.LegacyFocusHandling.cs @@ -22,107 +22,120 @@ internal enum FocusFlags : ushort /// /// In editor this means the GameView has focus. In a built player this means the player has focus. /// - ApplicationFocus = (1 << 0) + ApplicationFocus = (1 << 0), }; internal partial class InputManager { internal void OnFocusChanged(bool focus) { + // We set this to temporarily override applicationHasFocus before processing the focus change + // as defaultUpdateType is influenced by it. Before returning from this method, clear the + // bool to stop overriding to indicate this manager has finished processing the focus change. + m_IsHandlingFocusChange = true; + m_ApplicationHadFocus = !focus; + #if UNITY_EDITOR - SyncAllDevicesWhenEditorIsActivated(); + var shouldClearCurrentUpdateInFinally = false; +#endif - if (!m_Runtime.isInPlayMode) + try { - focusState = focus ? FocusFlags.ApplicationFocus : FocusFlags.None; - return; - } +#if UNITY_EDITOR + SyncAllDevicesWhenEditorIsActivated(); + + if (!m_Runtime.isInPlayMode) + return; - var gameViewFocus = m_Settings.editorInputBehaviorInPlayMode; + var gameViewFocus = m_Settings.editorInputBehaviorInPlayMode; #endif - var runInBackground = + var runInBackground = #if UNITY_EDITOR - // In the editor, the player loop will always be run even if the Game View does not have focus. This - // amounts to runInBackground being always true in the editor, regardless of what the setting in - // the Player Settings window is. - // - // If, however, "Game View Focus" is set to "Exactly As In Player", we force code here down the same - // path as in the player. - gameViewFocus != InputSettings.EditorInputBehaviorInPlayMode.AllDeviceInputAlwaysGoesToGameView || m_Runtime.runInBackground; + // In the editor, the player loop will always be run even if the Game View does not have focus. This + // amounts to runInBackground being always true in the editor, regardless of what the setting in + // the Player Settings window is. + // + // If, however, "Game View Focus" is set to "Exactly As In Player", we force code here down the same + // path as in the player. + gameViewFocus != InputSettings.EditorInputBehaviorInPlayMode.AllDeviceInputAlwaysGoesToGameView || m_Runtime.runInBackground; #else - m_Runtime.runInBackground; + m_Runtime.runInBackground; #endif - var backgroundBehavior = m_Settings.backgroundBehavior; - if (backgroundBehavior == InputSettings.BackgroundBehavior.IgnoreFocus && runInBackground) - { - // If runInBackground is true, no device changes should happen, even when focus is gained. So early out. - // If runInBackground is false, we still want to sync devices when focus is gained. So we need to continue further. - focusState = focus ? FocusFlags.ApplicationFocus : FocusFlags.None; - return; - } + if (m_Settings.backgroundBehavior == InputSettings.BackgroundBehavior.IgnoreFocus && runInBackground) + { + // If runInBackground is true, no device changes should happen, even when focus is gained. So early out. + // If runInBackground is false, we still want to sync devices when focus is gained. So we need to continue further. + return; + } #if UNITY_EDITOR - // Set the current update type while we process the focus changes to make sure we - // feed into the right buffer. No need to do this in the player as it doesn't have - // the editor/player confusion. - m_CurrentUpdate = m_UpdateMask.GetUpdateTypeForPlayer(); + // Set the current update type while we process the focus changes to make sure we + // feed into the right buffer. No need to do this in the player as it doesn't have + // the editor/player confusion. + m_CurrentUpdate = m_UpdateMask.GetUpdateTypeForPlayer(); + shouldClearCurrentUpdateInFinally = true; #endif - if (!focus) - { - // We only react to loss of focus when we will keep running in the background. If not, - // we'll do nothing and just wait for focus to come back (where we then try to sync all devices). - if (runInBackground) + if (!focus) { - for (var i = 0; i < m_DevicesCount; ++i) + // We only react to loss of focus when we will keep running in the background. If not, + // we'll do nothing and just wait for focus to come back (where we then try to sync all devices). + if (runInBackground) { - // Determine whether to run this device in the background. - var device = m_Devices[i]; - if (!device.enabled || ShouldRunDeviceInBackground(device)) - continue; + for (var i = 0; i < m_DevicesCount; ++i) + { + // Determine whether to run this device in the background. + var device = m_Devices[i]; + if (!device.enabled || ShouldRunDeviceInBackground(device)) + continue; - // Disable the device. This will also soft-reset it. - EnableOrDisableDevice(device, false, DeviceDisableScope.TemporaryWhilePlayerIsInBackground); + // Disable the device. This will also soft-reset it. + EnableOrDisableDevice(device, false, DeviceDisableScope.TemporaryWhilePlayerIsInBackground); - // In case we invoked a callback that messed with our device array, adjust our index. - var index = m_Devices.IndexOfReference(device, m_DevicesCount); - if (index == -1) - --i; - else - i = index; + // In case we invoked a callback that messed with our device array, adjust our index. + var index = m_Devices.IndexOfReference(device, m_DevicesCount); + if (index == -1) + --i; + else + i = index; + } } } - } - else - { - m_DiscardOutOfFocusEvents = true; - m_FocusRegainedTime = m_Runtime.currentTime; - // On focus gain, reenable and sync devices. - for (var i = 0; i < m_DevicesCount; ++i) + else { - var device = m_Devices[i]; + m_DiscardOutOfFocusEvents = true; + m_FocusRegainedTime = m_Runtime.currentTime; + + // On focus gain, reenable and sync devices. + for (var i = 0; i < m_DevicesCount; ++i) + { + var device = m_Devices[i]; - // Re-enable the device if we disabled it on focus loss. This will also issue a sync. - if (device.disabledWhileInBackground) - EnableOrDisableDevice(device, true, DeviceDisableScope.TemporaryWhilePlayerIsInBackground); - // Try to sync. If it fails and we didn't run in the background, perform - // a reset instead. This is to cope with backends that are unable to sync but - // may still retain state which now may be outdated because the input device may - // have changed state while we weren't running. So at least make the backend flush - // its state (if any). - else if (device.enabled && !runInBackground && !device.RequestSync() && m_Settings.backgroundBehavior != InputSettings.BackgroundBehavior.IgnoreFocus) - ResetDevice(device); + // Re-enable the device if we disabled it on focus loss. This will also issue a sync. + if (device.disabledWhileInBackground) + EnableOrDisableDevice(device, true, DeviceDisableScope.TemporaryWhilePlayerIsInBackground); + + // Try to sync. If it fails and we didn't run in the background, perform + // a reset instead. This is to cope with backends that are unable to sync but + // may still retain state which now may be outdated because the input device may + // have changed state while we weren't running. So at least make the backend flush + // its state (if any). + else if (device.enabled && !runInBackground && !device.RequestSync() && m_Settings.backgroundBehavior != InputSettings.BackgroundBehavior.IgnoreFocus) + ResetDevice(device); + } } } - + finally + { #if UNITY_EDITOR - m_CurrentUpdate = InputUpdateType.None; + if (shouldClearCurrentUpdateInFinally) + m_CurrentUpdate = InputUpdateType.None; #endif - // We set this *after* the block above as defaultUpdateType is influenced by the setting. - focusState = focus ? FocusFlags.ApplicationFocus : FocusFlags.None; + m_IsHandlingFocusChange = false; + } } /// @@ -150,8 +163,6 @@ private bool ShouldFlushEventBuffer() /// /// Determines if we should exit early from event processing without handling events. /// - /// The current event buffer - /// Whether the buffer can be flushed /// The current update type /// True if we should exit early, false otherwise. [MethodImpl(MethodImplOptions.AggressiveInlining)] diff --git a/Packages/com.unity.inputsystem/InputSystem/Runtime/InputManager.cs b/Packages/com.unity.inputsystem/InputSystem/Runtime/InputManager.cs index d396770da4..994538ecc2 100644 --- a/Packages/com.unity.inputsystem/InputSystem/Runtime/InputManager.cs +++ b/Packages/com.unity.inputsystem/InputSystem/Runtime/InputManager.cs @@ -241,7 +241,7 @@ public InputUpdateType defaultUpdateType // The solution here would be to make update calls explicitly specify the update type and no longer use this property. if (!m_RunPlayerUpdatesInEditMode && (!gameIsPlaying || !gameHasFocus)) return InputUpdateType.Editor; - #endif +#endif return m_UpdateMask.GetUpdateTypeForPlayer(); } @@ -264,27 +264,6 @@ public InputSettings.ScrollDeltaBehavior scrollDeltaBehavior } } - public FocusFlags focusState - { - get - { -#if UNITY_INPUTSYSTEM_SUPPORTS_FOCUS_EVENTS - return m_Runtime.focusState; -#else - return m_FocusState; -#endif - } - set - { -#if UNITY_INPUTSYSTEM_SUPPORTS_FOCUS_EVENTS - if (m_Runtime != null) - m_Runtime.focusState = value; -#else - m_FocusState = value; -#endif - } - } - public float pollingFrequency { get @@ -557,7 +536,17 @@ internal static void StopEditorEventPassthrough() #else true; #endif - private bool applicationHasFocus => (focusState & FocusFlags.ApplicationFocus) != FocusFlags.None; + private bool applicationHasFocus + { + get + { +#if !UNITY_INPUTSYSTEM_SUPPORTS_FOCUS_EVENTS + if (m_IsHandlingFocusChange) + return m_ApplicationHadFocus; +#endif + return m_Runtime != null ? m_Runtime.isPlayerFocused : Application.isFocused; + } + } private bool gameHasFocus => #if UNITY_EDITOR @@ -2027,11 +2016,6 @@ internal void InitializeData() // we don't know which one the user is going to use. The user // can manually turn off one of them to optimize operation. m_UpdateMask = InputUpdateType.Dynamic | InputUpdateType.Fixed; - #if !UNITY_INPUTSYSTEM_SUPPORTS_FOCUS_EVENTS - m_FocusState = Application.isFocused - ? m_FocusState | FocusFlags.ApplicationFocus - : m_FocusState & ~FocusFlags.ApplicationFocus; - #endif #if UNITY_EDITOR m_EditorIsActive = true; m_UpdateMask |= InputUpdateType.Editor; @@ -2277,9 +2261,7 @@ internal void InstallRuntime(IInputRuntime runtime) #endif m_Runtime.pollingFrequency = pollingFrequency; - focusState = m_Runtime.isPlayerFocused - ? focusState | FocusFlags.ApplicationFocus - : focusState & ~FocusFlags.ApplicationFocus; + m_Runtime.InitializeFocusState(); // We only hook NativeInputSystem.onBeforeUpdate if necessary. if (m_BeforeUpdateListeners.length > 0 || m_HaveDevicesWithStateCallbackReceivers) @@ -2451,9 +2433,14 @@ internal struct AvailableDevice private bool m_NativeBeforeUpdateHooked; private bool m_HaveDevicesWithStateCallbackReceivers; #if !UNITY_INPUTSYSTEM_SUPPORTS_FOCUS_EVENTS - private FocusFlags m_FocusState = FocusFlags.ApplicationFocus; private bool m_DiscardOutOfFocusEvents; private double m_FocusRegainedTime; + // These two bools are used for overriding applicationHasFocus which affects gameHasFocus + // and thus defaultUpdateType. See comments in defaultUpdateType. While processing the + // focus change in the OnFocusChanged method, these are used to temporarily override + // those properties. + private bool m_IsHandlingFocusChange; + private bool m_ApplicationHadFocus; #endif private InputEventStream m_InputEventStream; @@ -3938,8 +3925,7 @@ private void ProcessDeviceConfigurationEvent(InputDevice device) private unsafe void ProcessFocusEvent(InputEvent* currentEventReadPtr) { var focusEventPtr = (InputFocusEvent*)currentEventReadPtr; - FocusFlags state = focusEventPtr->focusFlags; - focusState = state; + m_Runtime.focusState = focusEventPtr->focusFlags; #if UNITY_EDITOR SyncAllDevicesWhenEditorIsActivated(); diff --git a/Packages/com.unity.inputsystem/InputSystem/Runtime/NativeInputRuntime.cs b/Packages/com.unity.inputsystem/InputSystem/Runtime/NativeInputRuntime.cs index be296bc800..7a0bdb68d0 100644 --- a/Packages/com.unity.inputsystem/InputSystem/Runtime/NativeInputRuntime.cs +++ b/Packages/com.unity.inputsystem/InputSystem/Runtime/NativeInputRuntime.cs @@ -316,11 +316,22 @@ private bool OnWantsToShutdown() return true; } + public void InitializeFocusState() + { + m_FocusState = Application.isFocused + ? m_FocusState | FocusFlags.ApplicationFocus + : m_FocusState & ~FocusFlags.ApplicationFocus; + } + #if !UNITY_INPUTSYSTEM_SUPPORTS_FOCUS_EVENTS private Action m_FocusChangedMethod; private void OnFocusChanged(bool focus) { + m_FocusState = focus + ? m_FocusState | FocusFlags.ApplicationFocus + : m_FocusState & ~FocusFlags.ApplicationFocus; + m_FocusChangedMethod(focus); } diff --git a/Packages/com.unity.inputsystem/Tests/TestFixture/InputTestRuntime.cs b/Packages/com.unity.inputsystem/Tests/TestFixture/InputTestRuntime.cs index 91fa3e4bb6..70348143e6 100644 --- a/Packages/com.unity.inputsystem/Tests/TestFixture/InputTestRuntime.cs +++ b/Packages/com.unity.inputsystem/Tests/TestFixture/InputTestRuntime.cs @@ -234,11 +234,19 @@ public unsafe long DeviceCommand(int deviceId, InputDeviceCommand* commandPtr) } } + public void InitializeFocusState() + { + // Testing should start in focus and individual tests can change this test runtime to lose focus + // by calling InvokePlayerFocusChanged via InputTestFixture.ScheduleFocusChangedEvent. + m_FocusState = FocusFlags.ApplicationFocus; + } + public void InvokePlayerFocusChanged(bool newFocusState) { m_FocusState = newFocusState ? m_FocusState | FocusFlags.ApplicationFocus : m_FocusState & ~FocusFlags.ApplicationFocus; + onPlayerFocusChanged?.Invoke(newFocusState); }