From 9c129bf7adc598a9e382aea3296d361242528c89 Mon Sep 17 00:00:00 2001 From: Thiago Santos Date: Fri, 15 Nov 2024 19:34:34 -0300 Subject: [PATCH] feat: creating branch options --- .../AsyncBranchingBuilder.cs | 8 +-- .../BaseBranchingBuilder.cs | 6 +- .../BranchRunOptions.cs | 8 +++ .../BranchingBuilder.cs | 8 +-- .../Internal/AsyncBranchContext.cs | 15 ++++ .../Internal/BranchContext.cs | 17 +++-- .../Internal/IBranchContext.cs | 9 +++ .../Internal/LinkedNode.cs | 10 ++- .../AsyncEnumerableExtensionsTest.cs | 72 +++++++++++++++++++ .../EnumerableExtensionsTest.cs | 33 +++++++++ 10 files changed, 168 insertions(+), 18 deletions(-) create mode 100644 src/Codibre.EnumerableExtensions.Branching/BranchRunOptions.cs create mode 100644 src/Codibre.EnumerableExtensions.Branching/Internal/AsyncBranchContext.cs create mode 100644 src/Codibre.EnumerableExtensions.Branching/Internal/IBranchContext.cs diff --git a/src/Codibre.EnumerableExtensions.Branching/AsyncBranchingBuilder.cs b/src/Codibre.EnumerableExtensions.Branching/AsyncBranchingBuilder.cs index 8f40503..1e1d96a 100644 --- a/src/Codibre.EnumerableExtensions.Branching/AsyncBranchingBuilder.cs +++ b/src/Codibre.EnumerableExtensions.Branching/AsyncBranchingBuilder.cs @@ -9,12 +9,12 @@ namespace Codibre.EnumerableExtensions.Branching; public sealed class AsyncBranchingBuilder(IAsyncEnumerable source) : BaseBranchingBuilder { private static readonly LinkedNode? _null = null; - internal override LinkedNode Iterate(int branchCount) + internal override LinkedNode Iterate(BranchRunOptions options) { var enumerator = source.GetAsyncEnumerator(); - return new(enumerator.Current, new( + return LinkedNode.Root( async (c) => await enumerator.MoveNextAsync() ? new(enumerator.Current, c) : _null, - branchCount - )); + options + ); } } \ No newline at end of file diff --git a/src/Codibre.EnumerableExtensions.Branching/BaseBranchingBuilder.cs b/src/Codibre.EnumerableExtensions.Branching/BaseBranchingBuilder.cs index 82caf54..d094c27 100644 --- a/src/Codibre.EnumerableExtensions.Branching/BaseBranchingBuilder.cs +++ b/src/Codibre.EnumerableExtensions.Branching/BaseBranchingBuilder.cs @@ -16,11 +16,11 @@ internal BaseBranchingBuilder Add(Func, Task> branch) return this; } - public async Task Run() + public async Task Run(BranchRunOptions? options = null) { - var node = Iterate(_branches.Count); + var node = Iterate(options ?? BranchRunOptions.Default); await Task.WhenAll(_branches.Select(x => x(node.GetBranchedIterable()))); } - internal abstract LinkedNode Iterate(int branchCount); + internal abstract LinkedNode Iterate(BranchRunOptions limit); } \ No newline at end of file diff --git a/src/Codibre.EnumerableExtensions.Branching/BranchRunOptions.cs b/src/Codibre.EnumerableExtensions.Branching/BranchRunOptions.cs new file mode 100644 index 0000000..d46b151 --- /dev/null +++ b/src/Codibre.EnumerableExtensions.Branching/BranchRunOptions.cs @@ -0,0 +1,8 @@ + +namespace Codibre.EnumerableExtensions.Branching; + +public readonly struct BranchRunOptions(int limit) +{ + public static readonly BranchRunOptions Default = new(ushort.MaxValue / 4); + public int Limit => limit; +} \ No newline at end of file diff --git a/src/Codibre.EnumerableExtensions.Branching/BranchingBuilder.cs b/src/Codibre.EnumerableExtensions.Branching/BranchingBuilder.cs index bb124c9..fcd17e3 100644 --- a/src/Codibre.EnumerableExtensions.Branching/BranchingBuilder.cs +++ b/src/Codibre.EnumerableExtensions.Branching/BranchingBuilder.cs @@ -8,12 +8,12 @@ namespace Codibre.EnumerableExtensions.Branching; public sealed class BranchingBuilder(IEnumerable source) : BaseBranchingBuilder { - internal override LinkedNode Iterate(int branchCount) + internal override LinkedNode Iterate(BranchRunOptions options) { var enumerator = source.GetEnumerator(); - return new(default!, new( + return LinkedNode.Root( (c) => new(enumerator.MoveNext() ? new LinkedNode(enumerator.Current, c) : null), - branchCount - )); + options + ); } } \ No newline at end of file diff --git a/src/Codibre.EnumerableExtensions.Branching/Internal/AsyncBranchContext.cs b/src/Codibre.EnumerableExtensions.Branching/Internal/AsyncBranchContext.cs new file mode 100644 index 0000000..0ea24c4 --- /dev/null +++ b/src/Codibre.EnumerableExtensions.Branching/Internal/AsyncBranchContext.cs @@ -0,0 +1,15 @@ +using System.Runtime.CompilerServices; +using System.Xml.XPath; + +namespace Codibre.EnumerableExtensions.Branching.Internal; + +internal sealed record AsyncBranchContext(Func, ValueTask?>> GetNext) + : IBranchContext +{ + + public ValueTask?> FillNext() + { + var result = GetNext(this); + return result.IsCompleted ? new(Task.Run(() => result.Result)) : result; + } +} \ No newline at end of file diff --git a/src/Codibre.EnumerableExtensions.Branching/Internal/BranchContext.cs b/src/Codibre.EnumerableExtensions.Branching/Internal/BranchContext.cs index c0c8188..f096f39 100644 --- a/src/Codibre.EnumerableExtensions.Branching/Internal/BranchContext.cs +++ b/src/Codibre.EnumerableExtensions.Branching/Internal/BranchContext.cs @@ -3,18 +3,23 @@ namespace Codibre.EnumerableExtensions.Branching.Internal; -internal sealed record BranchContext(Func, ValueTask?>> GetNext, int _branchCount) +internal sealed record BranchContext(Func, ValueTask?>> GetNext, BranchRunOptions options) + : IBranchContext { private ushort _count = 0; - private readonly ushort _limit = ushort.MaxValue / 4; - internal ValueTask?> FillNext() - => ++_count <= _limit ? GetNext(this) : GetYielded(); + public ValueTask?> FillNext() + { + var result = GetNext(this); + if (result.IsCompleted) return ++_count <= options.Limit ? result : GetYielded(result.Result); + _count = 0; + return result; + } - private async ValueTask?> GetYielded() + private async ValueTask?> GetYielded(LinkedNode? result) { _count = 0; await Task.Yield(); - return await GetNext(this); + return result; } } \ No newline at end of file diff --git a/src/Codibre.EnumerableExtensions.Branching/Internal/IBranchContext.cs b/src/Codibre.EnumerableExtensions.Branching/Internal/IBranchContext.cs new file mode 100644 index 0000000..402099e --- /dev/null +++ b/src/Codibre.EnumerableExtensions.Branching/Internal/IBranchContext.cs @@ -0,0 +1,9 @@ +using System.Runtime.CompilerServices; +using System.Xml.XPath; + +namespace Codibre.EnumerableExtensions.Branching.Internal; + +internal interface IBranchContext +{ + ValueTask?> FillNext(); +} \ No newline at end of file diff --git a/src/Codibre.EnumerableExtensions.Branching/Internal/LinkedNode.cs b/src/Codibre.EnumerableExtensions.Branching/Internal/LinkedNode.cs index e6245fc..f7afdb5 100644 --- a/src/Codibre.EnumerableExtensions.Branching/Internal/LinkedNode.cs +++ b/src/Codibre.EnumerableExtensions.Branching/Internal/LinkedNode.cs @@ -1,6 +1,14 @@ namespace Codibre.EnumerableExtensions.Branching.Internal; -internal sealed record LinkedNode(T Value, BranchContext Context) +internal sealed record LinkedNode(T Value, IBranchContext Context) { public Lazy?>> Next { get; } = new(Context.FillNext, LazyThreadSafetyMode.ExecutionAndPublication); + + public static LinkedNode Root(Func, ValueTask?>> getNext, BranchRunOptions options) + { + IBranchContext context = options.Limit <= 1 + ? new AsyncBranchContext(getNext) + : new BranchContext(getNext, options); + return new(default!, context); + } } \ No newline at end of file diff --git a/test/Codibre.EnumerableExtensions.Branching.Test/AsyncEnumerableExtensionsTest.cs b/test/Codibre.EnumerableExtensions.Branching.Test/AsyncEnumerableExtensionsTest.cs index 195117d..6c5b216 100644 --- a/test/Codibre.EnumerableExtensions.Branching.Test/AsyncEnumerableExtensionsTest.cs +++ b/test/Codibre.EnumerableExtensions.Branching.Test/AsyncEnumerableExtensionsTest.cs @@ -102,4 +102,76 @@ await enumerable.Branch() var refValue = steps[0]; steps.TakeWhile((x) => x == refValue).Count().Should().BeLessThan(total); } + + [Fact] + public async Task Should_Intercalate_The_Steps_Between_Every_Branch_When_EnumerableIsTrulyAsync() + { + // Arrange + var total = 1000; + var list = Enumerable.Range(0, total) + .ToAsyncEnumerable() + .SelectAwait(async (x) => + { + await Task.Yield(); + return x; + }); + List steps = []; + var enumerable = Op(list); + + // Act + await enumerable.Branch() + .Add(x => x.Select((x) => + { + steps.Add(1); + return x; + }).ToArrayAsync(), out var a) + .Add(x => x.Select((x) => + { + steps.Add(2); + return x; + }).ToArrayAsync(), out var b) + .Add(x => x.Select((x) => + { + steps.Add(3); + return x; + }).ToArrayAsync(), out var c) + .Run(); + + // Assert + var refValue = steps[0]; + steps.TakeWhile((x) => x == refValue).Count().Should().BeLessThan(total); + } + + [Fact] + public async Task Should_Intercalate_The_Steps_Between_Every_Branch_When_Using_AsyncContextBranch() + { + // Arrange + var total = 100; + var list = Enumerable.Range(0, total).ToAsyncEnumerable(); + List steps = []; + var enumerable = Op(list); + + // Act + await enumerable.Branch() + .Add(x => x.Select((x) => + { + steps.Add(1); + return x; + }).ToArrayAsync(), out var a) + .Add(x => x.Select((x) => + { + steps.Add(2); + return x; + }).ToArrayAsync(), out var b) + .Add(x => x.Select((x) => + { + steps.Add(3); + return x; + }).ToArrayAsync(), out var c) + .Run(new(1)); + + // Assert + var refValue = steps[0]; + steps.TakeWhile((x) => x == refValue).Count().Should().BeLessThan(total); + } } \ No newline at end of file diff --git a/test/Codibre.EnumerableExtensions.Branching.Test/EnumerableExtensionsTest.cs b/test/Codibre.EnumerableExtensions.Branching.Test/EnumerableExtensionsTest.cs index 478bfa4..22716c2 100644 --- a/test/Codibre.EnumerableExtensions.Branching.Test/EnumerableExtensionsTest.cs +++ b/test/Codibre.EnumerableExtensions.Branching.Test/EnumerableExtensionsTest.cs @@ -102,4 +102,37 @@ await enumerable.Branch() var refValue = steps[0]; steps.TakeWhile((x) => x == refValue).Count().Should().BeLessThan(total); } + + [Fact] + public async Task Should_Intercalate_The_Steps_Between_Every_Branch_When_Using_AsyncContextBranch() + { + // Arrange + var total = 100000; + var list = Enumerable.Range(0, total); + List steps = []; + var enumerable = Op(list); + + // Act + await enumerable.Branch() + .Add(x => x.Select((x) => + { + steps.Add(1); + return x; + }).ToArrayAsync(), out var a) + .Add(x => x.Select((x) => + { + steps.Add(2); + return x; + }).ToArrayAsync(), out var b) + .Add(x => x.Select((x) => + { + steps.Add(3); + return x; + }).ToArrayAsync(), out var c) + .Run(new(1)); + + // Assert + var refValue = steps[0]; + steps.TakeWhile((x) => x == refValue).Count().Should().BeLessThan(total); + } } \ No newline at end of file