TIGSource Forums

Developer => Technical => Topic started by: BorisTheBrave on January 04, 2012, 03:57:57 PM



Title: Event libraries in C++
Post by: BorisTheBrave on January 04, 2012, 03:57:57 PM
So I'm need of events, for a C++ project. Specifically, for MVC type stuff, whicih I'm experimenting with.

I'm mainly familiar with the built-in AS3 library (EventDispatcher), and have just used std::function or rolled my own when in C++. But I thought I might try a library for it this time.

But I see there's actually quite a few choices, like libsigc++, sigslot, Signals, boost.signals, Cpp::Events. Which are popular / good?

Ideally, I'd like an implemenation with:

* Arbitrary payload
* Lightweight (the vast majority of events will have zero or one subscribers)
* Memory allocation efficient
* Doesn't pull in a huge framework (except maybe boost)
* Multithreaded

Or are they all junk? How do you deal with events?


Title: Re: Event libraries in C++
Post by: eigenbom on January 04, 2012, 04:44:45 PM
I use boost::signal and boost::bind to connect things in my simple GUI. Not sure how it would go for a huge amount of events, but the code is nice...

Code:
class Button {
public:
  boost::signal<void (void)> clicked;
  void update(){
    if (mouse_clicked_here) clicked(); // emit signal
  }
};

class Listener {
public:
  void buttonClicked(){print("hi!");}
};
...
Button* button = new Button;
Listener * li = new Listener;
button->clicked.connect(boost::bind(&Listener::clicked,li,_1));


Title: Re: Event libraries in C++
Post by: Average Software on January 04, 2012, 05:40:09 PM
My game has a fairly extensive GUI framework, and I handle events in two ways:

Every window has as accompanying interface that is implemented by something to handle the higher level window events, and the window maintains a pointer to its handler.

(Ada code, but you should get the gist)

Code:
package Pause_Windows is
    pragma Elaborate_Body;

    -- Enumeration of pause window options.
    type Pause_Option is (Save_Game,
                          Load_Game,
                          Options,
                          Host_Options,
                          Objectives,
                          Restart_Mission,
                          Franchisopedia,
                          Resume,
                          Quit_To_Title,
                          Quit_Game);

    -- Interface for the window's event handler.
    type Pause_Window_Handler is limited interface;

    -- Handle a pause window selection.
    not overriding
    procedure Pause_Window_Selection(This: in out Pause_Window_Handler;
                                     Selection: in Pause_Option) is abstract;

For individual widget events, I just take pointers to handler object and arbitrary handler function with a specific signature.

Code:
package Buttons is
    -- Abstract button type.
    type Button is abstract new Widget with private;
    type Button_Access is access all Button'Class;
    -- Button state.  When the mouse is not over a button, it is Normal.
    -- When the mouse is over the button, it is Selected.
    -- If the button has been pressed but not yet released, it is Pushed.
    type Button_State is (Normal, Selected, Pushed);
    -- Every button takes a callback with this signature.
    type Click_Handler is access procedure (Parent: in out Widget'Class; Sender: in out Button'Class);

This is a little more difficult to do in C++, because Ada does type encapsulation in a very different way, but something like static member functions would be my first thought.

An interesting approach in C++ would be to have lightwight template handler adapters that can call an arbitrary member function on a target object.  Something like so:

Code:
class Button
{
    // Most of the button code goes here.
private:
    virtual void dispatch_click() = 0;
};

template <typename Handler>
class HandledButton : public Button
{
    Handler *handler;
    void (Handler::*callback)(Button &sender);

    void dispatch_click()
    {
        (handler->*callback)();
    }

public:
    HandledButton(Handler &handler, void (Handler::*callback)(Button &sender);
};

To be used as such:

Code:
class SomeWindow
{
    HandledButton<SomeWindow> quit_button;

