diff --git a/src/UniTask/Assets/Plugins/UniTask/Runtime/Linq/UnityExtensions/EveryUpdate.cs b/src/UniTask/Assets/Plugins/UniTask/Runtime/Linq/UnityExtensions/EveryUpdate.cs index 51cae08..ddd0751 100644 --- a/src/UniTask/Assets/Plugins/UniTask/Runtime/Linq/UnityExtensions/EveryUpdate.cs +++ b/src/UniTask/Assets/Plugins/UniTask/Runtime/Linq/UnityExtensions/EveryUpdate.cs @@ -1,7 +1,4 @@ -using Cysharp.Threading.Tasks.Internal; -using System; -using System.Collections.Generic; -using System.Threading; +using System.Threading; namespace Cysharp.Threading.Tasks.Linq { diff --git a/src/UniTask/Assets/Plugins/UniTask/Runtime/Linq/UnityExtensions/EveryValueChanged.cs b/src/UniTask/Assets/Plugins/UniTask/Runtime/Linq/UnityExtensions/EveryValueChanged.cs new file mode 100644 index 0000000..b4faf6d --- /dev/null +++ b/src/UniTask/Assets/Plugins/UniTask/Runtime/Linq/UnityExtensions/EveryValueChanged.cs @@ -0,0 +1,241 @@ +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() + { + UnityEngine.Debug.Log("TRY_RESULT:" + target.TryGetTarget(out var _)); + 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; + } + } + } +} \ No newline at end of file diff --git a/src/UniTask/Assets/Plugins/UniTask/Runtime/Linq/UnityExtensions/EveryValueChanged.cs.meta b/src/UniTask/Assets/Plugins/UniTask/Runtime/Linq/UnityExtensions/EveryValueChanged.cs.meta new file mode 100644 index 0000000..9d2be70 --- /dev/null +++ b/src/UniTask/Assets/Plugins/UniTask/Runtime/Linq/UnityExtensions/EveryValueChanged.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 1ec39f1c41c305344854782c935ad354 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: