216 lines
8.5 KiB
C#
216 lines
8.5 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.Threading;
|
|
using KitsuneCafe.Sys;
|
|
using R3;
|
|
using UnityEngine;
|
|
using UnityEngine.Rendering.Universal;
|
|
|
|
namespace KitsuneCafe.Extension
|
|
{
|
|
public static class ObservableExtensions
|
|
{
|
|
public static Observable<TResult> Compose<TSource, TResult>(this Observable<TSource> source, Func<Observable<TSource>, Observable<TResult>> transform)
|
|
{
|
|
return transform(source);
|
|
}
|
|
|
|
private struct SmoothDampState
|
|
{
|
|
public float CurrentValue;
|
|
public float CurrentVelocityRef;
|
|
}
|
|
|
|
public static Observable<float> EveryUpdateDelta(this TimeProvider provider)
|
|
{
|
|
if (provider == null)
|
|
{
|
|
provider = UnityTimeProvider.Update;
|
|
}
|
|
|
|
if (provider.TryGetFrameProvider(out var frameProvider))
|
|
{
|
|
return Observable.EveryUpdate(frameProvider)
|
|
.Select(_ => provider.GetDeltaTime(Time.deltaTime));
|
|
}
|
|
else
|
|
{
|
|
return Observable.Empty<float>();
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Smoothly dampens a value from its current state towards a target value over time,
|
|
/// driven by a FrameProvider.
|
|
/// </summary>
|
|
/// <param name="source">The observable stream providing the *target* values for damping.</param>
|
|
/// <param name="valueGetter">A function that provides the initial value for the damping process when a new target is received (e.g., current value from Animator).</param>
|
|
/// <param name="smoothTime">The approximate time it takes for the value to reach the target.</param>
|
|
/// <param name="maxSpeed">The maximum speed the value can change per second.</param>
|
|
/// <param name="provider">The FrameProvider to use for update ticks (e.g., UnityFrameProvider.FixedUpdate).</param>
|
|
/// <returns>An observable stream of the smoothed values.</returns>
|
|
public static Observable<float> SmoothDamp(
|
|
this Observable<float> source,
|
|
Func<float> valueGetter,
|
|
float smoothTime,
|
|
float maxSpeed,
|
|
TimeProvider provider)
|
|
{
|
|
return source
|
|
.DistinctUntilChanged(FEqualityComparer<float>.Create(KMath.IsApproxEqual))
|
|
.Select(target =>
|
|
{
|
|
float initialCurrentValue = valueGetter();
|
|
|
|
return Observable.EveryUpdate(provider.GetFrameProvider())
|
|
.Scan(new SmoothDampState { CurrentValue = initialCurrentValue, CurrentVelocityRef = 0f }, (state, _) =>
|
|
{
|
|
float currentVelocity = state.CurrentVelocityRef;
|
|
float nextValue = Mathf.SmoothDamp(
|
|
state.CurrentValue,
|
|
target,
|
|
ref currentVelocity,
|
|
smoothTime,
|
|
maxSpeed,
|
|
provider.GetDeltaTime()
|
|
);
|
|
return new SmoothDampState { CurrentValue = nextValue, CurrentVelocityRef = currentVelocity };
|
|
})
|
|
.Select(state => state.CurrentValue)
|
|
.TakeWhile(val => Mathf.Abs(val - target) > float.Epsilon)
|
|
.Concat(Observable.Return(target));
|
|
})
|
|
.Switch();
|
|
}
|
|
|
|
public static Observable<float> SmoothDamp(
|
|
this Observable<float> source,
|
|
Func<float> valueGetter,
|
|
float smoothTime)
|
|
{
|
|
return SmoothDamp(source, valueGetter, smoothTime, Mathf.Infinity, UnityTimeProvider.Update);
|
|
}
|
|
|
|
public static Observable<float> SmoothDamp(
|
|
this Observable<float> source,
|
|
Func<float> valueGetter,
|
|
float smoothTime,
|
|
float maxSpeed)
|
|
{
|
|
return SmoothDamp(source, valueGetter, smoothTime, maxSpeed, UnityTimeProvider.Update);
|
|
}
|
|
|
|
public static Observable<float> SmoothDamp(
|
|
this Observable<float> source,
|
|
Func<float> valueGetter,
|
|
float smoothTime,
|
|
TimeProvider provider)
|
|
{
|
|
return SmoothDamp(source, valueGetter, smoothTime, Mathf.Infinity, provider);
|
|
}
|
|
|
|
public static float SqrDistance(this Vector3 vector, Vector3 other)
|
|
{
|
|
return (vector - other).sqrMagnitude;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Returns the element from a sequence that has the minimum value for a specified key.
|
|
/// If the source sequence is empty, returns default(TSource).
|
|
/// </summary>
|
|
/// <typeparam name="TSource">The type of the elements in the source sequence.</typeparam>
|
|
/// <typeparam name="TKey">The type of the key to compare elements by.</typeparam>
|
|
/// <param name="source">The sequence to find the minimum element from.</param>
|
|
/// <param name="keySelector">A function to extract the key from an element.</param>
|
|
/// <returns>The element that has the minimum key value, or default(TSource) if the source is empty.</returns>
|
|
public static TSource MinBy<TSource, TKey>(this IEnumerable<TSource> source, Func<TSource, TKey> keySelector)
|
|
where TKey : IComparable<TKey>
|
|
{
|
|
if (source == null) throw new ArgumentNullException(nameof(source));
|
|
if (keySelector == null) throw new ArgumentNullException(nameof(keySelector));
|
|
|
|
using (var enumerator = source.GetEnumerator())
|
|
{
|
|
if (!enumerator.MoveNext())
|
|
{
|
|
return default;
|
|
}
|
|
|
|
TSource minElement = enumerator.Current;
|
|
TKey minKey = keySelector(minElement);
|
|
|
|
while (enumerator.MoveNext())
|
|
{
|
|
TSource currentElement = enumerator.Current;
|
|
TKey currentKey = keySelector(currentElement);
|
|
|
|
if (currentKey.CompareTo(minKey) < 0)
|
|
{
|
|
minKey = currentKey;
|
|
minElement = currentElement;
|
|
}
|
|
}
|
|
return minElement;
|
|
}
|
|
}
|
|
|
|
public static Observable<TResult> SelectSwitch<T, TResult>(this Observable<T> source, Func<T, Observable<TResult>> selector)
|
|
{
|
|
return source.Select(selector).Switch();
|
|
}
|
|
|
|
public static Observable<T> SwitchIf<T>(this Observable<T> source, Observable<bool> selectionSource, Observable<T> other)
|
|
{
|
|
return selectionSource.SelectSwitch(value => value ? other : source);
|
|
}
|
|
|
|
public static Observable<T> SwitchIf<T>(this Observable<T> source, Observable<bool> selectionSource, Func<Observable<T>> other)
|
|
{
|
|
return selectionSource.SelectSwitch(value => value ? other() : source);
|
|
}
|
|
|
|
public static Observable<T> WhereEquals<T>(this Observable<T> source, T value) where T : IEquatable<T>
|
|
{
|
|
return source.Where(value.Equals);
|
|
}
|
|
|
|
public static Observable<bool> WhereTrue(this Observable<bool> source)
|
|
{
|
|
return source.Where(Func.Identity);
|
|
}
|
|
|
|
public static Observable<bool> WhereFalse(this Observable<bool> source)
|
|
{
|
|
return WhereEquals(source, false);
|
|
}
|
|
|
|
public static Observable<T> ObserveBool<T>(this T component, Func<T, bool> predicate) where T : MonoBehaviour
|
|
{
|
|
if (predicate(component))
|
|
{
|
|
return Observable.Return(component);
|
|
}
|
|
|
|
return Observable.EveryValueChanged(component, predicate)
|
|
.Where(Func.Identity)
|
|
.Take(1)
|
|
.Select(_ => component);
|
|
}
|
|
|
|
public static Observable<T> ObserveAwake<T>(this T component) where T : MonoBehaviour
|
|
{
|
|
return component.ObserveBool(c => c.didAwake);
|
|
}
|
|
|
|
public static Observable<T> ObserveStart<T>(this T component) where T : MonoBehaviour
|
|
{
|
|
return component.ObserveBool(c => c.didStart);
|
|
}
|
|
|
|
public static Observable<TResult> SelectOrDefault<TValue, TResult>(this Observable<TValue> source, Func<TValue, TResult> selector)
|
|
{
|
|
return source.Select(value => EqualityComparer<TValue>.Default.Equals(value, default) ? default : selector(value));
|
|
}
|
|
}
|
|
}
|
|
|