using Cysharp.Threading.Tasks.Internal; using System; using System.Threading; namespace Cysharp.Threading.Tasks.Linq { 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(); } } } } }