123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243 |
- 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));
- }
- }
- }
|