    void quit_click(Button &sender);

public:
    SomeWindow() : quit_button(*this, &SomeWindow::quit_click) {}
};

Hopefully I got all that right.  The template thing is particularly attractive, I need to remember that one.


Title: Re: Event libraries in C++
Post by: Mikademus on January 05, 2012, 06:46:07 AM
Without compiling it and testing it looks right to me.

It is also the classic situation where the observer pattern would be suitable, where any number of listeners can subscribe to an event-raising source. Of course, a template solution like in Average's post or leveraging boost::signal, would be part of implementing an observer solution.


Title: Re: Event libraries in C++
Post by: BorisTheBrave on January 05, 2012, 11:53:03 AM
The obsever pattern is too much boilerplate, imho.

Average, your C++ solution is somewhat like std::function, except you seem to be specializing the object emitting events. That's no good for me, I need unrelated components to connect to each other, at runtime, potentially with multiple listeners.

The ada I'm having a little difficulty reading. Could you spell it out to me?


Title: Re: Event libraries in C++
Post by: Average Software on January 05, 2012, 12:24:00 PM
I need unrelated components to connect to each other, at runtime, potentially with multiple listeners.

If this is the case, the simplest solution might actually be to use Objective-C++ and write the event system in terms of Objective-C.  Obj-C's message passing system is much better suited to this sort of thing, since it allows any message to be sent to any object without the need for template tricks or interface classes.  In fact, I'm surprised more people don't do this.

Quote
The ada I'm having a little difficulty reading. Could you spell it out to me?

The first example is just a simple interface through which a window communicates its events to a handler.  In this case, it's a pause menu with a number of options.  When an option is selected, the window calls Pause_Menu_Selection on its registered handler, passing it the user's choice.

The second one I'll try to translate into rough C++:

Code:
class Button; // Type which sends events.

typedef void (*ButtonHandler)(Widget &handler, Button &sender); // Handler function type.

The button maintains a pointer to a handler object (descended from Widget) and a pointer to a function of this signature.  When the button is clicked, it calls the function, passing its handler object and itself as the params.

This is bit tricky in C++, because the function either has to be a static member of the handler class, or a friend of it, both of which can get a little cumbersome (as I mentioned, Ada's encapsulation system is different and this isn't an issue there).  Generally you downcast handler to whatever type you know the handler is.  Perhaps it would look like this:

Code:
// Static function in PauseWindow.
void PauseWindow::quit_clicked(Widget &handler, Button &sender)
{
    PauseWindow &self = dynamic_cast<PauseWindow&>(handler);

    // React to the button press.
}

It's ugly, certainly, but without using a language with dynamic message semantics like Obj-C, there are really only ugly solutions.


Title: Re: Event libraries in C++
Post by: BorisTheBrave on January 05, 2012, 02:09:19 PM
So you mean the button stores the pair (handler, handler_callback)? The typesafety issue is nothing a little templating cannot fix. But you raise a good point, that if I'm only concerned about dispatching from one object to another (which I think I am), I can do this a lot more cheaply than std::function. Now you describe it this way, I imagine that is how Qt works. Tempted to go with this (with changes to support multiple listeners).

After reading up on Obj-C, it seems the relevant part of the message passing is the fact you don't need define a fixed list of event types, instead relying on the fact most objects will ignore most messages. It'd look something like this in C++:

Code:
class Listener
{
  void send(int selector, void* event) = 0;
};

class MyListener
{
  void send(int selector, void* event)
  {
    switch(selector){
      case 133: doMethod1(event);
      default:
      // Unknown selector, do nothing, or forward elsewhere
    }
  }
}
Except with runtime type safety, automatic interning of selectors, assembling the switch statement from separate methods and site-based caching. It does seem a bit tricky to reproduce that nicely in C++. But perhaps I could live without some of that. I don't intend to move to Obj-C.


Title: Re: Event libraries in C++
Post by: Average Software on January 05, 2012, 02:53:20 PM
Except with runtime type safety, automatic interning of selectors, assembling the switch statement from separate methods and site-based caching. It does seem a bit tricky to reproduce that nicely in C++. But perhaps I could live without some of that. I don't intend to move to Obj-C.

This is why I suggested Obj-C++, which lets you use the two languages together.  I envision an Obj-C proxy object that relays messages to a C++ object.

Code:
class Observer
{
public:
    Observer();
    ~Observer();
    void handle_specific_event();

private:
    MessageProxy *proxy;
};

@interface MessageProxy : Object
{
    Observer *target;
}

- (id) initWithTarget:(Observer&) target;
- (void) someDispatchedMessage;

@end

Observer::Observer()
:proxy([[MessageProxy alloc] initWithTarget:*this])
{
    [message_center registerObserver:proxy forEvent:@selector(someDispatchedMessage)];
}

Observer::~Observer()
{
    [message_center unregisterObserver:proxy forEvent:@selector(someDispatchedMessage)];
    [proxy release];
}

void Observer::handle_specific_event()
{
    // whatever
}

@implementation MessageProxy

- (id) initWithTarget:(Observer&) target
{
    self = [super init];

    if (self != nil)
    {
        self->target = &target;
    }

    return self;
}

- (void) someDispatchedMessage
{
    target->handle_specific_event();
}

@end

You would have to write a small ObjC proxy class for each of your C++ classes that wanted event notifications.  It would be a really interesting thing to try, but perhaps a touch overkill for what you might be doing.


Title: Re: Event libraries in C++
Post by: BorisTheBrave on January 05, 2012, 04:12:01 PM
I agree, this would be interesting, but I think probably not what I'm looking for. I doubt I'd be able to understand my own code a week later if I started mish-mashing Obj-C++.

Part of the problem is if I have 5 listeners to subscribe to 5 different events on the same object, then as far as obj C is concerned, you just have proxies, so 25 message events, with 20 ignored. At least for more conventional subscriptions systems that doesn't come up. But I definitely like the appeal of having an objects that can support an open set of events, with reasonably efficient dispatch.


Title: Re: Event libraries in C++
Post by: BorisTheBrave on January 06, 2012, 04:56:11 PM
Ok, I'm trying something hand rolled that mimics some of what we've discussed:

Code:
typedef void (*DelegateCallback)(void* self, void* data);
struct Delegate
{
void* self;
DelegateCallback callback;
};

class EventDispatcher
{
public:
void AddListener(EventType eventType, const Delegate& delegate);
void RemoveListener(EventType eventType, void* self);

void Dispatch(EventType eventType, void* data);
private:
std::map<EventType, std::list<Delegate>> m_listeners;
};

By using a map, I don't pay for unused event types, and can have tons of different events.

Delegate is just a lightweight replacement for std::function<void(void*)>, which the added bonus that it is introspectable.

I use template magic to construct Deletegate objects in such a way that void* self is always typesafe. But I've yet to figure out type safety for the data argument. Or how to avoid using a macro and C++11's decltype:

Code:
#define DELEGATE_BIND(callback, self) (Delegate_Make<decltype(callback)>::Make<(callback)>((self)))

Delegate myDelegate = DELEGATE_BIND(&MyClass::OnEvent, myClassInstance);


Title: Re: Event libraries in C++
Post by: Ivan on January 06, 2012, 06:14:23 PM
This is how I do events in Polycode, which is pretty similar:

http://polycode.org/learning/timers_events


Title: Re: Event libraries in C++
Post by: _Tommo_ on January 06, 2012, 06:49:05 PM
I use interfaces and virtual functions  8)
Each virtual "onCollision", "onStateBegin", etc, method represents an event, and a receiver registers to event sources (callback callers) via the Observer Pattern, after having implemented the needed interface.

