Hide menu
Loading...
Searching...
No Matches
Progress Status Support

Demonstrates how to monitor and update the progress of long-running algorithms using ProgressStatus and ProgressScope .

Overview

Several Manufacturing Toolkit algorithms that can take significant time for execution support progress status update. This is achieved via ProgressStatus which instance can be updated by the algorithm. During its execution the algorithm increments progress status via a hierarchy of nested scopes (ProgressScope ).

ProgressStatus also supports cancelation of the operation.

To enable forwarding notifications of the progress status update to the user-defined entities, ProgressStatus implements an observer pattern with the help of a class ProgressStatus::Observer . The subclasses implement particular actions to reflect the status update, e.g. updating a graphical progress indicator, printing a new value of the progress status into console window, etc.

The following code snippet demonstrates subscription to the progress status notifications and update of the graphical progress bar.

The progress status object

The ProgressStatus object represents a range [0, 100]. Upon creation, the current value of the status object is 0. It gets incremented during the algorithm execution. The frequency and uniformity of updates depends on the algorithm and its workload. For example when importing a file with numerous entities, the importer will trigger updates very often. However a file with a couple of curves only will be able to trigger very few updates.

The current value is returned by ProgressStatus.Value() . The type of the returned value is float what allows to monitor more fine-grained progress. Depending on the structure of an algorithm (nested scopes, number of updates, etc) the returned value may accumulate rounding errors. If you plan to convert a returned value to integer types (e.g. when using GUI progress bar widget accepting integer range) you might want to apply rounding, for example:

int aValue = static_cast<int> (floor (aStatus.Value() + 0.5f));

This will allow to minimize impact of the internal rounding errors and display a value 79.9995 as 80% not as 79%.

User-defined observers

To receive notifications on the progress status updates, define a subclass of ProgressStatus::Observer and redefine its virtual methods ProgressStatus::Observer::ChangedValue() and ProgressStatus::Observer::Completed() . The instance of the subclass must be registered in the progress status object with ProgressStatus::Register() .

The life span of an observer must be greater than the status object it is registered in. If the observer's life span needs to be shorter then ProgressStatus.Unregister() must be called to explicitly remove the observer from the status object's list. Otherwise, a status object will contain a dangling pointer and an attempt to access by the status object will lead to a crash.

The method ProgressStatus::Observer::ChangedValue() of the observer will be called each time the current value of the status object has been incremented greater than by a threashold and if the time since the last update has exceeded the threshold. Thresholds are specified as the ProgressStatus.Register() method parameters. Settings thresholds to zero will send notifications with every update. Upon destruction of the status object the observer will be notified via ProgressStatus::Observer::Completed() .

The calls to notification methods are blocking, i.e. the execution of the algorithm will not continue until the method returns. Therefore caution must be applied to not introduce noticeable overhead and/or to balance the frequency of notifications by setting higher thresholds when registering an observer:

MyObserver anObserver (...);
ProgressStatus aStatus;
aStatus.Register (anObserver, 1., 200U); //will be called for every 1% and 200ms update

Scopes

For multi-stage algorithms you can define a contribution of each stage into the entire range of the progress status object. For example, importing a file with format-specific reader consists of two stages – parsing a file contents and conversion of the file representation in memory into respective data model. Definition of each portion is done via ProgressScope object:

ProgressStatus aStatus;
ProgressScope aTopScope (aStatus); //take entire range (100%)
{
ProgressScope aReaderScope (aTopScope, 40); // 40% of aTopScope
{
ProgressScope aSubScope (aReaderScope, 75); // 75% of ReaderScope (30% of TopScope)
anIsOK = aReader.ReadFile (theFileName);
}
}
if (anIsOK) {
ProgressScope anAnalyzerScope (aTopScope, -1); // remaining 60% of TopScope
anAnalyzer.Perform (aModel);
}

Define an average contribution (weight) of each scope based on some representative workloads.

A scope has a range [0, 100] by default. You can redefine it to an arbitrary range [a, b] with the help of ProgressScope.SetRange() . This can be convenient when reporting progress of a loop with a fixed number of iterations. For example:

int n = ...;
ProgressScope aScope (aStatus);
aScope.SetRange (0, n);
for (size_t i = 0; i < n; ++i) {
ProgressScope aSubScope (aScope, 1); //takes 1/n-th of aScope
//compute i-th iteration
...
}

In each thread there can be only one current scope. A new scope created in that thread will be implicitly or explicitly connected to its parent. Therefore sibling scopes must be explicitly destroyed before opening a following sibling scope, as shown in the example above.

Using progress status in user-defined algorithms

The progress status object can be used in user-defined algorithms via sharing the same object instance and using ProgressScope instances. The scopes can be nested and have own ranges for more convenient progress update. For example:

{
ProgressScope aScope (aStatus, 25); // 25%
anAnalyzer.Perform (aPart);
}
{
ProgressScope aScope (aStatus); // the remaining range
size_t n = ...; // compute number of indexed face sets
aScope.SetRange (0, n);
for (size_t i = 0; i < n; ++i) {
ProgressScope aSubScope (aScope, 1); //takes 1 step in parent scope
std::vector <ModelData::Body> aBodies = ...;
MyAlgo.Process (aBodies);
}
}

Multi-threading considerations

In a multi-threaded application, by default, the observer will only get notification in a thread in which it was created. This is to minimize a risk of potential data race if the user-defined subclass is not thread-safe. If the subclass is thread-safe and can accept notifications from any thread in which the progress status object can be updated then ProgressStatus::Observer::SetAllNotifyingThreads() can be called to allow all threads notify the observer.

Cancellation support

To check if the operation has been canceled use ProgressStatus.WasCanceled() .

The operation can be canceled using two approaches:

Manufacturing Toolkit algorithms that support progress status regularly check if the operation has been canceled and return faster if so. The user-defined algorithm can use the following patterns to regularly check if the operation has been canceled:

if (!aStatus.WasCanceled()) {
ProgressScope aScope (aStatus, 50);
anAnalyzer.Perform(aPart);
}

or:

for (size_t i = 0; i < n && !aStatus.WasCanceled(); ++i) {
ProgressScope aSubScope (aStatus, 1);
ModelData::Part aPart = ...;
MyAlgo.Process (aPart);
}

Once the operation has been canceled the registered observers will be notified via calling ProgressStatus::Observer::Canceled() .