diff --git a/src/LightWorkFlowManager.Tests/ProgressCompositorTest.cs b/src/LightWorkFlowManager.Tests/ProgressCompositorTest.cs new file mode 100644 index 0000000..04ca90f --- /dev/null +++ b/src/LightWorkFlowManager.Tests/ProgressCompositorTest.cs @@ -0,0 +1,41 @@ +using System; +using DC.LightWorkFlowManager.Monitors; +using Microsoft.VisualStudio.TestTools.UnitTesting; + +using MSTest.Extensions.Contracts; + +namespace LightWorkFlowManager.Tests; + +[TestClass] +public class ProgressCompositorTest +{ + [ContractTestCase] + public void TestSubProgressReport() + { + "自身也上报进度的情况下,存在两个各占一半比例的子进度,两个子进度和自身进度都为百分之50时,实际进度为百分之75的值".Test(() => + { + var progressCompositor = new ProgressCompositor("Xxx"); + var registerSubProgressCompositors = progressCompositor.RegisterSubProgressCompositors(new SubProgressCompositorInfo("1", 50), new SubProgressCompositorInfo("2", 50)); + + // 自身进度为百分之50时 + var selfProcess = 0.5; + progressCompositor.Report(new ProgressPercentage(selfProcess)); + + // 两个子进度为百分之50时 + registerSubProgressCompositors[0].Report(new ProgressPercentage(0.5)); + registerSubProgressCompositors[1].Report(new ProgressPercentage(0.5)); + + Assert.IsTrue(Math.Abs(progressCompositor.CurrentProgress.Value - (selfProcess + 0.5 / 2)) < 0.01); + }); + + "存在两个各占一半比例的子进度,首个子进度为百分之50时,实际进度为百分之25的值".Test(() => + { + var progressCompositor = new ProgressCompositor("Xxx"); + var registerSubProgressCompositors = progressCompositor.RegisterSubProgressCompositors(new SubProgressCompositorInfo("1", 50), new SubProgressCompositorInfo("2", 50)); + + registerSubProgressCompositors[0].Report(new ProgressPercentage(0.5)); + + Assert.IsTrue(Math.Abs(progressCompositor.CurrentProgress.Value - 0.5 / 2) < 0.01); + }); + } +} \ No newline at end of file diff --git a/src/LightWorkFlowManager/LightWorkFlowManager.csproj.DotSettings b/src/LightWorkFlowManager/LightWorkFlowManager.csproj.DotSettings new file mode 100644 index 0000000..e54616d --- /dev/null +++ b/src/LightWorkFlowManager/LightWorkFlowManager.csproj.DotSettings @@ -0,0 +1,2 @@ + + True \ No newline at end of file diff --git a/src/LightWorkFlowManager/Monitors/Progress/ProgressCompositor.cs b/src/LightWorkFlowManager/Monitors/Progress/ProgressCompositor.cs new file mode 100644 index 0000000..bb218ee --- /dev/null +++ b/src/LightWorkFlowManager/Monitors/Progress/ProgressCompositor.cs @@ -0,0 +1,148 @@ +using System; +using System.Collections.Generic; + +namespace DC.LightWorkFlowManager.Monitors; + +/// +/// 进度合成器,允许包含多个子进度 +/// +/// +/// 规则: +/// - 可以注册多个子进度,每个子进度都有自己的权值 +/// - 子进度的进度贡献到上级进度时,将叠加上子进度自己的权值。如占比一半权值的子进度,就最多只贡献一半的进度 +/// - 自身可以报告进度。当自身报告进度时,除去自身进度外的剩余进度将由子进度贡献。如带两个各占一半权值的子进度时,两个子进度当前进度都是百分之50的值,自身进度报告是百分之50时,当前进度=自身进度+(剩余进度x (子进度1.进度值x子进度1.权重比例 + 子进度2.进度值x子进度2.权重比例)) = 自身进度(0.5)+(剩余进度(1-0.5)x (子进度1.进度值(0.5)x子进度1.权重比例(0.5) + 子进度2.进度值(0.5)x子进度2.权重比例(0.5)))=0.5+(0.5x(0.5x0.5+0.5x0.5))=0.75 +public class ProgressCompositor +{ + /// + /// 创建进度合成器 + /// + /// + public ProgressCompositor(string name) + { + Name = name; + _subProgressCompositorReportedEventHandler = SubProgressCompositor_Reported; + } + + /// + /// 进度名 + /// + public string Name { get; } + + /// + /// 当前进度值 + /// + public ProgressPercentage CurrentProgress + { + get + { + if (_subProgressCompositorDictionary.Count > 0) + { + double selfValue = _selfProgressPercentage.Value; + + var value = selfValue; + + var remain = ProgressPercentage.MaxValue.Value - selfValue; + if (remain > 0) + { + var totalWeight = 0d; + foreach (var subProgressCompositorInfo in _subProgressCompositorDictionary.Keys) + { + totalWeight += subProgressCompositorInfo.Weight; + } + + var subValue = 0d; + + foreach (var (subInfo, subProgress) in _subProgressCompositorDictionary) + { + subValue += subProgress.CurrentProgress.Value * (subInfo.Weight / totalWeight); + } + + value += remain * subValue; + } + + value = Math.Clamp(value, 0, 1); + + return new ProgressPercentage(value); + } + + return _selfProgressPercentage; + } + } + + public IReadOnlyList> RegisterSubProgressCompositors( + params SubProgressCompositorInfo[] subList) => RegisterSubProgressCompositors((IReadOnlyList) subList); + + public IReadOnlyList> RegisterSubProgressCompositors(IReadOnlyList subList) + { + var subProgressList = new ProgressCompositor[subList.Count]; + + _subProgressCompositorDictionary.EnsureCapacity(_subProgressCompositorDictionary.Count + subList.Count); + + for (var i = 0; i < subList.Count; i++) + { + var info = subList[i]; + var progressCompositor = RegisterSubProgressCompositor(info); + subProgressList[i] = progressCompositor; + } + + return subProgressList; + } + + public ProgressCompositor RegisterSubProgressCompositor(SubProgressCompositorInfo subProgressCompositor) + { + var progressCompositor = new ProgressCompositor(subProgressCompositor.Name); + _subProgressCompositorDictionary[subProgressCompositor] = progressCompositor; + + progressCompositor.Reported += _subProgressCompositorReportedEventHandler; + return progressCompositor; + } + + private void SubProgressCompositor_Reported(object? sender, ProgressReportedEventArgument e) + { + _currentValue = e.Value; + + OnReported(); + } + + private readonly Dictionary> _subProgressCompositorDictionary = new(); + + /// + /// 上报进度 + /// + /// + /// + public void Report(ProgressPercentage percentage, T? value = default) + { + _selfProgressPercentage = percentage; + _currentValue = value; + + OnReported(); + } + + private ProgressPercentage _selfProgressPercentage = default; + private T? _currentValue; + + private readonly EventHandler> _subProgressCompositorReportedEventHandler; + + /// + /// 上报增量进度,将叠加上当前的进度 + /// + /// + /// + public void ReportIncreased(ProgressPercentage percentage, T? value = default) + { + var currentPercentageValue = _selfProgressPercentage.Value + percentage.Value; + currentPercentageValue = Math.Clamp(currentPercentageValue, 0, 1); + Report(new ProgressPercentage(currentPercentageValue), value); + } + + private void OnReported() + { + Reported?.Invoke(this, new ProgressReportedEventArgument(CurrentProgress, _currentValue)); + } + + /// + /// 进度变更时触发 + /// + public event EventHandler>? Reported; +} \ No newline at end of file diff --git a/src/LightWorkFlowManager/Monitors/Progress/ProgressPercentage.cs b/src/LightWorkFlowManager/Monitors/Progress/ProgressPercentage.cs new file mode 100644 index 0000000..343e896 --- /dev/null +++ b/src/LightWorkFlowManager/Monitors/Progress/ProgressPercentage.cs @@ -0,0 +1,30 @@ +using System; + +namespace DC.LightWorkFlowManager.Monitors; + +/// +/// 进度百分比 0-1 范围 +/// +public readonly record struct ProgressPercentage +{ + /// + /// 进度百分比 0-1 范围 + /// + /// + public ProgressPercentage(double value) + { + Value = value; + + if (value < 0 || value > 1) + { + throw new ArgumentOutOfRangeException(nameof(value), + $"Range of {nameof(ProgressPercentage)} is between 0 to 1"); + } + } + + public double Value { get; init; } + + public static ProgressPercentage MinValue => new ProgressPercentage(0); + + public static ProgressPercentage MaxValue => new ProgressPercentage(1); +} \ No newline at end of file diff --git a/src/LightWorkFlowManager/Monitors/Progress/ProgressReportedEventArgument.cs b/src/LightWorkFlowManager/Monitors/Progress/ProgressReportedEventArgument.cs new file mode 100644 index 0000000..2864f41 --- /dev/null +++ b/src/LightWorkFlowManager/Monitors/Progress/ProgressReportedEventArgument.cs @@ -0,0 +1,3 @@ +namespace DC.LightWorkFlowManager.Monitors; + +public readonly record struct ProgressReportedEventArgument(ProgressPercentage ProgressPercentage, T? Value); \ No newline at end of file diff --git a/src/LightWorkFlowManager/Monitors/Progress/SubProgressCompositorInfo.cs b/src/LightWorkFlowManager/Monitors/Progress/SubProgressCompositorInfo.cs new file mode 100644 index 0000000..03b177c --- /dev/null +++ b/src/LightWorkFlowManager/Monitors/Progress/SubProgressCompositorInfo.cs @@ -0,0 +1,3 @@ +namespace DC.LightWorkFlowManager.Monitors; + +public readonly record struct SubProgressCompositorInfo(string Name, double Weight); \ No newline at end of file