diff --git a/include/appbase/application.hpp b/include/appbase/application.hpp index 571d26492..761ed6b27 100644 --- a/include/appbase/application.hpp +++ b/include/appbase/application.hpp @@ -1,8 +1,9 @@ #pragma once #include +#include +#include #include #include -#include namespace appbase { namespace bpo = boost::program_options; @@ -13,6 +14,7 @@ namespace appbase { public: ~application(); + /** @brief Set version * * @param version Version output with -v/--version @@ -100,6 +102,48 @@ namespace appbase { return *ptr; } + /** + * Fetch a reference to the method declared by the passed in type. This will construct the method + * on first access. This allows loose and deferred binding between plugins + * + * @tparam MethodDecl - @ref appbase::method_decl + * @return reference to the method described by the declaration + */ + template + auto get_method() -> std::enable_if_t::value, typename MethodDecl::method_type&> + { + using method_type = typename MethodDecl::method_type; + auto key = std::type_index(typeid(MethodDecl)); + auto itr = methods.find(key); + if(itr != methods.end()) { + return *method_type::get_method(itr->second); + } else { + methods.emplace(std::make_pair(key, method_type::make_unique())); + return *method_type::get_method(methods.at(key)); + } + } + + /** + * Fetch a reference to the channel declared by the passed in type. This will construct the channel + * on first access. This allows loose and deferred binding between plugins + * + * @tparam ChannelDecl - @ref appbase::channel_decl + * @return reference to the channel described by the declaration + */ + template + auto get_channel() -> std::enable_if_t::value, typename ChannelDecl::channel_type&> + { + using channel_type = typename ChannelDecl::channel_type; + auto key = std::type_index(typeid(ChannelDecl)); + auto itr = channels.find(key); + if(itr != channels.end()) { + return *channel_type::get_channel(itr->second); + } else { + channels.emplace(std::make_pair(key, channel_type::make_unique(io_serv))); + return *channel_type::get_channel(channels.at(key)); + } + } + boost::asio::io_service& get_io_service() { return *io_serv; } protected: template @@ -120,6 +164,10 @@ namespace appbase { map> plugins; ///< all registered plugins vector initialized_plugins; ///< stored in the order they were started running vector running_plugins; ///< stored in the order they were started running + + map methods; + map channels; + std::shared_ptr io_serv; void set_program_options(); diff --git a/include/appbase/channel.hpp b/include/appbase/channel.hpp new file mode 100644 index 000000000..f8a710875 --- /dev/null +++ b/include/appbase/channel.hpp @@ -0,0 +1,207 @@ +#pragma once + +//clashes with BOOST PP and Some Applications +#pragma push_macro("N") +#undef N + +#include +#include +#include + +namespace appbase { + + using erased_channel_ptr = std::unique_ptr; + + /** + * A basic DispatchPolicy that will catch and drop any exceptions thrown + * during the dispatch of messages on a channel + */ + struct drop_exceptions { + drop_exceptions() = default; + using result_type = void; + + template + result_type operator()(InputIterator first, InputIterator last) { + while (first != last) { + try { + *first; + } catch (...) { + // drop + } + ++first; + } + } + }; + + /** + * A channel is a loosely bound asynchronous data pub/sub concept. + * + * This removes the need to tightly couple different plugins in the application for the use-case of + * sending data around + * + * Data passed to a channel is *copied*, consider using a shared_ptr if the use-case allows it + * + * @tparam Data - the type of data to publish + */ + template + class channel final { + public: + using ios_ptr_type = std::shared_ptr; + + /** + * Type that represents an active subscription to a channel allowing + * for ownership via RAII and also explicit unsubscribe actions + */ + class handle { + public: + ~handle() { + unsubscribe(); + } + + /** + * Explicitly unsubcribe from channel before the lifetime + * of this object expires + */ + void unsubscribe() { + if (_handle.connected()) { + _handle.disconnect(); + } + } + + // This handle can be constructed and moved + handle() = default; + handle(handle&&) = default; + handle& operator= (handle&& rhs) = default; + + // dont allow copying since this protects the resource + handle(const handle& ) = delete; + handle& operator= (const handle& ) = delete; + + private: + using handle_type = boost::signals2::connection; + handle_type _handle; + + /** + * Construct a handle from an internal represenation of a handle + * In this case a boost::signals2::connection + * + * @param _handle - the boost::signals2::connection to wrap + */ + handle(handle_type&& _handle) + :_handle(std::move(_handle)) + {} + + friend class channel; + }; + + /** + * Publish data to a channel. This data is *copied* on publish. + * @param data - the data to publish + */ + void publish(const Data& data) { + if (has_subscribers()) { + // this will copy data into the lambda + ios_ptr->post([this, data]() { + _signal(data); + }); + } + } + + /** + * subscribe to data on a channel + * @tparam Callback the type of the callback (functor|lambda) + * @param cb the callback + * @return handle to the subscription + */ + template + handle subscribe(Callback cb) { + return handle(_signal.connect(cb)); + } + + /** + * set the dispatcher according to the DispatchPolicy + * this can be used to set a stateful dispatcher + * + * This method is only available when the DispatchPolicy is copy constructible due to implementation details + * + * @param policy - the DispatchPolicy to copy + */ + auto set_dispatcher(const DispatchPolicy& policy ) -> std::enable_if_t::value,void> + { + _signal.set_combiner(policy); + } + + /** + * Returns whether or not there are subscribers + */ + bool has_subscribers() { + auto connections = _signal.num_slots(); + return connections > 0; + } + + private: + explicit channel(const ios_ptr_type& ios_ptr) + :ios_ptr(ios_ptr) + { + } + + virtual ~channel() = default; + + /** + * Proper deleter for type-erased channel + * note: no type checking is performed at this level + * + * @param erased_channel_ptr + */ + static void deleter(void* erased_channel_ptr) { + auto ptr = reinterpret_cast(erased_channel_ptr); + delete ptr; + } + + /** + * get the channel back from an erased pointer + * + * @param ptr - the type-erased channel pointer + * @return - the type safe channel pointer + */ + static channel* get_channel(erased_channel_ptr& ptr) { + return reinterpret_cast(ptr.get()); + } + + /** + * Construct a unique_ptr for the type erased method poiner + * @return + */ + static erased_channel_ptr make_unique(const ios_ptr_type& ios_ptr) + { + return erased_channel_ptr(new channel(ios_ptr), &deleter); + } + + ios_ptr_type ios_ptr; + boost::signals2::signal _signal; + + friend class appbase::application; + }; + + /** + * + * @tparam Tag - API specific discriminator used to distinguish between otherwise identical data types + * @tparam Data - the typ of the Data the channel carries + * @tparam DispatchPolicy - The dispatch policy to use for this channel (defaults to @ref drop_exceptions) + */ + template< typename Tag, typename Data, typename DispatchPolicy = drop_exceptions > + struct channel_decl { + using channel_type = channel; + using tag_type = Tag; + }; + + template + std::true_type is_channel_decl_impl(const channel_decl*); + + std::false_type is_channel_decl_impl(...); + + template + using is_channel_decl = decltype(is_channel_decl_impl(std::declval())); +} + +#pragma pop_macro("N") diff --git a/include/appbase/method.hpp b/include/appbase/method.hpp new file mode 100644 index 000000000..e7c7408e1 --- /dev/null +++ b/include/appbase/method.hpp @@ -0,0 +1,219 @@ +#pragma once + +//clashes with BOOST PP and Some Applications +#pragma push_macro("N") +#undef N + +#include +#include + +namespace appbase { + + using erased_method_ptr = std::unique_ptr; + + namespace impl { + template + struct dispatch_policy_helper_impl { + using result_type = Ret; + }; + + template + struct method_traits; + + template + struct method_traits { + using result_type = typename dispatch_policy_helper_impl::result_type; + using args_tuple_type = std::tuple; + + }; + } + + /** + * Basic DispatchPolicy that will try providers sequentially until one succeeds + * without throwing an exception. that result becomes the result of the method + */ + template + struct first_success_policy { + using result_type = typename impl::method_traits::result_type; + std::string err; + + /** + * Iterate through the providers, calling (dereferencing) each + * if the provider throws, then store then try the next provider + * if none succeed throw an error with the aggregated error descriptions + * + * @tparam InputIterator + * @param first + * @param last + * @return + */ + template + result_type operator()(InputIterator first, InputIterator last) { + while (first != last) { + try { + return *first; // de-referencing the iterator causes the provider to run + } catch (...) { + if (!err.empty()) { + err += "\",\""; + } + + err += boost::current_exception_diagnostic_information(); + } + + ++first; + } + + throw std::length_error(std::string("No Result Available, All providers returned exceptions[") + err + "]"); + } + }; + + /** + * A method is a loosely linked application level function. + * Callers can grab a method and call it + * Providers can grab a method and register themselves + * + * This removes the need to tightly couple different plugins in the application. + * + * @tparam FunctionSig - the signature of the method (eg void(int, int)) + * @tparam DispatchPolicy - the policy for dispatching this method + */ + template + class method final { + public: + using traits = impl::method_traits; + using args_tuple_type = typename traits::args_tuple_type; + using result_type = typename traits::result_type; + + /** + * Type that represents a registered provider for a method allowing + * for ownership via RAII and also explicit unregistered actions + */ + class handle { + public: + ~handle() { + unregister(); + } + + /** + * Explicitly unregister a provider for this channel + * of this object expires + */ + void unregister() { + if (_handle.connected()) { + _handle.disconnect(); + } + } + + // This handle can be constructed and moved + handle() = default; + handle(handle&&) = default; + handle& operator= (handle&& rhs) = default; + + // dont allow copying since this protects the resource + handle(const handle& ) = delete; + handle& operator= (const handle& ) = delete; + + private: + using handle_type = boost::signals2::connection; + handle_type _handle; + + /** + * Construct a handle from an internal represenation of a handle + * In this case a boost::signals2::connection + * + * @param _handle - the boost::signals2::connection to wrap + */ + handle(handle_type&& _handle) + :_handle(std::move(_handle)) + {} + + friend class method; + }; + + /** + * Register a provider of this method + * + * @tparam T - the type of the provider (functor, lambda) + * @param provider - the provider + * @param priority - the priority of this provider, lower is called before higher + */ + template + handle register_provider(T provider, int priority = 0) { + return handle(_signal.connect(priority, provider)); + } + + /** + * inhereted call operator from boost::signals2 + * + * @throws exception depending on the DispatchPolicy + */ + template + auto operator()(Args&&... args) -> typename std::enable_if_t, args_tuple_type>::value, result_type> + { + return _signal(std::forward(args)...); + } + + protected: + method() = default; + virtual ~method() = default; + + /** + * Proper deleter for type-erased method + * note: no type checking is performed at this level + * + * @param erased_method_ptr + */ + static void deleter(void* erased_method_ptr) { + auto ptr = reinterpret_cast(erased_method_ptr); + delete ptr; + } + + /** + * get the method* back from an erased pointer + * + * @param ptr - the type-erased method pointer + * @return - the type safe method pointer + */ + static method* get_method(erased_method_ptr& ptr) { + return reinterpret_cast(ptr.get()); + } + + /** + * Construct a unique_ptr for the type erased method poiner + * @return + */ + static erased_method_ptr make_unique() { + return erased_method_ptr(new method(), &deleter); + } + + boost::signals2::signal _signal; + + friend class appbase::application; + }; + + + /** + * + * @tparam Tag - API specific discriminator used to distinguish between otherwise identical method signatures + * @tparam FunctionSig - the signature of the method + * @tparam DispatchPolicy - dispatch policy that dictates how providers for a method are accessed defaults to @ref first_success_policy + */ + template< typename Tag, typename FunctionSig, typename DispatchPolicy = first_success_policy> + struct method_decl { + using method_type = method; + using tag_type = Tag; + }; + + template + std::true_type is_method_decl_impl(const method_decl*); + + std::false_type is_method_decl_impl(...); + + template + using is_method_decl = decltype(is_method_decl_impl(std::declval())); + + +} + +#pragma pop_macro("N") +