356 lines
15 KiB
C#
356 lines
15 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.Linq;
|
|
using KitsuneCafe.Sys;
|
|
using R3;
|
|
using UnityEngine;
|
|
using UnityEngine.UIElements;
|
|
|
|
namespace KitsuneCafe.Extension
|
|
{
|
|
public static class R3Extensions
|
|
{
|
|
public static Observable<TResult> Compose<TSource, TResult>(this Observable<TSource> source, Func<Observable<TSource>, Observable<TResult>> transform) =>
|
|
transform(source);
|
|
|
|
private struct SmoothDampState
|
|
{
|
|
public float Value;
|
|
public float Velocity;
|
|
|
|
public SmoothDampState(float initialValue, float initialVelocity)
|
|
{
|
|
Value = initialValue;
|
|
Velocity = initialVelocity;
|
|
}
|
|
|
|
public SmoothDampState(float initialValue) : this(initialValue, 0) { }
|
|
}
|
|
|
|
public static Observable<float> EveryUpdateDelta(this TimeProvider provider)
|
|
{
|
|
provider ??= UnityTimeProvider.Update;
|
|
|
|
return provider.TryGetFrameProvider(out var frameProvider) ?
|
|
Observable.EveryUpdate(frameProvider)
|
|
.Select(_ => provider.GetDeltaTime(Time.deltaTime)) :
|
|
Observable.Empty<float>();
|
|
}
|
|
|
|
private static readonly Lazy<IEqualityComparer<float>> floatComparer = new(
|
|
() => FEqualityComparer<float>.Create(KMath.IsApproxEqual)
|
|
);
|
|
|
|
public static Observable<float> SmoothDamp(
|
|
this Observable<float> source,
|
|
Func<float> valueGetter,
|
|
float smoothTime,
|
|
float maxSpeed,
|
|
TimeProvider provider) =>
|
|
source
|
|
.DistinctUntilChanged(floatComparer.Value)
|
|
.Select(target => Observable.EveryUpdate(provider.GetFrameProvider())
|
|
.Scan(new SmoothDampState(valueGetter()),
|
|
(state, _) =>
|
|
{
|
|
var currentVelocity = state.Velocity;
|
|
var nextValue = Mathf.SmoothDamp(
|
|
state.Value,
|
|
target,
|
|
ref currentVelocity,
|
|
smoothTime,
|
|
maxSpeed,
|
|
provider.GetDeltaTime()
|
|
);
|
|
|
|
return new SmoothDampState(nextValue, currentVelocity);
|
|
})
|
|
.Select(state => state.Value)
|
|
.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) =>
|
|
SmoothDamp(source, valueGetter, smoothTime, Mathf.Infinity, UnityTimeProvider.Update);
|
|
|
|
public static Observable<float> SmoothDamp(
|
|
this Observable<float> source,
|
|
Func<float> valueGetter,
|
|
float smoothTime,
|
|
float maxSpeed) =>
|
|
SmoothDamp(source, valueGetter, smoothTime, maxSpeed, UnityTimeProvider.Update);
|
|
|
|
public static Observable<float> SmoothDamp(
|
|
this Observable<float> source,
|
|
Func<float> valueGetter,
|
|
float smoothTime,
|
|
TimeProvider provider) =>
|
|
SmoothDamp(source, valueGetter, smoothTime, Mathf.Infinity, provider);
|
|
|
|
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) =>
|
|
source.Select(selector).Switch();
|
|
|
|
public static Observable<T> SwitchIfElse<T>(this Observable<bool> source, Func<Observable<T>> ifTrue, Observable<T> ifFalse) =>
|
|
SwitchIf(ifFalse, source, ifTrue);
|
|
|
|
public static Observable<T> SwitchIfElse<T>(this Observable<bool> source, Observable<T> ifTrue, Observable<T> ifFalse) =>
|
|
SwitchIf(ifFalse, source, ifTrue);
|
|
|
|
public static Observable<T> SwitchIf<T>(this Observable<T> source, Observable<bool> selectionSource, Observable<T> other) =>
|
|
selectionSource.SelectSwitch(value => value ? other : source);
|
|
|
|
public static Observable<T> SwitchIf<T>(this Observable<T> source, Observable<bool> selectionSource, Func<Observable<T>> other) =>
|
|
selectionSource.SelectSwitch(value => value ? other() : source);
|
|
|
|
public static Observable<T> WhereEquals<T>(this Observable<T> source, T value) where T : IEquatable<T> =>
|
|
source.Where(value.Equals);
|
|
|
|
public static Observable<bool> WhereTrue(this Observable<bool> source) =>
|
|
source.Where(Func.Identity);
|
|
|
|
public static Observable<bool> WhereFalse(this Observable<bool> source) =>
|
|
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 =>
|
|
component.ObserveBool(c => c.didAwake);
|
|
|
|
public static Observable<T> ObserveStart<T>(this T component) where T : MonoBehaviour =>
|
|
component.ObserveBool(c => c.didStart);
|
|
|
|
public static Observable<TResult> SelectOrDefault<TValue, TResult>(this Observable<TValue> source, Func<TValue, TResult> selector) =>
|
|
source.Select(value => EqualityComparer<TValue>.Default.Equals(value, default) ? default : selector(value));
|
|
|
|
public static Observable<TResult> ObserveValueChanged<TResult>(this VisualElement ve)
|
|
{
|
|
var subject = new Subject<TResult>();
|
|
void OnNext(ChangeEvent<TResult> evt)
|
|
{
|
|
subject.OnNext(evt.newValue);
|
|
}
|
|
|
|
ve.RegisterCallback<ChangeEvent<TResult>>(OnNext);
|
|
return subject.AsObservable();
|
|
}
|
|
private readonly struct TimeProviderInfo
|
|
{
|
|
public readonly FrameProvider FrameProvider;
|
|
public readonly TimeKind TimeKind;
|
|
|
|
public TimeProviderInfo(FrameProvider frameProvider, TimeKind timeKind)
|
|
{
|
|
FrameProvider = frameProvider;
|
|
TimeKind = timeKind;
|
|
}
|
|
}
|
|
|
|
private static readonly Dictionary<TimeProvider, TimeProviderInfo> timeProviderInfo = new()
|
|
{
|
|
{ UnityTimeProvider.Initialization, new (UnityFrameProvider.Initialization, TimeKind.Time) },
|
|
{ UnityTimeProvider.EarlyUpdate, new (UnityFrameProvider.EarlyUpdate, TimeKind.Time) },
|
|
{ UnityTimeProvider.FixedUpdate, new (UnityFrameProvider.FixedUpdate, TimeKind.Time) },
|
|
{ UnityTimeProvider.PreUpdate, new (UnityFrameProvider.PreUpdate, TimeKind.Time) },
|
|
{ UnityTimeProvider.Update, new (UnityFrameProvider.Update, TimeKind.Time) },
|
|
{ UnityTimeProvider.PreLateUpdate, new (UnityFrameProvider.PreLateUpdate, TimeKind.Time) },
|
|
{ UnityTimeProvider.PostLateUpdate, new (UnityFrameProvider.PostLateUpdate, TimeKind.Time) },
|
|
{ UnityTimeProvider.TimeUpdate, new (UnityFrameProvider.TimeUpdate, TimeKind.Time) },
|
|
|
|
{ UnityTimeProvider.InitializationIgnoreTimeScale, new (UnityFrameProvider.Initialization, TimeKind.UnscaledTime) },
|
|
{ UnityTimeProvider.EarlyUpdateIgnoreTimeScale, new (UnityFrameProvider.EarlyUpdate, TimeKind.UnscaledTime) },
|
|
{ UnityTimeProvider.FixedUpdateIgnoreTimeScale, new (UnityFrameProvider.FixedUpdate, TimeKind.UnscaledTime) },
|
|
{ UnityTimeProvider.PreUpdateIgnoreTimeScale, new (UnityFrameProvider.PreUpdate, TimeKind.UnscaledTime) },
|
|
{ UnityTimeProvider.UpdateIgnoreTimeScale, new (UnityFrameProvider.Update, TimeKind.UnscaledTime) },
|
|
{ UnityTimeProvider.PreLateUpdateIgnoreTimeScale, new (UnityFrameProvider.PreLateUpdate, TimeKind.UnscaledTime) },
|
|
{ UnityTimeProvider.PostLateUpdateIgnoreTimeScale, new (UnityFrameProvider.PostLateUpdate, TimeKind.UnscaledTime) },
|
|
{ UnityTimeProvider.TimeUpdateIgnoreTimeScale, new (UnityFrameProvider.TimeUpdate, TimeKind.UnscaledTime) },
|
|
|
|
{ UnityTimeProvider.InitializationRealtime, new (UnityFrameProvider.Initialization, TimeKind.Realtime) },
|
|
{ UnityTimeProvider.EarlyUpdateRealtime, new (UnityFrameProvider.EarlyUpdate, TimeKind.Realtime) },
|
|
{ UnityTimeProvider.FixedUpdateRealtime, new (UnityFrameProvider.FixedUpdate, TimeKind.Realtime) },
|
|
{ UnityTimeProvider.PreUpdateRealtime, new (UnityFrameProvider.PreUpdate, TimeKind.Realtime) },
|
|
{ UnityTimeProvider.UpdateRealtime, new (UnityFrameProvider.Update, TimeKind.Realtime) },
|
|
{ UnityTimeProvider.PreLateUpdateRealtime, new (UnityFrameProvider.PreLateUpdate, TimeKind.Realtime) },
|
|
{ UnityTimeProvider.PostLateUpdateRealtime, new (UnityFrameProvider.PostLateUpdate, TimeKind.Realtime) },
|
|
{ UnityTimeProvider.TimeUpdateRealtime, new (UnityFrameProvider.TimeUpdate, TimeKind.Realtime) }
|
|
};
|
|
|
|
public enum PlayerLoopTiming
|
|
{
|
|
Initialization,
|
|
EarlyUpdate,
|
|
FixedUpdate,
|
|
PreUpdate,
|
|
Update,
|
|
PreLateUpdate,
|
|
PostLateUpdate,
|
|
TimeUpdate,
|
|
PostFixedUpdate
|
|
}
|
|
|
|
private static readonly Dictionary<FrameProvider, PlayerLoopTiming> frameProviderTiming = new()
|
|
{
|
|
{ UnityFrameProvider.Initialization, PlayerLoopTiming.Initialization },
|
|
{ UnityFrameProvider.EarlyUpdate, PlayerLoopTiming.EarlyUpdate },
|
|
{ UnityFrameProvider.FixedUpdate, PlayerLoopTiming.FixedUpdate },
|
|
{ UnityFrameProvider.PreUpdate, PlayerLoopTiming.PreUpdate },
|
|
{ UnityFrameProvider.Update, PlayerLoopTiming.Update },
|
|
{ UnityFrameProvider.PreLateUpdate, PlayerLoopTiming.PreLateUpdate },
|
|
{ UnityFrameProvider.PostLateUpdate, PlayerLoopTiming.PostLateUpdate },
|
|
{ UnityFrameProvider.TimeUpdate, PlayerLoopTiming.TimeUpdate },
|
|
{ UnityFrameProvider.PostFixedUpdate, PlayerLoopTiming.PostFixedUpdate },
|
|
|
|
};
|
|
|
|
|
|
public static bool TryGetFrameProvider(this TimeProvider provider, out FrameProvider frameProvider)
|
|
{
|
|
if (timeProviderInfo.TryGetValue(provider, out var info))
|
|
{
|
|
frameProvider = info.FrameProvider;
|
|
return true;
|
|
}
|
|
else
|
|
{
|
|
frameProvider = null;
|
|
return false;
|
|
}
|
|
}
|
|
|
|
public static FrameProvider GetFrameProvider(this TimeProvider provider) =>
|
|
TryGetFrameProvider(provider, out var frameProvider) ? frameProvider : null;
|
|
|
|
public static FrameProvider GetFrameProvider(this PlayerLoopTiming timing) =>
|
|
timing switch
|
|
{
|
|
PlayerLoopTiming.Initialization => UnityFrameProvider.Initialization,
|
|
PlayerLoopTiming.EarlyUpdate => UnityFrameProvider.EarlyUpdate,
|
|
PlayerLoopTiming.FixedUpdate => UnityFrameProvider.FixedUpdate,
|
|
PlayerLoopTiming.PreUpdate => UnityFrameProvider.PreUpdate,
|
|
PlayerLoopTiming.Update => UnityFrameProvider.Update,
|
|
PlayerLoopTiming.PreLateUpdate => UnityFrameProvider.PreLateUpdate,
|
|
PlayerLoopTiming.PostLateUpdate => UnityFrameProvider.PostLateUpdate,
|
|
PlayerLoopTiming.TimeUpdate => UnityFrameProvider.TimeUpdate,
|
|
PlayerLoopTiming.PostFixedUpdate => UnityFrameProvider.PostFixedUpdate,
|
|
_ => throw new ArgumentOutOfRangeException(timing.ToString()),
|
|
};
|
|
|
|
public static bool TryGetDeltaTime(this TimeProvider provider, out float deltaTime)
|
|
{
|
|
if (
|
|
timeProviderInfo.TryGetValue(provider, out var info)
|
|
&& info.FrameProvider.TryGetFrameTiming(out var timing)
|
|
&& (info.TimeKind != TimeKind.Realtime)
|
|
)
|
|
{
|
|
deltaTime = (timing, info.TimeKind) switch
|
|
{
|
|
(PlayerLoopTiming.FixedUpdate, TimeKind.Time) => Time.fixedDeltaTime,
|
|
(PlayerLoopTiming.FixedUpdate, TimeKind.UnscaledTime) => Time.fixedUnscaledDeltaTime,
|
|
(_, TimeKind.Time) => Time.deltaTime,
|
|
(_, TimeKind.UnscaledTime) => Time.unscaledDeltaTime,
|
|
(_, _) => default
|
|
};
|
|
|
|
return true;
|
|
}
|
|
else
|
|
{
|
|
deltaTime = default;
|
|
return false;
|
|
}
|
|
}
|
|
|
|
public static float GetDeltaTime(this TimeProvider provider, float defaultValue = default) =>
|
|
TryGetDeltaTime(provider, out var deltaTime) ? deltaTime : defaultValue;
|
|
|
|
public static bool TryGetFrameTiming(this FrameProvider provider, out PlayerLoopTiming timing) =>
|
|
frameProviderTiming.TryGetValue(provider, out timing);
|
|
|
|
public static PlayerLoopTiming? GetFrameTiming(this UnityFrameProvider provider) =>
|
|
provider.TryGetFrameTiming(out var timing) ? timing : null;
|
|
|
|
public static TimeProvider GetTimeProvider(PlayerLoopTiming timing, TimeKind kind) =>
|
|
(timing, kind) switch
|
|
{
|
|
(PlayerLoopTiming.Initialization, TimeKind.Time) => UnityTimeProvider.Initialization,
|
|
(PlayerLoopTiming.Initialization, TimeKind.UnscaledTime) => UnityTimeProvider.InitializationIgnoreTimeScale,
|
|
(PlayerLoopTiming.Initialization, TimeKind.Realtime) => UnityTimeProvider.InitializationRealtime,
|
|
|
|
(PlayerLoopTiming.EarlyUpdate, TimeKind.Time) => UnityTimeProvider.EarlyUpdate,
|
|
(PlayerLoopTiming.EarlyUpdate, TimeKind.UnscaledTime) => UnityTimeProvider.EarlyUpdate,
|
|
(PlayerLoopTiming.EarlyUpdate, TimeKind.Realtime) => UnityTimeProvider.EarlyUpdate,
|
|
|
|
(PlayerLoopTiming.FixedUpdate, TimeKind.Time) => UnityTimeProvider.FixedUpdate,
|
|
(PlayerLoopTiming.FixedUpdate, TimeKind.UnscaledTime) => UnityTimeProvider.FixedUpdate,
|
|
(PlayerLoopTiming.FixedUpdate, TimeKind.Realtime) => UnityTimeProvider.FixedUpdate,
|
|
|
|
(PlayerLoopTiming.PreUpdate, TimeKind.Time) => UnityTimeProvider.PreUpdate,
|
|
(PlayerLoopTiming.PreUpdate, TimeKind.UnscaledTime) => UnityTimeProvider.PreUpdate,
|
|
(PlayerLoopTiming.PreUpdate, TimeKind.Realtime) => UnityTimeProvider.PreUpdate,
|
|
|
|
(PlayerLoopTiming.Update, TimeKind.Time) => UnityTimeProvider.Update,
|
|
(PlayerLoopTiming.Update, TimeKind.UnscaledTime) => UnityTimeProvider.Update,
|
|
(PlayerLoopTiming.Update, TimeKind.Realtime) => UnityTimeProvider.Update,
|
|
|
|
(PlayerLoopTiming.PreLateUpdate, TimeKind.Time) => UnityTimeProvider.PreLateUpdate,
|
|
(PlayerLoopTiming.PreLateUpdate, TimeKind.UnscaledTime) => UnityTimeProvider.PreLateUpdate,
|
|
(PlayerLoopTiming.PreLateUpdate, TimeKind.Realtime) => UnityTimeProvider.PreLateUpdate,
|
|
|
|
(PlayerLoopTiming.PostLateUpdate, TimeKind.Time) => UnityTimeProvider.PostLateUpdate,
|
|
(PlayerLoopTiming.PostLateUpdate, TimeKind.UnscaledTime) => UnityTimeProvider.PostLateUpdate,
|
|
(PlayerLoopTiming.PostLateUpdate, TimeKind.Realtime) => UnityTimeProvider.PostLateUpdate,
|
|
|
|
(PlayerLoopTiming.TimeUpdate, TimeKind.Time) => UnityTimeProvider.TimeUpdate,
|
|
(PlayerLoopTiming.TimeUpdate, TimeKind.UnscaledTime) => UnityTimeProvider.TimeUpdate,
|
|
(PlayerLoopTiming.TimeUpdate, TimeKind.Realtime) => UnityTimeProvider.TimeUpdate,
|
|
|
|
(var t, var k) => throw new ArgumentException("({t}, {k}) are not valid values.")
|
|
};
|
|
}
|
|
}
|
|
|