1

I would like to use callbacks with boost::progress_display. Firstly I have a class Foo that I need to broadcast events from:

class Foo
{
  public:
    template <typename L>
    void attachListener(L const &listener)
    {
        m_callback.connect(listener);
    }

    void doWork()
    {
        for(...stuff...) {
            m_callback(m_someData);
        }
    }
  private:
    Data m_someData;
    boost::signals2::signal<void(Data const&)> m_callback;

}

Then, in some other code, I have a callback that is used to handle events coming from Foo. I then create a Foo in otherFunction and attach callback to it:

void callback(Data const &someData, boost::progress_display &pd)
{
   // other stuff
   ++pd;
}

void otherFunction()
{
    boost::progress_display pd(100); 
    boost::function<void(Data const&)> f(boost::bind(&callback, _1, boost::ref(pd)));
    Foo foo;
    foo.attachListener(f);
    foo.doWork();
}

When the above is run, the callback will be called from Foo::doWork.

Is this the correct way to use callbacks in combination with boost::progress_display?

It can become annoying where I need to create and attach multiple handlers each with their own boost::progress_display. Doing this would mean that each individual progress_display percentage bar is printed out one after the other.

Thanks, Ben.

sehe
  • 374,641
  • 47
  • 450
  • 633
Ben J
  • 1,367
  • 2
  • 15
  • 33
  • What library is `boost::progress_display` in? I have never seen it before (whoa) – sehe Jun 17 '14 at 13:32
  • http://www.boost.org/doc/libs/1_55_0/libs/timer/doc/index.html – sehe Jun 17 '14 at 13:39
  • I might be wrong but I believe it's a header-only thing, i.e. no library requirement. Just include . Its pretty handy :-). Ah I see you've found it! Still, I don't think you actually need the lib dependency! – Ben J Jun 17 '14 at 13:41
  • you are wrong indeed. The includes tree doesn't always reflect the library structure ([sadly, e.g.](http://www.boost.org/doc/libs/1_55_0/libs/iterator/doc/function_output_iterator.html)). I already posted the link :) – sehe Jun 17 '14 at 13:43
  • Yeah.. it can be a bit annoying. But anyway, it seems I don't have to link against libboost_timer, in order for linking to work in this case. – Ben J Jun 17 '14 at 13:47

1 Answers1

1

UPDATE In response to the comment, here's a simple implementation of progress_group and group_progress_display classes that together make it easy to display a single progress bar for several different progress items (progress_group::item instances).

See it Live On Coliru.

Let's look at progress_group:

struct progress_group {
    struct item {
        size_t current, total;

        item(size_t total=100, size_t current=0)
            : current(current), total(total) { }

        void tick() {
            if (current < total) current++;
        }
    };

    std::list<boost::weak_ptr<progress_group::item> > members;

    void add(boost::shared_ptr<item> const& pi) {
        assert(pi);
        members.push_back(pi);
    }

    item get_cumulative() {
        item cumul(0, 0);

        for(auto& wpi : members) {
            auto pi = wpi.lock();

            if (pi) {
                cumul.current += pi->current;
                cumul.total   += pi->total;
            }
        }

        return cumul;
    }
};

Note that I (arbitrarily) opted to use weak pointers so the progress items may disappear and the scale will just be adjusted.


The actual progress is dynamically scaled to the resolution of the underlying display widget (boost::progress_display). The main limitation this leaves is that the progress needs to be strictly increasing over time (as the output might not be to a tty):

struct group_progress_display {

    group_progress_display() : _display(1000), _reentrancy(0) {
    }

    void add(boost::shared_ptr<progress_group::item> pi) {
        _group.add(pi);
    }

    void update() {
        if (1 == ++_reentrancy) // cheap synch
        {
            auto cumul = _group.get_cumulative();

            if (cumul.total > 0)
            {
                size_t target = (1.0 * cumul.current)/cumul.total * _display.expected_count();

                if (target >= _display.count())
                    _display += target - _display.count();
            }
        }
        --_reentrancy;
    }
  private:
    boost::progress_display _display;
    progress_group          _group;
    boost::atomic_int       _reentrancy;
};

This sample runs 100 background jobs of varying loads in threads, and displays a single, cumulative progress widget:

int main()
{
    boost::thread_group workers;
    group_progress_display display;

    for (int i = 0; i < 100; ++i)
    {
        auto load = (rand()%5) * 1500;
        auto progress_item = boost::make_shared<progress_group::item>(load);
        display.add(progress_item);

        worker this_worker(progress_item->total);
        this_worker.attachListener([=,&display]{
                progress_item->tick();
                display.update();
            });

        workers.create_thread(this_worker);
    }

    workers.join_all();
}

(Note how the lambda (or boost::bind expression for c++03) itself keeps the shared pointer alive.)

The worker doesn't know a single thing about the progress implementation:

struct worker {
    explicit worker(size_t count) : count(count) {}

    template <typename L>
    void attachListener(L&& listener) {
        m_callback = std::forward<L>(listener);
    }

    void operator()()
    {
        for (size_t i = 0; i < count; ++i) {
            boost::this_thread::sleep_for(boost::chrono::microseconds(500));
            m_callback();
        }
    }

  private:
    boost::function<void()> m_callback;
    size_t count;
};

Old Answer

This would appear to work Live On Coliru.

A potential issue would be that, at this moment, the progress indicator somehow has to have accurate information about the process steps. You might want to hide that information (inside Data, or wrap it in a class that also adds the progress information), then using some simple arithmetics you could translate to a fixed scale (say, 100).

This would allow even your Foo::doWork to adjust the scale on-the-fly, which is pretty much proof that the decoupling is working.

sehe
  • 374,641
  • 47
  • 450
  • 633
  • Many thanks sehe. One of the main issues is that the moment boost::progress_display is declared, it prints out the 'percentage bar'. Imagine if Foo had two call back signals that would fire from within Foo::doWork. Then in otherFunction I would declare 'progress_display pd' and 'progress_display pd2', binding them in to respective callbacks and attaching them to Foo. This would result in the progress bars being printed on top of each other with both lines of asterisks being printed underneath. – Ben J Jun 17 '14 at 15:02
  • @BenJ Don't do that then :) You could of course wrap the progress_display into one that accumulates the scales and the progress. ---This has little to do with your original question--- (it seems I missed the detail about _multiple handlers_), and is just a limitation of `boost::progress_display` itself. – sehe Jun 17 '14 at 15:16
  • Thanks, yes you're right -- I wasn't very clear. I was hoping there was some nifty trick regarding progress_displays and callbacks and that all the pieces would fall in to place once I was doing things properly... But it seems that progress_display wasn't designed to be used with callbacks. Ah well.. Cheers! – Ben J Jun 17 '14 at 15:30
  • @BenJ The callbacks are no issue. Having multiple progress bars is a problem. I've updated my answer with a demo that wraps the display as I hinted in my previous comment using a [`progress_group` and `group_progress_display` wrapper](http://coliru.stacked-crooked.com/a/260912a57c5ea09e) (I dropped signals because I thought `boost::function<>` was enough here) – sehe Jun 17 '14 at 23:26