3

In an application I'm making at the moment, I have an EventDispatcher class that works with a base Event class. The dispatcher is not templated, it works with the run-time types of each event; this is to allow scripts to inherit from the base Event class and make their own types of events.

It'd like this event dispatcher to handle inheritance of events as well. For example, I have FooEvent that inherits from a FooBaseEvent; whenever a FooEvent occurs, callbacks interested in FooBaseEvent are also notified, but not the other way around.

Is there any library that would make this easier? Remember that inheritance checking should be extended to events defined in scripts as well.

(The scripting language is Python, but that shouldn't matter so much.)


Edit: My EventDispatcher has the following interface (Python):

class EventDispatcher:
    def subscribe(self, event_type, callback) -> EventDispatcher.Subscription
    def post(self, event)

    class Subscription:
        def cancel(self)
        def alive(self) -> bool
Paul Manta
  • 30,618
  • 31
  • 128
  • 208

1 Answers1

0

I'm not aware of any library that would make your life easier but that doesn't prove it doesn't exist. That said, the type system of Python and C++ have some important differences so it might be a bit tricky to find a general purpose type information system to bridge the two.

If you only want to track the inheritance hierarchy and are prepared to manually register the types, you could roll your own relatively quickly. The following records the hierarchy in type_registry_t and then dispatcher_t looks at the hierarchy to see if a listener is interested in an event. Note uses some c++11 features.

#include <iostream>
#include <memory>
#include <set>
#include <string>
#include <map>
#include <vector>

typedef std::string class_id_t;

class type_registry_t {
    std::multimap<class_id_t, class_id_t> parent_;
public:
    void register_type(class_id_t const& id, std::vector<class_id_t> const& parent)
    {
        for (size_t i = 0, sz = parent.size(); i < sz; ++i)
            parent_.insert(std::make_pair(id, parent[i]));
    }

    template <class out_t>
    out_t all_parents(class_id_t const& id, out_t out) const
    {

        for (auto r = parent_.equal_range(id); r.first != r.second; ++r.first) {
            *out++ = r.first->second;
            out = all_parents(r.first->second, out);
        }

        return out;
    }
};

class event_t {
public:
    virtual class_id_t id() const = 0;
    virtual std::vector<class_id_t> parent() const = 0;
};

inline void register_type(type_registry_t& r, event_t const& e)
{
    r.register_type(e.id(), e.parent());
}

class listener_t {
    std::vector<class_id_t> listen_for_;

protected:
    listener_t(std::vector<class_id_t> const& listen_for)
    :   listen_for_ (listen_for)
    { }

public: 

    std::set<class_id_t> listen_for(type_registry_t const& reg) const
    {
        std::set<class_id_t> s;
        for (size_t i = 0, sz = listen_for_.size(); i < sz; ++i) {
            s.insert(listen_for_[i]);
            reg.all_parents(listen_for_[i], std::inserter(s, s.end()));
        }
        return s;
    }

    virtual void notify(event_t const&) = 0; 
};

class dispatcher_t {
    type_registry_t const* reg_;
    std::vector<std::shared_ptr<listener_t>> listener_;
public:
    dispatcher_t(type_registry_t const& reg)
    :   reg_ (&reg)
    { }

    void connect(std::shared_ptr<listener_t> const listener)
    {
        listener_.push_back(listener);
    }

    void signal(event_t& event)
    {
        class_id_t const id = event.id();
        for (size_t i = 0, sz = listener_.size(); i < sz; ++i) {
            std::set<class_id_t> const s = listener_[i]->listen_for(*reg_);

            if (s.find(id) != s.end())
                listener_[i]->notify(event);
        }
    }
};

This lets you perform the selection of events based on their position in the hierarchy. As in the following (which is what I think you were describing in your example).

struct foo_base_event_t : event_t {
    class_id_t id() const { return "foo_base_event_t"; }
    std::vector<class_id_t> parent() const
    { 
        std::vector<class_id_t> r;
        r.push_back("event_t");
        return r;
    }
};

struct foo_event_t : foo_base_event_t {
    class_id_t id() const { return "foo_event_t"; }
    std::vector<class_id_t> parent() const
    { 
        std::vector<class_id_t> r;
        r.push_back("foo_base_event_t");
        return r;
    }
};

struct foo_event_listener_t : listener_t {
    static std::vector<class_id_t> relevant_ids()
    {
        std::vector<class_id_t> r;
        r.push_back("foo_event_t");
        return r;
    }

    foo_event_listener_t()
    : listener_t (relevant_ids())
    { }

    void notify(event_t const& e)
    {
        std::cout << "foo_event_listener_t::notify() with " << typeid(e).name() << " " << (void*)&e << "\n";
    }
};

struct foo_base_event_listener_t : listener_t {
    static std::vector<class_id_t> relevant_ids()
    {
        std::vector<class_id_t> r;
        r.push_back("foo_base_event_t");
        return r;
    }

    foo_base_event_listener_t()
    : listener_t (relevant_ids())
    { }

    void notify(event_t const& e)
    {
        std::cout << "foo_base_event_listener_t::notify()" << typeid(e).name() << " " << (void*)&e << "\n";
    }
};

int main()
{
    type_registry_t reg;

    reg.register_type("event_t", std::vector<class_id_t>());
    reg.register_type("foo_base_event_t", std::vector<class_id_t>(1, "event_t"));
    reg.register_type("foo_event_t", std::vector<class_id_t>(1, "foo_base_event_t"));

    dispatcher_t dispatcher (reg);
    dispatcher.connect(std::shared_ptr<listener_t>(new foo_event_listener_t()));
    dispatcher.connect(std::shared_ptr<listener_t>(new foo_base_event_listener_t()));

    foo_base_event_t foo_base_event;
    dispatcher.signal(foo_base_event);

    foo_event_t foo_event;
    dispatcher.signal(foo_event);


    return 0;
}

You would need to expose some of this to Python, using your preferred method, to allow registration of event types. I have not included error checking and constructing the set of class_id's each call to listen_for() is probably slow.

Bowie Owens
  • 2,798
  • 23
  • 20
  • Thank you for the help. I already implemented a solution to this, but I didn't update the question. I posted an answer describing my own solution. – Paul Manta Jan 23 '12 at 21:51
  • Your welcome. Cheers for posting your code. It is interesting to consider the similarities and the differences. Looking at yours I release I forgot to suggest using macros. They can shorten a lot of a repetitive code in situations like this. I recently became aware of variadic macros. If your compiler supports them, they may be handy for defining the parent list. http://stackoverflow.com/questions/679979/how-to-make-a-variadic-macro-variable-number-of-arguments – Bowie Owens Jan 23 '12 at 23:24
  • Yes, that is what `PARENTS...` does. :) (You can name variadic macro parameters, if you want to.) – Paul Manta Jan 24 '12 at 07:41
  • I must have missed that in your code. Seems like we are on the same wavelength. :) – Bowie Owens Jan 25 '12 at 01:00