From 55fb3a9221ea91453573453288ecedb6ea79b7ba Mon Sep 17 00:00:00 2001 From: "Claudius \"keldu\" Holeksa" Date: Sat, 7 Nov 2026 17:30:41 +0100 Subject: Creating out of source doc files --- modules/remote/c++/remote.hpp | 56 ++++++++++++++++ modules/remote/c++/remote_loopback.hpp | 113 ++++++++++++++++++++++++++++++--- 2 files changed, 159 insertions(+), 10 deletions(-) (limited to 'modules/remote/c++') 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 #include +#include #include @@ -99,6 +100,61 @@ struct tmpl_group_to_data_client_std_tuple, Encoding, Remote> { using type = std::tuple...>; }; } +/** + * 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 +struct rpc_function_id { + static constexpr uint32_t value = hash_literal::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 +struct iface_dispatch { + static_assert(always_false, "Expecting an Interface Type"); +}; + +template +struct iface_dispatch...>> { + template + static Ret apply_ele(uint32_t func_id, Func& func){ + if constexpr ( i < sizeof...(Names) ){ + constexpr string_literal Lit = parameter_key_pack_type::literal; + + if(func_id == rpc_function_id::value){ + return func.template operator()(); + } + + return apply_ele(func_id, func); + } else { + (void) func_id; + (void) func; + return make_error("No interface member with this function id"); + } + } + + /** + * Calls func.operator()() 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 + static Ret apply(uint32_t func_id, Func&& func){ + return apply_ele(func_id, func); + } +}; +} + template 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 - error_or< + conveyor< remote_data::type::ResponseT, Encoding, rmt::Loopback> > call(data_or_id::type::RequestT, Encoding> input){ using Req = typename schema_member_type::type::RequestT; using Resp = typename schema_member_type::type::ResponseT; auto& req_client = get_data_client(); - auto& resp_client = get_data_client(); /** * 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(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(eo_resp_id.get_value()); + } + + /** + * Call a function on the remote and store the response in a slot which + * was allocated beforehand. + */ + template + conveyor< + remote_data::type::ResponseT, Encoding, rmt::Loopback> + > call(data_or_id::type::RequestT, Encoding> input, id::type::ResponseT> resp_id){ + using Req = typename schema_member_type::type::RequestT; + using Resp = typename schema_member_type::type::ResponseT; + + auto& req_client = get_data_client(); + + auto eo_req_id = [&]() -> error_or> { + 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(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 call_by_id(uint32_t func_id, uint64_t id_param){ + auto dispatch = [&]() -> error_or { + using Req = typename schema_member_type::type::RequestT; + + auto eov = this->template call(id{id_param}); + if(eov.is_error()){ + return std::move(eov.get_error()); + } + + return eov.get_value().get_value(); + }; + + return impl::iface_dispatch::template apply>(func_id, dispatch); + } + + /** + * Runtime call into a slot the caller allocated beforehand. + */ + error_or call_by_id(uint32_t func_id, uint64_t id_param, uint64_t id_resp){ + auto dispatch = [&]() -> error_or { + using Req = typename schema_member_type::type::RequestT; + using Resp = typename schema_member_type::type::ResponseT; + + return this->template call(id{id_param}, id{id_resp}); + }; + + return impl::iface_dispatch::template apply>(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 + error_or::type::ResponseT>> call(id::type::RequestT> id_param){ + return call_impl(id_param, {}); + } + + /** + * Call a function and store the response in a slot which was allocated + * beforehand. + */ template error_or call(id::type::RequestT> id_param, id::type::ResponseT> id_resp){ + auto eov = call_impl(id_param, id_resp); + if(eov.is_error()){ + return std::move(eov.get_error()); + } + + return make_void(); + } +private: + template + error_or::type::ResponseT>> call_impl(id::type::RequestT> id_param, maybe::type::ResponseT>> id_resp){ using Req = typename schema_member_type::type::RequestT; auto req_dat_ptr = std::get>>(dat_srvs_); auto eo_req_param = req_dat_ptr().find(id_param); @@ -230,14 +317,20 @@ public: using Resp = typename schema_member_type::type::ResponseT; auto resp_dat_ptr = std::get>>(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 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; } }; -- cgit v1.3.1