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 EveryValueChanged(TTarget target, Func propertySelector, PlayerLoopTiming monitorTiming = PlayerLoopTiming.Update, IEqualityComparer equalityComparer = null) 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(target, propertySelector, equalityComparer ?? UnityEqualityComparer.GetDefault(), monitorTiming); } else { return new EveryValueChangedStandardObject(target, propertySelector, equalityComparer ?? UnityEqualityComparer.GetDefault(), monitorTiming); } } } internal sealed class EveryValueChangedUnityObject : IUniTaskAsyncEnumerable { readonly TTarget target; readonly Func propertySelector; readonly IEqualityComparer equalityComparer; readonly PlayerLoopTiming monitorTiming; public EveryValueChangedUnityObject(TTarget target, Func propertySelector, IEqualityComparer equalityComparer, PlayerLoopTiming monitorTiming) { this.target = target; this.propertySelector = propertySelector; this.equalityComparer = equalityComparer; this.monitorTiming = monitorTiming; } public IUniTaskAsyncEnumerator GetAsyncEnumerator(CancellationToken cancellationToken = default) { return new _EveryValueChanged(target, propertySelector, equalityComparer, monitorTiming, cancellationToken); } sealed class _EveryValueChanged : MoveNextSource, IUniTaskAsyncEnumerator, IPlayerLoopItem { readonly TTarget target; readonly UnityEngine.Object targetAsUnityObject; readonly IEqualityComparer equalityComparer; readonly Func propertySelector; CancellationToken cancellationToken; bool first; TProperty currentValue; bool disposed; public _EveryValueChanged(TTarget target, Func propertySelector, IEqualityComparer equalityComparer, PlayerLoopTiming monitorTiming, CancellationToken cancellationToken) { this.target = target; this.targetAsUnityObject = target as UnityEngine.Object; this.propertySelector = propertySelector; this.equalityComparer = equalityComparer; this.cancellationToken = cancellationToken; this.first = true; TaskTracker.TrackActiveTask(this, 2); PlayerLoopHelper.AddAction(monitorTiming, this); } public TProperty Current => currentValue; public UniTask MoveNextAsync() { // return false instead of throw if (disposed || cancellationToken.IsCancellationRequested) return CompletedTasks.False; if (first) { first = false; if (targetAsUnityObject == null) { return CompletedTasks.False; } this.currentValue = propertySelector(target); return CompletedTasks.True; } completionSource.Reset(); return new UniTask(this, completionSource.Version); } public UniTask DisposeAsync() { if (!disposed) { disposed = true; TaskTracker.RemoveTracking(this); } return default; } public bool MoveNext() { if (disposed || cancellationToken.IsCancellationRequested || targetAsUnityObject == null) // destroyed = cancel. { completionSource.TrySetResult(false); DisposeAsync().Forget(); 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 : IUniTaskAsyncEnumerable where TTarget : class { readonly WeakReference target; readonly Func propertySelector; readonly IEqualityComparer equalityComparer; readonly PlayerLoopTiming monitorTiming; public EveryValueChangedStandardObject(TTarget target, Func propertySelector, IEqualityComparer equalityComparer, PlayerLoopTiming monitorTiming) { this.target = new WeakReference(target, false); this.propertySelector = propertySelector; this.equalityComparer = equalityComparer; this.monitorTiming = monitorTiming; } public IUniTaskAsyncEnumerator GetAsyncEnumerator(CancellationToken cancellationToken = default) { return new _EveryValueChanged(target, propertySelector, equalityComparer, monitorTiming, cancellationToken); } sealed class _EveryValueChanged : MoveNextSource, IUniTaskAsyncEnumerator, IPlayerLoopItem { readonly WeakReference target; readonly IEqualityComparer equalityComparer; readonly Func propertySelector; CancellationToken cancellationToken; bool first; TProperty currentValue; bool disposed; public _EveryValueChanged(WeakReference target, Func propertySelector, IEqualityComparer equalityComparer, PlayerLoopTiming monitorTiming, CancellationToken cancellationToken) { this.target = target; this.propertySelector = propertySelector; this.equalityComparer = equalityComparer; this.cancellationToken = cancellationToken; this.first = true; TaskTracker.TrackActiveTask(this, 2); PlayerLoopHelper.AddAction(monitorTiming, this); } public TProperty Current => currentValue; public UniTask MoveNextAsync() { if (disposed || cancellationToken.IsCancellationRequested) return CompletedTasks.False; if (first) { first = false; if (!target.TryGetTarget(out var t)) { return CompletedTasks.False; } this.currentValue = propertySelector(t); return CompletedTasks.True; } completionSource.Reset(); return new UniTask(this, completionSource.Version); } public UniTask DisposeAsync() { if (!disposed) { disposed = true; TaskTracker.RemoveTracking(this); } return default; } public bool MoveNext() { if (disposed || cancellationToken.IsCancellationRequested || !target.TryGetTarget(out var t)) { completionSource.TrySetResult(false); DisposeAsync().Forget(); 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; } } } }