xh_Utilities: ProgressiveTask

I'm tidying up my personal set of juce modules to share, so thought I'd start by introducing one useful set of classes from the first basic module (xh_Utilities). 

Link: https://github.com/haydxn/xh_jucemodules



This is a base for encapsulating a task which may take some time to perform, and where you might want to be able to present progress and status feedback to the user as it executes.

These can be implemented in exactly the same way as in ThreadWithProgressWindow. You can even use a ProgressiveTask as the body of a ThreadWithProgressWindow if you want. However, this class has been specifically design to be more flexible than that, in a variety of ways.

1. Tasks can run sub-tasks, and progress calculations are automatic.

Tasks can be made with any granularity, and each task need only be aware of its own normalised (0-1) progress. When starting a sub-task, you just specify a proportion of the current level's progress that it should occupy, and the calculations propagate upwards automatically. It is also possible to batch tasks into a sequence with relative weightings, allowing the proportions of each task to also be automatically calculated.

2. Member functions can be used as sub-tasks.

Because they're so modular, they also need to be easy to use in a range of situations. As well as simply creating a new subclass for any given task, there are also templates (MemberFunctionTask) allowing you to easily wrap a member function as a task. These make it very easy to break a task down into multiple parts without requiring a load of boilerplate code to encapsulate each one; each member function simply has a ProgressiveTask& parameter, which it can use to update its own local normalised progress, whilst still having access to data belonging to the parent task (since it is the same object).

3. Task implementation is separate from execution implementation.

One downside to implementing a task with ThreadWithProgressWindow is that the task is bound to that specific implementation. If you wanted to change it to run in some other way, you would need to rewrite it. A ProgressiveTask is not bound to any particular means of execution; you can decide how your program will perform it at any time. Obviously you will want to run it on a dedicated thread (although you could just run it as-is if you wanted), but the implementation of this is kept entirely separate. You could have it run individually via a modal popup, or you might want some kind of background queue with a status bar, or maybe even run batches of them via a ThreadPool.

It is entirely possible to use a ThreadWithProgressWindow to run a task. In fact, TaskThreadWithProgressWindow does just that.

However, the TaskHandler base class provides a simple interface to create any kind of custom task executor. It has callbacks (dispatched from the message thread) with which you can respond to a task starting and stopping, as well as periodically checking the status of it (and aborting it if necessary). These are also separated from the calling implementation - you still need to run it from a thread - but that's no problem, since you can easily just spawn a TaskThread for any given TaskHandler.

ModalTaskPopup basically re-implements the functionality of ThreadWithProgressWindow using this interface, but you can easily make something entirely custom in the same way.


I'll post some examples soon, but the code is already available (in the link above). I just need to figure out the best way to share a jucer project that includes custom modules.

I've refactored this a bit so that it's a bit more flexible now. Some of the above descriptions aren't quite accurate now, but the comments in most of the important class headers explain everything :)

I'll update this post with some more useful information when I get a chance. I just thought I'd best get the repository version up to date as there have been quite a few posts recently from people who may find it useful.

The easiest way to use it though is:

1) Subclass ProgressiveTask and implement your task in its run() function (ensuring you early-out if shouldAbort() returns true).

2) Call...

ModalTaskPopup::launch (new MyTask(), "threadName", 5 /*priority*/, new MyCallback());

...to spawn a thread with a modal popup (with progress bar) and an optional ProgressiveTask::Callback subclass instance to be notified when it finishes.

You can also run a task in loads of other ways (without any UI on a thread in the background, as a job in a ThreadPool, etc..), and you can attach any kind of interface to display the status (e.g. there is a somewhat undocumented example of a listbox which displays the status of all tasks held in a TaskThreadPool).