|
- using System;
- using System.Collections.Generic;
- using System.Linq;
- using System.Threading;
- using Cysharp.Threading.Tasks.Internal;
-
- namespace Cysharp.Threading.Tasks.Linq
- {
- public static partial class UniTaskAsyncEnumerable
- {
- public static IUniTaskAsyncEnumerable<T> Merge<T>(this IUniTaskAsyncEnumerable<T> first, IUniTaskAsyncEnumerable<T> second)
- {
- Error.ThrowArgumentNullException(first, nameof(first));
- Error.ThrowArgumentNullException(second, nameof(second));
-
- return new Merge<T>(new [] { first, second });
- }
-
- public static IUniTaskAsyncEnumerable<T> Merge<T>(this IUniTaskAsyncEnumerable<T> first, IUniTaskAsyncEnumerable<T> second, IUniTaskAsyncEnumerable<T> third)
- {
- Error.ThrowArgumentNullException(first, nameof(first));
- Error.ThrowArgumentNullException(second, nameof(second));
- Error.ThrowArgumentNullException(third, nameof(third));
-
- return new Merge<T>(new[] { first, second, third });
- }
-
- public static IUniTaskAsyncEnumerable<T> Merge<T>(this IEnumerable<IUniTaskAsyncEnumerable<T>> sources)
- {
- return sources is IUniTaskAsyncEnumerable<T>[] array
- ? new Merge<T>(array)
- : new Merge<T>(sources.ToArray());
- }
-
- public static IUniTaskAsyncEnumerable<T> Merge<T>(params IUniTaskAsyncEnumerable<T>[] sources)
- {
- return new Merge<T>(sources);
- }
- }
-
- internal sealed class Merge<T> : IUniTaskAsyncEnumerable<T>
- {
- readonly IUniTaskAsyncEnumerable<T>[] sources;
-
- public Merge(IUniTaskAsyncEnumerable<T>[] sources)
- {
- if (sources.Length <= 0)
- {
- Error.ThrowArgumentException("No source async enumerable to merge");
- }
- this.sources = sources;
- }
-
- public IUniTaskAsyncEnumerator<T> GetAsyncEnumerator(CancellationToken cancellationToken = default)
- => new _Merge(sources, cancellationToken);
-
- enum MergeSourceState
- {
- Pending,
- Running,
- Completed,
- }
-
- sealed class _Merge : MoveNextSource, IUniTaskAsyncEnumerator<T>
- {
- static readonly Action<object> GetResultAtAction = GetResultAt;
-
- readonly int length;
- readonly IUniTaskAsyncEnumerator<T>[] enumerators;
- readonly MergeSourceState[] states;
- readonly Queue<(T, Exception, bool)> queuedResult = new Queue<(T, Exception, bool)>();
- readonly CancellationToken cancellationToken;
-
- int moveNextCompleted;
-
- public T Current { get; private set; }
-
- public _Merge(IUniTaskAsyncEnumerable<T>[] sources, CancellationToken cancellationToken)
- {
- this.cancellationToken = cancellationToken;
- length = sources.Length;
- states = ArrayPool<MergeSourceState>.Shared.Rent(length);
- enumerators = ArrayPool<IUniTaskAsyncEnumerator<T>>.Shared.Rent(length);
- for (var i = 0; i < length; i++)
- {
- enumerators[i] = sources[i].GetAsyncEnumerator(cancellationToken);
- states[i] = (int)MergeSourceState.Pending;;
- }
- }
-
- public UniTask<bool> MoveNextAsync()
- {
- cancellationToken.ThrowIfCancellationRequested();
- completionSource.Reset();
- Interlocked.Exchange(ref moveNextCompleted, 0);
-
- if (HasQueuedResult() && Interlocked.CompareExchange(ref moveNextCompleted, 1, 0) == 0)
- {
- (T, Exception, bool) value;
- lock (states)
- {
- value = queuedResult.Dequeue();
- }
- var resultValue = value.Item1;
- var exception = value.Item2;
- var hasNext = value.Item3;
- if (exception != null)
- {
- completionSource.TrySetException(exception);
- }
- else
- {
- Current = resultValue;
- completionSource.TrySetResult(hasNext);
- }
- return new UniTask<bool>(this, completionSource.Version);
- }
-
- for (var i = 0; i < length; i++)
- {
- lock (states)
- {
- if (states[i] == MergeSourceState.Pending)
- {
- states[i] = MergeSourceState.Running;
- }
- else
- {
- continue;
- }
- }
- var awaiter = enumerators[i].MoveNextAsync().GetAwaiter();
- if (awaiter.IsCompleted)
- {
- GetResultAt(i, awaiter);
- }
- else
- {
- awaiter.SourceOnCompleted(GetResultAtAction, StateTuple.Create(this, i, awaiter));
- }
- }
- return new UniTask<bool>(this, completionSource.Version);
- }
-
- public async UniTask DisposeAsync()
- {
- for (var i = 0; i < length; i++)
- {
- await enumerators[i].DisposeAsync();
- }
-
- ArrayPool<MergeSourceState>.Shared.Return(states, true);
- ArrayPool<IUniTaskAsyncEnumerator<T>>.Shared.Return(enumerators, true);
- }
-
- static void GetResultAt(object state)
- {
- using (var tuple = (StateTuple<_Merge, int, UniTask<bool>.Awaiter>)state)
- {
- tuple.Item1.GetResultAt(tuple.Item2, tuple.Item3);
- }
- }
-
- void GetResultAt(int index, UniTask<bool>.Awaiter awaiter)
- {
- bool hasNext;
- bool completedAll;
- try
- {
- hasNext = awaiter.GetResult();
- }
- catch (Exception ex)
- {
- if (Interlocked.CompareExchange(ref moveNextCompleted, 1, 0) == 0)
- {
- completionSource.TrySetException(ex);
- }
- else
- {
- lock (states)
- {
- queuedResult.Enqueue((default, ex, default));
- }
- }
- return;
- }
-
- lock (states)
- {
- states[index] = hasNext ? MergeSourceState.Pending : MergeSourceState.Completed;
- completedAll = !hasNext && IsCompletedAll();
- }
- if (hasNext || completedAll)
- {
- if (Interlocked.CompareExchange(ref moveNextCompleted, 1, 0) == 0)
- {
- Current = enumerators[index].Current;
- completionSource.TrySetResult(!completedAll);
- }
- else
- {
- lock (states)
- {
- queuedResult.Enqueue((enumerators[index].Current, null, !completedAll));
- }
- }
- }
- }
-
- bool HasQueuedResult()
- {
- lock (states)
- {
- return queuedResult.Count > 0;
- }
- }
-
- bool IsCompletedAll()
- {
- lock (states)
- {
- for (var i = 0; i < length; i++)
- {
- if (states[i] != MergeSourceState.Completed)
- {
- return false;
- }
- }
- }
- return true;
- }
- }
- }
- }
|