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