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:
parent
2d521c390d
commit
5c1bb56177
4 changed files with 146 additions and 6 deletions
|
|
@ -38,8 +38,33 @@ namespace fc {
|
||||||
OtherType& _source;
|
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 >
|
template<typename Interface, typename Transform = identity_member >
|
||||||
class api {
|
class api : public api_base {
|
||||||
public:
|
public:
|
||||||
typedef vtable<Interface,Transform> vtable_type;
|
typedef vtable<Interface,Transform> vtable_type;
|
||||||
|
|
||||||
|
|
@ -58,10 +83,12 @@ namespace fc {
|
||||||
}
|
}
|
||||||
|
|
||||||
api( const api& cpy ):_vtable(cpy._vtable),_data(cpy._data) {}
|
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; }
|
||||||
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; }
|
||||||
vtable_type* operator->()const { FC_ASSERT( _vtable ); return _vtable.get(); }
|
vtable_type* operator->()const { FC_ASSERT( _vtable ); return _vtable.get(); }
|
||||||
|
|
|
||||||
|
|
@ -13,8 +13,6 @@
|
||||||
namespace fc {
|
namespace fc {
|
||||||
class api_connection;
|
class api_connection;
|
||||||
|
|
||||||
typedef uint32_t api_id_type;
|
|
||||||
|
|
||||||
namespace detail {
|
namespace detail {
|
||||||
template<typename Signature>
|
template<typename Signature>
|
||||||
class callback_functor
|
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
|
} // namespace detail
|
||||||
|
|
||||||
class generic_api
|
class generic_api
|
||||||
|
|
@ -151,6 +176,9 @@ namespace fc {
|
||||||
template<typename Interface, typename Adaptor, typename ... Args>
|
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;
|
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>
|
template<typename R, typename ... Args>
|
||||||
std::function<variant(const fc::variants&)> to_generic( const std::function<R(Args...)>& f )const;
|
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() );
|
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>
|
template<typename T>
|
||||||
static fc::variant convert_callbacks( const std::shared_ptr<fc::api_connection>&, const T& v )
|
static fc::variant convert_callbacks( const std::shared_ptr<fc::api_connection>&, const T& v )
|
||||||
{
|
{
|
||||||
|
|
@ -370,6 +407,24 @@ namespace fc {
|
||||||
return variant();
|
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>
|
template<typename R, typename ... Args>
|
||||||
std::function<variant(const fc::variants&)> generic_api::api_visitor::to_generic( const std::function<R(Args...)>& f )const
|
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 {
|
namespace detail {
|
||||||
template<typename Signature>
|
template<typename Signature>
|
||||||
template<typename... Args>
|
template<typename... Args>
|
||||||
|
|
|
||||||
|
|
@ -11,9 +11,22 @@ http_api_connection::http_api_connection()
|
||||||
{
|
{
|
||||||
_rpc_state.add_method( "call", [this]( const variants& args ) -> variant
|
_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() );
|
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(
|
return this->receive_call(
|
||||||
args[0].as_uint64(),
|
api_id,
|
||||||
args[1].as_string(),
|
args[1].as_string(),
|
||||||
args[2].get_array() );
|
args[2].get_array() );
|
||||||
} );
|
} );
|
||||||
|
|
|
||||||
|
|
@ -13,8 +13,19 @@ websocket_api_connection::websocket_api_connection( fc::http::websocket_connecti
|
||||||
_rpc_state.add_method( "call", [this]( const variants& args ) -> variant
|
_rpc_state.add_method( "call", [this]( const variants& args ) -> variant
|
||||||
{
|
{
|
||||||
FC_ASSERT( args.size() == 3 && args[2].is_array() );
|
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(
|
return this->receive_call(
|
||||||
args[0].as_uint64(),
|
api_id,
|
||||||
args[1].as_string(),
|
args[1].as_string(),
|
||||||
args[2].get_array() );
|
args[2].get_array() );
|
||||||
} );
|
} );
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue