I want to create an object, that represents a progress output.
In a GUI this would be a progress bar, in a console application it may be a text output, that can only be written, but not changed anymore (e.g. the progress bar cannot rewind).
Other possible implementations could include sending a e-mail every 10% of progress and in the best case the API should be open for any other idea how to communicate progress as well.
When I start using the API in programs, it will be hard to change it later on, so it should be well-thought. I hope to get helpful feedback here and hints which use cases the current API may not cover.
My current idea for the API is this one:
class Progress{
public:
// Called by the function that reports progress
virtual void setMaxProgress(int value);
virtual void setCanStop(bool can_stop);
virtual void start(std::string description);
virtual void progress(int value);
virtual void incProgress(int steps = 1);
virtual void info(std::string message);
virtual bool shouldStop();
virtual void end();
virtual bool wasStopped();
private:
// Called by the progress implementation
virtual bool canStop();
virtual int currentProgress();
virtual int maxProgress();
};
The functions in detail:
- The program sets the maximum progress, e.g. for processing 50 items:
setMaxProgress(50)
- The program sets that the process can be interrupted (e.g. by rolling back a transaction or returning a partial result):
setCanStop(true)
. - The program starts progress:
start("I am calculating foo")
. - The program reports that
11
items in total were processed:progress(11)
- The program reports that it processed 1 item:
incProgress()
. - After each item the program asks if the user requested the operation to be interrupted (e.g. using a cancel button) with:
shouldStop()
. - The program displays some status message with
info("Second step ...")
. - The program reports that the task is finished (the progress implementation should hide the progress bar):
end()
. - The progress implementation asks how many steps are needed with:
maxProgress()
. - The progress implementation asks how many steps are already finished with:
currentProgress()
. - The progress implementation asks if it should show a cancel button (may not be implemented):
canStop()
. - The program queries if the user clicked cancel to determine if the algorithm finished or was interrupted with:
wasStopped()
.
An implementation with a progress bar (GUI or TUI) may set the maximum value to 50 and display a cancel button when canStop()
is true.
[==== ] 40% [cancel]
A text mode implementation may calculate the percentage and display a simple progress bar, ignoring canStop()
0 =============== 100
|||||
I currently use two implementations that look almost like this one:
- One counts the progress steps and every time the percentage is increased by a certain amount (depending on the console width), it outputs a dot. When some info message is printed, it outputs the current progress after it again.
- The other one updates the global Maya progress bar. On each progress update it sends a script command to Maya to ask if cancel was pressed and sends the updated progress information. When cancel was pressed, an internal variable is set that makes
shouldStop
return true. Theinfo
method uses the Maya status bar to display a message.
(I don't really use the info method, but it was in the code base that contained the first implementation
Example implementation:
class SimpleProgress {
public:
SimpleProgress(int progress_bar_length = 50, int max_progress = 100): progress_bar_length(progress_bar_length), max_progress(max_progress) {};
virtual void start(std::string describtion) {
std::cerr << "Starting: " << describtion << std::endl;
_description = describtion;
};
virtual void end() {
std::cerr << "Finished: " << _description << std::endl;
};
virtual void setMaxProgress(int value) {
max_progress = value;
};
virtual void progress(int progress) {
assert(max_progress >= 0);
assert(progress <= max_progress);
for(int i = 0; i < (progress - current_progress) / max_progress * progress_bar_length; i++) {
std::cerr << "=";
}
current_progress = progress;
};
virtual void incProgress(int steps = 1) {
if(max_progress >= 0) {
assert(current_progress <= max_progress);
progress(current_progress + steps);
} else {
// Progress without a known maximum value.
std::cerr << "." << std::endl;
}
};
virtual int maxProgress() override {
return max_progress;
};
virtual void info(std::string message) {
std::cerr << message << std::endl;
for(int i = 0; i < current_progress / max_progress * progress_bar_length; i++) {
std::cerr << "=";
}
};
virtual void setCanStop(bool can_stop) {
};
virtual bool canStop() {
return false;
};
virtual bool shouldStop() {
return false;
}
private:
int current_progress = 0;
int max_progress = -1;
int progress_bar_length;
std::string _description;
};
(Note that this is only a prototype as I am currently rewriting the class from a more complex project)
For a non-interactive implementation, you could for example want to stop when a certain runtime is exceeded, implementing the methods like this:
virtual void setCanStop(bool can_stop) {
_can_stop = can_stop;
};
virtual bool canStop() {
return _can_stop;
};
virtual bool shouldStop() {
if(_can_stop && runtime >= max_runtime) {
return true;
} else {
return false;
}
}
Example usage:
void doSomething(Progress *progress = nullptr) {
DummyProgress dummyProgress;
if(progress == nullptr) {
progress = &dummyProgress;
}
progress->setMaxProgress(50);
progress->canStop(true);
progress->start("Calculating");
for(int i=0; i < 50; i++) {
progress->progress(i);
//
// Calculate something for step i
//
if(progress->shouldStop()) {
progress->end();
break;
}
}
if(progress->wasStopped()) {
std::cerr << "Not all items were processed." << std::endl;
} else {
progress->end();
}
}
I would like to get feedback about what is possibly missing in my API, what could be improved and what may be a bad idea?
Feedback on suggestions:
- Replace
setMaxProgress
and other setters with parameters forstart()
. This will help to avoid code that did not set theses properties. - Making getters
const
- I cannot purely rely on callbacks for the cancel button, as I need to support one implementation that requires an active query if cancel was clicked, i.e., running
progressBar -query -isCancelled
in Maya (docs).