Stroika Library 3.0d16
 
Loading...
Searching...
No Matches
ProgressMonitor.h
1/*
2 * Copyright(c) Sophist Solutions, Inc. 1990-2025. All rights reserved
3 */
4#ifndef _Stroika_Foundation_Execution_ProgressMonitor_h_
5#define _Stroika_Foundation_Execution_ProgressMonitor_h_ 1
6
7#include "Stroika/Foundation/StroikaPreComp.h"
8
9#include <mutex>
10#include <string>
11
12#include "Stroika/Foundation/Common/Common.h"
13#include "Stroika/Foundation/Containers/Sequence.h"
18
19/**
20 * TODO:
21 * \note Code-Status: <a href="Code-Status.md#Alpha">Alpha</a>
22 *
23 * @todo BETTER INTEGRATE STROIKA THREAD/CANCELATION WITH THIS FORM OF CANCELATION (OK now - but could be better
24 * especially if we could Abort on the main thread / any thread; then could lose the cancel flag in our
25 * rep and just use the thread local rep for cancelation - same notaiton as thread cancel) ; and always have
26 * a worker thread object.
27 * --LGP 2023-12-06
28 *
29 * @todo MAYBE allow copy - but just document its a smart pointer and copy just increments refcount.
30 * NOT copy by value semantics.
31 *
32 * @todo Look at stuff I did I HeatthFrame progress code to automatically increase displayed progress values
33 * to monotonically when i have guesses - like for network activities. That should somehow integrate
34 * with this - maybe as an optional 'updater' module/adapter?
35 *
36 * @todo Not sure if we need to decompose into more component classes and use subtyping for stuff like
37 * thread support. It seems it mgiht work like this, but I'm sure it's not yet elegant.
38 */
39
41
42 /**
43 * ProgressMonitor is the basic interface used for progress tracking. Progress tracking both
44 * measures progress, and supports the notion of canceling. The reason progress and cancelability
45 * are tied together is that its only for 'long lived' tasks one might want to measure the progress of,
46 * that one might want to allow canceling.
47 *
48 * A progress Monitor owns a list of ChangedCallbackType that can be associated with the ProgressMonitor.
49 * These callbacks are each called whenever the progress is known to have changed. Note - this callback
50 * uses std::function<>, and the callback runs on an arbitrary thread, not necessarily the one used
51 * by the creator of the ProgressMonitor.
52 *
53 * A ProgressMonitor also has associated with it an arbitrary number of Updater objects. These are the things
54 * that one hands to processes (not OS processes, but long lived procedures or threads) which they then
55 * callback to to notify of their progress.
56 *
57 * An updater is retrieved from the root ProgressMonitor, and it has 'scope' of 0..1. You can construct
58 * sub-updaters by passing a base Updater to the Updater constructor along with a subrange (inside 0..1).
59 * That way - if you have sub-procedures, they can report on their progress (0..1) and that is mapped to a subrange
60 * of the overall progress.
61 *
62 * Note - in order to help debug the progress values, ProgressMonitor strictly enforces some rules. Progress
63 * starts at zero, and successive values are non-degreasing. This means the progress bar grows monotonically (though
64 * not necessarily smoothly). In order to avoid common floating point bugs with rounding errors, ProgressMonitor
65 * employs Math::PinToSpecialPoint ().
66 *
67 * Users of ProgressMonitor can call "Cancel" on the ProgressMonitor at any point. This records a cancelation
68 * in the Updater object, so that when it calls Updater::SetProgress () - and
69 * perhaps in other situations (see thread support below) - the progress will terminate immediately.
70 *
71 * ProgressMonitor supports having the underlying long-lived-task happen EITHER in the current thread, or in
72 * another thread (the ProgressMonitor and related code is fully threadsafe).
73 *
74 * If ProgressMonitor is constructed with an argument Thread (optional) - then attempts to Cancel the ProgressMonitor
75 * will also send an Abort() command to the associated thread. This can accelerate - depending less on co-operative
76 * checking - to cancel the long-lived progress-monitored process.
77 *
78 * \par Example Usage
79 * \code
80 * void CompileData (const filesystem::path& sourceFile, ProgressMonitor::Updater progress)
81 * {
82 * ...
83 * progress.SetCurrentTaskInfo ("Compiling Strings"_k);
84 * progress.SetProgress (0.3f);
85 * SubTask_ (ProgressMonitor::Updater{progress, 0.50f, 0.60f}); // also may do progress calls (0..1 in subtask mapped into .5 to .6 range here)
86 * ...
87 * }
88 * ProgressMonitor progMon{[lastProg = 0u, lastDesc = String{}] (const ProgressMonitor& pm) mutable {
89 * unsigned int curProgPct = static_cast<unsigned int> (pm.GetProgress () * 100);
90 * Assert (0 <= curProgPct and curProgPct <= 100);
91 * if (lastDesc != pm.GetCurrentTaskInfo ().fName) {
92 * cout << "\r\n";
93 * lastDesc = pm.GetCurrentTaskInfo ().fName;
94 * cout << "\t" << lastDesc << "\r\n";
95 * }
96 * if (lastProg != curProgPct) {
97 * cout << "\r";
98 * cout << Format ("\t\t{}% complete "_f, curProgPct); /// spaces to wipe-out rest of line
99 * lastProg = curProgPct;
100 * }
101 * }};
102 * CompileData (file, progMon); // CompileData() takes updater, but ProgressMonitor has conversion op to create Updater...
103 * \endcode
104 */
105 class ProgressMonitor final {
106 public:
107 /**
108 *
109 */
110 using ProgressRangeType = float;
111
112 public:
113 /**
114 * This is for consumers of progress information. Consumers MAY either poll the ProgressMonitor,
115 * or may register a callback to be notified of progress.
116 *
117 * Callback should be short lived, not hold any locks (because that could make it long lived and create a deadlock).
118 *
119 * Also don't throw exceptions in these callbacks. Just record the info needed, and schedule further work
120 * in a GUI or whatever (queue it maybe).
121 *
122 * \todo revisit 'noexcept' in C++23 - see https://stackoverflow.com/questions/41293025/stdfunction-with-noexcept-in-c17
123 * but for now, cannot declare teh function as noexcept
124 * Execution::Function<void (const ProgressMonitor& progressMonitor) noexcept>;
125 *
126 * \note - though un-enforced by the language, callers should still treat these callbacks as noexcept
127 */
128 using ChangedCallbackType = Execution::Function<void (const ProgressMonitor& progressMonitor)>;
129
130 private:
131 class Rep_;
132
133 public:
134 /**
135 * If work thread is specified (optional) - then thread cancelation will work more efficiently.
136 * But this is not required.
137 *
138 * \todo Consider if we should take ChangedCallbackType CTOR arg; not sure of the workerThread usecase. CONSIDER!
139 */
141 ProgressMonitor (const ProgressMonitor&) = delete;
142 ProgressMonitor (Thread::Ptr workThread);
143 ProgressMonitor (ChangedCallbackType callback, Thread::Ptr workThread = nullptr);
145
146 private:
147 ProgressMonitor (const shared_ptr<Rep_>& rep);
148
149 public:
150 ~ProgressMonitor () = default;
151
152 public:
153 nonvirtual ProgressMonitor& operator= (const ProgressMonitor&) = delete;
154
155 public:
156 /**
157 * This doesn't need to be used. You can use ProgressMonitor progress monitor just periodically calling
158 * GetProgress(). But you may use AddCallback () to receive notifications of progress changes.
159 *
160 * Also note, these callbacks may be mutable, and the same instance will be re-used on each progress callback
161 * (but it maybe a copy of what is originally passed in ).
162 */
163 nonvirtual void AddOnProgressCallback (const ChangedCallbackType& progressChangedCallback);
164
165 public:
166 /**
167 * Return the progress value (between 0..1). This values starts at zero, and increases
168 * monotonically to 1.0
169 */
170 nonvirtual ProgressRangeType GetProgress () const;
171
172 public:
173 /**
174 * Cancelability. Anyone can call Cancel () on this progress object. If the progress
175 * object is handed to some long-lived task, that task may (at its discretion) - check
176 * the progress callback, and cancel its operation by throwing a UserCanceledException.
177 *
178 * It is safe to call multiple times (and just may have no additional effect).
179 *
180 * If a work thread is associated with the ProgressMonitor, it will be automatically
181 * aborted.
182 */
183 nonvirtual void Cancel ();
184
185 public:
186 class Updater;
187
188 public:
189 /**
190 * Progress isn't updated directly through the ProgressMonitor object. Instead, get an Updater, and call methods
191 * on it to update the progress.
192 *
193 * \par Example Usage
194 * \code
195 * ProgressMonitor prog = ...;
196 * static_cast<Updater> (prog).SetProgress (0.3);
197 * static_cast<Updater> (prog).SetCurrentTaskInfo ("doing stuff"_k);
198 * \endcode
199 */
200 nonvirtual operator Updater ();
201
202 public:
203 struct CurrentTaskInfo;
204
205 public:
206 /**
207 * Often in displaying progress, its useful to have a notion of what the system is doing,
208 * and that is usually displayed far away from where the notion of progress stage resides.
209 * This API is usually called by the bit of code performing actions (to set the current task)
210 * and by the calling GUI to Get the current task description.
211 *
212 * Note also - for reasons of localization - its often helpful to pass back specific
213 * information about the task in progress (like file 1 of 4).
214 *
215 * Use the 'fExtraData' field of the CurrentTaskInfo.
216 */
217 nonvirtual CurrentTaskInfo GetCurrentTaskInfo () const;
218
219 private:
220 shared_ptr<Rep_> fRep_;
221
222 private:
223 friend class Updater;
224 };
225
226 /**
227 * Often in displaying progress, its useful to have a notion of what the system is doing,
228 * and that's usually displayed far away from where the notion of progress stage resides.
229 * This API is usually called by the bit of code performing actions (to set the current task)
230 * and by the calling GUI to Get the current task description.
231 *
232 * Note also - for reasons of localization - its often helpful to pass back specific information
233 * about the task in progress (like file 1 of 4).
234 *
235 * Use the 'fExtraData' field of the CurrentTaskInfo.
236 */
238 Characters::String fName;
240
241 CurrentTaskInfo (const Characters::String& taskName = {}, const DataExchange::VariantValue& details = {});
242 CurrentTaskInfo (const CurrentTaskInfo&) = default;
243
244 nonvirtual bool operator== (const CurrentTaskInfo& rhs) const = default;
245 };
246
247 /**
248 * The Updater is the API passed to code which knows about its progress through a long-lived task and makes callbacks
249 * to indicate phase, and percent progress (# 0..1).
250 *
251 * \note in old RFLLib code - this was called ProgressSubTask
252 *
253 * \todo Consider if DTOR should REMOVE added 'curTaskInfo' (stack behavior will require restructuring) - save old info on entry/construction perhaps
254 */
256 public:
257 /**
258 * Use of the given Updater will generate 'setprogress' calls with appropriately scaled
259 * progress values.
260 *
261 * Helper used to continue reporting progress, but breaking the progress into subtasks,
262 * and doing the arithmetic of integrating the total into an overall progress total.
263 *
264 * \note - initial updater generated via ProgressMonitor::operator Updater (); null-updater
265 * maybe used if there are no progress updates to display;
266 *
267 * \note - all 'Updater' methods (besides the constructor and destructor) - are expected to be called from the context
268 * of the worker task (especially ThrowIfCanceled).
269 */
270 Updater () = delete;
271 Updater (const Updater&) = default;
272 Updater (nullptr_t);
273 Updater (const Updater& parentTask, ProgressRangeType fromProg, ProgressRangeType toProg, bool restoreTaskInfoOnDTOR = true);
274 Updater (const Updater& parentTask, ProgressRangeType fromProg, ProgressRangeType toProg, const CurrentTaskInfo& taskInfo,
275 bool restoreTaskInfoOnDTOR = true);
276
277 private:
278 Updater (const shared_ptr<Rep_>& r);
279
280 public:
281 ~Updater ();
282
283 public:
284 bool operator== (const nullptr_t) const
285 {
286 return fRep_ == nullptr;
287 }
288
289 public:
290 /**
291 * Progress is a number 0..1. However, if outside that range, it will be silently pinned to be in that range (so
292 * caller need not check/be careful).
293 *
294 * However, we do 'weak assert' that its close to that range, since being more than a float-point round-off away, probably
295 * indicates a bug.
296 *
297 * \note ***Cancelation Point*** (and checks internal canceled flag and maps that to a cancelation point)
298 */
299 nonvirtual void SetProgress (ProgressRangeType p);
300
301 public:
302 /**
303 * Called from the context of a thread which has been given this progress object. This method will check
304 * if this progress object has been canceled, and in if so throw UserCanceledException.
305 *
306 * Note - this function does NOT check if the itself thread has been aborted (as that is usually
307 * taken care of automatically or via CheckForInterruption)
308 *
309 * \note ***Cancelation Point*** (and checks internal canceled flag and maps that to a cancelation point)
310 */
311 nonvirtual void ThrowIfCanceled ();
312
313 public:
314 [[deprecated ("Since Stroika v3.0d5 - SetProgress is cancelation point")]] void SetCurrentProgressAndThrowIfCanceled (ProgressRangeType currentProgress);
315
316 public:
317 /**
318 */
319 nonvirtual void SetCurrentTaskInfo (const CurrentTaskInfo& taskInfo);
320
321 private:
322 friend class ProgressMonitor;
323
324 private:
325 nonvirtual void CallNotifyProgress_ () const noexcept;
326
327 private:
328 shared_ptr<Rep_> fRep_;
329 ProgressRangeType fFromProg_{0.0};
330 ProgressRangeType fToProg_{1.0};
331 optional<CurrentTaskInfo> fRestoreTaskInfo_;
332 };
333
334 /**
335 * Take an input-stream object, and produce another identical, except that it updates the argument progress updater object.
336 * Note this works better if the argument 'in' supports RemainingLength, but guesses otherwise.
337 */
338 template <typename T>
340
341}
342
343/*
344 ********************************************************************************
345 ***************************** Implementation Details ***************************
346 ********************************************************************************
347 */
348#include "ProgressMonitor.inl"
349
350#endif /*_Stroika_Foundation_Execution_ProgressMonitor_h_*/
String is like std::u32string, except it is much easier to use, often much more space efficient,...
Definition String.h:201
Simple variant-value (case variant union) object, with (variant) basic types analogous to a value in ...
nonvirtual CurrentTaskInfo GetCurrentTaskInfo() const
nonvirtual ProgressRangeType GetProgress() const
nonvirtual void AddOnProgressCallback(const ChangedCallbackType &progressChangedCallback)
Thread::Ptr is a (unsynchronized) smart pointer referencing an internally synchronized std::thread ob...
Definition Thread.h:334
InputStream<>::Ptr is Smart pointer (with abstract Rep) class defining the interface to reading from ...
Iterable<T> is a base class for containers which easily produce an Iterator<T> to traverse them.
Definition Iterable.h:237
Streams::InputStream::Ptr< T > MakeInputStreamWithProgress(const Streams::InputStream::Ptr< T > &in, ProgressMonitor::Updater progress)