I tried a lot of anything else, but C++ always manages to make advanced methods look like a mess to me.
In fact, elegant Event-State management would be my #1 reason to switch to a scripting language... but as for now, it isn't enough.


Title: Re: Event libraries in C++
Post by: eclectocrat on January 06, 2012, 07:15:56 PM
Code:
struct Observer {virtual ~Observer(){}};

class EventDispatcher
{
public:
    void observe (shared_ptr<Observer>, function<void(Event const&)> const&);
    void observe (shared_ptr<Observer>, function<void(Event const&)> const&, EventType);
private:
    struct ObserverEntry {
        weak_ptr<Observer> observer;
        function<void(Event const&)> callback;
    };
    typedef std::list<ObserverEntry> ObserverList;
    typedef std::map<EventType, ObserverList> ObserverMap;
public:
    void dispatch (Event const& event)
    {
        for(ObserverList::iterator i=_generalObservers.begin();
             i!=_generalObservers.end(); ++i)
        {
            shared_ptr<Observer> obs = i->observer.lock();
            if(obs)
                i->callback(event);
        }
        ObserverMap::iterator i=_observers.find(event.type());
        if(i!=_observers.end()) ... blah blah blah ...
    }
};

That's a way to avoid the need to unregister observers if you're using shared_ptr's. I found that observer links become another resource management problem that can cause just as many bugs as memory if not handled correctly.