CAD Exchanger SDK
Progress Status Support

Overview

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

Base_ProgressStatus also supports cancelation of the operation.

To enable forwarding notifications of the progress status update to the user-defined entities, Base_ProgressStatus implements an observer pattern with the help of a class Base_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. For a complete example that uses Qt graphical framework refer to the Progress Status Example.

class ProgressBarObserver : public Base_ProgressStatus::Observer
{
public:
ProgressBarObserver (QProgressBar* theProgressBar) : myProgressBar (theProgressBar) {}
virtual void ChangedValue (const Base_ProgressStatus& theInfo) override
{
myProgressBar->setValue (theInfo.Value());
}
virtual void Completed (const Base_ProgressStatus& theInfo) override
{
myProgressBar->setValue (theInfo.Value());
}
private:
QProgressBar* myProgressBar;
};
...
ProgressBarObserver anObserver (theProgressBar);
{
ACIS_Reader aReader;
Base_ProgressStatus& aStatus = aReader.ProgressStatus();
aStatus.Register (anObserver);
Base_ProgressScope aTopScope (aStatus);
{
Base_ProgressScope aScope (aStatus, 25);
anIsOK = aReader.ReadFile (theFileName.c_str());
}
if (anIsOK) {
Base_ProgressScope aScope (aStatus, 75);
anIsOK = aReader.Transfer (aModel);
}
}

The progress status object

The Base_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 Base_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 Base_ProgressStatus::Observer and redefine its virtual methods ChangedValue() and Completed(). The instance of the subclass must be registered in the progress status object with Base_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 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 Base_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 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 Base_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 (...);
Base_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 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 Base_ProgressScope object:

Base_ProgressStatus& aStatus = aReader.ProgressStatus();
Base_ProgressScope aTopScope (aStatus); //take entire range (100%)
{
Base_ProgressScope aScope (aTopScope, 10); //10% of aTopScope
anIsOK = aReader.ReadFile (theFileName.c_str());
}
if (anIsOK) {
Base_ProgressScope aScope (aTopScope, 30); //30% of aTopScope
anIsOK = aReader.Transfer (aShapeModel);
}
if (anIsOK) {
{
Base_ProgressScope aScope (aTopScope, 50); //50% of aTopScope
aMesher.Triangulate (aShapeModel);
}
{
Base_ProgressScope aScope (aTopScope); //the remaining 10% of aTopScope
aMeshModel = aMesher.Paste (aShapeModel);
}
}

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 Base_ProgressScope::SetRange(). This can be convenient when reporting progress of a loop with a fixed number of iterations. For example:

int n = ...;
Base_ProgressScope aScope (aStatus);
aScope.SetRange (0, n);
for (i = 0; i < n; ++i) {
Base_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.

Algorithms chain

If several algorithms which support progress status need to represent a common range they should be chained to share the same Base_ProgressStatus. To do so, just assign one shared status object to all the algorithms before their execution starts:

ACIS_Reader aReader;
ModelAlgo_BRepMesher aMesher;
Base_ProgressStatus& aStatus = aReader.ProgressStatus();
aMesher.ProgressStatus() = aStatus;
aStatus.Register (anObserver);

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 Base_ProgressScope instances. The scopes can be nested and have own ranges for more convenient progress update. For example:

{
Base_ProgressScope aScope (aStatus, 25); //25%
aMesher.Triangulate (aShapeModel);
}
{
Base_ProgressScope aScope (aStatus, 5); //5%
aMeshModel = aMesher.Paste (aShapeModel);
}
{
Base_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) {
aSubScope (aStatus, 1); //takes 1 step in parent scope
Handle_X3DGeometry_IndexedFaceSet aFaceSet = ...;
MyAlgo.Process (aFaceSet);
}
}

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 Base_ProgressStatus::Observer::SetAllNotifyingThreads() can be called to allow all threads notify the observer. Refer to the Progress Status Example which uses this feature.

Cancellation support

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

The operation can be canceled using two approaches:

CAD Exchanger 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()) {
Base_ProgressScope aScope (aStatus, 50);
aMesher.Compute (aModel);
}

or:

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

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

Refer to the Progress Status Example for an example that demonstrates operation cancelation.