Keine Beschreibung
Du kannst nicht mehr als 25 Themen auswählen Themen müssen mit entweder einem Buchstaben oder einer Ziffer beginnen. Sie können Bindestriche („-“) enthalten und bis zu 35 Zeichen lang sein.

InputTestFixture.cs 42KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Linq;
  4. using System.Reflection;
  5. using UnityEngine.InputSystem.Controls;
  6. using NUnit.Framework;
  7. using NUnit.Framework.Constraints;
  8. using NUnit.Framework.Internal;
  9. using Unity.Collections;
  10. using UnityEngine.InputSystem.LowLevel;
  11. using UnityEngine.InputSystem.Utilities;
  12. using UnityEngine.TestTools;
  13. using UnityEngine.TestTools.Utils;
  14. #if UNITY_EDITOR
  15. using UnityEditor;
  16. using UnityEngine.InputSystem.Editor;
  17. #endif
  18. ////TODO: must allow running UnityTests which means we have to be able to get per-frame updates yet not receive input from native
  19. ////TODO: when running tests in players, make sure that remoting is turned off
  20. ////REVIEW: always enable event diagnostics in InputTestFixture?
  21. namespace UnityEngine.InputSystem
  22. {
  23. /// <summary>
  24. /// A test fixture for writing tests that use the input system. Can be derived from
  25. /// or simply instantiated from another test fixture.
  26. /// </summary>
  27. /// <remarks>
  28. /// The fixture will put the input system into a known state where it has only the
  29. /// built-in set of basic layouts and no devices. The state of the system before
  30. /// starting a test is recorded and restored when the test finishes.
  31. ///
  32. /// <example>
  33. /// <code>
  34. /// public class MyInputTests : InputTestFixture
  35. /// {
  36. /// public override void Setup()
  37. /// {
  38. /// base.Setup();
  39. ///
  40. /// InputSystem.RegisterLayout&lt;MyDevice&gt;();
  41. /// }
  42. ///
  43. /// [Test]
  44. /// public void CanCreateMyDevice()
  45. /// {
  46. /// InputSystem.AddDevice&lt;MyDevice&gt;();
  47. /// Assert.That(InputSystem.devices, Has.Exactly(1).TypeOf&lt;MyDevice&gt;());
  48. /// }
  49. /// }
  50. /// </code>
  51. /// </example>
  52. ///
  53. /// The test fixture will also sever the tie of the input system to the Unity runtime.
  54. /// This means that while the test fixture is active, the input system will not receive
  55. /// input and device discovery or removal notifications from platform code. This ensures
  56. /// that while the test is running, input that may be generated on the machine running
  57. /// the test will not infer with it.
  58. /// </remarks>
  59. public class InputTestFixture
  60. {
  61. /// <summary>
  62. /// Put <see cref="InputSystem"/> into a known state where it only has a basic set of
  63. /// layouts and does not have any input devices.
  64. /// </summary>
  65. /// <remarks>
  66. /// If you derive your own test fixture directly from InputTestFixture, this
  67. /// method will automatically be called. If you embed InputTestFixture into
  68. /// your fixture, you have to explicitly call this method yourself.
  69. /// </remarks>
  70. /// <seealso cref="TearDown"/>
  71. [SetUp]
  72. public virtual void Setup()
  73. {
  74. try
  75. {
  76. // Apparently, NUnit is reusing instances :(
  77. m_KeyInfos = default;
  78. m_IsUnityTest = default;
  79. m_CurrentTest = default;
  80. // Disable input debugger so we don't waste time responding to all the
  81. // input system activity from the tests.
  82. #if UNITY_EDITOR
  83. InputDebuggerWindow.Disable();
  84. #endif
  85. runtime = new InputTestRuntime();
  86. // Push current input system state on stack.
  87. #if DEVELOPMENT_BUILD || UNITY_EDITOR
  88. InputSystem.SaveAndReset(enableRemoting: false, runtime: runtime);
  89. #endif
  90. // Override the editor messing with logic like canRunInBackground and focus and
  91. // make it behave like in the player.
  92. #if UNITY_EDITOR
  93. InputSystem.settings.editorInputBehaviorInPlayMode = InputSettings.EditorInputBehaviorInPlayMode.AllDeviceInputAlwaysGoesToGameView;
  94. #endif
  95. // For a [UnityTest] play mode test, we don't want editor updates interfering with the test,
  96. // so turn them off.
  97. #if UNITY_EDITOR
  98. if (Application.isPlaying && IsUnityTest())
  99. InputSystem.s_Manager.m_UpdateMask &= ~InputUpdateType.Editor;
  100. #endif
  101. // We use native collections in a couple places. We when leak them, we want to know where exactly
  102. // the allocation came from so enable full leak detection in tests.
  103. NativeLeakDetection.Mode = NativeLeakDetectionMode.EnabledWithStackTrace;
  104. // For [UnityTest]s, we need to process input in sync with the player loop. However, InputTestRuntime
  105. // is divorced from the player loop by virtue of not being tied into NativeInputSystem. Listen
  106. // for NativeInputSystem.Update here and trigger input processing in our isolated InputSystem.
  107. // This is irrelevant for normal [Test]s but for [UnityTest]s that run over several frames, it's crucial.
  108. // NOTE: We're severing the tie the previous InputManager had to NativeInputRuntime here. This means that
  109. // device removal events that happen to occur while tests are running will get lost.
  110. NativeInputRuntime.instance.onUpdate =
  111. (InputUpdateType updateType, ref InputEventBuffer buffer) =>
  112. {
  113. if (InputSystem.s_Manager.ShouldRunUpdate(updateType))
  114. InputSystem.Update(updateType);
  115. // We ignore any input coming from native.
  116. buffer.Reset();
  117. };
  118. NativeInputRuntime.instance.onShouldRunUpdate =
  119. updateType => true;
  120. #if UNITY_EDITOR
  121. m_OnPlayModeStateChange = OnPlayModeStateChange;
  122. EditorApplication.playModeStateChanged += m_OnPlayModeStateChange;
  123. #endif
  124. // Always want to merge by default
  125. InputSystem.settings.disableRedundantEventsMerging = false;
  126. // Turn on all optimizations and checks
  127. InputSystem.settings.SetInternalFeatureFlag(InputFeatureNames.kUseOptimizedControls, true);
  128. InputSystem.settings.SetInternalFeatureFlag(InputFeatureNames.kUseReadValueCaching, true);
  129. InputSystem.settings.SetInternalFeatureFlag(InputFeatureNames.kParanoidReadValueCachingChecks, true);
  130. #if UNITY_EDITOR
  131. // Default mock dialogs to avoid unexpected cancellation of standard flows
  132. Dialog.InputActionAsset.SetSaveChanges((_) => Dialog.Result.Discard);
  133. Dialog.InputActionAsset.SetDiscardUnsavedChanges((_) => Dialog.Result.Discard);
  134. Dialog.InputActionAsset.SetCreateAndOverwriteExistingAsset((_) => Dialog.Result.Discard);
  135. Dialog.ControlScheme.SetDeleteControlScheme((_) => Dialog.Result.Delete);
  136. #endif
  137. }
  138. catch (Exception exception)
  139. {
  140. Debug.LogError("Failed to set up input system for test " + TestContext.CurrentContext.Test.Name);
  141. Debug.LogException(exception);
  142. throw;
  143. }
  144. m_Initialized = true;
  145. if (InputSystem.devices.Count > 0)
  146. Assert.Fail("Input system should not have devices after reset");
  147. }
  148. /// <summary>
  149. /// Restore the state of the input system it had when the test was started.
  150. /// </summary>
  151. /// <seealso cref="Setup"/>
  152. [TearDown]
  153. public virtual void TearDown()
  154. {
  155. if (!m_Initialized)
  156. return;
  157. try
  158. {
  159. #if DEVELOPMENT_BUILD || UNITY_EDITOR
  160. InputSystem.Restore();
  161. #endif
  162. runtime.Dispose();
  163. // Unhook from play mode state changes.
  164. #if UNITY_EDITOR
  165. if (m_OnPlayModeStateChange != null)
  166. EditorApplication.playModeStateChanged -= m_OnPlayModeStateChange;
  167. #endif
  168. // Re-enable input debugger.
  169. #if UNITY_EDITOR
  170. InputDebuggerWindow.Enable();
  171. #endif
  172. #if UNITY_EDITOR
  173. // Re-enable dialogs.
  174. Dialog.InputActionAsset.SetSaveChanges(null);
  175. Dialog.InputActionAsset.SetDiscardUnsavedChanges(null);
  176. Dialog.InputActionAsset.SetCreateAndOverwriteExistingAsset(null);
  177. Dialog.ControlScheme.SetDeleteControlScheme(null);
  178. #endif
  179. }
  180. catch (Exception exception)
  181. {
  182. Debug.LogError("Failed to shut down and restore input system after test " + TestContext.CurrentContext.Test.Name);
  183. Debug.LogException(exception);
  184. throw;
  185. }
  186. m_Initialized = false;
  187. }
  188. private bool? m_IsUnityTest;
  189. private Test m_CurrentTest;
  190. // True if the current test is a [UnityTest].
  191. private bool IsUnityTest()
  192. {
  193. // We cache this value so that any call after the first in a test no
  194. // longer allocates GC memory. Otherwise we'll run into trouble with
  195. // DoesNotAllocate tests.
  196. var test = TestContext.CurrentTestExecutionContext.CurrentTest;
  197. if (m_IsUnityTest.HasValue && m_CurrentTest == test)
  198. return m_IsUnityTest.Value;
  199. var className = test.ClassName;
  200. var methodName = test.MethodName;
  201. // Doesn't seem like there's a proper way to get the current test method based on
  202. // the information provided by NUnit (see https://github.com/nunit/nunit/issues/3354).
  203. var type = Type.GetType(className);
  204. if (type == null)
  205. {
  206. foreach (var assembly in AppDomain.CurrentDomain.GetAssemblies())
  207. {
  208. type = assembly.GetType(className);
  209. if (type != null)
  210. break;
  211. }
  212. }
  213. if (type == null)
  214. {
  215. m_IsUnityTest = false;
  216. }
  217. else
  218. {
  219. var method = type.GetMethod(methodName);
  220. m_IsUnityTest = method?.GetCustomAttribute<UnityTestAttribute>() != null;
  221. }
  222. m_CurrentTest = test;
  223. return m_IsUnityTest.Value;
  224. }
  225. #if UNITY_EDITOR
  226. private Action<PlayModeStateChange> m_OnPlayModeStateChange;
  227. private void OnPlayModeStateChange(PlayModeStateChange change)
  228. {
  229. if (change == PlayModeStateChange.ExitingPlayMode && m_Initialized)
  230. TearDown();
  231. }
  232. #endif
  233. // ReSharper disable once MemberCanBeProtected.Global
  234. public static void AssertButtonPress<TState>(InputDevice device, TState state, params ButtonControl[] buttons)
  235. where TState : struct, IInputStateTypeInfo
  236. {
  237. // Update state.
  238. InputSystem.QueueStateEvent(device, state);
  239. InputSystem.Update();
  240. // Now verify that only the buttons we expect to be pressed are pressed.
  241. foreach (var control in device.allControls)
  242. {
  243. if (!(control is ButtonControl controlAsButton))
  244. continue;
  245. var isInList = buttons.Contains(controlAsButton);
  246. if (!isInList)
  247. Assert.That(controlAsButton.isPressed, Is.False,
  248. $"Expected button {controlAsButton} to NOT be pressed");
  249. else
  250. Assert.That(controlAsButton.isPressed, Is.True,
  251. $"Expected button {controlAsButton} to be pressed");
  252. }
  253. }
  254. public static void AssertStickValues(StickControl stick, Vector2 stickValue, float up, float down, float left,
  255. float right)
  256. {
  257. Assert.That(stick.ReadUnprocessedValue(), Is.EqualTo(stickValue));
  258. Assert.That(stick.up.ReadUnprocessedValue(), Is.EqualTo(up).Within(0.0001), "Incorrect 'up' value");
  259. Assert.That(stick.down.ReadUnprocessedValue(), Is.EqualTo(down).Within(0.0001), "Incorrect 'down' value");
  260. Assert.That(stick.left.ReadUnprocessedValue(), Is.EqualTo(left).Within(0.0001), "Incorrect 'left' value");
  261. Assert.That(stick.right.ReadUnprocessedValue(), Is.EqualTo(right).Within(0.0001), "Incorrect 'right' value");
  262. }
  263. private Dictionary<Key, Tuple<string, int>> m_KeyInfos;
  264. private bool m_Initialized;
  265. /// <summary>
  266. /// Set <see cref="Keyboard.keyboardLayout"/> of the given keyboard.
  267. /// </summary>
  268. /// <param name="name">Name of the keyboard layout to switch to.</param>
  269. /// <param name="keyboard">Keyboard to switch layout on. If <c>null</c>, <see cref="Keyboard.current"/> is used.</param>
  270. /// <exception cref="ArgumentException"><paramref name="keyboard"/> and <see cref="Keyboard.current"/> are both <c>null</c>.</exception>
  271. /// <remarks>
  272. /// Also queues and immediately processes an <see cref="DeviceConfigurationEvent"/> for the keyboard.
  273. /// </remarks>
  274. public unsafe void SetKeyboardLayout(string name, Keyboard keyboard = null)
  275. {
  276. if (keyboard == null)
  277. {
  278. keyboard = Keyboard.current;
  279. if (keyboard == null)
  280. throw new ArgumentException("No keyboard has been created and no keyboard has been given", nameof(keyboard));
  281. }
  282. runtime.SetDeviceCommandCallback(keyboard, (id, command) =>
  283. {
  284. if (id == QueryKeyboardLayoutCommand.Type)
  285. {
  286. var commandPtr = (QueryKeyboardLayoutCommand*)command;
  287. commandPtr->WriteLayoutName(name);
  288. return InputDeviceCommand.GenericSuccess;
  289. }
  290. return InputDeviceCommand.GenericFailure;
  291. });
  292. // Make sure caches on keys are flushed.
  293. InputSystem.QueueConfigChangeEvent(Keyboard.current);
  294. InputSystem.Update();
  295. }
  296. /// <summary>
  297. /// Set the <see cref="InputControl.displayName"/> of <paramref name="key"/> on the current
  298. /// <see cref="Keyboard"/> to be <paramref name="displayName"/>.
  299. /// </summary>
  300. /// <param name="key">Key to set the display name for.</param>
  301. /// <param name="displayName">Display name for the key.</param>
  302. /// <param name="scanCode">Optional <see cref="KeyControl.scanCode"/> to report for the key.</param>
  303. /// <remarks>
  304. /// Automatically adds a <see cref="Keyboard"/> if none has been added yet.
  305. /// </remarks>
  306. public unsafe void SetKeyInfo(Key key, string displayName, int scanCode = 0)
  307. {
  308. if (Keyboard.current == null)
  309. InputSystem.AddDevice<Keyboard>();
  310. if (m_KeyInfos == null)
  311. {
  312. m_KeyInfos = new Dictionary<Key, Tuple<string, int>>();
  313. runtime.SetDeviceCommandCallback(Keyboard.current,
  314. (id, commandPtr) =>
  315. {
  316. if (commandPtr->type == QueryKeyNameCommand.Type)
  317. {
  318. var keyNameCommand = (QueryKeyNameCommand*)commandPtr;
  319. if (m_KeyInfos.TryGetValue((Key)keyNameCommand->scanOrKeyCode, out var info))
  320. {
  321. keyNameCommand->scanOrKeyCode = info.Item2;
  322. StringHelpers.WriteStringToBuffer(info.Item1, (IntPtr)keyNameCommand->nameBuffer,
  323. QueryKeyNameCommand.kMaxNameLength);
  324. }
  325. return QueryKeyNameCommand.kSize;
  326. }
  327. return InputDeviceCommand.GenericFailure;
  328. });
  329. }
  330. m_KeyInfos[key] = new Tuple<string, int>(displayName, scanCode);
  331. // Make sure caches on keys are flushed.
  332. InputSystem.QueueConfigChangeEvent(Keyboard.current);
  333. InputSystem.Update();
  334. }
  335. /// <summary>
  336. /// Add support for <see cref="QueryCanRunInBackground"/> to <paramref name="device"/> and return
  337. /// <paramref name="value"/> as <see cref="QueryCanRunInBackground.canRunInBackground"/>.
  338. /// </summary>
  339. /// <param name="device"></param>
  340. internal unsafe void SetCanRunInBackground(InputDevice device, bool canRunInBackground = true)
  341. {
  342. runtime.SetDeviceCommandCallback(device, (id, command) =>
  343. {
  344. if (command->type == QueryCanRunInBackground.Type)
  345. {
  346. ((QueryCanRunInBackground*)command)->canRunInBackground = canRunInBackground;
  347. return InputDeviceCommand.GenericSuccess;
  348. }
  349. return InputDeviceCommand.GenericFailure;
  350. });
  351. }
  352. public ActionConstraint Started(InputAction action, InputControl control = null, double? time = null, object value = null)
  353. {
  354. return new ActionConstraint(InputActionPhase.Started, action, control, time: time, duration: 0, value: value);
  355. }
  356. public ActionConstraint Started<TValue>(InputAction action, InputControl<TValue> control, TValue value, double? time = null)
  357. where TValue : struct
  358. {
  359. return new ActionConstraint(InputActionPhase.Started, action, control, value, time: time, duration: 0);
  360. }
  361. public ActionConstraint Performed(InputAction action, InputControl control = null, double? time = null, double? duration = null, object value = null)
  362. {
  363. return new ActionConstraint(InputActionPhase.Performed, action, control, time: time, duration: duration, value: value);
  364. }
  365. public ActionConstraint Performed<TValue>(InputAction action, InputControl<TValue> control, TValue value, double? time = null, double? duration = null)
  366. where TValue : struct
  367. {
  368. return new ActionConstraint(InputActionPhase.Performed, action, control, value, time: time, duration: duration);
  369. }
  370. public ActionConstraint Canceled(InputAction action, InputControl control = null, double? time = null, double? duration = null, object value = null)
  371. {
  372. return new ActionConstraint(InputActionPhase.Canceled, action, control, time: time, duration: duration, value: value);
  373. }
  374. public ActionConstraint Canceled<TValue>(InputAction action, InputControl<TValue> control, TValue value, double? time = null, double? duration = null)
  375. where TValue : struct
  376. {
  377. return new ActionConstraint(InputActionPhase.Canceled, action, control, value, time: time, duration: duration);
  378. }
  379. public ActionConstraint Started<TInteraction>(InputAction action, InputControl control = null, object value = null, double? time = null)
  380. where TInteraction : IInputInteraction
  381. {
  382. return new ActionConstraint(InputActionPhase.Started, action, control, interaction: typeof(TInteraction), time: time,
  383. duration: 0, value: value);
  384. }
  385. public ActionConstraint Performed<TInteraction>(InputAction action, InputControl control = null, object value = null, double? time = null, double? duration = null)
  386. where TInteraction : IInputInteraction
  387. {
  388. return new ActionConstraint(InputActionPhase.Performed, action, control, interaction: typeof(TInteraction), time: time,
  389. duration: duration, value: value);
  390. }
  391. public ActionConstraint Canceled<TInteraction>(InputAction action, InputControl control = null, object value = null, double? time = null, double? duration = null)
  392. where TInteraction : IInputInteraction
  393. {
  394. return new ActionConstraint(InputActionPhase.Canceled, action, control, interaction: typeof(TInteraction), time: time,
  395. duration: duration, value: value);
  396. }
  397. ////REVIEW: Should we determine queueEventOnly automatically from whether we're in a UnityTest?
  398. // ReSharper disable once MemberCanBeProtected.Global
  399. public void Press(ButtonControl button, double time = -1, double timeOffset = 0, bool queueEventOnly = false)
  400. {
  401. Set(button, 1, time, timeOffset, queueEventOnly: queueEventOnly);
  402. }
  403. // ReSharper disable once MemberCanBeProtected.Global
  404. public void Release(ButtonControl button, double time = -1, double timeOffset = 0, bool queueEventOnly = false)
  405. {
  406. Set(button, 0, time, timeOffset, queueEventOnly: queueEventOnly);
  407. }
  408. // ReSharper disable once MemberCanBePrivate.Global
  409. public void PressAndRelease(ButtonControl button, double time = -1, double timeOffset = 0, bool queueEventOnly = false)
  410. {
  411. Press(button, time, timeOffset, queueEventOnly: true); // This one is always just a queue.
  412. Release(button, time, timeOffset, queueEventOnly: queueEventOnly);
  413. }
  414. // ReSharper disable once MemberCanBeProtected.Global
  415. public void Click(ButtonControl button, double time = -1, double timeOffset = 0, bool queueEventOnly = false)
  416. {
  417. PressAndRelease(button, time, timeOffset, queueEventOnly: queueEventOnly);
  418. }
  419. /// <summary>
  420. /// Set the control with the given <paramref name="path"/> on <paramref name="device"/> to the given <paramref name="state"/>
  421. /// by sending a state event with the value to the device.
  422. /// </summary>
  423. /// <param name="device">Device on which to find a control.</param>
  424. /// <param name="path">Path of the control on the device.</param>
  425. /// <param name="state">New state for the control.</param>
  426. /// <param name="time">Timestamp to use for the state event. If -1 (default), current time is used (see <see cref="InputTestFixture.currentTime"/>).</param>
  427. /// <param name="timeOffset">Offset to apply to the current time. This is an alternative to <paramref name="time"/>. By default, no offset is applied.</param>
  428. /// <param name="queueEventOnly">If true, no <see cref="InputSystem.Update"/> will be performed after queueing the event. This will only put
  429. /// the state event on the event queue and not do anything else. The default is to call <see cref="InputSystem.Update"/> after queuing the event.
  430. /// Note that not issuing an update means the state of the device will not change yet. This may affect subsequent Set/Press/Release/etc calls
  431. /// as they will not yet see the state change.
  432. ///
  433. /// Note that this parameter will be ignored if the test is a <c>[UnityTest]</c>. Multi-frame
  434. /// playmode tests will automatically process input as part of the Unity player loop.</param>
  435. /// <typeparam name="TValue">Value type of the control.</typeparam>
  436. /// <example>
  437. /// <code>
  438. /// var device = InputSystem.AddDevice("TestDevice");
  439. /// Set&lt;ButtonControl&gt;(device, "button", 1);
  440. /// Set&lt;AxisControl&gt;(device, "{Primary2DMotion}/x", 123.456f);
  441. /// </code>
  442. /// </example>
  443. public void Set<TValue>(InputDevice device, string path, TValue state, double time = -1, double timeOffset = 0,
  444. bool queueEventOnly = false)
  445. where TValue : struct
  446. {
  447. if (device == null)
  448. throw new ArgumentNullException(nameof(device));
  449. if (string.IsNullOrEmpty(path))
  450. throw new ArgumentNullException(nameof(path));
  451. var control = (InputControl<TValue>)device[path];
  452. Set(control, state, time, timeOffset, queueEventOnly);
  453. }
  454. /// <summary>
  455. /// Set the control to the given value by sending a state event with the value to the
  456. /// control's device.
  457. /// </summary>
  458. /// <param name="control">An input control on a device that has been added to the system.</param>
  459. /// <param name="state">New value for the input control.</param>
  460. /// <param name="time">Timestamp to use for the state event. If -1 (default), current time is used (see <see cref="InputTestFixture.currentTime"/>).</param>
  461. /// <param name="timeOffset">Offset to apply to the current time. This is an alternative to <paramref name="time"/>. By default, no offset is applied.</param>
  462. /// <param name="queueEventOnly">If true, no <see cref="InputSystem.Update"/> will be performed after queueing the event. This will only put
  463. /// the state event on the event queue and not do anything else. The default is to call <see cref="InputSystem.Update"/> after queuing the event.
  464. /// Note that not issuing an update means the state of the device will not change yet. This may affect subsequent Set/Press/Release/etc calls
  465. /// as they will not yet see the state change.
  466. ///
  467. /// Note that this parameter will be ignored if the test is a <c>[UnityTest]</c>. Multi-frame
  468. /// playmode tests will automatically process input as part of the Unity player loop.</param>
  469. /// <typeparam name="TValue">Value type of the given control.</typeparam>
  470. /// <example>
  471. /// <code>
  472. /// var gamepad = InputSystem.AddDevice&lt;Gamepad&gt;();
  473. /// Set(gamepad.leftButton, 1);
  474. /// </code>
  475. /// </example>
  476. public void Set<TValue>(InputControl<TValue> control, TValue state, double time = -1, double timeOffset = 0, bool queueEventOnly = false)
  477. where TValue : struct
  478. {
  479. if (control == null)
  480. throw new ArgumentNullException(nameof(control));
  481. if (!control.device.added)
  482. throw new ArgumentException(
  483. $"Device of control '{control}' has not been added to the system", nameof(control));
  484. if (IsUnityTest())
  485. queueEventOnly = true;
  486. void SetUpAndQueueEvent(InputEventPtr eventPtr)
  487. {
  488. eventPtr.time = (time >= 0 ? time : InputState.currentTime) + timeOffset;
  489. control.WriteValueIntoEvent(state, eventPtr);
  490. InputSystem.QueueEvent(eventPtr);
  491. }
  492. // Touchscreen does not support delta events involving TouchState.
  493. if (control is TouchControl)
  494. {
  495. using (StateEvent.From(control.device, out var eventPtr))
  496. SetUpAndQueueEvent(eventPtr);
  497. }
  498. else
  499. {
  500. // We use delta state events rather than full state events here to mitigate the following problem:
  501. // Grabbing state from the device will preserve the current values of controls covered in the state.
  502. // However, running an update may alter the value of one or more of those controls. So with a full
  503. // state event, we may be writing outdated data back into the device. For example, in the case of delta
  504. // controls which will reset in OnBeforeUpdate().
  505. //
  506. // Using delta events, we may still grab state outside of just the one control in case we're looking at
  507. // bit-addressed controls but at least we can avoid the problem for the majority of controls.
  508. using (DeltaStateEvent.From(control, out var eventPtr))
  509. SetUpAndQueueEvent(eventPtr);
  510. }
  511. if (!queueEventOnly)
  512. InputSystem.Update();
  513. }
  514. public void Move(InputControl<Vector2> positionControl, Vector2 position, Vector2? delta = null, double time = -1, double timeOffset = 0, bool queueEventOnly = false)
  515. {
  516. Set(positionControl, position, time: time, timeOffset: timeOffset, queueEventOnly: true);
  517. var deltaControl = (Vector2Control)positionControl.device.TryGetChildControl("delta");
  518. if (deltaControl != null)
  519. Set(deltaControl, delta ?? position - positionControl.ReadValue(), time: time, timeOffset: timeOffset, queueEventOnly: true);
  520. if (!queueEventOnly)
  521. InputSystem.Update();
  522. }
  523. ////TODO: obsolete this one in 2.0 and use pressure=1 default value
  524. public void BeginTouch(int touchId, Vector2 position, bool queueEventOnly = false, Touchscreen screen = null,
  525. double time = -1, double timeOffset = 0, byte displayIndex = 0)
  526. {
  527. SetTouch(touchId, TouchPhase.Began, position, 1, queueEventOnly: queueEventOnly, screen: screen, time: time, timeOffset: timeOffset, displayIndex: displayIndex);
  528. }
  529. public void BeginTouch(int touchId, Vector2 position, float pressure, bool queueEventOnly = false, Touchscreen screen = null,
  530. double time = -1, double timeOffset = 0)
  531. {
  532. SetTouch(touchId, TouchPhase.Began, position, pressure, queueEventOnly: queueEventOnly, screen: screen, time: time, timeOffset: timeOffset);
  533. }
  534. ////TODO: obsolete this one in 2.0 and use pressure=1 default value
  535. public void MoveTouch(int touchId, Vector2 position, Vector2 delta = default, bool queueEventOnly = false,
  536. Touchscreen screen = null, double time = -1, double timeOffset = 0)
  537. {
  538. SetTouch(touchId, TouchPhase.Moved, position, 1, delta, queueEventOnly: queueEventOnly, screen: screen, time: time, timeOffset: timeOffset);
  539. }
  540. public void MoveTouch(int touchId, Vector2 position, float pressure, Vector2 delta = default, bool queueEventOnly = false,
  541. Touchscreen screen = null, double time = -1, double timeOffset = 0)
  542. {
  543. SetTouch(touchId, TouchPhase.Moved, position, pressure, delta, queueEventOnly, screen: screen, time: time, timeOffset: timeOffset);
  544. }
  545. ////TODO: obsolete this one in 2.0 and use pressure=1 default value
  546. public void EndTouch(int touchId, Vector2 position, Vector2 delta = default, bool queueEventOnly = false,
  547. Touchscreen screen = null, double time = -1, double timeOffset = 0, byte displayIndex = 0)
  548. {
  549. SetTouch(touchId, TouchPhase.Ended, position, 1, delta, queueEventOnly: queueEventOnly, screen: screen, time: time, timeOffset: timeOffset, displayIndex: displayIndex);
  550. }
  551. public void EndTouch(int touchId, Vector2 position, float pressure, Vector2 delta = default, bool queueEventOnly = false,
  552. Touchscreen screen = null, double time = -1, double timeOffset = 0)
  553. {
  554. SetTouch(touchId, TouchPhase.Ended, position, pressure, delta, queueEventOnly, screen: screen, time: time, timeOffset: timeOffset);
  555. }
  556. ////TODO: obsolete this one in 2.0 and use pressure=1 default value
  557. public void CancelTouch(int touchId, Vector2 position, Vector2 delta = default, bool queueEventOnly = false,
  558. Touchscreen screen = null, double time = -1, double timeOffset = 0)
  559. {
  560. SetTouch(touchId, TouchPhase.Canceled, position, delta, queueEventOnly: queueEventOnly, screen: screen, time: time, timeOffset: timeOffset);
  561. }
  562. public void CancelTouch(int touchId, Vector2 position, float pressure, Vector2 delta = default, bool queueEventOnly = false,
  563. Touchscreen screen = null, double time = -1, double timeOffset = 0)
  564. {
  565. SetTouch(touchId, TouchPhase.Canceled, position, pressure, delta, queueEventOnly, screen: screen, time: time, timeOffset: timeOffset);
  566. }
  567. ////TODO: obsolete this one in 2.0 and use pressure=1 default value
  568. public void SetTouch(int touchId, TouchPhase phase, Vector2 position, Vector2 delta = default,
  569. bool queueEventOnly = true, Touchscreen screen = null, double time = -1, double timeOffset = 0)
  570. {
  571. SetTouch(touchId, phase, position, 1, delta: delta, queueEventOnly: queueEventOnly, screen: screen, time: time,
  572. timeOffset: timeOffset);
  573. }
  574. public void SetTouch(int touchId, TouchPhase phase, Vector2 position, float pressure, Vector2 delta = default, bool queueEventOnly = true,
  575. Touchscreen screen = null, double time = -1, double timeOffset = 0, byte displayIndex = 0)
  576. {
  577. if (screen == null)
  578. {
  579. screen = Touchscreen.current;
  580. if (screen == null)
  581. screen = InputSystem.AddDevice<Touchscreen>();
  582. }
  583. InputSystem.QueueStateEvent(screen, new TouchState
  584. {
  585. touchId = touchId,
  586. phase = phase,
  587. position = position,
  588. delta = delta,
  589. pressure = pressure,
  590. displayIndex = displayIndex,
  591. }, (time >= 0 ? time : InputState.currentTime) + timeOffset);
  592. if (!queueEventOnly)
  593. InputSystem.Update();
  594. }
  595. public void Trigger<TValue>(InputAction action, InputControl<TValue> control, TValue value)
  596. where TValue : struct
  597. {
  598. throw new NotImplementedException();
  599. }
  600. /// <summary>
  601. /// Perform the input action without having to know what it is bound to.
  602. /// </summary>
  603. /// <param name="action">An input action that is currently enabled and has controls it is bound to.</param>
  604. /// <remarks>
  605. /// Blindly triggering an action requires making a few assumptions. Actions are not built to be able to trigger
  606. /// without any input. This means that this method has to generate input on a control that the action is bound to.
  607. ///
  608. /// Note that this method has no understanding of the interactions that may be present on the action and thus
  609. /// does not know how they may affect the triggering of the action.
  610. /// </remarks>
  611. public void Trigger(InputAction action)
  612. {
  613. if (action == null)
  614. throw new ArgumentNullException(nameof(action));
  615. if (!action.enabled)
  616. throw new ArgumentException(
  617. $"Action '{action}' must be enabled in order to be able to trigger it", nameof(action));
  618. var controls = action.controls;
  619. if (controls.Count == 0)
  620. throw new ArgumentException(
  621. $"Action '{action}' must be bound to controls in order to be able to trigger it", nameof(action));
  622. // See if we have a button we can trigger.
  623. for (var i = 0; i < controls.Count; ++i)
  624. {
  625. if (!(controls[i] is ButtonControl button))
  626. continue;
  627. // Press and release button.
  628. Set(button, 1);
  629. Set(button, 0);
  630. return;
  631. }
  632. // See if we have an axis we can slide a bit.
  633. for (var i = 0; i < controls.Count; ++i)
  634. {
  635. if (!(controls[i] is AxisControl axis))
  636. continue;
  637. // We do, so nudge its value a bit.
  638. Set(axis, axis.ReadValue() + 0.01f);
  639. return;
  640. }
  641. ////TODO: support a wider range of controls
  642. throw new NotImplementedException();
  643. }
  644. /// <summary>
  645. /// The input runtime used during testing.
  646. /// </summary>
  647. internal InputTestRuntime runtime { get; private set; }
  648. /// <summary>
  649. /// Get or set the current time used by the input system.
  650. /// </summary>
  651. /// <value>Current time used by the input system.</value>
  652. public double currentTime
  653. {
  654. get => runtime.currentTime - runtime.currentTimeOffsetToRealtimeSinceStartup;
  655. set
  656. {
  657. runtime.currentTime = value + runtime.currentTimeOffsetToRealtimeSinceStartup;
  658. runtime.dontAdvanceTimeNextDynamicUpdate = true;
  659. }
  660. }
  661. internal float unscaledGameTime
  662. {
  663. get => runtime.unscaledGameTime;
  664. set
  665. {
  666. runtime.unscaledGameTime = value;
  667. runtime.dontAdvanceUnscaledGameTimeNextDynamicUpdate = true;
  668. }
  669. }
  670. public class ActionConstraint : Constraint
  671. {
  672. public InputActionPhase phase { get; set; }
  673. public double? time { get; set; }
  674. public double? duration { get; set; }
  675. public InputAction action { get; set; }
  676. public InputControl control { get; set; }
  677. public object value { get; set; }
  678. public Type interaction { get; set; }
  679. private readonly List<ActionConstraint> m_AndThen = new List<ActionConstraint>();
  680. public ActionConstraint(InputActionPhase phase, InputAction action, InputControl control, object value = null, Type interaction = null, double? time = null, double? duration = null)
  681. {
  682. this.phase = phase;
  683. this.time = time;
  684. this.duration = duration;
  685. this.action = action;
  686. this.control = control;
  687. this.value = value;
  688. this.interaction = interaction;
  689. var interactionText = string.Empty;
  690. if (interaction != null)
  691. interactionText = InputInteraction.GetDisplayName(interaction);
  692. var actionName = action.actionMap != null ? $"{action.actionMap}/{action.name}" : action.name;
  693. // Use same text format as InputActionTrace for easier comparison.
  694. var description = $"{{ action={actionName} phase={phase}";
  695. if (time != null)
  696. description += $" time={time}";
  697. if (control != null)
  698. description += $" control={control}";
  699. if (value != null)
  700. description += $" value={value}";
  701. if (interaction != null)
  702. description += $" interaction={interactionText}";
  703. if (duration != null)
  704. description += $" duration={duration}";
  705. description += " }";
  706. Description = description;
  707. }
  708. public override ConstraintResult ApplyTo(object actual)
  709. {
  710. var trace = (InputActionTrace)actual;
  711. var actions = trace.ToArray();
  712. if (actions.Length == 0)
  713. return new ConstraintResult(this, actual, false);
  714. if (!Verify(actions[0]))
  715. return new ConstraintResult(this, actual, false);
  716. var i = 1;
  717. foreach (var constraint in m_AndThen)
  718. {
  719. if (i >= actions.Length || !constraint.Verify(actions[i]))
  720. return new ConstraintResult(this, actual, false);
  721. ++i;
  722. }
  723. if (i != actions.Length)
  724. return new ConstraintResult(this, actual, false);
  725. return new ConstraintResult(this, actual, true);
  726. }
  727. private bool Verify(InputActionTrace.ActionEventPtr eventPtr)
  728. {
  729. // NOTE: Using explicit "return false" branches everywhere for easier setting of breakpoints.
  730. if (eventPtr.action != action ||
  731. eventPtr.phase != phase)
  732. return false;
  733. // Check time.
  734. if (time != null && !Mathf.Approximately((float)time.Value, (float)eventPtr.time))
  735. return false;
  736. // Check duration.
  737. if (duration != null && !Mathf.Approximately((float)duration.Value, (float)eventPtr.duration))
  738. return false;
  739. // Check control.
  740. if (control != null && eventPtr.control != control)
  741. return false;
  742. // Check interaction.
  743. if (interaction != null && (eventPtr.interaction == null ||
  744. !interaction.IsInstanceOfType(eventPtr.interaction)))
  745. return false;
  746. // Check value.
  747. if (value != null)
  748. {
  749. var val = eventPtr.ReadValueAsObject();
  750. if (val is float f)
  751. {
  752. if (!Mathf.Approximately(f, Convert.ToSingle(value)))
  753. return false;
  754. }
  755. else if (val is double d)
  756. {
  757. if (!Mathf.Approximately((float)d, (float)Convert.ToDouble(value)))
  758. return false;
  759. }
  760. else if (val is Vector2 v2)
  761. {
  762. if (!Vector2EqualityComparer.Instance.Equals(v2, value.As<Vector2>()))
  763. return false;
  764. }
  765. else if (val is Vector3 v3)
  766. {
  767. if (!Vector3EqualityComparer.Instance.Equals(v3, value.As<Vector3>()))
  768. return false;
  769. }
  770. else if (!val.Equals(value))
  771. return false;
  772. }
  773. return true;
  774. }
  775. public ActionConstraint AndThen(ActionConstraint constraint)
  776. {
  777. m_AndThen.Add(constraint);
  778. Description += " and\n";
  779. Description += constraint.Description;
  780. return this;
  781. }
  782. }
  783. #if UNITY_EDITOR
  784. internal void SimulateDomainReload()
  785. {
  786. // This quite invasively goes into InputSystem internals. Unfortunately, we
  787. // have no proper way of simulating domain reloads ATM. So we directly call various
  788. // internal methods here in a sequence similar to what we'd get during a domain reload.
  789. InputSystem.s_SystemObject.OnBeforeSerialize();
  790. InputSystem.s_SystemObject = null;
  791. InputSystem.InitializeInEditor(runtime);
  792. }
  793. #endif
  794. }
  795. }