40#if defined(RPC_HPP_DOXYGEN_GEN)
42# define RPC_HPP_ENABLE_SERVER_CACHE
44# define RPC_HPP_CLIENT_IMPL
46# define RPC_HPP_MODULE_IMPL
48# define RPC_HPP_SERVER_IMPL
51#if !defined(RPC_HPP_CLIENT_IMPL) && !defined(RPC_HPP_SERVER_IMPL) && !defined(RPC_HPP_MODULE_IMPL)
52# error At least one implementation type must be defined using 'RPC_HPP_{CLIENT, SERVER, MODULE}_IMPL'
64#if defined(RPC_HPP_MODULE_IMPL) || defined(RPC_HPP_SERVER_IMPL)
66# include <unordered_map>
69#if defined(RPC_HPP_SERVER_IMPL) || defined(RPC_HPP_MODULE_IMPL)
70# define RPC_HEADER_FUNC(RETURN, FUNCNAME, ...) extern RETURN FUNCNAME(__VA_ARGS__)
71#elif defined(RPC_HPP_CLIENT_IMPL)
72# define RPC_HEADER_FUNC(RETURN, FUNCNAME, ...) inline RETURN (*FUNCNAME)(__VA_ARGS__) = nullptr
75#define RPC_HPP_PRECONDITION(EXPR) assert(EXPR)
76#define RPC_HPP_POSTCONDITION(EXPR) assert(EXPR)
78#if defined(__GNUC__) || defined(__clang__)
79# define RPC_HPP_INLINE __attribute__((always_inline))
80#elif defined(_MSC_VER)
81# define RPC_HPP_INLINE __forceinline
83# define RPC_HPP_INLINE
90static constexpr unsigned version[]{ 0, 8, 1 };
92enum class exception_type
109 explicit rpc_exception(
const std::string& mesg, exception_type type) noexcept
110 : std::runtime_error(mesg), m_type(type)
114 explicit rpc_exception(
const char* mesg, exception_type type) noexcept
115 : std::runtime_error(mesg), m_type(type)
119 exception_type get_type()
const noexcept {
return m_type; }
122 exception_type m_type;
260#if !defined(RPC_HPP_DOXYGEN_GEN)
261 template<
typename,
typename T>
262 struct is_serializable_base
264 static_assert(std::integral_constant<T, false>::value,
265 "Second template parameter needs to be of function type");
268 template<
typename C,
typename R,
typename... Args>
269 struct is_serializable_base<C, R(Args...)>
273 static constexpr auto check(T*)
noexcept ->
274 typename std::is_same<decltype(std::declval<T>().serialize(std::declval<Args>()...)),
278 static constexpr std::false_type check(...)
noexcept;
280 using type =
decltype(check<C>(
nullptr));
283 static constexpr bool value = type::value;
286 template<
typename,
typename T>
287 struct is_deserializable_base
289 static_assert(std::integral_constant<T, false>::value,
290 "Second template parameter needs to be of function type");
293 template<
typename C,
typename R,
typename... Args>
294 struct is_deserializable_base<C, R(Args...)>
298 static constexpr auto check(T*)
noexcept ->
299 typename std::is_same<decltype(std::declval<T>().deserialize(std::declval<Args>()...)),
303 static constexpr std::false_type check(...)
noexcept;
305 using type =
decltype(check<C>(
nullptr));
308 static constexpr bool value = type::value;
311 template<
typename Serial,
typename Value>
312 struct is_serializable :
313 std::integral_constant<bool,
314 is_serializable_base<Value, typename Serial::serial_t(const Value&)>::value
315 && is_deserializable_base<Value, Value(const typename Serial::serial_t&)>::value>
319 template<
typename Serial,
typename Value>
320 inline constexpr bool is_serializable_v = is_serializable<Serial, Value>::value;
327 static constexpr auto check(T*)
noexcept ->
328 typename std::is_same<decltype(std::declval<T>().begin()),
typename T::iterator>::type;
331 static constexpr std::false_type check(...)
noexcept;
333 using type =
decltype(check<C>(
nullptr));
336 static constexpr bool value = type::value;
344 static constexpr auto check(T*)
noexcept ->
345 typename std::is_same<decltype(std::declval<T>().end()),
typename T::iterator>::type;
348 static constexpr std::false_type check(...)
noexcept;
350 using type =
decltype(check<C>(
nullptr));
353 static constexpr bool value = type::value;
361 static constexpr auto check(T*)
noexcept ->
362 typename std::is_same<decltype(std::declval<T>().size()),
size_t>::type;
365 static constexpr std::false_type check(...)
noexcept;
367 using type =
decltype(check<C>(
nullptr));
370 static constexpr bool value = type::value;
374 struct is_container :
375 std::integral_constant<bool, has_size<C>::value && has_begin<C>::value && has_end<C>::value>
380 inline constexpr bool is_container_v = is_container<C>::value;
382 template<
typename F,
typename... Ts,
size_t... Is>
383 constexpr void for_each_tuple(
const std::tuple<Ts...>& tuple,
const F& func,
384 [[maybe_unused]] std::index_sequence<Is...> iseq)
386 using expander =
int[];
387 std::ignore = expander{ 0, ((void)func(std::get<Is>(tuple)), 0)... };
390 template<
typename F,
typename... Ts>
391 constexpr void for_each_tuple(
const std::tuple<Ts...>& tuple,
const F& func)
393 for_each_tuple(tuple, func, std::make_index_sequence<
sizeof...(Ts)>());
396# if defined(RPC_HPP_CLIENT_IMPL)
401 static_assert(!std::is_pointer_v<std::remove_cv_t<std::remove_reference_t<T>>>,
402 "Pointer parameters are not allowed");
404 static_assert(!std::is_array_v<std::remove_cv_t<std::remove_reference_t<T>>>,
405 "C-style array parameters are not allowed");
411 struct decay_str<const char*>
413 using type =
const std::string&;
417 struct decay_str<const char*&>
419 using type =
const std::string&;
423 struct decay_str<const char*
const&>
425 using type =
const std::string&;
429 struct decay_str<const char (&)[N]>
431 using type =
const std::string&;
435 using decay_str_t =
typename decay_str<T>::type;
437 template<
typename... Args,
size_t... Is>
438 constexpr void tuple_bind(
439 const std::tuple<std::remove_cv_t<std::remove_reference_t<decay_str_t<Args>>>...>& src,
440 std::index_sequence<Is...>, Args&&... dest)
442 using expander =
int[];
443 std::ignore = expander{ 0,
445 (void)[](
auto&& x,
auto&& y) {
448 decltype(x)> && !std::is_const_v<std::remove_reference_t<
decltype(x)>> && !std::is_pointer_v<std::remove_reference_t<
decltype(x)>>)
450 x = std::forward<decltype(y)>(y);
452 }(dest, std::get<Is>(src)),
456 template<
typename... Args>
457 constexpr void tuple_bind(
458 const std::tuple<std::remove_cv_t<std::remove_reference_t<decay_str_t<Args>>>...>& src,
461 tuple_bind(src, std::make_index_sequence<
sizeof...(Args)>(), std::forward<Args>(dest)...);
465 template<
typename... Args>
466 class packed_func_base
469 using args_t = std::tuple<std::remove_cv_t<std::remove_reference_t<Args>>...>;
471 packed_func_base() =
delete;
472 packed_func_base(std::string func_name, args_t args) noexcept
473 : m_func_name(std::move(func_name)), m_args(std::move(args))
477 explicit operator bool()
const noexcept {
return m_except_type == exception_type::none; }
478 const std::string& get_err_mesg()
const noexcept {
return m_err_mesg; }
479 const std::string& get_func_name()
const noexcept {
return m_func_name; }
480 exception_type get_except_type()
const noexcept {
return m_except_type; }
482 void set_exception(std::string&& mesg,
const exception_type type) &
noexcept
484 m_except_type = type;
485 m_err_mesg = std::move(mesg);
488 const args_t& get_args()
const noexcept {
return m_args; }
489 args_t& get_args()
noexcept {
return m_args; }
492 ~packed_func_base()
noexcept =
default;
493 packed_func_base(
const packed_func_base&) =
default;
494 packed_func_base(packed_func_base&&)
noexcept =
default;
495 packed_func_base& operator=(
const packed_func_base&) & =
default;
496 packed_func_base& operator=(packed_func_base&&) &
noexcept =
default;
498 [[noreturn]]
void throw_ex()
const noexcept(
false)
500 switch (m_except_type)
502 case exception_type::func_not_found:
505 case exception_type::remote_exec:
508 case exception_type::serialization:
511 case exception_type::deserialization:
514 case exception_type::signature_mismatch:
517 case exception_type::client_send:
520 case exception_type::client_receive:
523 case exception_type::server_send:
526 case exception_type::server_receive:
529 case exception_type::none:
536 exception_type m_except_type{ exception_type::none };
537 std::string m_func_name;
538 std::string m_err_mesg{};
542 template<
typename R,
typename... Args>
543 class packed_func final :
public packed_func_base<Args...>
547 using typename packed_func_base<Args...>::args_t;
549 packed_func(std::string func_name, std::optional<result_t> result, args_t args) noexcept
550 : packed_func_base<Args...>(std::move(func_name), std::move(args)),
551 m_result(std::move(result))
555 explicit operator bool()
const noexcept
557 return m_result.has_value() && packed_func_base<Args...>::operator bool();
560 const R& get_result()
const
562 if (!
static_cast<bool>(*
this))
568 return m_result.value();
571 void set_result(
const R& value) &
noexcept(std::is_nothrow_copy_assignable_v<R>)
576 void set_result(R&& value) &
noexcept(std::is_nothrow_move_assignable_v<R>)
578 m_result = std::move(value);
581 void clear_result() &
noexcept { m_result.reset(); }
584 std::optional<result_t> m_result{};
587 template<
typename... Args>
588 class packed_func<void, Args...> final :
public packed_func_base<Args...>
591 using result_t = void;
592 using typename packed_func_base<Args...>::args_t;
593 using packed_func_base<Args...>::packed_func_base;
594 using packed_func_base<Args...>::operator bool;
596 void get_result()
const
598 if (!
static_cast<bool>(*
this))
606 template<
typename Adapter>
607 struct serial_adapter_base
612 static std::optional<serial_t> from_bytes(bytes_t&& bytes) =
delete;
613 static bytes_t to_bytes(serial_t&& serial_obj) =
delete;
614 static serial_t empty_object() =
delete;
616 template<
typename R,
typename... Args>
617 static serial_t serialize_pack(
const packed_func<R, Args...>& pack) =
delete;
619 template<
typename R,
typename... Args>
620 static packed_func<R, Args...> deserialize_pack(
const serial_t& serial_obj) =
delete;
622 static std::string get_func_name(
const serial_t& serial_obj) =
delete;
623 static rpc_exception extract_exception(
const serial_t& serial_obj) =
delete;
624 static void set_exception(serial_t& serial_obj,
const rpc_exception& ex) =
delete;
629#if defined(RPC_HPP_SERVER_IMPL) || defined(RPC_HPP_MODULE_IMPL)
633inline namespace server
638 template<
typename Serial>
642 using adapter_t = Serial;
653# if defined(RPC_HPP_SERVER_IMPL) && defined(RPC_HPP_ENABLE_SERVER_CACHE)
659 template<
typename Val>
661 const std::string& func_name)
663 RPC_HPP_PRECONDITION(!func_name.empty());
665 update_all_cache<Val>(func_name);
666 return *
static_cast<std::unordered_map<typename Serial::bytes_t, Val>*
>(
667 m_cache_map.at(func_name));
680 template<
typename R,
typename... Args>
683 m_dispatch_table.emplace(std::move(func_name),
684 [
this, func_ptr](
typename Serial::serial_t& serial_obj)
688 dispatch_cached_func(func_ptr, serial_obj);
692 Serial::set_exception(serial_obj, ex);
704 template<
typename R,
typename... Args,
typename F>
707 using fptr_t = R (*)(Args...);
709 bind_cached(std::move(func_name), fptr_t{ std::forward<F>(func) });
718 template<
typename R,
typename... Args>
719 void bind(std::string func_name, R (*func_ptr)(Args...))
721 m_dispatch_table.emplace(std::move(func_name),
722 [func_ptr](
typename Serial::serial_t& serial_obj)
726 dispatch_func(func_ptr, serial_obj);
730 Serial::set_exception(serial_obj, ex);
742 template<
typename R,
typename... Args,
typename F>
743 RPC_HPP_INLINE
void bind(std::string func_name, F&& func)
745 using fptr_t = R (*)(Args...);
747 bind(std::move(func_name), fptr_t{ std::forward<F>(func) });
755 [[nodiscard]]
typename Serial::bytes_t
dispatch(
typename Serial::bytes_t&& bytes)
const
757 auto serial_obj = Serial::from_bytes(std::move(bytes));
759 if (!serial_obj.has_value())
761 auto err_obj = Serial::empty_object();
763 return Serial::to_bytes(std::move(err_obj));
766 const auto func_name = adapter_t::get_func_name(serial_obj.value());
768 if (
const auto it = m_dispatch_table.find(func_name); it != m_dispatch_table.end())
770 it->second(serial_obj.value());
771 return Serial::to_bytes(std::move(serial_obj).value());
774 Serial::set_exception(serial_obj.value(),
777 return Serial::to_bytes(std::move(serial_obj).value());
783# if defined(RPC_HPP_SERVER_IMPL) && defined(RPC_HPP_ENABLE_SERVER_CACHE)
784 template<
typename R,
typename... Args>
785 void dispatch_cached_func(R (*func)(Args...),
typename Serial::serial_t& serial_obj)
787 RPC_HPP_PRECONDITION(func !=
nullptr);
789 auto pack = [&serial_obj]
793 return Serial::template deserialize_pack<R, Args...>(serial_obj);
799 catch (
const std::exception& ex)
801 throw deserialization_error(ex.what());
805 auto& result_cache = get_func_cache<R>(pack.get_func_name());
807 if constexpr (!std::is_void_v<R>)
809 auto bytes = Serial::to_bytes(std::move(serial_obj));
811 if (
const auto it = result_cache.find(bytes); it != result_cache.end())
813 pack.set_result(it->second);
817 serial_obj = Serial::template serialize_pack<R, Args...>(pack);
820 catch (
const rpc_exception&)
824 catch (
const std::exception& ex)
826 throw serialization_error(ex.what());
830 run_callback(func, pack);
831 result_cache[std::move(bytes)] = pack.get_result();
835 run_callback(func, pack);
840 serial_obj = Serial::template serialize_pack<R, Args...>(pack);
842 catch (
const rpc_exception&)
846 catch (
const std::exception& ex)
848 throw serialization_error(ex.what());
852 template<
typename R,
typename... Args>
853 RPC_HPP_INLINE
void dispatch_cached_func(
854 R (*func)(Args...),
typename Serial::serial_t& serial_obj)
const
856 dispatch_func(func, serial_obj);
860 template<
typename R,
typename... Args>
861 static void dispatch_func(R (*func)(Args...),
typename Serial::serial_t& serial_obj)
863 RPC_HPP_PRECONDITION(func !=
nullptr);
865 auto pack = [&serial_obj]
869 return Serial::template deserialize_pack<R, Args...>(serial_obj);
871 catch (
const rpc_exception&)
875 catch (
const std::exception& ex)
877 throw deserialization_error(ex.what());
881 run_callback(func, pack);
885 serial_obj = Serial::template serialize_pack<R, Args...>(pack);
887 catch (
const rpc_exception&)
891 catch (
const std::exception& ex)
893 throw serialization_error(ex.what());
898 template<
typename R,
typename... Args>
899 static void run_callback(R (*func)(Args...), detail::packed_func<R, Args...>& pack)
901 RPC_HPP_PRECONDITION(func !=
nullptr);
903 auto& args = pack.get_args();
905 if constexpr (std::is_void_v<R>)
909 std::apply(func, args);
911 catch (
const std::exception& ex)
913 throw remote_exec_error(ex.what());
920 auto result = std::apply(func, args);
921 pack.set_result(std::move(result));
923 catch (
const std::exception& ex)
925 throw remote_exec_error(ex.what());
930# if defined(RPC_HPP_SERVER_IMPL) && defined(RPC_HPP_ENABLE_SERVER_CACHE)
931 template<
typename Val>
932 static void* get_func_cache_impl(
const std::string& func_name)
934 static std::unordered_map<std::string,
935 std::unordered_map<typename Serial::bytes_t, Val>>
938 return &cache[func_name];
941 template<
typename Val>
942 void update_all_cache(
const std::string& func_name)
944 RPC_HPP_PRECONDITION(!func_name.empty());
946 m_cache_map.insert_or_assign(func_name, get_func_cache_impl<Val>(func_name));
949 std::unordered_map<std::string, void*> m_cache_map{};
952 std::unordered_map<std::string, std::function<void(
typename Serial::serial_t&)>>
958#if defined(RPC_HPP_CLIENT_IMPL)
959# define call_header_func(FUNCNAME, ...) call_header_func_impl(FUNCNAME, # FUNCNAME, __VA_ARGS__)
964inline namespace client
969 template<
typename Serial>
993 template<
typename R = void,
typename... Args>
994 [[nodiscard]] R
call_func(std::string func_name, Args&&... args)
996 RPC_HPP_PRECONDITION(!func_name.empty());
999 serialize_call<R, Args...>(std::move(func_name), std::forward<Args>(args)...);
1003 send(std::move(bytes));
1005 catch (
const std::exception& ex)
1014 catch (
const std::exception& ex)
1019 const auto pack = deserialize_call<R, Args...>(std::move(bytes));
1022 detail::tuple_bind(pack.get_args(), std::forward<Args>(args)...);
1023 return pack.get_result();
1035 template<
typename R,
typename... Args>
1037 [[maybe_unused]] R (*func)(Args...), std::string func_name, Args&&... args)
1039 RPC_HPP_PRECONDITION(!func_name.empty());
1041 return call_func<R, Args...>(std::move(func_name), std::forward<Args>(args)...);
1050 virtual
void send(const typename Serial::bytes_t& bytes) = 0;
1058 template<typename R, typename... Args>
1059 static RPC_HPP_INLINE typename Serial::bytes_t serialize_call(
1060 std::
string func_name, Args&&... args)
1062 detail::packed_func<R, detail::decay_str_t<Args>...> pack = [&]()
noexcept
1064 if constexpr (std::is_void_v<R>)
1066 return detail::packed_func<void, detail::decay_str_t<Args>...>{
1067 std::move(func_name), std::forward_as_tuple(args...)
1072 return detail::packed_func<R, detail::decay_str_t<Args>...>{
1073 std::move(func_name), std::nullopt, std::forward_as_tuple(args...)
1078 auto serial_obj = [&pack]
1082 return Serial::serialize_pack(pack);
1088 catch (
const std::exception& ex)
1094 return Serial::to_bytes(std::move(serial_obj));
1097 template<
typename R,
typename... Args>
1098 static RPC_HPP_INLINE
auto deserialize_call(
typename Serial::bytes_t&& bytes)
1100 const auto ret_obj = Serial::from_bytes(std::move(bytes));
1102 if (!ret_obj.has_value())
1109 return Serial::template deserialize_pack<R, detail::decay_str_t<Args>...>(
1112 catch (
const rpc_exception&)
1116 catch (
const std::exception& ex)
1118 throw deserialization_error(ex.what());
Class defining an interface for calling into an RPC server or module.
Definition: rpc.hpp:971
virtual Serial::bytes_t receive()=0
Receives serialized data from a server or module.
R call_func(std::string func_name, Args &&... args)
Sends an RPC call request to a server, waits for a response, then returns the result.
Definition: rpc.hpp:994
R call_header_func_impl(R(*func)(Args...), std::string func_name, Args &&... args)
Sends an RPC call request to a server, waits for a response, then returns the result.
Definition: rpc.hpp:1036
virtual void send(const typename Serial::bytes_t &bytes)=0
Sends serialized data to a server or module.
Class defining an interface for serving functions via RPC.
Definition: rpc.hpp:640
std::unordered_map< typename Serial::bytes_t, Val > & get_func_cache(const std::string &func_name)
Gets a reference to the server's function cache.
Definition: rpc.hpp:660
Serial::bytes_t dispatch(typename Serial::bytes_t &&bytes) const
Parses the received serialized data and determines which function to call.
Definition: rpc.hpp:755
void bind(std::string func_name, R(*func_ptr)(Args...))
Binds a string to a callback.
Definition: rpc.hpp:719
void bind_cached(std::string func_name, R(*func_ptr)(Args...))
Binds a string to a callback, utilizing the server's cache.
Definition: rpc.hpp:681
RPC_HPP_INLINE void clear_all_cache() noexcept
Clears the server's function cache.
Definition: rpc.hpp:671
Top-level namespace for rpc.hpp classes and functions.
Definition: rpc.hpp:88