diff --git a/PowerKit.Tests/ProgressMuxerTests.cs b/PowerKit.Tests/ProgressMuxerTests.cs new file mode 100644 index 0000000..d8a6854 --- /dev/null +++ b/PowerKit.Tests/ProgressMuxerTests.cs @@ -0,0 +1,59 @@ +using FluentAssertions; +using Gress; +using PowerKit; +using Xunit; + +namespace PowerKit.Tests; + +public class ProgressMuxerTests +{ + [Fact] + public void CreateInput_Test() + { + // Arrange + var progress = new ProgressCollector(); + var muxer = new ProgressMuxer(progress); + var input = muxer.CreateInput(); + + // Act + input.Report(0.5); + input.Report(1.0); + + // Assert + progress.GetValues().Should().Equal(0.5, 1.0); + } + + [Fact] + public void CreateInput_Weight_Test() + { + // Arrange + var progress = new ProgressCollector(); + var muxer = new ProgressMuxer(progress); + var input = muxer.CreateInput(weight: 0.5); + + // Act + input.Report(0.5); + input.Report(1.0); + + // Assert + progress.GetValues().Should().Equal(0.25, 0.5); + } + + [Fact] + public void CreateInput_MultipleInputs_Test() + { + // Arrange + var progress = new ProgressCollector(); + var muxer = new ProgressMuxer(progress); + var input1 = muxer.CreateInput(weight: 0.6); + var input2 = muxer.CreateInput(weight: 0.4); + + // Act + input1.Report(1.0); + input2.Report(1.0); + + // Assert + var values = progress.GetValues(); + values[^1].Should().BeApproximately(1.0, 1e-10); + } +} diff --git a/PowerKit/DelegateProgress.cs b/PowerKit/DelegateProgress.cs new file mode 100644 index 0000000..fbbd7cc --- /dev/null +++ b/PowerKit/DelegateProgress.cs @@ -0,0 +1,20 @@ +#if NET40_OR_GREATER || NETSTANDARD || NET +#nullable enable +using System; +using System.Diagnostics.CodeAnalysis; + +namespace PowerKit; + +/// +/// Provides a lightweight implementation that delegates +/// progress reporting to an action. +/// +#if !POWERKIT_INCLUDE_COVERAGE +[ExcludeFromCodeCoverage] +#endif +internal sealed class DelegateProgress(Action report) : IProgress +{ + /// + public void Report(T value) => report(value); +} +#endif diff --git a/PowerKit/ProgressMuxer.cs b/PowerKit/ProgressMuxer.cs new file mode 100644 index 0000000..21be350 --- /dev/null +++ b/PowerKit/ProgressMuxer.cs @@ -0,0 +1,43 @@ +#if NET40_OR_GREATER || NETSTANDARD || NET +#nullable enable +using System; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.Linq; +using System.Threading; + +namespace PowerKit; + +/// +/// Multiplexes multiple reporters into a single output reporter, +/// combining weighted progress values from multiple sources. +/// +#if !POWERKIT_INCLUDE_COVERAGE +[ExcludeFromCodeCoverage] +#endif +internal class ProgressMuxer(IProgress output) +{ + private readonly Lock _lock = new(); + private readonly Dictionary _splitTotals = new(); + + private int _splitCount; + + /// + /// Creates a new progress input with the specified weight. + /// Progress reported to this input is multiplied by + /// and combined with all other inputs before being forwarded to the output. + /// + public IProgress CreateInput(double weight = 1) + { + var index = Interlocked.Increment(ref _splitCount) - 1; + return new DelegateProgress(p => + { + using (_lock.EnterScope()) + { + _splitTotals[index] = weight * p; + output.Report(_splitTotals.Values.Sum()); + } + }); + } +} +#endif