using Cysharp.Threading.Tasks.Internal; using System; using System.Collections.Generic; using System.Threading; namespace Cysharp.Threading.Tasks.Linq { public static partial class UniTaskAsyncEnumerable { public static IUniTaskAsyncEnumerable<TProperty> EveryValueChanged<TTarget, TProperty>(TTarget target, Func<TTarget, TProperty> propertySelector, PlayerLoopTiming monitorTiming = PlayerLoopTiming.Update, IEqualityComparer<TProperty> equalityComparer = null, bool cancelImmediately = false) where TTarget : class { var unityObject = target as UnityEngine.Object; var isUnityObject = target is UnityEngine.Object; // don't use (unityObject == null) if (isUnityObject) { return new EveryValueChangedUnityObject<TTarget, TProperty>(target, propertySelector, equalityComparer ?? UnityEqualityComparer.GetDefault<TProperty>(), monitorTiming, cancelImmediately); } else { return new EveryValueChangedStandardObject<TTarget, TProperty>(target, propertySelector, equalityComparer ?? UnityEqualityComparer.GetDefault<TProperty>(), monitorTiming, cancelImmediately); } } } internal sealed class EveryValueChangedUnityObject<TTarget, TProperty> : IUniTaskAsyncEnumerable<TProperty> { readonly TTarget target; readonly Func<TTarget, TProperty> propertySelector; readonly IEqualityComparer<TProperty> equalityComparer; readonly PlayerLoopTiming monitorTiming; readonly bool cancelImmediately; public EveryValueChangedUnityObject(TTarget target, Func<TTarget, TProperty> propertySelector, IEqualityComparer<TProperty> equalityComparer, PlayerLoopTiming monitorTiming, bool cancelImmediately) { this.target = target; this.propertySelector = propertySelector; this.equalityComparer = equalityComparer; this.monitorTiming = monitorTiming; this.cancelImmediately = cancelImmediately; } public IUniTaskAsyncEnumerator<TProperty> GetAsyncEnumerator(CancellationToken cancellationToken = default) { return new _EveryValueChanged(target, propertySelector, equalityComparer, monitorTiming, cancellationToken, cancelImmediately); } sealed class _EveryValueChanged : MoveNextSource, IUniTaskAsyncEnumerator<TProperty>, IPlayerLoopItem { readonly TTarget target; readonly UnityEngine.Object targetAsUnityObject; readonly IEqualityComparer<TProperty> equalityComparer; readonly Func<TTarget, TProperty> propertySelector; readonly CancellationToken cancellationToken; readonly CancellationTokenRegistration cancellationTokenRegistration; bool first; TProperty currentValue; bool disposed; public _EveryValueChanged(TTarget target, Func<TTarget, TProperty> propertySelector, IEqualityComparer<TProperty> equalityComparer, PlayerLoopTiming monitorTiming, CancellationToken cancellationToken, bool cancelImmediately) { this.target = target; this.targetAsUnityObject = target as UnityEngine.Object; this.propertySelector = propertySelector; this.equalityComparer = equalityComparer; this.cancellationToken = cancellationToken; this.first = true; if (cancelImmediately && cancellationToken.CanBeCanceled) { cancellationTokenRegistration = cancellationToken.RegisterWithoutCaptureExecutionContext(state => { var source = (_EveryValueChanged)state; source.completionSource.TrySetCanceled(source.cancellationToken); }, this); } TaskTracker.TrackActiveTask(this, 2); PlayerLoopHelper.AddAction(monitorTiming, this); } public TProperty Current => currentValue; public UniTask<bool> MoveNextAsync() { if (disposed) return CompletedTasks.False; completionSource.Reset(); if (cancellationToken.IsCancellationRequested) { completionSource.TrySetCanceled(cancellationToken); return new UniTask<bool>(this, completionSource.Version); } if (first) { first = false; if (targetAsUnityObject == null) { return CompletedTasks.False; } this.currentValue = propertySelector(target); return CompletedTasks.True; } return new UniTask<bool>(this, completionSource.Version); } public UniTask DisposeAsync() { if (!disposed) { cancellationTokenRegistration.Dispose(); disposed = true; TaskTracker.RemoveTracking(this); } return default; } public bool MoveNext() { if (disposed || targetAsUnityObject == null) { completionSource.TrySetResult(false); DisposeAsync().Forget(); return false; } if (cancellationToken.IsCancellationRequested) { completionSource.TrySetCanceled(cancellationToken); return false; } TProperty nextValue = default(TProperty); try { nextValue = propertySelector(target); if (equalityComparer.Equals(currentValue, nextValue)) { return true; } } catch (Exception ex) { completionSource.TrySetException(ex); DisposeAsync().Forget(); return false; } currentValue = nextValue; completionSource.TrySetResult(true); return true; } } } internal sealed class EveryValueChangedStandardObject<TTarget, TProperty> : IUniTaskAsyncEnumerable<TProperty> where TTarget : class { readonly WeakReference<TTarget> target; readonly Func<TTarget, TProperty> propertySelector; readonly IEqualityComparer<TProperty> equalityComparer; readonly PlayerLoopTiming monitorTiming; readonly bool cancelImmediately; public EveryValueChangedStandardObject(TTarget target, Func<TTarget, TProperty> propertySelector, IEqualityComparer<TProperty> equalityComparer, PlayerLoopTiming monitorTiming, bool cancelImmediately) { this.target = new WeakReference<TTarget>(target, false); this.propertySelector = propertySelector; this.equalityComparer = equalityComparer; this.monitorTiming = monitorTiming; this.cancelImmediately = cancelImmediately; } public IUniTaskAsyncEnumerator<TProperty> GetAsyncEnumerator(CancellationToken cancellationToken = default) { return new _EveryValueChanged(target, propertySelector, equalityComparer, monitorTiming, cancellationToken, cancelImmediately); } sealed class _EveryValueChanged : MoveNextSource, IUniTaskAsyncEnumerator<TProperty>, IPlayerLoopItem { readonly WeakReference<TTarget> target; readonly IEqualityComparer<TProperty> equalityComparer; readonly Func<TTarget, TProperty> propertySelector; readonly CancellationToken cancellationToken; readonly CancellationTokenRegistration cancellationTokenRegistration; bool first; TProperty currentValue; bool disposed; public _EveryValueChanged(WeakReference<TTarget> target, Func<TTarget, TProperty> propertySelector, IEqualityComparer<TProperty> equalityComparer, PlayerLoopTiming monitorTiming, CancellationToken cancellationToken, bool cancelImmediately) { this.target = target; this.propertySelector = propertySelector; this.equalityComparer = equalityComparer; this.cancellationToken = cancellationToken; this.first = true; if (cancelImmediately && cancellationToken.CanBeCanceled) { cancellationTokenRegistration = cancellationToken.RegisterWithoutCaptureExecutionContext(state => { var source = (_EveryValueChanged)state; source.completionSource.TrySetCanceled(source.cancellationToken); }, this); } TaskTracker.TrackActiveTask(this, 2); PlayerLoopHelper.AddAction(monitorTiming, this); } public TProperty Current => currentValue; public UniTask<bool> MoveNextAsync() { if (disposed) return CompletedTasks.False; completionSource.Reset(); if (cancellationToken.IsCancellationRequested) { completionSource.TrySetCanceled(cancellationToken); return new UniTask<bool>(this, completionSource.Version); } if (first) { first = false; if (!target.TryGetTarget(out var t)) { return CompletedTasks.False; } this.currentValue = propertySelector(t); return CompletedTasks.True; } return new UniTask<bool>(this, completionSource.Version); } public UniTask DisposeAsync() { if (!disposed) { cancellationTokenRegistration.Dispose(); disposed = true; TaskTracker.RemoveTracking(this); } return default; } public bool MoveNext() { if (disposed || !target.TryGetTarget(out var t)) { completionSource.TrySetResult(false); DisposeAsync().Forget(); return false; } if (cancellationToken.IsCancellationRequested) { completionSource.TrySetCanceled(cancellationToken); return false; } TProperty nextValue = default(TProperty); try { nextValue = propertySelector(t); if (equalityComparer.Equals(currentValue, nextValue)) { return true; } } catch (Exception ex) { completionSource.TrySetException(ex); DisposeAsync().Forget(); return false; } currentValue = nextValue; completionSource.TrySetResult(true); return true; } } } }