From 95514bd94bb0331cdb66f7187f6dc168891caf22 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ivan=20=C4=8Cuki=C4=87?= Date: Mon, 10 Jun 2024 06:36:53 +0200 Subject: [PATCH 1/2] Message-based API for block and edge creation and deletion MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Runtime Graph::emplaceBlock that accepts stringified block info - Added messages for emplacing, removing and replacing blocks (as well as their reply messages) - Added messages for connecting and disconnecting ports - Introduced ENABLE_BLOCK_REGISTRY and ENABLE_BLOCK_PLUGINS cmake options - Introduced GNURADIO4_PLUGIN_DIRECTORIES environment variable to override the paths where the global plugin loader searches for plugins Signed-off-by: Ivan Čukić --- CMakeLists.txt | 26 +- .../gnuradio-4.0/testing/NullSources.hpp | 87 +- cmake/config.h.in | 10 - cmake/config.hpp.in | 13 + core/CMakeLists.txt | 2 +- core/include/gnuradio-4.0/Block.hpp | 895 ++++++--------- core/include/gnuradio-4.0/BlockModel.hpp | 405 +++++++ core/include/gnuradio-4.0/BlockRegistry.hpp | 64 +- core/include/gnuradio-4.0/Graph.hpp | 1022 +++++------------ core/include/gnuradio-4.0/PluginLoader.hpp | 184 ++- core/include/gnuradio-4.0/Port.hpp | 484 +++----- core/include/gnuradio-4.0/plugin.hpp | 86 +- core/src/main.cpp | 43 +- core/test/CMakeLists.txt | 14 +- core/test/app_plugins_test.cpp | 23 +- core/test/qa_GraphMessages.cpp | 295 +++++ core/test/qa_plugins_test.cpp | 9 +- meson.build | 66 -- meson_options.txt | 1 - meta/include/gnuradio-4.0/meta/utils.hpp | 191 ++- 20 files changed, 1818 insertions(+), 2102 deletions(-) delete mode 100644 cmake/config.h.in create mode 100644 cmake/config.hpp.in create mode 100644 core/include/gnuradio-4.0/BlockModel.hpp create mode 100644 core/test/qa_GraphMessages.cpp delete mode 100644 meson.build delete mode 100644 meson_options.txt diff --git a/CMakeLists.txt b/CMakeLists.txt index b1be9a69..54959040 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -5,19 +5,30 @@ set(CMAKE_CXX_STANDARD 23) set(CMAKE_CXX_VISIBILITY_PRESET hidden) set(CMAKE_VISIBILITY_INLINES_HIDDEN 1) -option(NOPLUGINS "Disable plugins" OFF) +if (CMAKE_CXX_COMPILER MATCHES "/em\\+\\+(-[a-zA-Z0-9.])?$") # if this hasn't been set before via e.g. emcmake + message(" Transpiling to WASM: using: Emscripten (${CMAKE_CXX_COMPILER})") + set(EMSCRIPTEN ON) +endif () + +option(ENABLE_BLOCK_PLUGINS "Enable building the plugin system" ON) +option(ENABLE_BLOCK_REGISTRY "Enable building the block registry" ON) option(EMBEDDED "Enable embedded mode" OFF) option(TIMETRACE "Enable clang's -ftime-trace" OFF) option(ADDRESS_SANITIZER "Enable address sanitizer" OFF) option(UB_SANITIZER "Enable undefined behavior sanitizer" OFF) option(THREAD_SANITIZER "Enable thread sanitizer" OFF) -if (NOPLUGINS) - set(NOPLUGINS ON) - add_compile_definitions(NOPLUGINS) - message(STATUS "disable plugin system (faster compile-times and when runtime or Python wrapping APIs are not required): ${NOPLUGINS}") +if (EMSCRIPTEN) + set(ENABLE_BLOCK_PLUGINS OFF) +endif () + +if (ENABLE_BLOCK_PLUGINS) + set(ENABLE_BLOCK_REGISTRY ON) endif () +message(STATUS "Is block registry enabled? (faster compile-times and when runtime or Python wrapping APIs are not required) ${ENABLE_BLOCK_REGISTRY}") +message(STATUS "Is plugin system enabled? ${ENABLE_BLOCK_PLUGINS}") + # Determine if fmt is built as a subproject (using add_subdirectory) or if it is the master project. if (NOT DEFINED GR_TOPLEVEL_PROJECT) set(GR_TOPLEVEL_PROJECT OFF) @@ -136,11 +147,6 @@ add_library(gnuradio-options INTERFACE) include(cmake/CompilerWarnings.cmake) set_project_warnings(gnuradio-options) -if (CMAKE_CXX_COMPILER MATCHES "/em\\+\\+(-[a-zA-Z0-9.])?$") # if this hasn't been set before via e.g. emcmake - message(" Transpiling to WASM: using: Emscripten (${CMAKE_CXX_COMPILER})") - set(EMSCRIPTEN true) -endif () - if (EMSCRIPTEN) set(CMAKE_EXECUTABLE_SUFFIX ".js") add_compile_options( diff --git a/blocks/testing/include/gnuradio-4.0/testing/NullSources.hpp b/blocks/testing/include/gnuradio-4.0/testing/NullSources.hpp index 3ce4adc2..2756027f 100644 --- a/blocks/testing/include/gnuradio-4.0/testing/NullSources.hpp +++ b/blocks/testing/include/gnuradio-4.0/testing/NullSources.hpp @@ -16,10 +16,7 @@ Ideal for scenarios that require a simple, low-overhead source of consistent val gr::PortOut out; - [[nodiscard]] constexpr T - processOne() const noexcept { - return T{}; - } + [[nodiscard]] constexpr T processOne() const noexcept { return T{}; } }; static_assert(gr::BlockLike>); @@ -36,13 +33,9 @@ Commonly used for testing and simulations where consistent output and finite exe Annotatedn_samples_max -> signal DONE (0: infinite)">> n_samples_max = 0U; Annotated> count = 0U; - void - reset() { - count = 0U; - } + void reset() { count = 0U; } - [[nodiscard]] constexpr T - processOne() noexcept { + [[nodiscard]] constexpr T processOne() noexcept { count++; if (n_samples_max > 0 && count >= n_samples_max) { this->requestStop(); @@ -54,7 +47,32 @@ Commonly used for testing and simulations where consistent output and finite exe static_assert(gr::BlockLike>); template - requires(std::is_arithmetic_v) +struct SlowSource : public gr::Block> { + using Description = Doc; + + gr::PortOut out; + Annotated> default_value{}; + Annotated> n_delay = 100U; + + std::optional> lastEventAt; + + [[nodiscard]] gr::work::Status processBulk(PublishableSpan auto& output) { + if (!lastEventAt || std::chrono::system_clock::now() - *lastEventAt > std::chrono::milliseconds(n_delay)) { + lastEventAt = std::chrono::system_clock::now(); + + output[0] = default_value; + output.publish(1); + return gr::work::Status::OK; + } + + return gr::work::Status::INSUFFICIENT_OUTPUT_ITEMS; + } +}; + +static_assert(gr::BlockLike>); + +template +requires(std::is_arithmetic_v) struct CountingSource : public gr::Block> { using Description = Docn_samples_max -> signal DONE (0: infinite)">> n_samples_max = 0U; Annotated> count = 0U; - void - reset() { - count = 0U; - } + void reset() { count = 0U; } - [[nodiscard]] constexpr T - processOne() noexcept { + [[nodiscard]] constexpr T processOne() noexcept { count++; if (n_samples_max > 0 && count >= n_samples_max) { this->requestStop(); @@ -92,8 +106,7 @@ Commonly used used to isolate parts of a flowgraph, manage buffer sizes, or simp gr::PortOut out; template V> - [[nodiscard]] constexpr auto - processOne(V input) const noexcept { + [[nodiscard]] constexpr auto processOne(V input) const noexcept { return input; } }; @@ -111,13 +124,9 @@ Commonly used to control data flow in systems where precise sample counts are cr Annotatedn_samples_max -> signal DONE (0: infinite)">> n_samples_max = 0U; Annotated> count = 0U; - void - reset() { - count = 0U; - } + void reset() { count = 0U; } - [[nodiscard]] constexpr auto - processOne(T input) noexcept { + [[nodiscard]] constexpr auto processOne(T input) noexcept { count++; if (n_samples_max > 0 && count >= n_samples_max) { this->requestStop(); @@ -137,8 +146,7 @@ Commonly used for testing, performance benchmarking, and in scenarios where sign gr::PortIn in; template V> - void - processOne(V) const noexcept {} + void processOne(V) const noexcept {} }; static_assert(gr::BlockLike>); @@ -154,14 +162,10 @@ Commonly used for testing scenarios and signal termination where output is unnec Annotatedn_samples_max -> signal DONE (0: infinite)">> n_samples_max = 0U; Annotated> count = 0U; - void - reset() { - count = 0U; - } + void reset() { count = 0U; } template V> - void - processOne(V) noexcept { + void processOne(V) noexcept { if constexpr (stdx::is_simd_v) { count += V::size(); } else { @@ -180,24 +184,13 @@ static_assert(gr::BlockLike>); ENABLE_REFLECTION_FOR_TEMPLATE(gr::testing::NullSource, out); ENABLE_REFLECTION_FOR_TEMPLATE(gr::testing::ConstantSource, out, n_samples_max, count); ENABLE_REFLECTION_FOR_TEMPLATE(gr::testing::CountingSource, out, n_samples_max, count); +ENABLE_REFLECTION_FOR_TEMPLATE(gr::testing::SlowSource, out, n_delay); ENABLE_REFLECTION_FOR_TEMPLATE(gr::testing::Copy, in, out); ENABLE_REFLECTION_FOR_TEMPLATE(gr::testing::HeadBlock, in, out, n_samples_max, count); ENABLE_REFLECTION_FOR_TEMPLATE(gr::testing::NullSink, in); ENABLE_REFLECTION_FOR_TEMPLATE(gr::testing::CountingSink, in, n_samples_max, count); -const inline auto registerNullSources - = gr::registerBlock, std::complex, std::string, - gr::Packet, gr::Packet, gr::Tensor, gr::Tensor, gr::DataSet, gr::DataSet>(gr::globalBlockRegistry()) - | gr::registerBlock, std::complex, - std::string, gr::Packet, gr::Packet, gr::Tensor, gr::Tensor, gr::DataSet, gr::DataSet>(gr::globalBlockRegistry()) - | gr::registerBlock(gr::globalBlockRegistry()) - | gr::registerBlock, std::complex, std::string, - gr::Packet, gr::Packet, gr::Tensor, gr::Tensor, gr::DataSet, gr::DataSet>(gr::globalBlockRegistry()) - | gr::registerBlock, std::complex, std::string, - gr::Packet, gr::Packet, gr::Tensor, gr::Tensor, gr::DataSet, gr::DataSet>(gr::globalBlockRegistry()) - | gr::registerBlock, std::complex, std::string, - gr::Packet, gr::Packet, gr::Tensor, gr::Tensor, gr::DataSet, gr::DataSet>(gr::globalBlockRegistry()) - | gr::registerBlock, std::complex, std::string, - gr::Packet, gr::Packet, gr::Tensor, gr::Tensor, gr::DataSet, gr::DataSet>(gr::globalBlockRegistry()); +const inline auto registerNullSources = gr::registerBlock, std::complex, std::string, gr::Packet, gr::Packet, gr::Tensor, gr::Tensor, gr::DataSet, gr::DataSet>(gr::globalBlockRegistry()) | gr::registerBlock, std::complex, std::string, gr::Packet, gr::Packet, gr::Tensor, gr::Tensor, gr::DataSet, gr::DataSet>(gr::globalBlockRegistry()) | gr::registerBlock(gr::globalBlockRegistry()) | gr::registerBlock, std::complex, std::string, gr::Packet, gr::Packet, gr::Tensor, gr::Tensor, gr::DataSet, gr::DataSet>(gr::globalBlockRegistry()) | gr::registerBlock, std::complex, std::string, gr::Packet, gr::Packet, gr::Tensor, gr::Tensor, gr::DataSet, gr::DataSet>(gr::globalBlockRegistry()) | gr::registerBlock, std::complex, std::string, gr::Packet, gr::Packet, gr::Tensor, gr::Tensor, gr::DataSet, gr::DataSet>(gr::globalBlockRegistry()) | + gr::registerBlock, std::complex, std::string, gr::Packet, gr::Packet, gr::Tensor, gr::Tensor, gr::DataSet, gr::DataSet>(gr::globalBlockRegistry()); #endif // GNURADIO_NULLSOURCES_HPP diff --git a/cmake/config.h.in b/cmake/config.h.in deleted file mode 100644 index 809bc34b..00000000 --- a/cmake/config.h.in +++ /dev/null @@ -1,10 +0,0 @@ -#ifndef GNURADIO_GRAPH_PROJECT_CONFIG -#define GNURADIO_GRAPH_PROJECT_CONFIG - -#define CXX_COMPILER_PATH "@CMAKE_CXX_COMPILER@" -#define CXX_COMPILER_ARG1 "@CMAKE_CXX_COMPILER_ARG1@" -#define CXX_COMPILER_ID "@CMAKE_CXX_COMPILER_ID@" -#define CXX_COMPILER_VERSION "@CMAKE_CXX_COMPILER_VERSION@" -#define CXX_COMPILER_FLAGS "@ALL_COMPILER_FLAGS@" - -#endif // GNURADIO_GRAPH_PROJECT_CONFIG \ No newline at end of file diff --git a/cmake/config.hpp.in b/cmake/config.hpp.in new file mode 100644 index 00000000..245e990a --- /dev/null +++ b/cmake/config.hpp.in @@ -0,0 +1,13 @@ +#ifndef GNURADIO_GRAPH_PROJECT_CONFIG +#define GNURADIO_GRAPH_PROJECT_CONFIG + +#define CXX_COMPILER_PATH "@CMAKE_CXX_COMPILER@" +#define CXX_COMPILER_ARG1 "@CMAKE_CXX_COMPILER_ARG1@" +#define CXX_COMPILER_ID "@CMAKE_CXX_COMPILER_ID@" +#define CXX_COMPILER_VERSION "@CMAKE_CXX_COMPILER_VERSION@" +#define CXX_COMPILER_FLAGS "@ALL_COMPILER_FLAGS@" + +#cmakedefine ENABLE_BLOCK_PLUGINS +#cmakedefine ENABLE_BLOCK_REGISTRY + +#endif // GNURADIO_GRAPH_PROJECT_CONFIG diff --git a/core/CMakeLists.txt b/core/CMakeLists.txt index a71b3b14..9aec4b0a 100644 --- a/core/CMakeLists.txt +++ b/core/CMakeLists.txt @@ -3,7 +3,7 @@ target_include_directories(gnuradio-core INTERFACE $ #include -#include // This needs to be included after fmt/format.h, as it defines formatters only if FMT_FORMAT_H_ is defined -#include #include #include +#include // This needs to be included after fmt/format.h, as it defines formatters only if FMT_FORMAT_H_ is defined +#include #include @@ -31,8 +31,7 @@ namespace stdx = vir::stdx; using gr::meta::fixed_string; template -constexpr void -simd_epilogue(auto width, F &&fun) { +constexpr void simd_epilogue(auto width, F&& fun) { static_assert(std::has_single_bit(+width)); auto w2 = std::integral_constant{}; if constexpr (w2 > 0) { @@ -42,25 +41,22 @@ simd_epilogue(auto width, F &&fun) { } template -constexpr auto -simdize_tuple_load_and_apply(auto width, const std::tuple &rngs, auto offset, auto &&fun, Flag f = {}) { +constexpr auto simdize_tuple_load_and_apply(auto width, const std::tuple& rngs, auto offset, auto&& fun, Flag f = {}) { using Tup = meta::simdize...>, width>; - return [&](std::index_sequence) { - return fun(std::tuple_element_t(std::ranges::data(std::get(rngs)) + offset, f)...); - }(std::make_index_sequence()); + return [&](std::index_sequence) { return fun(std::tuple_element_t(std::ranges::data(std::get(rngs)) + offset, f)...); }(std::make_index_sequence()); } template -auto -invokeProcessOneWithOrWithoutOffset(T &node, std::size_t offset, const Us &...inputs) { - if constexpr (traits::block::can_processOne_with_offset) return node.processOne(offset, inputs...); - else +auto invokeProcessOneWithOrWithoutOffset(T& node, std::size_t offset, const Us&... inputs) { + if constexpr (traits::block::can_processOne_with_offset) { + return node.processOne(offset, inputs...); + } else { return node.processOne(inputs...); + } } template -[[nodiscard]] constexpr auto & -inputPort(Self *self) noexcept { +[[nodiscard]] constexpr auto& inputPort(Self* self) noexcept { using TRequestedPortType = typename traits::block::ports_data::template for_type::input_ports::template at; if constexpr (traits::block::block_defines_ports_as_member_variables) { using member_descriptor = traits::block::get_port_member_descriptor; @@ -71,8 +67,7 @@ inputPort(Self *self) noexcept { } template -[[nodiscard]] constexpr auto & -outputPort(Self *self) noexcept { +[[nodiscard]] constexpr auto& outputPort(Self* self) noexcept { using TRequestedPortType = typename traits::block::ports_data::template for_type::output_ports::template at; if constexpr (traits::block::block_defines_ports_as_member_variables) { using member_descriptor = traits::block::get_port_member_descriptor; @@ -83,8 +78,7 @@ outputPort(Self *self) noexcept { } template -[[nodiscard]] constexpr auto & -inputPort(Self *self) noexcept { +[[nodiscard]] constexpr auto& inputPort(Self* self) noexcept { constexpr int Index = meta::indexForName>(); if constexpr (Index == meta::default_message_port_index) { return self->msgIn; @@ -93,8 +87,7 @@ inputPort(Self *self) noexcept { } template -[[nodiscard]] constexpr auto & -outputPort(Self *self) noexcept { +[[nodiscard]] constexpr auto& outputPort(Self* self) noexcept { constexpr int Index = meta::indexForName>(); if constexpr (Index == meta::default_message_port_index) { return self->msgOut; @@ -103,29 +96,22 @@ outputPort(Self *self) noexcept { } template -[[nodiscard]] constexpr auto -inputPorts(Self *self) noexcept { - return [self](std::index_sequence) { - return std::tie(inputPort(self)...); - }(std::make_index_sequence::template for_type::input_ports::size()>()); +[[nodiscard]] constexpr auto inputPorts(Self* self) noexcept { + return [self](std::index_sequence) { return std::tie(inputPort(self)...); }(std::make_index_sequence::template for_type::input_ports::size()>()); } template -[[nodiscard]] constexpr auto -outputPorts(Self *self) noexcept { - return [self](std::index_sequence) { - return std::tie(outputPort(self)...); - }(std::make_index_sequence::template for_type::output_ports::size>()); +[[nodiscard]] constexpr auto outputPorts(Self* self) noexcept { + return [self](std::index_sequence) { return std::tie(outputPort(self)...); }(std::make_index_sequence::template for_type::output_ports::size>()); } namespace work { class Counter { - std::atomic_uint64_t encodedCounter{ static_cast(std::numeric_limits::max()) << 32 }; + std::atomic_uint64_t encodedCounter{static_cast(std::numeric_limits::max()) << 32}; public: - void - increment(std::size_t workRequestedInc, std::size_t workDoneInc) { + void increment(std::size_t workRequestedInc, std::size_t workDoneInc) { uint64_t oldCounter; uint64_t newCounter; do { @@ -140,26 +126,24 @@ class Counter { } while (!encodedCounter.compare_exchange_weak(oldCounter, newCounter)); } - std::pair - getAndReset() { + std::pair getAndReset() { uint64_t oldCounter = encodedCounter.exchange(0); auto workRequested = static_cast(oldCounter >> 32); auto workDone = static_cast(oldCounter & 0xFFFFFFFF); if (workRequested == std::numeric_limits::max()) { - return { std::numeric_limits::max(), static_cast(workDone) }; + return {std::numeric_limits::max(), static_cast(workDone)}; } - return { static_cast(workRequested), static_cast(workDone) }; + return {static_cast(workRequested), static_cast(workDone)}; } - std::pair - get() { + std::pair get() { uint64_t oldCounter = std::atomic_load_explicit(&encodedCounter, std::memory_order_acquire); auto workRequested = static_cast(oldCounter >> 32); auto workDone = static_cast(oldCounter & 0xFFFFFFFF); if (workRequested == std::numeric_limits::max()) { - return { std::numeric_limits::max(), static_cast(workDone) }; + return {std::numeric_limits::max(), static_cast(workDone)}; } - return { static_cast(workRequested), static_cast(workDone) }; + return {static_cast(workRequested), static_cast(workDone)}; } }; @@ -185,14 +169,14 @@ concept HasWork = requires(T t, std::size_t requested_work) { template concept BlockLike = requires(T t, std::size_t requested_work) { - { t.unique_name } -> std::same_as; + { t.unique_name } -> std::same_as; { unwrap_if_wrapped_t{} } -> std::same_as; { unwrap_if_wrapped_t{} } -> std::same_as; - { t.description } noexcept -> std::same_as; + { t.description } noexcept -> std::same_as; { t.isBlocking() } noexcept -> std::same_as; - { t.settings() } -> std::same_as; + { t.settings() } -> std::same_as; // N.B. TODO discuss these requirements requires !std::is_copy_constructible_v; @@ -218,8 +202,7 @@ template concept HasRequiredProcessFunction = (HasProcessBulkFunction or HasProcessOneFunction) and (HasProcessOneFunction + HasProcessBulkFunction) == 1; template> -inline void -checkBlockContracts(); +inline void checkBlockContracts(); template struct isBlockDependent { @@ -227,15 +210,15 @@ struct isBlockDependent { }; namespace block::property { -inline static const char *kHeartbeat = "Heartbeat"; ///< heartbeat property - the canary in the coal mine (supports block-specific subscribe/unsubscribe) -inline static const char *kEcho = "Echo"; ///< basic property that receives any matching message and sends a mirror with it's serviceName/unique_name -inline static const char *kLifeCycleState = "LifecycleState"; ///< basic property that sets the block's @see lifecycle::StateMachine -inline static const char *kSetting = "Settings"; ///< asynchronous message-based setting handling, +inline static const char* kHeartbeat = "Heartbeat"; ///< heartbeat property - the canary in the coal mine (supports block-specific subscribe/unsubscribe) +inline static const char* kEcho = "Echo"; ///< basic property that receives any matching message and sends a mirror with it's serviceName/unique_name +inline static const char* kLifeCycleState = "LifecycleState"; ///< basic property that sets the block's @see lifecycle::StateMachine +inline static const char* kSetting = "Settings"; ///< asynchronous message-based setting handling, // N.B. 'Set' Settings are first staged before being applied within the work(...) function (real-time/non-real-time decoupling) -inline static const char *kStagedSetting = "StagedSettings"; ///< asynchronous message-based staging of settings +inline static const char* kStagedSetting = "StagedSettings"; ///< asynchronous message-based staging of settings -inline static const char *kStoreDefaults = "StoreDefaults"; ///< store present settings as default, for counterpart @see kResetDefaults -inline static const char *kResetDefaults = "ResetDefaults"; ///< retrieve and reset to default setting, for counterpart @see kStoreDefaults +inline static const char* kStoreDefaults = "StoreDefaults"; ///< store present settings as default, for counterpart @see kResetDefaults +inline static const char* kResetDefaults = "ResetDefaults"; ///< retrieve and reset to default setting, for counterpart @see kStoreDefaults } // namespace block::property /** @@ -404,34 +387,31 @@ class Block : public lifecycle::StateMachine, protected std::tuple, Arguments>...> || std::disjunction_v, Arguments>...>; template - auto & - getArgument() { + auto& getArgument() { return std::get(*this); } template - const auto & - getArgument() const { + const auto& getArgument() const { return std::get(*this); } // TODO: These are not involved in move operations, might be a problem later - alignas(hardware_destructive_interference_size) std::atomic ioRequestedWork{ std::numeric_limits::max() }; + alignas(hardware_destructive_interference_size) std::atomic ioRequestedWork{std::numeric_limits::max()}; alignas(hardware_destructive_interference_size) work::Counter ioWorkDone{}; - alignas(hardware_destructive_interference_size) std::atomic ioLastWorkStatus{ work::Status::OK }; + alignas(hardware_destructive_interference_size) std::atomic ioLastWorkStatus{work::Status::OK}; alignas(hardware_destructive_interference_size) std::shared_ptr progress = std::make_shared(); alignas(hardware_destructive_interference_size) std::shared_ptr ioThreadPool; - alignas(hardware_destructive_interference_size) std::atomic ioThreadRunning{ false }; + alignas(hardware_destructive_interference_size) std::atomic ioThreadRunning{false}; constexpr static TagPropagationPolicy tag_policy = TagPropagationPolicy::TPP_ALL_TO_ALL; - using RatioValue = std::conditional_t; - A1: Interpolate, =1: No change)">, Limits<1UL, std::numeric_limits::max()>> numerator = Resampling::kNumerator; - A1: Interpolate, =1: No change)">, Limits<1UL, std::numeric_limits::max()>> denominator - = Resampling::kDenominator; - using StrideValue = std::conditional_t; - AN for skip, =0 for back-to-back.">> stride = StrideControl::kStride; - A> disconnect_on_done = true; + using RatioValue = std::conditional_t; + A1: Interpolate, =1: No change)">, Limits<1UL, std::numeric_limits::max()>> numerator = Resampling::kNumerator; + A1: Interpolate, =1: No change)">, Limits<1UL, std::numeric_limits::max()>> denominator = Resampling::kDenominator; + using StrideValue = std::conditional_t; + AN for skip, =0 for back-to-back.">> stride = StrideControl::kStride; + A> disconnect_on_done = true; gr::Size_t strideCounter = 0UL; // leftover stride from previous calls @@ -454,8 +434,7 @@ class Block : public lifecycle::StateMachine, protected std::tuple) { @@ -478,13 +457,13 @@ class Block : public lifecycle::StateMachine, protected std::tuple (Derived::*)(std::string_view, Message); std::map propertyCallbacks{ - { block::property::kHeartbeat, &Block::propertyCallbackHeartbeat }, // - { block::property::kEcho, &Block::propertyCallbackEcho }, // - { block::property::kLifeCycleState, &Block::propertyCallbackLifecycleState }, // - { block::property::kSetting, &Block::propertyCallbackSettings }, // - { block::property::kStagedSetting, &Block::propertyCallbackStagedSettings }, // - { block::property::kStoreDefaults, &Block::propertyCallbackStoreDefaults }, // - { block::property::kResetDefaults, &Block::propertyCallbackResetDefaults }, // + {block::property::kHeartbeat, &Block::propertyCallbackHeartbeat}, // + {block::property::kEcho, &Block::propertyCallbackEcho}, // + {block::property::kLifeCycleState, &Block::propertyCallbackLifecycleState}, // + {block::property::kSetting, &Block::propertyCallbackSettings}, // + {block::property::kStagedSetting, &Block::propertyCallbackStagedSettings}, // + {block::property::kStoreDefaults, &Block::propertyCallbackStoreDefaults}, // + {block::property::kResetDefaults, &Block::propertyCallbackResetDefaults}, // }; std::map> propertySubscriptions; @@ -495,27 +474,20 @@ class Block : public lifecycle::StateMachine, protected std::tuplereal-time setting states std::unique_ptr _settings; - [[nodiscard]] constexpr auto & - self() noexcept { - return *static_cast(this); - } + [[nodiscard]] constexpr auto& self() noexcept { return *static_cast(this); } - [[nodiscard]] constexpr const auto & - self() const noexcept { - return *static_cast(this); - } + [[nodiscard]] constexpr const auto& self() const noexcept { return *static_cast(this); } template - [[maybe_unused]] constexpr inline auto - invokeUserProvidedFunction(std::string_view callingSite, TFunction &&func, Args &&...args, const std::source_location &location = std::source_location::current()) noexcept { + [[maybe_unused]] constexpr inline auto invokeUserProvidedFunction(std::string_view callingSite, TFunction&& func, Args&&... args, const std::source_location& location = std::source_location::current()) noexcept { if constexpr (noexcept(func(std::forward(args)...))) { // function declared as 'noexcept' skip exception handling return std::forward(func)(std::forward(args)...); } else { // function not declared with 'noexcept' -> may throw try { return std::forward(func)(std::forward(args)...); - } catch (const gr::exception &e) { + } catch (const gr::exception& e) { emitErrorMessageIfAny(callingSite, std::unexpected(gr::Error(std::move(e)))); - } catch (const std::exception &e) { + } catch (const std::exception& e) { emitErrorMessageIfAny(callingSite, std::unexpected(gr::Error(e, location))); } catch (...) { emitErrorMessageIfAny(callingSite, std::unexpected(gr::Error("unknown error", location))); @@ -526,11 +498,11 @@ class Block : public lifecycle::StateMachine, protected std::tuple> initParameter) noexcept(false) : Block(property_map(initParameter)) {} - Block(property_map initParameter = {}) noexcept(false) // N.B. throws in case of on contract violations - : _settings(std::make_unique>(*static_cast(this))) { // N.B. safe delegated use of this (i.e. not used during construction) + Block(property_map initParameter = {}) noexcept(false) // N.B. throws in case of on contract violations + : _settings(std::make_unique>(*static_cast(this))) { // N.B. safe delegated use of this (i.e. not used during construction) // check Block contracts - checkBlockContracts(this))>(); + checkBlockContracts(this))>(); if (initParameter.size() != 0) { if (const property_map failed = settings().set(std::move(initParameter)); !failed.empty()) { @@ -539,25 +511,12 @@ class Block : public lifecycle::StateMachine, protected std::tuple(std::move(other)) - , std::tuple(std::move(other)) - , numerator(std::move(other.numerator)) - , denominator(std::move(other.denominator)) - , stride(std::move(other.stride)) - , strideCounter(std::move(other.strideCounter)) - , msgIn(std::move(other.msgIn)) - , msgOut(std::move(other.msgOut)) - , _outputTagsChanged(std::move(other._outputTagsChanged)) - , _mergedInputTag(std::move(other._mergedInputTag)) - , _settings(std::move(other._settings)) {} + Block(Block&& other) noexcept : lifecycle::StateMachine(std::move(other)), std::tuple(std::move(other)), numerator(std::move(other.numerator)), denominator(std::move(other.denominator)), stride(std::move(other.stride)), strideCounter(std::move(other.strideCounter)), msgIn(std::move(other.msgIn)), msgOut(std::move(other.msgOut)), propertyCallbacks(std::move(other.propertyCallbacks)), _outputTagsChanged(std::move(other._outputTagsChanged)), _mergedInputTag(std::move(other._mergedInputTag)), _settings(std::move(other._settings)) {} // There are a few const or conditionally const member variables, // we can not have a move-assignment that is equivalent to // the move constructor - Block & - operator=(Block &&other) - = delete; + Block& operator=(Block&& other) = delete; ~Block() { // NOSONAR -- need to request the (potentially) running ioThread to stop if (lifecycle::isActive(this->state())) { @@ -575,28 +534,27 @@ class Block : public lifecycle::StateMachine, protected std::tuplechangeStateTo(lifecycle::State::STOPPED)); } - void - init(std::shared_ptr progress_, std::shared_ptr ioThreadPool_) { + void init(std::shared_ptr progress_, std::shared_ptr ioThreadPool_) { progress = std::move(progress_); ioThreadPool = std::move(ioThreadPool_); // Set names of port member variables // TODO: Refactor the library not to assign names to ports. The // block and the graph are the only things that need the port name - auto setPortName = [&]([[maybe_unused]] std::size_t index, auto &&t) { + auto setPortName = [&]([[maybe_unused]] std::size_t index, auto&& t) { using CurrentPortType = std::remove_cvref_t; if constexpr (traits::port::is_port_v) { using PortDescriptor = typename CurrentPortType::ReflDescriptor; if constexpr (refl::trait::is_descriptor_v) { - auto &port = (self().*(PortDescriptor::pointer)); + auto& port = (self().*(PortDescriptor::pointer)); port.name = CurrentPortType::Name; } } else { using PortCollectionDescriptor = typename CurrentPortType::value_type::ReflDescriptor; if constexpr (refl::trait::is_descriptor_v) { - auto &collection = (self().*(PortCollectionDescriptor::pointer)); + auto& collection = (self().*(PortCollectionDescriptor::pointer)); std::string collectionName = refl::descriptor::get_name(PortCollectionDescriptor()).data; - for (auto &port : collection) { + for (auto& port : collection) { port.name = collectionName; } } @@ -623,8 +581,7 @@ class Block : public lifecycle::StateMachine, protected std::tuple - [[nodiscard]] constexpr std::size_t - availableInputSamples(Container &data) const noexcept { + [[nodiscard]] constexpr std::size_t availableInputSamples(Container& data) const noexcept { if constexpr (gr::meta::vector_type) { data.resize(traits::block::stream_input_port_types::size); } else if constexpr (gr::meta::array_type) { @@ -633,23 +590,22 @@ class Block : public lifecycle::StateMachine, protected std::tuple, "type not supported"); } meta::tuple_for_each_enumerate( - [&data](auto index, Port &input_port) { - if constexpr (traits::port::is_port_v) { - data[index] = input_port.streamReader().available(); - } else { - data[index] = 0; - for (auto &port : input_port) { - data[index] += port.streamReader().available(); - } + [&data](auto index, Port& input_port) { + if constexpr (traits::port::is_port_v) { + data[index] = input_port.streamReader().available(); + } else { + data[index] = 0; + for (auto& port : input_port) { + data[index] += port.streamReader().available(); } - }, - inputPorts(&self())); + } + }, + inputPorts(&self())); return traits::block::stream_input_port_types::size; } template - [[nodiscard]] constexpr std::size_t - availableOutputSamples(Container &data) const noexcept { + [[nodiscard]] constexpr std::size_t availableOutputSamples(Container& data) const noexcept { if constexpr (gr::meta::vector_type) { data.resize(traits::block::stream_output_port_types::size); } else if constexpr (gr::meta::array_type) { @@ -658,69 +614,48 @@ class Block : public lifecycle::StateMachine, protected std::tuple, "type not supported"); } meta::tuple_for_each_enumerate( - [&data](auto index, Port &output_port) { - if constexpr (traits::port::is_port_v) { - data[index] = output_port.streamWriter().available(); - } else { - data[index] = 0; - for (auto &port : output_port) { - data[index] += port.streamWriter().available(); - } + [&data](auto index, Port& output_port) { + if constexpr (traits::port::is_port_v) { + data[index] = output_port.streamWriter().available(); + } else { + data[index] = 0; + for (auto& port : output_port) { + data[index] += port.streamWriter().available(); } - }, - outputPorts(&self())); + } + }, + outputPorts(&self())); return traits::block::stream_output_port_types::size; } - [[nodiscard]] constexpr bool - isBlocking() const noexcept { - return blockingIO; - } + [[nodiscard]] constexpr bool isBlocking() const noexcept { return blockingIO; } - [[nodiscard]] constexpr bool - input_tags_present() const noexcept { - return !_mergedInputTag.map.empty(); - }; + [[nodiscard]] constexpr bool input_tags_present() const noexcept { return !_mergedInputTag.map.empty(); }; - [[nodiscard]] Tag - mergedInputTag() const noexcept { - return _mergedInputTag; - } + [[nodiscard]] Tag mergedInputTag() const noexcept { return _mergedInputTag; } - [[nodiscard]] constexpr SettingsBase & - settings() const noexcept { - return *_settings; - } + [[nodiscard]] constexpr SettingsBase& settings() const noexcept { return *_settings; } - [[nodiscard]] constexpr SettingsBase & - settings() noexcept { - return *_settings; - } + [[nodiscard]] constexpr SettingsBase& settings() noexcept { return *_settings; } template - void - setSettings(std::unique_ptr &settings) { + void setSettings(std::unique_ptr& settings) { _settings = std::move(settings); } template - friend constexpr auto & - inputPort(Self *self) noexcept; + friend constexpr auto& inputPort(Self* self) noexcept; template - friend constexpr auto & - outputPort(Self *self) noexcept; + friend constexpr auto& outputPort(Self* self) noexcept; template - friend constexpr auto & - inputPort(Self *self) noexcept; + friend constexpr auto& inputPort(Self* self) noexcept; template - friend constexpr auto & - outputPort(Self *self) noexcept; + friend constexpr auto& outputPort(Self* self) noexcept; - constexpr void - checkBlockParameterConsistency() { + constexpr void checkBlockParameterConsistency() { constexpr bool kIsSourceBlock = traits::block::stream_input_port_types::size == 0; constexpr bool kIsSinkBlock = traits::block::stream_output_port_types::size == 0; @@ -730,8 +665,7 @@ class Block : public lifecycle::StateMachine, protected std::tuple, "Blocks which allow decimation/interpolation must implement processBulk(...) method. Remove 'ResamplingRatio<>' from the block definition."); } else { if (numerator != 1ULL || denominator != 1ULL) { - emitErrorMessage("Block::checkParametersAndThrowIfNeeded:", - fmt::format("Block is not defined as `ResamplingRatio<>`, but numerator = {}, denominator = {}, they both must equal to 1.", numerator, denominator)); + emitErrorMessage("Block::checkParametersAndThrowIfNeeded:", fmt::format("Block is not defined as `ResamplingRatio<>`, but numerator = {}, denominator = {}, they both must equal to 1.", numerator, denominator)); requestStop(); return; } @@ -771,96 +705,92 @@ class Block : public lifecycle::StateMachine, protected std::tuple::size > 0) { meta::tuple_for_each_enumerate( - [nSamples](auto, OutputRange &outputRange) { - auto processOneRange = [nSamples](Out &out) { - if constexpr (Out::isMultiThreadedStrategy()) { - if (!out.isFullyPublished()) { - fmt::print(stderr, "Block::publishSamples - did not publish all samples for MultiThreadedStrategy\n"); - std::abort(); - } - } - if (!out.isPublished()) { - using enum gr::SpanReleasePolicy; - if constexpr (Out::spanReleasePolicy() == Terminate) { - fmt::print(stderr, "Block::publishSamples - samples were not published, default SpanReleasePolicy is {}\n", magic_enum::enum_name(Terminate)); - std::abort(); - } else if constexpr (Out::spanReleasePolicy() == ProcessAll) { - out.publish(nSamples); - } else if constexpr (Out::spanReleasePolicy() == ProcessNone) { - out.publish(0U); - } + [nSamples](auto, OutputRange& outputRange) { + auto processOneRange = [nSamples](Out& out) { + if constexpr (Out::isMultiThreadedStrategy()) { + if (!out.isFullyPublished()) { + fmt::print(stderr, "Block::publishSamples - did not publish all samples for MultiThreadedStrategy\n"); + std::abort(); } - }; - if constexpr (refl::trait::is_instance_of_v>) { - for (auto &out : outputRange) { - processOneRange(out); + } + if (!out.isPublished()) { + using enum gr::SpanReleasePolicy; + if constexpr (Out::spanReleasePolicy() == Terminate) { + fmt::print(stderr, "Block::publishSamples - samples were not published, default SpanReleasePolicy is {}\n", magic_enum::enum_name(Terminate)); + std::abort(); + } else if constexpr (Out::spanReleasePolicy() == ProcessAll) { + out.publish(nSamples); + } else if constexpr (Out::spanReleasePolicy() == ProcessNone) { + out.publish(0U); } - } else { - processOneRange(outputRange); } - }, - publishableSpanTuple); + }; + if constexpr (refl::trait::is_instance_of_v>) { + for (auto& out : outputRange) { + processOneRange(out); + } + } else { + processOneRange(outputRange); + } + }, + publishableSpanTuple); } } - bool - consumeReaders(std::size_t nSamples, auto &consumableSpanTuple) { + bool consumeReaders(std::size_t nSamples, auto& consumableSpanTuple) { bool success = true; if constexpr (traits::block::stream_input_ports::size > 0) { meta::tuple_for_each_enumerate( - [nSamples, &success](auto, InputRange &inputRange) { - auto processOneRange = [nSamples, &success](In &in) { - if (!in.isConsumeRequested()) { - using enum gr::SpanReleasePolicy; - if constexpr (In::spanReleasePolicy() == Terminate) { - fmt::print(stderr, "Block::consumeReaders - samples were not consumed, default SpanReleasePolicy is {}\n", magic_enum::enum_name(Terminate)); - std::abort(); - } else if constexpr (In::spanReleasePolicy() == ProcessAll) { - success = success && in.consume(nSamples); - } else if constexpr (In::spanReleasePolicy() == ProcessNone) { - success = success && in.consume(0U); - } + [nSamples, &success](auto, InputRange& inputRange) { + auto processOneRange = [nSamples, &success](In& in) { + if (!in.isConsumeRequested()) { + using enum gr::SpanReleasePolicy; + if constexpr (In::spanReleasePolicy() == Terminate) { + fmt::print(stderr, "Block::consumeReaders - samples were not consumed, default SpanReleasePolicy is {}\n", magic_enum::enum_name(Terminate)); + std::abort(); + } else if constexpr (In::spanReleasePolicy() == ProcessAll) { + success = success && in.consume(nSamples); + } else if constexpr (In::spanReleasePolicy() == ProcessNone) { + success = success && in.consume(0U); } - }; - if constexpr (refl::trait::is_instance_of_v>) { - for (auto &in : inputRange) { - processOneRange(in); - } - } else { - processOneRange(inputRange); } - }, - consumableSpanTuple); + }; + if constexpr (refl::trait::is_instance_of_v>) { + for (auto& in : inputRange) { + processOneRange(in); + } + } else { + processOneRange(inputRange); + } + }, + consumableSpanTuple); } return success; } template - constexpr auto - invoke_processOne(std::size_t offset, Ts &&...inputs) { + constexpr auto invoke_processOne(std::size_t offset, Ts&&... inputs) { if constexpr (traits::block::stream_output_ports::size == 0) { invokeProcessOneWithOrWithoutOffset(self(), offset, std::forward(inputs)...); return std::tuple{}; } else if constexpr (traits::block::stream_output_ports::size == 1) { - return std::tuple{ invokeProcessOneWithOrWithoutOffset(self(), offset, std::forward(inputs)...) }; + return std::tuple{invokeProcessOneWithOrWithoutOffset(self(), offset, std::forward(inputs)...)}; } else { return invokeProcessOneWithOrWithoutOffset(self(), offset, std::forward(inputs)...); } } template - constexpr auto - invoke_processOne_simd(std::size_t offset, auto width, Ts &&...input_simds) { + constexpr auto invoke_processOne_simd(std::size_t offset, auto width, Ts&&... input_simds) { if constexpr (sizeof...(Ts) == 0) { if constexpr (traits::block::stream_output_ports::size == 0) { self().processOne_simd(offset, width); return std::tuple{}; } else if constexpr (traits::block::stream_output_ports::size == 1) { - return std::tuple{ self().processOne_simd(offset, width) }; + return std::tuple{self().processOne_simd(offset, width)}; } else { return self().processOne_simd(offset, width); } @@ -869,14 +799,13 @@ class Block : public lifecycle::StateMachine, protected std::tuple(&self())); + for_each_port([](PortLike auto& outPort) noexcept { outPort.publishPendingTags(); }, outputPorts(&self())); _outputTagsChanged = false; } @@ -886,26 +815,25 @@ class Block : public lifecycle::StateMachine, protected std::tuple(Port &input_port) noexcept { - auto mergeSrcMapInto = [](const property_map &sourceMap, property_map &destinationMap) { - assert(&sourceMap != &destinationMap); - for (const auto &[key, value] : sourceMap) { - destinationMap.insert_or_assign(key, value); - } - }; + [untilOffset, this](Port& input_port) noexcept { + auto mergeSrcMapInto = [](const property_map& sourceMap, property_map& destinationMap) { + assert(&sourceMap != &destinationMap); + for (const auto& [key, value] : sourceMap) { + destinationMap.insert_or_assign(key, value); + } + }; - const Tag mergedPortTags = input_port.getTag(static_cast(untilOffset)); - mergeSrcMapInto(mergedPortTags.map, _mergedInputTag.map); - }, - inputPorts(&self())); + const Tag mergedPortTags = input_port.getTag(static_cast(untilOffset)); + mergeSrcMapInto(mergedPortTags.map, _mergedInputTag.map); + }, + inputPorts(&self())); if (!mergedInputTag().map.empty()) { settings().autoUpdate(mergedInputTag()); // apply tags as new settings if matching if constexpr (Derived::tag_policy == TagPropagationPolicy::TPP_ALL_TO_ALL) { - for_each_port([this](PortLike auto &outPort) noexcept { outPort.publishTag(mergedInputTag().map, 0); }, outputPorts(&self())); + for_each_port([this](PortLike auto& outPort) noexcept { outPort.publishTag(mergedInputTag().map, 0); }, outputPorts(&self())); } if (mergedInputTag().map.contains(gr::tag::END_OF_STREAM)) { requestStop(); @@ -913,8 +841,7 @@ class Block : public lifecycle::StateMachine, protected std::tuple, protected std::tuple(PortOrCollection &output_port_or_collection) noexcept { - auto process_single_port = [&sync_samples](Port &&port) { - using enum gr::SpanReleasePolicy; - if constexpr (std::remove_cvref_t::kIsSynch) { - if constexpr (std::remove_cvref_t::kIsInput) { - return std::forward(port).streamReader().template get(sync_samples); - } else if constexpr (std::remove_cvref_t::kIsOutput) { - return std::forward(port).streamWriter().template reserve(sync_samples); - } - } else { - // for the Async port get/reserve all available samples - if constexpr (std::remove_cvref_t::kIsInput) { - return std::forward(port).streamReader().template get(port.streamReader().available()); - } else if constexpr (std::remove_cvref_t::kIsOutput) { - return std::forward(port).streamWriter().template reserve(port.streamWriter().available()); - } + [sync_samples](PortOrCollection& output_port_or_collection) noexcept { + auto process_single_port = [&sync_samples](Port&& port) { + using enum gr::SpanReleasePolicy; + if constexpr (std::remove_cvref_t::kIsSynch) { + if constexpr (std::remove_cvref_t::kIsInput) { + return std::forward(port).streamReader().template get(sync_samples); + } else if constexpr (std::remove_cvref_t::kIsOutput) { + return std::forward(port).streamWriter().template reserve(sync_samples); } - }; - if constexpr (traits::port::is_port_v) { - return process_single_port(output_port_or_collection); } else { - using value_span = decltype(process_single_port(std::declval())); - std::vector result{}; - std::transform(output_port_or_collection.begin(), output_port_or_collection.end(), std::back_inserter(result), process_single_port); - return result; + // for the Async port get/reserve all available samples + if constexpr (std::remove_cvref_t::kIsInput) { + return std::forward(port).streamReader().template get(port.streamReader().available()); + } else if constexpr (std::remove_cvref_t::kIsOutput) { + return std::forward(port).streamWriter().template reserve(port.streamWriter().available()); + } } - }, - ports); + }; + if constexpr (traits::port::is_port_v) { + return process_single_port(output_port_or_collection); + } else { + using value_span = decltype(process_single_port(std::declval())); + std::vector result{}; + std::transform(output_port_or_collection.begin(), output_port_or_collection.end(), std::back_inserter(result), process_single_port); + return result; + } + }, + ports); } - inline constexpr void - publishTag(property_map &&tag_data, Tag::signed_index_type tagOffset = -1) noexcept { - for_each_port([tag_data = std::move(tag_data), tagOffset](PortLike auto &outPort) { outPort.publishTag(tag_data, tagOffset); }, outputPorts(&self())); + inline constexpr void publishTag(property_map&& tag_data, Tag::signed_index_type tagOffset = -1) noexcept { + for_each_port([tag_data = std::move(tag_data), tagOffset](PortLike auto& outPort) { outPort.publishTag(tag_data, tagOffset); }, outputPorts(&self())); } - inline constexpr void - publishTag(const property_map &tag_data, Tag::signed_index_type tagOffset = -1) noexcept { - for_each_port([&tag_data, tagOffset](PortLike auto &outPort) { outPort.publishTag(tag_data, tagOffset); }, outputPorts(&self())); + inline constexpr void publishTag(const property_map& tag_data, Tag::signed_index_type tagOffset = -1) noexcept { + for_each_port([&tag_data, tagOffset](PortLike auto& outPort) { outPort.publishTag(tag_data, tagOffset); }, outputPorts(&self())); } - constexpr void - requestStop() noexcept { - emitErrorMessageIfAny("requestStop()", this->changeStateTo(lifecycle::State::REQUESTED_STOP)); - } + constexpr void requestStop() noexcept { emitErrorMessageIfAny("requestStop()", this->changeStateTo(lifecycle::State::REQUESTED_STOP)); } - constexpr void - processScheduledMessages() { + constexpr void processScheduledMessages() { using namespace std::chrono; const std::uint64_t nanoseconds_count = static_cast(duration_cast(system_clock::now().time_since_epoch()).count()); - notifyListeners(block::property::kHeartbeat, { { "heartbeat", nanoseconds_count } }); + notifyListeners(block::property::kHeartbeat, {{"heartbeat", nanoseconds_count}}); - auto processPort = [this](TPort &inPort) { + auto processPort = [this](TPort& inPort) { const auto available = inPort.streamReader().available(); if (available == 0UZ) { return; @@ -1011,14 +931,13 @@ class Block : public lifecycle::StateMachine, protected std::tuple - propertyCallbackHeartbeat(std::string_view propertyName, Message message) { + std::optional propertyCallbackHeartbeat(std::string_view propertyName, Message message) { using enum gr::message::Command; assert(propertyName == block::property::kHeartbeat); if (message.cmd == Set || message.cmd == Get) { std::uint64_t nanoseconds_count = static_cast(std::chrono::duration_cast(std::chrono::system_clock::now().time_since_epoch()).count()); - message.data = { { "heartbeat", nanoseconds_count } }; + message.data = {{"heartbeat", nanoseconds_count}}; return message; } else if (message.cmd == Subscribe) { if (!message.clientRequestID.empty()) { @@ -1033,8 +952,7 @@ class Block : public lifecycle::StateMachine, protected std::tuple - propertyCallbackEcho(std::string_view propertyName, Message message) { + std::optional propertyCallbackEcho(std::string_view propertyName, Message message) { using enum gr::message::Command; assert(propertyName == block::property::kEcho); @@ -1045,8 +963,7 @@ class Block : public lifecycle::StateMachine, protected std::tuple - propertyCallbackLifecycleState(std::string_view propertyName, Message message) { + std::optional propertyCallbackLifecycleState(std::string_view propertyName, Message message) { using enum gr::message::Command; assert(propertyName == block::property::kLifeCycleState); @@ -1058,7 +975,7 @@ class Block : public lifecycle::StateMachine, protected std::tuple(message.data.value().at("state")); - } catch (const std::exception &e) { + } catch (const std::exception& e) { throw gr::exception(fmt::format("propertyCallbackLifecycleState - state conversion throws {}, msg: {}", e.what(), message)); } catch (...) { throw gr::exception(fmt::format("propertyCallbackLifecycleState - state conversion throws unknown exception, msg: {}", message)); @@ -1069,11 +986,11 @@ class Block : public lifecycle::StateMachine, protected std::tuplechangeStateTo(state.value()); !e) { throw gr::exception(fmt::format("propertyCallbackLifecycleState - error in state transition - what: {}", // - e.error().message, e.error().sourceLocation, e.error().errorTime)); + e.error().message, e.error().sourceLocation, e.error().errorTime)); } return std::nullopt; } else if (message.cmd == Get) { - message.data = { { "state", std::string(magic_enum::enum_name(this->state())) } }; + message.data = {{"state", std::string(magic_enum::enum_name(this->state()))}}; return message; } else if (message.cmd == Subscribe) { if (!message.clientRequestID.empty()) { @@ -1088,8 +1005,7 @@ class Block : public lifecycle::StateMachine, protected std::tuple - propertyCallbackSettings(std::string_view propertyName, Message message) { + std::optional propertyCallbackSettings(std::string_view propertyName, Message message) { using enum gr::message::Command; assert(propertyName == block::property::kSetting); @@ -1117,13 +1033,12 @@ class Block : public lifecycle::StateMachine, protected std::tuple - propertyCallbackStagedSettings(std::string_view propertyName, Message message) { + std::optional propertyCallbackStagedSettings(std::string_view propertyName, Message message) { using enum gr::message::Command; assert(propertyName == block::property::kStagedSetting); - const auto keys = [](const property_map &map) noexcept { + const auto keys = [](const property_map& map) noexcept { std::string result; - for (const auto &pair : map) { + for (const auto& pair : map) { if (!result.empty()) { result += ", "; } @@ -1166,8 +1081,7 @@ class Block : public lifecycle::StateMachine, protected std::tuple - propertyCallbackStoreDefaults(std::string_view propertyName, Message message) { + std::optional propertyCallbackStoreDefaults(std::string_view propertyName, Message message) { using enum gr::message::Command; assert(propertyName == block::property::kStoreDefaults); @@ -1179,8 +1093,7 @@ class Block : public lifecycle::StateMachine, protected std::tuple - propertyCallbackResetDefaults(std::string_view propertyName, Message message) { + std::optional propertyCallbackResetDefaults(std::string_view propertyName, Message message) { using enum gr::message::Command; assert(propertyName == block::property::kResetDefaults); @@ -1199,8 +1112,7 @@ class Block : public lifecycle::StateMachine, protected std::tuple - auto - getPortLimits(P &&ports) { + auto getPortLimits(P&& ports) { struct { std::size_t minSync = 0UL; // the minimum amount of samples that the block needs for processing on the sync ports std::size_t maxSync = std::numeric_limits::max(); // the maximum amount of that can be consumed on all sync ports @@ -1208,7 +1120,7 @@ class Block : public lifecycle::StateMachine, protected std::tuple(Port &port) { + auto adjustForInputPort = [&result](Port& port) { const std::size_t available = [&port]() { if constexpr (gr::traits::port::is_input_v) { return port.streamReader().available(); @@ -1226,15 +1138,14 @@ class Block : public lifecycle::StateMachine, protected std::tuple(ports)); + for_each_port([&adjustForInputPort](PortLike auto& port) { adjustForInputPort(port); }, std::forward

