Signals & slots in Juce


#1

fyi you can use juce with c++11 signals & slots from the following article:

http://www.gamedev.net/page/resources/_/technical/general-programming/using-varadic-templates-for-a-signals-and-slots-implementation-in-c-r3782

It led to drastically simpler logic and smaller source code on the juce-only project I'm currently contracting on.

I also have one tortous app using wxWidgets & juce at the same time, with juce in its own thread & messages going back and forth between the two plus from orphan std::async() tasks using a "thread-crosser" by wrapping signal arguments in a lambda + std::bind() that get queued to a Component that'll dequeue & play them back in its async updater.

-- p


C++11 Event System
MouseEvents and Lambdas
Add callbacks for components
Function pointers as an alternative to broadcasters & listeners
#2

paste this into a file called SignalSlotEventSystem.h : 

#ifndef SIGNALSLOTEVENTSYSTEM_H_INCLUDED
#define SIGNALSLOTEVENTSYSTEM_H_INCLUDED

#include <vector>
#include <algorithm>

template<class... Args> class Event; //class prototype

class BaseDelegate
{
public:
    virtual ~BaseDelegate(){ }
};

template<class... Args> class AbstractDelegate : public BaseDelegate
{
protected:
    virtual ~AbstractDelegate()
    {
        for(auto i : events) i->disconnect(*this); //calls Event::disconnect(*this) on each event
    }

    friend class Event<Args...>;

    void add(Event<Args...> *newEvent)
    {
        events.push_back(newEvent);
    }
    void remove(Event<Args...> *eventToRemove)
    {
        events.erase(std::remove(events.begin(), events.end(), eventToRemove), events.end());
    }
    virtual void call(Args... args) = 0;
    std::vector<Event<Args...>*> events; //a vector of events that we respond to.
};

template<class T, class... Args> class ConcreteDelegate : public AbstractDelegate<Args...>
{
public:
    ConcreteDelegate(T *t, void(T::*f)(Args...), Event<Args...> &s)
    : t(t), f(f)
    {
        s.connect(*this); //adds
    }
private:
    ConcreteDelegate(const ConcreteDelegate&);
    void operator=(const ConcreteDelegate&);
    friend class Event<Args...>; //this lets Event<> objects access ConcreteDelegate::call(), which is private

    virtual void call(Args... args)
    {
        (t->*f)(args...);
    }
    T *t;   //pointer to object instance.
    void(T::*f)(Args...); //pointer to object instance method with <Args...> signature
};

template<class... Args> class Event
{
public:

    Event(){ }
    ~Event()
    {
        for(auto i: delegates)
            i->remove(this);    //calls AbstractDelegate::remove()
    }

    void connect(AbstractDelegate<Args...> &delegateToConnect)
    {
        delegates.push_back(&delegateToConnect);
        delegateToConnect.add(this); //calls AbstractDelegate::add()
    }

    void disconnect(AbstractDelegate<Args...> &delegateToDisconnect)
    {
        delegates.erase(std::remove(delegates.begin(), delegates.end(), &delegateToDisconnect), delegates.end());
    }

    void operator()(Args... args)
    {
        for(auto i: delegates) i->call(args...); //calls the function pointer stored on the ConcreteDelegate object with the templated args.
    }
private:
    Event(const Event&);
    void operator=(const Event&);
    std::vector< AbstractDelegate<Args...>* > delegates; //a list of delegates with this argument signature
};

class Delegate
{
public:
    Delegate(){ }
    ~Delegate()
    {
        for(auto i: delegates) delete i; //automatically calls ConcreteDelegate::AbstractDelegate::~AbstractDelegate(), which calls Event::disconnect()
    }

    /** 
     Connects an event to an object's instance method for handling said event.
     T *t is the object with the instance method
     void(T::*f) is the instance method on the object.  use the format &MyClass::MyMethod with no arguments.
     event is the event that your method should respond to.  usually defined in a global location, such as an AppEvents class that holds each type of event.
     usage: 
        1) add an AppEvents member to any class that needs to trigger events.
        2) add a Delegate to each class that will respond to these events.
        3) pass a reference to the AppEvents member to these classes with the delegate
        4) connect your class methods to the events via this method.
        5) trigger an event by calling AppEvents::event(Args...)
     */
    template<class T, class... Args> void connect(T *t,         //Object instance
                                                  void(T::*f)(Args...),  //Object instance method that handles the args
                                                  Event<Args...> &event) //Event that carries the args
    {
        delegates.push_back(new ConcreteDelegate<T, Args...>(t, f, event));
    }
private:
    Delegate(const Delegate&);
    void operator=(const Delegate&);
    std::vector<BaseDelegate*> delegates;
}; 
#endif /* SIGNALSLOTEVENTSYSTEM_H_INCLUDED */

