summaryrefslogtreecommitdiff
path: root/modules/remote/c++
diff options
context:
space:
mode:
Diffstat (limited to 'modules/remote/c++')
-rw-r--r--modules/remote/c++/remote.hpp56
-rw-r--r--modules/remote/c++/remote_loopback.hpp113
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;
}
};