diff --git a/src/UniTask.NetCore/Linq/AsyncEnumeratorBase.cs b/src/UniTask.NetCore/Linq/AsyncEnumeratorBase.cs index 6813a7d..d9ccd84 100644 --- a/src/UniTask.NetCore/Linq/AsyncEnumeratorBase.cs +++ b/src/UniTask.NetCore/Linq/AsyncEnumeratorBase.cs @@ -46,8 +46,21 @@ namespace Cysharp.Threading.Tasks.Linq return false; } } - } + protected bool TryGetResult(UniTask.Awaiter awaiter) + { + try + { + awaiter.GetResult(); + return true; + } + catch (Exception ex) + { + completionSource.TrySetException(ex); + return false; + } + } + } public abstract class AsyncEnumeratorBase : MoveNextSource, IUniTaskAsyncEnumerator { diff --git a/src/UniTask.NetCore/Linq/SelectMany.cs b/src/UniTask.NetCore/Linq/SelectMany.cs index 6f5ceee..da8924a 100644 --- a/src/UniTask.NetCore/Linq/SelectMany.cs +++ b/src/UniTask.NetCore/Linq/SelectMany.cs @@ -1,775 +1,279 @@ -namespace Cysharp.Threading.Tasks.Linq +using Cysharp.Threading.Tasks.Internal; +using System; +using System.Threading; + +namespace Cysharp.Threading.Tasks.Linq { - internal sealed class SelectMany + public static partial class UniTaskAsyncEnumerable { + + public static IUniTaskAsyncEnumerable SelectMany(this IUniTaskAsyncEnumerable source, Func> selector) + { + Error.ThrowArgumentNullException(source, nameof(source)); + Error.ThrowArgumentNullException(selector, nameof(selector)); + + return new SelectMany(source, selector); + } + + public static IUniTaskAsyncEnumerable SelectMany(this IUniTaskAsyncEnumerable source, Func> selector) + { + throw new NotImplementedException(); + } + + public static IUniTaskAsyncEnumerable SelectMany(this IUniTaskAsyncEnumerable source, Func> collectionSelector, Func resultSelector) + { + throw new NotImplementedException(); + } + + public static IUniTaskAsyncEnumerable SelectMany(this IUniTaskAsyncEnumerable source, Func> collectionSelector, Func resultSelector) + { + throw new NotImplementedException(); + } + + public static IUniTaskAsyncEnumerable SelectManyAwait(this IUniTaskAsyncEnumerable source, Func>> selector) + { + throw new NotImplementedException(); + } + + public static IUniTaskAsyncEnumerable SelectManyAwait(this IUniTaskAsyncEnumerable source, Func>> selector) + { + throw new NotImplementedException(); + } + + public static IUniTaskAsyncEnumerable SelectManyAwait(this IUniTaskAsyncEnumerable source, Func>> collectionSelector, Func> resultSelector) + { + throw new NotImplementedException(); + } + + public static IUniTaskAsyncEnumerable SelectManyAwait(this IUniTaskAsyncEnumerable source, Func>> collectionSelector, Func> resultSelector) + { + throw new NotImplementedException(); + } + + public static IUniTaskAsyncEnumerable SelectManyAwaitWithCancellation(this IUniTaskAsyncEnumerable source, Func>> selector) + { + throw new NotImplementedException(); + } + + public static IUniTaskAsyncEnumerable SelectManyAwaitWithCancellation(this IUniTaskAsyncEnumerable source, Func>> selector) + { + throw new NotImplementedException(); + } + + public static IUniTaskAsyncEnumerable SelectManyAwaitWithCancellation(this IUniTaskAsyncEnumerable source, Func>> collectionSelector, Func> resultSelector) + { + throw new NotImplementedException(); + } + + public static IUniTaskAsyncEnumerable SelectManyAwaitWithCancellation(this IUniTaskAsyncEnumerable source, Func>> collectionSelector, Func> resultSelector) + { + throw new NotImplementedException(); + } } + internal sealed class SelectMany : IUniTaskAsyncEnumerable + { + readonly IUniTaskAsyncEnumerable source; + readonly Func> selector; + public SelectMany(IUniTaskAsyncEnumerable source, Func> selector) + { + this.source = source; + this.selector = selector; + } + + public IUniTaskAsyncEnumerator GetAsyncEnumerator(CancellationToken cancellationToken = default) + { + return new Enumerator(source, selector, cancellationToken); + } + + sealed class Enumerator : MoveNextSource, IUniTaskAsyncEnumerator + { + static readonly Action sourceMoveNextCoreDelegate = SourceMoveNextCore; + static readonly Action selectedSourceMoveNextCoreDelegate = SeletedSourceMoveNextCore; + static readonly Action selectedEnumeratorDisposeAsyncCoreDelegate = SelectedEnumeratorDisposeAsyncCore; + + readonly IUniTaskAsyncEnumerable source; + readonly Func> selector; + CancellationToken cancellationToken; + + IUniTaskAsyncEnumerator sourceEnumerator; + IUniTaskAsyncEnumerator selectedEnumerator; + UniTask.Awaiter sourceAwaiter; + UniTask.Awaiter selectedAwaiter; + UniTask.Awaiter selectedDisposeAsyncAwaiter; + + public Enumerator(IUniTaskAsyncEnumerable source, Func> selector, CancellationToken cancellationToken) + { + this.source = source; + this.selector = selector; + this.cancellationToken = cancellationToken; + } + + public TResult Current { get; private set; } + + public UniTask MoveNextAsync() + { + completionSource.Reset(); + + // iterate selected field + if (selectedEnumerator != null) + { + MoveNextSelected(); + } + else + { + // iterate source field + if (sourceEnumerator == null) + { + sourceEnumerator = source.GetAsyncEnumerator(cancellationToken); + } + MoveNextSource(); + } + + return new UniTask(this, completionSource.Version); + } + + void MoveNextSource() + { + try + { + sourceAwaiter = sourceEnumerator.MoveNextAsync().GetAwaiter(); + } + catch (Exception ex) + { + completionSource.TrySetException(ex); + return; + } + + if (sourceAwaiter.IsCompleted) + { + SourceMoveNextCore(this); + } + else + { + sourceAwaiter.SourceOnCompleted(sourceMoveNextCoreDelegate, this); + } + } + + void MoveNextSelected() + { + try + { + selectedAwaiter = selectedEnumerator.MoveNextAsync().GetAwaiter(); + } + catch (Exception ex) + { + completionSource.TrySetException(ex); + return; + } + + if (selectedAwaiter.IsCompleted) + { + SeletedSourceMoveNextCore(this); + } + else + { + selectedAwaiter.SourceOnCompleted(selectedSourceMoveNextCoreDelegate, this); + } + } + + static void SourceMoveNextCore(object state) + { + var self = (Enumerator)state; + + if (self.TryGetResult(self.selectedAwaiter, out var result)) + { + if (result) + { + try + { + var current = self.sourceEnumerator.Current; + self.selectedEnumerator = self.selector(current).GetAsyncEnumerator(self.cancellationToken); + } + catch (Exception ex) + { + self.completionSource.TrySetException(ex); + return; + } + + self.MoveNextSelected(); // iterated selected source. + } + else + { + self.completionSource.TrySetResult(false); + } + } + } + + static void SeletedSourceMoveNextCore(object state) + { + var self = (Enumerator)state; + + if (self.TryGetResult(self.selectedAwaiter, out var result)) + { + if (result) + { + try + { + self.Current = self.selectedEnumerator.Current; + } + catch (Exception ex) + { + self.completionSource.TrySetException(ex); + return; + } + + self.completionSource.TrySetResult(true); + } + else + { + // dispose selected source and try iterate source. + try + { + self.selectedDisposeAsyncAwaiter = self.selectedEnumerator.DisposeAsync().GetAwaiter(); + } + catch (Exception ex) + { + self.completionSource.TrySetException(ex); + return; + } + if (self.selectedDisposeAsyncAwaiter.IsCompleted) + { + SelectedEnumeratorDisposeAsyncCore(self); + } + else + { + self.selectedDisposeAsyncAwaiter.SourceOnCompleted(selectedEnumeratorDisposeAsyncCoreDelegate, self); + } + } + } + } + + static void SelectedEnumeratorDisposeAsyncCore(object state) + { + var self = (Enumerator)state; + + if (self.TryGetResult(self.selectedDisposeAsyncAwaiter)) + { + self.selectedEnumerator = null; + self.selectedAwaiter = default; + + self.MoveNextSource(); // iterate next source + } + } + + public async UniTask DisposeAsync() + { + if (selectedEnumerator != null) + { + await selectedEnumerator.DisposeAsync(); + } + if (sourceEnumerator != null) + { + await sourceEnumerator.DisposeAsync(); + } + } + } + } } - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/UniTask.NetCore/Linq/_FileMaker.cs b/src/UniTask.NetCore/Linq/_FileMaker.cs index 41494b1..14d51ad 100644 --- a/src/UniTask.NetCore/Linq/_FileMaker.cs +++ b/src/UniTask.NetCore/Linq/_FileMaker.cs @@ -305,11 +305,6 @@ namespace ___Dummy - public static IUniTaskAsyncEnumerable Reverse(this IUniTaskAsyncEnumerable source) - { - throw new NotImplementedException(); - } - public static IUniTaskAsyncEnumerable SelectMany(this IUniTaskAsyncEnumerable source, Func> selector)