now create an AppEvents.h file that #include "SignalSlotEventSystem.h" and add some events as such: 

#include "SignalSlotEventSystem.h"
#include "../JuceLibraryCode/JuceHeader.h"

class AppEvents {
    Event<int, const MidiMessage &, const String&> IMS_Event;
    Event<float, float, int, float> FFIF_Event;
    Event<bool, String> BS_Event;
    Event<String, float, bool> SFB_Event;
};

now add an instance of this AppEvents class to your project along with an instance of the Delegate class from the SignalSlotEventSystem.h in each class that is gonna use it: 

class MainContentComponent : Component 
{
public:
  MainContentComponent();
  ~MainContentComponent() {}
  void paint(Graphics &g) override;
  void resized() override;
  void someMethod(int, const MidiMessage&, const String &) {}
  void someOtherMethod( float, float, int, float ) {}
  void fireEvents();
private:
  SharedResourcePointer<AppEvents> appEvents;
  Delegate delegate;
};

class OtherComponent : Component
{
public: 
  OtherComponent();
  ~OtherComponent() {}
  void paint(Graphics &g) override;
  void resized() override;
  void someMethod(int, const MidiMessage&, const String &) {}
  void fireEvents();
private: 
  SharedResourcePointer<AppEvents> appEvents;
  Delegate delegate;
};

Next, set up your connections with the delegate in each class, probably in your constructor

MainContentComponent::MainContentComponent() {
   delegate.connect(this, &MainContentComponent::someMethod, appEvents.IMS_Event);
   delegate.connect(this, &MainContentComponent::someOtherMethod, appEvents.FFIF_Event);
}

OtherComponent::OtherComponent() {
    delegate.connect(this, &OtherComponent::someMethod, appEvents.IMS_Event);
}

Finally, trigger the events when you need to!

void MainContentComponent::fireEvents() {
  appEvents.IMS_Event( 25, MidiMessage::noteOn(1, 42, 1.f), "Bb13#9" );
  appEvents.FFIF_Event( 2.3, 3.2, 7, 4.625 );
  appEvents.BS_Event( true, "This event has no connection to receive it" );
}
void OtherComponent::fireEvents() {
  appEvents.IMS_Event( 382, MidiMessage::noteOn(1, 26, 1.f), "16 + 5 = 21" );
  appEvents.FFIF_Event( 68.1, 69.1, 33, 0.125 );
  appEvents.SFB_Event( "This won't go anywhere", 3.3, false );
}

it works AWESOME, and you can have a ton of events that use identical signatures but have different names, like 

Event<MidiMessage> NoteOnEvent;
Event<MidiMessage> DrawbarEvent;
Event<MidiMessage> SustainPedalEvent; 
Event<MidiMessage> NoteOffEvent;

This makes it suuuuuper easy to set up handlers for specific message types instead of having to use a bunch of if/switch statements! 

Thanks to the OP for sharing this idea and that link!

Hopefully this post of mine will help you use it, because that link was kinda confusing to read thru! 


#3

I think JUCE could really do with event handling, but I wonder whether it can be any simpler / cleaner.  That article is a bit of a headache.

I don't like the word "delegate".

I'm going to have a go:

Usage:

struct EventSrc {
    Event<float> eventA;
    Event<bool, int> eventB;
    void triggerEvent() { eventA.fire(42.0f); }
};

struct EventSink {
    EventSink(EventSrc& src) { src.eventA.addSubscriber(this, &EventSink::gotEventA); }
    void gotEventA(float x) { cout << "Dest recieving EventA! " << x; }
};

int main() { EventSrc src;  EventSink sink(src);  src.triggerEvent(); }

Implementation:

template<class... Args>
class SubscriberBase {
public:
    virtual void call(Args... args) = 0;
};

