tgstation-server 6.12.0
The /tg/station 13 server suite
Loading...
Searching...
No Matches
JobProgressReporter.cs
Go to the documentation of this file.
1using System;
2
3using Microsoft.Extensions.Logging;
4using Microsoft.Extensions.Logging.Abstractions;
5
7
9{
13 public sealed class JobProgressReporter : IDisposable
14 {
18 public string? StageName
19 {
20 get => stageName;
21 set
22 {
23 if (stageName == value)
24 return;
25
26 stageName = value;
28 }
29 }
30
34 readonly ILogger<JobProgressReporter> logger;
35
39 readonly Action<string?, double?> callback;
40
44 string? stageName;
45
49 double? lastProgress;
50
55
60
66 : this(
67 NullLogger<JobProgressReporter>.Instance,
68 null,
69 (_, _) => { },
70 false)
71 {
72 }
73
80 public JobProgressReporter(ILogger<JobProgressReporter> logger, string? stageName, Action<string?, double?> callback)
81 : this(
82 logger,
85 true)
86 {
87 }
88
96 private JobProgressReporter(ILogger<JobProgressReporter> logger, string? stageName, Action<string?, double?> callback, bool setStageName)
97 {
98 this.logger = logger ?? throw new ArgumentNullException(nameof(logger));
99 this.callback = callback ?? throw new ArgumentNullException(nameof(callback));
100 if (setStageName)
101 {
103 }
104 else
105 {
106 this.stageName = stageName;
107 }
108
109 logger.LogDebug("Job progress reporter created. Stage: {stageName}", stageName ?? "(null)");
110 }
111
113 public void Dispose()
114 {
115 if (sectionReservations.HasValue)
116 if (sectionReservations.Value != 1.0)
117 {
118 // not an error, processes can throw
119 sectionReservations = null;
120 }
121 else if (sectionProgression < 1.0)
122 {
123 logger.LogError(
124 new InvalidOperationException($"Parent progress reporter has child sections that didn't complete! Current: {sectionProgression}"),
125 "TGS BUG: Progress reporter children didn't complete!");
126 sectionReservations = null;
127 }
128
129 if (!sectionReservations.HasValue)
131 }
132
137 public void ReportProgress(double? progress)
138 {
139 if (sectionReservations.HasValue)
140 if (progress == 0)
141 {
142 // might be a stage reset
143 sectionReservations = null;
144 }
145 else
146 {
147 logger.LogError(
148 new InvalidOperationException("Progress reporter is reporting progress with existing nested sections!"),
149 "TGS BUG: A progress reporter is using mixed local and nested progress, this is not supported");
150 }
151
152 var clampedProgress = progress;
153 if (progress.HasValue)
154 if (progress > 1 || progress < 0)
155 {
156 logger.LogError(
157 new ArgumentOutOfRangeException(nameof(progress), progress, "Progress must be a value from 0-1!"),
158 "TGS BUG: Invalid progress value for stage {stageName}",
159 StageName ?? "(null)");
160 clampedProgress = null;
161 }
162 else
163 sectionProgression = progress.Value;
164
165 callback(StageName, clampedProgress);
166 lastProgress = clampedProgress;
167 }
168
176 public JobProgressReporter CreateSection(string? newStageName, double percentage)
177 {
178 if (percentage > 1 || percentage < 0)
179 {
180 logger.LogError(
181 new ArgumentOutOfRangeException(nameof(percentage), percentage, "Percentage must be a value from 0-1!"),
182 "TGS BUG: Invalid percentage value for stage {newStageName}! Clamping...",
183 newStageName ?? "(null)");
184
185 percentage = Math.Min(Math.Max(percentage, 0.0), 1.0);
186 }
187
188 if (!sectionReservations.HasValue)
189 {
190 if (sectionProgression != 0)
191 {
192 logger.LogError(
193 new InvalidOperationException("Progress reporter is creating a section with local progress!"),
194 "TGS BUG: A progress reporter is using mixed local and nested progress, this is not supported");
195 }
196
198 }
199
200 // floating point >.<
201 if (percentage + sectionReservations.Value > 1.0001)
202 {
203 var remainingPercentage = 1.0 - sectionReservations.Value;
204 logger.LogError(
205 "Stage {newStageName} is overbudgeted ({budget}/{remainingPercentage})! Clamping...",
206 newStageName,
207 percentage,
208 remainingPercentage);
209 percentage = remainingPercentage;
210 }
211
212 Math.Min(sectionReservations.Value + percentage, 1);
213
214 var childLocalProgress = 0.0;
215 var newReporter = new JobProgressReporter(
216 logger,
217 newStageName,
218 (currentStage, progress) =>
219 {
220 currentStage ??= StageName;
221 if (!progress.HasValue)
222 {
223 callback(currentStage, null);
224 return;
225 }
226
227 var progressWithoutChild = sectionProgression - childLocalProgress;
228 childLocalProgress = progress.Value * percentage;
229
230 // floating point >.<
231 sectionProgression = Math.Min(progressWithoutChild + childLocalProgress, 1);
232 if (sectionProgression > 9.9999)
234
235 callback(currentStage, sectionProgression);
236 },
237 false);
238
239 newReporter.ReportProgress(0);
240 return newReporter;
241 }
242 }
243}
double? lastProgress
The last progress value pushed into the callback.
double sectionProgression
The total progress reported so far in this section.
string? StageName
The name of the current stage.
readonly Action< string?, double?> callback
Progress reporter callback taking a description of what the job is currently doing and the (optional)...
JobProgressReporter CreateSection(string? newStageName, double percentage)
Create a subsection of the JobProgressReporter with its optional own stage name.
JobProgressReporter(ILogger< JobProgressReporter > logger, string? stageName, Action< string?, double?> callback)
Initializes a new instance of the JobProgressReporter class.
readonly ILogger< JobProgressReporter > logger
The ILogger<TCategoryName> for the JobProgressReporter.
JobProgressReporter(ILogger< JobProgressReporter > logger, string? stageName, Action< string?, double?> callback, bool setStageName)
Initializes a new instance of the JobProgressReporter class.
JobProgressReporter()
Initializes a new instance of the JobProgressReporter class.
double? sectionReservations
The total progress reserved for use in this section.
void ReportProgress(double? progress)
Report progress.
string? stageName
Backing field for StageName.
Represents an Api.Models.Instance in the database.
Definition Instance.cs:11