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, 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(target, propertySelector, equalityComparer ?? UnityEqualityComparer.GetDefault(), monitorTiming, cancelImmediately); } else { return new EveryValueChangedStandardObject(target, propertySelector, equalityComparer ?? UnityEqualityComparer.GetDefault(), monitorTiming, cancelImmediately); } } } internal sealed class EveryValueChangedUnityObject : IUniTaskAsyncEnumerable { readonly TTarget target; readonly Func propertySelector; readonly IEqualityComparer equalityComparer; readonly PlayerLoopTiming monitorTiming; readonly bool cancelImmediately; public EveryValueChangedUnityObject(TTarget target, Func propertySelector, IEqualityComparer equalityComparer, PlayerLoopTiming monitorTiming, bool cancelImmediately) { this.target = target; this.propertySelector = propertySelector; this.equalityComparer = equalityComparer; this.monitorTiming = monitorTiming; this.cancelImmediately = cancelImmediately; } public IUniTaskAsyncEnumerator GetAsyncEnumerator(CancellationToken cancellationToken = default) { return new _EveryValueChanged(target, propertySelector, equalityComparer, monitorTiming, cancellationToken, cancelImmediately); } sealed class _EveryValueChanged : MoveNextSource, IUniTaskAsyncEnumerator, IPlayerLoopItem { readonly TTarget target; readonly UnityEngine.Object targetAsUnityObject; readonly IEqualityComparer equalityComparer; readonly Func propertySelector; readonly CancellationToken cancellationToken; readonly CancellationTokenRegistration cancellationTokenRegistration; bool first; TProperty currentValue; bool disposed; public _EveryValueChanged(TTarget target, Func propertySelector, IEqualityComparer 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 MoveNextAsync() { if (disposed) return CompletedTasks.False; completionSource.Reset(); if (cancellationToken.IsCancellationRequested) { completionSource.TrySetCanceled(cancellationToken); return new UniTask(this, completionSource.Version); } if (first) { first = false; if (targetAsUnityObject == null) { return CompletedTasks.False; } this.currentValue = propertySelector(target); return CompletedTasks.True; } return new UniTask(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 : IUniTaskAsyncEnumerable where TTarget : class { readonly WeakReference target; readonly Func propertySelector; readonly IEqualityComparer equalityComparer; readonly PlayerLoopTiming monitorTiming; readonly bool cancelImmediately; public EveryValueChangedStandardObject(TTarget target, Func propertySelector, IEqualityComparer equalityComparer, PlayerLoopTiming monitorTiming, bool cancelImmediately) { this.target = new WeakReference(target, false); this.propertySelector = propertySelector; this.equalityComparer = equalityComparer; this.monitorTiming = monitorTiming; this.cancelImmediately = cancelImmediately; } public IUniTaskAsyncEnumerator GetAsyncEnumerator(CancellationToken cancellationToken = default) { return new _EveryValueChanged(target, propertySelector, equalityComparer, monitorTiming, cancellationToken, cancelImmediately); } sealed class _EveryValueChanged : MoveNextSource, IUniTaskAsyncEnumerator, IPlayerLoopItem { readonly WeakReference target; readonly IEqualityComparer equalityComparer; readonly Func propertySelector; readonly CancellationToken cancellationToken; readonly CancellationTokenRegistration cancellationTokenRegistration; bool first; TProperty currentValue; bool disposed; public _EveryValueChanged(WeakReference target, Func propertySelector, IEqualityComparer 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 MoveNextAsync() { if (disposed) return CompletedTasks.False; completionSource.Reset(); if (cancellationToken.IsCancellationRequested) { completionSource.TrySetCanceled(cancellationToken); return new UniTask(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(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; } } } }