Implement modular API support

- Create class `api_base` to be base class of `api<T>`, and `typedef shared_ptr<api_base> api_ptr`
- Create function `api_base::as<T>()` to allow simple downcast to `api<T>`
- Create class `any_api` to contain an API which has been returned from the remote end, but not yet cast with `as<T>`
- `to_generic()` override allowing remote API to return `api_ptr`, thus we need not know the type of the returned API at compile time
- Allow API's to be referenced by name, if we call with a string API name in the JSON the framework calls get_api_by_name on API 1 to determine the API ID
This commit is contained in:
theoreticalbts 2016-04-03 23:29:50 -04:00
parent 2d521c390d
commit 5c1bb56177
4 changed files with 146 additions and 6 deletions

View file

@ -38,8 +38,33 @@ namespace fc {
OtherType& _source;
};
template<typename Interface, typename Transform >
class api;
class api_connection;
typedef uint32_t api_id_type;
class api_base
{
public:
api_base() {}
virtual ~api_base() {}
virtual uint64_t get_handle()const = 0;
virtual api_id_type register_api( api_connection& conn )const = 0;
// defined in api_connection.hpp
template< typename T >
api<T, identity_member> as();
};
typedef std::shared_ptr< api_base > api_ptr;
class api_connection;
template<typename Interface, typename Transform = identity_member >
class api {
class api : public api_base {
public:
typedef vtable<Interface,Transform> vtable_type;
@ -58,10 +83,12 @@ namespace fc {
}
api( const api& cpy ):_vtable(cpy._vtable),_data(cpy._data) {}
virtual ~api() {}
friend bool operator == ( const api& a, const api& b ) { return a._data == b._data && a._vtable == b._vtable; }
friend bool operator != ( const api& a, const api& b ) { return !(a._data == b._data && a._vtable == b._vtable); }
uint64_t get_handle()const { return uint64_t(_data.get()); }
virtual uint64_t get_handle()const override { return uint64_t(_data.get()); }
virtual api_id_type register_api( api_connection& conn )const override; // defined in api_connection.hpp
vtable_type& operator*()const { FC_ASSERT( _vtable ); return *_vtable; }
vtable_type* operator->()const { FC_ASSERT( _vtable ); return _vtable.get(); }

View file

@ -13,8 +13,6 @@
namespace fc {
class api_connection;
typedef uint32_t api_id_type;
namespace detail {
template<typename Signature>
class callback_functor
@ -68,6 +66,33 @@ namespace fc {
};
}
/**
* If api<T> is returned from a remote method, the API is eagerly bound to api<T> of
* the correct type in api_visitor::from_variant(). This binding [1] needs a reference
* to the api_connection, which is made available to from_variant() as a parameter.
*
* However, in the case of a remote method which returns api_base which can subsequently
* be cast by the caller with as<T>, we need to keep track of the connection because
* the binding is done later (when the client code actually calls as<T>).
*
* [1] The binding actually happens in get_remote_api().
*/
class any_api : public api_base
{
public:
any_api( api_id_type api_id, const std::shared_ptr<fc::api_connection>& con )
: _api_id(api_id), _api_connection(con) {}
virtual uint64_t get_handle()const override
{ return _api_id; }
virtual api_id_type register_api( api_connection& conn )const override
{ FC_ASSERT( false ); return api_id_type(); }
api_id_type _api_id;
std::weak_ptr<fc::api_connection> _api_connection;
};
} // namespace detail
class generic_api
@ -151,6 +176,9 @@ namespace fc {
template<typename Interface, typename Adaptor, typename ... Args>
std::function<variant(const fc::variants&)> to_generic( const std::function<fc::optional<api<Interface,Adaptor>>(Args...)>& f )const;
template<typename ... Args>
std::function<variant(const fc::variants&)> to_generic( const std::function<fc::api_ptr(Args...)>& f )const;
template<typename R, typename ... Args>
std::function<variant(const fc::variants&)> to_generic( const std::function<R(Args...)>& f )const;
@ -267,6 +295,15 @@ namespace fc {
return con->get_remote_api<ResultInterface>( v.as_uint64() );
}
static fc::api_ptr from_variant(
const variant& v,
fc::api_ptr* /* used for template deduction */,
const std::shared_ptr<fc::api_connection>& con
)
{
return fc::api_ptr( new detail::any_api( v.as_uint64(), con ) );
}
template<typename T>
static fc::variant convert_callbacks( const std::shared_ptr<fc::api_connection>&, const T& v )
{
@ -370,6 +407,24 @@ namespace fc {
return variant();
};
}
template<typename ... Args>
std::function<variant(const fc::variants&)> generic_api::api_visitor::to_generic(
const std::function<fc::api_ptr(Args...)>& f )const
{
auto api_con = _api_con;
auto gapi = &api;
return [=]( const variants& args ) -> fc::variant {
auto con = api_con.lock();
FC_ASSERT( con, "not connected" );
auto api_result = gapi->call_generic( f, args.begin(), args.end() );
if( !api_result )
return variant();
return api_result->register_api( *con );
};
}
template<typename R, typename ... Args>
std::function<variant(const fc::variants&)> generic_api::api_visitor::to_generic( const std::function<R(Args...)>& f )const
{
@ -389,6 +444,40 @@ namespace fc {
};
}
/**
* It is slightly unclean tight coupling to have this method in the api class.
* It breaks encapsulation by requiring an api class method to have a pointer
* to an api_connection. The reason this is necessary is we have a goal of being
* able to call register_api() on an api<T> through its base class api_base. But
* register_api() must know the template parameters!
*
* The only reasonable way to achieve the goal is to implement register_api()
* as a method in api<T> (which obviously knows the template parameter T),
* then make the implementation accessible through the base class (by making
* it a pure virtual method in the base class which is overridden by the subclass's
* implementation).
*/
template< typename Interface, typename Transform >
api_id_type api< Interface, Transform >::register_api( api_connection& conn )const
{
return conn.register_api( *this );
}
template< typename T >
api<T> api_base::as()
{
// TODO: this method should probably be const (if it is not too hard)
api<T>* maybe_requested_type = dynamic_cast< api<T>* >(this);
if( maybe_requested_type != nullptr )
return *maybe_requested_type;
detail::any_api* maybe_any = dynamic_cast< detail::any_api* >(this);
FC_ASSERT( maybe_any != nullptr );
std::shared_ptr< api_connection > api_conn = maybe_any->_api_connection.lock();
FC_ASSERT( api_conn );
return api_conn->get_remote_api<T>( maybe_any->_api_id );
}
namespace detail {
template<typename Signature>
template<typename... Args>

View file

@ -11,9 +11,22 @@ http_api_connection::http_api_connection()
{
_rpc_state.add_method( "call", [this]( const variants& args ) -> variant
{
// TODO: This logic is duplicated between http_api_connection and websocket_api_connection
// it should be consolidated into one place instead of copy-pasted
FC_ASSERT( args.size() == 3 && args[2].is_array() );
api_id_type api_id;
if( args[0].is_string() )
{
variants subargs;
subargs.push_back( args[0] );
variant subresult = this->receive_call( 1, "get_api_by_name", subargs );
api_id = subresult.as_uint64();
}
else
api_id = args[0].as_uint64();
return this->receive_call(
args[0].as_uint64(),
api_id,
args[1].as_string(),
args[2].get_array() );
} );

View file

@ -13,8 +13,19 @@ websocket_api_connection::websocket_api_connection( fc::http::websocket_connecti
_rpc_state.add_method( "call", [this]( const variants& args ) -> variant
{
FC_ASSERT( args.size() == 3 && args[2].is_array() );
api_id_type api_id;
if( args[0].is_string() )
{
variants subargs;
subargs.push_back( args[0] );
variant subresult = this->receive_call( 1, "get_api_by_name", subargs );
api_id = subresult.as_uint64();
}
else
api_id = args[0].as_uint64();
return this->receive_call(
args[0].as_uint64(),
api_id,
args[1].as_string(),
args[2].get_array() );
} );