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