No Description
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.

InputTestRuntime.cs 17KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464
  1. using System;
  2. using System.Collections.Generic;
  3. using NUnit.Framework;
  4. using UnityEngine.InputSystem.LowLevel;
  5. using Unity.Collections;
  6. using Unity.Collections.LowLevel.Unsafe;
  7. using UnityEngine.InputSystem.Layouts;
  8. using UnityEngine.InputSystem.Utilities;
  9. #if UNITY_EDITOR
  10. using UnityEditor;
  11. #endif
  12. namespace UnityEngine.InputSystem
  13. {
  14. /// <summary>
  15. /// An implementation of <see cref="IInputRuntime"/> for use during tests.
  16. /// </summary>
  17. /// <remarks>
  18. /// This class is only available in the editor and in development players.
  19. ///
  20. /// The test runtime replaces the services usually supplied by <see cref="UnityEngineInternal.Input.NativeInputSystem"/>.
  21. /// </remarks>
  22. /// <seealso cref="InputTestFixture.runtime"/>
  23. internal class InputTestRuntime : IInputRuntime, IDisposable
  24. {
  25. public unsafe delegate long DeviceCommandCallback(int deviceId, InputDeviceCommand* command);
  26. ~InputTestRuntime()
  27. {
  28. Dispose();
  29. }
  30. public int AllocateDeviceId()
  31. {
  32. var result = m_NextDeviceId;
  33. ++m_NextDeviceId;
  34. return result;
  35. }
  36. public unsafe void Update(InputUpdateType type)
  37. {
  38. if (!onShouldRunUpdate.Invoke(type))
  39. return;
  40. lock (m_Lock)
  41. {
  42. if (type == InputUpdateType.Dynamic && !dontAdvanceUnscaledGameTimeNextDynamicUpdate)
  43. {
  44. unscaledGameTime += 1 / 30f;
  45. dontAdvanceUnscaledGameTimeNextDynamicUpdate = false;
  46. }
  47. if (m_NewDeviceDiscoveries != null && m_NewDeviceDiscoveries.Count > 0)
  48. {
  49. if (onDeviceDiscovered != null)
  50. foreach (var entry in m_NewDeviceDiscoveries)
  51. onDeviceDiscovered(entry.Key, entry.Value);
  52. m_NewDeviceDiscoveries.Clear();
  53. }
  54. onBeforeUpdate?.Invoke(type);
  55. // Advance time *after* onBeforeUpdate so that events generated from onBeforeUpdate
  56. // don't get bumped into the following update.
  57. if (type == InputUpdateType.Dynamic && !dontAdvanceTimeNextDynamicUpdate)
  58. {
  59. currentTime += advanceTimeEachDynamicUpdate;
  60. dontAdvanceTimeNextDynamicUpdate = false;
  61. }
  62. if (onUpdate != null)
  63. {
  64. var buffer = new InputEventBuffer(
  65. (InputEvent*)NativeArrayUnsafeUtility.GetUnsafeBufferPointerWithoutChecks(m_EventBuffer),
  66. m_EventCount, m_EventWritePosition, m_EventBuffer.Length);
  67. try
  68. {
  69. onUpdate(type, ref buffer);
  70. }
  71. catch (Exception e)
  72. {
  73. // Same order as in NativeInputRuntime
  74. Debug.LogException(e);
  75. Debug.LogError($"{e.GetType().Name} during event processing of {type} update; resetting event buffer");
  76. // Rethrow exception for test runtime to enable us to assert against it in tests.
  77. m_EventCount = 0;
  78. m_EventWritePosition = 0;
  79. throw;
  80. }
  81. m_EventCount = buffer.eventCount;
  82. m_EventWritePosition = (int)buffer.sizeInBytes;
  83. if (NativeArrayUnsafeUtility.GetUnsafeBufferPointerWithoutChecks(buffer.data) !=
  84. NativeArrayUnsafeUtility.GetUnsafeBufferPointerWithoutChecks(m_EventBuffer))
  85. m_EventBuffer = buffer.data;
  86. }
  87. else
  88. {
  89. m_EventCount = 0;
  90. m_EventWritePosition = 0;
  91. }
  92. }
  93. }
  94. public unsafe void QueueEvent(InputEvent* eventPtr)
  95. {
  96. var eventSize = eventPtr->sizeInBytes;
  97. var alignedEventSize = eventSize.AlignToMultipleOf(4);
  98. lock (m_Lock)
  99. {
  100. eventPtr->eventId = m_NextEventId;
  101. eventPtr->handled = false;
  102. ++m_NextEventId;
  103. // Enlarge buffer, if we have to.
  104. if ((m_EventWritePosition + alignedEventSize) > m_EventBuffer.Length)
  105. {
  106. var newBufferSize = m_EventBuffer.Length + Mathf.Max((int)alignedEventSize, 1024);
  107. var newBuffer = new NativeArray<byte>(newBufferSize, Allocator.Persistent);
  108. UnsafeUtility.MemCpy(newBuffer.GetUnsafePtr(), m_EventBuffer.GetUnsafePtr(), m_EventWritePosition);
  109. m_EventBuffer.Dispose();
  110. m_EventBuffer = newBuffer;
  111. }
  112. // Copy event.
  113. UnsafeUtility.MemCpy((byte*)m_EventBuffer.GetUnsafePtr() + m_EventWritePosition, eventPtr, eventSize);
  114. m_EventWritePosition += (int)alignedEventSize;
  115. ++m_EventCount;
  116. }
  117. }
  118. public unsafe void SetCanRunInBackground(int deviceId)
  119. {
  120. SetDeviceCommandCallback(deviceId,
  121. (id, command) =>
  122. {
  123. if (command->type == QueryCanRunInBackground.Type)
  124. {
  125. ((QueryCanRunInBackground*)command)->canRunInBackground = true;
  126. return InputDeviceCommand.GenericSuccess;
  127. }
  128. return InputDeviceCommand.GenericFailure;
  129. });
  130. }
  131. public void SetDeviceCommandCallback(InputDevice device, DeviceCommandCallback callback)
  132. {
  133. SetDeviceCommandCallback(device.deviceId, callback);
  134. }
  135. public void SetDeviceCommandCallback(int deviceId, DeviceCommandCallback callback)
  136. {
  137. lock (m_Lock)
  138. {
  139. if (m_DeviceCommandCallbacks == null)
  140. m_DeviceCommandCallbacks = new List<KeyValuePair<int, DeviceCommandCallback>>();
  141. else
  142. {
  143. for (var i = 0; i < m_DeviceCommandCallbacks.Count; ++i)
  144. {
  145. if (m_DeviceCommandCallbacks[i].Key == deviceId)
  146. {
  147. m_DeviceCommandCallbacks[i] = new KeyValuePair<int, DeviceCommandCallback>(deviceId, callback);
  148. return;
  149. }
  150. }
  151. }
  152. m_DeviceCommandCallbacks.Add(new KeyValuePair<int, DeviceCommandCallback>(deviceId, callback));
  153. }
  154. }
  155. public void SetDeviceCommandCallback<TCommand>(int deviceId, TCommand result)
  156. where TCommand : struct, IInputDeviceCommandInfo
  157. {
  158. bool? receivedCommand = null;
  159. unsafe
  160. {
  161. SetDeviceCommandCallback(deviceId,
  162. (id, commandPtr) =>
  163. {
  164. if (commandPtr->type == result.typeStatic)
  165. {
  166. Assert.That(receivedCommand.HasValue, Is.False);
  167. receivedCommand = true;
  168. UnsafeUtility.MemCpy(commandPtr, UnsafeUtility.AddressOf(ref result),
  169. UnsafeUtility.SizeOf<TCommand>());
  170. return InputDeviceCommand.GenericSuccess;
  171. }
  172. return InputDeviceCommand.GenericFailure;
  173. });
  174. }
  175. }
  176. public unsafe long DeviceCommand(int deviceId, InputDeviceCommand* commandPtr)
  177. {
  178. lock (m_Lock)
  179. {
  180. if (commandPtr->type == QueryPairedUserAccountCommand.Type)
  181. {
  182. foreach (var pairing in userAccountPairings)
  183. {
  184. if (pairing.deviceId != deviceId)
  185. continue;
  186. var queryPairedUser = (QueryPairedUserAccountCommand*)commandPtr;
  187. queryPairedUser->handle = pairing.userHandle;
  188. queryPairedUser->name = pairing.userName;
  189. queryPairedUser->id = pairing.userId;
  190. return (long)QueryPairedUserAccountCommand.Result.DevicePairedToUserAccount;
  191. }
  192. }
  193. var result = InputDeviceCommand.GenericFailure;
  194. if (m_DeviceCommandCallbacks != null)
  195. foreach (var entry in m_DeviceCommandCallbacks)
  196. {
  197. if (entry.Key == deviceId)
  198. {
  199. result = entry.Value(deviceId, commandPtr);
  200. if (result >= 0)
  201. return result;
  202. }
  203. }
  204. return result;
  205. }
  206. }
  207. public void InvokePlayerFocusChanged(bool newFocusState)
  208. {
  209. m_HasFocus = newFocusState;
  210. onPlayerFocusChanged?.Invoke(newFocusState);
  211. }
  212. public void PlayerFocusLost()
  213. {
  214. InvokePlayerFocusChanged(false);
  215. }
  216. public void PlayerFocusGained()
  217. {
  218. InvokePlayerFocusChanged(true);
  219. }
  220. public int ReportNewInputDevice(string deviceDescriptor, int deviceId = InputDevice.InvalidDeviceId)
  221. {
  222. lock (m_Lock)
  223. {
  224. if (deviceId == InputDevice.InvalidDeviceId)
  225. deviceId = AllocateDeviceId();
  226. if (m_NewDeviceDiscoveries == null)
  227. m_NewDeviceDiscoveries = new List<KeyValuePair<int, string>>();
  228. m_NewDeviceDiscoveries.Add(new KeyValuePair<int, string>(deviceId, deviceDescriptor));
  229. return deviceId;
  230. }
  231. }
  232. public int ReportNewInputDevice(InputDeviceDescription description, int deviceId = InputDevice.InvalidDeviceId,
  233. ulong userHandle = 0, string userName = null, string userId = null)
  234. {
  235. deviceId = ReportNewInputDevice(description.ToJson(), deviceId);
  236. // If we have user information, automatically set up
  237. if (userHandle != 0)
  238. AssociateInputDeviceWithUser(deviceId, userHandle, userName, userId);
  239. return deviceId;
  240. }
  241. public int ReportNewInputDevice<TDevice>(int deviceId = InputDevice.InvalidDeviceId,
  242. ulong userHandle = 0, string userName = null, string userId = null)
  243. where TDevice : InputDevice
  244. {
  245. return ReportNewInputDevice(
  246. new InputDeviceDescription {deviceClass = typeof(TDevice).Name, interfaceName = "Test"}, deviceId,
  247. userHandle, userName, userId);
  248. }
  249. public unsafe void ReportInputDeviceRemoved(int deviceId)
  250. {
  251. var removeEvent = DeviceRemoveEvent.Create(deviceId);
  252. var removeEventPtr = UnsafeUtility.AddressOf(ref removeEvent);
  253. QueueEvent((InputEvent*)removeEventPtr);
  254. }
  255. public void ReportInputDeviceRemoved(InputDevice device)
  256. {
  257. if (device == null)
  258. throw new ArgumentNullException(nameof(device));
  259. ReportInputDeviceRemoved(device.deviceId);
  260. }
  261. public void AssociateInputDeviceWithUser(int deviceId, ulong userHandle, string userName = null, string userId = null)
  262. {
  263. var existingIndex = -1;
  264. for (var i = 0; i < userAccountPairings.Count; ++i)
  265. if (userAccountPairings[i].deviceId == deviceId)
  266. {
  267. existingIndex = i;
  268. break;
  269. }
  270. if (userHandle == 0)
  271. {
  272. if (existingIndex != -1)
  273. userAccountPairings.RemoveAt(existingIndex);
  274. }
  275. else if (existingIndex != -1)
  276. {
  277. userAccountPairings[existingIndex] =
  278. new PairedUser
  279. {
  280. deviceId = deviceId,
  281. userHandle = userHandle,
  282. userName = userName,
  283. userId = userId,
  284. };
  285. }
  286. else
  287. {
  288. userAccountPairings.Add(
  289. new PairedUser
  290. {
  291. deviceId = deviceId,
  292. userHandle = userHandle,
  293. userName = userName,
  294. userId = userId,
  295. });
  296. }
  297. }
  298. public void AssociateInputDeviceWithUser(InputDevice device, ulong userHandle, string userName = null, string userId = null)
  299. {
  300. AssociateInputDeviceWithUser(device.deviceId, userHandle, userName, userId);
  301. }
  302. public struct PairedUser
  303. {
  304. public int deviceId;
  305. public ulong userHandle;
  306. public string userName;
  307. public string userId;
  308. }
  309. public InputUpdateDelegate onUpdate { get; set; }
  310. public Action<InputUpdateType> onBeforeUpdate { get; set; }
  311. public Func<InputUpdateType, bool> onShouldRunUpdate { get; set; }
  312. #if UNITY_EDITOR
  313. public Action onPlayerLoopInitialization { get; set; }
  314. #endif
  315. public Action<int, string> onDeviceDiscovered { get; set; }
  316. public Action onShutdown { get; set; }
  317. public Action<bool> onPlayerFocusChanged { get; set; }
  318. public bool isPlayerFocused => m_HasFocus;
  319. public float pollingFrequency { get; set; }
  320. public double currentTime { get; set; }
  321. public double currentTimeForFixedUpdate { get; set; }
  322. public float unscaledGameTime { get; set; } = 1;
  323. public bool dontAdvanceUnscaledGameTimeNextDynamicUpdate { get; set; }
  324. public double advanceTimeEachDynamicUpdate { get; set; } = 1.0 / 60;
  325. public bool dontAdvanceTimeNextDynamicUpdate { get; set; }
  326. public bool runInBackground { get; set; } = false;
  327. public Vector2 screenSize { get; set; } = new Vector2(1024, 768);
  328. public ScreenOrientation screenOrientation { set; get; } = ScreenOrientation.Portrait;
  329. public List<PairedUser> userAccountPairings
  330. {
  331. get
  332. {
  333. if (m_UserPairings == null)
  334. m_UserPairings = new List<PairedUser>();
  335. return m_UserPairings;
  336. }
  337. }
  338. public void Dispose()
  339. {
  340. m_EventBuffer.Dispose();
  341. GC.SuppressFinalize(this);
  342. }
  343. public double currentTimeOffsetToRealtimeSinceStartup
  344. {
  345. get => m_CurrentTimeOffsetToRealtimeSinceStartup;
  346. set
  347. {
  348. m_CurrentTimeOffsetToRealtimeSinceStartup = value;
  349. InputRuntime.s_CurrentTimeOffsetToRealtimeSinceStartup = value;
  350. }
  351. }
  352. public bool isInBatchMode { get; set; }
  353. #if UNITY_EDITOR
  354. public bool isInPlayMode { get; set; } = true;
  355. public bool isPaused { get; set; }
  356. public bool isEditorActive { get; set; } = true;
  357. public Func<IntPtr, bool> onUnityRemoteMessage
  358. {
  359. get => m_UnityRemoteMessageHandler;
  360. set => m_UnityRemoteMessageHandler = value;
  361. }
  362. public bool? unityRemoteGyroEnabled;
  363. public float? unityRemoteGyroUpdateInterval;
  364. public void SetUnityRemoteGyroEnabled(bool value)
  365. {
  366. unityRemoteGyroEnabled = value;
  367. }
  368. public void SetUnityRemoteGyroUpdateInterval(float interval)
  369. {
  370. unityRemoteGyroUpdateInterval = interval;
  371. }
  372. public Action<PlayModeStateChange> onPlayModeChanged { get; set; }
  373. public Action onProjectChange { get; set; }
  374. #endif
  375. public int eventCount => m_EventCount;
  376. internal const int kDefaultEventBufferSize = 1024 * 512;
  377. private bool m_HasFocus = true;
  378. private int m_NextDeviceId = 1;
  379. private int m_NextEventId = 1;
  380. internal int m_EventCount;
  381. private int m_EventWritePosition;
  382. private NativeArray<byte> m_EventBuffer = new NativeArray<byte>(kDefaultEventBufferSize, Allocator.Persistent);
  383. private List<PairedUser> m_UserPairings;
  384. private List<KeyValuePair<int, string>> m_NewDeviceDiscoveries;
  385. private List<KeyValuePair<int, DeviceCommandCallback>> m_DeviceCommandCallbacks;
  386. private object m_Lock = new object();
  387. private double m_CurrentTimeOffsetToRealtimeSinceStartup;
  388. private Func<IntPtr, bool> m_UnityRemoteMessageHandler;
  389. #if UNITY_ANALYTICS || UNITY_EDITOR
  390. public Action<string, int, int> onRegisterAnalyticsEvent { get; set; }
  391. public Action<string, object> onSendAnalyticsEvent { get; set; }
  392. public void RegisterAnalyticsEvent(string name, int maxPerHour, int maxPropertiesPerEvent)
  393. {
  394. onRegisterAnalyticsEvent?.Invoke(name, maxPerHour, maxPropertiesPerEvent);
  395. }
  396. public void SendAnalyticsEvent(string name, object data)
  397. {
  398. onSendAnalyticsEvent?.Invoke(name, data);
  399. }
  400. #endif // UNITY_ANALYTICS || UNITY_EDITOR
  401. }
  402. }