template<class T, class... Args>
class Subscriber : public SubscriberBase<Args...> {
private:
    T* t;
    void(T::*f)(Args...);
public:
    Subscriber(T* _t, void(T::*_f)(Args...)) : t(_t), f(_f)  { }
    void call(Args... args) final  { (t->*f)(args...); }
};

template<class... Args>
class Event {
private:
    std::vector< SubscriberBase<Args...>* > subscribers;
public:
    void fire(Args... args) {
        for( auto& f : subscribers )
            f->call(args...);
    }

    template<class T>
    void addSubscriber( T* t, void(T::*f)(Args... args) ) {
        auto s = new Subscriber <T, Args...>(t, f);
        subscribers.push_back(s);
    }
};

Working online example: http://coliru.stacked-crooked.com/a/c9971dc9279a4ef7

hm well I can see why the article is coded the way it is.  It is the task itself that is a bit of a headache.

Anyway, I've got the basic mechanism down, which should make it easier to understand MKM's post or the original article.

π


#4

This is a C++ workout and no mistake.

ok, I've extended my original to include proper destruction.

I still haven't got my head completely around the original; I find it hard to read -- too many single letter variables, and the word Delegate everywhere. I'm not sure if it is leaking or not.  I'm not sure if my version is going to offer any improvement over the original, but rewriting it helps me understand what's going on and I thought I might as well share.

Version 2: http://coliru.stacked-crooked.com/a/0046d611b1d0287e

#include <vector>
#include <iostream>
#include <algorithm>
#include <memory>

using namespace std;

template<class... Args>
class SubscriberBase {
public:
    virtual void call(Args... args) = 0;
    virtual bool instanceIs(void* t) = 0;
    virtual ~SubscriberBase() { };
};

template<class T, class... Args>
class Subscriber : public SubscriberBase<Args...> {
private:
    T* t;
    void(T::*f)(Args...);
public:
    Subscriber(T* _t, void(T::*_f)(Args...)) : t(_t), f(_f)  { }
    void call(Args... args)   final  { (t->*f)(args...); }
    bool instanceIs(void* _t) final  { return _t == (void*)t; }
    ~Subscriber()             final  { cout << "~Subscriber() hit! \n"; }
};

template<class... Args>
class Event {
private:
    using SmartBasePointer = unique_ptr<SubscriberBase<Args...>>;
    std::vector<SmartBasePointer> subscribers;
public:
    void fire(Args... args) {
        for( auto& f : subscribers )
            f->call(args...);
    }

    template<class T>
    void addSubscriber( T* t, void(T::*f)(Args... args) ) {
        auto s = new Subscriber <T, Args...>(t, f);
        subscribers.push_back(SmartBasePointer(s));
    }
        
    template<class T>
    void removeSubscriber(T* t) {
        auto to_remove = std::remove_if(
            subscribers.begin(),
            subscribers.end(),  
            [t](auto& s) { return s->instanceIs((void*)t); }
            );
        subscribers.erase(to_remove, subscribers.end());
    }
};

// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

// example usage:
struct EventSrc {
    Event<float> eventFloat;
    Event<bool, int> eventB;
    string name;
    
    EventSrc(string _name) : name(_name) { }
    
    void triggerEvent() {
        cout << name << "::triggerEvent() ~ Firing event with: 42\n";
        eventFloat.fire(42.0f);
    }
};

struct EventSink {
    EventSrc& src;
    string name;
    EventSink(EventSrc& _src, string _name)
        : src(_src), name(_name)  {
        cout << name << "()\n";
        src.eventFloat.addSubscriber(this, &EventSink::gotEvent);
    }
    ~EventSink() {
        cout << "~" << name << "()\n";
        src.eventFloat.removeSubscriber(this);
    }
    
    void gotEvent(float x) { cout << name <<"::gotEvent hit with value: " << x << endl; }
};

int main() {
    EventSrc srcA("srcA");
    EventSink sink1(srcA, "sink1");
    {
        EventSink sink2(srcA, "sink2");
        srcA.triggerEvent();
    }

    srcA.triggerEvent();
    
    return 0;
}

π


#5

'EventSink'? 

'EventSrc'? 

your class names make no sense. 


#6

Ok changed for the time being, although I think event sources and sinks are pretty decent terminology. Subscribers, listeners, sinks, delegates, etc -- I will need to keep refining the language.

