From beebf59ff6812da8a63c911afd3be5edeeca192a Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 17 Apr 2026 12:36:30 +0000 Subject: [PATCH 1/7] feat: add ProgressMuxer utility Agent-Logs-Url: https://github.com/Tyrrrz/PowerKit/sessions/5703499f-a02f-4b43-9578-ef84f7514bc2 Co-authored-by: Tyrrrz <1935960+Tyrrrz@users.noreply.github.com> --- PowerKit.Tests/ProgressMuxerTests.cs | 61 ++++++++++++++++++++++++++++ PowerKit/ProgressMuxer.cs | 61 ++++++++++++++++++++++++++++ 2 files changed, 122 insertions(+) create mode 100644 PowerKit.Tests/ProgressMuxerTests.cs create mode 100644 PowerKit/ProgressMuxer.cs diff --git a/PowerKit.Tests/ProgressMuxerTests.cs b/PowerKit.Tests/ProgressMuxerTests.cs new file mode 100644 index 0000000..89a6605 --- /dev/null +++ b/PowerKit.Tests/ProgressMuxerTests.cs @@ -0,0 +1,61 @@ +using System; +using System.Collections.Generic; +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, precision: 1e-10); + } +} diff --git a/PowerKit/ProgressMuxer.cs b/PowerKit/ProgressMuxer.cs new file mode 100644 index 0000000..fd8c4df --- /dev/null +++ b/PowerKit/ProgressMuxer.cs @@ -0,0 +1,61 @@ +#if NET40_OR_GREATER || NETSTANDARD || NET +#nullable enable +using System; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.Linq; + +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 +{ + private readonly object _lock = new(); + private readonly IProgress _output; + private readonly Dictionary _splitTotals; + + private int _splitCount; + + /// + /// Initializes a new instance of that forwards combined + /// progress to the specified output reporter. + /// + public ProgressMuxer(IProgress output) + { + _output = output; + _splitTotals = new Dictionary(); + } + + /// + /// 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 = _splitCount++; + return new DelegateProgress(p => + { + lock (_lock) + { + _splitTotals[index] = weight * p; + _output.Report(_splitTotals.Values.Sum()); + } + }); + } +} + +#if !POWERKIT_INCLUDE_COVERAGE +[ExcludeFromCodeCoverage] +#endif +file sealed class DelegateProgress(Action report) : IProgress +{ + public void Report(double value) => report(value); +} +#endif From d12d7cfeea57a778e2d3431d180eb27f99d38632 Mon Sep 17 00:00:00 2001 From: Oleksii Holub <1935960+Tyrrrz@users.noreply.github.com> Date: Fri, 17 Apr 2026 15:51:57 +0300 Subject: [PATCH 2/7] Update ProgressMuxerTests.cs --- PowerKit.Tests/ProgressMuxerTests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/PowerKit.Tests/ProgressMuxerTests.cs b/PowerKit.Tests/ProgressMuxerTests.cs index 89a6605..1ed1888 100644 --- a/PowerKit.Tests/ProgressMuxerTests.cs +++ b/PowerKit.Tests/ProgressMuxerTests.cs @@ -56,6 +56,6 @@ public void CreateInput_MultipleInputs_Test() // Assert var values = progress.GetValues(); - values[^1].Should().BeApproximately(1.0, precision: 1e-10); + values[^1].Should().BeApproximately(1.0, 1e-10); } } From 550b8e23b4d39849ad8cc1fc4f58978e029982f2 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 17 Apr 2026 12:56:02 +0000 Subject: [PATCH 3/7] refactor: use Lock object and EnterScope in ProgressMuxer Agent-Logs-Url: https://github.com/Tyrrrz/PowerKit/sessions/d43e56fe-c8df-4051-86c7-922770a45791 Co-authored-by: Tyrrrz <1935960+Tyrrrz@users.noreply.github.com> --- PowerKit/ProgressMuxer.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/PowerKit/ProgressMuxer.cs b/PowerKit/ProgressMuxer.cs index fd8c4df..88bf776 100644 --- a/PowerKit/ProgressMuxer.cs +++ b/PowerKit/ProgressMuxer.cs @@ -4,6 +4,7 @@ using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.Linq; +using System.Threading; namespace PowerKit; @@ -16,7 +17,7 @@ namespace PowerKit; #endif internal class ProgressMuxer { - private readonly object _lock = new(); + private readonly Lock _lock = new(); private readonly IProgress _output; private readonly Dictionary _splitTotals; @@ -42,7 +43,7 @@ public IProgress CreateInput(double weight = 1) var index = _splitCount++; return new DelegateProgress(p => { - lock (_lock) + using (_lock.EnterScope()) { _splitTotals[index] = weight * p; _output.Report(_splitTotals.Values.Sum()); From 5f92d254ca75339ac54211212c287b4fb45f8bbf Mon Sep 17 00:00:00 2001 From: Oleksii Holub <1935960+Tyrrrz@users.noreply.github.com> Date: Fri, 17 Apr 2026 15:58:16 +0300 Subject: [PATCH 4/7] Update PowerKit.Tests/ProgressMuxerTests.cs Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- PowerKit.Tests/ProgressMuxerTests.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/PowerKit.Tests/ProgressMuxerTests.cs b/PowerKit.Tests/ProgressMuxerTests.cs index 1ed1888..d8a6854 100644 --- a/PowerKit.Tests/ProgressMuxerTests.cs +++ b/PowerKit.Tests/ProgressMuxerTests.cs @@ -1,5 +1,3 @@ -using System; -using System.Collections.Generic; using FluentAssertions; using Gress; using PowerKit; From 1b660fa15282d885877926d495949865c4547ac9 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 17 Apr 2026 12:59:41 +0000 Subject: [PATCH 5/7] refactor: primary constructor and generic DelegateProgress in ProgressMuxer Agent-Logs-Url: https://github.com/Tyrrrz/PowerKit/sessions/662d174c-e8b7-4753-b581-fed6a70a59d3 Co-authored-by: Tyrrrz <1935960+Tyrrrz@users.noreply.github.com> --- PowerKit/ProgressMuxer.cs | 23 ++++++----------------- 1 file changed, 6 insertions(+), 17 deletions(-) diff --git a/PowerKit/ProgressMuxer.cs b/PowerKit/ProgressMuxer.cs index 88bf776..c55221c 100644 --- a/PowerKit/ProgressMuxer.cs +++ b/PowerKit/ProgressMuxer.cs @@ -15,24 +15,13 @@ namespace PowerKit; #if !POWERKIT_INCLUDE_COVERAGE [ExcludeFromCodeCoverage] #endif -internal class ProgressMuxer +internal class ProgressMuxer(IProgress output) { private readonly Lock _lock = new(); - private readonly IProgress _output; - private readonly Dictionary _splitTotals; + private readonly Dictionary _splitTotals = new(); private int _splitCount; - /// - /// Initializes a new instance of that forwards combined - /// progress to the specified output reporter. - /// - public ProgressMuxer(IProgress output) - { - _output = output; - _splitTotals = new Dictionary(); - } - /// /// Creates a new progress input with the specified weight. /// Progress reported to this input is multiplied by @@ -41,12 +30,12 @@ public ProgressMuxer(IProgress output) public IProgress CreateInput(double weight = 1) { var index = _splitCount++; - return new DelegateProgress(p => + return new DelegateProgress(p => { using (_lock.EnterScope()) { _splitTotals[index] = weight * p; - _output.Report(_splitTotals.Values.Sum()); + output.Report(_splitTotals.Values.Sum()); } }); } @@ -55,8 +44,8 @@ public IProgress CreateInput(double weight = 1) #if !POWERKIT_INCLUDE_COVERAGE [ExcludeFromCodeCoverage] #endif -file sealed class DelegateProgress(Action report) : IProgress +file sealed class DelegateProgress(Action report) : IProgress { - public void Report(double value) => report(value); + public void Report(T value) => report(value); } #endif From 0bd1f9f19c5e7a53c47c038c861e45a406e4b226 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 17 Apr 2026 13:30:17 +0000 Subject: [PATCH 6/7] fix: use Interlocked.Increment for thread-safe _splitCount in CreateInput Agent-Logs-Url: https://github.com/Tyrrrz/PowerKit/sessions/4f4ae9d9-b51c-4c66-8ce5-55f669f5c5f3 Co-authored-by: Tyrrrz <1935960+Tyrrrz@users.noreply.github.com> --- PowerKit/ProgressMuxer.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/PowerKit/ProgressMuxer.cs b/PowerKit/ProgressMuxer.cs index c55221c..4b55ad5 100644 --- a/PowerKit/ProgressMuxer.cs +++ b/PowerKit/ProgressMuxer.cs @@ -29,7 +29,7 @@ internal class ProgressMuxer(IProgress output) /// public IProgress CreateInput(double weight = 1) { - var index = _splitCount++; + var index = Interlocked.Increment(ref _splitCount) - 1; return new DelegateProgress(p => { using (_lock.EnterScope()) From a7b0e906745f750b221e42b23c6160e6295acf51 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 17 Apr 2026 13:36:22 +0000 Subject: [PATCH 7/7] refactor: extract DelegateProgress into its own file as a standalone utility type Agent-Logs-Url: https://github.com/Tyrrrz/PowerKit/sessions/ad02ed49-8949-4ff7-a3e4-873109152869 Co-authored-by: Tyrrrz <1935960+Tyrrrz@users.noreply.github.com> --- PowerKit/DelegateProgress.cs | 20 ++++++++++++++++++++ PowerKit/ProgressMuxer.cs | 8 -------- 2 files changed, 20 insertions(+), 8 deletions(-) create mode 100644 PowerKit/DelegateProgress.cs 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 index 4b55ad5..21be350 100644 --- a/PowerKit/ProgressMuxer.cs +++ b/PowerKit/ProgressMuxer.cs @@ -40,12 +40,4 @@ public IProgress CreateInput(double weight = 1) }); } } - -#if !POWERKIT_INCLUDE_COVERAGE -[ExcludeFromCodeCoverage] -#endif -file sealed class DelegateProgress(Action report) : IProgress -{ - public void Report(T value) => report(value); -} #endif