diff options
Diffstat (limited to 'modules/remote/c++')
| -rw-r--r-- | modules/remote/c++/remote.hpp | 56 | ||||
| -rw-r--r-- | modules/remote/c++/remote_loopback.hpp | 113 |
2 files changed, 159 insertions, 10 deletions
diff --git a/modules/remote/c++/remote.hpp b/modules/remote/c++/remote.hpp index 83e6042..62d79c9 100644 --- a/modules/remote/c++/remote.hpp +++ b/modules/remote/c++/remote.hpp @@ -5,6 +5,7 @@ #include <forstio/codec/data.hpp> #include <forstio/codec/interface.hpp> +#include <forstio/codec/schema_hash.hpp> #include <variant> @@ -99,6 +100,61 @@ struct tmpl_group_to_data_client_std_tuple<tmpl_group<T...>, Encoding, Remote> { using type = std::tuple<data_client<T,Encoding,Remote>...>; }; } +/** + * Identifier of an interface function on the wire. + * + * Keyed on the member name instead of the member index, so reordering an + * interface doesn't silently rebind calls between versions. + */ +template<string_literal Lit> +struct rpc_function_id { + static constexpr uint32_t value = hash_literal<Lit>::apply(0u); +}; + +namespace impl { +/** + * Translates a runtime function id back into the compile time member name + * of an interface. + * + * Needed by every backend where the called function arrives as data instead + * of as a template argument. + */ +template<typename Iface> +struct iface_dispatch { + static_assert(always_false<Iface>, "Expecting an Interface Type"); +}; + +template<typename... T, string_literal... Names> +struct iface_dispatch<schema::Interface<schema::Member<T,Names>...>> { + template<typename Ret, uint64_t i, typename Func> + static Ret apply_ele(uint32_t func_id, Func& func){ + if constexpr ( i < sizeof...(Names) ){ + constexpr string_literal Lit = parameter_key_pack_type<i, Names...>::literal; + + if(func_id == rpc_function_id<Lit>::value){ + return func.template operator()<Lit>(); + } + + return apply_ele<Ret, i+1u>(func_id, func); + } else { + (void) func_id; + (void) func; + return make_error<err::not_found>("No interface member with this function id"); + } + } + + /** + * Calls func.operator()<Name>() for the member matching the id. + * The return type has to be provided, since there is no member to + * deduce it from if the id doesn't match anything. + */ + template<typename Ret, typename Func> + static Ret apply(uint32_t func_id, Func&& func){ + return apply_ele<Ret, 0u>(func_id, func); + } +}; +} + template<typename Remote> class i_rpc_server { protected: diff --git a/modules/remote/c++/remote_loopback.hpp b/modules/remote/c++/remote_loopback.hpp index d8cd7fa..3f39e41 100644 --- a/modules/remote/c++/remote_loopback.hpp +++ b/modules/remote/c++/remote_loopback.hpp @@ -111,16 +111,19 @@ public: * The parameter is either already present on the remote, in which case * only its id is handed over, or it is sent through the data layer * first. + * + * The id of the response is picked by the server and travels back with + * the answer. A client can't know which ids are free, so this is + * asynchronous in the id as well, not only in the data. */ template<string_literal Name> - error_or< + conveyor< remote_data<typename schema_member_type<Name, Iface>::type::ResponseT, Encoding, rmt::Loopback> > call(data_or_id<typename schema_member_type<Name, Iface>::type::RequestT, Encoding> input){ using Req = typename schema_member_type<Name, Iface>::type::RequestT; using Resp = typename schema_member_type<Name, Iface>::type::ResponseT; auto& req_client = get_data_client<Req>(); - auto& resp_client = get_data_client<Resp>(); /** * Resolve the parameter to an id on the remote. @@ -136,14 +139,37 @@ public: return std::move(eo_req_id.get_error()); } - /** - * Reserve the slot the response is supposed to end up in. - */ - auto eo_resp_id = resp_client.next_free_id(); + auto eo_resp_id = srv_().template call<Name>(eo_req_id.get_value()); if(eo_resp_id.is_error()){ return std::move(eo_resp_id.get_error()); } - auto resp_id = eo_resp_id.get_value(); + + return request_data<Resp>(eo_resp_id.get_value()); + } + + /** + * Call a function on the remote and store the response in a slot which + * was allocated beforehand. + */ + template<string_literal Name> + conveyor< + remote_data<typename schema_member_type<Name, Iface>::type::ResponseT, Encoding, rmt::Loopback> + > call(data_or_id<typename schema_member_type<Name, Iface>::type::RequestT, Encoding> input, id<typename schema_member_type<Name, Iface>::type::ResponseT> resp_id){ + using Req = typename schema_member_type<Name, Iface>::type::RequestT; + using Resp = typename schema_member_type<Name, Iface>::type::ResponseT; + + auto& req_client = get_data_client<Req>(); + + auto eo_req_id = [&]() -> error_or<id<Req>> { + if(input.is_id()){ + return input.get_id(); + } + + return req_client.send(input.get_data()); + }(); + if(eo_req_id.is_error()){ + return std::move(eo_req_id.get_error()); + } { auto eov = srv_().template call<Name>(eo_req_id.get_value(), resp_id); @@ -211,8 +237,69 @@ public: return dat_srvs_; } + /** + * Call a function which is only known at runtime. + * + * This is the entry point for every backend transporting the call + * itself. The ids are untyped here, since the wire only knows the + * function id and a number. The id the response ended up in is meant + * to travel back in the reply. + */ + error_or<uint64_t> call_by_id(uint32_t func_id, uint64_t id_param){ + auto dispatch = [&]<string_literal Lit>() -> error_or<uint64_t> { + using Req = typename schema_member_type<Lit, Iface>::type::RequestT; + + auto eov = this->template call<Lit>(id<Req>{id_param}); + if(eov.is_error()){ + return std::move(eov.get_error()); + } + + return eov.get_value().get_value(); + }; + + return impl::iface_dispatch<Iface>::template apply<error_or<uint64_t>>(func_id, dispatch); + } + + /** + * Runtime call into a slot the caller allocated beforehand. + */ + error_or<void> call_by_id(uint32_t func_id, uint64_t id_param, uint64_t id_resp){ + auto dispatch = [&]<string_literal Lit>() -> error_or<void> { + using Req = typename schema_member_type<Lit, Iface>::type::RequestT; + using Resp = typename schema_member_type<Lit, Iface>::type::ResponseT; + + return this->template call<Lit>(id<Req>{id_param}, id<Resp>{id_resp}); + }; + + return impl::iface_dispatch<Iface>::template apply<error_or<void>>(func_id, dispatch); + } + + /** + * Call a function and let the server decide where the response is + * stored. Clients can't know which ids are free without asking, so + * this is the variant which doesn't need a roundtrip beforehand. + */ + template<string_literal Lit> + error_or<id<typename schema_member_type<Lit,Iface>::type::ResponseT>> call(id<typename schema_member_type<Lit, Iface>::type::RequestT> id_param){ + return call_impl<Lit>(id_param, {}); + } + + /** + * Call a function and store the response in a slot which was allocated + * beforehand. + */ template<string_literal Lit> error_or<void> call(id<typename schema_member_type<Lit, Iface>::type::RequestT> id_param, id<typename schema_member_type<Lit,Iface>::type::ResponseT> id_resp){ + auto eov = call_impl<Lit>(id_param, id_resp); + if(eov.is_error()){ + return std::move(eov.get_error()); + } + + return make_void(); + } +private: + template<string_literal Lit> + error_or<id<typename schema_member_type<Lit,Iface>::type::ResponseT>> call_impl(id<typename schema_member_type<Lit, Iface>::type::RequestT> id_param, maybe<id<typename schema_member_type<Lit,Iface>::type::ResponseT>> id_resp){ using Req = typename schema_member_type<Lit,Iface>::type::RequestT; auto req_dat_ptr = std::get<ptr<data_server<Req,Encode,rmt::Loopback>>>(dat_srvs_); auto eo_req_param = req_dat_ptr().find(id_param); @@ -230,14 +317,20 @@ public: using Resp = typename schema_member_type<Lit,Iface>::type::ResponseT; auto resp_dat_ptr = std::get<ptr<data_server<Resp,Encode,rmt::Loopback>>>(dat_srvs_); - auto& resp_test_ptr = resp_dat_ptr(); + auto& resp_dat_srv = resp_dat_ptr(); - auto eo_send = resp_test_ptr.send(std::move(answer), id_resp); + /** + * The answer exists at this point, so this is the latest and + * therefore most accurate moment to pick a free slot. + */ + id<Resp> store_id = id_resp.has_value() ? id_resp.value() : resp_dat_srv.next_free_id(); + + auto eo_send = resp_dat_srv.send(std::move(answer), store_id); if(eo_send.is_error()){ return std::move(eo_send.get_error()); } - return make_void(); + return store_id; } }; |