I figured out a way to solve the awkwardness of manually having to remove listeners (http://stackoverflow.com/questions/35847756/robust-c-event-pattern)

I've created an EventListener class.

So your listening class Foo derives from EventListener<Foo> which exposes a connect method:

struct Publisher {
    Event<float> eventFloat;
    void triggerEvent() { eventFloat.fire(42.0f); }
};
struct Listener : EventListener<Listener> { }

    // event source and listener unaware of each other's existence
    Publisher publisherA();   
    Listener listener1();

    listener1.connect(publisherA.eventFloat, &Listener::gotEvent);
    publisherA.triggerEvent();

This EventListener base class holds a list of all events that we have connected to. When the listener is destroyed, the base destructor notifies each event to remove the subscriber whose address is this object.

It's ended up really similar to the original code I think!

http://coliru.stacked-crooked.com/a/b2733e334f4a5289

// events.h
// π 8.03.2016

#include <vector>
#include <iostream>
#include <algorithm>
#include <memory>
#include <string>

using namespace std;

// an event holds a vector of subscribers
// when it fires, each is called

template<class... Args>
class SubscriberBase {
public:
    virtual void call(Args... args) = 0;
    virtual bool instanceIs(void* t) = 0;
    virtual ~SubscriberBase() { };
};

template<class T, class... Args>
class Subscriber : public SubscriberBase<Args...> {
private:
    T* t;
    void(T::*f)(Args...);
public:
    Subscriber(T* _t, void(T::*_f)(Args...)) : t(_t), f(_f) { }
    void call(Args... args)   final { (t->*f)(args...); }
    bool instanceIs(void* _t) final { return _t == (void*)t; }
    ~Subscriber()             final { cout << "~Subscriber() hit! \n"; }
};

// our Listener will derive from EventListener<Listener>
// which holds a list of a events it is subscribed to.
// As these events will have different sigs, we need a base-class.
// We will store pointers to this base-class.
class EventBase {
public:
    virtual void removeSubscriber(void* t) = 0;
};

template<class... Args>
class Event : public EventBase {
private:
    using SmartBasePointer = unique_ptr<SubscriberBase<Args...>>;
    std::vector<SmartBasePointer> subscribers;
public:
    void fire(Args... args) {
        for (auto& f : subscribers)
            f->call(args...);
    }

    template<class T>
    void addSubscriber(T* t, void(T::*f)(Args... args)) {
        auto s = new Subscriber <T, Args...>(t, f);
        subscribers.push_back(SmartBasePointer(s));
    }

    //template<class T>
    void removeSubscriber(void* t) final {
        auto to_remove = std::remove_if(
            subscribers.begin(),
            subscribers.end(),
            [t](auto& s) { return s->instanceIs(t); }
        );
        subscribers.erase(to_remove, subscribers.end());
    }
};

// derive your listener classes: struct MyListener : EventListener<MyListener>, i.e. CRTP
template<class Derived>
class EventListener {
private:
    // all events holding a subscription to us...
    std::vector<EventBase*> events;

public:
    template<class... Args>
    void connect(Event<Args...>& ev, void(Derived::*listenerMethod)(Args... args)) {
        ev.addSubscriber((Derived*)this, listenerMethod);
        events.push_back(&ev);
    }

    // ...when the listener dies, we must notify them all to remove subscription
    ~EventListener() {
        for (auto& e : events)
            e->removeSubscriber((void*)this);
    }
};

// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

// example usage:
class Publisher {
private:
    string name;
public:
    Event<float> eventFloat;
    Event<bool, int> eventB;

    Publisher(string _name) : name(_name) { }

    void triggerEvent() {
        cout << name << "::triggerEvent() ~ Firing event with: 42\n";
        eventFloat.fire(42.0f);
    }
};

struct Listener : EventListener<Listener> {
    string name;
    Listener(string _name)
        : name(_name) {
        cout << name << "()\n";
    }
    ~Listener() {
        cout << "~" << name << "()\n";
        //emitter.eventFloat.removeSubscriber(this);
    }

    void gotEvent(float x) { cout << name << "::gotEvent hit with value: " << x << endl; }
};

int main() {
    // event source and listener unaware of each other's existence
    Publisher publisherA("publisherA");
    
    Listener listener1("listener1");
    listener1.connect(publisherA.eventFloat, &Listener::gotEvent);

    {
        Listener listener2("listener2");
        listener2.connect(publisherA.eventFloat, &Listener::gotEvent);
        
        publisherA.triggerEvent();
    }

    publisherA.triggerEvent();

    return 0;
}

#7

http://coliru.stacked-crooked.com/a/c74b2feda7987307

hm I didn't mean to use this thread as a VCS. Each post I thought would be the end of it, but the OCD!

This is the first complete version.

This is actually the first time I've managed to fit the whole structure in my head, such that I can perceive the whole machine.

This version is fully documented, including a walk-through: if you load the file in your IDE of choice and search for "// [" it will locate //[1] //[2] etc giving you an index so you can click through by number.

I think this is a very good way to document complex code, I'm surprised I've never seen it done anywhere else.

I suppose it could still do with unit tests, but I'm happy to leave it for the time being (not sure how long that'll last).

