Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
50 changes: 49 additions & 1 deletion include/appbase/application.hpp
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
#pragma once
#include <appbase/plugin.hpp>
#include <appbase/channel.hpp>
#include <appbase/method.hpp>
#include <boost/filesystem/path.hpp>
#include <boost/core/demangle.hpp>
#include <boost/asio.hpp>

namespace appbase {
namespace bpo = boost::program_options;
Expand All @@ -13,6 +14,7 @@ namespace appbase {
public:
~application();


/** @brief Set version
*
* @param version Version output with -v/--version
Expand Down Expand Up @@ -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<typename MethodDecl>
auto get_method() -> std::enable_if_t<is_method_decl<MethodDecl>::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<typename ChannelDecl>
auto get_channel() -> std::enable_if_t<is_channel_decl<ChannelDecl>::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<typename Impl>
Expand All @@ -120,6 +164,10 @@ namespace appbase {
map<string, std::unique_ptr<abstract_plugin>> plugins; ///< all registered plugins
vector<abstract_plugin*> initialized_plugins; ///< stored in the order they were started running
vector<abstract_plugin*> running_plugins; ///< stored in the order they were started running

map<std::type_index, erased_method_ptr> methods;
map<std::type_index, erased_channel_ptr> channels;

std::shared_ptr<boost::asio::io_service> io_serv;

void set_program_options();
Expand Down
207 changes: 207 additions & 0 deletions include/appbase/channel.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,207 @@
#pragma once

//clashes with BOOST PP and Some Applications
#pragma push_macro("N")
#undef N

#include <boost/asio.hpp>
#include <boost/signals2.hpp>
#include <boost/exception/diagnostic_information.hpp>

namespace appbase {

using erased_channel_ptr = std::unique_ptr<void, void(*)(void*)>;

/**
* 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<typename InputIterator>
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<typename Data, typename DispatchPolicy>
class channel final {
public:
using ios_ptr_type = std::shared_ptr<boost::asio::io_service>;

/**
* 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<typename Callback>
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<std::is_copy_constructible<DispatchPolicy>::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<channel*>(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<channel*>(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<void(const Data&), DispatchPolicy> _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<Data, DispatchPolicy>;
using tag_type = Tag;
};

template <typename...Ts>
std::true_type is_channel_decl_impl(const channel_decl<Ts...>*);

std::false_type is_channel_decl_impl(...);

template <typename T>
using is_channel_decl = decltype(is_channel_decl_impl(std::declval<T*>()));
}

#pragma pop_macro("N")
Loading