ChangeBroadcaster::addListener(Listener*, lambda)

@ the JUCE team:

Has there been any thoughts or discussion of adding per-listener lambda callbacks to ChangeBroadcaster?

It would eliminate the changeListenerCallback(ChangeBroadcaster*) member function we need to implement.

If we want to have multiple objects listen to a single ChangeBroadcaster, it would be really helpful:

struct BackgroundThread : Thread, ChangeBroadcaster { ... };

and then you could add listeners to it:

backgroundThread.addListener(&widgetA, [](){ /* what WidgetA does when the thread fires */ });
backgroundThread.addListener(&widgetB, [](){ /* */ });
1 Like

Here is a hacky way to add the functionality (untested) using a wrapper class:

/*
 this class wraps a ChangeBroadcaster, adding the ability to set lambda callbacks per listener

 we inherit from ChangeListener privately to act as a wrapper around a ChangeBroadcaster 
 held internally, which actually performs the messaging.

 We inherit from ChangeBroadcaster publicly, so other ChangeListeners can still listen to 
 us and use the changeListenerCallback() function if they want.
 */
struct EnhancedChangeBroadcaster : public ChangeBroadcaster, private ChangeListener
{
private:
    /* the changeBroadcaster we wrap that actually does the messaging */
    ChangeBroadcaster changeBroadcaster;
public:
    EnhancedChangeBroadcaster()
    {
        changeBroadcaster.addChangeListener(this);
    }
    
    ~EnhancedChangeBroadcaster()
    {
        changeBroadcaster.removeChangeListener(this);
    }
    /*
     we replicate the ChangeBroadcaster interface for triggering callbacks.
     */
    void sendChangeMessage() { changeBroadcaster.sendChangeMessage(); }
    void sendSynchronousChangeMessage() { changeBroadcaster.sendSynchronousChangeMessage(); }
    void dispatchPendingMessages() { changeBroadcaster.dispatchPendingMessages(); }
private:
    struct LambdaChangeListener
    {
        ChangeListener* changeListener = nullptr;
        std::function<void()> func = nullptr;
        
        LambdaChangeListener(ChangeListener* cl, std::function<void()> f) :
        changeListener(cl), func(std::move(f)) { }
    };

    std::vector<LambdaChangeListener> listeners;
    
    /*
     here is where we intercept the ChangeBroadcaster callback,
     and call the lambda for each changeListener that has been registered with us.
     */
    void changeListenerCallback (ChangeBroadcaster* source)
    {
        for( auto& l : listeners )
        {
            if( l.changeListener != nullptr )
            {
                if( l.func )
                    l.func();
                l.changeListener->changeListenerCallback(this);
            }
        }
    }
public:
    /*
     here is where we replicate the rest of the ChangeBroadcaster interface
     */
    void addChangeListener(ChangeListener* l, std::function<void()> func = nullptr)
    {
        auto existing = std::find_if(listeners.begin(),
                                     listeners.end(),
                                     [l](const LambdaChangeListener& lcl)
                                     {
                                         return lcl.changeListener == l;
                                     });
        if( existing == listeners.end() )
        {
            listeners.emplace_back(l, func);
        }
    }
    void removeChangeListener(ChangeListener* l)
    {
        auto existing = std::find_if(listeners.begin(),
                                     listeners.end(),
                                     [l](const LambdaChangeListener& lcl)
        {
            return lcl.changeListener == l;
        });
        
        listeners.erase(existing);
    }
    
    void modifyListenerCallback(ChangeListener* l, std::function<void()> func)
    {
        auto existing = std::find_if(listeners.begin(),
                                     listeners.end(),
                                     [l](const LambdaChangeListener& lcl)
                                     {
                                         return lcl.changeListener == l;
                                     });
        if( existing != listeners.end() )
        {
            existing->func = std::move(func);
        }
    }
    void removeAllChangeListeners()
    {
        listeners.clear();
    }
};

Were you going to revise your suggestion? I had something similar already in place but the final behavior isn’t the same as my example above. I’m curious about your thoughts @yfede

Honestly, I withdrew my post because I was not sure it fitted your case. Your reimplementation of a ChangeBroadcaster that is also a ChangeListener got me all confused so I may be missing your point entirely, but my suggestion was:

With a listener class that simply calls a lambda when its change callback is triggered (I see you have a similar class in your code):

class LambdaChangeListener : public ChangeListener
{
    std::function <void (ChangeBroadcaster*)> onChange;

    void changeListenerCallback (ChangeBroadcaster* source) override
    {
        if (onChange)
            onChange (source); 
    }
};

And then use as many of those as members (without the need to inherit from them) to define the behavior you desire. For example (I tried to grasp the idea of your initial code):

class MyBeautifulClass
{
public:
    MyBeautifulClass()
    {
        widget1Response.onChange = [this] (ChangeBroadcaster*) { widget1.doSomething (); }
        widget2Response.onChange = [this] (ChangeBroadcaster* source) { widget2.doSomethingWith (source); }

        backgroundThread.addListener (&widget1Response);
        backgroundThread.addListener (&widget2Response);
    }  

private:
    SomeComponent widget1;
    LambdaChangeListener widget1Response;

    SomeOtherComponent widget2;
    LambdaChangeListener widget2Response;
};

Honestly what we really need is something like this added to ChangeBroadcaster:

void addCallback(std::function<void()> callback)
{
    callbacks.push_back(std::move(callback));
} 

and then

void ChangeBroadcaster::callListeners()
{
    changeListeners.call ([this] (ChangeListener& l) { l.changeListenerCallback (this); });

    for( auto & cb : callbacks )
    {
        if( cb )
            cb();
    }
}

and add this as a private member:

std::vector< std::function<void()> > callbacks;

The implementation that I showed a few posts prior shows how to enable the above idea, but with the ability to remove lambda callbacks and also change the lambda callback for a registered listener.
That is something you can’t do with what I’ve shown in this post, as far as I know.
I don’t think this is really possible:

void removeCallback(std::function<void()> lambda) 
{
    callbacks.erase(std::remove_if(callbacks.begin(), 
                                   callbacks.end(), 
                                   [&lambda](const auto& callback){ return lambda == callback; }), 
                    callbacks.end());
}

It wouldn’t be very DRY to pass the same lambda to that addCallback function, and then pass it to removeCallback() later, unless you’re storing it as a variable somewhere. This is the part of C++ I’m not totally 100% on.

I also had this initial idea:

struct LambdaChangeListener : ChangeListener
{
    LambdaChangeListener(ChangeBroadcaster* cb_, std::function<void()> f) :
    cb(cb_),
    func(std::move(f))
    {
        cb->addChangeListener(this);
    }
    ~LambdaChangeListener() override
    {
        cb->removeChangeListener(this);
    }
    void changeListenerCallback(ChangeBroadcaster*) override
    {
        if( func )
            func();
    }
private:
    ChangeBroadcaster* cb;
    std::function<void()> func;
};

I don’t think you can compare std::function objects like this, I think you can only compare them to nullptr. Even if you could I wouldn’t expect them to return equal even if they’re created with the sample lambda as std::function is a fairly complex type erased beast.

There seems to be a bit of conflation between “lambda” and “std::function” function here.

Looks like an almost identical proposal was done some time ago:

My thoughts about this: it would be really nice if it were possible, with some template magic, to do something like

ScopedListener <ChangeListener> listener (broadcaster);

ScopedListener <Button::Listener> listener (button);

etc.

1 Like

You could assign identifiers to them when adding, to remove them later. The ids could be string or int
 but I personally wouldn’t go with that route because it feels like overcomplicating and still not very RAII.

The approach taken by boost::signals2 is to return a Connection object after registering a callback. To sever a connection, you call a detach (I forget the exact name) member function on the Connection. By default, Connections stay attached indefinitely, but they can also be converted to instances of ScopedConnection, which will detach automatically when they go out of scope.

1 Like

Is there any chance JUCE could get something like this?

I think it’s fairly unlikely. Too much code relies on the current listener system to change or remove it, and adding a completely separate listener system would be confusing. GUI controls already have both a ‘ListenerList’ and ‘onChange’ lambdas, and I don’t think adding a third way of achieving the same thing is a good idea.

@matkatmusic https://github.com/palacaze/sigslot/blob/master/include/sigslot/signal.hpp is a single header file and might do all that you need.

2 Likes

I’m not requesting it for GUI controls. I’m requesting it for ChangeBroadcaster, which is often used to signal that a background thread finished doing some work.

I think this would depend on whatever class you’re using as the Listener to have a standardized interface with regard to adding listeners, and removing them.

Some classes have addListener, others have addChangeListener, others have other types of listener.

You could use some if constexpr (std::is_same<T, Button::Listener>::value) to decide if you’re going to use addListener or if constexpr (std::is_same<T, ChangeListener>::value) to decide if you’re going to use addChangeListener.

Yes, something like that, or the generic implementation of ScopedListener could assume that addListener() and removeListener() are the methods to use, and templace specializations could be provided for deviations to that rule (e.g. ChangeListener)

1 Like

So


template<typename ListeneeType, typename ListenerType>
struct ScopedListener
{
    ScopedListener(ListeneeType& listenee, ListenerType& listener) :
    objectToListenTo(&listenee),
    objectThatIsListening(&listener)
    {
        objectToListenTo->addListener(objectThatIsListening);
    }

    ~ScopedListener()
    {
        objectToListenTo->removeListener(objectThatIsListening);
    }
private:
    ListeneeType* objectToListenTo = nullptr;
    ListenerType* objectThatIsListening = nullptr;
};

?

I would need to understand a usage example to think about how to expand it.

I think objectToListenTo would need to be some kind of WeakReference, in case the object is deleted before the listener (in which case we just skip the removeListener).

For Components this could be a Component::SafePointer, but for any other it works only, if the ListeneeType implements JUCE_DECLARE_WEAK_REFERENCEABLE, which is not the majority.

Seems it enters a terretory where it creates considerable overhead, but I am not the one to say.

Yeah, I thought about throwing that in, but it would have complicated the example coee.
Weakreference isn’t expensive to use. The class adds an if() statements when you try to use operator-> but that’s really it. What kind of overhead were you referring to?

Oh, I was just thinking that every class that can inherit ChangeBroadcaster needs to be WEAK_REFERENCEABLE, so that would mean add it to all classes.

Not the processing overhead, so overhead was probably the wrong word, I meant massive changes.

Thinking about it again, maybe adding the WEAK_REFERENCEABLE in the ChangeBroadcaster class is an option?