(ports)); return result; } /*** * Check the input ports for available samples */ - auto - getNextTagAndEosPosition() { + auto getNextTagAndEosPosition() { struct { bool hasTag = false; std::size_t nextTag = std::numeric_limits::max(); @@ -1242,7 +1153,7 @@ class Block : public lifecycle::StateMachine, protected std::tuple(Port &port) { + auto adjustForInputPort = [&result](Port& port) { if (port.isConnected()) { if constexpr (std::remove_cvref_t::kIsSynch) { // get the tag after the one at position 0 that will be evaluated for this chunk. @@ -1258,7 +1169,7 @@ class Block : public lifecycle::StateMachine, protected std::tuple(&self())); + for_each_port([&adjustForInputPort](PortLike auto& port) { adjustForInputPort(port); }, inputPorts(&self())); return result; } @@ -1267,8 +1178,7 @@ class Block : public lifecycle::StateMachine, protected std::tuple, protected std::tuple, protected std::tuple, protected std::tuple, protected std::tuple(nResamplingChunks * denominator), static_cast(nResamplingChunks * numerator) }; + return ResamplingResult{static_cast(nResamplingChunks * denominator), static_cast(nResamplingChunks * numerator)}; } } - std::size_t - getMergedBlockLimit() { - if constexpr (requires(const Derived &d) { + std::size_t getMergedBlockLimit() { + if constexpr (requires(const Derived& d) { { available_samples(d) } -> std::same_as; }) { return available_samples(self()); - } else if constexpr (traits::block::stream_input_port_types::size == 0 - && traits::block::stream_output_port_types::size - == 0) { // allow blocks that have neither input nor output ports (by merging source to sink block) -> use internal buffer size + } else if constexpr (traits::block::stream_input_port_types::size == 0 && traits::block::stream_output_port_types::size == 0) { // allow blocks that have neither input nor output ports (by merging source to sink block) -> use internal buffer size constexpr gr::Size_t chunkSize = Derived::merged_work_chunk_size(); static_assert(chunkSize != std::dynamic_extent && chunkSize > 0, "At least one internal port must define a maximum number of samples or the non-member/hidden " "friend function `available_samples(const BlockType&)` must be defined."); @@ -1349,17 +1254,12 @@ class Block : public lifecycle::StateMachine, protected std::tuple - gr::work::Status - invokeProcessBulk(TIn &inputReaderTuple, TOut &outputReaderTuple) { - auto tempInputSpanStorage = std::apply([]( - PortReader &...args) { return std::tuple{ (gr::meta::array_or_vector_type ? std::span{ args.data(), args.size() } : args)... }; }, - inputReaderTuple); + gr::work::Status invokeProcessBulk(TIn& inputReaderTuple, TOut& outputReaderTuple) { + auto tempInputSpanStorage = std::apply([](PortReader&... args) { return std::tuple{(gr::meta::array_or_vector_type ? std::span{args.data(), args.size()} : args)...}; }, inputReaderTuple); - auto tempOutputSpanStorage = std::apply([]( - PortReader &...args) { return std::tuple{ (gr::meta::array_or_vector_type ? std::span{ args.data(), args.size() } : args)... }; }, - outputReaderTuple); + auto tempOutputSpanStorage = std::apply([](PortReader&... args) { return std::tuple{(gr::meta::array_or_vector_type ? std::span{args.data(), args.size()} : args)...}; }, outputReaderTuple); - auto refToSpan = [](T &&original, U &&temporary) -> decltype(auto) { + auto refToSpan = [](T&& original, U&& temporary) -> decltype(auto) { if constexpr (gr::meta::array_or_vector_type>) { return std::forward(temporary); } else { @@ -1367,41 +1267,34 @@ class Block : public lifecycle::StateMachine, protected std::tuple(std::index_sequence, std::index_sequence) { - return self().processBulk(refToSpan(std::get(inputReaderTuple), std::get(tempInputSpanStorage))..., - refToSpan(std::get(outputReaderTuple), std::get(tempOutputSpanStorage))...); - }(std::make_index_sequence>>(), - std::make_index_sequence>>()); + return [&](std::index_sequence, std::index_sequence) { return self().processBulk(refToSpan(std::get(inputReaderTuple), std::get(tempInputSpanStorage))..., refToSpan(std::get(outputReaderTuple), std::get(tempOutputSpanStorage))...); }(std::make_index_sequence>>(), std::make_index_sequence>>()); } - work::Status - invokeProcessOneSimd(auto &inputSpans, auto &outputSpans, auto width, std::size_t nSamplesToProcess) { + work::Status invokeProcessOneSimd(auto& inputSpans, auto& outputSpans, auto width, std::size_t nSamplesToProcess) { std::size_t i = 0; for (; i + width <= nSamplesToProcess; i += width) { - const auto &results = simdize_tuple_load_and_apply(width, inputSpans, i, [&](const auto &...input_simds) { return invoke_processOne_simd(i, width, input_simds...); }); - meta::tuple_for_each([i](auto &output_range, const auto &result) { result.copy_to(output_range.data() + i, stdx::element_aligned); }, outputSpans, results); + const auto& results = simdize_tuple_load_and_apply(width, inputSpans, i, [&](const auto&... input_simds) { return invoke_processOne_simd(i, width, input_simds...); }); + meta::tuple_for_each([i](auto& output_range, const auto& result) { result.copy_to(output_range.data() + i, stdx::element_aligned); }, outputSpans, results); } simd_epilogue(width, [&](auto w) { if (i + w <= nSamplesToProcess) { - const auto results = simdize_tuple_load_and_apply(w, inputSpans, i, [&](auto &&...input_simds) { return invoke_processOne_simd(i, w, input_simds...); }); - meta::tuple_for_each([i](auto &output_range, auto &result) { result.copy_to(output_range.data() + i, stdx::element_aligned); }, outputSpans, results); + const auto results = simdize_tuple_load_and_apply(w, inputSpans, i, [&](auto&&... input_simds) { return invoke_processOne_simd(i, w, input_simds...); }); + meta::tuple_for_each([i](auto& output_range, auto& result) { result.copy_to(output_range.data() + i, stdx::element_aligned); }, outputSpans, results); i += w; } }); return work::Status::OK; } - work::Status - invokeProcessOnePure(auto &inputSpans, auto &outputSpans, std::size_t nSamplesToProcess) { + work::Status invokeProcessOnePure(auto& inputSpans, auto& outputSpans, std::size_t nSamplesToProcess) { for (std::size_t i = 0; i < nSamplesToProcess; ++i) { - auto results = std::apply([this, i](auto &...inputs) { return this->invoke_processOne(i, inputs[i]...); }, inputSpans); - meta::tuple_for_each([i](auto &output_range, R &&result) { output_range[i] = std::forward(result); }, outputSpans, results); + auto results = std::apply([this, i](auto&... inputs) { return this->invoke_processOne(i, inputs[i]...); }, inputSpans); + meta::tuple_for_each([i](auto& output_range, R&& result) { output_range[i] = std::forward(result); }, outputSpans, results); } return work::Status::OK; } - auto - invokeProcessOneNonConst(auto &inputSpans, auto &outputSpans, std::size_t nSamplesToProcess) { + auto invokeProcessOneNonConst(auto& inputSpans, auto& outputSpans, std::size_t nSamplesToProcess) { using enum work::Status; struct ProcessOneResult { @@ -1412,88 +1305,75 @@ class Block : public lifecycle::StateMachine, protected std::tupleinvoke_processOne(i, inputs[i]...); }, inputSpans); + auto results = std::apply([this, i](auto&... inputs) { return this->invoke_processOne(i, inputs[i]...); }, inputSpans); meta::tuple_for_each( - [i](auto &output_range, R &&result) { - if constexpr (meta::array_or_vector_type>) { - for (int j = 0; j < result.size(); j++) { - output_range[i][j] = std::move(result[j]); - } - } else { - output_range[i] = std::forward(result); + [i](auto& output_range, R&& result) { + if constexpr (meta::array_or_vector_type>) { + for (int j = 0; j < result.size(); j++) { + output_range[i][j] = std::move(result[j]); } - }, - outputSpans, results); + } else { + output_range[i] = std::forward(result); + } + }, + outputSpans, results); nOutSamplesBeforeRequestedStop++; // the block implementer can set `_outputTagsChanged` to true in `processOne` to prematurely leave the loop and apply his changes if (_outputTagsChanged || lifecycle::isShuttingDown(this->state())) [[unlikely]] { // emitted tag and/or requested to stop break; } } - return ProcessOneResult{ lifecycle::isShuttingDown(this->state()) ? DONE : OK, nSamplesToProcess, std::min(nSamplesToProcess, nOutSamplesBeforeRequestedStop) }; + return ProcessOneResult{lifecycle::isShuttingDown(this->state()) ? DONE : OK, nSamplesToProcess, std::min(nSamplesToProcess, nOutSamplesBeforeRequestedStop)}; } - [[nodiscard]] bool - hasNoDownStreamConnectedChildren() const noexcept { + [[nodiscard]] bool hasNoDownStreamConnectedChildren() const noexcept { std::size_t nMandatoryChildren = 0UZ; std::size_t nMandatoryConnectedChildren = 0UZ; for_each_port( - [&nMandatoryChildren, &nMandatoryConnectedChildren](const Port &outputPort) { - if constexpr (!Port::isOptional()) { - nMandatoryChildren++; - if (outputPort.isConnected()) { - nMandatoryConnectedChildren++; - } + [&nMandatoryChildren, &nMandatoryConnectedChildren](const Port& outputPort) { + if constexpr (!Port::isOptional()) { + nMandatoryChildren++; + if (outputPort.isConnected()) { + nMandatoryConnectedChildren++; } - }, - outputPorts(&self())); + } + }, + outputPorts(&self())); return nMandatoryChildren > 0UZ && nMandatoryConnectedChildren == 0UZ; } - constexpr void - disconnectFromUpStreamParents() noexcept { + constexpr void disconnectFromUpStreamParents() noexcept { using TInputTypes = traits::block::stream_input_port_types; if constexpr (TInputTypes::size.value > 0UZ) { if (!disconnect_on_done) { return; } for_each_port( - [](Port &inputPort) { - if (inputPort.isConnected()) { - std::ignore = inputPort.disconnect(); - } - }, - inputPorts(&self())); + [](Port& inputPort) { + if (inputPort.isConnected()) { + std::ignore = inputPort.disconnect(); + } + }, + inputPorts(&self())); } } - void - emitMessage(std::string_view endpoint, property_map message, std::string_view clientRequestID = "") noexcept { - sendMessage(msgOut, unique_name /* serviceName */, endpoint, std::move(message), clientRequestID); - } + void emitMessage(std::string_view endpoint, property_map message, std::string_view clientRequestID = "") noexcept { sendMessage(msgOut, unique_name /* serviceName */, endpoint, std::move(message), clientRequestID); } - void - notifyListeners(std::string_view endpoint, property_map message) noexcept { + void notifyListeners(std::string_view endpoint, property_map message) noexcept { const auto it = propertySubscriptions.find(std::string(endpoint)); if (it != propertySubscriptions.end()) { - for (const auto &clientID : it->second) { + for (const auto& clientID : it->second) { emitMessage(endpoint, message, clientID); } } } - void - emitErrorMessage(std::string_view endpoint, std::string_view errorMsg, std::string_view clientRequestID = "", std::source_location location = std::source_location::current()) noexcept { - emitErrorMessageIfAny(endpoint, std::unexpected(Error(errorMsg, location)), clientRequestID); - } + void emitErrorMessage(std::string_view endpoint, std::string_view errorMsg, std::string_view clientRequestID = "", std::source_location location = std::source_location::current()) noexcept { emitErrorMessageIfAny(endpoint, std::unexpected(Error(errorMsg, location)), clientRequestID); } - void - emitErrorMessage(std::string_view endpoint, Error e, std::string_view clientRequestID = "") noexcept { - emitErrorMessageIfAny(endpoint, std::unexpected(e), clientRequestID); - } + void emitErrorMessage(std::string_view endpoint, Error e, std::string_view clientRequestID = "") noexcept { emitErrorMessageIfAny(endpoint, std::unexpected(e), clientRequestID); } - inline void - emitErrorMessageIfAny(std::string_view endpoint, std::expected e, std::string_view clientRequestID = "") noexcept { + inline void emitErrorMessageIfAny(std::string_view endpoint, std::expected e, std::string_view clientRequestID = "") noexcept { if (!e.has_value()) [[unlikely]] { sendMessage(msgOut, unique_name /* serviceName */, endpoint, std::move(e.error()), clientRequestID); } @@ -1531,8 +1411,7 @@ class Block : public lifecycle::StateMachine, protected std::tuple; using TOutputTypes = traits::block::stream_output_port_types; @@ -1554,7 +1433,7 @@ class Block : public lifecycle::StateMachine, protected std::tuplestate() == lifecycle::State::STOPPED) { disconnectFromUpStreamParents(); - return { requested_work, 0UZ, work::Status::DONE }; + return {requested_work, 0UZ, work::Status::DONE}; } // evaluate number of available and processable samples @@ -1562,10 +1441,10 @@ class Block : public lifecycle::StateMachine, protected std::tuple(&self())); auto [hasTag, nextTag, nextEosTag, asyncEoS] = getNextTagAndEosPosition(); std::size_t maxChunk = getMergedBlockLimit(); // handle special cases for merged blocks. TODO: evaluate if/how we can get rid of these - const auto inputSkipBefore = inputSamplesToSkipBeforeNextChunk(std::min({ maxSyncAvailableIn, nextTag, nextEosTag })); - const auto availableToProcess = std::min({ maxSyncIn, maxChunk, (maxSyncAvailableIn - inputSkipBefore), (nextTag - inputSkipBefore), (nextEosTag - inputSkipBefore) }); - const auto availableToPublish = std::min({ maxSyncOut, maxSyncAvailableOut }); - const auto [resampledIn, resampledOut] = computeResampling(minSyncIn, availableToProcess, minSyncOut, availableToPublish); + const auto inputSkipBefore = inputSamplesToSkipBeforeNextChunk(std::min({maxSyncAvailableIn, nextTag, nextEosTag})); + const auto availableToProcess = std::min({maxSyncIn, maxChunk, (maxSyncAvailableIn - inputSkipBefore), (nextTag - inputSkipBefore), (nextEosTag - inputSkipBefore)}); + const auto availableToPublish = std::min({maxSyncOut, maxSyncAvailableOut}); + const auto [resampledIn, resampledOut] = computeResampling(minSyncIn, availableToProcess, minSyncOut, availableToPublish); if (inputSkipBefore > 0) { // consume samples on sync ports that need to be consumed due to the stride updateInputAndOutputTags(inputSkipBefore); // apply all tags in the skipped data range @@ -1576,20 +1455,20 @@ class Block : public lifecycle::StateMachine, protected std::tuple REQUESTED_STOP", this->changeStateTo(lifecycle::State::REQUESTED_STOP)); - publishTag({ { gr::tag::END_OF_STREAM, true } }, 0); + publishTag({{gr::tag::END_OF_STREAM, true}}, 0); updateInputAndOutputTags(); forwardTags(); this->setAndNotifyState(lifecycle::State::STOPPED); - return { requested_work, 0UZ, work::Status::DONE }; + return {requested_work, 0UZ, work::Status::DONE}; } if (nextEosTag <= 0 || lifecycle::isShuttingDown(this->state())) { emitErrorMessageIfAny("workInternal(): REQUESTED_STOP", this->changeStateTo(lifecycle::State::REQUESTED_STOP)); updateInputAndOutputTags(); applyChangedSettings(); forwardTags(); - return { requested_work, 0UZ, work::Status::DONE }; + return {requested_work, 0UZ, work::Status::DONE}; } - return { requested_work, 0UZ, resampledOut == 0 ? INSUFFICIENT_OUTPUT_ITEMS : INSUFFICIENT_INPUT_ITEMS }; + return {requested_work, 0UZ, resampledOut == 0 ? INSUFFICIENT_OUTPUT_ITEMS : INSUFFICIENT_INPUT_ITEMS}; } // process stream tags updateInputAndOutputTags(); @@ -1624,17 +1503,13 @@ class Block : public lifecycle::StateMachine, protected std::tuple width{}; - if constexpr ((meta::simdize_size_v != 0) and ((requires(Derived &d) { - { d.processOne_simd(simd_size) }; - }) or (meta::simdize_size_v != 0 and traits::block::can_processOne_simd))) { // SIMD loop - invokeUserProvidedFunction("invokeProcessOneSimd", [&ret, &inputSpans, &outputSpans, &width, &processedIn, this] noexcept(HasNoexceptProcessOneFunction) { - ret = invokeProcessOneSimd(inputSpans, outputSpans, width, processedIn); - }); + if constexpr ((meta::simdize_size_v != 0) and ((requires(Derived& d) { + { d.processOne_simd(simd_size) }; + }) or (meta::simdize_size_v != 0 and traits::block::can_processOne_simd))) { // SIMD loop + invokeUserProvidedFunction("invokeProcessOneSimd", [&ret, &inputSpans, &outputSpans, &width, &processedIn, this] noexcept(HasNoexceptProcessOneFunction) { ret = invokeProcessOneSimd(inputSpans, outputSpans, width, processedIn); }); } else { // Non-SIMD loop if constexpr (HasConstProcessOneFunction) { // processOne is const -> can process whole batch similar to SIMD-ised call - invokeUserProvidedFunction("invokeProcessOnePure", [&ret, &inputSpans, &outputSpans, &processedIn, this] noexcept(HasNoexceptProcessOneFunction) { - ret = invokeProcessOnePure(inputSpans, outputSpans, processedIn); - }); + invokeUserProvidedFunction("invokeProcessOnePure", [&ret, &inputSpans, &outputSpans, &processedIn, this] noexcept(HasNoexceptProcessOneFunction) { ret = invokeProcessOnePure(inputSpans, outputSpans, processedIn); }); } else { // processOne isn't const i.e. not a pure function w/o side effects -> need to evaluate state after each sample const auto result = invokeProcessOneNonConst(inputSpans, outputSpans, processedIn); ret = result.status; @@ -1667,15 +1542,14 @@ class Block : public lifecycle::StateMachine, protected std::tuplesetAndNotifyState(lifecycle::State::STOPPED); - publishTag({ { gr::tag::END_OF_STREAM, true } }, 0); + publishTag({{gr::tag::END_OF_STREAM, true}}, 0); } - return { requested_work, processedIn, success ? ret : work::Status::ERROR }; + return {requested_work, processedIn, success ? ret : work::Status::ERROR}; } // end: work_return_t workInternal() noexcept { ..} public: - work::Status - invokeWork() - requires(blockingIO) + work::Status invokeWork() + requires(blockingIO) { auto [work_requested, work_done, last_status] = workInternal(std::atomic_load_explicit(&ioRequestedWork, std::memory_order_acquire)); ioWorkDone.increment(work_requested, work_done); @@ -1694,8 +1568,7 @@ class Block : public lifecycle::StateMachine, protected std::tuple - work::Result - work(std::size_t requested_work = std::numeric_limits::max()) noexcept { + work::Result work(std::size_t requested_work = std::numeric_limits::max()) noexcept { if constexpr (blockingIO) { constexpr bool useIoThread = std::disjunction_v, Arguments>...>; std::atomic_store_explicit(&ioRequestedWork, requested_work, std::memory_order_release); @@ -1705,7 +1578,7 @@ class Block : public lifecycle::StateMachine, protected std::tupleexecute([this]() { assert(lifecycle::isActive(this->state())); @@ -1732,28 +1605,27 @@ class Block : public lifecycle::StateMachine, protected std::tuplestate()); if (!blockIsActive) { - publishTag({ { gr::tag::END_OF_STREAM, true } }, 0); + publishTag({{gr::tag::END_OF_STREAM, true}}, 0); ioLastWorkStatus.exchange(work::Status::DONE, std::memory_order_relaxed); } } - const auto &[accumulatedRequestedWork, performedWork] = ioWorkDone.getAndReset(); + const auto& [accumulatedRequestedWork, performedWork] = ioWorkDone.getAndReset(); // TODO: this is just "working" solution for deadlock with emscripten, need to be investigated further #if defined(__EMSCRIPTEN__) std::this_thread::sleep_for(std::chrono::nanoseconds(1)); #endif - return { accumulatedRequestedWork, performedWork, ioLastWorkStatus.load() }; + return {accumulatedRequestedWork, performedWork, ioLastWorkStatus.load()}; } else { return workInternal(requested_work); } } - void - processMessages([[maybe_unused]] const MsgPortInNamed<"__Builtin"> &port, std::span messages) { + void processMessages([[maybe_unused]] const MsgPortInNamed<"__Builtin">& port, std::span messages) { using enum gr::message::Command; assert(std::addressof(port) == std::addressof(msgIn) && "got a message on wrong port"); - for (const auto &message : messages) { + for (const auto& message : messages) { if (!message.serviceName.empty() && message.serviceName != unique_name && message.serviceName != name) { // Skip if target does not match the block's (unique) name and is not empty. continue; @@ -1778,14 +1650,14 @@ class Block : public lifecycle::StateMachine, protected std::tuple retMessage; try { retMessage = (self().*callback)(message.endpoint, message); // N.B. life-time: message is copied - } catch (const gr::exception &e) { - retMessage = Message{ message }; + } catch (const gr::exception& e) { + retMessage = Message{message}; retMessage->data = std::unexpected(Error(e)); - } catch (const std::exception &e) { - retMessage = Message{ message }; + } catch (const std::exception& e) { + retMessage = Message{message}; retMessage->data = std::unexpected(Error(e)); } catch (...) { - retMessage = Message{ message }; + retMessage = Message{message}; retMessage->data = std::unexpected(Error(fmt::format("unknown exception in Block {} property '{}'\n request message: {} ", unique_name, message.endpoint, message))); } @@ -1795,7 +1667,7 @@ class Block : public lifecycle::StateMachine, protected std::tuplecmd = Final; // N.B. could enable/allow for partial if we return multiple messages (e.g. using coroutines?) retMessage->serviceName = unique_name; - msgOut.streamWriter().publish([&](auto &out) { out[0] = *retMessage; }, 1UZ); + msgOut.streamWriter().publish([&](auto& out) { out[0] = *retMessage; }, 1UZ); } // - end - for (const auto &message : messages) { .. } @@ -1803,8 +1675,7 @@ class Block : public lifecycle::StateMachine, protected std::tuple -inline constexpr auto -for_each_type_to_string(StringFunction func) -> std::string { +inline constexpr auto for_each_type_to_string(StringFunction func) -> std::string { if constexpr (Index < List::size) { using T = typename List::template at; return std::string(Index > 0 ? ", " : "") + func(Index, T()) + for_each_type_to_string(func); @@ -1814,8 +1685,7 @@ for_each_type_to_string(StringFunction func) -> std::string { } template -inline constexpr std::string -container_type_name() { +inline constexpr std::string container_type_name() { if constexpr (requires { typename T::allocator_type; }) { return fmt::format("std::vector<{}>", gr::meta::type_name()); } else if constexpr (requires { std::tuple_size::value; }) { @@ -1832,8 +1702,7 @@ container_type_name() { } // namespace detail template -inline void -checkBlockContracts() { +inline void checkBlockContracts() { // N.B. some checks could be evaluated during compile time but the expressed intent is to do this during runtime to allow // for more verbose feedback on method signatures etc. constexpr static auto processMembers = [](Func func) { @@ -1866,9 +1735,9 @@ checkBlockContracts() { using Type = std::remove_cvref_t>; constexpr bool isAnnotated = !std::is_same_v; // N.B. this function is compile-time ready but static_assert does not allow for configurable error messages - if constexpr (!gr::settings::isSupportedType() && !(traits::port::is_port_v || traits::port::is_port_collection_v) ) { + if constexpr (!gr::settings::isSupportedType() && !(traits::port::is_port_v || traits::port::is_port_collection_v)) { throw std::invalid_argument(fmt::format("block {} {}member '{}' has unsupported setting type '{}'", // - gr::meta::type_name(), isAnnotated ? "" : "annotated ", get_display_name(member), shortTypeName.template operator()())); + gr::meta::type_name(), isAnnotated ? "" : "annotated ", get_display_name(member), shortTypeName.template operator()())); } }; processMembers(checkSettingsTypes); @@ -1931,8 +1800,7 @@ fmt::format(R"(gr::work::Status processBulk({}{}{}) {{ TInputTypes::for_each([&has_port_collection](auto, T) { has_port_collection |= requires { typename T::value_type; }; }); TOutputTypes::for_each([&has_port_collection](auto, T) { has_port_collection |= requires { typename T::value_type; }; }); const std::string signatures = (has_port_collection ? "" : signatureProcessOne) + signaturesProcessBulk; - throw std::invalid_argument(fmt::format("block {} has neither a valid processOne(...) nor valid processBulk(...) method\nPossible valid signatures (copy-paste):\n\n{}", - shortTypeName.template operator()(), signatures)); + throw std::invalid_argument(fmt::format("block {} has neither a valid processOne(...) nor valid processBulk(...) method\nPossible valid signatures (copy-paste):\n\n{}", shortTypeName.template operator()(), signatures)); } // test for optional Drawable interface @@ -1944,7 +1812,7 @@ fmt::format(R"(gr::work::Status processBulk({}{}{}) {{ } template -inline std::atomic_size_t Block::_uniqueIdCounter{ 0UZ }; +inline std::atomic_size_t Block::_uniqueIdCounter{0UZ}; } // namespace gr ENABLE_REFLECTION_FOR_TEMPLATE_FULL((typename T, typename... Arguments), (gr::Block), numerator, denominator, stride, disconnect_on_done, unique_name, name, meta_information); @@ -1955,8 +1823,7 @@ namespace gr { * @brief a short human-readable/markdown description of the node -- content is not contractual and subject to change */ template -[[nodiscard]] /*constexpr*/ std::string -blockDescription() noexcept { +[[nodiscard]] /*constexpr*/ std::string blockDescription() noexcept { using DerivedBlock = typename TBlock::derived_t; using ArgumentList = typename TBlock::block_template_parameters; using SupportedTypes = typename ArgumentList::template find_or_default; @@ -1964,9 +1831,8 @@ blockDescription() noexcept { // re-enable once string and constexpr static is supported by all compilers /*constexpr*/ std::string ret = fmt::format("# {}\n{}\n{}\n**supported data types:**", // - gr::meta::type_name(), TBlock::description, - kIsBlocking ? "**BlockingIO**\n_i.e. potentially non-deterministic/non-real-time behaviour_\n" : ""); - gr::meta::typelist::for_each([&](std::size_t index, auto &&t) { + gr::meta::type_name(), TBlock::description, kIsBlocking ? "**BlockingIO**\n_i.e. potentially non-deterministic/non-real-time behaviour_\n" : ""); + gr::meta::typelist::for_each([&](std::size_t index, auto&& t) { std::string type_name = gr::meta::type_name(); ret += fmt::format("{}:{} ", index, type_name); }); @@ -1976,17 +1842,17 @@ blockDescription() noexcept { using RawType = std::remove_cvref_t; using Type = unwrap_if_wrapped_t; - if constexpr (is_readable(member) && (std::integral || std::floating_point || std::is_same_v) ) { + if constexpr (is_readable(member) && (std::integral || std::floating_point || std::is_same_v)) { if constexpr (is_annotated()) { const std::string type_name = refl::detail::get_type_name().str(); const std::string member_name = get_display_name_const(member).str(); ret += fmt::format("{}{:10} {:<20} - annotated info: {} unit: [{}] documentation: {}{}\n", - RawType::visible() ? "" : "_", // - type_name, - member_name, // - RawType::description(), RawType::unit(), - RawType::documentation(), // - RawType::visible() ? "" : "_"); + RawType::visible() ? "" : "_", // + type_name, + member_name, // + RawType::description(), RawType::unit(), + RawType::documentation(), // + RawType::visible() ? "" : "_"); } else { const std::string type_name = refl::detail::get_type_name().str(); const std::string member_name = get_display_name_const(member).str(); @@ -2003,8 +1869,7 @@ namespace detail { using namespace std::string_literals; template -std::string -reflFirstTypeName() { +std::string reflFirstTypeName() { // // Using refl cpp for getting names of types does not work // with class templates. It returns "Template" as the name @@ -2026,16 +1891,16 @@ reflFirstTypeName() { } template -std::string -encodeListOfTypes() { +std::string encodeListOfTypes() { struct accumulator { std::string value; - accumulator & - operator%(const std::string &type) { - if (value.empty()) value = type; - else + accumulator& operator%(const std::string& type) { + if (value.empty()) { + value = type; + } else { value += ","s + type; + } return *this; } @@ -2045,16 +1910,14 @@ encodeListOfTypes() { } template -std::string -blockBaseName() { +std::string blockBaseName() { auto blockName = reflFirstTypeName(); auto it = std::ranges::find(blockName, '<'); return std::string(blockName.begin(), it); } template -std::string -nttpToString() { +std::string nttpToString() { if constexpr (magic_enum::is_scoped_enum_v || magic_enum::is_unscoped_enum_v) { return std::string(magic_enum::enum_name(Value)); } else { @@ -2065,10 +1928,7 @@ nttpToString() { template struct BlockParameters : meta::typelist { - static std::string - toString() { - return detail::encodeListOfTypes(); - } + static std::string toString() { return detail::encodeListOfTypes(); } }; /** @@ -2084,8 +1944,7 @@ struct BlockParameters : meta::typelist { * - TBlockParameters -- types that the block can be instantiated with */ template typename TBlock, typename... TBlockParameters, typename TRegisterInstance> -inline constexpr int -registerBlock(TRegisterInstance ®isterInstance) { +inline constexpr int registerBlock(TRegisterInstance& registerInstance) { auto addBlockType = [&] { using ThisBlock = TBlock; static_assert(!meta::is_instantiation_of); @@ -2096,8 +1955,7 @@ registerBlock(TRegisterInstance ®isterInstance) { } template typename TBlock, typename... TBlockParameters, typename TRegisterInstance> -inline constexpr int -registerBlock(TRegisterInstance ®isterInstance) { +inline constexpr int registerBlock(TRegisterInstance& registerInstance) { auto addBlockType = [&] { using ThisBlock = TBlock, typename Type::template at<1>>; static_assert(meta::is_instantiation_of); @@ -2109,94 +1967,89 @@ registerBlock(TRegisterInstance ®isterInstance) { } template typename TBlock, auto Value0, typename... TBlockParameters, typename TRegisterInstance> -inline constexpr int -registerBlock(TRegisterInstance ®isterInstance) { +inline constexpr int registerBlock(TRegisterInstance& registerInstance) { auto addBlockType = [&] { static_assert(!meta::is_instantiation_of); using ThisBlock = TBlock; registerInstance.template addBlockType(detail::blockBaseName(), // - detail::reflFirstTypeName() + "," + detail::nttpToString()); + detail::reflFirstTypeName() + "," + detail::nttpToString()); }; ((addBlockType.template operator()()), ...); return {}; } template typename TBlock, auto Value0, typename... TBlockParameters, typename TRegisterInstance> -inline constexpr int -registerBlock(TRegisterInstance ®isterInstance) { +inline constexpr int registerBlock(TRegisterInstance& registerInstance) { auto addBlockType = [&] { static_assert(meta::is_instantiation_of); static_assert(Type::size == 2); using ThisBlock = TBlock, typename Type::template at<1>, Value0>; registerInstance.template addBlockType(detail::blockBaseName(), // - Type::toString() + "," + detail::nttpToString()); + Type::toString() + "," + detail::nttpToString()); }; ((addBlockType.template operator()()), ...); return {}; } template typename TBlock, auto Value0, auto Value1, typename... TBlockParameters, typename TRegisterInstance> -inline constexpr int -registerBlock(TRegisterInstance ®isterInstance) { +inline constexpr int registerBlock(TRegisterInstance& registerInstance) { auto addBlockType = [&] { static_assert(!meta::is_instantiation_of); using ThisBlock = TBlock; registerInstance.template addBlockType(detail::blockBaseName(), // - detail::reflFirstTypeName() + "," + detail::nttpToString() + "," + detail::nttpToString()); + detail::reflFirstTypeName() + "," + detail::nttpToString() + "," + detail::nttpToString()); }; ((addBlockType.template operator()()), ...); return {}; } template typename TBlock, auto Value0, auto Value1, typename... TBlockParameters, typename TRegisterInstance> -inline constexpr int -registerBlock(TRegisterInstance ®isterInstance) { +inline constexpr int registerBlock(TRegisterInstance& registerInstance) { auto addBlockType = [&] { static_assert(meta::is_instantiation_of); static_assert(Type::size == 2); using ThisBlock = TBlock, typename Type::template at<1>, Value0, Value1>; registerInstance.template addBlockType(detail::blockBaseName(), // - Type::toString() + "," + detail::nttpToString() + "," + detail::nttpToString()); + Type::toString() + "," + detail::nttpToString() + "," + detail::nttpToString()); }; ((addBlockType.template operator()()), ...); return {}; } template -inline constexpr auto -for_each_port(Function &&function, Tuple &&tuple, Tuples &&...tuples) { +inline constexpr auto for_each_port(Function&& function, Tuple&& tuple, Tuples&&... tuples) { return gr::meta::tuple_for_each( - [&function](auto &&...args) { - (..., ([&function](auto &&arg) { - using ArgType = std::decay_t; - if constexpr (traits::port::is_port_v) { - function(arg); // arg is a port, apply function directly - } else if constexpr (traits::port::is_port_collection_v) { - for (auto &port : arg) { // arg is a collection of ports, apply function to each port - function(port); - } - } else { - static_assert(gr::meta::always_false, "not a port or collection of ports"); - } - }(args))); - }, - std::forward(tuple), std::forward(tuples)...); + [&function](auto&&... args) { + (..., ([&function](auto&& arg) { + using ArgType = std::decay_t; + if constexpr (traits::port::is_port_v) { + function(arg); // arg is a port, apply function directly + } else if constexpr (traits::port::is_port_collection_v) { + for (auto& port : arg) { // arg is a collection of ports, apply function to each port + function(port); + } + } else { + static_assert(gr::meta::always_false, "not a port or collection of ports"); + } + }(args))); + }, + std::forward(tuple), std::forward(tuples)...); } } // namespace gr template<> struct fmt::formatter { - static constexpr auto - parse(const format_parse_context &ctx) { + static constexpr auto parse(const format_parse_context& ctx) { const auto it = ctx.begin(); - if (it != ctx.end() && *it != '}') throw format_error("invalid format"); + if (it != ctx.end() && *it != '}') { + throw format_error("invalid format"); + } return it; } template - auto - format(const gr::work::Result &work_return, FormatContext &ctx) { + auto format(const gr::work::Result& work_return, FormatContext& ctx) { return fmt::format_to(ctx.out(), "requested_work: {}, performed_work: {}, status: {}", work_return.requested_work, work_return.performed_work, magic_enum::enum_name(work_return.status)); } }; diff --git a/core/include/gnuradio-4.0/BlockModel.hpp b/core/include/gnuradio-4.0/BlockModel.hpp new file mode 100644 index 00000000..44f209dc --- /dev/null +++ b/core/include/gnuradio-4.0/BlockModel.hpp @@ -0,0 +1,405 @@ +#ifndef GNURADIO_BLOCK_MODEL_HPP +#define GNURADIO_BLOCK_MODEL_HPP + +#include +#include +#include +#include +#include + +#include + +namespace gr { + +class BlockModel { +protected: + struct NamedPortCollection { + std::string name; + std::vector ports; + }; + + using DynamicPortOrCollection = std::variant; + using DynamicPorts = std::vector; + bool _dynamicPortsLoaded = false; + std::function _dynamicPortsLoader; + DynamicPorts _dynamicInputPorts; + DynamicPorts _dynamicOutputPorts; + + BlockModel() = default; + + [[nodiscard]] gr::DynamicPort& dynamicPortFromName(DynamicPorts& what, std::string_view name) { + initDynamicPorts(); + + if (auto separatorIt = std::ranges::find(name, '.'); separatorIt == name.end()) { + auto it = std::ranges::find_if(what, [name](const DynamicPortOrCollection& portOrCollection) { + const auto* port = std::get_if(&portOrCollection); + return port && port->name == name; + }); + + if (it == what.end()) { + throw gr::exception(fmt::format("Port {} not found in {}\n", name, uniqueName())); + } + + return std::get(*it); + } else { + const std::string_view base(name.begin(), separatorIt); + const std::string_view indexString(separatorIt + 1, name.end()); + std::size_t index = -1UZ; + auto [_, ec] = std::from_chars(indexString.data(), indexString.data() + indexString.size(), index); + if (ec != std::errc()) { + throw gr::exception(fmt::format("Invalid index {} specified, needs to be an integer", indexString)); + } + + auto collectionIt = std::ranges::find_if(what, [name](const DynamicPortOrCollection& portOrCollection) { + const auto* collection = std::get_if(&portOrCollection); + return collection && collection->name == name; + }); + + auto& collection = std::get(*collectionIt); + + if (index >= collection.ports.size()) { + throw gr::exception(fmt::format("Invalid index {} specified, out of range. Number of ports is {}", index, collection.ports.size())); + } + + return collection.ports[index]; + } + } + +public: + BlockModel(const BlockModel&) = delete; + BlockModel& operator=(const BlockModel&) = delete; + BlockModel(BlockModel&& other) = delete; + BlockModel& operator=(BlockModel&& other) = delete; + + void initDynamicPorts() const { + if (!_dynamicPortsLoaded) { + _dynamicPortsLoader(); + } + } + + MsgPortInNamed<"__Builtin">* msgIn; + MsgPortOutNamed<"__Builtin">* msgOut; + + [[nodiscard]] gr::DynamicPort& dynamicInputPort(std::string_view name) { return dynamicPortFromName(_dynamicInputPorts, name); } + + [[nodiscard]] gr::DynamicPort& dynamicOutputPort(std::string_view name) { return dynamicPortFromName(_dynamicOutputPorts, name); } + + [[nodiscard]] gr::DynamicPort& dynamicInputPort(std::size_t index, std::size_t subIndex = meta::invalid_index) { + initDynamicPorts(); + if (auto* portCollection = std::get_if(&_dynamicInputPorts.at(index))) { + if (subIndex == meta::invalid_index) { + throw std::invalid_argument("Need to specify the index in the port collection"); + } else { + return portCollection->ports[subIndex]; + } + + } else if (auto* port = std::get_if(&_dynamicInputPorts.at(index))) { + if (subIndex == meta::invalid_index) { + return *port; + } else { + throw std::invalid_argument("Specified sub-index for a normal port"); + } + } + + throw std::logic_error("Variant construction failed"); + } + + [[nodiscard]] gr::DynamicPort& dynamicOutputPort(std::size_t index, std::size_t subIndex = meta::invalid_index) { + initDynamicPorts(); + if (auto* portCollection = std::get_if(&_dynamicOutputPorts.at(index))) { + if (subIndex == meta::invalid_index) { + throw std::invalid_argument("Need to specify the index in the port collection"); + } else { + return portCollection->ports[subIndex]; + } + + } else if (auto* port = std::get_if(&_dynamicOutputPorts.at(index))) { + if (subIndex == meta::invalid_index) { + return *port; + } else { + throw std::invalid_argument("Specified sub-index for a normal port"); + } + } + + throw std::logic_error("Variant construction failed"); + } + + [[nodiscard]] std::size_t dynamicInputPortsSize(std::size_t parentIndex = meta::invalid_index) const { + initDynamicPorts(); + if (parentIndex == meta::invalid_index) { + return _dynamicInputPorts.size(); + } else { + if (auto* portCollection = std::get_if(&_dynamicInputPorts.at(parentIndex))) { + return portCollection->ports.size(); + } else { + return meta::invalid_index; + } + } + } + + [[nodiscard]] std::size_t dynamicOutputPortsSize(std::size_t parentIndex = meta::invalid_index) const { + initDynamicPorts(); + if (parentIndex == meta::invalid_index) { + return _dynamicOutputPorts.size(); + } else { + if (auto* portCollection = std::get_if(&_dynamicOutputPorts.at(parentIndex))) { + return portCollection->ports.size(); + } else { + return meta::invalid_index; + } + } + } + + std::size_t dynamicInputPortIndex(std::string_view name) const { + initDynamicPorts(); + for (std::size_t i = 0; i < _dynamicInputPorts.size(); ++i) { + if (auto* portCollection = std::get_if(&_dynamicInputPorts.at(i))) { + if (portCollection->name == name) { + return i; + } + } else if (auto* port = std::get_if(&_dynamicInputPorts.at(i))) { + if (port->name == name) { + return i; + } + } + } + + throw std::invalid_argument(fmt::format("Port {} does not exist", name)); + } + + std::size_t dynamicOutputPortIndex(std::string_view name) const { + initDynamicPorts(); + for (std::size_t i = 0; i < _dynamicOutputPorts.size(); ++i) { + if (auto* portCollection = std::get_if(&_dynamicOutputPorts.at(i))) { + if (portCollection->name == name) { + return i; + } + } else if (auto* port = std::get_if(&_dynamicOutputPorts.at(i))) { + if (port->name == name) { + return i; + } + } + } + + throw std::invalid_argument(fmt::format("Port {} does not exist", name)); + } + + virtual ~BlockModel() = default; + + /** + * @brief to be called by scheduler->graph to initialise block + */ + virtual void init(std::shared_ptr progress, std::shared_ptr ioThreadPool) = 0; + + /** + * @brief returns scheduling hint that invoking the work(...) function may block on IO or system-calls + */ + [[nodiscard]] virtual constexpr bool isBlocking() const noexcept = 0; + + /** + * @brief change Block state (N.B. IDLE, INITIALISED, RUNNING, REQUESTED_STOP, REQUESTED_PAUSE, STOPPED, PAUSED, ERROR) + * See enum description for details. + */ + [[nodiscard]] virtual std::expected changeState(lifecycle::State newState) noexcept = 0; + + /** + * @brief Block state (N.B. IDLE, INITIALISED, RUNNING, REQUESTED_STOP, REQUESTED_PAUSE, STOPPED, PAUSED, ERROR) + * See enum description for details. + */ + [[nodiscard]] virtual lifecycle::State state() const noexcept = 0; + + /** + * @brief number of available readable samples at the block's input ports + */ + [[nodiscard]] virtual constexpr std::size_t availableInputSamples(std::vector&) const noexcept = 0; + + /** + * @brief number of available writable samples at the block's output ports + */ + [[nodiscard]] virtual constexpr std::size_t availableOutputSamples(std::vector&) const noexcept = 0; + + /** + * @brief user defined name + */ + [[nodiscard]] virtual std::string_view name() const = 0; + + /** + * @brief the type of the node as a string + */ + [[nodiscard]] virtual std::string_view typeName() const = 0; + + /** + * @brief user-defined name + * N.B. may not be unique -> ::uniqueName + */ + virtual void setName(std::string name) noexcept = 0; + + /** + * @brief used to store non-graph-processing information like UI block position etc. + */ + [[nodiscard]] virtual property_map& metaInformation() noexcept = 0; + + [[nodiscard]] virtual const property_map& metaInformation() const = 0; + + /** + * @brief process-wide unique name + * N.B. can be used to disambiguate in case user provided the same 'name()' for several blocks. + */ + [[nodiscard]] virtual std::string_view uniqueName() const = 0; + + [[nodiscard]] virtual SettingsBase& settings() const = 0; + + [[nodiscard]] virtual work::Result work(std::size_t requested_work) = 0; + + [[nodiscard]] virtual work::Status draw() = 0; + + virtual void processScheduledMessages() = 0; + + virtual UICategory uiCategory() const { return UICategory::None; } + + [[nodiscard]] virtual void* raw() = 0; +}; + +namespace detail { +template +constexpr bool contains_type = (std::is_same_v || ...); +} + +template +requires std::is_constructible_v +class BlockWrapper : public BlockModel { +private: + static_assert(std::is_same_v>); + T _block; + std::string _type_name = gr::meta::type_name(); + + [[nodiscard]] constexpr const auto& blockRef() const noexcept { + if constexpr (requires { *_block; }) { + return *_block; + } else { + return _block; + } + } + + [[nodiscard]] constexpr auto& blockRef() noexcept { + if constexpr (requires { *_block; }) { + return *_block; + } else { + return _block; + } + } + + void initMessagePorts() { + msgIn = std::addressof(_block.msgIn); + msgOut = std::addressof(_block.msgOut); + } + + template + constexpr static auto& processPort(auto& where, TPort& port) noexcept { + where.push_back(gr::DynamicPort(port, DynamicPort::non_owned_reference_tag{})); + return where.back(); + } + + void dynamicPortLoader() { + if (_dynamicPortsLoaded) { + return; + } + + auto registerPort = [this](DynamicPorts& where, [[maybe_unused]] Direction direction, [[maybe_unused]] ConstIndex index, CurrentPortType&&) noexcept { + if constexpr (traits::port::is_port_v) { + using PortDescriptor = typename CurrentPortType::ReflDescriptor; + if constexpr (refl::trait::is_descriptor_v) { + auto& port = (blockRef().*(PortDescriptor::pointer)); + if (port.name.empty()) { + port.name = refl::descriptor::get_name(PortDescriptor()).data; + } + processPort(where, port); + } else { + // We can also have ports defined as template parameters + if constexpr (Direction::value == PortDirection::INPUT) { + processPort(where, gr::inputPort(&blockRef())); + } else { + processPort(where, gr::outputPort(&blockRef())); + } + } + } else { + using PortCollectionDescriptor = typename CurrentPortType::value_type::ReflDescriptor; + if constexpr (refl::trait::is_descriptor_v) { + auto& collection = (blockRef().*(PortCollectionDescriptor::pointer)); + NamedPortCollection result; + result.name = refl::descriptor::get_name(PortCollectionDescriptor()).data; + for (auto& port : collection) { + processPort(result.ports, port); + } + where.push_back(std::move(result)); + } else { + static_assert(meta::always_false, "Port collections are only supported for member variables"); + } + } + }; + + using Node = std::remove_cvref_t; + traits::block::all_input_ports::for_each(registerPort, _dynamicInputPorts, std::integral_constant{}); + traits::block::all_output_ports::for_each(registerPort, _dynamicOutputPorts, std::integral_constant{}); + + _dynamicPortsLoaded = true; + } + +public: + BlockWrapper(const BlockWrapper& other) = delete; + BlockWrapper(BlockWrapper&& other) = delete; + BlockWrapper& operator=(const BlockWrapper& other) = delete; + BlockWrapper& operator=(BlockWrapper&& other) = delete; + + ~BlockWrapper() override = default; + + explicit BlockWrapper(property_map initParameter = {}) : _block(std::move(initParameter)) { + initMessagePorts(); + _dynamicPortsLoader = std::bind(&BlockWrapper::dynamicPortLoader, this); + } + + void init(std::shared_ptr progress, std::shared_ptr ioThreadPool) override { return blockRef().init(progress, ioThreadPool); } + + [[nodiscard]] constexpr work::Result work(std::size_t requested_work = std::numeric_limits::max()) override { return blockRef().work(requested_work); } + + constexpr work::Status draw() override { + if constexpr (requires { blockRef().draw(); }) { + return blockRef().draw(); + } + return work::Status::ERROR; + } + + UICategory uiCategory() const override { return T::DrawableControl::kCategory; } + + void processScheduledMessages() override { return blockRef().processScheduledMessages(); } + + [[nodiscard]] constexpr bool isBlocking() const noexcept override { return blockRef().isBlocking(); } + + [[nodiscard]] std::expected changeState(lifecycle::State newState) noexcept override { return blockRef().changeStateTo(newState); } + + [[nodiscard]] lifecycle::State state() const noexcept override { return blockRef().state(); } + + [[nodiscard]] constexpr std::size_t availableInputSamples(std::vector& data) const noexcept override { return blockRef().availableInputSamples(data); } + + [[nodiscard]] constexpr std::size_t availableOutputSamples(std::vector& data) const noexcept override { return blockRef().availableOutputSamples(data); } + + [[nodiscard]] std::string_view name() const override { return blockRef().name; } + + void setName(std::string name) noexcept override { blockRef().name = std::move(name); } + + [[nodiscard]] std::string_view typeName() const override { return _type_name; } + + [[nodiscard]] property_map& metaInformation() noexcept override { return blockRef().meta_information; } + + [[nodiscard]] const property_map& metaInformation() const override { return blockRef().meta_information; } + + [[nodiscard]] std::string_view uniqueName() const override { return blockRef().unique_name; } + + [[nodiscard]] SettingsBase& settings() const override { return blockRef().settings(); } + + [[nodiscard]] void* raw() override { return std::addressof(blockRef()); } +}; + +} // namespace gr + +#endif // GNURADIO_BLOCK_MODEL_HPP diff --git a/core/include/gnuradio-4.0/BlockRegistry.hpp b/core/include/gnuradio-4.0/BlockRegistry.hpp index 356fc54d..233b00e1 100644 --- a/core/include/gnuradio-4.0/BlockRegistry.hpp +++ b/core/include/gnuradio-4.0/BlockRegistry.hpp @@ -1,13 +1,15 @@ -#ifndef BLOCK_REGISTRY_HPP -#define BLOCK_REGISTRY_HPP +#ifndef GNURADIO_BLOCK_REGISTRY_HPP +#define GNURADIO_BLOCK_REGISTRY_HPP #include #include #include -#include +#include #include +#include "BlockModel.hpp" + namespace gr { using namespace std::string_literals; @@ -16,9 +18,8 @@ using namespace std::string_view_literals; namespace detail { template - requires std::is_constructible_v -std::unique_ptr -blockFactory(property_map params) { +requires std::is_constructible_v +std::unique_ptr blockFactory(property_map params) { return std::make_unique>(std::move(params)); } @@ -29,8 +30,7 @@ class BlockRegistry { std::vector _blockTypes; std::unordered_map> _blockTypeHandlers; - auto & - findBlockTypeHandlersMap(const std::string &blockType) { + auto& findBlockTypeHandlersMap(const std::string& blockType) { if (auto it = _blockTypeHandlers.find(blockType); it != _blockTypeHandlers.end()) { return it->second; } @@ -39,32 +39,25 @@ class BlockRegistry { } public: -#ifndef NOPLUGINS +#ifdef ENABLE_BLOCK_REGISTRY template - requires std::is_constructible_v - void - addBlockType(std::string blockType, std::string blockParams) { - auto &block_handlers = findBlockTypeHandlersMap(blockType); + requires std::is_constructible_v + void addBlockType(std::string blockType, std::string blockParams) { + auto& block_handlers = findBlockTypeHandlersMap(blockType); block_handlers[std::move(blockParams)] = detail::blockFactory; } #else template - requires std::is_constructible_v - void - addBlockType(std::string, std::string) { + requires std::is_constructible_v + void addBlockType(std::string, std::string) { // disables plugin system in favour of faster compile-times and when runtime or Python wrapping APIs are not requrired // e.g. for compile-time only flow-graphs or for CI runners } #endif + [[nodiscard]] std::span providedBlocks() const { return _blockTypes; } - [[nodiscard]] std::span - providedBlocks() const { - return _blockTypes; - } - - [[nodiscard]] std::unique_ptr - createBlock(std::string_view name, std::string_view type, property_map params) const { + [[nodiscard]] std::unique_ptr createBlock(std::string_view name, std::string_view type, property_map params) const { if (auto blockIt = _blockTypeHandlers.find(std::string(name)); blockIt != _blockTypeHandlers.end()) { if (auto handlerIt = blockIt->second.find(std::string(type)); handlerIt != blockIt->second.end()) { return handlerIt->second(std::move(params)); @@ -73,23 +66,16 @@ class BlockRegistry { return nullptr; } - [[nodiscard]] auto - knownBlocks() const { - return _blockTypes; - } + [[nodiscard]] auto knownBlocks() const { return _blockTypes; } - bool - isBlockKnown(std::string_view block) const { - return _blockTypeHandlers.find(std::string(block)) != _blockTypeHandlers.end(); - } + bool isBlockKnown(std::string_view block) const { return _blockTypeHandlers.find(std::string(block)) != _blockTypeHandlers.end(); } - auto - knownBlockParameterizations(std::string_view block) const { + auto knownBlockParameterizations(std::string_view block) const { std::vector result; if (auto it = _blockTypeHandlers.find(std::string(block)); it != _blockTypeHandlers.end()) { - const auto &map = it->second; + const auto& map = it->second; result.reserve(map.size()); - for (const auto &[key, _] : map) { + for (const auto& [key, _] : map) { result.push_back(key); } } @@ -97,16 +83,14 @@ class BlockRegistry { return result; } - friend inline BlockRegistry & - globalBlockRegistry(); + friend inline BlockRegistry& globalBlockRegistry(); }; -inline BlockRegistry & -globalBlockRegistry() { +inline BlockRegistry& globalBlockRegistry() { static BlockRegistry s_instance; return s_instance; } } // namespace gr -#endif // BLOCK_REGISTRY_HPP +#endif // GNURADIO_BLOCK_REGISTRY_HPP diff --git a/core/include/gnuradio-4.0/Graph.hpp b/core/include/gnuradio-4.0/Graph.hpp index 7817cfd3..b95bf0a0 100644 --- a/core/include/gnuradio-4.0/Graph.hpp +++ b/core/include/gnuradio-4.0/Graph.hpp @@ -2,12 +2,14 @@ #define GNURADIO_GRAPH_HPP #include +#include #include #include -#include +#include #include -#include #include +#include +#include #include #include @@ -33,478 +35,20 @@ namespace gr { -class BlockModel { -protected: - struct NamedPortCollection { - std::string name; - std::vector ports; - }; - - using DynamicPortOrCollection = std::variant; - using DynamicPorts = std::vector; - bool _dynamicPortsLoaded = false; - std::function _dynamicPortsLoader; - DynamicPorts _dynamicInputPorts; - DynamicPorts _dynamicOutputPorts; - - BlockModel() = default; - -public: - BlockModel(const BlockModel &) = delete; - BlockModel & - operator=(const BlockModel &) - = delete; - BlockModel(BlockModel &&other) = delete; - BlockModel & - operator=(BlockModel &&other) - = delete; - - void - initDynamicPorts() const { - if (!_dynamicPortsLoaded) _dynamicPortsLoader(); - } - - MsgPortInNamed<"__Builtin"> *msgIn; - MsgPortOutNamed<"__Builtin"> *msgOut; - - [[nodiscard]] gr::DynamicPort & - dynamicInputPort(std::size_t index, std::size_t subIndex = meta::invalid_index) { - initDynamicPorts(); - if (auto *portCollection = std::get_if(&_dynamicInputPorts.at(index))) { - if (subIndex == meta::invalid_index) { - throw std::invalid_argument("Need to specify the index in the port collection"); - } else { - return portCollection->ports[subIndex]; - } - - } else if (auto *port = std::get_if(&_dynamicInputPorts.at(index))) { - if (subIndex == meta::invalid_index) { - return *port; - } else { - throw std::invalid_argument("Specified sub-index for a normal port"); - } - } - - throw std::logic_error("Variant construction failed"); - } - - [[nodiscard]] gr::DynamicPort & - dynamicOutputPort(std::size_t index, std::size_t subIndex = meta::invalid_index) { - initDynamicPorts(); - if (auto *portCollection = std::get_if(&_dynamicOutputPorts.at(index))) { - if (subIndex == meta::invalid_index) { - throw std::invalid_argument("Need to specify the index in the port collection"); - } else { - return portCollection->ports[subIndex]; - } - - } else if (auto *port = std::get_if(&_dynamicOutputPorts.at(index))) { - if (subIndex == meta::invalid_index) { - return *port; - } else { - throw std::invalid_argument("Specified sub-index for a normal port"); - } - } - - throw std::logic_error("Variant construction failed"); - } - - [[nodiscard]] std::size_t - dynamicInputPortsSize(std::size_t parentIndex = meta::invalid_index) const { - initDynamicPorts(); - if (parentIndex == meta::invalid_index) { - return _dynamicInputPorts.size(); - } else { - if (auto *portCollection = std::get_if(&_dynamicInputPorts.at(parentIndex))) { - return portCollection->ports.size(); - } else { - return meta::invalid_index; - } - } - } - - [[nodiscard]] std::size_t - dynamicOutputPortsSize(std::size_t parentIndex = meta::invalid_index) const { - initDynamicPorts(); - if (parentIndex == meta::invalid_index) { - return _dynamicOutputPorts.size(); - } else { - if (auto *portCollection = std::get_if(&_dynamicOutputPorts.at(parentIndex))) { - return portCollection->ports.size(); - } else { - return meta::invalid_index; - } - } - } - - std::size_t - dynamicInputPortIndex(std::string_view name) const { - initDynamicPorts(); - for (std::size_t i = 0; i < _dynamicInputPorts.size(); ++i) { - if (auto *portCollection = std::get_if(&_dynamicInputPorts.at(i))) { - if (portCollection->name == name) { - return i; - } - } else if (auto *port = std::get_if(&_dynamicInputPorts.at(i))) { - if (port->name == name) { - return i; - } - } - } - - throw std::invalid_argument(fmt::format("Port {} does not exist", name)); - } - - std::size_t - dynamicOutputPortIndex(std::string_view name) const { - initDynamicPorts(); - for (std::size_t i = 0; i < _dynamicOutputPorts.size(); ++i) { - if (auto *portCollection = std::get_if(&_dynamicOutputPorts.at(i))) { - if (portCollection->name == name) { - return i; - } - } else if (auto *port = std::get_if(&_dynamicOutputPorts.at(i))) { - if (port->name == name) { - return i; - } - } - } - - throw std::invalid_argument(fmt::format("Port {} does not exist", name)); - } - - virtual ~ - BlockModel() - = default; - - /** - * @brief to be called by scheduler->graph to initialise block - */ - virtual void - init(std::shared_ptr progress, std::shared_ptr ioThreadPool) - = 0; - - /** - * @brief returns scheduling hint that invoking the work(...) function may block on IO or system-calls - */ - [[nodiscard]] virtual constexpr bool - isBlocking() const noexcept - = 0; - - /** - * @brief change Block state (N.B. IDLE, INITIALISED, RUNNING, REQUESTED_STOP, REQUESTED_PAUSE, STOPPED, PAUSED, ERROR) - * See enum description for details. - */ - [[nodiscard]] virtual std::expected - changeState(lifecycle::State newState) noexcept = 0; - - /** - * @brief Block state (N.B. IDLE, INITIALISED, RUNNING, REQUESTED_STOP, REQUESTED_PAUSE, STOPPED, PAUSED, ERROR) - * See enum description for details. - */ - [[nodiscard]] virtual lifecycle::State - state() const noexcept - = 0; - - /** - * @brief number of available readable samples at the block's input ports - */ - [[nodiscard]] virtual constexpr std::size_t - availableInputSamples(std::vector &) const noexcept - = 0; - - /** - * @brief number of available writable samples at the block's output ports - */ - [[nodiscard]] virtual constexpr std::size_t - availableOutputSamples(std::vector &) const noexcept - = 0; - - /** - * @brief user defined name - */ - [[nodiscard]] virtual std::string_view - name() const - = 0; - - /** - * @brief the type of the node as a string - */ - [[nodiscard]] virtual std::string_view - typeName() const - = 0; - - /** - * @brief user-defined name - * N.B. may not be unique -> ::uniqueName - */ - virtual void - setName(std::string name) noexcept - = 0; - - /** - * @brief used to store non-graph-processing information like UI block position etc. - */ - [[nodiscard]] virtual property_map & - metaInformation() noexcept - = 0; - - [[nodiscard]] virtual const property_map & - metaInformation() const - = 0; - - /** - * @brief process-wide unique name - * N.B. can be used to disambiguate in case user provided the same 'name()' for several blocks. - */ - [[nodiscard]] virtual std::string_view - uniqueName() const - = 0; - - [[nodiscard]] virtual SettingsBase & - settings() const - = 0; - - [[nodiscard]] virtual work::Result - work(std::size_t requested_work) - = 0; - - [[nodiscard]] virtual work::Status - draw() = 0; - - virtual void - processScheduledMessages() - = 0; - - virtual UICategory - uiCategory() const { - return UICategory::None; - } - - [[nodiscard]] virtual void * - raw() = 0; -}; - -namespace detail { -template -constexpr bool contains_type = (std::is_same_v || ...); -} - -template - requires std::is_constructible_v -class BlockWrapper : public BlockModel { -private: - static_assert(std::is_same_v>); - T _block; - std::string _type_name = gr::meta::type_name(); - - [[nodiscard]] constexpr const auto & - blockRef() const noexcept { - if constexpr (requires { *_block; }) { - return *_block; - } else { - return _block; - } - } - - [[nodiscard]] constexpr auto & - blockRef() noexcept { - if constexpr (requires { *_block; }) { - return *_block; - } else { - return _block; - } - } - - void - initMessagePorts() { - msgIn = std::addressof(_block.msgIn); - msgOut = std::addressof(_block.msgOut); - } - - template - constexpr static auto & - processPort(auto &where, TPort &port) noexcept { - where.push_back(gr::DynamicPort(port, DynamicPort::non_owned_reference_tag{})); - return where.back(); - } - - void - dynamicPortLoader() { - if (_dynamicPortsLoaded) return; - - auto registerPort = [this](DynamicPorts &where, [[maybe_unused]] Direction direction, [[maybe_unused]] ConstIndex index, - CurrentPortType &&) noexcept { - if constexpr (traits::port::is_port_v) { - using PortDescriptor = typename CurrentPortType::ReflDescriptor; - if constexpr (refl::trait::is_descriptor_v) { - auto &port = (blockRef().*(PortDescriptor::pointer)); - if (port.name.empty()) { - port.name = refl::descriptor::get_name(PortDescriptor()).data; - } - processPort(where, port); - } else { - // We can also have ports defined as template parameters - if constexpr (Direction::value == PortDirection::INPUT) { - processPort(where, gr::inputPort(&blockRef())); - } else { - processPort(where, gr::outputPort(&blockRef())); - } - } - } else { - using PortCollectionDescriptor = typename CurrentPortType::value_type::ReflDescriptor; - if constexpr (refl::trait::is_descriptor_v) { - auto &collection = (blockRef().*(PortCollectionDescriptor::pointer)); - NamedPortCollection result; - result.name = refl::descriptor::get_name(PortCollectionDescriptor()).data; - for (auto &port : collection) { - processPort(result.ports, port); - } - where.push_back(std::move(result)); - } else { - static_assert(meta::always_false, "Port collections are only supported for member variables"); - } - } - }; - - using Node = std::remove_cvref_t; - traits::block::all_input_ports::for_each(registerPort, _dynamicInputPorts, std::integral_constant{}); - traits::block::all_output_ports::for_each(registerPort, _dynamicOutputPorts, std::integral_constant{}); - - _dynamicPortsLoaded = true; - } - -public: - BlockWrapper(const BlockWrapper &other) = delete; - BlockWrapper(BlockWrapper &&other) = delete; - BlockWrapper & - operator=(const BlockWrapper &other) - = delete; - BlockWrapper & - operator=(BlockWrapper &&other) - = delete; - - ~ - BlockWrapper() override - = default; - - explicit - BlockWrapper(property_map initParameter = {}) - : _block(std::move(initParameter)) { - initMessagePorts(); - _dynamicPortsLoader = std::bind(&BlockWrapper::dynamicPortLoader, this); - } - - void - init(std::shared_ptr progress, std::shared_ptr ioThreadPool) override { - return blockRef().init(progress, ioThreadPool); - } - - [[nodiscard]] constexpr work::Result - work(std::size_t requested_work = std::numeric_limits::max()) override { - return blockRef().work(requested_work); - } - - constexpr work::Status - draw() override { - if constexpr (requires { blockRef().draw(); }) { - return blockRef().draw(); - } - return work::Status::ERROR; - } - - UICategory - uiCategory() const override { - return T::DrawableControl::kCategory; - } - - void - processScheduledMessages() override { - return blockRef().processScheduledMessages(); - } - - [[nodiscard]] constexpr bool - isBlocking() const noexcept override { - return blockRef().isBlocking(); - } - - [[nodiscard]] std::expected - changeState(lifecycle::State newState) noexcept override { - return blockRef().changeStateTo(newState); - } - - [[nodiscard]] lifecycle::State - state() const noexcept override { - return blockRef().state(); - } - - [[nodiscard]] constexpr std::size_t - availableInputSamples(std::vector &data) const noexcept override { - return blockRef().availableInputSamples(data); - } - - [[nodiscard]] constexpr std::size_t - availableOutputSamples(std::vector &data) const noexcept override { - return blockRef().availableOutputSamples(data); - } - - [[nodiscard]] std::string_view - name() const override { - return blockRef().name; - } - - void - setName(std::string name) noexcept override { - blockRef().name = std::move(name); - } - - [[nodiscard]] std::string_view - typeName() const override { - return _type_name; - } - - [[nodiscard]] property_map & - metaInformation() noexcept override { - return blockRef().meta_information; - } - - [[nodiscard]] const property_map & - metaInformation() const override { - return blockRef().meta_information; - } - - [[nodiscard]] std::string_view - uniqueName() const override { - return blockRef().unique_name; - } - - [[nodiscard]] SettingsBase & - settings() const override { - return blockRef().settings(); - } - - [[nodiscard]] void * - raw() override { - return std::addressof(blockRef()); - } -}; - template struct PortIndexDefinition { T topLevel; std::size_t subIndex; - constexpr - PortIndexDefinition(T _topLevel, std::size_t _subIndex = meta::invalid_index) - : topLevel(std::move(_topLevel)), subIndex(_subIndex) {} + constexpr PortIndexDefinition(T _topLevel, std::size_t _subIndex = meta::invalid_index) : topLevel(std::move(_topLevel)), subIndex(_subIndex) {} }; class Edge { public: // TODO: consider making this private and to use accessors (that can be safely used by users) using PortDirection::INPUT; using PortDirection::OUTPUT; - BlockModel *_sourceBlock; - BlockModel *_destinationBlock; + BlockModel* _sourceBlock; + BlockModel* _destinationBlock; PortIndexDefinition _sourcePortDefinition; PortIndexDefinition _destinationPortDefinition; std::size_t _minBufferSize; @@ -515,108 +59,84 @@ class Edge { public: Edge() = delete; - Edge(const Edge &) = delete; + Edge(const Edge&) = delete; - Edge & - operator=(const Edge &) - = delete; + Edge& operator=(const Edge&) = delete; - Edge(Edge &&) noexcept = default; + Edge(Edge&&) noexcept = default; - Edge & - operator=(Edge &&) noexcept - = default; + Edge& operator=(Edge&&) noexcept = default; - Edge(BlockModel *sourceBlock, PortIndexDefinition sourcePortDefinition, BlockModel *destinationBlock, PortIndexDefinition destinationPortDefinition, - std::size_t minBufferSize, std::int32_t weight, std::string_view name) - : _sourceBlock(sourceBlock) - , _destinationBlock(destinationBlock) - , _sourcePortDefinition(sourcePortDefinition) - , _destinationPortDefinition(destinationPortDefinition) - , _minBufferSize(minBufferSize) - , _weight(weight) - , _name(name) {} + Edge(BlockModel* sourceBlock, PortIndexDefinition sourcePortDefinition, BlockModel* destinationBlock, PortIndexDefinition destinationPortDefinition, std::size_t minBufferSize, std::int32_t weight, std::string_view name) : _sourceBlock(sourceBlock), _destinationBlock(destinationBlock), _sourcePortDefinition(sourcePortDefinition), _destinationPortDefinition(destinationPortDefinition), _minBufferSize(minBufferSize), _weight(weight), _name(name) {} - [[nodiscard]] constexpr const BlockModel & - sourceBlock() const noexcept { - return *_sourceBlock; - } + [[nodiscard]] constexpr const BlockModel& sourceBlock() const noexcept { return *_sourceBlock; } - [[nodiscard]] constexpr const BlockModel & - destinationBlock() const noexcept { - return *_destinationBlock; - } + [[nodiscard]] constexpr const BlockModel& destinationBlock() const noexcept { return *_destinationBlock; } - [[nodiscard]] constexpr PortIndexDefinition - sourcePortDefinition() const noexcept { - return _sourcePortDefinition; - } + [[nodiscard]] constexpr PortIndexDefinition sourcePortDefinition() const noexcept { return _sourcePortDefinition; } - [[nodiscard]] constexpr PortIndexDefinition - destinationPortDefinition() const noexcept { - return _destinationPortDefinition; - } + [[nodiscard]] constexpr PortIndexDefinition destinationPortDefinition() const noexcept { return _destinationPortDefinition; } - [[nodiscard]] constexpr std::string_view - name() const noexcept { - return _name; - } + [[nodiscard]] constexpr std::string_view name() const noexcept { return _name; } - [[nodiscard]] constexpr std::size_t - minBufferSize() const noexcept { - return _minBufferSize; - } + [[nodiscard]] constexpr std::size_t minBufferSize() const noexcept { return _minBufferSize; } - [[nodiscard]] constexpr std::int32_t - weight() const noexcept { - return _weight; - } + [[nodiscard]] constexpr std::int32_t weight() const noexcept { return _weight; } - [[nodiscard]] constexpr bool - is_connected() const noexcept { - return _connected; - } + [[nodiscard]] constexpr bool is_connected() const noexcept { return _connected; } }; +namespace graph::property { +inline static const char* kEmplaceBlock = "EmplaceBlock"; +inline static const char* kRemoveBlock = "RemoveBlock"; +inline static const char* kReplaceBlock = "ReplaceBlock"; + +inline static const char* kBlockEmplaced = "BlockEmplaced"; +inline static const char* kBlockRemoved = "BlockRemoved"; +inline static const char* kBlockReplaced = "BlockReplaced"; + +inline static const char* kEmplaceEdge = "EmplaceEdge"; +inline static const char* kRemoveEdge = "RemoveEdge"; + +inline static const char* kEdgeEmplaced = "EdgeEmplaced"; +inline static const char* kEdgeRemoved = "EdgeRemoved"; + +} // namespace graph::property + class Graph : public gr::Block { alignas(hardware_destructive_interference_size) std::shared_ptr progress = std::make_shared(); - alignas(hardware_destructive_interference_size) std::shared_ptr ioThreadPool = std::make_shared( - "graph_thread_pool", gr::thread_pool::TaskType::IO_BOUND, 2UZ, std::numeric_limits::max()); + alignas(hardware_destructive_interference_size) std::shared_ptr ioThreadPool = std::make_shared("graph_thread_pool", gr::thread_pool::TaskType::IO_BOUND, 2UZ, std::numeric_limits::max()); private: - std::vector> _connectionDefinitions; - std::vector _edges; + std::vector> _connectionDefinitions; + std::vector _edges; std::vector> _blocks; template - std::unique_ptr & - findBlock(TBlock &what) { + std::unique_ptr& findBlock(TBlock& what) { static_assert(!std::is_pointer_v>); auto it = [&, this] { if constexpr (std::is_same_v) { - return std::find_if(_blocks.begin(), _blocks.end(), [&](const auto &block) { return block.get() == &what; }); + return std::find_if(_blocks.begin(), _blocks.end(), [&](const auto& block) { return block.get() == &what; }); } else { - return std::find_if(_blocks.begin(), _blocks.end(), [&](const auto &block) { return block->raw() == &what; }); + return std::find_if(_blocks.begin(), _blocks.end(), [&](const auto& block) { return block->raw() == &what; }); } }(); - if (it == _blocks.end()) throw std::runtime_error(fmt::format("No such block in this graph")); + if (it == _blocks.end()) { + throw std::runtime_error(fmt::format("No such block in this graph")); + } return *it; } - template - [[nodiscard]] ConnectionResult - connectImpl(Source &sourceNodeRaw, SourcePort &source_port_or_collection, Destination &destinationNodeRaw, DestinationPort &destinationPort_or_collection, std::size_t minBufferSize = 65536, - std::int32_t weight = 0, std::string_view edgeName = "unnamed edge") { - if (!std::any_of(_blocks.begin(), _blocks.end(), [&](const auto ®isteredNode) { return registeredNode->raw() == std::addressof(sourceNodeRaw); }) - || !std::any_of(_blocks.begin(), _blocks.end(), [&](const auto ®isteredNode) { return registeredNode->raw() == std::addressof(destinationNodeRaw); })) { - throw std::runtime_error( - fmt::format("Can not connect nodes that are not registered first:\n {}:{} -> {}:{}\n", sourceNodeRaw.name, sourcePortIndex, destinationNodeRaw.name, destinationPortIndex)); + template + [[nodiscard]] ConnectionResult connectImpl(Source& sourceNodeRaw, SourcePort& source_port_or_collection, Destination& destinationNodeRaw, DestinationPort& destinationPort_or_collection, std::size_t minBufferSize = 65536, std::int32_t weight = 0, std::string_view edgeName = "unnamed edge") { + if (!std::any_of(_blocks.begin(), _blocks.end(), [&](const auto& registeredNode) { return registeredNode->raw() == std::addressof(sourceNodeRaw); }) || !std::any_of(_blocks.begin(), _blocks.end(), [&](const auto& registeredNode) { return registeredNode->raw() == std::addressof(destinationNodeRaw); })) { + throw std::runtime_error(fmt::format("Can not connect nodes that are not registered first:\n {}:{} -> {}:{}\n", sourceNodeRaw.name, sourcePortIndex, destinationNodeRaw.name, destinationPortIndex)); } - auto *sourcePort = [&] { + auto* sourcePort = [&] { if constexpr (traits::port::is_port_v) { return &source_port_or_collection; } else { @@ -624,7 +144,7 @@ class Graph : public gr::Block { } }(); - auto *destinationPort = [&] { + auto* destinationPort = [&] { if constexpr (traits::port::is_port_v) { return &destinationPort_or_collection; } else { @@ -633,17 +153,15 @@ class Graph : public gr::Block { }(); if constexpr (!std::is_same_v::value_type, typename std::remove_pointer_t::value_type>) { - meta::print_types, typename std::remove_pointer_t::value_type, - typename std::remove_pointer_t::value_type>{}; + meta::print_types, typename std::remove_pointer_t::value_type, typename std::remove_pointer_t::value_type>{}; } auto result = sourcePort->connect(*destinationPort); if (result == ConnectionResult::SUCCESS) { - auto *sourceNode = findBlock(sourceNodeRaw).get(); - auto *destinationNode = findBlock(destinationNodeRaw).get(); + auto* sourceNode = findBlock(sourceNodeRaw).get(); + auto* destinationNode = findBlock(destinationNodeRaw).get(); // TODO: Rethink edge definition, indices, message port -1 etc. - _edges.emplace_back(sourceNode, PortIndexDefinition{ sourcePortIndex, sourcePortSubIndex }, destinationNode, - PortIndexDefinition{ destinationPortIndex, destinationPortSubIndex }, minBufferSize, weight, edgeName); + _edges.emplace_back(sourceNode, PortIndexDefinition{sourcePortIndex, sourcePortSubIndex}, destinationNode, PortIndexDefinition{destinationPortIndex, destinationPortSubIndex}, minBufferSize, weight, edgeName); } return result; @@ -654,31 +172,25 @@ class Graph : public gr::Block { // connect(source) and .to(destination) template struct SourceConnector { - Graph &self; - Source &source; - Port &port; + Graph& self; + Source& source; + Port& port; - SourceConnector(Graph &_self, Source &_source, Port &_port) : self(_self), source(_source), port(_port) {} + SourceConnector(Graph& _self, Source& _source, Port& _port) : self(_self), source(_source), port(_port) {} - static_assert(std::is_same_v || traits::port::is_port_v || (sourcePortSubIndex != meta::invalid_index), - "When we have a collection of ports, we need to have an index to access the desired port in the collection"); + static_assert(std::is_same_v || traits::port::is_port_v || (sourcePortSubIndex != meta::invalid_index), "When we have a collection of ports, we need to have an index to access the desired port in the collection"); private: template - [[nodiscard]] constexpr ConnectionResult - to(Destination &destination, DestinationPort &destinationPort) { + [[nodiscard]] constexpr ConnectionResult to(Destination& destination, DestinationPort& destinationPort) { // Not overly efficient as the block doesn't know the graph it belongs to, // but this is not a frequent operation and the check is important. - auto is_block_known = [this](const auto &query_block) { - return std::any_of(self._blocks.cbegin(), self._blocks.cend(), [&query_block](const auto &known_block) { return known_block->raw() == std::addressof(query_block); }); - }; + auto is_block_known = [this](const auto& query_block) { return std::any_of(self._blocks.cbegin(), self._blocks.cend(), [&query_block](const auto& known_block) { return known_block->raw() == std::addressof(query_block); }); }; if (!is_block_known(source) || !is_block_known(destination)) { fmt::print("Source {} and/or destination {} do not belong to this graph\n", source.name, destination.name); return ConnectionResult::FAILED; } - self._connectionDefinitions.push_back([src = &source, source_port = &port, destination = &destination, destinationPort = &destinationPort](Graph &graph) { - return graph.connectImpl(*src, *source_port, *destination, *destinationPort); - }); + self._connectionDefinitions.push_back([src = &source, source_port = &port, destination = &destination, destinationPort = &destinationPort](Graph& graph) { return graph.connectImpl(*src, *source_port, *destination, *destinationPort); }); return ConnectionResult::SUCCESS; } @@ -686,21 +198,18 @@ class Graph : public gr::Block { // connect using the port index template - [[nodiscard]] auto - to_internal(Destination &destination) { - auto &destinationPort = inputPort(&destination); + [[nodiscard]] auto to_internal(Destination& destination) { + auto& destinationPort = inputPort(&destination); return to, destinationPortIndex, destinationPortSubIndex>(destination, destinationPort); } template - [[nodiscard, deprecated("For internal use only, the one with the port name should be used")]] auto - to(Destination &destination) { + [[nodiscard, deprecated("For internal use only, the one with the port name should be used")]] auto to(Destination& destination) { return to_internal(destination); } template - [[nodiscard]] auto - to(Destination &destination) { + [[nodiscard]] auto to(Destination& destination) { if constexpr (destinationPortIndex == gr::meta::default_message_port_index) { return to(destination, destination.msgIn); @@ -712,111 +221,214 @@ class Graph : public gr::Block { // connect using the port name template - [[nodiscard]] constexpr auto - to(Destination &destination) { + [[nodiscard]] constexpr auto to(Destination& destination) { using destination_input_ports = typename traits::block::all_input_ports; constexpr std::size_t destinationPortIndex = meta::indexForName(); if constexpr (destinationPortIndex == meta::invalid_index) { - meta::print_types, Destination, meta::message_type, - meta::message_type<"These are the known names:">, traits::block::all_input_port_names, meta::message_type<"Full ports info:">, destination_input_ports> - port_not_found_error{}; + meta::print_types, Destination, meta::message_type, meta::message_type<"These are the known names:">, traits::block::all_input_port_names, meta::message_type<"Full ports info:">, destination_input_ports> port_not_found_error{}; } return to_internal(destination); } template - [[nodiscard]] constexpr auto - to(Destination &destination) { + [[nodiscard]] constexpr auto to(Destination& destination) { return to(destination); } - SourceConnector(const SourceConnector &) = delete; - SourceConnector(SourceConnector &&) = delete; - SourceConnector & - operator=(const SourceConnector &) - = delete; - SourceConnector & - operator=(SourceConnector &&) - = delete; + SourceConnector(const SourceConnector&) = delete; + SourceConnector(SourceConnector&&) = delete; + SourceConnector& operator=(const SourceConnector&) = delete; + SourceConnector& operator=(SourceConnector&&) = delete; }; public: - Graph(Graph &) = delete; - Graph(Graph &&) = default; - Graph() = default; - Graph & - operator=(Graph &) - = delete; - Graph & - operator=(Graph &&) - = delete; + Graph(Graph&) = delete; + Graph(Graph&&) = default; + Graph& operator=(Graph&) = delete; + Graph& operator=(Graph&&) = delete; + + Graph() { + _blocks.reserve(100); + propertyCallbacks[graph::property::kEmplaceBlock] = &Graph::propertyCallbackEmplaceBlock; + propertyCallbacks[graph::property::kRemoveBlock] = &Graph::propertyCallbackRemoveBlock; + propertyCallbacks[graph::property::kReplaceBlock] = &Graph::propertyCallbackReplaceBlock; + propertyCallbacks[graph::property::kEmplaceEdge] = &Graph::propertyCallbackEmplaceEdge; + propertyCallbacks[graph::property::kRemoveEdge] = &Graph::propertyCallbackRemoveEdge; + } /** * @return a list of all blocks contained in this graph * N.B. some 'blocks' may be (sub-)graphs themselves */ - [[nodiscard]] std::span> - blocks() noexcept { - return { _blocks }; - } + [[nodiscard]] std::span> blocks() noexcept { return {_blocks}; } /** * @return a list of all edges in this graph connecting blocks */ - [[nodiscard]] std::span - edges() noexcept { - return { _edges }; - } + [[nodiscard]] std::span edges() noexcept { return {_edges}; } - BlockModel & - addBlock(std::unique_ptr block) { - auto &new_block_ref = _blocks.emplace_back(std::move(block)); + BlockModel& addBlock(std::unique_ptr block) { + auto& new_block_ref = _blocks.emplace_back(std::move(block)); new_block_ref->init(progress, ioThreadPool); // TODO: Should we connectChildMessagePorts for these blocks as well? return *new_block_ref.get(); } template - requires std::is_constructible_v - auto & - emplaceBlock(Args &&...args) { // TODO for review: do we still need this factory method or allow only pmt-map-type constructors (see below) + requires std::is_constructible_v + auto& emplaceBlock(Args&&... args) { // TODO for review: do we still need this factory method or allow only pmt-map-type constructors (see below) static_assert(std::is_same_v>); - auto &new_block_ref = _blocks.emplace_back(std::make_unique>(std::forward(args)...)); - auto raw_ref = static_cast(new_block_ref->raw()); + auto& new_block_ref = _blocks.emplace_back(std::make_unique>(std::forward(args)...)); + auto raw_ref = static_cast(new_block_ref->raw()); raw_ref->init(progress, ioThreadPool); return *raw_ref; } template - requires std::is_constructible_v - auto & - emplaceBlock(property_map initialSettings) { + requires std::is_constructible_v + auto& emplaceBlock(property_map initialSettings) { static_assert(std::is_same_v>); - auto &new_block_ref = _blocks.emplace_back(std::make_unique>(std::move(initialSettings))); - auto raw_ref = static_cast(new_block_ref->raw()); + auto& new_block_ref = _blocks.emplace_back(std::make_unique>(std::move(initialSettings))); + auto raw_ref = static_cast(new_block_ref->raw()); const auto failed = raw_ref->settings().set(initialSettings); raw_ref->init(progress, ioThreadPool); return *raw_ref; } + auto& emplaceBlock(std::string_view type, std::string_view parameters, property_map initialSettings, PluginLoader& loader = gr::globalPluginLoader()) { + auto block_load = loader.instantiate(type, parameters, initialSettings); + if (!block_load) { + throw gr::exception(fmt::format("Can not create block {}<{}>", type, parameters)); + } + return addBlock(std::move(block_load)); + } + + std::optional propertyCallbackEmplaceBlock(std::string_view propertyName, Message message) { + const auto& data = message.data.value(); + const std::string& type = std::get(data.at("type"s)); + const std::string& parameters = std::get(data.at("parameters"s)); + const property_map& properties = std::get(data.at("properties"s)); + + auto& newBlock = emplaceBlock(type, parameters, properties); + + std::optional result = gr::Message{}; + result->endpoint = graph::property::kBlockEmplaced; + result->data = property_map{{"uniqueName"s, std::string(newBlock.uniqueName())}}; + + return result; + } + + std::optional propertyCallbackRemoveBlock(std::string_view propertyName, Message message) { + const auto& data = message.data.value(); + const std::string& uniqueName = std::get(data.at("uniqueName"s)); + auto it = std::ranges::find_if(_blocks, [&uniqueName](const auto& block) { return block->uniqueName() == uniqueName; }); + + if (it == _blocks.end()) { + throw gr::exception(fmt::format("Block {} was not found in {}", uniqueName, this->unique_name)); + } + + _blocks.erase(it); + message.endpoint = graph::property::kBlockRemoved; + + return {message}; + } + + std::optional propertyCallbackReplaceBlock(std::string_view propertyName, Message message) { + const auto& data = message.data.value(); + const std::string& uniqueName = std::get(data.at("uniqueName"s)); + const std::string& type = std::get(data.at("type"s)); + const std::string& parameters = std::get(data.at("parameters"s)); + const property_map& properties = std::get(data.at("properties"s)); + + auto it = std::ranges::find_if(_blocks, [&uniqueName](const auto& block) { return block->uniqueName() == uniqueName; }); + if (it == _blocks.end()) { + throw gr::exception(fmt::format("Block {} was not found in {}", uniqueName, this->unique_name)); + } + + auto block_load = gr::globalPluginLoader().instantiate(type, parameters, properties); + if (!block_load) { + throw gr::exception(fmt::format("Can not create block {}<{}>", type, parameters)); + } + + _blocks.erase(it); + const auto newName = block_load->uniqueName(); + addBlock(std::move(block_load)); + + std::optional result = gr::Message{}; + result->endpoint = graph::property::kBlockEmplaced; + result->data = property_map{{"uniqueName"s, std::string(newName)}}; + + return result; + } + + std::optional propertyCallbackEmplaceEdge(std::string_view propertyName, Message message) { + const auto& data = message.data.value(); + const std::string& sourceBlock = std::get(data.at("sourceBlock"s)); + const std::string& sourcePort = std::get(data.at("sourcePort"s)); + const std::string& destinationBlock = std::get(data.at("destinationBlock"s)); + const std::string& destinationPort = std::get(data.at("destinationPort"s)); + + auto sourceBlockIt = std::ranges::find_if(_blocks, [&sourceBlock](const auto& block) { return block->uniqueName() == sourceBlock; }); + if (sourceBlockIt == _blocks.end()) { + throw gr::exception(fmt::format("Block {} was not found in {}", sourceBlock, this->unique_name)); + } + + auto destinationBlockIt = std::ranges::find_if(_blocks, [&destinationBlock](const auto& block) { return block->uniqueName() == destinationBlock; }); + if (destinationBlockIt == _blocks.end()) { + throw gr::exception(fmt::format("Block {} was not found in {}", destinationBlock, this->unique_name)); + } + + auto& sourcePortRef = (*sourceBlockIt)->dynamicOutputPort(sourcePort); + auto& destinationPortRef = (*destinationBlockIt)->dynamicInputPort(destinationPort); + + if (sourcePortRef.defaultValue().type() != destinationPortRef.defaultValue().type()) { + throw gr::exception(fmt::format("{}.{} can not be connected to {}.{} -- different types", sourceBlock, sourcePort, destinationBlock, destinationPort)); + } + + auto connectionResult = sourcePortRef.connect(destinationPortRef); + + if (connectionResult != ConnectionResult::SUCCESS) { + throw gr::exception(fmt::format("{}.{} can not be connected to {}.{}", sourceBlock, sourcePort, destinationBlock, destinationPort)); + } + + // _edges.emplace_back(sourceBlock, sourcePortDefinition, destinationBlock, destinationPortDefinition, minBufferSize, weight, edgeName); + + message.endpoint = graph::property::kEdgeEmplaced; + return message; + } + + std::optional propertyCallbackRemoveEdge(std::string_view propertyName, Message message) { + const auto& data = message.data.value(); + const std::string& sourceBlock = std::get(data.at("sourceBlock"s)); + const std::string& sourcePort = std::get(data.at("sourcePort"s)); + + auto sourceBlockIt = std::ranges::find_if(_blocks, [&sourceBlock](const auto& block) { return block->uniqueName() == sourceBlock; }); + if (sourceBlockIt == _blocks.end()) { + throw gr::exception(fmt::format("Block {} was not found in {}", sourceBlock, this->unique_name)); + } + + auto& sourcePortRef = (*sourceBlockIt)->dynamicOutputPort(sourcePort); + + sourcePortRef.disconnect(); + message.endpoint = graph::property::kEdgeRemoved; + return message; + } + // connect using the port index template - [[nodiscard]] auto - connect_internal(Source &source) { - auto &port_or_collection = outputPort(&source); + [[nodiscard]] auto connect_internal(Source& source) { + auto& port_or_collection = outputPort(&source); return SourceConnector, sourcePortIndex, sourcePortSubIndex>(*this, source, port_or_collection); } template - [[nodiscard, deprecated("The connect with the port name should be used")]] auto - connect(Source &source) { + [[nodiscard, deprecated("The connect with the port name should be used")]] auto connect(Source& source) { return connect_internal(source); } template - [[nodiscard]] auto - connect(Source &source) { + [[nodiscard]] auto connect(Source& source) { if constexpr (sourcePortIndex == meta::default_message_port_index) { return SourceConnector(*this, source, source.msgOut); } else { @@ -827,63 +439,51 @@ class Graph : public gr::Block { // connect using the port name template - [[nodiscard]] auto - connect(Source &source) { + [[nodiscard]] auto connect(Source& source) { using source_output_ports = typename traits::block::all_output_ports; constexpr std::size_t sourcePortIndex = meta::indexForName(); if constexpr (sourcePortIndex == meta::invalid_index) { - meta::print_types, Source, meta::message_type, - meta::message_type<"These are the known names:">, traits::block::all_output_port_names, meta::message_type<"Full ports info:">, source_output_ports> - port_not_found_error{}; + meta::print_types, Source, meta::message_type, meta::message_type<"These are the known names:">, traits::block::all_output_port_names, meta::message_type<"Full ports info:">, source_output_ports> port_not_found_error{}; } return connect_internal(source); } template - [[nodiscard]] auto - connect(Source &source) { + [[nodiscard]] auto connect(Source& source) { return connect(source); } // dynamic/runtime connections template - requires(!std::is_pointer_v> && !std::is_pointer_v>) - ConnectionResult - connect(Source &sourceBlockRaw, PortIndexDefinition sourcePortDefinition, Destination &destinationBlockRaw, PortIndexDefinition destinationPortDefinition, - std::size_t minBufferSize = 65536, std::int32_t weight = 0, std::string_view edgeName = "unnamed edge") { - auto result = findBlock(sourceBlockRaw) - ->dynamicOutputPort(sourcePortDefinition.topLevel, sourcePortDefinition.subIndex) - .connect(findBlock(destinationBlockRaw)->dynamicInputPort(destinationPortDefinition.topLevel, destinationPortDefinition.subIndex)); + requires(!std::is_pointer_v> && !std::is_pointer_v>) + ConnectionResult connect(Source& sourceBlockRaw, PortIndexDefinition sourcePortDefinition, Destination& destinationBlockRaw, PortIndexDefinition destinationPortDefinition, std::size_t minBufferSize = 65536, std::int32_t weight = 0, std::string_view edgeName = "unnamed edge") { + auto result = findBlock(sourceBlockRaw)->dynamicOutputPort(sourcePortDefinition.topLevel, sourcePortDefinition.subIndex).connect(findBlock(destinationBlockRaw)->dynamicInputPort(destinationPortDefinition.topLevel, destinationPortDefinition.subIndex)); if (result == ConnectionResult::SUCCESS) { - auto *sourceBlock = findBlock(sourceBlockRaw).get(); - auto *destinationBlock = findBlock(destinationBlockRaw).get(); + auto* sourceBlock = findBlock(sourceBlockRaw).get(); + auto* destinationBlock = findBlock(destinationBlockRaw).get(); _edges.emplace_back(sourceBlock, sourcePortDefinition, destinationBlock, destinationPortDefinition, minBufferSize, weight, edgeName); } return result; } template - requires(!std::is_pointer_v> && !std::is_pointer_v>) - ConnectionResult - connect(Source &sourceBlockRaw, PortIndexDefinition sourcePortDefinition, Destination &destinationBlockRaw, PortIndexDefinition destinationPortDefinition, - std::size_t minBufferSize = 65536, std::int32_t weight = 0, std::string_view edgeName = "unnamed edge") { + requires(!std::is_pointer_v> && !std::is_pointer_v>) + ConnectionResult connect(Source& sourceBlockRaw, PortIndexDefinition sourcePortDefinition, Destination& destinationBlockRaw, PortIndexDefinition destinationPortDefinition, std::size_t minBufferSize = 65536, std::int32_t weight = 0, std::string_view edgeName = "unnamed edge") { auto sourcePortIndex = this->findBlock(sourceBlockRaw)->dynamicOutputPortIndex(sourcePortDefinition.topLevel); auto destinationPortIndex = this->findBlock(destinationBlockRaw)->dynamicInputPortIndex(destinationPortDefinition.topLevel); - return connect(sourceBlockRaw, { sourcePortIndex, sourcePortDefinition.subIndex }, destinationBlockRaw, { destinationPortIndex, destinationPortDefinition.subIndex }, minBufferSize, weight, - edgeName); + return connect(sourceBlockRaw, {sourcePortIndex, sourcePortDefinition.subIndex}, destinationBlockRaw, {destinationPortIndex, destinationPortDefinition.subIndex}, minBufferSize, weight, edgeName); } + using Block::processMessages; + template - void - processMessages(MsgPortInNamed<"__FromChildren"> & /*port*/, std::span /*input*/) { + void processMessages(MsgPortInNamed<"__FromChildren">& /*port*/, std::span /*input*/) { static_assert(meta::always_false, "This is not called, children are processed in processScheduledMessages"); } - bool - performConnections() { - auto result = std::all_of(_connectionDefinitions.begin(), _connectionDefinitions.end(), - [this](auto &connection_definition) { return connection_definition(*this) == ConnectionResult::SUCCESS; }); + bool performConnections() { + auto result = std::all_of(_connectionDefinitions.begin(), _connectionDefinitions.end(), [this](auto& connection_definition) { return connection_definition(*this) == ConnectionResult::SUCCESS; }); if (result) { _connectionDefinitions.clear(); } @@ -891,15 +491,13 @@ class Graph : public gr::Block { } template // TODO: F must be constraint by a descriptive concept - void - forEachBlock(F &&f) const { - std::ranges::for_each(_blocks, [f](const auto &block_ptr) { std::invoke(f, *block_ptr.get()); }); + void forEachBlock(F&& f) const { + std::ranges::for_each(_blocks, [f](const auto& block_ptr) { std::invoke(f, *block_ptr.get()); }); } template // TODO: F must be constraint by a descriptive concept - void - forEachEdge(F &&f) const { - std::ranges::for_each(_edges, [f](const auto &edge) { std::invoke(f, edge); }); + void forEachEdge(F&& f) const { + std::ranges::for_each(_edges, [f](const auto& edge) { std::invoke(f, edge); }); } }; @@ -958,9 +556,7 @@ concept SinkBlockLike = traits::block::can_processOne and traits::block: static_assert(not SinkBlockLike); template -class MergedGraph - : public Block, meta::concat, meta::remove_at>>, - meta::concat>, typename traits::block::stream_output_ports>> { +class MergedGraph : public Block, meta::concat, meta::remove_at>>, meta::concat>, typename traits::block::stream_output_ports>> { static std::atomic_size_t _unique_id_counter; public: @@ -969,8 +565,7 @@ class MergedGraph private: // copy-paste from above, keep in sync - using base = Block, meta::concat, meta::remove_at>>, - meta::concat>, typename traits::block::stream_output_ports>>; + using base = Block, meta::concat, meta::remove_at>>, meta::concat>, typename traits::block::stream_output_ports>>; Left left; Right right; @@ -982,8 +577,7 @@ class MergedGraph friend class MergedGraph; // returns the minimum of all internal max_samples port template parameters - static constexpr std::size_t - merged_work_chunk_size() noexcept { + static constexpr std::size_t merged_work_chunk_size() noexcept { constexpr std::size_t left_size = []() { if constexpr (requires { { Left::merged_work_chunk_size() } -> std::same_as; @@ -1002,27 +596,21 @@ class MergedGraph return std::dynamic_extent; } }(); - return std::min({ traits::block::stream_input_ports::template apply::value, - traits::block::stream_output_ports::template apply::value, left_size, right_size }); + return std::min({traits::block::stream_input_ports::template apply::value, traits::block::stream_output_ports::template apply::value, left_size, right_size}); } template - constexpr auto - apply_left(std::size_t offset, auto &&input_tuple) noexcept { - return [&](std::index_sequence) { - return invokeProcessOneWithOrWithoutOffset(left, offset, std::get(std::forward(input_tuple))...); - }(std::make_index_sequence()); + constexpr auto apply_left(std::size_t offset, auto&& input_tuple) noexcept { + return [&](std::index_sequence) { return invokeProcessOneWithOrWithoutOffset(left, offset, std::get(std::forward(input_tuple))...); }(std::make_index_sequence()); } template - constexpr auto - apply_right(std::size_t offset, auto &&input_tuple, auto &&tmp) noexcept { + constexpr auto apply_right(std::size_t offset, auto&& input_tuple, auto&& tmp) noexcept { return [&](std::index_sequence, std::index_sequence) { constexpr std::size_t first_offset = traits::block::stream_input_port_types::size; constexpr std::size_t second_offset = traits::block::stream_input_port_types::size + sizeof...(Is); static_assert(second_offset + sizeof...(Js) == std::tuple_size_v>); - return invokeProcessOneWithOrWithoutOffset(right, offset, std::get(std::forward(input_tuple))..., std::forward(tmp), - std::get(input_tuple)...); + return invokeProcessOneWithOrWithoutOffset(right, offset, std::get(std::forward(input_tuple))..., std::forward(tmp), std::get(input_tuple)...); }(std::make_index_sequence(), std::make_index_sequence()); } @@ -1031,38 +619,32 @@ class MergedGraph using TOutputPortTypes = typename traits::block::stream_output_port_types; using TReturnType = typename traits::block::stream_return_type; - constexpr - MergedGraph(Left l, Right r) - : left(std::move(l)), right(std::move(r)) {} + constexpr MergedGraph(Left l, Right r) : left(std::move(l)), right(std::move(r)) {} // if the left block (source) implements available_samples (a customization point), then pass the call through - friend constexpr std::size_t - available_samples(const MergedGraph &self) noexcept - requires requires(const Left &l) { - { available_samples(l) } -> std::same_as; - } + friend constexpr std::size_t available_samples(const MergedGraph& self) noexcept + requires requires(const Left& l) { + { available_samples(l) } -> std::same_as; + } { return available_samples(self.left); } template - requires traits::block::can_processOne_simd and traits::block::can_processOne_simd - constexpr meta::simdize>> - processOne(std::size_t offset, const Ts &...inputs) { + requires traits::block::can_processOne_simd and traits::block::can_processOne_simd + constexpr meta::simdize>> processOne(std::size_t offset, const Ts&... inputs) { static_assert(traits::block::stream_output_port_types::size == 1, "TODO: SIMD for multiple output ports not implemented yet"); - return apply_right::size() - InId - 1>(offset, std::tie(inputs...), - apply_left::size()>(offset, std::tie(inputs...))); + return apply_right::size() - InId - 1>(offset, std::tie(inputs...), apply_left::size()>(offset, std::tie(inputs...))); } - constexpr auto - processOne_simd(std::size_t offset, auto N) - requires traits::block::can_processOne_simd + constexpr auto processOne_simd(std::size_t offset, auto N) + requires traits::block::can_processOne_simd { - if constexpr (requires(Left &l) { + if constexpr (requires(Left& l) { { l.processOne_simd(offset, N) }; }) { return invokeProcessOneWithOrWithoutOffset(right, offset, left.processOne_simd(offset, N)); - } else if constexpr (requires(Left &l) { + } else if constexpr (requires(Left& l) { { l.processOne_simd(N) }; }) { return invokeProcessOneWithOrWithoutOffset(right, offset, left.processOne_simd(N)); @@ -1079,23 +661,19 @@ class MergedGraph template // Nicer error messages for the following would be good, but not at the expense of breaking can_processOne_simd. - requires(TInputPortTypes::template are_equal...>) - constexpr TReturnType - processOne(std::size_t offset, Ts &&...inputs) { + requires(TInputPortTypes::template are_equal...>) + constexpr TReturnType processOne(std::size_t offset, Ts&&... inputs) { // if (sizeof...(Ts) == 0) we could call `return processOne_simd(integral_constant)`. But if // the caller expects to process *one* sample (no inputs for the caller to explicitly // request simd), and we process more, we risk inconsistencies. if constexpr (traits::block::stream_output_port_types::size == 1) { // only the result from the right block needs to be returned - return apply_right::size() - InId - - 1>(offset, std::forward_as_tuple(std::forward(inputs)...), - apply_left::size()>(offset, std::forward_as_tuple(std::forward(inputs)...))); + return apply_right::size() - InId - 1>(offset, std::forward_as_tuple(std::forward(inputs)...), apply_left::size()>(offset, std::forward_as_tuple(std::forward(inputs)...))); } else { // left produces a tuple auto left_out = apply_left::size()>(offset, std::forward_as_tuple(std::forward(inputs)...)); - auto right_out = apply_right::size() - InId - 1>(offset, std::forward_as_tuple(std::forward(inputs)...), - std::move(std::get(left_out))); + auto right_out = apply_right::size() - InId - 1>(offset, std::forward_as_tuple(std::forward(inputs)...), std::move(std::get(left_out))); if constexpr (traits::block::stream_output_port_types::size == 2 && traits::block::stream_output_port_types::size == 1) { return std::make_tuple(std::move(std::get(left_out)), std::move(right_out)); @@ -1104,15 +682,10 @@ class MergedGraph return std::tuple_cat(std::make_tuple(std::move(std::get(left_out))), std::move(right_out)); } else if constexpr (traits::block::stream_output_port_types::size == 1) { - return [&](std::index_sequence, std::index_sequence) { - return std::make_tuple(std::move(std::get(left_out))..., std::move(std::get(left_out))..., std::move(right_out)); - }(std::make_index_sequence(), std::make_index_sequence::size - OutId - 1>()); + return [&](std::index_sequence, std::index_sequence) { return std::make_tuple(std::move(std::get(left_out))..., std::move(std::get(left_out))..., std::move(right_out)); }(std::make_index_sequence(), std::make_index_sequence::size - OutId - 1>()); } else { - return [&](std::index_sequence, std::index_sequence, std::index_sequence) { - return std::make_tuple(std::move(std::get(left_out))..., std::move(std::get(left_out))..., std::move(std::get(right_out)...)); - }(std::make_index_sequence(), std::make_index_sequence::size - OutId - 1>(), - std::make_index_sequence()); + return [&](std::index_sequence, std::index_sequence, std::index_sequence) { return std::make_tuple(std::move(std::get(left_out))..., std::move(std::get(left_out))..., std::move(std::get(right_out)...)); }(std::make_index_sequence(), std::make_index_sequence::size - OutId - 1>(), std::make_index_sequence()); } } } // end:: processOne @@ -1124,7 +697,7 @@ class MergedGraph }; template -inline std::atomic_size_t MergedGraph::_unique_id_counter{ 0UZ }; +inline std::atomic_size_t MergedGraph::_unique_id_counter{0UZ}; /** * This methods can merge simple blocks that are defined via a single `auto processOne(..)` producing a @@ -1149,17 +722,13 @@ inline std::atomic_size_t MergedGraph::_unique_id_coun * @endcode */ template -constexpr auto -mergeByIndex(A &&a, B &&b) -> MergedGraph, std::remove_cvref_t, OutId, InId> { - if constexpr (!std::is_same_v>::template at, - typename traits::block::stream_input_port_types>::template at>) { - gr::meta::print_types, typename traits::block::stream_output_port_types>, std::integral_constant, - typename traits::block::stream_output_port_types>::template at, - - gr::meta::message_type<"INPUT_PORTS_ARE:">, typename traits::block::stream_input_port_types>, std::integral_constant, - typename traits::block::stream_input_port_types>::template at>{}; +constexpr auto mergeByIndex(A&& a, B&& b) -> MergedGraph, std::remove_cvref_t, OutId, InId> { + if constexpr (!std::is_same_v>::template at, typename traits::block::stream_input_port_types>::template at>) { + gr::meta::print_types, typename traits::block::stream_output_port_types>, std::integral_constant, typename traits::block::stream_output_port_types>::template at, + + gr::meta::message_type<"INPUT_PORTS_ARE:">, typename traits::block::stream_input_port_types>, std::integral_constant, typename traits::block::stream_input_port_types>::template at>{}; } - return { std::forward(a), std::forward(b) }; + return {std::forward(a), std::forward(b)}; } /** @@ -1185,18 +754,15 @@ mergeByIndex(A &&a, B &&b) -> MergedGraph, std::remove_cv * @endcode */ template -constexpr auto -merge(A &&a, B &&b) { +constexpr auto merge(A&& a, B&& b) { constexpr int OutIdUnchecked = meta::indexForName>(); constexpr int InIdUnchecked = meta::indexForName>(); static_assert(OutIdUnchecked != -1); static_assert(InIdUnchecked != -1); constexpr auto OutId = static_cast(OutIdUnchecked); constexpr auto InId = static_cast(InIdUnchecked); - static_assert(std::same_as>::template at, - typename traits::block::stream_input_port_types>::template at>, - "Port types do not match"); - return MergedGraph, std::remove_cvref_t, OutId, InId>{ std::forward(a), std::forward(b) }; + static_assert(std::same_as>::template at, typename traits::block::stream_input_port_types>::template at>, "Port types do not match"); + return MergedGraph, std::remove_cvref_t, OutId, InId>{std::forward(a), std::forward(b)}; } /*******************************************************************************************************/ @@ -1204,39 +770,17 @@ merge(A &&a, B &&b) { /*******************************************************************************************************/ // TODO: add nicer enum formatter -inline std::ostream & -operator<<(std::ostream &os, const ConnectionResult &value) { - return os << static_cast(value); -} +inline std::ostream& operator<<(std::ostream& os, const ConnectionResult& value) { return os << static_cast(value); } -inline std::ostream & -operator<<(std::ostream &os, const PortType &value) { - return os << static_cast(value); -} +inline std::ostream& operator<<(std::ostream& os, const PortType& value) { return os << static_cast(value); } -inline std::ostream & -operator<<(std::ostream &os, const PortDirection &value) { - return os << static_cast(value); -} +inline std::ostream& operator<<(std::ostream& os, const PortDirection& value) { return os << static_cast(value); } template -inline std::ostream & -operator<<(std::ostream &os, const T &value) { +inline std::ostream& operator<<(std::ostream& os, const T& value) { return os << value.Name; } -#if HAVE_SOURCE_LOCATION -inline auto -this_source_location(std::source_location l = std::source_location::current()) { - return fmt::format("{}:{},{}", l.file_name(), l.line(), l.column()); -} -#else -inline auto -this_source_location() { - return "not yet implemented"; -} -#endif // HAVE_SOURCE_LOCATION - } // namespace gr REFL_TYPE(gr::Graph) REFL_END // minimal reflection declaration diff --git a/core/include/gnuradio-4.0/PluginLoader.hpp b/core/include/gnuradio-4.0/PluginLoader.hpp index dc118dde..683cd6a3 100644 --- a/core/include/gnuradio-4.0/PluginLoader.hpp +++ b/core/include/gnuradio-4.0/PluginLoader.hpp @@ -12,9 +12,8 @@ #include #include "BlockRegistry.hpp" -#include "Graph.hpp" -#ifndef __EMSCRIPTEN__ +#ifdef ENABLE_BLOCK_PLUGINS #include #include "plugin.hpp" @@ -25,23 +24,22 @@ namespace gr { using namespace std::string_literals; using namespace std::string_view_literals; -#ifndef __EMSCRIPTEN__ +#ifdef ENABLE_BLOCK_PLUGINS // Plugins are not supported on WASM -using plugin_create_function_t = void (*)(gr_plugin_base **); -using plugin_destroy_function_t = void (*)(gr_plugin_base *); +using plugin_create_function_t = void (*)(gr_plugin_base**); +using plugin_destroy_function_t = void (*)(gr_plugin_base*); class PluginHandler { private: - void *_dl_handle = nullptr; + void* _dl_handle = nullptr; plugin_create_function_t _create_fn = nullptr; plugin_destroy_function_t _destroy_fn = nullptr; - gr_plugin_base *_instance = nullptr; + gr_plugin_base* _instance = nullptr; std::string _status; - void - release() { + void release() { if (_instance) { _destroy_fn(_instance); _instance = nullptr; @@ -56,7 +54,7 @@ class PluginHandler { public: PluginHandler() = default; - explicit PluginHandler(const std::string &plugin_file) { + explicit PluginHandler(const std::string& plugin_file) { _dl_handle = dlopen(plugin_file.c_str(), RTLD_LAZY); if (!_dl_handle) { _status = "Failed to load the plugin file"; @@ -91,19 +89,12 @@ class PluginHandler { } } - PluginHandler(const PluginHandler &other) = delete; - PluginHandler & - operator=(const PluginHandler &other) - = delete; + PluginHandler(const PluginHandler& other) = delete; + PluginHandler& operator=(const PluginHandler& other) = delete; - PluginHandler(PluginHandler &&other) noexcept - : _dl_handle(std::exchange(other._dl_handle, nullptr)) - , _create_fn(std::exchange(other._create_fn, nullptr)) - , _destroy_fn(std::exchange(other._destroy_fn, nullptr)) - , _instance(std::exchange(other._instance, nullptr)) {} + PluginHandler(PluginHandler&& other) noexcept : _dl_handle(std::exchange(other._dl_handle, nullptr)), _create_fn(std::exchange(other._create_fn, nullptr)), _destroy_fn(std::exchange(other._destroy_fn, nullptr)), _instance(std::exchange(other._instance, nullptr)) {} - PluginHandler & - operator=(PluginHandler &&other) noexcept { + PluginHandler& operator=(PluginHandler&& other) noexcept { auto tmp = std::move(other); std::swap(_dl_handle, tmp._dl_handle); std::swap(_create_fn, tmp._create_fn); @@ -114,34 +105,24 @@ class PluginHandler { ~PluginHandler() { release(); } - explicit - operator bool() const { - return _instance; - } + explicit operator bool() const { return _instance; } - [[nodiscard]] const std::string & - status() const { - return _status; - } + [[nodiscard]] const std::string& status() const { return _status; } - auto * - operator->() const { - return _instance; - } + auto* operator->() const { return _instance; } }; class PluginLoader { private: - std::vector _handlers; - std::unordered_map _handlerForName; - std::unordered_map _failedPlugins; - std::unordered_set _loadedPluginFiles; + std::vector _handlers; + std::unordered_map _handlerForName; + std::unordered_map _failedPlugins; + std::unordered_set _loadedPluginFiles; - BlockRegistry *_registry; + BlockRegistry* _registry; std::vector _knownBlocks; - gr_plugin_base * - handlerForName(std::string_view name) const { + gr_plugin_base* handlerForName(std::string_view name) const { if (auto it = _handlerForName.find(std::string(name)); it != _handlerForName.end()) { return it->second; } else { @@ -150,20 +131,24 @@ class PluginLoader { } public: - PluginLoader(BlockRegistry ®istry, std::span plugin_directories) : _registry(®istry) { - for (const auto &directory : plugin_directories) { + PluginLoader(BlockRegistry& registry, std::span plugin_directories) : _registry(®istry) { + for (const auto& directory : plugin_directories) { std::cerr << std::filesystem::current_path() << std::endl; - if (!std::filesystem::is_directory(directory)) continue; + if (!std::filesystem::is_directory(directory)) { + continue; + } - for (const auto &file : std::filesystem::directory_iterator{ directory }) { + for (const auto& file : std::filesystem::directory_iterator{directory}) { if (file.is_regular_file() && file.path().extension() == ".so") { auto fileString = file.path().string(); - if (_loadedPluginFiles.contains(fileString)) continue; + if (_loadedPluginFiles.contains(fileString)) { + continue; + } _loadedPluginFiles.insert(fileString); if (PluginHandler handler(file.path().string()); handler) { - for (const auto &block_name : handler->providedBlocks()) { + for (const auto& block_name : handler->providedBlocks()) { _handlerForName.emplace(std::string(block_name), handler.operator->()); _knownBlocks.emplace_back(block_name); } @@ -178,36 +163,25 @@ class PluginLoader { } } - BlockRegistry & - registry() { - return *_registry; - } + BlockRegistry& registry() { return *_registry; } - const auto & - plugins() const { - return _handlers; - } + const auto& plugins() const { return _handlers; } - const auto & - failed_plugins() const { - return _failedPlugins; - } + const auto& failed_plugins() const { return _failedPlugins; } - auto - knownBlocks() const { + auto knownBlocks() const { auto result = _knownBlocks; - const auto &builtin = _registry->knownBlocks(); + const auto& builtin = _registry->knownBlocks(); result.insert(result.end(), builtin.begin(), builtin.end()); return result; } - std::vector - knownBlockParameterizations(std::string_view block) const { + std::vector knownBlockParameterizations(std::string_view block) const { if (_registry->isBlockKnown(block)) { return _registry->knownBlockParameterizations(block); } - gr_plugin_base *handler = handlerForName(block); + gr_plugin_base* handler = handlerForName(block); if (handler) { return handler->knownBlockParameterizations(block); } else { @@ -215,53 +189,35 @@ class PluginLoader { } } - std::unique_ptr - instantiate(std::string_view name, std::string_view type, const property_map ¶ms = {}) { + std::unique_ptr instantiate(std::string_view name, std::string_view type, const property_map& params = {}) { // Try to create a node from the global registry if (auto result = _registry->createBlock(name, type, params)) { return result; } - auto *handler = handlerForName(name); + auto* handler = handlerForName(name); if (handler == nullptr) { return {}; } return handler->createBlock(name, type, params); } - - template - gr::BlockModel & - instantiateInGraph(Graph &graph, InstantiateArgs &&...args) { - auto block_load = instantiate(std::forward(args)...); - if (!block_load) { - throw fmt::format("Unable to create node"); - } - return graph.addBlock(std::move(block_load)); - } }; #else // PluginLoader on WASM is just a wrapper on BlockRegistry to provide the // same API as proper PluginLoader class PluginLoader { private: - BlockRegistry *_registry; + BlockRegistry* _registry; public: - PluginLoader(BlockRegistry ®istry, std::span /*plugin_directories*/) : _registry(®istry) {} + PluginLoader(BlockRegistry& registry, std::span /*plugin_directories*/) : _registry(®istry) {} - BlockRegistry & - registry() { - return *_registry; - } + BlockRegistry& registry() { return *_registry; } - auto - knownBlocks() const { - return _registry->knownBlocks(); - } + auto knownBlocks() const { return _registry->knownBlocks(); } - std::vector - knownBlockParameterizations(std::string_view block) const { + std::vector knownBlockParameterizations(std::string_view block) const { if (_registry->isBlockKnown(block)) { return _registry->knownBlockParameterizations(block); } @@ -269,23 +225,45 @@ class PluginLoader { return {}; } - std::unique_ptr - instantiate(std::string_view name, std::string_view type, const property_map ¶ms = {}) { - return _registry->createBlock(name, type, params); - } - - template - gr::BlockModel & - instantiateInGraph(Graph &graph, InstantiateArgs &&...args) { - auto block_load = instantiate(std::forward(args)...); - if (!block_load) { - throw fmt::format("Unable to create node"); - } - return graph.addBlock(std::move(block_load)); - } + std::unique_ptr instantiate(std::string_view name, std::string_view type, const property_map& params = {}) { return _registry->createBlock(name, type, params); } }; #endif +inline auto& globalPluginLoader() { + auto pluginPaths = [] { + std::vector result; + + auto* envpath = ::getenv("GNURADIO4_PLUGIN_DIRECTORIES"); + if (envpath == nullptr) { + // TODO choose proper paths when we get the system GR installation done + result.emplace_back("core/test/plugins"); + + } else { + std::string_view paths(envpath); + + auto i = paths.cbegin(); + + // TODO If we want to support Windows, this should be ; there + auto isSeparator = [](char c) { return c == ':'; }; + + while (i != paths.cend()) { + i = std::find_if_not(i, paths.cend(), isSeparator); + auto j = std::find_if(i, paths.cend(), isSeparator); + + if (i != paths.cend()) { + result.emplace_back(std::string_view(i, j)); + } + i = j; + } + } + + return result; + }; + + static PluginLoader instance(gr::globalBlockRegistry(), {pluginPaths()}); + return instance; +} + } // namespace gr #endif // include guard diff --git a/core/include/gnuradio-4.0/Port.hpp b/core/include/gnuradio-4.0/Port.hpp index 09e1df4f..bc26d36f 100644 --- a/core/include/gnuradio-4.0/Port.hpp +++ b/core/include/gnuradio-4.0/Port.hpp @@ -9,11 +9,11 @@ #include -#include "annotated.hpp" #include "CircularBuffer.hpp" #include "DataSet.hpp" #include "Message.hpp" #include "Tag.hpp" +#include "annotated.hpp" namespace gr { @@ -54,7 +54,7 @@ static_assert(is_port_domain::value); static_assert(!is_port_domain::value); template -concept PortLike = requires(T t, const std::size_t n_items, const std::any &newDefault) { // dynamic definitions +concept PortLike = requires(T t, const std::size_t n_items, const std::any& newDefault) { // dynamic definitions typename T::value_type; { t.defaultValue() } -> std::same_as; { t.setDefaultValue(newDefault) } -> std::same_as; @@ -78,8 +78,8 @@ concept PortLike = requires(T t, const std::size_t n_items, const std::any &newD * N.B. void* needed for type-erasure/Python compatibility/wrapping */ struct InternalPortBuffers { - void *streamHandler; - void *tagHandler; + void* streamHandler; + void* tagHandler; }; /** @@ -184,36 +184,27 @@ Follows the ISO 80000-1:2022 Quantities and Units conventions: Annotated> signal_max = std::numeric_limits::max(); // controls automatic (if set) or manual update of above parameters - std::set> auto_update{ "sample_rate", "signal_name", "signal_quantity", "signal_unit", "signal_min", "signal_max" }; + std::set> auto_update{"sample_rate", "signal_name", "signal_quantity", "signal_unit", "signal_min", "signal_max"}; PortMetaInfo() noexcept(true) : PortMetaInfo({}) {} - explicit - PortMetaInfo(std::initializer_list> initMetaInfo) noexcept(true) - : PortMetaInfo(property_map{ initMetaInfo.begin(), initMetaInfo.end() }) {} + explicit PortMetaInfo(std::initializer_list> initMetaInfo) noexcept(true) : PortMetaInfo(property_map{initMetaInfo.begin(), initMetaInfo.end()}) {} - explicit - PortMetaInfo(const property_map &metaInfo) noexcept(true) { - update(metaInfo); - } + explicit PortMetaInfo(const property_map& metaInfo) noexcept(true) { update(metaInfo); } - void - reset() { - auto_update = { "sample_rate", "signal_name", "signal_quantity", "signal_unit", "signal_min", "signal_max" }; - } + void reset() { auto_update = {"sample_rate", "signal_name", "signal_quantity", "signal_unit", "signal_min", "signal_max"}; } template - void - update(const property_map &metaInfo) noexcept(isNoexcept) { + void update(const property_map& metaInfo) noexcept(isNoexcept) { if (metaInfo.empty()) { return; } - auto updateValue = [&metaInfo](const std::string &key, auto &member) { + auto updateValue = [&metaInfo](const std::string& key, auto& member) { if (!metaInfo.contains(key)) { return; } - const auto &value = metaInfo.at(key); + const auto& value = metaInfo.at(key); using T = std::decay_t; if (std::holds_alternative(value)) { member = std::get(value); @@ -222,7 +213,7 @@ Follows the ISO 80000-1:2022 Quantities and Units conventions: } }; - for (const auto &key : auto_update) { + for (const auto& key : auto_update) { if (key == "sample_rate") { updateValue(key, sample_rate); } else if (key == "signal_name") { @@ -239,8 +230,7 @@ Follows the ISO 80000-1:2022 Quantities and Units conventions: } } - [[nodiscard]] property_map - get() const noexcept { + [[nodiscard]] property_map get() const noexcept { property_map metaInfo; metaInfo["sample_rate"] = sample_rate; metaInfo["signal_name"] = signal_name; @@ -349,8 +339,7 @@ struct Port { TagIoType _tagIoHandler = newTagIoHandler(); Tag _cachedTag{}; - [[nodiscard]] constexpr auto - newIoHandler(std::size_t buffer_size = 65536) const noexcept { + [[nodiscard]] constexpr auto newIoHandler(std::size_t buffer_size = 65536) const noexcept { if constexpr (kIsInput) { return BufferType(buffer_size).new_reader(); } else { @@ -358,8 +347,7 @@ struct Port { } } - [[nodiscard]] constexpr auto - newTagIoHandler(std::size_t buffer_size = 65536) const noexcept { + [[nodiscard]] constexpr auto newTagIoHandler(std::size_t buffer_size = 65536) const noexcept { if constexpr (kIsInput) { return TagBufferType(buffer_size).new_reader(); } else { @@ -368,52 +356,32 @@ struct Port { } public: - constexpr - Port() noexcept - = default; + constexpr Port() noexcept = default; - Port(std::string port_name, std::int16_t priority_ = 0, std::size_t min_samples_ = 0UZ, std::size_t max_samples_ = SIZE_MAX) noexcept - : name(std::move(port_name)), priority{ priority_ }, min_samples(min_samples_), max_samples(max_samples_), _ioHandler{ newIoHandler() }, _tagIoHandler{ newTagIoHandler() } { - static_assert(portName.empty(), "port name must be exclusively declared via NTTP or constructor parameter"); - } + Port(std::string port_name, std::int16_t priority_ = 0, std::size_t min_samples_ = 0UZ, std::size_t max_samples_ = SIZE_MAX) noexcept : name(std::move(port_name)), priority{priority_}, min_samples(min_samples_), max_samples(max_samples_), _ioHandler{newIoHandler()}, _tagIoHandler{newTagIoHandler()} { static_assert(portName.empty(), "port name must be exclusively declared via NTTP or constructor parameter"); } - constexpr - Port(Port &&other) noexcept - : name(std::move(other.name)) - , priority{ other.priority } - , min_samples(other.min_samples) - , max_samples(other.max_samples) - , _ioHandler(std::move(other._ioHandler)) - , _tagIoHandler(std::move(other._tagIoHandler)) {} - - Port(const Port &) = delete; - auto - operator=(const Port &) - = delete; - constexpr Port & - operator=(Port &&other) - = delete; - - ~ - Port() = default; - - [[nodiscard]] constexpr bool - initBuffer(std::size_t nSamples = 0) noexcept { + constexpr Port(Port&& other) noexcept : name(std::move(other.name)), priority{other.priority}, min_samples(other.min_samples), max_samples(other.max_samples), _ioHandler(std::move(other._ioHandler)), _tagIoHandler(std::move(other._tagIoHandler)) {} + + Port(const Port&) = delete; + auto operator=(const Port&) = delete; + constexpr Port& operator=(Port&& other) = delete; + + ~Port() = default; + + [[nodiscard]] constexpr bool initBuffer(std::size_t nSamples = 0) noexcept { if constexpr (kIsOutput) { // write one default value into output -- needed for cyclic graph initialisation - return _ioHandler.try_publish([val = default_value](std::span &out) { std::ranges::fill(out, val); }, nSamples); + return _ioHandler.try_publish([val = default_value](std::span& out) { std::ranges::fill(out, val); }, nSamples); } return true; } - [[nodiscard]] InternalPortBuffers - writerHandlerInternal() noexcept { + [[nodiscard]] InternalPortBuffers writerHandlerInternal() noexcept { static_assert(kIsOutput, "only to be used with output ports"); - return { static_cast(std::addressof(_ioHandler)), static_cast(std::addressof(_tagIoHandler)) }; + return {static_cast(std::addressof(_ioHandler)), static_cast(std::addressof(_tagIoHandler))}; } - [[nodiscard]] bool - updateReaderInternal(InternalPortBuffers buffer_writer_handler_other) noexcept { + [[nodiscard]] bool updateReaderInternal(InternalPortBuffers buffer_writer_handler_other) noexcept { static_assert(kIsInput, "only to be used with input ports"); if (buffer_writer_handler_other.streamHandler == nullptr) { @@ -427,14 +395,13 @@ struct Port { // this will fail. We need to add a check that two ports that // connect to each other use the same buffer type // (std::any could be a viable approach) - auto typed_buffer_writer = static_cast(buffer_writer_handler_other.streamHandler); - auto typed_tag_buffer_writer = static_cast(buffer_writer_handler_other.tagHandler); + auto typed_buffer_writer = static_cast(buffer_writer_handler_other.streamHandler); + auto typed_tag_buffer_writer = static_cast(buffer_writer_handler_other.tagHandler); setBuffer(typed_buffer_writer->buffer(), typed_tag_buffer_writer->buffer()); return true; } - [[nodiscard]] constexpr bool - isConnected() const noexcept { + [[nodiscard]] constexpr bool isConnected() const noexcept { if constexpr (kIsInput) { return _ioHandler.buffer().n_writers() > 0; } else { @@ -442,45 +409,25 @@ struct Port { } } - [[nodiscard]] constexpr static PortType - type() noexcept { - return portType; - } + [[nodiscard]] constexpr static PortType type() noexcept { return portType; } - [[nodiscard]] constexpr static PortDirection - direction() noexcept { - return portDirection; - } + [[nodiscard]] constexpr static PortDirection direction() noexcept { return portDirection; } - [[nodiscard]] constexpr static std::string_view - domain() noexcept { - return std::string_view(Domain::Name); - } + [[nodiscard]] constexpr static std::string_view domain() noexcept { return std::string_view(Domain::Name); } - [[nodiscard]] constexpr static bool - isSynchronous() noexcept { - return kIsSynch; - } + [[nodiscard]] constexpr static bool isSynchronous() noexcept { return kIsSynch; } - [[nodiscard]] constexpr static bool - isOptional() noexcept { - return kIsOptional; - } + [[nodiscard]] constexpr static bool isOptional() noexcept { return kIsOptional; } - [[nodiscard]] constexpr static decltype(portName) - static_name() noexcept - requires(!portName.empty()) + [[nodiscard]] constexpr static decltype(portName) static_name() noexcept + requires(!portName.empty()) { return portName; } - [[nodiscard]] std::any - defaultValue() const noexcept { - return default_value; - } + [[nodiscard]] std::any defaultValue() const noexcept { return default_value; } - [[nodiscard]] bool - setDefaultValue(const std::any &newDefault) { + [[nodiscard]] bool setDefaultValue(const std::any& newDefault) { if (newDefault.type() == typeid(T)) { default_value = std::any_cast(newDefault); return true; @@ -488,13 +435,9 @@ struct Port { return false; } - [[nodiscard]] constexpr static std::size_t - available() noexcept { - return 0; - } // ↔ maps to Buffer::Buffer[Reader, Writer].available() + [[nodiscard]] constexpr static std::size_t available() noexcept { return 0; } // ↔ maps to Buffer::Buffer[Reader, Writer].available() - [[nodiscard]] constexpr std::size_t - min_buffer_size() const noexcept { + [[nodiscard]] constexpr std::size_t min_buffer_size() const noexcept { if constexpr (Required::kIsConst) { return Required::kMinSamples; } else { @@ -502,8 +445,7 @@ struct Port { } } - [[nodiscard]] constexpr std::size_t - max_buffer_size() const noexcept { + [[nodiscard]] constexpr std::size_t max_buffer_size() const noexcept { if constexpr (Required::kIsConst) { return Required::kMaxSamples; } else { @@ -511,8 +453,7 @@ struct Port { } } - [[nodiscard]] constexpr ConnectionResult - resizeBuffer(std::size_t min_size) noexcept { + [[nodiscard]] constexpr ConnectionResult resizeBuffer(std::size_t min_size) noexcept { using enum gr::ConnectionResult; if constexpr (kIsInput) { return SUCCESS; @@ -527,18 +468,16 @@ struct Port { return SUCCESS; } - [[nodiscard]] auto - buffer() { + [[nodiscard]] auto buffer() { struct port_buffers { BufferType streamBuffer; TagBufferType tagBuffer; }; - return port_buffers{ _ioHandler.buffer(), _tagIoHandler.buffer() }; + return port_buffers{_ioHandler.buffer(), _tagIoHandler.buffer()}; } - void - setBuffer(gr::Buffer auto streamBuffer, gr::Buffer auto tagBuffer) noexcept { + void setBuffer(gr::Buffer auto streamBuffer, gr::Buffer auto tagBuffer) noexcept { if constexpr (kIsInput) { _ioHandler = streamBuffer.new_reader(); _tagIoHandler = tagBuffer.new_reader(); @@ -548,56 +487,47 @@ struct Port { } } - [[nodiscard]] constexpr const ReaderType & - streamReader() const noexcept { + [[nodiscard]] constexpr const ReaderType& streamReader() const noexcept { static_assert(!kIsOutput, "streamReader() not applicable for outputs (yet)"); return _ioHandler; } - [[nodiscard]] constexpr ReaderType & - streamReader() noexcept { + [[nodiscard]] constexpr ReaderType& streamReader() noexcept { static_assert(!kIsOutput, "streamReader() not applicable for outputs (yet)"); return _ioHandler; } - [[nodiscard]] constexpr const WriterType & - streamWriter() const noexcept { + [[nodiscard]] constexpr const WriterType& streamWriter() const noexcept { static_assert(!kIsInput, "streamWriter() not applicable for inputs (yet)"); return _ioHandler; } - [[nodiscard]] constexpr WriterType & - streamWriter() noexcept { + [[nodiscard]] constexpr WriterType& streamWriter() noexcept { static_assert(!kIsInput, "streamWriter() not applicable for inputs (yet)"); return _ioHandler; } - [[nodiscard]] constexpr const TagReaderType & - tagReader() const noexcept { + [[nodiscard]] constexpr const TagReaderType& tagReader() const noexcept { static_assert(!kIsOutput, "tagReader() not applicable for outputs (yet)"); return _tagIoHandler; } - [[nodiscard]] constexpr TagReaderType & - tagReader() noexcept { + [[nodiscard]] constexpr TagReaderType& tagReader() noexcept { static_assert(!kIsOutput, "tagReader() not applicable for outputs (yet)"); return _tagIoHandler; } - [[nodiscard]] constexpr const TagWriterType & - tagWriter() const noexcept { + [[nodiscard]] constexpr const TagWriterType& tagWriter() const noexcept { static_assert(!kIsInput, "tagWriter() not applicable for inputs (yet)"); return _tagIoHandler; } - [[nodiscard]] constexpr TagWriterType & - tagWriter() noexcept { + [[nodiscard]] constexpr TagWriterType& tagWriter() noexcept { static_assert(!kIsInput, "tagWriter() not applicable for inputs (yet)"); return _tagIoHandler; } - [[nodiscard]] ConnectionResult - disconnect() noexcept { + [[nodiscard]] ConnectionResult disconnect() noexcept { if (isConnected() == false) { return ConnectionResult::FAILED; } @@ -607,9 +537,9 @@ struct Port { } template - [[nodiscard]] ConnectionResult - connect(Other &&other) { + [[nodiscard]] ConnectionResult connect(Other&& other) { static_assert(kIsOutput && std::remove_cvref_t::kIsInput); + static_assert(std::is_same_v::value_type>); auto src_buffer = writerHandlerInternal(); return std::forward(other).updateReaderInternal(src_buffer) ? ConnectionResult::SUCCESS : ConnectionResult::FAILED; } @@ -617,16 +547,15 @@ struct Port { /** * @return get all (incl. past unconsumed) tags () until the read-position + optional offset */ - inline constexpr ConsumableSpan auto - getTags(Tag::signed_index_type untilOffset = 0) noexcept - requires(kIsInput) + inline constexpr ConsumableSpan auto getTags(Tag::signed_index_type untilOffset = 0) noexcept + requires(kIsInput) { const auto readPos = streamReader().position(); const auto tags = tagReader().get(); // N.B. returns all old/available/pending tags std::size_t nTagsProcessed = 0UZ; bool properTagDistance = false; - for (const Tag &tag : tags) { + for (const Tag& tag : tags) { const auto relativeTagPosition = (tag.index - readPos); // w.r.t. present stream reader position const bool tagIsWithinRange = (tag.index != -1) && relativeTagPosition <= untilOffset; if ((!properTagDistance && tag.index < 0) || tagIsWithinRange) { // 'index == -1' wildcard Tag index -> process unconditionally @@ -641,9 +570,8 @@ struct Port { return tagReader().get(nTagsProcessed); } - inline const Tag - getTag(Tag::signed_index_type untilOffset = 0) - requires(kIsInput) + inline const Tag getTag(Tag::signed_index_type untilOffset = 0) + requires(kIsInput) { const auto readPos = streamReader().position(); if (_cachedTag.index == readPos && readPos >= 0) { @@ -651,38 +579,35 @@ struct Port { } _cachedTag.reset(); - auto mergeSrcMapInto = [](const property_map &sourceMap, property_map &destinationMap) { + auto mergeSrcMapInto = [](const property_map& sourceMap, property_map& destinationMap) { assert(&sourceMap != &destinationMap); - for (const auto &[key, value] : sourceMap) { + for (const auto& [key, value] : sourceMap) { destinationMap.insert_or_assign(key, value); } }; const auto tags = getTags(untilOffset); _cachedTag.index = readPos; - std::ranges::for_each(tags, [&mergeSrcMapInto, this](const Tag &tag) { mergeSrcMapInto(tag.map, _cachedTag.map); }); + std::ranges::for_each(tags, [&mergeSrcMapInto, this](const Tag& tag) { mergeSrcMapInto(tag.map, _cachedTag.map); }); std::ignore = tags.consume(tags.size()); return _cachedTag; } - inline constexpr void - publishTag(property_map &&tag_data, Tag::signed_index_type tagOffset = -1) noexcept - requires(kIsOutput) + inline constexpr void publishTag(property_map&& tag_data, Tag::signed_index_type tagOffset = -1) noexcept + requires(kIsOutput) { processPublishTag(std::move(tag_data), tagOffset); } - inline constexpr void - publishTag(const property_map &tag_data, Tag::signed_index_type tagOffset = -1) noexcept - requires(kIsOutput) + inline constexpr void publishTag(const property_map& tag_data, Tag::signed_index_type tagOffset = -1) noexcept + requires(kIsOutput) { processPublishTag(tag_data, tagOffset); } - [[maybe_unused]] inline constexpr bool - publishPendingTags() noexcept - requires(kIsOutput) + [[maybe_unused]] inline constexpr bool publishPendingTags() noexcept + requires(kIsOutput) { if (_cachedTag.map.empty() /*|| streamWriter().buffer().n_readers() == 0UZ*/) { return false; @@ -698,20 +623,19 @@ struct Port { private: template - inline constexpr void - processPublishTag(PropertyMap &&tag_data, Tag::signed_index_type tagOffset) noexcept { + inline constexpr void processPublishTag(PropertyMap&& tag_data, Tag::signed_index_type tagOffset) noexcept { const auto newTagIndex = tagOffset < 0 ? tagOffset : streamWriter().position() + tagOffset; if (tagOffset >= 0 && (_cachedTag.index != newTagIndex && _cachedTag.index != -1)) { // do not cache tags that have an explicit index publishPendingTags(); } _cachedTag.index = newTagIndex; - if constexpr (std::is_rvalue_reference_v) { // -> move semantics - for (auto &[key, value] : tag_data) { + if constexpr (std::is_rvalue_reference_v) { // -> move semantics + for (auto& [key, value] : tag_data) { _cachedTag.map.insert_or_assign(std::move(key), std::move(value)); } } else { // -> copy semantics - for (const auto &[key, value] : tag_data) { + for (const auto& [key, value] : tag_data) { _cachedTag.map.insert_or_assign(key, value); } } @@ -729,8 +653,7 @@ template using just_t = T; template -consteval gr::meta::typelist(), portType, portDirection, Attributes...>, Is>...> -repeated_ports_impl(std::index_sequence) { +consteval gr::meta::typelist(), portType, portDirection, Attributes...>, Is>...> repeated_ports_impl(std::index_sequence) { return {}; } } // namespace detail @@ -795,64 +718,39 @@ static_assert(MsgPortIn::kPortType == PortType::MESSAGE); */ class DynamicPort { public: - const std::string &name; - std::int16_t &priority; // → dependents of a higher-prio port should be scheduled first (Q: make this by order of ports?) - std::size_t &min_samples; - std::size_t &max_samples; + const std::string& name; + std::int16_t& priority; // → dependents of a higher-prio port should be scheduled first (Q: make this by order of ports?) + std::size_t& min_samples; + std::size_t& max_samples; private: struct model { // intentionally class-private definition to limit interface exposure and enhance composition - virtual ~ - model() = default; + virtual ~model() = default; - [[nodiscard]] virtual std::any - defaultValue() const noexcept - = 0; + [[nodiscard]] virtual std::any defaultValue() const noexcept = 0; - [[nodiscard]] virtual bool - setDefaultValue(const std::any &val) noexcept - = 0; + [[nodiscard]] virtual bool setDefaultValue(const std::any& val) noexcept = 0; - [[nodiscard]] virtual PortType - type() const noexcept - = 0; + [[nodiscard]] virtual PortType type() const noexcept = 0; - [[nodiscard]] virtual PortDirection - direction() const noexcept - = 0; + [[nodiscard]] virtual PortDirection direction() const noexcept = 0; - [[nodiscard]] virtual std::string_view - domain() const noexcept - = 0; + [[nodiscard]] virtual std::string_view domain() const noexcept = 0; - [[nodiscard]] virtual bool - isSynchronous() noexcept - = 0; + [[nodiscard]] virtual bool isSynchronous() noexcept = 0; - [[nodiscard]] virtual bool - isOptional() noexcept - = 0; + [[nodiscard]] virtual bool isOptional() noexcept = 0; - [[nodiscard]] virtual ConnectionResult - resizeBuffer(std::size_t min_size) noexcept - = 0; + [[nodiscard]] virtual ConnectionResult resizeBuffer(std::size_t min_size) noexcept = 0; - [[nodiscard]] virtual bool - isConnected() const noexcept - = 0; + [[nodiscard]] virtual bool isConnected() const noexcept = 0; - [[nodiscard]] virtual ConnectionResult - disconnect() noexcept - = 0; + [[nodiscard]] virtual ConnectionResult disconnect() noexcept = 0; - [[nodiscard]] virtual ConnectionResult - connect(DynamicPort &dst_port) - = 0; + [[nodiscard]] virtual ConnectionResult connect(DynamicPort& dst_port) = 0; // internal runtime polymorphism access - [[nodiscard]] virtual bool - updateReaderInternal(InternalPortBuffers buffer_other) noexcept - = 0; + [[nodiscard]] virtual bool updateReaderInternal(InternalPortBuffers buffer_other) noexcept = 0; }; std::unique_ptr _accessor; @@ -860,15 +758,11 @@ class DynamicPort { template class wrapper final : public model { using TPortType = std::decay_t; - std::conditional_t _value; + std::conditional_t _value; - [[nodiscard]] InternalPortBuffers - writerHandlerInternal() noexcept { - return _value.writerHandlerInternal(); - }; + [[nodiscard]] InternalPortBuffers writerHandlerInternal() noexcept { return _value.writerHandlerInternal(); }; - [[nodiscard]] bool - updateReaderInternal(InternalPortBuffers buffer_other) noexcept override { + [[nodiscard]] bool updateReaderInternal(InternalPortBuffers buffer_other) noexcept override { if constexpr (T::kIsInput) { return _value.updateReaderInternal(buffer_other); } else { @@ -880,19 +774,13 @@ class DynamicPort { public: wrapper() = delete; - wrapper(const wrapper &) = delete; + wrapper(const wrapper&) = delete; - auto & - operator=(const wrapper &) - = delete; + auto& operator=(const wrapper&) = delete; - auto & - operator=(wrapper &&) - = delete; + auto& operator=(wrapper&&) = delete; - explicit constexpr - wrapper(T &arg) noexcept - : _value{ arg } { + explicit constexpr wrapper(T& arg) noexcept : _value{arg} { if constexpr (T::kIsInput) { static_assert(requires { arg.writerHandlerInternal(); }, "'private void* writerHandlerInternal()' not implemented"); } else { @@ -900,9 +788,7 @@ class DynamicPort { } } - explicit constexpr - wrapper(T &&arg) noexcept - : _value{ std::move(arg) } { + explicit constexpr wrapper(T&& arg) noexcept : _value{std::move(arg)} { if constexpr (T::kIsInput) { static_assert(requires { arg.writerHandlerInternal(); }, "'private void* writerHandlerInternal()' not implemented"); } else { @@ -910,62 +796,29 @@ class DynamicPort { } } - ~ - wrapper() override - = default; + ~wrapper() override = default; - [[nodiscard]] std::any - defaultValue() const noexcept override { - return _value.defaultValue(); - } + [[nodiscard]] std::any defaultValue() const noexcept override { return _value.defaultValue(); } - [[nodiscard]] bool - setDefaultValue(const std::any &val) noexcept override { - return _value.setDefaultValue(val); - } + [[nodiscard]] bool setDefaultValue(const std::any& val) noexcept override { return _value.setDefaultValue(val); } - [[nodiscard]] constexpr PortType - type() const noexcept override { - return _value.type(); - } + [[nodiscard]] constexpr PortType type() const noexcept override { return _value.type(); } - [[nodiscard]] constexpr PortDirection - direction() const noexcept override { - return _value.direction(); - } + [[nodiscard]] constexpr PortDirection direction() const noexcept override { return _value.direction(); } - [[nodiscard]] constexpr std::string_view - domain() const noexcept override { - return _value.domain(); - } + [[nodiscard]] constexpr std::string_view domain() const noexcept override { return _value.domain(); } - [[nodiscard]] bool - isSynchronous() noexcept override { - return _value.isSynchronous(); - } + [[nodiscard]] bool isSynchronous() noexcept override { return _value.isSynchronous(); } - [[nodiscard]] bool - isOptional() noexcept override { - return _value.isOptional(); - } + [[nodiscard]] bool isOptional() noexcept override { return _value.isOptional(); } - [[nodiscard]] ConnectionResult - resizeBuffer(std::size_t min_size) noexcept override { - return _value.resizeBuffer(min_size); - } + [[nodiscard]] ConnectionResult resizeBuffer(std::size_t min_size) noexcept override { return _value.resizeBuffer(min_size); } - [[nodiscard]] bool - isConnected() const noexcept override { - return _value.isConnected(); - } + [[nodiscard]] bool isConnected() const noexcept override { return _value.isConnected(); } - [[nodiscard]] ConnectionResult - disconnect() noexcept override { - return _value.disconnect(); - } + [[nodiscard]] ConnectionResult disconnect() noexcept override { return _value.disconnect(); } - [[nodiscard]] ConnectionResult - connect(DynamicPort &dst_port) override { + [[nodiscard]] ConnectionResult connect(DynamicPort& dst_port) override { using enum gr::ConnectionResult; if constexpr (T::kIsOutput) { auto src_buffer = _value.writerHandlerInternal(); @@ -977,10 +830,7 @@ class DynamicPort { } }; - bool - updateReaderInternal(InternalPortBuffers buffer_other) noexcept { - return _accessor->updateReaderInternal(buffer_other); - } + bool updateReaderInternal(InternalPortBuffers buffer_other) noexcept { return _accessor->updateReaderInternal(buffer_other); } public: using value_type = void; // a sterile port @@ -989,99 +839,60 @@ class DynamicPort { struct non_owned_reference_tag {}; - constexpr - DynamicPort() - = delete; + constexpr DynamicPort() = delete; - DynamicPort(const DynamicPort &arg) = delete; - DynamicPort & - operator=(const DynamicPort &arg) - = delete; + DynamicPort(const DynamicPort& arg) = delete; + DynamicPort& operator=(const DynamicPort& arg) = delete; - DynamicPort(DynamicPort &&arg) = default; - DynamicPort & - operator=(DynamicPort &&arg) - = delete; + DynamicPort(DynamicPort&& arg) = default; + DynamicPort& operator=(DynamicPort&& arg) = delete; // TODO: The lifetime of ports is a problem here, if we keep // a reference to the port in DynamicPort, the port object // can not be reallocated template - explicit constexpr DynamicPort(T &arg, non_owned_reference_tag) noexcept - : name(arg.name), priority(arg.priority), min_samples(arg.min_samples), max_samples(arg.max_samples), _accessor{ std::make_unique>(arg) } {} + explicit constexpr DynamicPort(T& arg, non_owned_reference_tag) noexcept : name(arg.name), priority(arg.priority), min_samples(arg.min_samples), max_samples(arg.max_samples), _accessor{std::make_unique>(arg)} {} template - explicit constexpr DynamicPort(T &&arg, owned_value_tag) noexcept - : name(arg.name), priority(arg.priority), min_samples(arg.min_samples), max_samples(arg.max_samples), _accessor{ std::make_unique>(std::forward(arg)) } {} + explicit constexpr DynamicPort(T&& arg, owned_value_tag) noexcept : name(arg.name), priority(arg.priority), min_samples(arg.min_samples), max_samples(arg.max_samples), _accessor{std::make_unique>(std::forward(arg))} {} - [[nodiscard]] std::any - defaultValue() const noexcept { - return _accessor->defaultValue(); - } + [[nodiscard]] std::any defaultValue() const noexcept { return _accessor->defaultValue(); } - [[nodiscard]] bool - setDefaultValue(const std::any &val) noexcept { - return _accessor->setDefaultValue(val); - } + [[nodiscard]] bool setDefaultValue(const std::any& val) noexcept { return _accessor->setDefaultValue(val); } - [[nodiscard]] PortType - type() const noexcept { - return _accessor->type(); - } + [[nodiscard]] PortType type() const noexcept { return _accessor->type(); } - [[nodiscard]] PortDirection - direction() const noexcept { - return _accessor->direction(); - } + [[nodiscard]] PortDirection direction() const noexcept { return _accessor->direction(); } - [[nodiscard]] std::string_view - domain() const noexcept { - return _accessor->domain(); - } + [[nodiscard]] std::string_view domain() const noexcept { return _accessor->domain(); } - [[nodiscard]] bool - isSynchronous() noexcept { - return _accessor->isSynchronous(); - } + [[nodiscard]] bool isSynchronous() noexcept { return _accessor->isSynchronous(); } - [[nodiscard]] bool - isOptional() noexcept { - return _accessor->isOptional(); - } + [[nodiscard]] bool isOptional() noexcept { return _accessor->isOptional(); } - [[nodiscard]] ConnectionResult - resizeBuffer(std::size_t min_size) { + [[nodiscard]] ConnectionResult resizeBuffer(std::size_t min_size) { if (direction() == PortDirection::OUTPUT) { return _accessor->resizeBuffer(min_size); } return ConnectionResult::FAILED; } - [[nodiscard]] bool - isConnected() const noexcept { - return _accessor->isConnected(); - } + [[nodiscard]] bool isConnected() const noexcept { return _accessor->isConnected(); } - [[nodiscard]] ConnectionResult - disconnect() noexcept { - return _accessor->disconnect(); - } + [[nodiscard]] ConnectionResult disconnect() noexcept { return _accessor->disconnect(); } - [[nodiscard]] ConnectionResult - connect(DynamicPort &dst_port) { - return _accessor->connect(dst_port); - } + [[nodiscard]] ConnectionResult connect(DynamicPort& dst_port) { return _accessor->connect(dst_port); } }; static_assert(PortLike); namespace detail { template -concept TagPredicate = requires(const T &t, const Tag &tag, Tag::signed_index_type readPosition) { +concept TagPredicate = requires(const T& t, const Tag& tag, Tag::signed_index_type readPosition) { { t(tag, readPosition) } -> std::convertible_to; }; -inline constexpr TagPredicate auto defaultTagMatcher = [](const Tag &tag, Tag::signed_index_type readPosition) noexcept { return tag.index >= readPosition; }; -inline constexpr TagPredicate auto defaultEOSTagMatcher = [](const Tag &tag, Tag::signed_index_type readPosition) noexcept { +inline constexpr TagPredicate auto defaultTagMatcher = [](const Tag& tag, Tag::signed_index_type readPosition) noexcept { return tag.index >= readPosition; }; +inline constexpr TagPredicate auto defaultEOSTagMatcher = [](const Tag& tag, Tag::signed_index_type readPosition) noexcept { auto eosTagIter = tag.map.find(gr::tag::END_OF_STREAM); if (eosTagIter != tag.map.end() && eosTagIter->second == true) { if (tag.index >= readPosition || tag.index < 0) { @@ -1092,8 +903,7 @@ inline constexpr TagPredicate auto defaultEOSTagMatcher = [](const Tag &tag, Tag }; } // namespace detail -inline constexpr std::optional -nSamplesToNextTagConditional(const PortLike auto &port, detail::TagPredicate auto &predicate, Tag::signed_index_type readOffset) { +inline constexpr std::optional nSamplesToNextTagConditional(const PortLike auto& port, detail::TagPredicate auto& predicate, Tag::signed_index_type readOffset) { const gr::ConsumableSpan auto tagData = port.tagReader().get(); if (!port.isConnected() || tagData.empty()) [[likely]] { return std::nullopt; // default: no tags in sight @@ -1101,7 +911,7 @@ nSamplesToNextTagConditional(const PortLike auto &port, detail::TagPredicate aut const Tag::signed_index_type readPosition = port.streamReader().position(); // at least one tag is present -> if tag is not on the first tag position read up to the tag position, or if the tag has a special 'index = -1' - const auto firstMatchingTag = std::ranges::find_if(tagData, [&](const auto &tag) { return predicate(tag, readPosition + readOffset); }); + const auto firstMatchingTag = std::ranges::find_if(tagData, [&](const auto& tag) { return predicate(tag, readPosition + readOffset); }); std::ignore = tagData.consume(0UZ); if (firstMatchingTag != tagData.end()) { return static_cast(std::max(firstMatchingTag->index - readPosition, Tag::signed_index_type(0))); // Tags in the past will have a negative distance -> deliberately map them to '0' @@ -1110,19 +920,11 @@ nSamplesToNextTagConditional(const PortLike auto &port, detail::TagPredicate aut } } -inline constexpr std::optional -nSamplesUntilNextTag(const PortLike auto &port, Tag::signed_index_type offset = 0) { - return nSamplesToNextTagConditional(port, detail::defaultTagMatcher, offset); -} +inline constexpr std::optional nSamplesUntilNextTag(const PortLike auto& port, Tag::signed_index_type offset = 0) { return nSamplesToNextTagConditional(port, detail::defaultTagMatcher, offset); } -inline constexpr std::optional -samples_to_eos_tag(const PortLike auto &port, Tag::signed_index_type offset = 0) { - return nSamplesToNextTagConditional(port, detail::defaultEOSTagMatcher, offset); -} +inline constexpr std::optional samples_to_eos_tag(const PortLike auto& port, Tag::signed_index_type offset = 0) { return nSamplesToNextTagConditional(port, detail::defaultEOSTagMatcher, offset); } } // namespace gr -ENABLE_REFLECTION_FOR_TEMPLATE_FULL((typename T, gr::fixed_string portName, gr::PortType portType, gr::PortDirection portDirection, typename... Attributes), - (gr::Port), kDirection, kPortType, kIsInput, kIsOutput, kIsSynch, kIsOptional, name, priority, min_samples, - max_samples, metaInfo) +ENABLE_REFLECTION_FOR_TEMPLATE_FULL((typename T, gr::fixed_string portName, gr::PortType portType, gr::PortDirection portDirection, typename... Attributes), (gr::Port), kDirection, kPortType, kIsInput, kIsOutput, kIsSynch, kIsOptional, name, priority, min_samples, max_samples, metaInfo) #endif // GNURADIO_PORT_HPP diff --git a/core/include/gnuradio-4.0/plugin.hpp b/core/include/gnuradio-4.0/plugin.hpp index d22e31f1..a916d3b4 100644 --- a/core/include/gnuradio-4.0/plugin.hpp +++ b/core/include/gnuradio-4.0/plugin.hpp @@ -8,7 +8,6 @@ #include #include "BlockRegistry.hpp" -#include "Graph.hpp" #include @@ -26,20 +25,15 @@ struct GNURADIO_PLUGIN_EXPORT gr_plugin_metadata { class GNURADIO_PLUGIN_EXPORT gr_plugin_base { public: - gr_plugin_metadata *metadata = nullptr; + gr_plugin_metadata* metadata = nullptr; virtual ~gr_plugin_base(); - virtual std::uint8_t - abi_version() const - = 0; + virtual std::uint8_t abi_version() const = 0; - virtual std::span - providedBlocks() const = 0; - virtual std::unique_ptr - createBlock(std::string_view name, std::string_view type, const gr::property_map ¶ms) = 0; - virtual std::vector - knownBlockParameterizations(std::string_view block) const = 0; + virtual std::span providedBlocks() const = 0; + virtual std::unique_ptr createBlock(std::string_view name, std::string_view type, const gr::property_map& params) = 0; + virtual std::vector knownBlockParameterizations(std::string_view block) const = 0; }; namespace gr { @@ -51,29 +45,16 @@ class plugin : public gr_plugin_base { public: plugin() {} - std::uint8_t - abi_version() const override { - return ABI_VERSION; - } + std::uint8_t abi_version() const override { return ABI_VERSION; } - std::span - providedBlocks() const override { - return registry.providedBlocks(); - } + std::span providedBlocks() const override { return registry.providedBlocks(); } - std::unique_ptr - createBlock(std::string_view name, std::string_view type, const property_map ¶ms) override { - return registry.createBlock(name, type, params); - } + std::unique_ptr createBlock(std::string_view name, std::string_view type, const property_map& params) override { return registry.createBlock(name, type, params); } - std::vector - knownBlockParameterizations(std::string_view block) const override { - return registry.knownBlockParameterizations(block); - } + std::vector knownBlockParameterizations(std::string_view block) const override { return registry.knownBlockParameterizations(block); } template - void - addBlockType(std::string blockType = {}, std::string blockParams = {}) { + void addBlockType(std::string blockType = {}, std::string blockParams = {}) { registry.addBlockType(std::move(blockType), std::move(blockParams)); } }; @@ -93,32 +74,27 @@ class plugin : public gr_plugin_base { * Example usage: * GR_PLUGIN("Good Base Plugin", "Unknown", "LGPL3", "v1") */ -#define GR_PLUGIN(Name, Author, License, Version) \ - inline namespace GR_PLUGIN_DEFINITION_NAMESPACE { \ - gr::plugin<> & \ - grPluginInstance() { \ - static gr::plugin<> *instance = [] { \ - auto *result = new gr::plugin<>(); \ - static gr_plugin_metadata plugin_metadata{ Name, Author, License, Version }; \ - result->metadata = &plugin_metadata; \ - return result; \ - }(); \ - return *instance; \ - } \ - } \ - extern "C" { \ - void GNURADIO_PLUGIN_EXPORT \ - gr_plugin_make(gr_plugin_base **plugin) { \ - *plugin = &grPluginInstance(); \ - } \ - void GNURADIO_PLUGIN_EXPORT \ - gr_plugin_free(gr_plugin_base *plugin) { \ - if (plugin != &grPluginInstance()) { \ - assert(false && "Requested to delete something that is not us"); \ - return; \ - } \ - delete plugin; \ - } \ +#define GR_PLUGIN(Name, Author, License, Version) \ + inline namespace GR_PLUGIN_DEFINITION_NAMESPACE { \ + gr::plugin<>& grPluginInstance() { \ + static gr::plugin<>* instance = [] { \ + auto* result = new gr::plugin<>(); \ + static gr_plugin_metadata plugin_metadata{Name, Author, License, Version}; \ + result->metadata = &plugin_metadata; \ + return result; \ + }(); \ + return *instance; \ + } \ + } \ + extern "C" { \ + void GNURADIO_PLUGIN_EXPORT gr_plugin_make(gr_plugin_base** plugin) { *plugin = &grPluginInstance(); } \ + void GNURADIO_PLUGIN_EXPORT gr_plugin_free(gr_plugin_base* plugin) { \ + if (plugin != &grPluginInstance()) { \ + assert(false && "Requested to delete something that is not us"); \ + return; \ + } \ + delete plugin; \ + } \ } #endif // include guard diff --git a/core/src/main.cpp b/core/src/main.cpp index 2d929c37..cbf932db 100644 --- a/core/src/main.cpp +++ b/core/src/main.cpp @@ -3,17 +3,14 @@ #include -#include // contains the project and compiler flags definitions #include +#include // contains the project and compiler flags definitions template struct CountSource : public gr::Block> { gr::PortOut random; - constexpr T - processOne() { - return 42; - } + constexpr T processOne() { return 42; } }; ENABLE_REFLECTION_FOR_TEMPLATE(CountSource, random); @@ -22,10 +19,7 @@ template struct ExpectSink : public gr::Block> { gr::PortIn sink; - void - processOne(T value) { - std::cout << value << std::endl; - } + void processOne(T value) { std::cout << value << std::endl; } }; ENABLE_REFLECTION_FOR_TEMPLATE(ExpectSink, sink); @@ -36,8 +30,7 @@ struct scale : public gr::Block> { gr::PortOut scaled; template V> - [[nodiscard]] constexpr auto - processOne(V a) const noexcept { + [[nodiscard]] constexpr auto processOne(V a) const noexcept { return a * Scale; } }; @@ -51,8 +44,7 @@ struct adder : public gr::Block> { gr::PortOut sum; template V> - [[nodiscard]] constexpr auto - processOne(V a, V b) const noexcept { + [[nodiscard]] constexpr auto processOne(V a, V b) const noexcept { return a + b; } }; @@ -67,22 +59,20 @@ class duplicate : public gr::Block, gr::meta::typelist; - [[nodiscard]] constexpr return_type - processOne(T a) const noexcept { - return [&a](std::index_sequence) { return std::make_tuple(((void) Is, a)...); }(std::make_index_sequence()); + [[nodiscard]] constexpr return_type processOne(T a) const noexcept { + return [&a](std::index_sequence) { return std::make_tuple(((void)Is, a)...); }(std::make_index_sequence()); } }; template - requires(Depth > 0) +requires(Depth > 0) struct delay : public gr::Block> { gr::PortIn in; gr::PortOut out; std::array buffer = {}; int pos = 0; - [[nodiscard]] constexpr T - processOne(T val) noexcept { + [[nodiscard]] constexpr T processOne(T val) noexcept { T ret = buffer[pos]; buffer[pos] = val; if (pos == Depth - 1) { @@ -96,8 +86,7 @@ struct delay : public gr::Block> { ENABLE_REFLECTION_FOR_TEMPLATE_FULL((typename T, std::size_t Depth), (delay), in, out); -int -main() { +int main() { using gr::merge; using gr::mergeByIndex; @@ -110,8 +99,8 @@ main() { auto merged = mergeByIndex<0, 0>(scale(), mergeByIndex<0, 0>(scale(), adder())); // execute graph - std::array a = { 1, 2, 3, 4 }; - std::array b = { 10, 10, 10, 10 }; + std::array a = {1, 2, 3, 4}; + std::array b = {10, 10, 10, 10}; int r = 0; for (std::size_t i = 0; i < 4; ++i) { @@ -127,7 +116,7 @@ main() { auto merged = mergeByIndex<0, 0>(duplicate(), scale()); // execute graph - std::array a = { 1, 2, 3, 4 }; + std::array a = {1, 2, 3, 4}; for (std::size_t i = 0; i < 4; ++i) { auto tuple = merged.processOne(i, a[i]); @@ -140,8 +129,8 @@ main() { auto merged = merge<"scaled", "addend1">(scale(), adder()); // execute graph - std::array a = { 1, 2, 3, 4 }; - std::array b = { 10, 10, 10, 10 }; + std::array a = {1, 2, 3, 4}; + std::array b = {10, 10, 10, 10}; int r = 0; for (std::size_t i = 0; i < 4; ++i) { @@ -157,7 +146,7 @@ main() { auto merged = mergeByIndex<1, 0>(mergeByIndex<0, 0>(duplicate(), scale()), scale()); // execute graph - std::array a = { 1, 2, 3, 4 }; + std::array a = {1, 2, 3, 4}; for (std::size_t i = 0; i < 4; ++i) { auto tuple = merged.processOne(i, a[i]); diff --git a/core/test/CMakeLists.txt b/core/test/CMakeLists.txt index 06ef5858..4c1c156a 100644 --- a/core/test/CMakeLists.txt +++ b/core/test/CMakeLists.txt @@ -21,6 +21,7 @@ endfunction() function(add_ut_test TEST_NAME) add_executable(${TEST_NAME} ${TEST_NAME}.cpp) setup_test(${TEST_NAME}) + set_property(TEST ${TEST_NAME} PROPERTY ENVIRONMENT "GNURADIO4_PLUGIN_DIRECTORIES=${CMAKE_CURRENT_BINARY_DIR}/plugins") endfunction() function(add_app_test TEST_NAME) @@ -41,15 +42,12 @@ add_ut_test(qa_reader_writer_lock) add_ut_test(qa_Settings) add_ut_test(qa_Tags) add_ut_test(qa_Messages) +add_ut_test(qa_GraphMessages) add_ut_test(qa_thread_affinity) add_ut_test(qa_thread_pool) -if (NOT NOPLUGINS) - if (NOT EMSCRIPTEN) - add_subdirectory(plugins) - add_app_test(qa_grc) - add_app_test(qa_plugins_test) - else () - add_ut_test(qa_grc) - endif () +if (ENABLE_BLOCK_REGISTRY AND ENABLE_BLOCK_PLUGINS) + add_app_test(qa_grc) + add_subdirectory(plugins) + add_app_test(qa_plugins_test) endif () diff --git a/core/test/app_plugins_test.cpp b/core/test/app_plugins_test.cpp index 4ed67c78..5878c6f7 100644 --- a/core/test/app_plugins_test.cpp +++ b/core/test/app_plugins_test.cpp @@ -28,8 +28,7 @@ const auto builtin_multiply = "builtin_multiply"s; const auto builtin_counter = "builtin_counter"s; } // namespace names -int -main(int argc, char *argv[]) { +int main(int argc, char* argv[]) { std::vector paths; if (argc < 2) { paths.emplace_back("test/plugins"); @@ -47,38 +46,38 @@ main(int argc, char *argv[]) { fmt::print("PluginLoaderTests\n"); using namespace gr; - for (const auto &plugin [[maybe_unused]] : context.loader.plugins()) { + for (const auto& plugin [[maybe_unused]] : context.loader.plugins()) { assert(plugin->metadata->plugin_name.starts_with("Good")); } - for (const auto &plugin [[maybe_unused]] : context.loader.failed_plugins()) { + for (const auto& plugin [[maybe_unused]] : context.loader.failed_plugins()) { assert(plugin.first.ends_with("bad_plugin.so")); } auto known = context.loader.knownBlocks(); - std::vector requireds{ names::cout_sink, names::fixed_source, names::divide, names::multiply }; + std::vector requireds{names::cout_sink, names::fixed_source, names::divide, names::multiply}; - for (const auto &plugin : known) { + for (const auto& plugin : known) { fmt::print("Known: {}\n", plugin); } - for (const auto &required [[maybe_unused]] : requireds) { + for (const auto& required [[maybe_unused]] : requireds) { assert(std::ranges::find(known, required) != known.end()); } gr::Graph testGraph; // Instantiate the node that is defined in a plugin - auto &block_source = context.loader.instantiateInGraph(testGraph, names::fixed_source, "double"); + auto& block_source = testGraph.emplaceBlock(names::fixed_source, "double", context.loader); // Instantiate a built-in node in a static way gr::property_map block_multiply_1_params; block_multiply_1_params["factor"] = 2.0; - auto &block_multiply_1 = testGraph.emplaceBlock>(block_multiply_1_params); + auto& block_multiply_1 = testGraph.emplaceBlock>(block_multiply_1_params); // Instantiate a built-in node via the plugin loader - auto &block_multiply_2 = context.loader.instantiateInGraph(testGraph, names::builtin_multiply, "double"); - auto &block_counter = context.loader.instantiateInGraph(testGraph, names::builtin_counter, "double"); + auto& block_multiply_2 = testGraph.emplaceBlock(names::builtin_multiply, "double", context.loader); + auto &block_counter = context.loadernames::builtin_counter, "double"); // const std::size_t repeats = 100; @@ -87,7 +86,7 @@ main(int argc, char *argv[]) { auto block_sink_load = context.loader.instantiate(names::cout_sink, "double", block_sink_params); assert(block_sink_load); - auto &block_sink = testGraph.addBlock(std::move(block_sink_load)); + auto& block_sink = testGraph.addBlock(std::move(block_sink_load)); auto connection_1 [[maybe_unused]] = testGraph.connect(block_source, 0, block_multiply_1, 0); auto connection_2 [[maybe_unused]] = testGraph.connect(block_multiply_1, 0, block_multiply_2, 0); diff --git a/core/test/qa_GraphMessages.cpp b/core/test/qa_GraphMessages.cpp new file mode 100644 index 00000000..cf490097 --- /dev/null +++ b/core/test/qa_GraphMessages.cpp @@ -0,0 +1,295 @@ +#include + +#include "gnuradio-4.0/Block.hpp" +#include "gnuradio-4.0/Message.hpp" +#include +#include +#include +#include + +#include +#include + +#include + +using namespace std::chrono_literals; +using namespace std::string_literals; + +using namespace gr::message; + +namespace gr::testing { + +using namespace boost::ut; +using namespace gr; + +auto returnReplyMsg(gr::MsgPortIn& port) { + expect(eq(port.streamReader().available(), 1UZ)) << "didn't receive a reply message"; + ConsumableSpan auto span = port.streamReader().get(1UZ); + Message msg = span[0]; + expect(span.consume(span.size())); + fmt::print("Test got a reply: {}\n", msg); + return msg; +}; + +const boost::ut::suite NonRunningGraphTests = [] { + using namespace std::string_literals; + using namespace boost::ut; + using namespace gr; + using enum gr::message::Command; + +#if 0 + "Block addition tests"_test = [] { + gr::MsgPortOut toGraph; + gr::Graph testGraph; + gr::MsgPortIn fromGraph; + + expect(eq(ConnectionResult::SUCCESS, toGraph.connect(testGraph.msgIn))); + expect(eq(ConnectionResult::SUCCESS, testGraph.msgOut.connect(fromGraph))); + + "Add a valid block"_test = [&] { + sendMessage(toGraph, "" /* serviceName */, graph::property::kEmplaceBlock /* endpoint */, // + {{"type", "good::multiply"}, {"parameters", "float"}, {"properties", property_map{}}} /* data */); + expect(nothrow([&] { testGraph.processScheduledMessages(); })) << "manually execute processing of messages"; + + const Message reply = returnReplyMsg(fromGraph); + + expect(reply.data.has_value()); + expect(eq(testGraph.blocks().size(), 1)); + }; + + "Add an invalid block"_test = [&] { + sendMessage(toGraph, "" /* serviceName */, graph::property::kEmplaceBlock /* endpoint */, // + {{"type", "doesnt_exist::multiply"}, {"parameters", "float"}, {"properties", property_map{}}} /* data */); + expect(nothrow([&] { testGraph.processScheduledMessages(); })) << "manually execute processing of messages"; + + const Message reply = returnReplyMsg(fromGraph); + + expect(!reply.data.has_value()); + expect(eq(testGraph.blocks().size(), 1)); + }; + }; + + "Block removal tests"_test = [] { + gr::MsgPortOut toGraph; + gr::Graph testGraph; + gr::MsgPortIn fromGraph; + + auto& block = testGraph.emplaceBlock("good::multiply", "float", {}); + expect(eq(testGraph.blocks().size(), 1)); + + expect(eq(ConnectionResult::SUCCESS, toGraph.connect(testGraph.msgIn))); + expect(eq(ConnectionResult::SUCCESS, testGraph.msgOut.connect(fromGraph))); + + "Remove a known block"_test = [&] { + auto& temporaryBlock = testGraph.emplaceBlock("good::multiply", "float", {}); + expect(eq(testGraph.blocks().size(), 2)); + + sendMessage(toGraph, "" /* serviceName */, graph::property::kRemoveBlock /* endpoint */, // + {{"uniqueName", std::string(temporaryBlock.uniqueName())}} /* data */); + expect(nothrow([&] { testGraph.processScheduledMessages(); })) << "manually execute processing of messages"; + + const Message reply = returnReplyMsg(fromGraph); + + expect(reply.data.has_value()); + expect(eq(testGraph.blocks().size(), 1)); + }; + + "Remove an unknown block"_test = [&] { + sendMessage(toGraph, "" /* serviceName */, graph::property::kRemoveBlock /* endpoint */, // + {{"uniqueName", "this_block_is_unknown"}} /* data */); + expect(nothrow([&] { testGraph.processScheduledMessages(); })) << "manually execute processing of messages"; + + const Message reply = returnReplyMsg(fromGraph); + + expect(!reply.data.has_value()); + expect(eq(testGraph.blocks().size(), 1)); + }; + }; + + "Block replacement tests"_test = [] { + gr::MsgPortOut toGraph; + gr::Graph testGraph; + gr::MsgPortIn fromGraph; + + auto& block = testGraph.emplaceBlock("good::multiply", "float", {}); + expect(eq(testGraph.blocks().size(), 1)); + + expect(eq(ConnectionResult::SUCCESS, toGraph.connect(testGraph.msgIn))); + expect(eq(ConnectionResult::SUCCESS, testGraph.msgOut.connect(fromGraph))); + + "Replace a known block"_test = [&] { + auto& temporaryBlock = testGraph.emplaceBlock("good::multiply", "float", {}); + expect(eq(testGraph.blocks().size(), 2)); + + sendMessage(toGraph, "" /* serviceName */, graph::property::kReplaceBlock /* endpoint */, // + {{"uniqueName", std::string(temporaryBlock.uniqueName())}, // + {"type", "good::multiply"}, {"parameters", "float"}, {"properties", property_map{}}} /* data */); + expect(nothrow([&] { testGraph.processScheduledMessages(); })) << "manually execute processing of messages"; + + const Message reply = returnReplyMsg(fromGraph); + + expect(reply.data.has_value()); + }; + + "Replace an unknown block"_test = [&] { + sendMessage(toGraph, "" /* serviceName */, graph::property::kReplaceBlock /* endpoint */, // + {{"uniqueName", "this_block_is_unknown"}, // + {"type", "good::multiply"}, {"parameters", "float"}, {"properties", property_map{}}} /* data */); + expect(nothrow([&] { testGraph.processScheduledMessages(); })) << "manually execute processing of messages"; + + const Message reply = returnReplyMsg(fromGraph); + + expect(!reply.data.has_value()); + }; + + "Replace with an unknown block"_test = [&] { + sendMessage(toGraph, "" /* serviceName */, graph::property::kReplaceBlock /* endpoint */, // + {{"uniqueName", std::string(block.uniqueName())}, // + {"type", "doesnt_exist::multiply"}, {"parameters", "float"}, {"properties", property_map{}}} /* data */); + expect(nothrow([&] { testGraph.processScheduledMessages(); })) << "manually execute processing of messages"; + + const Message reply = returnReplyMsg(fromGraph); + + expect(!reply.data.has_value()); + }; + }; + + "Edge addition tests"_test = [&] { + gr::MsgPortOut toGraph; + gr::Graph testGraph; + gr::MsgPortIn fromGraph; + + auto& blockOut = testGraph.emplaceBlock("good::multiply", "float", {}); + auto& blockIn = testGraph.emplaceBlock("good::multiply", "float", {}); + auto& blockWrongType = testGraph.emplaceBlock("good::multiply", "double", {}); + + expect(eq(ConnectionResult::SUCCESS, toGraph.connect(testGraph.msgIn))); + expect(eq(ConnectionResult::SUCCESS, testGraph.msgOut.connect(fromGraph))); + + "Add an edge"_test = [&] { + sendMessage(toGraph, "" /* serviceName */, graph::property::kEmplaceEdge /* endpoint */, // + {{"sourceBlock", std::string(blockOut.uniqueName())}, {"sourcePort", "out"}, // + {"destinationBlock", std::string(blockIn.uniqueName())}, {"destinationPort", "in"}} /* data */); + expect(nothrow([&] { testGraph.processScheduledMessages(); })) << "manually execute processing of messages"; + + const Message reply = returnReplyMsg(fromGraph); + + expect(reply.data.has_value()); + }; + + "Fail to add an edge because source port is invalid"_test = [&] { + sendMessage(toGraph, "" /* serviceName */, graph::property::kEmplaceEdge /* endpoint */, // + {{"sourceBlock", std::string(blockOut.uniqueName())}, {"sourcePort", "OUTPUT"}, // + {"destinationBlock", std::string(blockIn.uniqueName())}, {"destinationPort", "in"}} /* data */); + expect(nothrow([&] { testGraph.processScheduledMessages(); })) << "manually execute processing of messages"; + + const Message reply = returnReplyMsg(fromGraph); + expect(!reply.data.has_value()); + }; + + "Fail to add an edge because destination port is invalid"_test = [&] { + sendMessage(toGraph, "" /* serviceName */, graph::property::kEmplaceEdge /* endpoint */, // + {{"sourceBlock", std::string(blockOut.uniqueName())}, {"sourcePort", "in"}, // + {"destinationBlock", std::string(blockIn.uniqueName())}, {"destinationPort", "INPUT"}} /* data */); + expect(nothrow([&] { testGraph.processScheduledMessages(); })) << "manually execute processing of messages"; + + const Message reply = returnReplyMsg(fromGraph); + expect(!reply.data.has_value()); + }; + + "Fail to add an edge because ports are not compatible"_test = [&] { + sendMessage(toGraph, "" /* serviceName */, graph::property::kEmplaceEdge /* endpoint */, // + {{"sourceBlock", std::string(blockOut.uniqueName())}, {"sourcePort", "out"}, // + {"destinationBlock", std::string(blockWrongType.uniqueName())}, {"destinationPort", "in"}} /* data */); + expect(nothrow([&] { testGraph.processScheduledMessages(); })) << "manually execute processing of messages"; + + const Message reply = returnReplyMsg(fromGraph); + expect(!reply.data.has_value()); + }; + }; +#endif +}; + +const boost::ut::suite RunningGraphTests = [] { + using namespace std::string_literals; + using namespace boost::ut; + using namespace gr; + using enum gr::message::Command; + + std::atomic_bool keep_running = true; + + gr::scheduler::Simple scheduler{gr::Graph()}; + + // auto& source = scheduler.graph().emplaceBlock>(); + auto& source = scheduler.graph().emplaceBlock>(); + auto& sink = scheduler.graph().emplaceBlock>(); + expect(eq(ConnectionResult::SUCCESS, scheduler.graph().connect<"out">(source).to<"in">(sink))); + + gr::MsgPortOut toGraph; + gr::MsgPortIn fromGraph; + expect(eq(ConnectionResult::SUCCESS, toGraph.connect(scheduler.graph().msgIn))); + expect(eq(ConnectionResult::SUCCESS, scheduler.graph().msgOut.connect(fromGraph))); + + auto waitForAReply = [&](std::chrono::milliseconds maxWait = 1s, std::source_location source = std::source_location::current()) { + auto startedAt = std::chrono::system_clock::now(); + while (fromGraph.streamReader().available() == 0) { + std::this_thread::sleep_for(100ms); + if (std::chrono::system_clock::now() - startedAt > maxWait) { + break; + } + } + expect(fromGraph.streamReader().available() > 0) << "Caller at" << source.file_name() << ":" << source.line(); + return fromGraph.streamReader().available() > 0; + }; + + auto emplaceTestBlock = [&](std::string type, std::string params, property_map properties) { + sendMessage(toGraph, "" /* serviceName */, graph::property::kEmplaceBlock /* endpoint */, // + {{"type", std::move(type)}, {"parameters", std::move(params)}, {"properties", std::move(properties)}} /* data */); + expect(waitForAReply()) << "didn't receive a reply message"; + + const Message reply = returnReplyMsg(fromGraph); + expect(reply.data.has_value()) << "emplace block failed and returned an error"; + return reply.data.has_value() ? std::get(reply.data.value().at("uniqueName"s)) : std::string{}; + }; + + auto emplaceTestEdge = [&](std::string sourceBlock, std::string sourcePort, std::string destinationBlock, std::string destinationPort) { + sendMessage(toGraph, "" /* serviceName */, graph::property::kEmplaceEdge /* endpoint */, // + {{"sourceBlock", sourceBlock}, {"sourcePort", sourcePort}, // + {"destinationBlock", destinationBlock}, {"destinationPort", destinationPort}} /* data */); + expect(waitForAReply()) << "didn't receive a reply message"; + + const Message reply = returnReplyMsg(fromGraph); + expect(reply.data.has_value()) << "emplace block failed and returned an error"; + }; + + std::thread tester([&] { + // Adding a few blocks + auto multiply1 = emplaceTestBlock("good::multiply"s, "float"s, property_map{}); + auto multiply2 = emplaceTestBlock("good::multiply"s, "float"s, property_map{}); + + expect(eq(scheduler.graph().blocks().size(), 4)); + + emplaceTestEdge(source.unique_name, "out", multiply1, "in"); + emplaceTestEdge(multiply1, "out", sink.unique_name, "in"); + + // expect(eq(scheduler.graph().edges().size(), 2)); + while (sink.count < 10) { + std::this_thread::sleep_for(100ms); + } + + keep_running = false; + scheduler.requestStop(); + }); + + while (keep_running) { + scheduler.processScheduledMessages(); + std::ignore = scheduler.runAndWait(); + fmt::print("Counting sink counted to {}\n", sink.count); + } + + tester.join(); +}; + +} // namespace gr::testing + +int main() { /* tests are statically executed */ } diff --git a/core/test/qa_plugins_test.cpp b/core/test/qa_plugins_test.cpp index 47f9a7ab..1d6ad4db 100644 --- a/core/test/qa_plugins_test.cpp +++ b/core/test/qa_plugins_test.cpp @@ -8,6 +8,7 @@ #include #include +#include #include using namespace std::chrono_literals; @@ -149,7 +150,7 @@ const boost::ut::suite BasicPluginBlocksConnectionTests = [] { gr::Graph testGraph; // Instantiate the node that is defined in a plugin - auto& block_source = context().loader.instantiateInGraph(testGraph, names::fixed_source, "double"); + auto& block_source = testGraph.emplaceBlock(names::fixed_source, "double", {}, context().loader); // Instantiate a built-in node in a static way gr::property_map block_multiply_1_params; @@ -157,10 +158,10 @@ const boost::ut::suite BasicPluginBlocksConnectionTests = [] { auto& block_multiply_double = testGraph.emplaceBlock>(block_multiply_1_params); // Instantiate a built-in node via the plugin loader - auto& block_multiply_float = context().loader.instantiateInGraph(testGraph, names::builtin_multiply, "float"); + auto& block_multiply_float = testGraph.emplaceBlock(names::builtin_multiply, "float", {}, context().loader); - auto& block_convert_to_float = context().loader.instantiateInGraph(testGraph, names::convert, "double,float"); - auto& block_convert_to_double = context().loader.instantiateInGraph(testGraph, names::convert, "float,double"); + auto& block_convert_to_float = testGraph.emplaceBlock(names::convert, "double,float", {}, context().loader); + auto& block_convert_to_double = testGraph.emplaceBlock(names::convert, "float,double", {}, context().loader); // std::size_t repeats = 10; diff --git a/meson.build b/meson.build deleted file mode 100644 index 2df3ffb8..00000000 --- a/meson.build +++ /dev/null @@ -1,66 +0,0 @@ -project('graph-prototype', 'cpp', - version : '0.1', - default_options : ['warning_level=0', 'cpp_std=gnu++20']) - -clang_warnings = [ # '-Werror', # TODO: enable once the flags are set for the graph-prototype only and not also it's depenencies -'-Wall', '-Wextra', '-Wshadow','-Wnon-virtual-dtor','-Wold-style-cast','-Wcast-align','-Wunused','-Woverloaded-virtual','-Wpedantic','-Wconversion','-Wsign-conversion','-Wnull-dereference','-Wdouble-promotion','-Wformat=2','-Wno-unknown-pragmas','-Wimplicit-fallthrough'] -gcc_warnings = clang_warnings + [ '-Wno-dangling-reference', # TODO: remove this once the fmt dangling reference bug is fixed -'-Wmisleading-indentation','-Wduplicated-cond','-Wduplicated-branches','-Wlogical-op','-Wuseless-cast','-Wno-interference-size'] - -compiler = meson.get_compiler('cpp') -if compiler.get_id() == 'gcc' - message('Compiler: GCC') - add_global_arguments(gcc_warnings, language: 'cpp') -elif compiler.get_id() == 'clang' - message('Compiler: LLVM/clang') - add_global_arguments(gcc_warnings, language: 'cpp') -endif - - -fmt_dep = dependency('fmt', version:'8.1.1') -ut_dep = dependency('boost.ut') - -cmake = import('cmake') -libreflcpp = cmake.subproject('refl-cpp') -reflcpp_dep = libreflcpp.dependency('refl-cpp') -libpmtv = subproject('pmt') -pmt_dep = libpmtv.get_variable('pmt_dep') - - -graph_prototype_options = [] -if meson.is_cross_build() - if meson.get_external_property('EMSCRIPTEN', false) - graph_prototype_options = ['-s','ALLOW_MEMORY_GROWTH=1','-fwasm-exceptions','-pthread','SHELL:-s PTHREAD_POOL_SIZE=30"'] - endif -endif - -# Determine compiler flags based on build type -all_compiler_flags = '' -if get_option('buildtype') == 'debug' - all_compiler_flags = '-g' -elif get_option('buildtype') == 'release' - all_compiler_flags = '-O3' -elif get_option('buildtype') == 'debugoptimized' - all_compiler_flags = '-O2 -g' -elif get_option('buildtype') == 'minsize' - all_compiler_flags = '-Os' -endif - -# Additional compiler flags from the global arguments -all_compiler_flags += ' ' + ' '.join(gcc_warnings) + ' '.join(graph_prototype_options) - -# Configure the header file -config_h_data = configuration_data() -config_h_data.set('CMAKE_CXX_COMPILER', compiler.get_id()) -config_h_data.set('CMAKE_CXX_COMPILER_ARG1', '') -config_h_data.set('CMAKE_CXX_COMPILER_ID', compiler.get_id()) -config_h_data.set('CMAKE_CXX_COMPILER_VERSION', compiler.version()) -config_h_data.set('ALL_COMPILER_FLAGS', all_compiler_flags) -configure_file(input : 'cmake/config.h.in', output : 'config.h', configuration : config_h_data) - -subdir('include') -subdir('src') -if (get_option('enable_testing')) - subdir('test') - subdir('bench') -endif diff --git a/meson_options.txt b/meson_options.txt deleted file mode 100644 index 2d8964b8..00000000 --- a/meson_options.txt +++ /dev/null @@ -1 +0,0 @@ -option('enable_testing', type : 'boolean', value : true, description : 'Enable Testing and Benchmarks') \ No newline at end of file diff --git a/meta/include/gnuradio-4.0/meta/utils.hpp b/meta/include/gnuradio-4.0/meta/utils.hpp index 5317e700..567b7c99 100644 --- a/meta/include/gnuradio-4.0/meta/utils.hpp +++ b/meta/include/gnuradio-4.0/meta/utils.hpp @@ -43,56 +43,35 @@ struct fixed_string { constexpr fixed_string() = default; constexpr explicit(false) fixed_string(const CharT (&str)[N + 1]) noexcept { - if constexpr (N != 0) - for (std::size_t i = 0; i < N; ++i) _data[i] = str[i]; + if constexpr (N != 0) { + for (std::size_t i = 0; i < N; ++i) { + _data[i] = str[i]; + } + } } - [[nodiscard]] constexpr std::size_t - size() const noexcept { - return N; - } + [[nodiscard]] constexpr std::size_t size() const noexcept { return N; } - [[nodiscard]] constexpr bool - empty() const noexcept { - return N == 0; - } + [[nodiscard]] constexpr bool empty() const noexcept { return N == 0; } - [[nodiscard]] constexpr explicit(false) operator std::string_view() const noexcept { return { _data, N }; } + [[nodiscard]] constexpr explicit(false) operator std::string_view() const noexcept { return {_data, N}; } - [[nodiscard]] explicit - operator std::string() const noexcept { - return { _data, N }; - } + [[nodiscard]] explicit operator std::string() const noexcept { return {_data, N}; } - [[nodiscard]] explicit - operator const char *() const noexcept { - return _data; - } + [[nodiscard]] explicit operator const char*() const noexcept { return _data; } - [[nodiscard]] constexpr bool - operator==(const fixed_string &other) const noexcept { - return std::string_view{ _data, N } == std::string_view(other); - } + [[nodiscard]] constexpr bool operator==(const fixed_string& other) const noexcept { return std::string_view{_data, N} == std::string_view(other); } template - [[nodiscard]] friend constexpr bool - operator==(const fixed_string &, const fixed_string &) { + [[nodiscard]] friend constexpr bool operator==(const fixed_string&, const fixed_string&) { return false; } - constexpr auto - operator<=>(const fixed_string &other) const noexcept - = default; + constexpr auto operator<=>(const fixed_string& other) const noexcept = default; - friend constexpr auto - operator<=>(const fixed_string &fs, std::string_view sv) noexcept { - return std::string_view(fs) <=> sv; - } + friend constexpr auto operator<=>(const fixed_string& fs, std::string_view sv) noexcept { return std::string_view(fs) <=> sv; } - friend constexpr auto - operator<=>(const fixed_string &fs, const std::string &str) noexcept { - return std::string(fs) <=> str; - } + friend constexpr auto operator<=>(const fixed_string& fs, const std::string& str) noexcept { return std::string(fs) <=> str; } }; template @@ -108,8 +87,7 @@ template concept FixedString = is_fixed_string::value; template -constexpr fixed_string -operator+(const fixed_string &lhs, const fixed_string &rhs) noexcept { +constexpr fixed_string operator+(const fixed_string& lhs, const fixed_string& rhs) noexcept { meta::fixed_string result{}; for (std::size_t i = 0; i < N1; ++i) { result._data[i] = lhs._data[i]; @@ -122,31 +100,31 @@ operator+(const fixed_string &lhs, const fixed_string &rhs } namespace detail { -constexpr int -log10(int n) noexcept { - if (n < 10) return 0; +constexpr int log10(int n) noexcept { + if (n < 10) { + return 0; + } return 1 + log10(n / 10); } -constexpr int -pow10(int n) noexcept { - if (n == 0) return 1; +constexpr int pow10(int n) noexcept { + if (n == 0) { + return 1; + } return 10 * pow10(n - 1); } template -constexpr fixed_string -make_fixed_string_impl(std::index_sequence) { +constexpr fixed_string make_fixed_string_impl(std::index_sequence) { constexpr auto numDigits = sizeof...(Idx); - return { { ('0' + (N / pow10(numDigits - Idx - 1) % 10))..., 0 } }; + return {{('0' + (N / pow10(numDigits - Idx - 1) % 10))..., 0}}; } } // namespace detail template -constexpr auto -make_fixed_string() noexcept { +constexpr auto make_fixed_string() noexcept { if constexpr (N == 0) { - return fixed_string{ "0" }; + return fixed_string{"0"}; } else { constexpr std::size_t digits = 1U + static_cast(detail::log10(N)); return detail::make_fixed_string_impl(std::make_index_sequence()); @@ -160,11 +138,10 @@ static_assert(fixed_string("123") == make_fixed_string<123>()); static_assert((fixed_string("out") + make_fixed_string<123>()) == fixed_string("out123")); template -[[nodiscard]] std::string -type_name() noexcept { +[[nodiscard]] std::string type_name() noexcept { std::string type_name = typeid(T).name(); int status; - char *demangled_name = abi::__cxa_demangle(type_name.c_str(), nullptr, nullptr, &status); + char* demangled_name = abi::__cxa_demangle(type_name.c_str(), nullptr, nullptr, &status); if (status == 0) { std::string ret(demangled_name); free(demangled_name); @@ -185,32 +162,30 @@ constexpr std::size_t invalid_index = -1UZ; constexpr std::size_t default_message_port_index = -2UZ; #if HAVE_SOURCE_LOCATION -[[gnu::always_inline]] inline void -precondition(bool cond, const std::source_location loc = std::source_location::current()) { +[[gnu::always_inline]] inline void precondition(bool cond, const std::source_location loc = std::source_location::current()) { struct handle { - [[noreturn]] static void - failure(std::source_location const &loc) { + [[noreturn]] static void failure(std::source_location const& loc) { std::clog << "failed precondition in " << loc.file_name() << ':' << loc.line() << ':' << loc.column() << ": `" << loc.function_name() << "`\n"; __builtin_trap(); } }; - if (not cond) [[unlikely]] + if (not cond) [[unlikely]] { handle::failure(loc); + } } #else -[[gnu::always_inline]] inline void -precondition(bool cond) { +[[gnu::always_inline]] inline void precondition(bool cond) { struct handle { - [[noreturn]] static void - failure() { + [[noreturn]] static void failure() { std::clog << "failed precondition\n"; __builtin_trap(); } }; - if (not cond) [[unlikely]] + if (not cond) [[unlikely]] { handle::failure(); + } } #endif @@ -220,7 +195,7 @@ precondition(bool cond) { */ template concept tuple_like = (std::tuple_size::value > 0) && requires(T tup) { - { std::get<0>(tup) } -> std::same_as &>; + { std::get<0>(tup) } -> std::same_as&>; }; template class Template, typename Class> @@ -247,7 +222,7 @@ template concept array_type = is_std_array_type>::value; template -concept array_or_vector_type = (vector_type || array_type) &&(std::same_as || std::same_as); +concept array_or_vector_type = (vector_type || array_type) && (std::same_as || std::same_as); namespace stdx = vir::stdx; @@ -279,9 +254,7 @@ struct simdize_size> : stdx::simd_size {}; template struct simdize_size : simdize_size> { - static_assert([](std::index_sequence) { - return ((simdize_size>::value == simdize_size>::value) && ...); - }(std::make_index_sequence>())); + static_assert([](std::index_sequence) { return ((simdize_size>::value == simdize_size>::value) && ...); }(std::make_index_sequence>())); }; template @@ -301,7 +274,7 @@ struct simdize_impl { }; template - requires requires { typename stdx::native_simd; } +requires requires { typename stdx::native_simd; } struct simdize_impl { using type = deduced_simd::size() : N>; }; @@ -312,17 +285,14 @@ struct simdize_impl, N> { }; template - requires requires { typename simdize_impl, N>::type; } +requires requires { typename simdize_impl, N>::type; } struct simdize_impl { - static inline constexpr std::size_t size - = N > 0 ? N - : [](std::index_sequence) constexpr { return std::max({ simdize_size_v, 0>::type>... }); } + static inline constexpr std::size_t size = N > 0 ? N + : [](std::index_sequence) constexpr { return std::max({simdize_size_v, 0>::type>...}); } - (std::make_index_sequence>()); + (std::make_index_sequence>()); - using type = decltype([](std::index_sequence) -> std::tuple, size>::type...> { - return {}; - }(std::make_index_sequence>())); + using type = decltype([](std::index_sequence) -> std::tuple, size>::type...> { return {}; }(std::make_index_sequence>())); }; } // namespace detail @@ -336,13 +306,10 @@ struct simdize_impl { template using simdize = typename detail::simdize_impl::type; -static_assert(std::same_as, short, std::tuple>>, - std::tuple::size()>, detail::deduced_simd::size()>>, stdx::native_simd, - std::tuple::size()>>>>); +static_assert(std::same_as, short, std::tuple>>, std::tuple::size()>, detail::deduced_simd::size()>>, stdx::native_simd, std::tuple::size()>>>>); template -consteval std::size_t -indexForName() { +consteval std::size_t indexForName() { auto helper = [](std::index_sequence) { auto static_name_for_index = [] { using Port = typename PortList::template at; @@ -411,24 +378,20 @@ overloaded(Lambdas...) -> overloaded; namespace detail { template typename Mapper, template typename Wrapper, typename... Args> -Wrapper...> * -type_transform_impl(Wrapper *); +Wrapper...>* type_transform_impl(Wrapper*); template typename Mapper, typename T> -Mapper * -type_transform_impl(T *); +Mapper* type_transform_impl(T*); template typename Mapper> -void * -type_transform_impl(void *); +void* type_transform_impl(void*); } // namespace detail template typename Mapper, typename T> -using type_transform = std::remove_pointer_t(static_cast(nullptr)))>; +using type_transform = std::remove_pointer_t(static_cast(nullptr)))>; template -auto -safe_min(Arg &&arg, Args &&...args) { +auto safe_min(Arg&& arg, Args&&... args) { if constexpr (sizeof...(Args) == 0) { return arg; } else { @@ -437,8 +400,7 @@ safe_min(Arg &&arg, Args &&...args) { } template -auto -safe_pair_min(Arg &&arg, Args &&...args) { +auto safe_pair_min(Arg&& arg, Args&&... args) { if constexpr (sizeof...(Args) == 0) { return arg; } else { @@ -447,39 +409,27 @@ safe_pair_min(Arg &&arg, Args &&...args) { } template -auto -tuple_for_each(Function &&function, Tuple &&tuple, Tuples &&...tuples) { - static_assert(((std::tuple_size_v> == std::tuple_size_v>) &&...)); - return [&](std::index_sequence) { - (([&function, &tuple, &tuples...](auto I) { function(std::get(tuple), std::get(tuples)...); }(std::integral_constant{}), ...)); - }(std::make_index_sequence>>()); +auto tuple_for_each(Function&& function, Tuple&& tuple, Tuples&&... tuples) { + static_assert(((std::tuple_size_v> == std::tuple_size_v>) && ...)); + return [&](std::index_sequence) { (([&function, &tuple, &tuples...](auto I) { function(std::get(tuple), std::get(tuples)...); }(std::integral_constant{}), ...)); }(std::make_index_sequence>>()); } template -void -tuple_for_each_enumerate(Function &&function, Tuple &&tuple, Tuples &&...tuples) { - static_assert(((std::tuple_size_v> == std::tuple_size_v>) &&...)); - [&](std::index_sequence) { - ([&function](auto I, auto &&t0, auto &&...ts) { function(I, std::get(t0), std::get(ts)...); }(std::integral_constant{}, tuple, tuples...), ...); - }(std::make_index_sequence>>()); +void tuple_for_each_enumerate(Function&& function, Tuple&& tuple, Tuples&&... tuples) { + static_assert(((std::tuple_size_v> == std::tuple_size_v>) && ...)); + [&](std::index_sequence) { ([&function](auto I, auto&& t0, auto&&... ts) { function(I, std::get(t0), std::get(ts)...); }(std::integral_constant{}, tuple, tuples...), ...); }(std::make_index_sequence>>()); } template -auto -tuple_transform(Function &&function, Tuple &&tuple, Tuples &&...tuples) { - static_assert(((std::tuple_size_v> == std::tuple_size_v>) &&...)); - return [&](std::index_sequence) { - return std::make_tuple([&function, &tuple, &tuples...](auto I) { return function(std::get(tuple), std::get(tuples)...); }(std::integral_constant{})...); - }(std::make_index_sequence>>()); +auto tuple_transform(Function&& function, Tuple&& tuple, Tuples&&... tuples) { + static_assert(((std::tuple_size_v> == std::tuple_size_v>) && ...)); + return [&](std::index_sequence) { return std::make_tuple([&function, &tuple, &tuples...](auto I) { return function(std::get(tuple), std::get(tuples)...); }(std::integral_constant{})...); }(std::make_index_sequence>>()); } template -auto -tuple_transform_enumerated(Function &&function, Tuple &&tuple, Tuples &&...tuples) { - static_assert(((std::tuple_size_v> == std::tuple_size_v>) &&...)); - return [&](std::index_sequence) { - return std::make_tuple([&function, &tuple, &tuples...](auto I) { return function(I, std::get(tuple), std::get(tuples)...); }(std::integral_constant{})...); - }(std::make_index_sequence>>()); +auto tuple_transform_enumerated(Function&& function, Tuple&& tuple, Tuples&&... tuples) { + static_assert(((std::tuple_size_v> == std::tuple_size_v>) && ...)); + return [&](std::index_sequence) { return std::make_tuple([&function, &tuple, &tuples...](auto I) { return function(I, std::get(tuple), std::get(tuples)...); }(std::integral_constant{})...); }(std::make_index_sequence>>()); } static_assert(std::is_same_v, type_transform>); @@ -546,6 +496,13 @@ template concept IsNoexceptMemberFunction = std::is_member_function_pointer_v && detail::is_noexcept_member_function::value; } // namespace meta + +#if HAVE_SOURCE_LOCATION +inline auto this_source_location(std::source_location l = std::source_location::current()) { return fmt::format("{}:{},{}", l.file_name(), l.line(), l.column()); } +#else +inline auto this_source_location() { return "not yet implemented"; } +#endif // HAVE_SOURCE_LOCATION + } // namespace gr #endif // include guard From d3f7460402f2a94836d9aceb3552e68e392a1d63 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ivan=20=C4=8Cuki=C4=87?= Date: Mon, 10 Jun 2024 17:33:35 +0200 Subject: [PATCH 2/2] Don't use blocks from plugins in the test MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Ivan Čukić --- core/test/qa_GraphMessages.cpp | 26 ++++++++++++-------------- 1 file changed, 12 insertions(+), 14 deletions(-) diff --git a/core/test/qa_GraphMessages.cpp b/core/test/qa_GraphMessages.cpp index cf490097..f0946212 100644 --- a/core/test/qa_GraphMessages.cpp +++ b/core/test/qa_GraphMessages.cpp @@ -37,7 +37,6 @@ const boost::ut::suite NonRunningGraphTests = [] { using namespace gr; using enum gr::message::Command; -#if 0 "Block addition tests"_test = [] { gr::MsgPortOut toGraph; gr::Graph testGraph; @@ -48,7 +47,7 @@ const boost::ut::suite NonRunningGraphTests = [] { "Add a valid block"_test = [&] { sendMessage(toGraph, "" /* serviceName */, graph::property::kEmplaceBlock /* endpoint */, // - {{"type", "good::multiply"}, {"parameters", "float"}, {"properties", property_map{}}} /* data */); + {{"type", "gr::testing::Copy"}, {"parameters", "float"}, {"properties", property_map{}}} /* data */); expect(nothrow([&] { testGraph.processScheduledMessages(); })) << "manually execute processing of messages"; const Message reply = returnReplyMsg(fromGraph); @@ -74,14 +73,14 @@ const boost::ut::suite NonRunningGraphTests = [] { gr::Graph testGraph; gr::MsgPortIn fromGraph; - auto& block = testGraph.emplaceBlock("good::multiply", "float", {}); + auto& block = testGraph.emplaceBlock("gr::testing::Copy", "float", {}); expect(eq(testGraph.blocks().size(), 1)); expect(eq(ConnectionResult::SUCCESS, toGraph.connect(testGraph.msgIn))); expect(eq(ConnectionResult::SUCCESS, testGraph.msgOut.connect(fromGraph))); "Remove a known block"_test = [&] { - auto& temporaryBlock = testGraph.emplaceBlock("good::multiply", "float", {}); + auto& temporaryBlock = testGraph.emplaceBlock("gr::testing::Copy", "float", {}); expect(eq(testGraph.blocks().size(), 2)); sendMessage(toGraph, "" /* serviceName */, graph::property::kRemoveBlock /* endpoint */, // @@ -111,19 +110,19 @@ const boost::ut::suite NonRunningGraphTests = [] { gr::Graph testGraph; gr::MsgPortIn fromGraph; - auto& block = testGraph.emplaceBlock("good::multiply", "float", {}); + auto& block = testGraph.emplaceBlock("gr::testing::Copy", "float", {}); expect(eq(testGraph.blocks().size(), 1)); expect(eq(ConnectionResult::SUCCESS, toGraph.connect(testGraph.msgIn))); expect(eq(ConnectionResult::SUCCESS, testGraph.msgOut.connect(fromGraph))); "Replace a known block"_test = [&] { - auto& temporaryBlock = testGraph.emplaceBlock("good::multiply", "float", {}); + auto& temporaryBlock = testGraph.emplaceBlock("gr::testing::Copy", "float", {}); expect(eq(testGraph.blocks().size(), 2)); sendMessage(toGraph, "" /* serviceName */, graph::property::kReplaceBlock /* endpoint */, // {{"uniqueName", std::string(temporaryBlock.uniqueName())}, // - {"type", "good::multiply"}, {"parameters", "float"}, {"properties", property_map{}}} /* data */); + {"type", "gr::testing::Copy"}, {"parameters", "float"}, {"properties", property_map{}}} /* data */); expect(nothrow([&] { testGraph.processScheduledMessages(); })) << "manually execute processing of messages"; const Message reply = returnReplyMsg(fromGraph); @@ -134,7 +133,7 @@ const boost::ut::suite NonRunningGraphTests = [] { "Replace an unknown block"_test = [&] { sendMessage(toGraph, "" /* serviceName */, graph::property::kReplaceBlock /* endpoint */, // {{"uniqueName", "this_block_is_unknown"}, // - {"type", "good::multiply"}, {"parameters", "float"}, {"properties", property_map{}}} /* data */); + {"type", "gr::testing::Copy"}, {"parameters", "float"}, {"properties", property_map{}}} /* data */); expect(nothrow([&] { testGraph.processScheduledMessages(); })) << "manually execute processing of messages"; const Message reply = returnReplyMsg(fromGraph); @@ -159,9 +158,9 @@ const boost::ut::suite NonRunningGraphTests = [] { gr::Graph testGraph; gr::MsgPortIn fromGraph; - auto& blockOut = testGraph.emplaceBlock("good::multiply", "float", {}); - auto& blockIn = testGraph.emplaceBlock("good::multiply", "float", {}); - auto& blockWrongType = testGraph.emplaceBlock("good::multiply", "double", {}); + auto& blockOut = testGraph.emplaceBlock("gr::testing::Copy", "float", {}); + auto& blockIn = testGraph.emplaceBlock("gr::testing::Copy", "float", {}); + auto& blockWrongType = testGraph.emplaceBlock("gr::testing::Copy", "double", {}); expect(eq(ConnectionResult::SUCCESS, toGraph.connect(testGraph.msgIn))); expect(eq(ConnectionResult::SUCCESS, testGraph.msgOut.connect(fromGraph))); @@ -207,7 +206,6 @@ const boost::ut::suite NonRunningGraphTests = [] { expect(!reply.data.has_value()); }; }; -#endif }; const boost::ut::suite RunningGraphTests = [] { @@ -264,8 +262,8 @@ const boost::ut::suite RunningGraphTests = [] { std::thread tester([&] { // Adding a few blocks - auto multiply1 = emplaceTestBlock("good::multiply"s, "float"s, property_map{}); - auto multiply2 = emplaceTestBlock("good::multiply"s, "float"s, property_map{}); + auto multiply1 = emplaceTestBlock("gr::testing::Copy"s, "float"s, property_map{}); + auto multiply2 = emplaceTestBlock("gr::testing::Copy"s, "float"s, property_map{}); expect(eq(scheduler.graph().blocks().size(), 4));