Any suggestions for improving the code or documentation much appreciated!

π

// C++11 Event System
// π 06.03.2016
// 4th revision

#include <vector>
#include <iostream>
#include <algorithm>
#include <memory>
#include <string>

using namespace std;
/*
 Usage:
 
    class Sender {
        Event<int> event;
        void fireEvent() { event.fire(42); }
    
    class Reciever : EventListener<Reciever> {
        void handler(int k) {...}

    reciever.connect(mySender.event, &Reciever::handler);

We have two principal objects: Event and EventListener

Event contains a list of subscribers, each subscriber holding an instance pointer and member pointer.

reciever.connect(event, &Reciever::) adds {&reciever, &Reciever::handler} to event's subscriber list

The reason we are using an EventListener base class is because there is a danger: What if a receiver is destroyed?
Next time the event fires it will attempt to invoke a method on an instance that no longer exists. UB hazard!
It would be nice not to force the user to manually disconnect.
We can do the work in EventListener's destructor.
But in order to do this, EventListener is going to need to maintain a list of events that need to have their subscribers removed.
So, EventListener's connect also has to add the event to its own list.

Event list will have to be a vector<EventBase*>, and EventBase will have to contain a virtual removeSubscriber(void* inst)
*/

// [1c] Each subscriber is for a different object type, so how to store them in a list?
//    Answer: Make a base class!
template<class... Args>
class SubscriberBase {
public:
    virtual void call(Args... args) = 0;
    virtual void* getInstance() = 0;
    virtual ~SubscriberBase() { /* derived dtor executes before hitting here */ } // deleting base object invokes derived dtor
};

// [1b] A subscriber stores:
//       - a pointer to the subscribing object,
//       - a pointer to the member function that needs to be called when the event fires.
//    When it fires, each is invoked.
template<class T, class... Args>
class Subscriber : public SubscriberBase<Args...> {
private:
    T* t;
    void(T::*f)(Args...);
public:
    Subscriber(T* t, void(T::*f)(Args...)) : t(t), f(f) { }
    void call(Args... args)   final { (t->*f)(args...); }
    void* getInstance()       final { return (void*)t; }
    ~Subscriber()             final { cout << "~Subscriber() hit! \n"; }
};

class EventListenerBase {
public:
    virtual void removeEvent(void* ev) = 0;
};

// [2c] As these events will have different sigs, again we need the same base-class trick.
class EventBase {
public:
    virtual void removeSubscriber(void* t) = 0;
};

template<class... Args>
class Event : public EventBase {
private:
    // Note: When the instance is destroyed, delete is called on each list element thanks to unique_ptr
    using SmartBasePointer = unique_ptr<SubscriberBase<Args...>>;
    // [1a] An event holds a list of subscribers.
    std::vector<SmartBasePointer> subscribers;
public:
    // [5] boum!
    void fire(Args... args) {
        for (auto& s : subscribers)
            s->call(args...);
    }

    template<class T>
    void addSubscriber(T* t, void(T::*f)(Args... args)) {
        auto s = new Subscriber <T, Args...>(t, f);
        subscribers.push_back(SmartBasePointer(s));
    }

    void removeSubscriber(void* t) final {
        auto to_remove = std::remove_if(
            subscribers.begin(),
            subscribers.end(),
            [t](auto& elt) { return elt->getInstance() == t; }
            );
        subscribers.erase(to_remove, subscribers.end()); // yup std::remove_if doesn't actually do the removing (gah)
    }
    
