Progress.cs 3.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384
  1. namespace MeloongCore;
  2. public class ProgressProvider {
  3. // 主项进度
  4. private (double actual, double skiped, double splited) progressParts = (0, 0, 0);
  5. private double progressSum => progressParts.actual + progressParts.skiped + progressParts.splited;
  6. /// <summary>
  7. /// 将当前进度设置为指定值。
  8. /// 若 <paramref name="skiped"/> 为 true,这段进度的增量值将被计为跳过:当前观测进度不会增加,后续观测进度将增加地更快。
  9. /// </summary>
  10. public void Set(double value, bool skiped = false) {
  11. value = value.Clamp(0, 1);
  12. double delta = value - progressSum;
  13. if (delta < 0) { // 等比减少
  14. progressParts = (progressParts.actual * value / progressSum,
  15. progressParts.skiped * value / progressSum,
  16. progressParts.splited * value / progressSum);
  17. } else if (skiped) {
  18. progressParts.skiped += delta;
  19. } else {
  20. progressParts.actual += delta;
  21. }
  22. }
  23. /// <summary>
  24. /// 将当前进度增加指定值。
  25. /// 若 <paramref name="skiped"/> 为 true,这段进度的增量值将被计为跳过:当前观测进度不会增加,后续观测进度将增加地更快。
  26. /// </summary>
  27. public void Add(double value, bool skiped = false)
  28. => Set(value + progressSum, skiped);
  29. // 子项进度
  30. private readonly List<(double weight, ProgressProvider sub)> childrens = [];
  31. /// <summary>
  32. /// 拆分一个子项:当该子项完成时,当前进度将增至 <paramref name="value"/>。
  33. /// </summary>
  34. public ProgressProvider SplitTo(double value)
  35. => SplitBy(value - progressSum).First();
  36. /// <summary>
  37. /// 拆分多个子项,每个子项都占据对应指定的进度量。
  38. /// <para/>例如,调用 <c>SplitBy(0.2, 0.3)</c> 将返回两个子项,分别占据总进度的 20%、30%。
  39. /// </summary>
  40. public List<ProgressProvider> SplitBy(params double[] percentages) {
  41. var totalWeight = percentages.Sum().Clamp(0, 1);
  42. var totalPercentage = (totalWeight + progressSum).Clamp(0, 1) - progressSum;
  43. progressParts.splited += totalPercentage;
  44. return percentages.Select(percentage => {
  45. if (percentage <= 0) throw new ArgumentException("子项进度必须为正数");
  46. var weight = percentage / totalWeight * totalPercentage;
  47. var sub = new ProgressProvider();
  48. childrens.Add((weight, sub));
  49. return sub;
  50. }).ToList();
  51. }
  52. private (double actual, double skiped) GetTotalProgress() {
  53. (double actual, double skiped) current = (progressParts.actual, progressParts.skiped);
  54. if (childrens.Any()) { // 将子项的进度加入主项
  55. var perWeight = progressParts.splited / childrens.Sum(c => c.weight);
  56. childrens.ForEach(c => {
  57. var (subActual, subSkiped) = c.sub.GetTotalProgress();
  58. current.actual += subActual * perWeight * c.weight;
  59. current.skiped += subSkiped * perWeight * c.weight;
  60. });
  61. }
  62. return current;
  63. }
  64. // 观测
  65. private (double actual, double skiped) observedProgress = (0, 0);
  66. private double incrementProgress = 0;
  67. /// <summary>
  68. /// 获取一个在多次观测之间必定单调递增的进度值,范围为 [0, 1]。
  69. /// </summary>
  70. public double Observe() {
  71. (double actual, double skiped) current = GetTotalProgress();
  72. if (current == observedProgress) return incrementProgress; // 未改变
  73. if (current.actual >= observedProgress.actual) { // 进度增加
  74. incrementProgress += (1 - incrementProgress) *
  75. (current.actual - observedProgress.actual) / (1 - observedProgress.actual - current.skiped);
  76. }
  77. observedProgress = current;
  78. return incrementProgress;
  79. }
  80. }