Nessuna descrizione
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.

InputState.cs 15KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286
  1. using System;
  2. using Unity.Collections.LowLevel.Unsafe;
  3. using UnityEngine.InputSystem.Utilities;
  4. ////TODO: method to get raw state pointer for device/control
  5. ////REVIEW: allow to restrict state change monitors to specific updates?
  6. namespace UnityEngine.InputSystem.LowLevel
  7. {
  8. using NotifyControlValueChangeAction = Action<InputControl, double, InputEventPtr, long>;
  9. using NotifyTimerExpiredAction = Action<InputControl, double, long, int>;
  10. /// <summary>
  11. /// Low-level APIs for working with input state memory.
  12. /// </summary>
  13. public static class InputState
  14. {
  15. /// <summary>
  16. /// The type of update that was last run or is currently being run on the input state.
  17. /// </summary>
  18. /// <remarks>
  19. /// This determines which set of buffers are currently active and thus determines which view code
  20. /// that queries input state will receive. For example, during editor updates, this will be
  21. /// <see cref="InputUpdateType.Editor"/> and the state buffers for the editor will be active.
  22. /// </remarks>
  23. public static InputUpdateType currentUpdateType => InputUpdate.s_LatestUpdateType;
  24. ////FIXME: ATM this does not work for editor updates
  25. /// <summary>
  26. /// The number of times the current input state has been updated.
  27. /// </summary>
  28. public static uint updateCount => InputUpdate.s_UpdateStepCount;
  29. public static double currentTime => InputRuntime.s_Instance.currentTime - InputRuntime.s_CurrentTimeOffsetToRealtimeSinceStartup;
  30. /// <summary>
  31. /// Callback that is triggered when the state of an input device changes.
  32. /// </summary>
  33. /// <remarks>
  34. /// The first parameter is the device whose state was changed the second parameter is the event
  35. /// that triggered the change in state. Note that the latter may be <c>null</c> in case the
  36. /// change was performed directly through <see cref="Change"/> rather than through an event.
  37. /// </remarks>
  38. public static event Action<InputDevice, InputEventPtr> onChange
  39. {
  40. add => InputSystem.s_Manager.onDeviceStateChange += value;
  41. remove => InputSystem.s_Manager.onDeviceStateChange -= value;
  42. }
  43. public static unsafe void Change(InputDevice device, InputEventPtr eventPtr, InputUpdateType updateType = default)
  44. {
  45. if (device == null)
  46. throw new ArgumentNullException(nameof(device));
  47. if (!eventPtr.valid)
  48. throw new ArgumentNullException(nameof(eventPtr));
  49. // Make sure event is a StateEvent or DeltaStateEvent and has a format matching the device.
  50. FourCC stateFormat;
  51. var eventType = eventPtr.type;
  52. if (eventType == StateEvent.Type)
  53. stateFormat = StateEvent.FromUnchecked(eventPtr)->stateFormat;
  54. else if (eventType == DeltaStateEvent.Type)
  55. stateFormat = DeltaStateEvent.FromUnchecked(eventPtr)->stateFormat;
  56. else
  57. {
  58. #if UNITY_EDITOR
  59. InputSystem.s_Manager.m_Diagnostics?.OnEventFormatMismatch(eventPtr, device);
  60. #endif
  61. return;
  62. }
  63. if (stateFormat != device.stateBlock.format)
  64. throw new ArgumentException(
  65. $"State format {stateFormat} from event does not match state format {device.stateBlock.format} of device {device}",
  66. nameof(eventPtr));
  67. InputSystem.s_Manager.UpdateState(device, eventPtr,
  68. updateType != default ? updateType : InputSystem.s_Manager.defaultUpdateType);
  69. }
  70. /// <summary>
  71. /// Perform one update of input state.
  72. /// </summary>
  73. /// <remarks>
  74. /// Incorporates the given state and triggers all state change monitors as needed.
  75. ///
  76. /// Note that input state changes performed with this method will not be visible on remotes as they will bypass
  77. /// event processing. It is effectively equivalent to directly writing into input state memory except that it
  78. /// also performs related tasks such as checking state change monitors, flipping buffers, or making the respective
  79. /// device current.
  80. /// </remarks>
  81. public static void Change<TState>(InputControl control, TState state, InputUpdateType updateType = default,
  82. InputEventPtr eventPtr = default)
  83. where TState : struct
  84. {
  85. Change(control, ref state, updateType, eventPtr);
  86. }
  87. /// <summary>
  88. /// Perform one update of input state.
  89. /// </summary>
  90. /// <remarks>
  91. /// Incorporates the given state and triggers all state change monitors as needed.
  92. ///
  93. /// Note that input state changes performed with this method will not be visible on remotes as they will bypass
  94. /// event processing. It is effectively equivalent to directly writing into input state memory except that it
  95. /// also performs related tasks such as checking state change monitors, flipping buffers, or making the respective
  96. /// device current.
  97. /// </remarks>
  98. public static unsafe void Change<TState>(InputControl control, ref TState state, InputUpdateType updateType = default,
  99. InputEventPtr eventPtr = default)
  100. where TState : struct
  101. {
  102. if (control == null)
  103. throw new ArgumentNullException(nameof(control));
  104. if (control.stateBlock.bitOffset != 0 || control.stateBlock.sizeInBits % 8 != 0)
  105. throw new ArgumentException($"Cannot change state of bitfield control '{control}' using this method", nameof(control));
  106. var device = control.device;
  107. var stateSize = Math.Min(UnsafeUtility.SizeOf<TState>(), control.m_StateBlock.alignedSizeInBytes);
  108. var statePtr = UnsafeUtility.AddressOf(ref state);
  109. var stateOffset = control.stateBlock.byteOffset - device.stateBlock.byteOffset;
  110. InputSystem.s_Manager.UpdateState(device,
  111. updateType != default ? updateType : InputSystem.s_Manager.defaultUpdateType, statePtr, stateOffset,
  112. (uint)stateSize,
  113. eventPtr.valid
  114. ? eventPtr.internalTime
  115. : InputRuntime.s_Instance.currentTime,
  116. eventPtr: eventPtr);
  117. }
  118. public static bool IsIntegerFormat(this FourCC format)
  119. {
  120. return format == InputStateBlock.FormatBit ||
  121. format == InputStateBlock.FormatInt ||
  122. format == InputStateBlock.FormatByte ||
  123. format == InputStateBlock.FormatShort ||
  124. format == InputStateBlock.FormatSBit ||
  125. format == InputStateBlock.FormatUInt ||
  126. format == InputStateBlock.FormatUShort ||
  127. format == InputStateBlock.FormatLong ||
  128. format == InputStateBlock.FormatULong;
  129. }
  130. /// <summary>
  131. /// Add a monitor that gets triggered every time the state of <paramref name="control"/> changes.
  132. /// </summary>
  133. /// <param name="control">A control sitting on an <see cref="InputDevice"/> that has been <see cref="InputDevice.added"/>.</param>
  134. /// <param name="monitor">Instance of the monitor that should be notified when state changes occur.</param>
  135. /// <param name="monitorIndex">Numeric index of the monitors. Monitors on a device are ordered by <em>decreasing</em> monitor index
  136. /// and invoked in that order.</param>
  137. /// <param name="groupIndex">Numeric group of the monitor. See remarks.</param>
  138. /// <exception cref="ArgumentNullException"><paramref name="control"/> is <c>null</c> -or- <paramref name="monitor"/> is <c>null</c>.</exception>
  139. /// <exception cref="ArgumentException">The <see cref="InputDevice"/> of <paramref name="control"/> has not been <see cref="InputDevice.added"/>.</exception>
  140. /// <remarks>
  141. /// All monitors on an <see cref="InputDevice"/> are sorted by the complexity specified in their <paramref name="monitorIndex"/> (in decreasing order) and invoked
  142. /// in that order.
  143. ///
  144. /// Every handler gets an opportunity to set <see cref="InputEventPtr.handled"/> to <c>true</c>. When doing so, all remaining pending monitors
  145. /// from the same <paramref name="monitor"/> instance that have the same <paramref name="groupIndex"/> will be silenced and skipped over.
  146. /// This can be used to establish an order of event "consumption" where one change monitor may prevent another change monitor from triggering.
  147. ///
  148. /// Monitors are invoked <em>after</em> a state change has been written to the device. If, for example, a <see cref="StateEvent"/> is
  149. /// received that sets <see cref="Gamepad.leftTrigger"/> to <c>0.5</c>, the value is first applied to the control and then any state
  150. /// monitors that may be listening to the change are invoked (thus getting <c>0.5</c> if calling <see cref="InputControl{TValue}.ReadValue()"/>).
  151. ///
  152. /// <example>
  153. /// <code>
  154. /// class InputMonitor : IInputStateChangeMonitor
  155. /// {
  156. /// public InputMonitor()
  157. /// {
  158. /// // Watch the left and right mouse button.
  159. /// // By supplying monitor indices here, we not only receive the indices in NotifyControlStateChanged,
  160. /// // we also create an ordering between the two monitors. The one on RMB will fire *before* the one
  161. /// // on LMB in case there is a single event that changes both buttons.
  162. /// InputState.AddChangeMonitor(Mouse.current.leftButton, this, monitorIndex: 1);
  163. /// InputState.AddChangeMonitor(Mouse.current.rightButton, this, monitorIndex: 2);
  164. /// }
  165. ///
  166. /// public void NotifyControlStateChanged(InputControl control, double time, InputEventPtr eventPtr, long monitorIndex)
  167. /// {
  168. /// Debug.Log($"{control} changed");
  169. ///
  170. /// // We can add a monitor timeout that will trigger in case the state of the
  171. /// // given control is not changed within the given time. Let's watch the control
  172. /// // for 2 seconds. If nothing happens, we will get a call to NotifyTimerExpired.
  173. /// // If, however, there is a state change, the timeout is automatically removed
  174. /// // and we will see a call to NotifyControlStateChanged instead.
  175. /// InputState.AddChangeMonitorTimeout(control, this, 2);
  176. /// }
  177. ///
  178. /// public void NotifyTimerExpired(InputControl control, double time, long monitorIndex, int timerIndex)
  179. /// {
  180. /// Debug.Log($"{control} was not changed within 2 seconds");
  181. /// }
  182. /// }
  183. /// </code>
  184. /// </example>
  185. /// </remarks>
  186. public static void AddChangeMonitor(InputControl control, IInputStateChangeMonitor monitor, long monitorIndex = -1, uint groupIndex = default)
  187. {
  188. if (control == null)
  189. throw new ArgumentNullException(nameof(control));
  190. if (monitor == null)
  191. throw new ArgumentNullException(nameof(monitor));
  192. if (!control.device.added)
  193. throw new ArgumentException($"Device for control '{control}' has not been added to system");
  194. InputSystem.s_Manager.AddStateChangeMonitor(control, monitor, monitorIndex, groupIndex);
  195. }
  196. public static IInputStateChangeMonitor AddChangeMonitor(InputControl control,
  197. NotifyControlValueChangeAction valueChangeCallback, int monitorIndex = -1,
  198. NotifyTimerExpiredAction timerExpiredCallback = null)
  199. {
  200. if (valueChangeCallback == null)
  201. throw new ArgumentNullException(nameof(valueChangeCallback));
  202. var monitor = new StateChangeMonitorDelegate
  203. {
  204. valueChangeCallback = valueChangeCallback,
  205. timerExpiredCallback = timerExpiredCallback
  206. };
  207. AddChangeMonitor(control, monitor, monitorIndex);
  208. return monitor;
  209. }
  210. public static void RemoveChangeMonitor(InputControl control, IInputStateChangeMonitor monitor, long monitorIndex = -1)
  211. {
  212. if (control == null)
  213. throw new ArgumentNullException(nameof(control));
  214. if (monitor == null)
  215. throw new ArgumentNullException(nameof(monitor));
  216. InputSystem.s_Manager.RemoveStateChangeMonitor(control, monitor, monitorIndex);
  217. }
  218. /// <summary>
  219. /// Put a timeout on a previously registered state change monitor.
  220. /// </summary>
  221. /// <param name="control"></param>
  222. /// <param name="monitor"></param>
  223. /// <param name="time"></param>
  224. /// <param name="monitorIndex"></param>
  225. /// <param name="timerIndex"></param>
  226. /// <remarks>
  227. /// If by the given <paramref name="time"/>, no state change has been registered on the control monitored
  228. /// by the given <paramref name="monitor">state change monitor</paramref>, <see cref="IInputStateChangeMonitor.NotifyTimerExpired"/>
  229. /// will be called on <paramref name="monitor"/>. If a state change happens by the given <paramref name="time"/>,
  230. /// the monitor is notified as usual and the timer is automatically removed.
  231. /// </remarks>
  232. public static void AddChangeMonitorTimeout(InputControl control, IInputStateChangeMonitor monitor, double time, long monitorIndex = -1, int timerIndex = -1)
  233. {
  234. if (monitor == null)
  235. throw new ArgumentNullException(nameof(monitor));
  236. InputSystem.s_Manager.AddStateChangeMonitorTimeout(control, monitor, time, monitorIndex, timerIndex);
  237. }
  238. public static void RemoveChangeMonitorTimeout(IInputStateChangeMonitor monitor, long monitorIndex = -1, int timerIndex = -1)
  239. {
  240. if (monitor == null)
  241. throw new ArgumentNullException(nameof(monitor));
  242. InputSystem.s_Manager.RemoveStateChangeMonitorTimeout(monitor, monitorIndex, timerIndex);
  243. }
  244. private class StateChangeMonitorDelegate : IInputStateChangeMonitor
  245. {
  246. public NotifyControlValueChangeAction valueChangeCallback;
  247. public NotifyTimerExpiredAction timerExpiredCallback;
  248. public void NotifyControlStateChanged(InputControl control, double time, InputEventPtr eventPtr, long monitorIndex)
  249. {
  250. valueChangeCallback(control, time, eventPtr, monitorIndex);
  251. }
  252. public void NotifyTimerExpired(InputControl control, double time, long monitorIndex, int timerIndex)
  253. {
  254. timerExpiredCallback?.Invoke(control, time, monitorIndex, timerIndex);
  255. }
  256. }
  257. }
  258. }