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 Compose(this Observable source, Func, Observable> 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 EveryUpdateDelta(this TimeProvider provider) { provider ??= UnityTimeProvider.Update; return provider.TryGetFrameProvider(out var frameProvider) ? Observable.EveryUpdate(frameProvider) .Select(_ => provider.GetDeltaTime(Time.deltaTime)) : Observable.Empty(); } private static readonly Lazy> floatComparer = new( () => FEqualityComparer.Create(KMath.IsApproxEqual) ); public static Observable SmoothDamp( this Observable source, Func 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 SmoothDamp( this Observable source, Func valueGetter, float smoothTime) => SmoothDamp(source, valueGetter, smoothTime, Mathf.Infinity, UnityTimeProvider.Update); public static Observable SmoothDamp( this Observable source, Func valueGetter, float smoothTime, float maxSpeed) => SmoothDamp(source, valueGetter, smoothTime, maxSpeed, UnityTimeProvider.Update); public static Observable SmoothDamp( this Observable source, Func valueGetter, float smoothTime, TimeProvider provider) => SmoothDamp(source, valueGetter, smoothTime, Mathf.Infinity, provider); public static TSource MinBy(this IEnumerable source, Func keySelector) where TKey : IComparable { 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 SelectSwitch(this Observable source, Func> selector) => source.Select(selector).Switch(); public static Observable SwitchIfElse(this Observable source, Func> ifTrue, Observable ifFalse) => SwitchIf(ifFalse, source, ifTrue); public static Observable SwitchIfElse(this Observable source, Observable ifTrue, Observable ifFalse) => SwitchIf(ifFalse, source, ifTrue); public static Observable SwitchIf(this Observable source, Observable selectionSource, Observable other) => selectionSource.SelectSwitch(value => value ? other : source); public static Observable SwitchIf(this Observable source, Observable selectionSource, Func> other) => selectionSource.SelectSwitch(value => value ? other() : source); public static Observable WhereEquals(this Observable source, T value) where T : IEquatable => source.Where(value.Equals); public static Observable WhereTrue(this Observable source) => source.Where(Func.Identity); public static Observable WhereFalse(this Observable source) => WhereEquals(source, false); public static Observable ObserveBool(this T component, Func 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 ObserveAwake(this T component) where T : MonoBehaviour => component.ObserveBool(c => c.didAwake); public static Observable ObserveStart(this T component) where T : MonoBehaviour => component.ObserveBool(c => c.didStart); public static Observable SelectOrDefault(this Observable source, Func selector) => source.Select(value => EqualityComparer.Default.Equals(value, default) ? default : selector(value)); public static Observable ObserveValueChanged(this VisualElement ve) { var subject = new Subject(); void OnNext(ChangeEvent evt) { subject.OnNext(evt.newValue); } ve.RegisterCallback>(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 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 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.") }; } }