canto/Assets/Scripts/Extension/Observable.cs
2025-07-16 23:35:29 -04:00

182 lines
7.3 KiB
C#

using R3;
using UnityEngine;
using System;
using KitsuneCafe.System;
using System.Collections.Generic;
using System.Threading;
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);
}
}
}