    // [3] ok, So we have the EventListener cleaning up after itself. Now let's get Event to also do that.
    ~Event() {
        for (auto& s : subscribers)
            ((EventListenerBase*)(s->getInstance()))->removeEvent(this);
    }
};

// [2a] The consumer's Reciever must derive from EventListener<Reciever>
template<class Reciever>
class EventListener : public EventListenerBase {
private:
    // [2b] ... which holds a list of all events holding a subscription to us...
    std::vector<EventBase*> events;

public:
    // [4] This is the heart of the operation.
    //  Simultaneously add listener to event's subscribers while adding event to the listeners event-list.
    template<class... Args>
    void connect(Event<Args...>& ev, void(Reciever::*listenerMethod)(Args... args)) {
        ev.addSubscriber((Reciever*)this, listenerMethod); // [1a]
        events.push_back(&ev);
    }

    void removeEvent(void* ev) final {
        auto to_remove = std::remove_if(
            events.begin(),
            events.end(),
            [ev](auto& elt) { return elt==(EventBase*)ev; }
            );
        events.erase(to_remove, events.end()); // yup std::remove_if doesn't actually do the removing (gah)
    }
    
    // [2d] ...so that when the reciever dies, we notify each event to remove it's subscription.
    ~EventListener() {
        for (auto& e : events)
            e->removeSubscriber((void*)this);
    }
};

// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

// [0] Example usage:
class Sender {
private:
    string name;
public:
    Event<float> eventFloat;
    Event<bool, int> eventB; // etc.

    Sender(string s) : name(s) { }

    void triggerEvent() {
        cout << name << "::triggerEvent() ~ Firing event with: 42\n";
        eventFloat.fire(42.0f);
    }
};

struct Reciever : EventListener<Reciever> {
    string name;
    Reciever(string s) : name(s)  { cout << s << "()\n"; }
    ~Reciever()  { cout << "~" << name << "()\n"; }

    void gotEvent(float x) { cout << name << "::gotEvent hit with value: " << x << endl; }
};

int main() {
    // Event source and listener unaware of each other's existence.
    Sender sender("sender");
    Reciever reciever1("reciever1");
    
    reciever1.connect(sender.eventFloat, &Reciever::gotEvent);

    {
        Reciever reciever2("reciever2");
        reciever2.connect(sender.eventFloat, &Reciever::gotEvent);
        sender.triggerEvent(); // get 2 hits
    }

    sender.triggerEvent(); // get 1 hit

    return 0;
}

Code renders incorrectly
#8

At the very least, addListener methods seriously lack the overload for std::function<void (...)>.


#9

what would this allow us to do? 

button.addListener( [](ButtonListener* bl) { /*do some awesome handling code right here, java(script)-style */ } );


#10

That's a good point.

My only worry is that people would use it to pass capturing lambdas, e.g.

int w=42; myEvent += [&]void (float x){ cout << w+x; };

(http://stackoverflow.com/questions/28573986/how-to-pass-a-lambda-in-a-function-with-a-capture)

... which runs into potential trouble:

struct Event {std::function<void()>f;}; 
struct Foo { 
    int a=42; 
    Foo(Event& e){ 
        e.f = [&](){ cout << a; }; 
    } 
    ~Foo(){ cout<<"[dtor]";} 
}; 
{ Event ev; {Foo foo(ev);} ev.f(); } // outputs [dtor]42

i.e. Suddenly we have arrived in UB-land. That 42 isn't guaranteed -- its object has already been destroyed.

I do quite like the idea of allowing ..

event += {this, handler};

... Although it's back to the problem of requiring a subsequent

event -= this;

The solution I've got at the moment is robust -- there is no way to get in trouble using it. So I would have to ask myself if there is any compelling need to support vanilla C functions. Likely yes, not entirely sure either way.

π


#11

your design is mad confusing tho.

The original author was looking for something that would look like this, programmatically:

This is how you define an event:

Event<someData> eventName;

this is how you trigger the event:

eventName(someData);

this is how you create a method to handle that event:

void class::classMethod(someData);

this is how you assign a method/function to respond to an event:

delegate.connect(classInstance, classInstanceMethod, eventType);

You've got all this confusing "addListener()" "fire()" "addSubscriber()" which are just extra layers to mentally process to figure out how to use your design.   I get that you want to try to do it better/more simply, but damn lol.  yours is harder to use, even if the internals are easier to understand.  

I'll take outside simplicity any day over internal clarity for libraries where I don't care how they work. 


#12

Equivalent complexity, slightly different form.

Look at the example use in my listing for version 4 -- it boils down to this:

struct Sender {
    Event<float> e;
    void triggerEvent() { e.fire(42.0f); }
};
struct Reciever : EventListener<Reciever> {
    void gotEvent(float x) { ... }
};

Sender s();  Reciever r(); 

r.connect(s.event, &Reciever::gotEvent);
s.triggerEvent();

I've bolded the attachment points. There are only 4!

You've got all this confusing "addListener()" "fire()" "addSubscriber()" which are just extra layers to mentally process to figure out how to use your design.

The confusion is your end :) Well, maybe I am partially responsible in posting up 4 versions. Ignore all but the last.

