| using System; |
| using System.Linq; |
| using Unity.Collections.LowLevel.Unsafe; |
| using UnityEngine.InputSystem.Utilities; |
| using UnityEngineInternal.Input; |
|
|
| #if UNITY_EDITOR |
| using System.Reflection; |
| using UnityEditor; |
| using UnityEditorInternal; |
|
|
| #endif |
|
|
| |
|
|
| namespace UnityEngine.InputSystem.LowLevel |
| { |
| |
| |
| |
| internal class NativeInputRuntime : IInputRuntime |
| { |
| public static readonly NativeInputRuntime instance = new NativeInputRuntime(); |
|
|
| public int AllocateDeviceId() |
| { |
| return NativeInputSystem.AllocateDeviceId(); |
| } |
|
|
| public void Update(InputUpdateType updateType) |
| { |
| NativeInputSystem.Update((NativeInputUpdateType)updateType); |
| } |
|
|
| public unsafe void QueueEvent(InputEvent* ptr) |
| { |
| NativeInputSystem.QueueInputEvent((IntPtr)ptr); |
| } |
|
|
| [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1062:Validate arguments of public methods", MessageId = "0", Justification = "False positive.")] |
| public unsafe long DeviceCommand(int deviceId, InputDeviceCommand* commandPtr) |
| { |
| if (commandPtr == null) |
| throw new ArgumentNullException(nameof(commandPtr)); |
|
|
| return NativeInputSystem.IOCTL(deviceId, commandPtr->type, new IntPtr(commandPtr->payloadPtr), commandPtr->payloadSizeInBytes); |
| } |
|
|
| public unsafe InputUpdateDelegate onUpdate |
| { |
| get => m_OnUpdate; |
| set |
| { |
| if (value != null) |
| NativeInputSystem.onUpdate = |
| (updateType, eventBufferPtr) => |
| { |
| var buffer = new InputEventBuffer((InputEvent*)eventBufferPtr->eventBuffer, |
| eventBufferPtr->eventCount, |
| sizeInBytes: eventBufferPtr->sizeInBytes, |
| capacityInBytes: eventBufferPtr->capacityInBytes); |
|
|
| try |
| { |
| value((InputUpdateType)updateType, ref buffer); |
| } |
| catch (Exception e) |
| { |
| |
| Debug.LogException(e); |
| Debug.LogError($"{e.GetType().Name} during event processing of {updateType} update; resetting event buffer"); |
| buffer.Reset(); |
| } |
|
|
| if (buffer.eventCount > 0) |
| { |
| eventBufferPtr->eventCount = buffer.eventCount; |
| eventBufferPtr->sizeInBytes = (int)buffer.sizeInBytes; |
| eventBufferPtr->capacityInBytes = (int)buffer.capacityInBytes; |
| eventBufferPtr->eventBuffer = |
| NativeArrayUnsafeUtility.GetUnsafeBufferPointerWithoutChecks(buffer.data); |
| } |
| else |
| { |
| eventBufferPtr->eventCount = 0; |
| eventBufferPtr->sizeInBytes = 0; |
| } |
| }; |
| else |
| NativeInputSystem.onUpdate = null; |
| m_OnUpdate = value; |
| } |
| } |
|
|
| public Action<InputUpdateType> onBeforeUpdate |
| { |
| get => m_OnBeforeUpdate; |
| set |
| { |
| |
| |
| if (value != null) |
| NativeInputSystem.onBeforeUpdate = updateType => value((InputUpdateType)updateType); |
| else |
| NativeInputSystem.onBeforeUpdate = null; |
| m_OnBeforeUpdate = value; |
| } |
| } |
|
|
| public Func<InputUpdateType, bool> onShouldRunUpdate |
| { |
| get => m_OnShouldRunUpdate; |
| set |
| { |
| |
| |
| if (value != null) |
| NativeInputSystem.onShouldRunUpdate = updateType => value((InputUpdateType)updateType); |
| else |
| NativeInputSystem.onShouldRunUpdate = null; |
| m_OnShouldRunUpdate = value; |
| } |
| } |
|
|
| #if UNITY_EDITOR |
| private struct InputSystemPlayerLoopRunnerInitializationSystem {}; |
| public Action onPlayerLoopInitialization |
| { |
| get => m_PlayerLoopInitialization; |
| set |
| { |
| |
| |
| if (value != null) |
| { |
| |
| |
| var playerLoop = UnityEngine.LowLevel.PlayerLoop.GetCurrentPlayerLoop(); |
| var initStepIndex = playerLoop.subSystemList.IndexOf(x => x.type == typeof(PlayerLoop.Initialization)); |
| if (initStepIndex >= 0) |
| { |
| var systems = playerLoop.subSystemList[initStepIndex].subSystemList; |
|
|
| |
| if (!systems.Select(x => x.type) |
| .Contains(typeof(InputSystemPlayerLoopRunnerInitializationSystem))) |
| { |
| ArrayHelpers.InsertAt(ref systems, 0, new UnityEngine.LowLevel.PlayerLoopSystem |
| { |
| type = typeof(InputSystemPlayerLoopRunnerInitializationSystem), |
| updateDelegate = () => m_PlayerLoopInitialization?.Invoke() |
| }); |
|
|
| playerLoop.subSystemList[initStepIndex].subSystemList = systems; |
| UnityEngine.LowLevel.PlayerLoop.SetPlayerLoop(playerLoop); |
| } |
| } |
| } |
|
|
| m_PlayerLoopInitialization = value; |
| } |
| } |
| #endif |
|
|
| public Action<int, string> onDeviceDiscovered |
| { |
| get => NativeInputSystem.onDeviceDiscovered; |
| set => NativeInputSystem.onDeviceDiscovered = value; |
| } |
|
|
| public Action onShutdown |
| { |
| get => m_ShutdownMethod; |
| set |
| { |
| if (value == null) |
| { |
| #if UNITY_EDITOR |
| EditorApplication.wantsToQuit -= OnWantsToShutdown; |
| #else |
| Application.quitting -= OnShutdown; |
| #endif |
| } |
| else if (m_ShutdownMethod == null) |
| { |
| #if UNITY_EDITOR |
| EditorApplication.wantsToQuit += OnWantsToShutdown; |
| #else |
| Application.quitting += OnShutdown; |
| #endif |
| } |
|
|
| m_ShutdownMethod = value; |
| } |
| } |
|
|
| public Action<bool> onPlayerFocusChanged |
| { |
| get => m_FocusChangedMethod; |
| set |
| { |
| if (value == null) |
| Application.focusChanged -= OnFocusChanged; |
| else if (m_FocusChangedMethod == null) |
| Application.focusChanged += OnFocusChanged; |
| m_FocusChangedMethod = value; |
| } |
| } |
|
|
| public bool isPlayerFocused => Application.isFocused; |
|
|
| public float pollingFrequency |
| { |
| get => m_PollingFrequency; |
| set |
| { |
| m_PollingFrequency = value; |
| NativeInputSystem.SetPollingFrequency(value); |
| } |
| } |
|
|
| public double currentTime => NativeInputSystem.currentTime; |
|
|
| |
| public double currentTimeForFixedUpdate => Time.fixedUnscaledTime + currentTimeOffsetToRealtimeSinceStartup; |
|
|
| public double currentTimeOffsetToRealtimeSinceStartup => NativeInputSystem.currentTimeOffsetToRealtimeSinceStartup; |
| public float unscaledGameTime => Time.unscaledTime; |
|
|
| public bool runInBackground |
| { |
| get => |
| Application.runInBackground || |
| |
| |
| m_RunInBackground; |
| set => m_RunInBackground = value; |
| } |
|
|
| bool m_RunInBackground; |
|
|
| private Action m_ShutdownMethod; |
| private InputUpdateDelegate m_OnUpdate; |
| private Action<InputUpdateType> m_OnBeforeUpdate; |
| private Func<InputUpdateType, bool> m_OnShouldRunUpdate; |
| #if UNITY_EDITOR |
| private Action m_PlayerLoopInitialization; |
| #endif |
| private float m_PollingFrequency = 60.0f; |
| private bool m_DidCallOnShutdown = false; |
| private void OnShutdown() |
| { |
| m_ShutdownMethod(); |
| } |
|
|
| private bool OnWantsToShutdown() |
| { |
| if (!m_DidCallOnShutdown) |
| { |
| |
| |
| |
| |
| |
| |
|
|
| OnShutdown(); |
| m_DidCallOnShutdown = true; |
| } |
|
|
| return true; |
| } |
|
|
| private Action<bool> m_FocusChangedMethod; |
|
|
| private void OnFocusChanged(bool focus) |
| { |
| m_FocusChangedMethod(focus); |
| } |
|
|
| public Vector2 screenSize => new Vector2(Screen.width, Screen.height); |
| public ScreenOrientation screenOrientation => Screen.orientation; |
|
|
| public bool isInBatchMode => Application.isBatchMode; |
|
|
| #if UNITY_EDITOR |
|
|
| public bool isInPlayMode => EditorApplication.isPlaying; |
| public bool isPaused => EditorApplication.isPaused; |
| public bool isEditorActive => InternalEditorUtility.isApplicationActive; |
|
|
| public Func<IntPtr, bool> onUnityRemoteMessage |
| { |
| set |
| { |
| if (m_UnityRemoteMessageHandler == value) |
| return; |
|
|
| if (m_UnityRemoteMessageHandler != null) |
| { |
| var removeMethod = GetUnityRemoteAPIMethod("RemoveMessageHandler"); |
| removeMethod?.Invoke(null, new[] { m_UnityRemoteMessageHandler }); |
| m_UnityRemoteMessageHandler = null; |
| } |
|
|
| if (value != null) |
| { |
| var addMethod = GetUnityRemoteAPIMethod("AddMessageHandler"); |
| addMethod?.Invoke(null, new[] { value }); |
| m_UnityRemoteMessageHandler = value; |
| } |
| } |
| } |
|
|
| public void SetUnityRemoteGyroEnabled(bool value) |
| { |
| var setMethod = GetUnityRemoteAPIMethod("SetGyroEnabled"); |
| setMethod?.Invoke(null, new object[] { value }); |
| } |
|
|
| public void SetUnityRemoteGyroUpdateInterval(float interval) |
| { |
| var setMethod = GetUnityRemoteAPIMethod("SetGyroUpdateInterval"); |
| setMethod?.Invoke(null, new object[] { interval }); |
| } |
|
|
| private MethodInfo GetUnityRemoteAPIMethod(string methodName) |
| { |
| var editorAssembly = typeof(EditorApplication).Assembly; |
| var genericRemoteClass = editorAssembly.GetType("UnityEditor.Remote.GenericRemote"); |
| if (genericRemoteClass == null) |
| return null; |
|
|
| return genericRemoteClass.GetMethod(methodName); |
| } |
|
|
| private Func<IntPtr, bool> m_UnityRemoteMessageHandler; |
| private Action<PlayModeStateChange> m_OnPlayModeChanged; |
| private Action m_OnProjectChanged; |
|
|
| private void OnPlayModeStateChanged(PlayModeStateChange value) |
| { |
| m_OnPlayModeChanged(value); |
| } |
|
|
| private void OnProjectChanged() |
| { |
| m_OnProjectChanged(); |
| } |
|
|
| public Action<PlayModeStateChange> onPlayModeChanged |
| { |
| get => m_OnPlayModeChanged; |
| set |
| { |
| if (value == null) |
| EditorApplication.playModeStateChanged -= OnPlayModeStateChanged; |
| else if (m_OnPlayModeChanged == null) |
| EditorApplication.playModeStateChanged += OnPlayModeStateChanged; |
| m_OnPlayModeChanged = value; |
| } |
| } |
|
|
| public Action onProjectChange |
| { |
| get => m_OnProjectChanged; |
| set |
| { |
| if (value == null) |
| EditorApplication.projectChanged -= OnProjectChanged; |
| else if (m_OnProjectChanged == null) |
| EditorApplication.projectChanged += OnProjectChanged; |
| m_OnProjectChanged = value; |
| } |
| } |
|
|
| #endif // UNITY_EDITOR |
|
|
| public void RegisterAnalyticsEvent(string name, int maxPerHour, int maxPropertiesPerEvent) |
| { |
| #if UNITY_ANALYTICS |
| const string vendorKey = "unity.input"; |
| #if UNITY_EDITOR |
| EditorAnalytics.RegisterEventWithLimit(name, maxPerHour, maxPropertiesPerEvent, vendorKey); |
| #else |
| Analytics.Analytics.RegisterEvent(name, maxPerHour, maxPropertiesPerEvent, vendorKey); |
| #endif // UNITY_EDITOR |
| #endif // UNITY_ANALYTICS |
| } |
|
|
| public void SendAnalyticsEvent(string name, object data) |
| { |
| #if UNITY_ANALYTICS |
| #if UNITY_EDITOR |
| EditorAnalytics.SendEventWithLimit(name, data); |
| #else |
| Analytics.Analytics.SendEvent(name, data); |
| #endif // UNITY_EDITOR |
| #endif // UNITY_ANALYTICS |
| } |
| } |
| } |
|
|