#pragma once #include "enable_polymorphic_dispatch.h" #include "detail/typelist.h" #include #include "detail/leftright.h" #include #include #include //! The mpm namespace namespace mpm { //! \defgroup Concepts //! Concept is a term that describes a named set of requirements for a type //! \defgroup Event //! \ingroup Concepts //! \{ //! //! An instance of any C++ object type. That is, anything but a function, //! reference, or void type //! //! \par Requirements //! Given:\n //! E, an implementation of the Event concept //! //! |Expression | Requirements | Return type | //! |:--------------------------------|:-------------------------|:-------------------| //! |std::is_object::value == true | E must be an object type | bool, must be true | //! \} //! \defgroup EventHandler //! \ingroup Concepts //! \{ //! //! A Callable that can be invoked to handle an instance of Event. //! Callable's INVOKE operation must be noexcept. //! //! \par Extends //! Callable //! //! \par Requirements //! Given:\n //! E an implementation of the Event concept, //! e and instance of E, //! H an implementation of the EventHandler concept handling events of type E, //! h an instance of H //! //! |Expression | Requirements | //! |:-------------------------------|:-----------------------| //! |h(e) | well-formed | //! |noexcept(h(e)) == true | h(e) must be noexcept | //! \} namespace detail { //! Adapts an instance of the Event concept into the //! class hierarchy rooted at mpm::detail::event template class adapted_event : public detail::event { public: using dispatch_as = detail::typelist; adapted_event(const Event &event) : m_event{event} {} operator const Event &() const { return m_event; } private: const Event &m_event; }; //! Holds the subscriber event type and the handler instance //! in a type-erased manner so that they can be put into a //! homogeneous container (e.g. the std::unordered_multimap as below) class subscription { public: using id_t = std::intptr_t; template subscription(const H &handler, const Alloc &alloc, E *) : m_self{std::allocate_shared>(alloc, handler)} {} void deliver(const event &e) { m_self->deliver(e); } id_t id() const { return reinterpret_cast(m_self.get()); } private: struct concept { virtual ~concept(){}; virtual void deliver(const event &e) = 0; }; // base template for events that extend detail::event (i.e. for // polymorphic dispatchable events). These can be safely // static_cast to the subscribed event type template struct model : concept { explicit model(H h) : handler(std::move(h)) {} void deliver(const event &e) final { handler(static_cast(e)); } H handler; }; // Specialization for events that do not use // enable_polymorphic_dispatch. The diffence is that we static_cast // to adapted_event. template struct model::value>::type> : concept { explicit model(H h) : handler(std::move(h)) {} void deliver(const event &e) final { handler(static_cast &>(e)); } H handler; }; std::shared_ptr m_self; }; struct cookie { cookie() = default; cookie(subscription::id_t _id, std::type_index _ti) : id(_id), ti(_ti) {} subscription::id_t id = 0; std::type_index ti = typeid(std::nullptr_t); }; //! Extract a dispatch typelist for an event supplying a dispatch_as //! typedef. If the supplied event type is not in the head position of //! the dispatch_as typelist then it is prepended to said list template struct dispatch_typelist { // todo C++14 can be std::conditional_t // most derived type goes first using type = typename std::conditional< std::is_same::value, typename E::dispatch_as, detail::typelist>::type; }; template struct deliver { deliver(const event &e, const SubsMap &subs) : m_subs{subs}, m_event{e} {} template void operator()() { auto handlers = m_subs.equal_range(std::type_index(typeid(T))); for (auto pos = handlers.first; pos != handlers.second; ++pos) { const_cast(pos->second).deliver(m_event); } } const SubsMap &m_subs; const event &m_event; }; // not meant to be used as a virtual base class - nothing should // destruct via this type class unsubscribable { public: virtual void unsubscribe(const cookie &c) = 0; protected: ~unsubscribable() {} }; }// namespace detail //! A small POD type representing a subscription using cookie = detail::cookie; //! Accepts events from publishers and delivers them to subscribers template class basic_eventbus : public detail::unsubscribable { public: //! The type of the Allocator used by this eventbus using allocator_type = Allocator; //! Delegates to basic_eventbus(allocator_type) using //! default construction for the allocator_type instance. basic_eventbus(); //! Constructs an instance using the supplied allocator_type. explicit basic_eventbus(allocator_type alloc); #ifdef DOCS //! Publish an instance of E. //! //! The event instance will be delivered either as only type E or //! if E has been defined with mpm::enable_polymorphic_dispatch it //! will be delivered as every type in its inheritance chain. //! //! \tparam E An instance of the Event concept //! \param event The event to publish //! \returns void template void publish(const E &event) noexcept; #else template typename std::enable_if::value>::type publish(const E &event) noexcept; template typename std::enable_if::value>::type publish(const E &event) noexcept; #endif //! Subscribe to instances of Event //! //! The supplied handler will be invoked when events of type Event //! are published or when classes derived from Event that have chosen //! to enable polymorphic dispatch are published. //! //! \tparam Event The type for which to subscribe //! \tparam EventHandler an instance of the EventHandler concept //! \param handler An instance of EventHandler //! \returns A cookie which will allow for this handler to be //! unsubscribed later via basic_eventbus::unsubscribe template cookie subscribe(const EventHandler &handler); //! Unsubscribes an event handler //! //! \param c A cookie obtained when basic_eventbus::subscribe was called //! \returns void void unsubscribe(const cookie &c) override; private: using subscriptions = leftright, std::equal_to, allocator_type>>; allocator_type m_alloc; subscriptions m_subscriptions; }; //! Default eventbus uses std::allocator using eventbus = basic_eventbus>; template basic_eventbus::basic_eventbus() : basic_eventbus(allocator_type()) {} template basic_eventbus::basic_eventbus(allocator_type alloc) : m_alloc{std::move(alloc)}, m_subscriptions{in_place, alloc} {} #ifndef DOCS template template typename std::enable_if::value>::type basic_eventbus::publish(const Event &event) noexcept { using types = typename detail::dispatch_typelist::type; m_subscriptions.observe([&](typename subscriptions::const_reference subs) { detail::for_each_type( detail::deliver{event, subs}); }); } template template typename std::enable_if::value>::type basic_eventbus::publish(const Event &event) noexcept { publish(detail::adapted_event{event}); } #endif template template cookie basic_eventbus::subscribe(const EventHandler &handler) { static_assert(std::is_object::value, "Events must be object types"); static_assert(noexcept(handler(std::declval())), "Need noexcept handler for Event"); Event *ptr{}; detail::subscription sub{handler, m_alloc, ptr}; return m_subscriptions.modify( [&](typename subscriptions::reference subs) noexcept { auto idx = std::type_index(typeid(Event)); subs.emplace(idx, sub); return cookie{sub.id(), idx}; }); } template void basic_eventbus::unsubscribe(const cookie &c) { m_subscriptions.modify( [=](typename subscriptions::reference subs) noexcept { auto range = subs.equal_range(c.ti); for (auto pos = range.first; pos != range.second; ++pos) { if (pos->second.id() == c.id) { subs.erase(pos); return; } } }); } //! An RAII type for eventbus subscriptions //! //! This type will ensure that the encapsulated eventbus subscription //! is released when it goes out of scope. //! //! \tparam Event the type of event for which this subscription subscribes template class scoped_subscription { public: using event_type = Event; //! Construct a scoped_subscription not managing any subscription scoped_subscription() = default; //! Move from another scoped_subscription. scoped_subscription(scoped_subscription &&other) noexcept : m_ebus{other.m_ebus}, m_cookie{other.m_cookie} { other.m_ebus = nullptr; } //! Copying a scoped_subscription is prohibited scoped_subscription(const scoped_subscription &) = delete; //! Move-assign from another scoped_subscription scoped_subscription &operator=(scoped_subscription &&other) noexcept { reset(); m_ebus = other.m_ebus; m_cookie = other.m_cookie; other.m_ebus = nullptr; } void swap(scoped_subscription &other) noexcept { using std::swap; swap(m_ebus, other.m_ebus); swap(m_cookie, other.m_cookie); } //! Initializes a subscription that will be usubscribed when //! this object goes out of scope. //! //! Subscription is constructed with ebus.subscribe(h) //! //! \tparam Alloc The allocator type of the event bus supplied //! \tparam Handler the type of the handler supplied //! \param ebus An instance of basic_eventbus to use for subscribing //! \param h An instance of an event handler template scoped_subscription(basic_eventbus &ebus, const Handler &h) : m_ebus{&ebus}, m_cookie{ebus.template subscribe(h)} {} //! Initializes a subscription that will be usubscribed when //! this object goes out of scope. //! //! Subscription is constructed with ebus.subscribe(h). If //! this object currently manages a subscription, that subscription //! will be cleared as though reset() had been called. //! //! \tparam Alloc The allocator type of the event bus supplied //! \tparam Handler the type of the handler supplied //! \param ebus An instance of basic_eventbus to use for subscribing //! \param h An instance of an event handler template void assign(basic_eventbus &ebus, const Handler &h) { reset(); m_ebus = &ebus; m_cookie = ebus.template subscribe(h); } //! Unsubscribes this objects managed subscription if one exists ~scoped_subscription() { reset(); } //! Unsubscribes this objects managed subscription if one exists //! \returns void void reset() { if (m_ebus) { m_ebus->unsubscribe(m_cookie); } } private: friend void swap(scoped_subscription &lhs, scoped_subscription &rhs) { lhs.swap(rhs); } detail::unsubscribable *m_ebus = nullptr;//non-owning cookie m_cookie{}; }; }// namespace mpm namespace ulib { template using enable_polymorphic_dispatch = mpm::enable_polymorphic_dispatch; // using eventbus = mpm::basic_eventbus>; using eventbus = mpm::basic_eventbus>>; template using scoped_subscription = mpm::scoped_subscription; }// namespace ulib