There is no addListener. I'm using the word subscriber rather than listener. Because listening is an active process, whereas a subscription is passive. It drops through your letterbox. So subscriber is the right word.

And addSubscriber is an internal method.

And my event.fire() vs the original event() ... mine is the simpler construct. A plain method vs overloading ().  And it is more descriptive. We are performing an action on this event. So object.action() makes sense.  Otherwise, event() ... wtf? oh they have overloaded () to fire the event. Very clever! Saves one word! But less easy to instantly grok. I quite like overloading () actually, I would probably allow both forms.

I've also chosen an inheritance pattern over sticking a 'delegate' in the listener class. This was a conscious choice. It is better for several reasons:

  • That delegate object.. It is not a delegate! A delegate is something that act on behalf of another entity. That doesn't accurately describe what this object is doing.  Not even slightly. There is no way to reconcile. That object is doing a lot! EventSwitchboard is the best name I can come up with.
     
  • delegate would have to be public in order to connect from outside the class, member variables are preferred nonpublic.
     
  • An explicit inheritance (class Foo : EventListener<Foo>) indicates in the class declaration that it is an EventListener.
     
  • Using delegate, this needs to be passed for every connect call. With my system it is just foo.connect(event, &Foo::gotEvent) if connecting from outside Foo, or simply connect(event, &gotevent) if connecting from within Foo.  Much cleaner!

π


#13

I had a go at extending the system to handle static events as well.

It makes sense to use separate code; they can eventually be merged -- that will be trivial.

http://coliru.stacked-crooked.com/a/e0631620e8637786

Not quite sure what qualifies as the best Interface -- I've provided the following:

    event.addStaticHandler(&handler);
    event.fire(1);
    event.removeStaticHandler(&handler);
    
    // or...
    uint64_t id_A = event.addStaticHandler(&handler);
    event.fire(2);
    event.removeStaticHandler(id_A);
    
    // or...
    event += &handler;
    event.fire(3);
    event -= &handler;

Interestingly,  you can also pass a lambda, so the following also works:

    uint64_t id_L = event.addStaticHandler( [](int x){ ... } );
    event.fire(4);
    event.removeStaticHandler(id_L);

You can even cut it right down:

event += [] (int x) { ... }; 
event.fire(5);

The only problem is there is no way to remove that last one from event's subscriber list. I can imagine cases where this wouldn't be a problem, and it is very succinct!

π

PS Lambdas can capture, although I have no idea what happens if you add a lambda from inside some instance method that captures `this`, and then destroy the instance.

PPS I had tried to get the id to be the raw function pointer, but my technique for extracting this from a std::function seems to cause a segfault if I feed in a lambda.

PPPS complete listing:

// C++11 Static Event System
// π 10.03.2016
// 1st revision

#include <vector>
#include <iostream>
#include <algorithm>
#include <string>
#include <functional>

using namespace std;

template<class... Args>
class Event
{
private:
    using F = std::function<void(Args...)>;
    
    struct Invoker {
        F fp;
        uint64_t id;
    };
    
    std::vector<Invoker> staticInvokers;
    
    static void* getAddress(F f) {
        return (void*)*f.template target<void(*)(Args...)>();
    }
    
    uint64_t counter = 0;
    
public:
    void fire(Args... args) {
        for (auto& i : staticInvokers)  i.fp(args...);
    }

    uint64_t addStaticHandler(F f) {
        staticInvokers.push_back(Invoker{f,counter});
        return counter++; //getAddress(f);
    }
    
