설명 없음
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

NativeInputRuntime.cs 15KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402
  1. using System;
  2. using System.Linq;
  3. using Unity.Collections.LowLevel.Unsafe;
  4. using UnityEngine.InputSystem.Utilities;
  5. using UnityEngineInternal.Input;
  6. #if UNITY_EDITOR
  7. using System.Reflection;
  8. using UnityEditor;
  9. using UnityEditorInternal;
  10. #endif
  11. // This should be the only file referencing the API at UnityEngineInternal.Input.
  12. namespace UnityEngine.InputSystem.LowLevel
  13. {
  14. /// <summary>
  15. /// Implements <see cref="IInputRuntime"/> based on <see cref="NativeInputSystem"/>.
  16. /// </summary>
  17. internal class NativeInputRuntime : IInputRuntime
  18. {
  19. public static readonly NativeInputRuntime instance = new NativeInputRuntime();
  20. public int AllocateDeviceId()
  21. {
  22. return NativeInputSystem.AllocateDeviceId();
  23. }
  24. public void Update(InputUpdateType updateType)
  25. {
  26. NativeInputSystem.Update((NativeInputUpdateType)updateType);
  27. }
  28. public unsafe void QueueEvent(InputEvent* ptr)
  29. {
  30. NativeInputSystem.QueueInputEvent((IntPtr)ptr);
  31. }
  32. [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1062:Validate arguments of public methods", MessageId = "0", Justification = "False positive.")]
  33. public unsafe long DeviceCommand(int deviceId, InputDeviceCommand* commandPtr)
  34. {
  35. if (commandPtr == null)
  36. throw new ArgumentNullException(nameof(commandPtr));
  37. return NativeInputSystem.IOCTL(deviceId, commandPtr->type, new IntPtr(commandPtr->payloadPtr), commandPtr->payloadSizeInBytes);
  38. }
  39. public unsafe InputUpdateDelegate onUpdate
  40. {
  41. get => m_OnUpdate;
  42. set
  43. {
  44. if (value != null)
  45. NativeInputSystem.onUpdate =
  46. (updateType, eventBufferPtr) =>
  47. {
  48. var buffer = new InputEventBuffer((InputEvent*)eventBufferPtr->eventBuffer,
  49. eventBufferPtr->eventCount,
  50. sizeInBytes: eventBufferPtr->sizeInBytes,
  51. capacityInBytes: eventBufferPtr->capacityInBytes);
  52. try
  53. {
  54. value((InputUpdateType)updateType, ref buffer);
  55. }
  56. catch (Exception e)
  57. {
  58. // Always report the original exception first to confuse users less about what it the actual failure.
  59. Debug.LogException(e);
  60. Debug.LogError($"{e.GetType().Name} during event processing of {updateType} update; resetting event buffer");
  61. buffer.Reset();
  62. }
  63. if (buffer.eventCount > 0)
  64. {
  65. eventBufferPtr->eventCount = buffer.eventCount;
  66. eventBufferPtr->sizeInBytes = (int)buffer.sizeInBytes;
  67. eventBufferPtr->capacityInBytes = (int)buffer.capacityInBytes;
  68. eventBufferPtr->eventBuffer =
  69. NativeArrayUnsafeUtility.GetUnsafeBufferPointerWithoutChecks(buffer.data);
  70. }
  71. else
  72. {
  73. eventBufferPtr->eventCount = 0;
  74. eventBufferPtr->sizeInBytes = 0;
  75. }
  76. };
  77. else
  78. NativeInputSystem.onUpdate = null;
  79. m_OnUpdate = value;
  80. }
  81. }
  82. public Action<InputUpdateType> onBeforeUpdate
  83. {
  84. get => m_OnBeforeUpdate;
  85. set
  86. {
  87. // This is stupid but the enum prevents us from jacking the delegate in directly.
  88. // This means we get a double dispatch here :(
  89. if (value != null)
  90. NativeInputSystem.onBeforeUpdate = updateType => value((InputUpdateType)updateType);
  91. else
  92. NativeInputSystem.onBeforeUpdate = null;
  93. m_OnBeforeUpdate = value;
  94. }
  95. }
  96. public Func<InputUpdateType, bool> onShouldRunUpdate
  97. {
  98. get => m_OnShouldRunUpdate;
  99. set
  100. {
  101. // This is stupid but the enum prevents us from jacking the delegate in directly.
  102. // This means we get a double dispatch here :(
  103. if (value != null)
  104. NativeInputSystem.onShouldRunUpdate = updateType => value((InputUpdateType)updateType);
  105. else
  106. NativeInputSystem.onShouldRunUpdate = null;
  107. m_OnShouldRunUpdate = value;
  108. }
  109. }
  110. #if UNITY_EDITOR
  111. private struct InputSystemPlayerLoopRunnerInitializationSystem {};
  112. public Action onPlayerLoopInitialization
  113. {
  114. get => m_PlayerLoopInitialization;
  115. set
  116. {
  117. // This is a hot-fix for a critical problem in input system, case 1368559, case 1367556, case 1372830
  118. // TODO move it to a proper native callback instead
  119. if (value != null)
  120. {
  121. // Inject ourselves directly to PlayerLoop.Initialization as first subsystem to run,
  122. // Use InputSystemPlayerLoopRunnerInitializationSystem as system type
  123. var playerLoop = UnityEngine.LowLevel.PlayerLoop.GetCurrentPlayerLoop();
  124. var initStepIndex = playerLoop.subSystemList.IndexOf(x => x.type == typeof(PlayerLoop.Initialization));
  125. if (initStepIndex >= 0)
  126. {
  127. var systems = playerLoop.subSystemList[initStepIndex].subSystemList;
  128. // Check if we're not already injected
  129. if (!systems.Select(x => x.type)
  130. .Contains(typeof(InputSystemPlayerLoopRunnerInitializationSystem)))
  131. {
  132. ArrayHelpers.InsertAt(ref systems, 0, new UnityEngine.LowLevel.PlayerLoopSystem
  133. {
  134. type = typeof(InputSystemPlayerLoopRunnerInitializationSystem),
  135. updateDelegate = () => m_PlayerLoopInitialization?.Invoke()
  136. });
  137. playerLoop.subSystemList[initStepIndex].subSystemList = systems;
  138. UnityEngine.LowLevel.PlayerLoop.SetPlayerLoop(playerLoop);
  139. }
  140. }
  141. }
  142. m_PlayerLoopInitialization = value;
  143. }
  144. }
  145. #endif
  146. public Action<int, string> onDeviceDiscovered
  147. {
  148. get => NativeInputSystem.onDeviceDiscovered;
  149. set => NativeInputSystem.onDeviceDiscovered = value;
  150. }
  151. public Action onShutdown
  152. {
  153. get => m_ShutdownMethod;
  154. set
  155. {
  156. if (value == null)
  157. {
  158. #if UNITY_EDITOR
  159. EditorApplication.wantsToQuit -= OnWantsToShutdown;
  160. #else
  161. Application.quitting -= OnShutdown;
  162. #endif
  163. }
  164. else if (m_ShutdownMethod == null)
  165. {
  166. #if UNITY_EDITOR
  167. EditorApplication.wantsToQuit += OnWantsToShutdown;
  168. #else
  169. Application.quitting += OnShutdown;
  170. #endif
  171. }
  172. m_ShutdownMethod = value;
  173. }
  174. }
  175. public Action<bool> onPlayerFocusChanged
  176. {
  177. get => m_FocusChangedMethod;
  178. set
  179. {
  180. if (value == null)
  181. Application.focusChanged -= OnFocusChanged;
  182. else if (m_FocusChangedMethod == null)
  183. Application.focusChanged += OnFocusChanged;
  184. m_FocusChangedMethod = value;
  185. }
  186. }
  187. public bool isPlayerFocused => Application.isFocused;
  188. public float pollingFrequency
  189. {
  190. get => m_PollingFrequency;
  191. set
  192. {
  193. m_PollingFrequency = value;
  194. NativeInputSystem.SetPollingFrequency(value);
  195. }
  196. }
  197. public double currentTime => NativeInputSystem.currentTime;
  198. ////REVIEW: this applies the offset, currentTime doesn't
  199. public double currentTimeForFixedUpdate => Time.fixedUnscaledTime + currentTimeOffsetToRealtimeSinceStartup;
  200. public double currentTimeOffsetToRealtimeSinceStartup => NativeInputSystem.currentTimeOffsetToRealtimeSinceStartup;
  201. public float unscaledGameTime => Time.unscaledTime;
  202. public bool runInBackground
  203. {
  204. get =>
  205. Application.runInBackground ||
  206. // certain platforms ignore the runInBackground flag and always run. Make sure we're
  207. // not running on one of those and set the values when running on specific platforms.
  208. m_RunInBackground;
  209. set => m_RunInBackground = value;
  210. }
  211. bool m_RunInBackground;
  212. private Action m_ShutdownMethod;
  213. private InputUpdateDelegate m_OnUpdate;
  214. private Action<InputUpdateType> m_OnBeforeUpdate;
  215. private Func<InputUpdateType, bool> m_OnShouldRunUpdate;
  216. #if UNITY_EDITOR
  217. private Action m_PlayerLoopInitialization;
  218. #endif
  219. private float m_PollingFrequency = 60.0f;
  220. private bool m_DidCallOnShutdown = false;
  221. private void OnShutdown()
  222. {
  223. m_ShutdownMethod();
  224. }
  225. private bool OnWantsToShutdown()
  226. {
  227. if (!m_DidCallOnShutdown)
  228. {
  229. // we should use `EditorApplication.quitting`, but that is too late
  230. // to send an analytics event, because Analytics is already shut down
  231. // at that point. So we use `EditorApplication.wantsToQuit`, and make sure
  232. // to only use the first time. This is currently only used for analytics,
  233. // and getting analytics before we actually shut downn in some cases is
  234. // better then never.
  235. OnShutdown();
  236. m_DidCallOnShutdown = true;
  237. }
  238. return true;
  239. }
  240. private Action<bool> m_FocusChangedMethod;
  241. private void OnFocusChanged(bool focus)
  242. {
  243. m_FocusChangedMethod(focus);
  244. }
  245. public Vector2 screenSize => new Vector2(Screen.width, Screen.height);
  246. public ScreenOrientation screenOrientation => Screen.orientation;
  247. public bool isInBatchMode => Application.isBatchMode;
  248. #if UNITY_EDITOR
  249. public bool isInPlayMode => EditorApplication.isPlaying;
  250. public bool isPaused => EditorApplication.isPaused;
  251. public bool isEditorActive => InternalEditorUtility.isApplicationActive;
  252. public Func<IntPtr, bool> onUnityRemoteMessage
  253. {
  254. set
  255. {
  256. if (m_UnityRemoteMessageHandler == value)
  257. return;
  258. if (m_UnityRemoteMessageHandler != null)
  259. {
  260. var removeMethod = GetUnityRemoteAPIMethod("RemoveMessageHandler");
  261. removeMethod?.Invoke(null, new[] { m_UnityRemoteMessageHandler });
  262. m_UnityRemoteMessageHandler = null;
  263. }
  264. if (value != null)
  265. {
  266. var addMethod = GetUnityRemoteAPIMethod("AddMessageHandler");
  267. addMethod?.Invoke(null, new[] { value });
  268. m_UnityRemoteMessageHandler = value;
  269. }
  270. }
  271. }
  272. public void SetUnityRemoteGyroEnabled(bool value)
  273. {
  274. var setMethod = GetUnityRemoteAPIMethod("SetGyroEnabled");
  275. setMethod?.Invoke(null, new object[] { value });
  276. }
  277. public void SetUnityRemoteGyroUpdateInterval(float interval)
  278. {
  279. var setMethod = GetUnityRemoteAPIMethod("SetGyroUpdateInterval");
  280. setMethod?.Invoke(null, new object[] { interval });
  281. }
  282. private MethodInfo GetUnityRemoteAPIMethod(string methodName)
  283. {
  284. var editorAssembly = typeof(EditorApplication).Assembly;
  285. var genericRemoteClass = editorAssembly.GetType("UnityEditor.Remote.GenericRemote");
  286. if (genericRemoteClass == null)
  287. return null;
  288. return genericRemoteClass.GetMethod(methodName);
  289. }
  290. private Func<IntPtr, bool> m_UnityRemoteMessageHandler;
  291. private Action<PlayModeStateChange> m_OnPlayModeChanged;
  292. private Action m_OnProjectChanged;
  293. private void OnPlayModeStateChanged(PlayModeStateChange value)
  294. {
  295. m_OnPlayModeChanged(value);
  296. }
  297. private void OnProjectChanged()
  298. {
  299. m_OnProjectChanged();
  300. }
  301. public Action<PlayModeStateChange> onPlayModeChanged
  302. {
  303. get => m_OnPlayModeChanged;
  304. set
  305. {
  306. if (value == null)
  307. EditorApplication.playModeStateChanged -= OnPlayModeStateChanged;
  308. else if (m_OnPlayModeChanged == null)
  309. EditorApplication.playModeStateChanged += OnPlayModeStateChanged;
  310. m_OnPlayModeChanged = value;
  311. }
  312. }
  313. public Action onProjectChange
  314. {
  315. get => m_OnProjectChanged;
  316. set
  317. {
  318. if (value == null)
  319. EditorApplication.projectChanged -= OnProjectChanged;
  320. else if (m_OnProjectChanged == null)
  321. EditorApplication.projectChanged += OnProjectChanged;
  322. m_OnProjectChanged = value;
  323. }
  324. }
  325. #endif // UNITY_EDITOR
  326. public void RegisterAnalyticsEvent(string name, int maxPerHour, int maxPropertiesPerEvent)
  327. {
  328. #if UNITY_ANALYTICS
  329. const string vendorKey = "unity.input";
  330. #if UNITY_EDITOR
  331. EditorAnalytics.RegisterEventWithLimit(name, maxPerHour, maxPropertiesPerEvent, vendorKey);
  332. #else
  333. Analytics.Analytics.RegisterEvent(name, maxPerHour, maxPropertiesPerEvent, vendorKey);
  334. #endif // UNITY_EDITOR
  335. #endif // UNITY_ANALYTICS
  336. }
  337. public void SendAnalyticsEvent(string name, object data)
  338. {
  339. #if UNITY_ANALYTICS
  340. #if UNITY_EDITOR
  341. EditorAnalytics.SendEventWithLimit(name, data);
  342. #else
  343. Analytics.Analytics.SendEvent(name, data);
  344. #endif // UNITY_EDITOR
  345. #endif // UNITY_ANALYTICS
  346. }
  347. }
  348. }