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&lt;Gamepad&gt;() 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}