    void operator+=(F f) {
        addStaticHandler(f);
    }

    void removeStaticHandler(F f) {
        removeStaticHandler( getAddress(f) );
    }

    void operator-=(F f) {
        removeStaticHandler(f);
    }

void removeStaticHandler(void* addr) {
        auto to_remove = std::remove_if(
            staticInvokers.begin(),
            staticInvokers.end(),
            [addr](auto& item) { return Event::getAddress(item.fp) == addr; }
            );
        staticInvokers.erase(to_remove, staticInvokers.end()); // yup std::remove_if doesn't actually do the removing (gah)
    }
    
    void removeStaticHandler(uint64_t id) {
        auto to_remove = std::remove_if(
            staticInvokers.begin(),
            staticInvokers.end(),
            [id](auto& item) { return item.id == id; }
            );
        staticInvokers.erase(to_remove, staticInvokers.end()); // yup std::remove_if doesn't actually do the removing (gah)
    }
    
};

// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

static void handler(int x) {
    cout << "handler hit with value: " << x << endl;
}

int main() {
    Event<int> event;
    
    event.addStaticHandler(&handler);
    event.fire(1);
    event.removeStaticHandler(&handler);
    
    // or...
    uint64_t id_A = event.addStaticHandler(&handler);
    event.fire(2);
    event.removeStaticHandler(id_A);
    
    // or...
    event += &handler;
    event.fire(3);
    event -= &handler;

    uint64_t id_L = event.addStaticHandler(
        [] (int x) {
            cout << "lambda hit with value: " << x << endl;
            }
        );
    event.fire(4);
    event.removeStaticHandler(id_L);

    event += [] (int x) { cout << "second lambda hit with value: " << x << endl; };
    event.fire(5);

    return 0;
}

#14

I’ve been using a similar short thing I wrote called Notifier.
I hadn’t seen that article, though. I’ll check it out and might find something better. Thanks!


#15

I recently spotted that JUCE provides its own implementation of the event/listener pattern:

https://www.juce.com/doc/classListenerList

https://github.com/julianstorer/JUCE/blob/master/modules/juce_core/containers/juce_ListenerList.h

I haven't looked closely to see how closely that mirrors the original article or my revision, but I would personally prefer to use JUCE components over my own unless I can see very good reason -- I like to reduce to a minimum the amount of code in my own stack.

π


#16

_pi, I realize this is a year old but did you ever end up doing any more work on your sig/slots code?

I kind of agree with your design change from Aardvajk’s code on GameDev i.e. if only an object instance + member function can be connected to an event, it doesn’t make a lot of sense to require the user to pass in the “this” pointer as it would be super confusing for that pointer to be anything besides “this” so inheriting from what he calls “delegate” makes more sense than owning an instance of “delegate”.

I haven’t looked over your code super carefully yet but I think it is basically what I wanted to do with Aardvajk’s code so you may have saved me some work. One thing I’d probably change in your code is to hide the use of void* from the public interface. I get that you want to test for identity with a void* but there is no reason for the user classes to know about that.

My other thought is more general criticism regarding this and the original code. std::function is so close to what we want to use to as the delegates or slots or whatever and would provide more functionality e.g. lambdas with capture lists, that one wonders if it wouldn’t make more sense to basically use std::function and solve the problems that that entails.

The big problem with using std::function is that you can’t test them for identity. My thought is fine use a struct that contains an std::function along with a void* to use for identity tests. Your EventListener class could then have multiple overloads of its connect member function:

  1. for a free function make the subscriber be a struct{fnPtr, std::function(fnPtr)}
  2. for a member function make the subscriber be struct{ ptrReceiver, std::bind(ptrReceiver, memFnPtr)}
  3. for a lambda make the subscriber be struct{ nullptr, std::function(lambda)} and just don’t allow them to disconnect an individual anonymous callback/receiver.

The above would work but not sure the additional overhead is worth it.


#17

sorry I’m currently moving from Spain to Paris, sitting between boxes.

If I don’t reply to you i a couple weeks, please do ping me again.

Cheers!

– p


#18

I have a new version of your code that supports usage of lambdas as well as member functions. will post it somewhere soon I think.


#19

would love to see it!! post it soon!


#20

Here is the code I was talking about above