From 039e2316417c7c2e0bc87b2f6b3df55481b3d64c Mon Sep 17 00:00:00 2001 From: Bart Wyatt Date: Wed, 18 Apr 2018 15:01:51 -0400 Subject: [PATCH 1/7] initial support for channels and methods --- include/appbase/application.hpp | 36 +++++++- include/appbase/channel.hpp | 158 ++++++++++++++++++++++++++++++++ include/appbase/method.hpp | 157 +++++++++++++++++++++++++++++++ 3 files changed, 350 insertions(+), 1 deletion(-) create mode 100644 include/appbase/channel.hpp create mode 100644 include/appbase/method.hpp diff --git a/include/appbase/application.hpp b/include/appbase/application.hpp index 571d26492..9011688dd 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,34 @@ namespace appbase { return *ptr; } + 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)); + } + } + + 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 +150,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..22342b770 --- /dev/null +++ b/include/appbase/channel.hpp @@ -0,0 +1,158 @@ +#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; + + 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 : private boost::signals2::signal { + public: + using ios_ptr_type = std::shared_ptr; + using handle_type = boost::signals2::connection; + + /** + * Publish data to a channel + * @param data + */ + void publish(const Data& data) { + if (has_subscribers()) { + // this will copy data into the lambda + ios_ptr->post([this, data]() { + (*this)(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_type subscribe(Callback cb) { + return this->connect(cb); + } + + /** + * unsubscribe from data on a channel + * @param handle + */ + void unsubscribe(const handle_type& handle) { + handle.disconnect(); + } + + /** + * 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> + { + (*this).set_combiner(policy); + } + + /** + * Returns whether or not there are subscribers + */ + bool has_subscribers() { + return (*this).num_slots() > 0; + } + + private: + channel(const ios_ptr_type& ios_ptr) + :ios_ptr(ios_ptr) + { + } + + virtual ~channel() {}; + + /** + * 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) { + channel *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; + + friend class appbase::application; + }; + + 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..e088dd0a2 --- /dev/null +++ b/include/appbase/method.hpp @@ -0,0 +1,157 @@ +#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; + + template + struct dispatch_policy_helper_impl { + using result_type = Ret; + }; + + template + struct function_sig_helper; + + template + struct function_sig_helper { + using result_type = typename dispatch_policy_helper_impl::result_type; + using args_tuple_type = std::tuple; + }; + + template + struct first_success_policy { + using result_type = typename function_sig_helper::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 new 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 : private boost::signals2::signal { + public: + using args_tuple_type = typename function_sig_helper::args_tuple_type; + using result_type = typename function_sig_helper::result_type; + + /** + * 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 + void register_provider(T provider, int priority = 0) { + this->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 this->operator()(args...); + } + + protected: + method() = default; + virtual ~method() {}; + + /** + * 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) { + method *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); + } + + friend class appbase::application; + }; + + + 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") + From b82f863361768fe13e1b4c81fb4873e8d93d41c3 Mon Sep 17 00:00:00 2001 From: Bart Wyatt Date: Wed, 25 Apr 2018 22:31:12 -0400 Subject: [PATCH 2/7] remove inheritance from signal, now composed. also fix bad throw --- include/appbase/channel.hpp | 17 +++++++++-------- include/appbase/method.hpp | 14 ++++++++------ 2 files changed, 17 insertions(+), 14 deletions(-) diff --git a/include/appbase/channel.hpp b/include/appbase/channel.hpp index 22342b770..9792541ed 100644 --- a/include/appbase/channel.hpp +++ b/include/appbase/channel.hpp @@ -40,7 +40,7 @@ namespace appbase { * @tparam Data - the type of data to publish */ template - class channel final : private boost::signals2::signal { + class channel final { public: using ios_ptr_type = std::shared_ptr; using handle_type = boost::signals2::connection; @@ -53,7 +53,7 @@ namespace appbase { if (has_subscribers()) { // this will copy data into the lambda ios_ptr->post([this, data]() { - (*this)(data); + _signal(data); }); } } @@ -66,7 +66,7 @@ namespace appbase { */ template handle_type subscribe(Callback cb) { - return this->connect(cb); + return _signal.connect(cb); } /** @@ -87,23 +87,23 @@ namespace appbase { */ auto set_dispatcher(const DispatchPolicy& policy ) -> std::enable_if_t::value,void> { - (*this).set_combiner(policy); + _signal.set_combiner(policy); } /** * Returns whether or not there are subscribers */ bool has_subscribers() { - return (*this).num_slots() > 0; + return _signal.num_slots() > 0; } private: - channel(const ios_ptr_type& ios_ptr) + explicit channel(const ios_ptr_type& ios_ptr) :ios_ptr(ios_ptr) { } - virtual ~channel() {}; + virtual ~channel() = default; /** * Proper deleter for type-erased channel @@ -112,7 +112,7 @@ namespace appbase { * @param erased_channel_ptr */ static void deleter(void* erased_channel_ptr) { - channel *ptr = reinterpret_cast(erased_channel_ptr); + auto ptr = reinterpret_cast(erased_channel_ptr); delete ptr; } @@ -136,6 +136,7 @@ namespace appbase { } ios_ptr_type ios_ptr; + boost::signals2::signal _signal; friend class appbase::application; }; diff --git a/include/appbase/method.hpp b/include/appbase/method.hpp index e088dd0a2..d95abea17 100644 --- a/include/appbase/method.hpp +++ b/include/appbase/method.hpp @@ -56,7 +56,7 @@ namespace appbase { ++first; } - throw new std::length_error(std::string("No Result Available, All providers returned exceptions[") + err + "]"); + throw std::length_error(std::string("No Result Available, All providers returned exceptions[") + err + "]"); } }; @@ -71,7 +71,7 @@ namespace appbase { * @tparam DispatchPolicy - the policy for dispatching this method */ template - class method final : private boost::signals2::signal { + class method final { public: using args_tuple_type = typename function_sig_helper::args_tuple_type; using result_type = typename function_sig_helper::result_type; @@ -85,7 +85,7 @@ namespace appbase { */ template void register_provider(T provider, int priority = 0) { - this->connect(priority, provider); + _signal.connect(priority, provider); } /** @@ -96,12 +96,12 @@ namespace appbase { template auto operator()(Args ... args) -> typename std::enable_if_t, args_tuple_type>::value, result_type> { - return this->operator()(args...); + return _signal(args...); } protected: method() = default; - virtual ~method() {}; + virtual ~method() = default; /** * Proper deleter for type-erased method @@ -110,7 +110,7 @@ namespace appbase { * @param erased_method_ptr */ static void deleter(void* erased_method_ptr) { - method *ptr = reinterpret_cast(erased_method_ptr); + auto ptr = reinterpret_cast(erased_method_ptr); delete ptr; } @@ -132,6 +132,8 @@ namespace appbase { return erased_method_ptr(new method(), &deleter); } + boost::signals2::signal _signal; + friend class appbase::application; }; From 24cb7425908af922fbf67708f9c8ce447056c85c Mon Sep 17 00:00:00 2001 From: Bart Wyatt Date: Thu, 26 Apr 2018 09:06:16 -0400 Subject: [PATCH 3/7] added forwarding to the methods --- include/appbase/channel.hpp | 1 + include/appbase/method.hpp | 20 +++++++++++--------- 2 files changed, 12 insertions(+), 9 deletions(-) diff --git a/include/appbase/channel.hpp b/include/appbase/channel.hpp index 9792541ed..ffbe089be 100644 --- a/include/appbase/channel.hpp +++ b/include/appbase/channel.hpp @@ -44,6 +44,7 @@ namespace appbase { public: using ios_ptr_type = std::shared_ptr; using handle_type = boost::signals2::connection; + using scoped_handle_type = boost::signals2::scoped_connection; /** * Publish data to a channel diff --git a/include/appbase/method.hpp b/include/appbase/method.hpp index d95abea17..8e6c8c3d3 100644 --- a/include/appbase/method.hpp +++ b/include/appbase/method.hpp @@ -16,18 +16,19 @@ namespace appbase { using result_type = Ret; }; - template - struct function_sig_helper; + template + struct method_traits; template - struct function_sig_helper { + struct method_traits { using result_type = typename dispatch_policy_helper_impl::result_type; using args_tuple_type = std::tuple; + }; template struct first_success_policy { - using result_type = typename function_sig_helper::result_type; + using result_type = typename method_traits::result_type; std::string err; /** @@ -73,8 +74,9 @@ namespace appbase { template class method final { public: - using args_tuple_type = typename function_sig_helper::args_tuple_type; - using result_type = typename function_sig_helper::result_type; + using traits = method_traits; + using args_tuple_type = traits::args_tuple_type; + using result_type = traits::result_type; /** * Register a provider of this method @@ -94,9 +96,9 @@ namespace appbase { * @throws exception depending on the DispatchPolicy */ template - auto operator()(Args ... args) -> typename std::enable_if_t, args_tuple_type>::value, result_type> + auto operator()(Args&&... args) -> typename std::enable_if_t, args_tuple_type>::value, result_type> { - return _signal(args...); + return _signal(std::forward(args...)); } protected: @@ -138,7 +140,7 @@ namespace appbase { }; - template< typename Tag, typename FunctionSig, typename DispatchPolicy = first_success_policy > + template< typename Tag, typename FunctionSig, typename DispatchPolicy = first_success_policy> struct method_decl { using method_type = method; using tag_type = Tag; From f969e0f3d1070b3336c51d046eca7dd50ced7f0f Mon Sep 17 00:00:00 2001 From: Bart Wyatt Date: Thu, 26 Apr 2018 11:46:09 -0400 Subject: [PATCH 4/7] fix typos --- include/appbase/method.hpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/include/appbase/method.hpp b/include/appbase/method.hpp index 8e6c8c3d3..74ed8ee03 100644 --- a/include/appbase/method.hpp +++ b/include/appbase/method.hpp @@ -75,8 +75,8 @@ namespace appbase { class method final { public: using traits = method_traits; - using args_tuple_type = traits::args_tuple_type; - using result_type = traits::result_type; + using args_tuple_type = typename traits::args_tuple_type; + using result_type = typename traits::result_type; /** * Register a provider of this method @@ -98,7 +98,7 @@ namespace appbase { template auto operator()(Args&&... args) -> typename std::enable_if_t, args_tuple_type>::value, result_type> { - return _signal(std::forward(args...)); + return _signal(std::forward(args)...); } protected: From dd2245eca2de769661bb906f30a5195af46faab6 Mon Sep 17 00:00:00 2001 From: Bart Wyatt Date: Fri, 27 Apr 2018 18:37:49 -0400 Subject: [PATCH 5/7] wrap handle for unsub --- include/appbase/channel.hpp | 37 +++++++++++++++++++++++++------------ 1 file changed, 25 insertions(+), 12 deletions(-) diff --git a/include/appbase/channel.hpp b/include/appbase/channel.hpp index ffbe089be..0152c281d 100644 --- a/include/appbase/channel.hpp +++ b/include/appbase/channel.hpp @@ -43,8 +43,29 @@ namespace appbase { class channel final { public: using ios_ptr_type = std::shared_ptr; - using handle_type = boost::signals2::connection; - using scoped_handle_type = boost::signals2::scoped_connection; + + class handle { + public: + ~handle() { + unsubscribe(); + } + + void unsubscribe() { + if (_handle.connected()) { + _handle.disconnect(); + } + } + + private: + using handle_type = boost::signals2::connection; + handle_type _handle; + + handle(handle_type&& _handle) + :_handle(std::move(_handle)) + {} + + friend class channel; + }; /** * Publish data to a channel @@ -66,16 +87,8 @@ namespace appbase { * @return handle to the subscription */ template - handle_type subscribe(Callback cb) { - return _signal.connect(cb); - } - - /** - * unsubscribe from data on a channel - * @param handle - */ - void unsubscribe(const handle_type& handle) { - handle.disconnect(); + handle subscribe(Callback cb) { + return handle(_signal.connect(cb)); } /** From ca2e56c7081d1f29225b8ea711f05e34da7df0d2 Mon Sep 17 00:00:00 2001 From: Bart Wyatt Date: Sat, 28 Apr 2018 15:39:51 -0400 Subject: [PATCH 6/7] delcare and delete constructors so that handles are moved instead of duplicated/copied --- include/appbase/channel.hpp | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/include/appbase/channel.hpp b/include/appbase/channel.hpp index 0152c281d..3facf5d4c 100644 --- a/include/appbase/channel.hpp +++ b/include/appbase/channel.hpp @@ -56,6 +56,14 @@ namespace appbase { } } + 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; @@ -108,7 +116,8 @@ namespace appbase { * Returns whether or not there are subscribers */ bool has_subscribers() { - return _signal.num_slots() > 0; + auto connections = _signal.num_slots(); + return connections > 0; } private: From 189094f209628408a627018ec38f8bfca96123dd Mon Sep 17 00:00:00 2001 From: Bart Wyatt Date: Mon, 30 Apr 2018 11:46:43 -0400 Subject: [PATCH 7/7] add documentation --- include/appbase/application.hpp | 14 ++++++ include/appbase/channel.hpp | 29 ++++++++++- include/appbase/method.hpp | 88 +++++++++++++++++++++++++++------ 3 files changed, 114 insertions(+), 17 deletions(-) diff --git a/include/appbase/application.hpp b/include/appbase/application.hpp index 9011688dd..761ed6b27 100644 --- a/include/appbase/application.hpp +++ b/include/appbase/application.hpp @@ -102,6 +102,13 @@ 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&> { @@ -116,6 +123,13 @@ namespace appbase { } } + /** + * 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&> { diff --git a/include/appbase/channel.hpp b/include/appbase/channel.hpp index 3facf5d4c..f8a710875 100644 --- a/include/appbase/channel.hpp +++ b/include/appbase/channel.hpp @@ -12,6 +12,10 @@ 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; @@ -44,18 +48,27 @@ namespace appbase { 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; @@ -68,6 +81,12 @@ namespace appbase { 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)) {} @@ -76,8 +95,8 @@ namespace appbase { }; /** - * Publish data to a channel - * @param data + * 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()) { @@ -164,6 +183,12 @@ namespace appbase { 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; diff --git a/include/appbase/method.hpp b/include/appbase/method.hpp index 74ed8ee03..e7c7408e1 100644 --- a/include/appbase/method.hpp +++ b/include/appbase/method.hpp @@ -11,24 +11,30 @@ namespace appbase { using erased_method_ptr = std::unique_ptr; - template - struct dispatch_policy_helper_impl { - using result_type = Ret; - }; + namespace impl { + template + struct dispatch_policy_helper_impl { + using result_type = Ret; + }; - template - struct method_traits; + template + struct method_traits; - template - struct method_traits { - using result_type = typename dispatch_policy_helper_impl::result_type; - using args_tuple_type = std::tuple; + 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 method_traits::result_type; + using result_type = typename impl::method_traits::result_type; std::string err; /** @@ -74,10 +80,56 @@ namespace appbase { template class method final { public: - using traits = method_traits; + 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 * @@ -86,8 +138,8 @@ namespace appbase { * @param priority - the priority of this provider, lower is called before higher */ template - void register_provider(T provider, int priority = 0) { - _signal.connect(priority, provider); + handle register_provider(T provider, int priority = 0) { + return handle(_signal.connect(priority, provider)); } /** @@ -140,6 +192,12 @@ namespace appbase { }; + /** + * + * @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;