123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402 |
- 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
-
- // This should be the only file referencing the API at UnityEngineInternal.Input.
-
- namespace UnityEngine.InputSystem.LowLevel
- {
- /// <summary>
- /// Implements <see cref="IInputRuntime"/> based on <see cref="NativeInputSystem"/>.
- /// </summary>
- 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)
- {
- // Always report the original exception first to confuse users less about what it the actual failure.
- 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
- {
- // This is stupid but the enum prevents us from jacking the delegate in directly.
- // This means we get a double dispatch here :(
- 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
- {
- // This is stupid but the enum prevents us from jacking the delegate in directly.
- // This means we get a double dispatch here :(
- 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
- {
- // This is a hot-fix for a critical problem in input system, case 1368559, case 1367556, case 1372830
- // TODO move it to a proper native callback instead
- if (value != null)
- {
- // Inject ourselves directly to PlayerLoop.Initialization as first subsystem to run,
- // Use InputSystemPlayerLoopRunnerInitializationSystem as system type
- 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;
-
- // Check if we're not already injected
- 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;
-
- ////REVIEW: this applies the offset, currentTime doesn't
- public double currentTimeForFixedUpdate => Time.fixedUnscaledTime + currentTimeOffsetToRealtimeSinceStartup;
-
- public double currentTimeOffsetToRealtimeSinceStartup => NativeInputSystem.currentTimeOffsetToRealtimeSinceStartup;
- public float unscaledGameTime => Time.unscaledTime;
-
- public bool runInBackground
- {
- get =>
- Application.runInBackground ||
- // certain platforms ignore the runInBackground flag and always run. Make sure we're
- // not running on one of those and set the values when running on specific platforms.
- 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)
- {
- // we should use `EditorApplication.quitting`, but that is too late
- // to send an analytics event, because Analytics is already shut down
- // at that point. So we use `EditorApplication.wantsToQuit`, and make sure
- // to only use the first time. This is currently only used for analytics,
- // and getting analytics before we actually shut downn in some cases is
- // better then never.
-
- 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
- }
- }
- }
|