How can I effectively use callbacks with boost::progress_display?

488 views Asked by At

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.

1

There are 1 answers

4
sehe On BEST ANSWER

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.