init
This commit is contained in:
commit
83b8a98f1c
|
|
@ -0,0 +1,3 @@
|
|||
Language: Cpp
|
||||
ColumnLimit: 180
|
||||
IndentPPDirectives: AfterHash
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
-I/usr/include/c++/14
|
||||
-I/usr/include/x86_64-linux-gnu/c++/14
|
||||
-I../build/_deps/tpl-src
|
||||
-I./thirdparty
|
||||
|
||||
-std=gnu++23
|
||||
|
|
@ -0,0 +1,63 @@
|
|||
cmake_minimum_required(VERSION 3.14)
|
||||
|
||||
include(FetchContent)
|
||||
set(FETCHCONTENT_QUIET FALSE)
|
||||
set(FTXUI_BUILD_EXAMPLES ON)
|
||||
|
||||
FetchContent_Declare(
|
||||
fmt
|
||||
GIT_REPOSITORY https://github.com/fmtlib/fmt
|
||||
GIT_PROGRESS TRUE
|
||||
)
|
||||
|
||||
FetchContent_MakeAvailable(fmt)
|
||||
FetchContent_Declare(
|
||||
inja
|
||||
GIT_REPOSITORY https://github.com/pantor/inja
|
||||
GIT_PROGRESS TRUE
|
||||
GIT_TAG main
|
||||
)
|
||||
|
||||
FetchContent_MakeAvailable(inja)
|
||||
|
||||
FetchContent_Declare(
|
||||
libzmq
|
||||
GIT_REPOSITORY https://github.com/zeromq/libzmq
|
||||
GIT_PROGRESS TRUE
|
||||
)
|
||||
|
||||
FetchContent_MakeAvailable(libzmq)
|
||||
|
||||
FetchContent_Declare(
|
||||
cppzmq
|
||||
GIT_REPOSITORY https://github.com/zeromq/cppzmq
|
||||
GIT_PROGRESS TRUE
|
||||
)
|
||||
|
||||
FetchContent_MakeAvailable(cppzmq)
|
||||
|
||||
project(module_arch_POC)
|
||||
set(CMAKE_CXX_STANDARD 23)
|
||||
set(CMAKE_BUILD_TYPE Debug)
|
||||
set(CMAKE_MESSAGE_LOG_LEVEL TRACE)
|
||||
set(CMAKE_VERBOSE_MAKEFILE OFF)
|
||||
set(CMAKE_CXX_COMPILER /usr/bin/clang++)
|
||||
|
||||
file(GLOB_RECURSE SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/src/*.hpp ${CMAKE_CURRENT_SOURCE_DIR}/src/*.cpp)
|
||||
add_executable(${CMAKE_PROJECT_NAME} ${SOURCES})
|
||||
|
||||
target_include_directories(${CMAKE_PROJECT_NAME} PRIVATE
|
||||
${CMAKE_CURRENT_SOURCE_DIR}
|
||||
${fmt_SOURCE_DIR}/include
|
||||
${inja_SOURCE_DIR}/include
|
||||
${libzmq_SOURCE_DIR}/include
|
||||
${cppzmq_SOURCE_DIR}/include
|
||||
)
|
||||
|
||||
target_link_directories(${CMAKE_PROJECT_NAME}
|
||||
PRIVATE
|
||||
${fmt_BINARY_DIR}
|
||||
${inja_BINARY_DIR}
|
||||
${libzmq_BINARY_DIR}
|
||||
${cppzmq_BINARY_DIR}
|
||||
)
|
||||
|
|
@ -0,0 +1,46 @@
|
|||
#pragma once
|
||||
|
||||
#include <boost/callable_traits.hpp>
|
||||
#include <boost/callable_traits/args.hpp>
|
||||
#include <boost/callable_traits/return_type.hpp>
|
||||
#include <boost/signals2.hpp>
|
||||
#include <boost/signals2/variadic_signal.hpp>
|
||||
#include <string>
|
||||
#include <tuple>
|
||||
|
||||
#define FMT_HEADER_ONLY
|
||||
#include <fmt/format.h>
|
||||
#include <fmt/ranges.h>
|
||||
|
||||
#include "adapter_base.hpp"
|
||||
|
||||
using namespace boost::callable_traits;
|
||||
|
||||
template <typename Encoder, typename Decoder>
|
||||
requires(
|
||||
// Encoder return type is same as Decoder argument type, encoder and decoder have only 1 arg
|
||||
std::tuple_size_v<args_t<Decoder>> == 1 && std::is_same_v<return_type_t<Encoder>, std::remove_cvref_t<std::tuple_element_t<0, args_t<Decoder>>>> &&
|
||||
std::tuple_size_v<args_t<Encoder>> == 1 && std::is_same_v<return_type_t<Decoder>, std::remove_cvref_t<std::tuple_element_t<0, args_t<Encoder>>>>)
|
||||
|
||||
class Adapter : public AdapterBase<std::tuple_element_t<0, boost::callable_traits::args_t<Encoder>>> {
|
||||
public:
|
||||
using encoder_type_t = Encoder;
|
||||
using decoder_type_t = Decoder;
|
||||
using adapter_type_t = Adapter;
|
||||
using callback_arg_type_t = std::tuple_element_t<0, boost::callable_traits::args_t<Encoder>>;
|
||||
using base_t = AdapterBase<callback_arg_type_t>;
|
||||
using callback_type_t = base_t::callback_type_t;
|
||||
|
||||
Adapter(const std::string &name, std::pair<Encoder, Decoder> &&fns)
|
||||
: AdapterBase<callback_arg_type_t>(name), mc_enc_(std::forward<Encoder>(fns.first)), mc_dec_(std::forward<Decoder>(fns.second)) {}
|
||||
|
||||
inline const auto &encoder() const { return mc_enc_; }
|
||||
inline const auto &decoder() const { return mc_dec_; }
|
||||
|
||||
typename base_t::callback_type_t &callback() const override final { return m_callback_; };
|
||||
|
||||
private:
|
||||
const Encoder mc_enc_;
|
||||
const Decoder mc_dec_;
|
||||
mutable callback_type_t m_callback_;
|
||||
};
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
#pragma once
|
||||
|
||||
#include <boost/signals2.hpp>
|
||||
|
||||
template <typename InType> class AdapterBase {
|
||||
public:
|
||||
using callback_type_t = boost::signals2::signal<void(const InType &)>;
|
||||
|
||||
AdapterBase(const std::string &name) : mc_name_(name) {}
|
||||
|
||||
virtual callback_type_t &callback() const = 0;
|
||||
inline const auto &name() const { return mc_name_; }
|
||||
|
||||
protected:
|
||||
private:
|
||||
const std::string mc_name_;
|
||||
};
|
||||
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
#pragma once
|
||||
|
||||
#include "adapter.hpp"
|
||||
|
||||
template <typename Encoder, typename Decoder> auto makeAdapter(const std::string &name, std::pair<Encoder, Decoder> &&fns) {
|
||||
return std::make_unique<Adapter<Encoder, Decoder>>(name, std::forward<std::pair<Encoder, Decoder>>(fns));
|
||||
}
|
||||
|
|
@ -0,0 +1,87 @@
|
|||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
#include <tuple>
|
||||
#include <unordered_map>
|
||||
#include <utility>
|
||||
#include <zmq.hpp>
|
||||
|
||||
#include "port_base.hpp"
|
||||
#include "tuple.hpp"
|
||||
|
||||
enum class module_type_e : uint32_t {
|
||||
STANDALONE,
|
||||
INCOMPOSITION,
|
||||
};
|
||||
|
||||
template <typename EnvDataType> class ModuleBase {
|
||||
public:
|
||||
ModuleBase(int32_t argc, char **argv, char **envp, const std::string &name)
|
||||
: mc_name_(name), mc_cli_args_({
|
||||
.argc = argc,
|
||||
.argv = argv,
|
||||
.envp = envp,
|
||||
}) {}
|
||||
|
||||
inline const auto &name() const { return mc_name_; }
|
||||
|
||||
virtual const PortBase<EnvDataType> &port(const std::string &name) const = 0;
|
||||
virtual void run(void (*entry)(int32_t, char **, char **, const std::unordered_map<std::string, const PortBase<EnvDataType> *> &)) const = 0;
|
||||
|
||||
protected:
|
||||
const auto &cli__() const { return mc_cli_args_; }
|
||||
|
||||
private:
|
||||
const std::string mc_name_;
|
||||
|
||||
const struct {
|
||||
int32_t argc;
|
||||
char **argv, **envp;
|
||||
} mc_cli_args_;
|
||||
};
|
||||
|
||||
template <typename... Ports> class Module : public ModuleBase<std::tuple_element_t<0, std::tuple<typename Ports::port_data_type_t...>>> {
|
||||
public:
|
||||
using port_data_type_t = std::tuple_element_t<0, std::tuple<typename Ports::port_data_type_t...>>;
|
||||
|
||||
Module(int32_t argc, char **argv, char **envp, const std::string &name, zmq::context_t &zmq_ctx, std::tuple<std::unique_ptr<Ports>...> &&ports)
|
||||
: ModuleBase<port_data_type_t>(argc, argv, envp, name), mc_ports_([&]<size_t... Ids>(std::index_sequence<Ids...>) {
|
||||
return std::make_tuple([&]<size_t Idx>() {
|
||||
using port_type_t = std::remove_cvref_t<decltype(std::get<Idx>(ports))>;
|
||||
auto &port = std::get<Idx>(ports);
|
||||
|
||||
return std::make_tuple(port->name(), std::hash<std::string>()(port->name()), std::forward<port_type_t>(port));
|
||||
}.template operator()<Ids>()...);
|
||||
}(std::make_index_sequence<sizeof...(Ports)>{})) {}
|
||||
|
||||
const PortBase<port_data_type_t> &port(const std::string &name) const override {
|
||||
const PortBase<port_data_type_t> *ret = nullptr;
|
||||
size_t hash = std::hash<std::string>()(name);
|
||||
|
||||
tp::for_each(mc_ports_, [&](const auto &p) {
|
||||
if (!ret) {
|
||||
const auto &[port_name, name_hash, port] = p;
|
||||
if (name == port_name) {
|
||||
ret = static_cast<const PortBase<port_data_type_t> *>(port.get());
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
if (!ret) {
|
||||
throw std::runtime_error(fmt::format("Port with name '{}' not found in '{}' module\r\n", name, this->name()));
|
||||
}
|
||||
|
||||
return *ret;
|
||||
}
|
||||
|
||||
void run(void (*entry)(int32_t, char **, char **, const std::unordered_map<std::string, const PortBase<port_data_type_t> *> &)) const override {
|
||||
entry(this->cli__().argc, this->cli__().argv, this->cli__().envp,
|
||||
[&]<size_t... Ids>(std::index_sequence<Ids...>) -> std::unordered_map<std::string, const PortBase<port_data_type_t> *> {
|
||||
return {std::make_pair(std::get<0u>(std::get<Ids>(ports_())), std::get<2u>(std::get<Ids>(ports_())).get())...};
|
||||
}(std::make_index_sequence<std::tuple_size_v<std::remove_cvref_t<decltype(ports_())>>>{}));
|
||||
}
|
||||
|
||||
private:
|
||||
inline const auto &ports_() const { return mc_ports_; }
|
||||
const std::tuple<std::tuple<std::string, size_t, std::unique_ptr<Ports>>...> mc_ports_;
|
||||
};
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
#pragma once
|
||||
|
||||
#include "module.hpp"
|
||||
#include <memory>
|
||||
|
||||
template <enum module_type_e mt, typename... Ports>
|
||||
auto makeModule(int32_t argc, char **argv, char **envp, const std::string &name, zmq::context_t &zmq_ctx, std::tuple<std::unique_ptr<Ports>...> &&ports) {
|
||||
return std::make_unique<Module<Ports...>>(argc, argv, envp, name, zmq_ctx, std::forward<std::tuple<std::unique_ptr<Ports>...>>(ports));
|
||||
}
|
||||
|
|
@ -0,0 +1,134 @@
|
|||
#pragma once
|
||||
|
||||
#include <boost/callable_traits.hpp>
|
||||
#include <boost/callable_traits/args.hpp>
|
||||
#include <boost/callable_traits/return_type.hpp>
|
||||
#include <cstddef>
|
||||
|
||||
#include "port_base.hpp"
|
||||
#include "ports.hpp"
|
||||
#include "tuple.hpp"
|
||||
|
||||
using namespace boost::callable_traits;
|
||||
|
||||
template <typename...> class Port;
|
||||
template <typename... Adapters, typename... Args>
|
||||
class Port<std::tuple<Adapters...>, std::tuple<Args...>> : public PortBase<std::tuple_element_t<0, std::tuple<return_type_t<typename Adapters::encoder_type_t>...>>> {
|
||||
|
||||
public:
|
||||
using port_data_type_t = std::tuple_element_t<0, std::tuple<return_type_t<typename Adapters::encoder_type_t>...>>;
|
||||
|
||||
Port(enum port_types_e pt, const std::string &name, const std::string &endpoint, zmq::context_t &zmq_ctx, std::tuple<std::unique_ptr<Adapters>...> &&adapters,
|
||||
std::tuple<Args...> &&args)
|
||||
: PortBase<port_data_type_t>(pt, name, endpoint, zmq_ctx),
|
||||
|
||||
// Init adapters
|
||||
mc_adapters_([&]<size_t... Ids>(std::index_sequence<Ids...>) {
|
||||
return std::make_tuple([&]<size_t Idx>() {
|
||||
using adapter_type_t = std::remove_cvref_t<decltype(*std::get<Idx>(adapters))>;
|
||||
using adapter_input_type_t = return_type_t<typename adapter_type_t::decoder_type_t>;
|
||||
|
||||
return std::make_tuple(std::get<Idx>(adapters)->name(), std::hash<std::string>()(std::get<Idx>(adapters)->name()),
|
||||
typeid(std::remove_cvref_t<adapter_input_type_t>).hash_code(), std::forward<std::unique_ptr<adapter_type_t>>(std::get<Idx>(adapters)));
|
||||
}.template operator()<Ids>()...);
|
||||
}(std::make_index_sequence<sizeof...(Adapters)>{})) {
|
||||
std::apply([&, this](auto &&...args) { init_impl_(pt, zmq_ctx, std::move(endpoint), std::forward<Args>(args)...); }, std::forward<decltype(args)>(args));
|
||||
}
|
||||
|
||||
void stop() const override {
|
||||
this->stop__();
|
||||
mc_impl_->close();
|
||||
}
|
||||
|
||||
protected:
|
||||
void send__(const void *data, size_t size, size_t hash) const override {
|
||||
tp::for_each(mc_adapters_, [&](const auto &e) {
|
||||
const auto &[adapter_name, adapter_namehash, adapter_typehash, adapter] = e;
|
||||
|
||||
if (adapter_typehash == hash) {
|
||||
using adapter_in_type_t = std::remove_cvref_t<return_type_t<decltype(adapter->decoder())>>;
|
||||
|
||||
typename PortImplBase<cbk_type_t_>::port_payload_s payload = {
|
||||
.typehash = hash,
|
||||
.data = adapter->encoder()(*reinterpret_cast<const adapter_in_type_t *>(data)),
|
||||
};
|
||||
|
||||
msgpack::sbuffer buf;
|
||||
msgpack::pack(buf, payload);
|
||||
mc_impl_->send(buf);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void *get_adapter__(const std::string &name, size_t namehash, size_t typehash) const override final {
|
||||
void *ret = nullptr;
|
||||
|
||||
tp::for_each(mc_adapters_, [&](auto &a) {
|
||||
if (!ret) {
|
||||
auto &[adapter_name, adapter_namehash, adapter_typehash, adapter] = a;
|
||||
if (adapter_typehash == typehash && adapter_namehash == namehash) {
|
||||
ret = reinterpret_cast<void *>(adapter.get());
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
if (!ret) {
|
||||
throw std::runtime_error(fmt::format("No adapter '{}' in port '{}'\r\n", name, this->name()));
|
||||
}
|
||||
|
||||
return ret;
|
||||
};
|
||||
|
||||
private:
|
||||
using base_t_ = PortBase<port_data_type_t>;
|
||||
using cbk_type_t_ = std::function<void(const port_data_type_t &, size_t)>;
|
||||
|
||||
mutable std::tuple<std::tuple<std::string, size_t, size_t, std::unique_ptr<Adapters>>...> mc_adapters_;
|
||||
mutable std::unique_ptr<PortImplBase<cbk_type_t_>> mc_impl_;
|
||||
|
||||
void listen__(std::stop_token st) const override { mc_impl_->listen(st); }
|
||||
|
||||
template <typename... ImplArgs> void init_impl_(enum port_types_e pt, ImplArgs &&...args) const {
|
||||
using enum port_types_e;
|
||||
static constexpr auto make_null_impl_pair = []<enum port_types_e port_type>() consteval {
|
||||
if constexpr (port_type == UNKNOWN) {
|
||||
return std::make_pair(port_type, static_cast<PortImplBase<cbk_type_t_> *>(nullptr));
|
||||
} else {
|
||||
return std::make_pair(port_type, static_cast<PortImpl<port_type, cbk_type_t_> *>(nullptr));
|
||||
}
|
||||
};
|
||||
|
||||
static constexpr auto impl_map =
|
||||
std::make_tuple(make_null_impl_pair.template operator()<UNKNOWN>(), make_null_impl_pair.template operator()<PUB>(), make_null_impl_pair.template operator()<SUB>(),
|
||||
make_null_impl_pair.template operator()<REQ>(), make_null_impl_pair.template operator()<REP>(), make_null_impl_pair.template operator()<ROUTER>(),
|
||||
make_null_impl_pair.template operator()<DEALER>(), make_null_impl_pair.template operator()<PUSH>(), make_null_impl_pair.template operator()<PULL>(),
|
||||
make_null_impl_pair.template operator()<PAIR>());
|
||||
|
||||
tp::for_each(impl_map, [&](const auto &p) {
|
||||
const auto &[type, null_pimpl] = p;
|
||||
if (type == pt) {
|
||||
using impl_type_t = std::remove_pointer_t<decltype(null_pimpl)>;
|
||||
|
||||
if constexpr (std::is_constructible_v<impl_type_t, const ImplArgs &..., cbk_type_t_ &&>) {
|
||||
mc_impl_ = std::make_unique<impl_type_t>(
|
||||
// Args
|
||||
std::forward<ImplArgs>(args)...,
|
||||
|
||||
// Callback
|
||||
[this](const port_data_type_t &data, size_t hash) {
|
||||
tp::for_each(mc_adapters_, [&](const auto &e) {
|
||||
const auto &[adapter_name, adapter_namehash, adapter_typehash, adapter] = e;
|
||||
if (adapter_typehash == hash) {
|
||||
adapter->callback()(adapter->decoder()(data));
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
template <typename... Adapters, typename... Args>
|
||||
Port(enum port_types_e, const std::string &, const std::string &, zmq::context_t &, std::tuple<std::unique_ptr<Adapters>...> &&, std::tuple<Args...> &&)
|
||||
-> Port<std::tuple<Adapters...>, std::tuple<Args...>>;
|
||||
|
|
@ -0,0 +1,68 @@
|
|||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
#include <future>
|
||||
#include <msgpack.hpp>
|
||||
#include <zmq.hpp>
|
||||
|
||||
#define FMT_HEADER_ONLY
|
||||
#include <fmt/format.h>
|
||||
#include <fmt/ranges.h>
|
||||
|
||||
#include "port_types.hpp"
|
||||
#include "adapter_base.hpp"
|
||||
|
||||
template <class T> constexpr std::string_view type_name() {
|
||||
using namespace std;
|
||||
#ifdef __clang__
|
||||
string_view p = __PRETTY_FUNCTION__;
|
||||
return string_view(p.data() + 34, p.size() - 34 - 1);
|
||||
#elif defined(__GNUC__)
|
||||
string_view p = __PRETTY_FUNCTION__;
|
||||
# if __cplusplus < 201402
|
||||
return string_view(p.data() + 36, p.size() - 36 - 1);
|
||||
# else
|
||||
return string_view(p.data() + 49, p.find(';', 49) - 49);
|
||||
# endif
|
||||
#elif defined(_MSC_VER)
|
||||
string_view p = __FUNCSIG__;
|
||||
return string_view(p.data() + 84, p.size() - 84 - 7);
|
||||
#endif
|
||||
}
|
||||
|
||||
template <typename EncodedType> class PortBase {
|
||||
public:
|
||||
PortBase(enum port_types_e port_type, const std::string &name, const std::string &endpoint, zmq::context_t &zmq_ctx)
|
||||
: mc_type_(port_type), mc_name_(name), mc_endpoint_(endpoint), mc_name_hash_(std::hash<std::string>()(name)), mc_endpoint_hash_(std::hash<std::string>()(endpoint)) {}
|
||||
|
||||
inline const auto &type() const { return mc_type_; }
|
||||
inline const auto &name() const { return mc_name_; }
|
||||
inline const auto &endpoint() const { return mc_endpoint_; }
|
||||
inline const auto &name_hash() const { return mc_name_hash_; }
|
||||
inline const auto &endpoint_hash() const { return mc_endpoint_hash_; }
|
||||
|
||||
template <typename InType> const PortBase<EncodedType> &operator<<(const InType &in) const {
|
||||
send__(&in, sizeof(InType), typeid(InType).hash_code());
|
||||
return *this;
|
||||
}
|
||||
|
||||
void listen() const { listen__(m_ss_.get_token()); };
|
||||
virtual void stop() const = 0;
|
||||
|
||||
template <typename AdapterInType> auto &callback(const std::string &name) const {
|
||||
return (*static_cast<AdapterBase<AdapterInType> *>(get_adapter__(name, std::hash<std::string>()(name), typeid(std::remove_cvref_t<AdapterInType>).hash_code()))).callback();
|
||||
}
|
||||
|
||||
protected:
|
||||
void stop__() const { m_ss_.request_stop(); }
|
||||
|
||||
virtual void send__(const void *data, size_t size, size_t type_hash) const = 0;
|
||||
virtual void listen__(std::stop_token) const = 0;
|
||||
virtual void *get_adapter__(const std::string &name, size_t namehash, size_t typehash) const = 0;
|
||||
|
||||
private:
|
||||
const enum port_types_e mc_type_;
|
||||
const std::string mc_name_, mc_endpoint_;
|
||||
const size_t mc_name_hash_, mc_endpoint_hash_;
|
||||
mutable std::stop_source m_ss_;
|
||||
};
|
||||
|
|
@ -0,0 +1,22 @@
|
|||
#pragma once
|
||||
|
||||
#include "port_impl_base.hpp"
|
||||
#include "port_types.hpp"
|
||||
|
||||
#define FMT_HEADER_ONLY
|
||||
#include <fmt/format.h>
|
||||
#include <fmt/ranges.h>
|
||||
|
||||
template <typename Callback> class PortImpl<port_types_e::DEALER, Callback> : public PortImplBase<Callback> {
|
||||
public:
|
||||
PortImpl(zmq::context_t &zmq_ctx, const std::string &endpoint, Callback &&callback) : PortImplBase<Callback>(zmq_ctx, endpoint, std::forward<Callback>(callback)) {}
|
||||
void listen(std::stop_token st) const override {
|
||||
while (!st.stop_requested()) {
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(100u));
|
||||
}
|
||||
}
|
||||
|
||||
// Send to socket depending on implementation
|
||||
void send(const msgpack::sbuffer &data) const override {};
|
||||
};
|
||||
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
#pragma once
|
||||
|
||||
#include "port.hpp"
|
||||
|
||||
template <typename... Adapters, typename... Args>
|
||||
auto makePort(enum port_types_e pt, const std::string &name, const std::string &endpoint, zmq::context_t &zmq_ctx, std::tuple<std::unique_ptr<Adapters>...> &&adapters,
|
||||
std::tuple<Args...> &&args) {
|
||||
return std::make_unique<Port<std::tuple<Adapters...>, std::tuple<Args...>>>(pt, name, endpoint, zmq_ctx, std::forward<std::tuple<std::unique_ptr<Adapters>...>>(adapters),
|
||||
std::forward<std::tuple<Args...>>(args));
|
||||
}
|
||||
|
|
@ -0,0 +1,40 @@
|
|||
#pragma once
|
||||
|
||||
#include <boost/callable_traits/args.hpp>
|
||||
#include <future>
|
||||
#include <msgpack.hpp>
|
||||
#include <tuple>
|
||||
#include <zmq.hpp>
|
||||
|
||||
template <typename Callback> class PortImplBase {
|
||||
using port_data_type_t_ = std::remove_cvref_t<std::tuple_element_t<0, boost::callable_traits::args_t<Callback>>>;
|
||||
|
||||
public:
|
||||
struct port_payload_s {
|
||||
size_t typehash;
|
||||
port_data_type_t_ data;
|
||||
MSGPACK_DEFINE(typehash, data);
|
||||
};
|
||||
|
||||
PortImplBase(zmq::context_t &zmq_ctx, const std::string &endpoint, Callback &&callback) : mc_endpoint__(endpoint), m_ctx__(zmq_ctx), mc_cbk__(callback) {}
|
||||
|
||||
virtual ~PortImplBase() = default;
|
||||
|
||||
virtual void listen(std::stop_token st) const = 0;
|
||||
virtual void send(const msgpack::sbuffer &data) const = 0;
|
||||
void close() {
|
||||
m_sock__.close();
|
||||
|
||||
if (m_listener_thread__.valid()) {
|
||||
m_listener_thread__.get();
|
||||
}
|
||||
};
|
||||
|
||||
protected:
|
||||
mutable std::future<void> m_listener_thread__;
|
||||
mutable zmq::socket_t m_sock__;
|
||||
zmq::context_t &m_ctx__;
|
||||
const std::string mc_endpoint__;
|
||||
const Callback mc_cbk__;
|
||||
|
||||
};
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
#pragma once
|
||||
|
||||
#include "port_impl_base.hpp"
|
||||
#include "port_types.hpp"
|
||||
|
||||
#define FMT_HEADER_ONLY
|
||||
#include <fmt/format.h>
|
||||
#include <fmt/ranges.h>
|
||||
|
||||
template <typename Callback> class PortImpl<port_types_e::PAIR, Callback> : public PortImplBase<Callback> {
|
||||
public:
|
||||
PortImpl(zmq::context_t &zmq_ctx, const std::string &endpoint, Callback &&callback) : PortImplBase<Callback>(zmq_ctx, endpoint, std::forward<Callback>(callback)) {}
|
||||
void listen(std::stop_token st) const override {
|
||||
while (!st.stop_requested()) {
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(100u));
|
||||
}
|
||||
}
|
||||
|
||||
// Send to socket depending on implementation
|
||||
void send(const msgpack::sbuffer &data) const override {};
|
||||
};
|
||||
|
|
@ -0,0 +1,36 @@
|
|||
#pragma once
|
||||
|
||||
#include "port_impl_base.hpp"
|
||||
#include "port_types.hpp"
|
||||
|
||||
#define FMT_HEADER_ONLY
|
||||
#include <fmt/format.h>
|
||||
#include <fmt/ranges.h>
|
||||
|
||||
template <typename Callback> class PortImpl<port_types_e::PUB, Callback> final : public PortImplBase<Callback> {
|
||||
public:
|
||||
PortImpl(zmq::context_t &zmq_ctx, const std::string &endpoint, const std::list<std::string> &topics, Callback &&callback)
|
||||
: PortImplBase<Callback>(zmq_ctx, endpoint, std::forward<Callback>(callback)), mc_topics_(topics) {
|
||||
this->m_sock__ = zmq::socket_t(this->m_ctx__, zmq::socket_type::pub);
|
||||
this->m_sock__.bind(this->mc_endpoint__);
|
||||
}
|
||||
|
||||
~PortImpl() override {}
|
||||
|
||||
void listen(std::stop_token st) const override { throw std::runtime_error("Can't listen on PUBLISHER pattern socket"); }
|
||||
|
||||
// Send to socket depending on implementation
|
||||
void send(const msgpack::sbuffer &data) const override {
|
||||
try {
|
||||
for (const auto &topic : mc_topics_) {
|
||||
this->m_sock__.send(zmq::message_t(topic), zmq::send_flags::sndmore);
|
||||
this->m_sock__.send(zmq::message_t(data.data(), data.size()), zmq::send_flags::dontwait);
|
||||
}
|
||||
} catch (const zmq::error_t &err) {
|
||||
fmt::print("ZMQ error: {1} ({0})\r\n", err.num(), err.what());
|
||||
}
|
||||
};
|
||||
|
||||
private:
|
||||
const std::list<std::string> mc_topics_;
|
||||
};
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
#pragma once
|
||||
|
||||
#include "port_impl_base.hpp"
|
||||
#include "port_types.hpp"
|
||||
|
||||
#define FMT_HEADER_ONLY
|
||||
#include <fmt/format.h>
|
||||
#include <fmt/ranges.h>
|
||||
|
||||
template <typename Callback> class PortImpl<port_types_e::PULL, Callback> : public PortImplBase<Callback> {
|
||||
public:
|
||||
PortImpl(zmq::context_t &zmq_ctx, const std::string &endpoint, Callback &&callback) : PortImplBase<Callback>(zmq_ctx, endpoint, std::forward<Callback>(callback)) {}
|
||||
void listen(std::stop_token st) const override {
|
||||
while (!st.stop_requested()) {
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(100u));
|
||||
}
|
||||
}
|
||||
|
||||
// Send to socket depending on implementation
|
||||
void send(const msgpack::sbuffer &data) const override {};
|
||||
};
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
#pragma once
|
||||
|
||||
#include "port_impl_base.hpp"
|
||||
#include "port_types.hpp"
|
||||
|
||||
#define FMT_HEADER_ONLY
|
||||
#include <fmt/format.h>
|
||||
#include <fmt/ranges.h>
|
||||
|
||||
template <typename Callback> class PortImpl<port_types_e::PUSH, Callback> : public PortImplBase<Callback> {
|
||||
public:
|
||||
PortImpl(zmq::context_t &zmq_ctx, const std::string &endpoint, Callback &&callback) : PortImplBase<Callback>(zmq_ctx, endpoint, std::forward<Callback>(callback)) {}
|
||||
void listen(std::stop_token st) const override {
|
||||
while (!st.stop_requested()) {
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(100u));
|
||||
}
|
||||
}
|
||||
|
||||
// Send to socket depending on implementation
|
||||
void send(const msgpack::sbuffer &data) const override {};
|
||||
};
|
||||
|
|
@ -0,0 +1,22 @@
|
|||
#pragma once
|
||||
|
||||
#include "port_impl_base.hpp"
|
||||
#include "port_types.hpp"
|
||||
|
||||
#define FMT_HEADER_ONLY
|
||||
#include <fmt/format.h>
|
||||
#include <fmt/ranges.h>
|
||||
|
||||
template <typename Callback> class PortImpl<port_types_e::REP, Callback> : public PortImplBase<Callback> {
|
||||
public:
|
||||
PortImpl(zmq::context_t &zmq_ctx, const std::string &endpoint, Callback &&callback) : PortImplBase<Callback>(zmq_ctx, endpoint, std::forward<Callback>(callback)) {}
|
||||
void listen(std::stop_token st) const override {
|
||||
while (!st.stop_requested()) {
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(100u));
|
||||
}
|
||||
}
|
||||
|
||||
// Send to socket depending on implementation
|
||||
void send(const msgpack::sbuffer &data) const override {};
|
||||
};
|
||||
|
||||
|
|
@ -0,0 +1,22 @@
|
|||
#pragma once
|
||||
|
||||
#include "port_impl_base.hpp"
|
||||
#include "port_types.hpp"
|
||||
|
||||
#define FMT_HEADER_ONLY
|
||||
#include <fmt/format.h>
|
||||
#include <fmt/ranges.h>
|
||||
|
||||
template <typename Callback> class PortImpl<port_types_e::REQ, Callback> : public PortImplBase<Callback> {
|
||||
public:
|
||||
PortImpl(zmq::context_t &zmq_ctx, const std::string &endpoint, Callback &&callback) : PortImplBase<Callback>(zmq_ctx, endpoint, std::forward<Callback>(callback)) {}
|
||||
void listen(std::stop_token st) const override {
|
||||
while (!st.stop_requested()) {
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(100u));
|
||||
}
|
||||
}
|
||||
|
||||
// Send to socket depending on implementation
|
||||
void send(const msgpack::sbuffer &data) const override {};
|
||||
};
|
||||
|
||||
|
|
@ -0,0 +1,22 @@
|
|||
#pragma once
|
||||
|
||||
#include "port_impl_base.hpp"
|
||||
#include "port_types.hpp"
|
||||
|
||||
#define FMT_HEADER_ONLY
|
||||
#include <fmt/format.h>
|
||||
#include <fmt/ranges.h>
|
||||
|
||||
template <typename Callback> class PortImpl<port_types_e::ROUTER, Callback> : public PortImplBase<Callback> {
|
||||
public:
|
||||
PortImpl(zmq::context_t &zmq_ctx, const std::string &endpoint, Callback &&callback) : PortImplBase<Callback>(zmq_ctx, endpoint, std::forward<Callback>(callback)) {}
|
||||
void listen(std::stop_token st) const override {
|
||||
while (!st.stop_requested()) {
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(100u));
|
||||
}
|
||||
}
|
||||
|
||||
// Send to socket depending on implementation
|
||||
void send(const msgpack::sbuffer &data) const override {};
|
||||
};
|
||||
|
||||
|
|
@ -0,0 +1,55 @@
|
|||
#pragma once
|
||||
|
||||
#include "port_impl_base.hpp"
|
||||
#include "port_types.hpp"
|
||||
|
||||
#define FMT_HEADER_ONLY
|
||||
#include <fmt/format.h>
|
||||
#include <fmt/ranges.h>
|
||||
|
||||
template <typename Callback> class PortImpl<port_types_e::SUB, Callback> : public PortImplBase<Callback> {
|
||||
public:
|
||||
using base_t = PortImplBase<Callback>;
|
||||
PortImpl(zmq::context_t &zmq_ctx, const std::string &endpoint, const std::list<std::string> &topics, Callback &&callback)
|
||||
: PortImplBase<Callback>(zmq_ctx, endpoint, std::forward<Callback>(callback)), mc_topics_(topics) {
|
||||
this->m_sock__ = zmq::socket_t(this->m_ctx__, zmq::socket_type::sub);
|
||||
this->m_sock__.connect(this->mc_endpoint__);
|
||||
|
||||
for (const auto &topic : mc_topics_) {
|
||||
this->m_sock__.set(zmq::sockopt::subscribe, topic);
|
||||
}
|
||||
}
|
||||
|
||||
void listen(std::stop_token st) const override {
|
||||
this->m_listener_thread__ = std::async(
|
||||
std::launch::async,
|
||||
[this](std::stop_token st) {
|
||||
while (!st.stop_requested()) {
|
||||
zmq::message_t msg;
|
||||
|
||||
this->m_sock__.recv(msg, zmq::recv_flags::dontwait).and_then([&](const auto &res) {
|
||||
fmt::print("Received envelope: {}\r\n", std::string(static_cast<const char *>(msg.data()), msg.size()));
|
||||
this->m_sock__.recv(msg).and_then([&](const auto &res) {
|
||||
typename base_t::port_payload_s payload;
|
||||
|
||||
msgpack::sbuffer buf;
|
||||
buf.write(reinterpret_cast<const char *>(msg.data()), msg.size());
|
||||
const auto [typehash, data] = msgpack::unpack(buf.data(), buf.size()).get().convert(payload);
|
||||
|
||||
this->mc_cbk__(data, typehash);
|
||||
return std::optional(res);
|
||||
});
|
||||
|
||||
return std::optional(res);
|
||||
});
|
||||
}
|
||||
},
|
||||
st);
|
||||
}
|
||||
|
||||
// Send to socket depending on implementation
|
||||
void send(const msgpack::sbuffer &data) const override { throw std::runtime_error("Can't send anything on SUBSCRIBER pattern socket"); };
|
||||
|
||||
private:
|
||||
const std::list<std::string> mc_topics_;
|
||||
};
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
enum class port_types_e : uint32_t {
|
||||
UNKNOWN = 0,
|
||||
PUB,
|
||||
SUB,
|
||||
REQ,
|
||||
REP,
|
||||
ROUTER,
|
||||
DEALER,
|
||||
PUSH,
|
||||
PULL,
|
||||
PAIR,
|
||||
};
|
||||
|
||||
template <enum port_types_e, typename...> class PortImpl;
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
#pragma once
|
||||
|
||||
#include "port_base.hpp"
|
||||
#include "port_publisher_impl.hpp"
|
||||
#include "port_subscriber_impl.hpp"
|
||||
#include "port_push_impl.hpp"
|
||||
#include "port_pull_impl.hpp"
|
||||
#include "port_req_impl.hpp"
|
||||
#include "port_rep_impl.hpp"
|
||||
#include "port_router_impl.hpp"
|
||||
#include "port_dealer_impl.hpp"
|
||||
#include "port_pair_impl.hpp"
|
||||
|
||||
|
|
@ -0,0 +1,119 @@
|
|||
#include <boost/signals2/signal_type.hpp>
|
||||
#include <chrono>
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
#include <thread>
|
||||
#include <tuple>
|
||||
|
||||
#include <zmq.hpp>
|
||||
|
||||
#define FMT_HEADER_ONLY
|
||||
#include <fmt/format.h>
|
||||
#include <fmt/ranges.h>
|
||||
|
||||
#include "adapter_factory.hpp"
|
||||
#include "module_factory.hpp"
|
||||
#include "port_factory.hpp"
|
||||
|
||||
void entry(int32_t argc, char **argv, char **envp, const std::unordered_map<std::string, const PortBase<std::vector<uint8_t>> *> &ports) {
|
||||
const auto &subscriber = *ports.at("subscriber");
|
||||
const auto &publisher = *ports.at("publisher");
|
||||
|
||||
auto &a = subscriber.callback<int32_t>("int-string-int");
|
||||
auto &b = subscriber.callback<std::string>("string-string-string");
|
||||
|
||||
a.connect([](const int32_t &i) -> void { fmt::print("Echo: {}, typename: {}\r\n", i, type_name<decltype(i)>()); });
|
||||
b.connect([](const std::string &i) -> void { fmt::print("Echo: {}, typename: {}\r\n", i, type_name<decltype(i)>()); });
|
||||
|
||||
subscriber.listen();
|
||||
publisher << 1 << 2 << double{3.f} << std::string("test");
|
||||
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(1000u));
|
||||
subscriber.stop();
|
||||
}
|
||||
|
||||
int main(int argc, char *argv[], char *envp[]) {
|
||||
using enum port_types_e;
|
||||
zmq::context_t zmq_ctx;
|
||||
|
||||
auto mod = makeModule(argc, argv, envp, "test_mod", zmq_ctx,
|
||||
std::tuple{
|
||||
makePort(PUB, "publisher", "inproc://publisher_port", zmq_ctx,
|
||||
std::tuple{
|
||||
makeAdapter("int-string-int", std::pair{
|
||||
[](const int32_t &i) -> std::vector<uint8_t> {
|
||||
auto str = std::to_string(i + 5);
|
||||
return {str.begin(), str.end()};
|
||||
},
|
||||
|
||||
[](const std::vector<uint8_t> &s) -> int32_t { return 5; },
|
||||
}),
|
||||
|
||||
makeAdapter("string-string-string", std::pair{
|
||||
[](const std::string &i) -> std::vector<uint8_t> {
|
||||
auto str = i + "_test";
|
||||
return {str.begin(), str.end()};
|
||||
},
|
||||
|
||||
[](const std::vector<uint8_t> &i) -> std::string { return "works!"; },
|
||||
}),
|
||||
|
||||
makeAdapter("double-string-double", std::pair{
|
||||
[](const double &i) -> std::vector<uint8_t> {
|
||||
auto str = std::to_string(i / 2.f);
|
||||
return {str.begin(), str.end()};
|
||||
},
|
||||
|
||||
[](const std::vector<uint8_t> &s) -> double { return .1f; },
|
||||
}),
|
||||
},
|
||||
std::tuple{
|
||||
std::list<std::string>{
|
||||
"topic0",
|
||||
"topic1",
|
||||
"topic2",
|
||||
},
|
||||
}),
|
||||
|
||||
makePort(SUB, "subscriber", "inproc://publisher_port", zmq_ctx,
|
||||
std::tuple{
|
||||
makeAdapter("int-string-int", std::pair{
|
||||
[](const int32_t &i) -> std::vector<uint8_t> {
|
||||
auto str = std::to_string(i + 5);
|
||||
return {str.begin(), str.end()};
|
||||
},
|
||||
|
||||
[](const std::vector<uint8_t> &s) -> int32_t { return 5; },
|
||||
}),
|
||||
|
||||
makeAdapter("string-string-string", std::pair{
|
||||
[](const std::string &i) -> std::vector<uint8_t> {
|
||||
auto str = i + "_test";
|
||||
return {str.begin(), str.end()};
|
||||
},
|
||||
|
||||
[](const std::vector<uint8_t> &i) -> std::string { return "works!"; },
|
||||
}),
|
||||
|
||||
makeAdapter("double-string-double", std::pair{
|
||||
[](const double &i) -> std::vector<uint8_t> {
|
||||
auto str = std::to_string(i / 2.f);
|
||||
return {str.begin(), str.end()};
|
||||
},
|
||||
|
||||
[](const std::vector<uint8_t> &s) -> double { return .1f; },
|
||||
}),
|
||||
},
|
||||
|
||||
std::tuple{
|
||||
std::list<std::string>{
|
||||
"topic0",
|
||||
"topic1",
|
||||
"topic2",
|
||||
},
|
||||
}),
|
||||
});
|
||||
|
||||
mod.run(entry);
|
||||
return 0;
|
||||
}
|
||||
Loading…
Reference in New Issue