diff --git a/include/graph.hpp b/include/graph.hpp index b7f8b259f..08f4fbb84 100644 --- a/include/graph.hpp +++ b/include/graph.hpp @@ -738,9 +738,10 @@ operator<<(std::ostream &os, const port_direction_t &value) { return os << static_cast(value); } +template inline std::ostream & -operator<<(std::ostream &os, const port_domain_t &value) { - return os << static_cast(value); +operator<<(std::ostream &os, const T &value) { + return os << value.Name; } #if HAVE_SOURCE_LOCATION diff --git a/include/node.hpp b/include/node.hpp index 1bbb1eca3..fa51a92d7 100644 --- a/include/node.hpp +++ b/include/node.hpp @@ -1321,7 +1321,10 @@ merge(A &&a, B &&b) { #if !DISABLE_SIMD namespace test { -struct copy : public node::max(), "in">, OUT::max(), "out">> { +struct copy : public node, OUT> { + IN in; + OUT out; + public: template V> [[nodiscard]] constexpr V diff --git a/include/port.hpp b/include/port.hpp index a5d48bee0..3cc6c4a26 100644 --- a/include/port.hpp +++ b/include/port.hpp @@ -20,12 +20,32 @@ using namespace fair::literals; using supported_type = std::variant, std::complex, DataSet, DataSet /*, ...*/>; enum class port_direction_t { INPUT, OUTPUT, ANY }; // 'ANY' only for query and not to be used for port declarations + enum class connection_result_t { SUCCESS, FAILED }; + enum class port_type_t { STREAM, /*!< used for single-producer-only ond usually synchronous one-to-one or one-to-many communications */ MESSAGE /*!< used for multiple-producer one-to-one, one-to-many, many-to-one, or many-to-many communications */ }; -enum class port_domain_t { CPU, GPU, NET, FPGA, DSP, MLU }; + +template +struct port_domain { + inline static constexpr fixed_string Name = PortDomainName; +}; + +template +concept PortDomain = requires { T::Name; } && std::is_base_of_v, T>; + +template +using is_port_domain = std::bool_constant>; + +struct CPU : public port_domain<"CPU"> {}; + +struct GPU : public port_domain<"GPU"> {}; + +static_assert(is_port_domain::value); +static_assert(is_port_domain::value); +static_assert(!is_port_domain::value); template concept Port = requires(T t, const std::size_t n_items) { // dynamic definitions @@ -37,6 +57,7 @@ concept Port = requires(T t, const std::size_t n_items) { // dynamic definitions { t.max_samples } -> std::convertible_to; { t.type() } -> std::same_as; { t.direction() } -> std::same_as; + { t.domain() } -> std::same_as; { t.resize_buffer(n_items) } -> std::same_as; { t.disconnect() } -> std::same_as; }; @@ -51,6 +72,56 @@ struct internal_port_buffers { void *tagHandler; }; +template +struct RequiredSamples { + static constexpr std::size_t MinSamples = MIN_SAMPLES; + static constexpr std::size_t MaxSamples = MAX_SAMPLES; +}; + +template +concept IsRequiredSamples = requires { + T::MinSamples; + T::MaxSamples; +} && std::is_base_of_v, T>; + +template +using is_required_samples = std::bool_constant>; + +static_assert(is_required_samples>::value); +static_assert(!is_required_samples::value); + +template +struct StreamBuffer { + using type = T; +}; + +template +concept IsStreamBuffer = requires { typename T::type; } && gr::Buffer; + +template +using is_stream_buffer = std::bool_constant>; + +template +struct TagBuffer { + using type = T; +}; + +template +concept IsTagBuffer = requires { typename T::type; } && gr::Buffer; + +template +using is_tag_buffer = std::bool_constant>; + +template +struct DefaultStreamBuffer : StreamBuffer> {}; + +struct DefaultTagBuffer : TagBuffer> {}; + +static_assert(is_stream_buffer>::value); +// static_assert(!is_stream_buffer::value); +// static_assert(!is_tag_buffer>::value); +static_assert(is_tag_buffer::value); + /** * @brief 'ports' are interfaces that allows data to flow between blocks in a graph, similar to RF connectors. * Each block can have zero or more input/output ports. When connecting ports, either a single-step or a two-step @@ -73,25 +144,24 @@ struct internal_port_buffers { * @tparam PortName a string to identify the port, notably to be used in an UI- and hand-written explicit code context. * @tparam PortType STREAM or MESSAGE * @tparam PortDirection either input or output - * @tparam MIN_SAMPLES specifies the minimum number of samples the port/block requires for processing in one scheduler iteration - * @tparam MAX_SAMPLES specifies the maximum number of samples the port/block can process in one scheduler iteration - * @tparam BufferType user-extendable buffer implementation for the streaming data - * @tparam TagBufferType user-extendable buffer implementation for the tag data + * @tparam Arguments optional: default to 'DefaultStreamBuffer' and DefaultTagBuffer' based on 'gr::circular_buffer', and CPU domain */ -template, - gr::Buffer TagBufferType = gr::circular_buffer> +template class port { public: static_assert(PortDirection != port_direction_t::ANY, "ANY reserved for queries and not port direction declarations"); - using value_type = T; - - static constexpr bool IS_INPUT = PortDirection == port_direction_t::INPUT; - static constexpr bool IS_OUTPUT = PortDirection == port_direction_t::OUTPUT; + using value_type = T; + using Domain = typename fair::meta::typelist::template find_or_default; + using Required = typename fair::meta::typelist::template find_or_default>; + using BufferType = typename fair::meta::typelist::template find_or_default>::type; + using TagBufferType = typename fair::meta::typelist::template find_or_default::type; + static constexpr port_direction_t Direction = PortDirection; + static constexpr bool IS_INPUT = PortDirection == port_direction_t::INPUT; + static constexpr bool IS_OUTPUT = PortDirection == port_direction_t::OUTPUT; template - using with_name = port; + using with_name = port; using ReaderType = decltype(std::declval().new_reader()); using WriterType = decltype(std::declval().new_writer()); @@ -103,8 +173,8 @@ class port { // public properties const std::string name = static_cast(PortName); std::int16_t priority = 0; // → dependents of a higher-prio port should be scheduled first (Q: make this by order of ports?) - std::size_t min_samples = (MIN_SAMPLES == std::dynamic_extent ? 1 : MIN_SAMPLES); - std::size_t max_samples = MAX_SAMPLES; + std::size_t min_samples = (Required::MinSamples == std::dynamic_extent ? 1 : Required::MinSamples); + std::size_t max_samples = Required::MaxSamples; private: bool _connected = false; @@ -158,7 +228,7 @@ class port { } public: - port() = default; + constexpr port() = default; port(const port &) = delete; auto operator=(const port &) @@ -195,6 +265,11 @@ class port { return PortDirection; } + [[nodiscard]] constexpr static std::string_view + domain() noexcept { + return std::string_view(Domain::Name); + } + [[nodiscard]] constexpr static decltype(PortName) static_name() noexcept requires(!PortName.empty()) @@ -219,19 +294,19 @@ class port { [[nodiscard]] constexpr std::size_t min_buffer_size() const noexcept { - if constexpr (MIN_SAMPLES == std::dynamic_extent) { + if constexpr (Required::MinSamples == std::dynamic_extent) { return min_samples; } else { - return MIN_SAMPLES; + return Required::MinSamples; } } [[nodiscard]] constexpr std::size_t max_buffer_size() const noexcept { - if constexpr (MAX_SAMPLES == std::dynamic_extent) { + if constexpr (Required::MaxSamples == std::dynamic_extent) { return max_samples; } else { - return MAX_SAMPLES; + return Required::MaxSamples; } } @@ -354,18 +429,26 @@ repeated_ports_impl(std::index_sequence) { } // namespace detail // TODO: Add port index to BaseName -template -using repeated_ports = decltype(detail::repeated_ports_impl>(std::make_index_sequence())); - -template -using IN = port; -template -using OUT = port; -template -using IN_MSG = port; -template -using OUT_MSG = port; +template +using repeated_ports = decltype(detail::repeated_ports_impl>(std::make_index_sequence())); + +template +using IN = port; +template +using OUT = port; +template +using IN_MSG = port; +template +using OUT_MSG = port; + +template +using InNamed = port; +template +using OutNamed = port; +template +using MsgInNamed = port; +template +using MsgOutNamed = port; static_assert(Port>); static_assert(Port())>); @@ -373,12 +456,14 @@ static_assert(Port>); static_assert(Port>); static_assert(Port>); -static_assert(IN::static_name() == fixed_string("in")); -static_assert(requires { IN("in").name; }); +static_assert(IN>::Required::MinSamples == 1); +static_assert(IN>::Required::MaxSamples == 2); +static_assert(std::same_as>::Domain, CPU>); +static_assert(std::same_as, GPU>::Domain, GPU>); -static_assert(OUT_MSG::static_name() == fixed_string("out_msg")); -static_assert(!(OUT_MSG::with_name<"out_message">::static_name() == fixed_string("out_msg"))); -static_assert(OUT_MSG::with_name<"out_message">::static_name() == fixed_string("out_message")); +static_assert(MsgOutNamed::static_name() == fixed_string("out_msg")); +static_assert(!(MsgOutNamed::with_name<"out_message">::static_name() == fixed_string("out_msg"))); +static_assert(MsgOutNamed::with_name<"out_message">::static_name() == fixed_string("out_message")); /** * Runtime capable wrapper to be used within a block. It's primary purpose is to allow the runtime @@ -415,6 +500,10 @@ class dynamic_port { direction() const noexcept = 0; + [[nodiscard]] virtual std::string_view + domain() const noexcept + = 0; + [[nodiscard]] virtual connection_result_t resize_buffer(std::size_t min_size) noexcept = 0; @@ -489,7 +578,7 @@ class dynamic_port { } ~wrapper() override = default; - + // TODO revisit: constexpr was removed because emscripten does not support constexpr function for non literal type, like DataSet #if defined(__EMSCRIPTEN__) [[nodiscard]] supported_type @@ -510,6 +599,11 @@ class dynamic_port { return _value.direction(); } + [[nodiscard]] constexpr std::string_view + domain() const noexcept override { + return _value.domain(); + } + [[nodiscard]] connection_result_t resize_buffer(std::size_t min_size) noexcept override { return _value.resize_buffer(min_size); @@ -576,6 +670,11 @@ class dynamic_port { return _accessor->direction(); } + [[nodiscard]] std::string_view + domain() const noexcept { + return _accessor->domain(); + } + [[nodiscard]] connection_result_t resize_buffer(std::size_t min_size) { if (direction() == port_direction_t::OUTPUT) { diff --git a/include/port_traits.hpp b/include/port_traits.hpp index 9ae32d5c8..11b9b7d1e 100644 --- a/include/port_traits.hpp +++ b/include/port_traits.hpp @@ -8,11 +8,11 @@ namespace fair::graph::traits::port { template concept has_fixed_info_v = requires { - typename T::value_type; - { T::static_name() }; - { T::direction() } -> std::same_as; - { T::type() } -> std::same_as; - }; + typename T::value_type; + { T::static_name() }; + { T::direction() } -> std::same_as; + { T::type() } -> std::same_as; +}; template using has_fixed_info = std::integral_constant>; @@ -43,25 +43,15 @@ using is_output = std::integral_constant concept is_output_v = is_output::value; -template +template concept is_port_v = is_output_v || is_input_v; template -struct min_samples : std::integral_constant::value... })> {}; - -template -struct min_samples> - : std::integral_constant {}; +struct min_samples : std::integral_constant {}; template -struct max_samples : std::integral_constant::value... })> {}; - -template -struct max_samples> - : std::integral_constant {}; +struct max_samples : std::integral_constant {}; -} // namespace port +} // namespace fair::graph::traits::port #endif // include guard diff --git a/include/typelist.hpp b/include/typelist.hpp index 186e67e69..e9108f3cd 100644 --- a/include/typelist.hpp +++ b/include/typelist.hpp @@ -379,6 +379,9 @@ struct typelist { }())>; }; +template +constexpr bool is_any_of_v = std::disjunction_v...>; + namespace detail { template typename OtherTypelist, typename... Args> meta::typelist diff --git a/src/main.cpp b/src/main.cpp index 5b1dbe313..dd691b3bd 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -8,46 +8,58 @@ namespace fg = fair::graph; template -class count_source : public fg::node, fg::OUT::max(), "random">> { -public: +struct count_source : public fg::node> { + fg::OUT random; + constexpr T process_one() { return 42; } }; +ENABLE_REFLECTION_FOR_TEMPLATE_FULL((typename T), (count_source), random); template -class expect_sink : public fg::node, fg::IN::max(), "sink">> { -public: +struct expect_sink : public fg::node> { + fg::IN sink; + void process_one(T value) { std::cout << value << std::endl; } }; +ENABLE_REFLECTION_FOR_TEMPLATE_FULL((typename T), (expect_sink), sink); template() * std::declval())> -class scale : public fg::node, fg::IN::max(), "original">, fg::OUT::max(), "scaled">> { -public: +struct scale : public fg::node> { + fg::IN original; + fg::OUT scaled; + template V> [[nodiscard]] constexpr auto process_one(V a) const noexcept { return a * Scale; } }; +ENABLE_REFLECTION_FOR_TEMPLATE_FULL((typename T, T Scale, typename R), (scale), original, scaled); template() + std::declval())> -class adder : public fg::node, fg::IN::max(), "addend0">, fg::IN::max(), "addend1">, fg::OUT::max(), "sum">> { -public: +struct adder : public fg::node> { + fg::IN addend0; + fg::IN addend1; + fg::OUT sum; + template V> [[nodiscard]] constexpr auto process_one(V a, V b) const noexcept { return a + b; } }; +ENABLE_REFLECTION_FOR_TEMPLATE_FULL((typename T, typename R), (adder), addend0, addend1, sum); +using fg::port_type_t::STREAM, fg::port_direction_t::INPUT, fg::port_direction_t::OUTPUT; template -class duplicate : public fg::node, fair::meta::typelist::max(), "in">>, fg::repeated_ports> { - using base = fg::node, fair::meta::typelist::max(), "in">>, fg::repeated_ports>; +class duplicate : public fg::node, fair::meta::typelist>, fg::repeated_ports> { + using base = fg::node, fair::meta::typelist>, fg::repeated_ports>; public: using return_type = typename fg::traits::node::return_type; @@ -61,11 +73,12 @@ class duplicate : public fg::node, fair::meta::typelist requires(Depth > 0) -class delay : public fg::node, fg::IN::max(), "in">, fg::OUT::max(), "out">> { +struct delay : public fg::node> { + fg::IN in; + fg::OUT out; std::array buffer = {}; int pos = 0; -public: [[nodiscard]] constexpr T process_one(T in) noexcept { T ret = buffer[pos]; @@ -78,6 +91,7 @@ class delay : public fg::node, fg::IN), in, out); int main() { diff --git a/test/qa_dynamic_port.cpp b/test/qa_dynamic_port.cpp index 108ba81cf..2ce8651c7 100644 --- a/test/qa_dynamic_port.cpp +++ b/test/qa_dynamic_port.cpp @@ -103,24 +103,33 @@ const boost::ut::suite PortApiTests = [] { using namespace fair::graph; "PortApi"_test = [] { - static_assert(Port>); - static_assert(Port("in"))>); - static_assert(Port>); - static_assert(Port>); - static_assert(Port>); - - static_assert(IN::static_name() == fixed_string("in")); - static_assert(requires { IN("in").name; }); + static_assert(Port>); + static_assert(Port())>); + static_assert(Port>); + static_assert(Port>); + static_assert(Port>); + + static_assert(Port>); + static_assert(Port("in"))>); + static_assert(Port>); + static_assert(Port>); + static_assert(Port>); + + static_assert(IN>::Required::MinSamples == 1); + static_assert(IN>::Required::MaxSamples == 2); + static_assert(IN::direction() == port_direction_t::INPUT); + static_assert(OUT::direction() == port_direction_t::OUTPUT); }; "PortBufferApi"_test = [] { - OUT::max(), "out0"> output_port; - BufferWriter auto &writer = output_port.streamWriter(); + OUT output_port; + BufferWriter auto &writer = output_port.streamWriter(); // BufferWriter auto &tagWriter = output_port.tagWriter(); expect(ge(writer.available(), 32_UZ)); - IN::max(), "int0"> input_port; - const BufferReader auto &reader = input_port.streamReader(); + using ExplicitUnlimitedSize = RequiredSamples<0, std::numeric_limits::max()>; + IN input_port; + const BufferReader auto &reader = input_port.streamReader(); expect(eq(reader.available(), 0_UZ)); auto buffers = output_port.buffer(); input_port.setBuffer(buffers.streamBuffer, buffers.tagBufferType); @@ -139,9 +148,10 @@ const boost::ut::suite PortApiTests = [] { "RuntimePortApi"_test = [] { // declare in block - OUT::max(), "out"> out; - IN::max(), "in"> in; - std::vector port_list; + using ExplicitUnlimitedSize = RequiredSamples<0, std::numeric_limits::max()>; + OUT out; + IN in; + std::vector port_list; port_list.emplace_back(out); port_list.emplace_back(in);