Bez popisu
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.

Observable.cs 12KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243
  1. using System;
  2. using System.Collections.Generic;
  3. using UnityEngine.InputSystem.LowLevel;
  4. namespace UnityEngine.InputSystem.Utilities
  5. {
  6. /// <summary>
  7. /// Extension methods for working with <a ref="https://docs.microsoft.com/en-us/dotnet/api/system.iobservable-1">IObservable</a>
  8. /// in the context of the Input System.
  9. /// </summary>
  10. public static class Observable
  11. {
  12. /// <summary>
  13. /// Filter a stream of observable values by a predicate.
  14. /// </summary>
  15. /// <param name="source">The stream of observable values.</param>
  16. /// <param name="predicate">Filter to apply to the stream. Only values for which the predicate returns true
  17. /// are passed on to <c>OnNext</c> of the observer.</param>
  18. /// <typeparam name="TValue">Value type for the observable stream.</typeparam>
  19. /// <exception cref="ArgumentNullException"><paramref name="source"/> is <c>null</c> -or- <paramref name="predicate"/> is <c>null</c>.</exception>
  20. /// <returns>A new observable that is filtered by the given predicate.</returns>
  21. /// <remarks>
  22. /// <example>
  23. /// <code>
  24. /// InputSystem.onEvent
  25. /// .Where(e => e.HasButtonPress())
  26. /// .Call(e => Debug.Log("Press"));
  27. /// </code>
  28. /// </example>
  29. /// </remarks>
  30. /// <seealso cref="InputEventListener"/>
  31. /// <seealso cref="InputSystem.onEvent"/>
  32. public static IObservable<TValue> Where<TValue>(this IObservable<TValue> source, Func<TValue, bool> predicate)
  33. {
  34. if (source == null)
  35. throw new ArgumentNullException(nameof(source));
  36. if (predicate == null)
  37. throw new ArgumentNullException(nameof(predicate));
  38. return new WhereObservable<TValue>(source, predicate);
  39. }
  40. /// <summary>
  41. /// Transform each value in an observable stream of values into a value of a different type.
  42. /// </summary>
  43. /// <param name="source">The stream of observable values.</param>
  44. /// <param name="filter">Function to transform values in the stream.</param>
  45. /// <typeparam name="TSource">Type of source values to transform from.</typeparam>
  46. /// <typeparam name="TResult">Type of target values to transform to.</typeparam>
  47. /// <exception cref="ArgumentNullException"><paramref name="source"/> is <c>null</c> -or- <paramref name="filter"/> is <c>null</c>.</exception>
  48. /// <returns>A new observable of values of the new result type.</returns>
  49. /// <remarks>
  50. /// <example>
  51. /// <code>
  52. /// InputSystem.onEvent
  53. /// .Select(eventPtr => eventPtr.GetFirstButtonPressOrNull())
  54. /// .Call(ctrl =>
  55. /// {
  56. /// if (ctrl != null)
  57. /// Debug.Log(ctrl);
  58. /// });
  59. /// </code>
  60. /// </example>
  61. /// </remarks>
  62. /// <seealso cref="InputEventListener"/>
  63. /// <seealso cref="InputSystem.onEvent"/>
  64. public static IObservable<TResult> Select<TSource, TResult>(this IObservable<TSource> source, Func<TSource, TResult> filter)
  65. {
  66. if (source == null)
  67. throw new ArgumentNullException(nameof(source));
  68. if (filter == null)
  69. throw new ArgumentNullException(nameof(filter));
  70. return new SelectObservable<TSource, TResult>(source, filter);
  71. }
  72. /// <summary>
  73. /// Transform each value in an observable stream of values such that one value is translated to zero or more values
  74. /// of a new type.
  75. /// </summary>
  76. /// <param name="source">The stream of observable values.</param>
  77. /// <param name="filter">Function to transform each value in the stream into zero or more new values.</param>
  78. /// <typeparam name="TSource">Type of source values to transform from.</typeparam>
  79. /// <typeparam name="TResult">Type of target values to transform to.</typeparam>
  80. /// <exception cref="ArgumentNullException"><paramref name="source"/> is <c>null</c> -or- <paramref name="filter"/> is <c>null</c>.</exception>
  81. /// <returns>A new observable of values of the new result type.</returns>
  82. /// <remarks>
  83. /// <example>
  84. /// <code>
  85. /// InputSystem.onEvent
  86. /// .SelectMany(eventPtr => eventPtr.GetAllButtonPresses())
  87. /// .Call(ctrl =>
  88. /// Debug.Log($"Button {ctrl} pressed"));
  89. /// </code>
  90. /// </example>
  91. /// </remarks>
  92. /// <seealso cref="InputEventListener"/>
  93. /// <seealso cref="InputSystem.onEvent"/>
  94. public static IObservable<TResult> SelectMany<TSource, TResult>(this IObservable<TSource> source, Func<TSource, IEnumerable<TResult>> filter)
  95. {
  96. if (source == null)
  97. throw new ArgumentNullException(nameof(source));
  98. if (filter == null)
  99. throw new ArgumentNullException(nameof(filter));
  100. return new SelectManyObservable<TSource, TResult>(source, filter);
  101. }
  102. /// <summary>
  103. /// Take up to the first N values from the given observable stream of values.
  104. /// </summary>
  105. /// <param name="source">An observable source of values.</param>
  106. /// <param name="count">The maximum number of values to take from the source.</param>
  107. /// <typeparam name="TValue">Types of values to read from the stream.</typeparam>
  108. /// <exception cref="ArgumentNullException"><paramref name="source"/> is <c>null</c>.</exception>
  109. /// <exception cref="ArgumentOutOfRangeException"><paramref name="count"/> is negative.</exception>
  110. /// <returns>A stream of up to <paramref name="count"/> values.</returns>
  111. public static IObservable<TValue> Take<TValue>(this IObservable<TValue> source, int count)
  112. {
  113. if (source == null)
  114. throw new ArgumentNullException(nameof(source));
  115. if (count < 0)
  116. throw new ArgumentOutOfRangeException(nameof(count));
  117. return new TakeNObservable<TValue>(source, count);
  118. }
  119. /// <summary>
  120. /// From an observable stream of events, take only those that are for the given <paramref name="device"/>.
  121. /// </summary>
  122. /// <param name="source">An observable stream of events.</param>
  123. /// <param name="device">Device to filter events for.</param>
  124. /// <exception cref="ArgumentNullException"><paramref name="source"/> is <c>null</c>.</exception>
  125. /// <returns>An observable stream of events for the given device.</returns>
  126. /// <remarks>
  127. /// Each event has an <see cref="InputEvent.deviceId"/> associated with it. This is used to match
  128. /// against the <see cref="InputDevice.deviceId"/> of <paramref name="device"/>.
  129. ///
  130. /// <example>
  131. /// <code>
  132. /// InputSystem.onEvent
  133. /// .ForDevice(Mouse.current)
  134. /// .Call(e => Debug.Log($"Mouse event: {e}");
  135. /// </code>
  136. /// </example>
  137. /// </remarks>
  138. /// <seealso cref="InputEvent.deviceId"/>
  139. /// <seealso cref="InputEventListener"/>
  140. /// <seealso cref="InputSystem.onEvent"/>
  141. public static IObservable<InputEventPtr> ForDevice(this IObservable<InputEventPtr> source, InputDevice device)
  142. {
  143. if (source == null)
  144. throw new ArgumentNullException(nameof(source));
  145. return new ForDeviceEventObservable(source, null, device);
  146. }
  147. /// <summary>
  148. /// From an observable stream of events, take only those that are for a device of the given type.
  149. /// </summary>
  150. /// <param name="source">An observable stream of events.</param>
  151. /// <typeparam name="TDevice">Type of device (such as <see cref="Gamepad"/>) to filter for.</typeparam>
  152. /// <exception cref="ArgumentNullException"><paramref name="source"/> is <c>null</c>.</exception>
  153. /// <returns>An observable stream of events for devices of type <typeparamref name="TDevice"/>.</returns>
  154. /// <remarks>
  155. /// <example>
  156. /// <code>
  157. /// InputSystem.onEvent
  158. /// .ForDevice&lt;Gamepad&gt;()
  159. /// .Where(e => e.HasButtonPress())
  160. /// .CallOnce(e => PlayerInput.Instantiate(myPrefab,
  161. /// pairWithDevice: InputSystem.GetDeviceById(e.deviceId)));
  162. /// </code>
  163. /// </example>
  164. /// </remarks>
  165. /// <seealso cref="InputEventListener"/>
  166. /// <seealso cref="InputSystem.onEvent"/>
  167. public static IObservable<InputEventPtr> ForDevice<TDevice>(this IObservable<InputEventPtr> source)
  168. where TDevice : InputDevice
  169. {
  170. if (source == null)
  171. throw new ArgumentNullException(nameof(source));
  172. return new ForDeviceEventObservable(source, typeof(TDevice), null);
  173. }
  174. /// <summary>
  175. /// Call an action for the first value in the given stream of values and then automatically dispose
  176. /// the observer.
  177. /// </summary>
  178. /// <param name="source">An observable source of values.</param>
  179. /// <param name="action">Action to call for the first value that arrives from the source.</param>
  180. /// <typeparam name="TValue">Type of values delivered by the source.</typeparam>
  181. /// <exception cref="ArgumentNullException"><paramref name="source"/> is <c>null</c> -or- <paramref name="action"/> is <c>null</c>.</exception>
  182. /// <returns>A handle to the subscription. Call <c>Dispose</c> to unsubscribe at any time.</returns>
  183. /// <remarks>
  184. /// <example>
  185. /// <code>
  186. /// InputSystem.onEvent
  187. /// .Where(e => e.type == DeviceConfigurationEvent.typeStatic)
  188. /// .CallOnce(_ => Debug.Log("Device configuration changed"));
  189. /// </code>
  190. /// </example>
  191. /// </remarks>
  192. /// <seealso cref="InputEventListener"/>
  193. /// <seealso cref="InputSystem.onEvent"/>
  194. /// <seealso cref="Call{TValue}"/>
  195. public static IDisposable CallOnce<TValue>(this IObservable<TValue> source, Action<TValue> action)
  196. {
  197. if (source == null)
  198. throw new ArgumentNullException(nameof(source));
  199. if (action == null)
  200. throw new ArgumentNullException(nameof(action));
  201. IDisposable subscription = null;
  202. subscription = source.Take(1).Subscribe(new Observer<TValue>(action, () => subscription?.Dispose()));
  203. return subscription;
  204. }
  205. /// <summary>
  206. /// Call the given callback for every value generated by the given observable stream of values.
  207. /// </summary>
  208. /// <param name="source">An observable stream of values.</param>
  209. /// <param name="action">A callback to invoke for each value.</param>
  210. /// <typeparam name="TValue"></typeparam>
  211. /// <exception cref="ArgumentNullException"><paramref name="source"/> is <c>null</c> -or- <paramref name="action"/> is <c>null</c>.</exception>
  212. /// <returns>A handle to the subscription. Call <c>Dispose</c> to unsubscribe at any time.</returns>
  213. /// <remarks>
  214. /// <example>
  215. /// <code>
  216. /// InputSystem.onEvent
  217. /// .Where(e => e.type == DeviceConfigurationEvent.typeStatic)
  218. /// .Call(_ => Debug.Log("Device configuration changed"));
  219. /// </code>
  220. /// </example>
  221. /// </remarks>
  222. /// <seealso cref="InputEventListener"/>
  223. /// <seealso cref="InputSystem.onEvent"/>
  224. /// <seealso cref="CallOnce{TValue}"/>
  225. public static IDisposable Call<TValue>(this IObservable<TValue> source, Action<TValue> action)
  226. {
  227. if (source == null)
  228. throw new ArgumentNullException(nameof(source));
  229. if (action == null)
  230. throw new ArgumentNullException(nameof(action));
  231. return source.Subscribe(new Observer<TValue>(action));
  232. }
  233. }
  234. }