From 2c60a4bd5eec72ac679c64a75f2ac20afd04a710 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ivan=20=C4=8Cuki=C4=87?= Date: Tue, 23 Jul 2024 11:22:49 +0000 Subject: [PATCH] Blocks and connections refactor MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Reimplemented graph connection mechanism * Don't instantiate types in typelist's for_each * Low level support for hier-blocks Signed-off-by: Ivan Čukić --- blocks/basic/test/qa_PythonBlock.cpp | 8 +- blocks/basic/test/qa_Selector.cpp | 6 +- cmake/CompilerWarnings.cmake | 1 + core/include/gnuradio-4.0/Block.hpp | 22 +- core/include/gnuradio-4.0/BlockModel.hpp | 198 +++++++- core/include/gnuradio-4.0/Graph.hpp | 431 +++++++++++------- .../gnuradio-4.0/Graph_yaml_importer.hpp | 41 +- core/include/gnuradio-4.0/Port.hpp | 26 +- core/include/gnuradio-4.0/Scheduler.hpp | 37 +- core/include/gnuradio-4.0/Transactions.hpp | 2 +- core/test/qa_Block.cpp | 8 +- core/test/qa_DynamicBlock.cpp | 3 +- core/test/qa_GraphMessages.cpp | 15 +- core/test/qa_Scheduler.cpp | 11 +- core/test/qa_grc.cpp | 13 +- meta/include/gnuradio-4.0/meta/typelist.hpp | 53 +-- 16 files changed, 577 insertions(+), 298 deletions(-) diff --git a/blocks/basic/test/qa_PythonBlock.cpp b/blocks/basic/test/qa_PythonBlock.cpp index 577bfca7..de9f13b2 100644 --- a/blocks/basic/test/qa_PythonBlock.cpp +++ b/blocks/basic/test/qa_PythonBlock.cpp @@ -187,8 +187,8 @@ def process_bulk(ins, outs): auto& block = graph.emplaceBlock>({{"n_inputs", 1U}, {"n_outputs", 1U}, {"pythonScript", pythonScript}}); auto& sink = graph.emplaceBlock>({{"n_samples_expected", 5U}, {"verbose_console", true}}); - expect(gr::ConnectionResult::SUCCESS == graph.connect(src, {"out", gr::meta::invalid_index}, block, {"inputs", 0U})); - expect(gr::ConnectionResult::SUCCESS == graph.connect(block, {"outputs", 0U}, sink, {"in", gr::meta::invalid_index})); + expect(gr::ConnectionResult::SUCCESS == graph.connect(src, "out"s, block, "inputs#0"s)); + expect(gr::ConnectionResult::SUCCESS == graph.connect(block, "outputs#0"s, sink, "in"s)); scheduler::Simple sched{std::move(graph)}; bool throws = false; @@ -248,8 +248,8 @@ def process_bulk(ins, outs): auto& block = graph.emplaceBlock>({{"n_inputs", 1U}, {"n_outputs", 1U}, {"pythonScript", pythonScript}}); auto& sink = graph.emplaceBlock>({{"n_samples_expected", 5U}, {"verbose_console", true}}); - expect(gr::ConnectionResult::SUCCESS == graph.connect(src, {"out", gr::meta::invalid_index}, block, {"inputs", 0U})); - expect(gr::ConnectionResult::SUCCESS == graph.connect(block, {"outputs", 0U}, sink, {"in", gr::meta::invalid_index})); + expect(gr::ConnectionResult::SUCCESS == graph.connect(src, "out"s, block, "inputs#0"s)); + expect(gr::ConnectionResult::SUCCESS == graph.connect(block, "outputs#0"s, sink, "in"s)); scheduler::Simple sched{std::move(graph)}; block.pause(); // simplified calling diff --git a/blocks/basic/test/qa_Selector.cpp b/blocks/basic/test/qa_Selector.cpp index 78d7ca1c..39b7114c 100644 --- a/blocks/basic/test/qa_Selector.cpp +++ b/blocks/basic/test/qa_Selector.cpp @@ -12,6 +12,8 @@ #include #include +using namespace std::string_literals; + struct TestParams { gr::Size_t nSamples; std::vector> mapping; @@ -49,13 +51,13 @@ void execute_selector_test(TestParams params) { for (gr::Size_t i = 0; i < nSources; ++i) { sources.push_back(std::addressof(graph.emplaceBlock>({{"n_samples_max", params.nSamples}, {"values", params.inValues[i]}}))); expect(sources[i]->settings().applyStagedParameters().forwardParameters.empty()); - expect(gr::ConnectionResult::SUCCESS == graph.connect(*sources[i], {"out", gr::meta::invalid_index}, *selector, {"inputs", i})); + expect(gr::ConnectionResult::SUCCESS == graph.connect(*sources[i], "out"s, *selector, "inputs#"s + std::to_string(i))); } for (gr::Size_t i = 0; i < nSinks; ++i) { sinks.push_back(std::addressof(graph.emplaceBlock>())); expect(sinks[i]->settings().applyStagedParameters().forwardParameters.empty()); - expect(gr::ConnectionResult::SUCCESS == graph.connect(*selector, {"outputs", i}, *sinks[i], {"in"})); + expect(gr::ConnectionResult::SUCCESS == graph.connect(*selector, "outputs#"s + std::to_string(i), *sinks[i], "in"s)); } TagSink* monitorSink = std::addressof(graph.emplaceBlock>()); diff --git a/cmake/CompilerWarnings.cmake b/cmake/CompilerWarnings.cmake index 86ef199f..50e2181b 100644 --- a/cmake/CompilerWarnings.cmake +++ b/cmake/CompilerWarnings.cmake @@ -66,6 +66,7 @@ function(set_project_warnings project_name) -Wuseless-cast # warn if you perform a cast to the same type -Wno-interference-size # suppress ABI compatibility warnings for hardware inferred size -Wno-maybe-uninitialized # false positives if asan is enabled: https://gcc.gnu.org/bugzilla//show_bug.cgi?id=1056h6 + -Wno-tautological-compare # fmt has always true comparisons -fconcepts-diagnostics-depth=3 -Wno-missing-field-initializers # confusing warning which is not what most users expect: https://gcc.gnu.org/bugzilla/show_bug.cgi?id=96868#c3 ) diff --git a/core/include/gnuradio-4.0/Block.hpp b/core/include/gnuradio-4.0/Block.hpp index dd3e2edb..f8ac187e 100644 --- a/core/include/gnuradio-4.0/Block.hpp +++ b/core/include/gnuradio-4.0/Block.hpp @@ -221,6 +221,14 @@ inline static const char* kStoreDefaults = "StoreDefaults"; ///< store present s inline static const char* kResetDefaults = "ResetDefaults"; ///< retrieve and reset to default setting, for counterpart @see kStoreDefaults } // namespace block::property +namespace block { +enum class Category { + NormalBlock, ///< Block that does not contain children blocks + TransparentBlockGroup, ///< Block with children blocks which do not have a dedicated scheduler + ScheduledBlockGroup ///< Block with children that have a dedicated scheduler +}; +} + /** * @brief The 'Block' is a base class for blocks that perform specific signal processing operations. It stores * references to its input and output 'ports' that can be zero, one, or many, depending on the use case. @@ -363,7 +371,9 @@ class Block : public lifecycle::StateMachine, public std::tuple>; using AllowIncompleteFinalUpdate = ArgumentsTypeList::template find_or_default>; using DrawableControl = ArgumentsTypeList::template find_or_default>; - constexpr static bool blockingIO = std::disjunction_v, Arguments>...> || std::disjunction_v, Arguments>...>; + + constexpr static bool blockingIO = std::disjunction_v, Arguments>...> || std::disjunction_v, Arguments>...>; + constexpr static block::Category blockCategory = block::Category::NormalBlock; template auto& getArgument() { @@ -523,15 +533,15 @@ class Block : public lifecycle::StateMachine, public std::tuple; + auto setPortName = [&]([[maybe_unused]] std::size_t index, auto* t) { + using CurrentPortType = std::remove_pointer_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)); port.name = CurrentPortType::Name; } - } else { + } else if constexpr (traits::port::is_port_collection_v) { using PortCollectionDescriptor = typename CurrentPortType::value_type::ReflDescriptor; if constexpr (refl::trait::is_descriptor_v) { auto& collection = (self().*(PortCollectionDescriptor::pointer)); @@ -540,6 +550,8 @@ class Block : public lifecycle::StateMachine, public std::tuple, CurrentPortType>{}; } }; traits::block::all_input_ports::for_each(setPortName); @@ -1986,7 +1998,6 @@ template } namespace detail { -using namespace std::string_literals; template std::string reflFirstTypeName() { @@ -2012,6 +2023,7 @@ std::string reflFirstTypeName() { template std::string encodeListOfTypes() { + using namespace std::string_literals; struct accumulator { std::string value; diff --git a/core/include/gnuradio-4.0/BlockModel.hpp b/core/include/gnuradio-4.0/BlockModel.hpp index b0f82b9f..73177787 100644 --- a/core/include/gnuradio-4.0/BlockModel.hpp +++ b/core/include/gnuradio-4.0/BlockModel.hpp @@ -11,15 +11,74 @@ namespace gr { +class BlockModel; + +struct PortDefinition { + struct IndexBased { + std::size_t topLevel; + std::size_t subIndex; + }; + + struct StringBased { + std::string name; + }; + + std::variant definition; + + constexpr PortDefinition(std::size_t _topLevel, std::size_t _subIndex = meta::invalid_index) : definition(IndexBased{_topLevel, _subIndex}) {} + constexpr PortDefinition(std::string name) : definition(StringBased(std::move(name))) {} +}; + +struct Edge { + enum class EdgeState { WaitingToBeConnected, Connected, Overriden, ErrorConnecting, PortNotFound, IncompatiblePorts }; + + BlockModel* _sourceBlock; /// non-owning reference + BlockModel* _destinationBlock; /// non-owning reference + PortDefinition _sourcePortDefinition; + PortDefinition _destinationPortDefinition; + std::size_t _minBufferSize; + std::int32_t _weight = 0; + std::string _name = "unnamed edge"; // custom edge name + EdgeState _state = EdgeState::WaitingToBeConnected; + +public: + Edge() = delete; + + Edge(const Edge&) = delete; + + Edge& operator=(const Edge&) = delete; + + Edge(Edge&&) noexcept = default; + + Edge& operator=(Edge&&) noexcept = default; + + Edge(BlockModel* sourceBlock, PortDefinition sourcePortDefinition, BlockModel* destinationBlock, PortDefinition destinationPortDefinition, std::size_t minBufferSize, std::int32_t weight, std::string name) : _sourceBlock(sourceBlock), _destinationBlock(destinationBlock), _sourcePortDefinition(sourcePortDefinition), _destinationPortDefinition(destinationPortDefinition), _minBufferSize(minBufferSize), _weight(weight), _name(std::move(name)) {} + + [[nodiscard]] constexpr const BlockModel& sourceBlock() const noexcept { return *_sourceBlock; } + [[nodiscard]] constexpr const BlockModel& destinationBlock() const noexcept { return *_destinationBlock; } + [[nodiscard]] PortDefinition sourcePortDefinition() const noexcept { return _sourcePortDefinition; } + [[nodiscard]] PortDefinition destinationPortDefinition() const noexcept { return _destinationPortDefinition; } + [[nodiscard]] constexpr std::string_view name() const noexcept { return _name; } + [[nodiscard]] constexpr std::size_t minBufferSize() const noexcept { return _minBufferSize; } + [[nodiscard]] constexpr std::int32_t weight() const noexcept { return _weight; } + [[nodiscard]] constexpr EdgeState state() const noexcept { return _state; } +}; + class BlockModel { -protected: +public: struct NamedPortCollection { std::string name; std::vector ports; + + [[nodiscard]] auto disconnect() { + return std::ranges::count_if(ports, [](auto& port) { return port.disconnect() == ConnectionResult::FAILED; }) == 0 ? ConnectionResult::SUCCESS : ConnectionResult::FAILED; + } }; - using DynamicPortOrCollection = std::variant; - using DynamicPorts = std::vector; + using DynamicPortOrCollection = std::variant; + using DynamicPorts = std::vector; + +protected: bool _dynamicPortsLoaded = false; std::function _dynamicPortsLoader; DynamicPorts _dynamicInputPorts; @@ -27,10 +86,10 @@ class BlockModel { BlockModel() = default; - [[nodiscard]] gr::DynamicPort& dynamicPortFromName(DynamicPorts& what, std::string_view name) { + [[nodiscard]] gr::DynamicPort& dynamicPortFromName(DynamicPorts& what, const std::string& name) { initDynamicPorts(); - if (auto separatorIt = std::ranges::find(name, '.'); separatorIt == name.end()) { + 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; @@ -50,11 +109,15 @@ class BlockModel { 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) { + auto collectionIt = std::ranges::find_if(what, [&base](const DynamicPortOrCollection& portOrCollection) { const auto* collection = std::get_if(&portOrCollection); - return collection && collection->name == name; + return collection && collection->name == base; }); + if (collectionIt == what.cend()) { + throw gr::exception(fmt::format("Invalid name specified name={}, base={}\n", name, base)); + } + auto& collection = std::get(*collectionIt); if (index >= collection.ports.size()) { @@ -80,15 +143,34 @@ class BlockModel { MsgPortInNamed<"__Builtin">* msgIn; MsgPortOutNamed<"__Builtin">* msgOut; - [[nodiscard]] gr::DynamicPort& dynamicInputPort(std::string_view name) { return dynamicPortFromName(_dynamicInputPorts, name); } + static std::string portName(const DynamicPortOrCollection& portOrCollection) { + return std::visit(meta::overloaded{ // + [](const gr::DynamicPort& port) { return port.name; }, // + [](const NamedPortCollection& namedCollection) { return namedCollection.name; }}, + portOrCollection); + } - [[nodiscard]] gr::DynamicPort& dynamicOutputPort(std::string_view name) { return dynamicPortFromName(_dynamicOutputPorts, name); } + [[nodiscard]] virtual std::span> blocks() noexcept { return {}; }; + [[nodiscard]] virtual std::span edges() noexcept { return {}; } + + DynamicPorts& dynamicInputPorts() { + initDynamicPorts(); + return _dynamicInputPorts; + } + DynamicPorts& dynamicOutputPorts() { + initDynamicPorts(); + return _dynamicOutputPorts; + } + + [[nodiscard]] gr::DynamicPort& dynamicInputPort(const std::string& name) { return dynamicPortFromName(_dynamicInputPorts, name); } + + [[nodiscard]] gr::DynamicPort& dynamicOutputPort(const std::string& 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"); + throw std::invalid_argument(fmt::format("Need to specify the index in the port collection for {}", portCollection->name)); } else { return portCollection->ports[subIndex]; } @@ -97,7 +179,7 @@ class BlockModel { if (subIndex == meta::invalid_index) { return *port; } else { - throw std::invalid_argument("Specified sub-index for a normal port"); + throw std::invalid_argument(fmt::format("Specified sub-index for a normal port {}", port->name)); } } @@ -108,7 +190,7 @@ class BlockModel { 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"); + throw std::invalid_argument(fmt::format("Need to specify the index in the port collection for {}", portCollection->name)); } else { return portCollection->ports[subIndex]; } @@ -117,13 +199,27 @@ class BlockModel { if (subIndex == meta::invalid_index) { return *port; } else { - throw std::invalid_argument("Specified sub-index for a normal port"); + throw std::invalid_argument(fmt::format("Specified sub-index for a normal port {}", port->name)); } } throw std::logic_error("Variant construction failed"); } + [[nodiscard]] gr::DynamicPort& dynamicInputPort(PortDefinition definition) { + return std::visit(meta::overloaded( // + [this](const PortDefinition::IndexBased& _definition) -> DynamicPort& { return dynamicInputPort(_definition.topLevel, _definition.subIndex); }, // + [this](const PortDefinition::StringBased& _definition) -> DynamicPort& { return dynamicInputPort(_definition.name); }), // + definition.definition); + } + + [[nodiscard]] gr::DynamicPort& dynamicOutputPort(PortDefinition definition) { + return std::visit(meta::overloaded( // + [this](const PortDefinition::IndexBased& _definition) -> DynamicPort& { return dynamicOutputPort(_definition.topLevel, _definition.subIndex); }, // + [this](const PortDefinition::StringBased& _definition) -> DynamicPort& { return dynamicOutputPort(_definition.name); }), // + definition.definition); + } + [[nodiscard]] std::size_t dynamicInputPortsSize(std::size_t parentIndex = meta::invalid_index) const { initDynamicPorts(); if (parentIndex == meta::invalid_index) { @@ -150,7 +246,7 @@ class BlockModel { } } - std::size_t dynamicInputPortIndex(std::string_view name) const { + std::size_t dynamicInputPortIndex(const std::string& name) const { initDynamicPorts(); for (std::size_t i = 0; i < _dynamicInputPorts.size(); ++i) { if (auto* portCollection = std::get_if(&_dynamicInputPorts.at(i))) { @@ -167,7 +263,7 @@ class BlockModel { throw std::invalid_argument(fmt::format("Port {} does not exist", name)); } - std::size_t dynamicOutputPortIndex(std::string_view name) const { + std::size_t dynamicOutputPortIndex(const std::string& name) const { initDynamicPorts(); for (std::size_t i = 0; i < _dynamicOutputPorts.size(); ++i) { if (auto* portCollection = std::get_if(&_dynamicOutputPorts.at(i))) { @@ -253,6 +349,8 @@ class BlockModel { [[nodiscard]] virtual work::Status draw() = 0; + [[nodiscard]] virtual block::Category blockCategory() const { return block::Category::NormalBlock; } + virtual void processScheduledMessages() = 0; virtual UICategory uiCategory() const { return UICategory::None; } @@ -305,7 +403,7 @@ class BlockWrapper : public BlockModel { return; } - auto registerPort = [this](DynamicPorts& where, [[maybe_unused]] Direction direction, [[maybe_unused]] ConstIndex index, CurrentPortType&&) noexcept { + 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) { @@ -349,7 +447,7 @@ class BlockWrapper : public BlockModel { BlockWrapper() : BlockWrapper(gr::property_map()) {} explicit BlockWrapper(gr::property_map initParameter) : _block(std::move(initParameter)) { initMessagePorts(); - _dynamicPortsLoader = std::bind(&BlockWrapper::dynamicPortLoader, this); + _dynamicPortsLoader = std::bind_front(&BlockWrapper::dynamicPortLoader, this); } BlockWrapper(const BlockWrapper& other) = delete; @@ -369,9 +467,30 @@ class BlockWrapper : public BlockModel { 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]] block::Category blockCategory() const override { return T::blockCategory; } + + UICategory uiCategory() const override { return T::DrawableControl::kCategory; } + + void processScheduledMessages() override { return blockRef().processScheduledMessages(); } + + [[nodiscard]] std::span> blocks() noexcept override { + if constexpr (requires { blockRef().blocks(); }) { + return blockRef().blocks(); + } else { + return {}; + } + } + + [[nodiscard]] std::span edges() noexcept override { + if constexpr (requires { blockRef().edges(); }) { + return blockRef().edges(); + } else { + return {}; + } + } + + [[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); } @@ -388,4 +507,43 @@ class BlockWrapper : public BlockModel { } // namespace gr +template<> +struct fmt::formatter { + char formatSpecifier = 's'; + + constexpr auto parse(fmt::format_parse_context& ctx) { + auto it = ctx.begin(); + if (it != ctx.end() && (*it == 's' || *it == 'l')) { + formatSpecifier = *it++; + } else if (it != ctx.end() && *it != '}') { + throw fmt::format_error("invalid format specifier"); + } + return it; + } + + template + auto format(const gr::Edge& e, FormatContext& ctx) { + using PortIndex = gr::PortDefinition; + const auto& name = [this](const gr::BlockModel* block) { return (formatSpecifier == 'l') ? block->uniqueName() : block->name(); }; + + const auto portIndex = [](const gr::PortDefinition& port) { + return std::visit(gr::meta::overloaded( + [](const gr::PortDefinition::IndexBased& index) { + if (index.subIndex == gr::meta::invalid_index) { + return fmt::format("{}", index.topLevel); + } else { + return fmt::format("{}#{}", index.topLevel, index.subIndex); + } + }, + [](const gr::PortDefinition::StringBased& index) { return index.name; }), + port.definition); + }; + + return fmt::format_to(ctx.out(), "{}/{} ⟶ (name: '{}', size: {:2}, weight: {:2}, state: {}) ⟶ {}/{}", // + name(e._sourceBlock), portIndex(e._sourcePortDefinition), // + e._name, e._minBufferSize, e._weight, magic_enum::enum_name(e._state), // + name(e._destinationBlock), portIndex(e._destinationPortDefinition)); + } +}; + #endif // GNURADIO_BLOCK_MODEL_HPP diff --git a/core/include/gnuradio-4.0/Graph.hpp b/core/include/gnuradio-4.0/Graph.hpp index 030fc293..5e7f389e 100644 --- a/core/include/gnuradio-4.0/Graph.hpp +++ b/core/include/gnuradio-4.0/Graph.hpp @@ -13,10 +13,8 @@ #include #include -#include #include #include -#include #include #include @@ -35,43 +33,6 @@ namespace gr { -template -struct PortIndexDefinition { - T topLevel; - std::size_t subIndex = meta::invalid_index; - - constexpr PortIndexDefinition(T _topLevel, std::size_t _subIndex = meta::invalid_index) : topLevel(std::move(_topLevel)), subIndex(_subIndex) {} -}; - -struct Edge { - using PortDirection::INPUT; - using PortDirection::OUTPUT; - BlockModel* _sourceBlock; /// non-owning reference - BlockModel* _destinationBlock; /// non-owning reference - PortIndexDefinition _sourcePortDefinition; - PortIndexDefinition _destinationPortDefinition; - std::size_t _minBufferSize; - std::int32_t _weight = 0; - std::string _name = "unnamed edge"; // custom edge name - bool _connected = false; - - Edge() = delete; - Edge(const Edge&) = delete; - Edge& operator=(const Edge&) = delete; - Edge(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) {} - - [[nodiscard]] constexpr const BlockModel& sourceBlock() const noexcept { return *_sourceBlock; } - [[nodiscard]] constexpr const BlockModel& destinationBlock() const noexcept { return *_destinationBlock; } - [[nodiscard]] constexpr PortIndexDefinition sourcePortDefinition() const noexcept { return _sourcePortDefinition; } - [[nodiscard]] constexpr PortIndexDefinition destinationPortDefinition() const noexcept { return _destinationPortDefinition; } - [[nodiscard]] constexpr std::string_view name() const noexcept { return _name; } - [[nodiscard]] constexpr std::size_t minBufferSize() const noexcept { return _minBufferSize; } - [[nodiscard]] constexpr std::int32_t weight() const noexcept { return _weight; } - [[nodiscard]] constexpr bool is_connected() const noexcept { return _connected; } -}; - namespace graph::property { inline static const char* kEmplaceBlock = "EmplaceBlock"; inline static const char* kRemoveBlock = "RemoveBlock"; @@ -87,15 +48,17 @@ inline static const char* kRemoveEdge = "RemoveEdge"; inline static const char* kEdgeEmplaced = "EdgeEmplaced"; inline static const char* kEdgeRemoved = "EdgeRemoved"; +inline static const char* kGraphInspect = "GraphInspect"; +inline static const char* kGraphInspected = "GraphInspected"; } // namespace graph::property class Graph : public gr::Block { - std::shared_ptr _progress = std::make_shared(); - std::shared_ptr _ioThreadPool = std::make_shared("graph_thread_pool", gr::thread_pool::TaskType::IO_BOUND, 2UZ, std::numeric_limits::max()); - std::atomic_bool _topologyChanged{false}; - std::vector> _connectionDefinitions; - std::vector _edges; - std::vector> _blocks; +private: + std::shared_ptr _progress = std::make_shared(); + std::shared_ptr _ioThreadPool = std::make_shared("graph_thread_pool", gr::thread_pool::TaskType::IO_BOUND, 2UZ, std::numeric_limits::max()); + std::atomic_bool _topologyChanged{false}; + std::vector _edges; + std::vector> _blocks; template std::unique_ptr& findBlock(TBlock& what) { @@ -114,68 +77,57 @@ class Graph : public gr::Block { 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& 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 = [&] { - if constexpr (traits::port::is_port_v) { - return &source_port_or_collection; - } else { - return &source_port_or_collection[sourcePortSubIndex]; - } - }(); - - auto* destinationPort = [&] { - if constexpr (traits::port::is_port_v) { - return &destinationPort_or_collection; - } else { - return &destinationPort_or_collection[destinationPortSubIndex]; - } - }(); - - 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>{}; - } - - auto result = sourcePort->connect(*destinationPort); - if (result == ConnectionResult::SUCCESS) { - 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); - setTopologyChanged(); - } - - return result; - } - // Just a dummy class that stores the graph and the source block and port // to be able to split the connection into two separate calls // connect(source) and .to(destination) - template + template struct SourceConnector { - Graph& self; - Source& source; - Port& port; + Graph& self; + Source& sourceBlockRaw; + SourcePort& sourcePortOrCollectionRaw; - SourceConnector(Graph& _self, Source& _source, Port& _port) : self(_self), source(_source), port(_port) {} + SourceConnector(Graph& _self, Source& _source, SourcePort& _port) : self(_self), sourceBlockRaw(_source), sourcePortOrCollectionRaw(_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"); + SourceConnector(const SourceConnector&) = delete; + SourceConnector(SourceConnector&&) = delete; + SourceConnector& operator=(const SourceConnector&) = delete; + SourceConnector& operator=(SourceConnector&&) = delete; + + 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) { - // 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); }); }; - 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); + [[nodiscard]] constexpr ConnectionResult to(Destination& destinationBlockRaw, DestinationPort& destinationPortOrCollectionRaw, std::size_t minBufferSize = 65536, std::int32_t weight = 0, std::string edgeName = "unnamed edge") { + auto* sourceBlock = self.findBlock(sourceBlockRaw).get(); + auto* destinationBlock = self.findBlock(destinationBlockRaw).get(); + + if (sourceBlock == nullptr || destinationBlock == nullptr) { + fmt::print("Source {} and/or destination {} do not belong to this graph\n", sourceBlockRaw.name, destinationBlockRaw.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); }); + + auto* sourcePort = [&] { + if constexpr (traits::port::is_port_v) { + return &sourcePortOrCollectionRaw; + } else { + return &sourcePortOrCollectionRaw[sourcePortSubIndex]; + } + }(); + + auto* destinationPort = [&] { + if constexpr (traits::port::is_port_v) { + return &destinationPortOrCollectionRaw; + } else { + return &destinationPortOrCollectionRaw[destinationPortSubIndex]; + } + }(); + + 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>{}; + } + + self._edges.emplace_back(sourceBlock, PortDefinition{sourcePortIndex, sourcePortSubIndex}, destinationBlock, PortDefinition{destinationPortIndex, destinationPortSubIndex}, minBufferSize, weight, std::move(edgeName)); + return ConnectionResult::SUCCESS; } @@ -219,20 +171,19 @@ class Graph : public gr::Block { [[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; }; public: - Graph() noexcept { + constexpr static block::Category blockCategory = block::Category::TransparentBlockGroup; + + Graph(property_map settings = {}) : gr::Block(std::move(settings)) { + _blocks.reserve(100); // TODO: remove 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; + propertyCallbacks[graph::property::kGraphInspect] = &Graph::propertyCallbackGraphInspect; } Graph(Graph&) = delete; // there can be only one owner of Graph Graph& operator=(Graph&) = delete; // there can be only one owner of Graph @@ -244,9 +195,8 @@ class Graph : public gr::Block { _progress = std::move(other._progress); _ioThreadPool = std::move(other._ioThreadPool); _topologyChanged.store(other._topologyChanged.load(std::memory_order_acquire), std::memory_order_release); - _connectionDefinitions = std::move(other._connectionDefinitions); - _edges = std::move(other._edges); - _blocks = std::move(other._blocks); + _edges = std::move(other._edges); + _blocks = std::move(other._blocks); return *this; } @@ -292,6 +242,7 @@ class Graph : public gr::Block { } std::optional propertyCallbackEmplaceBlock(std::string_view /*propertyName*/, Message message) { + using namespace std::string_literals; 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)); @@ -307,6 +258,7 @@ class Graph : public gr::Block { } std::optional propertyCallbackRemoveBlock(std::string_view /*propertyName*/, Message message) { + using namespace std::string_literals; 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; }); @@ -322,6 +274,7 @@ class Graph : public gr::Block { } std::optional propertyCallbackReplaceBlock(std::string_view /*propertyName*/, Message message) { + using namespace std::string_literals; 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)); @@ -333,14 +286,25 @@ class Graph : public gr::Block { 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) { + auto newBlock = gr::globalPluginLoader().instantiate(type, parameters, properties); + if (!newBlock) { throw gr::exception(fmt::format("Can not create block {}<{}>", type, parameters)); } + const auto newName = newBlock->uniqueName(); + addBlock(std::move(newBlock)); + + BlockModel* oldBlock = it->get(); + for (auto& edge : _edges) { + if (edge._sourceBlock == oldBlock) { + edge._sourceBlock = newBlock.get(); + } + + if (edge._destinationBlock == oldBlock) { + edge._destinationBlock = newBlock.get(); + } + } _blocks.erase(it); - const auto newName = block_load->uniqueName(); - addBlock(std::move(block_load)); std::optional result = gr::Message{}; result->endpoint = graph::property::kBlockReplaced; @@ -350,6 +314,7 @@ class Graph : public gr::Block { } std::optional propertyCallbackEmplaceEdge(std::string_view /*propertyName*/, Message message) { + using namespace std::string_literals; 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)); @@ -382,14 +347,16 @@ class Graph : public gr::Block { throw gr::exception(fmt::format("{}.{} can not be connected to {}.{}", sourceBlock, sourcePort, destinationBlock, destinationPort)); } - // FIXME: continue here _edges.emplace_back(sourceBlock, sourcePort, destinationBlock, destinationBlock, minBufferSize, weight, edgeName); - //_edges.emplace_back(sourceBlock, sourcePortDefinition, destinationBlock, destinationPortDefinition, minBufferSize, weight, edgeName); + _edges.emplace_back(sourceBlockIt->get(), sourcePort, destinationBlockIt->get(), destinationPort, + // TODO: + 65536UZ, 0, "unnamed edge"); message.endpoint = graph::property::kEdgeEmplaced; return message; } std::optional propertyCallbackRemoveEdge(std::string_view /*propertyName*/, Message message) { + using namespace std::string_literals; 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)); @@ -408,16 +375,125 @@ class Graph : public gr::Block { return message; } + std::optional propertyCallbackGraphInspect([[maybe_unused]] std::string_view propertyName, Message message) { + auto serializePortOrCollection = [](const auto& portOrCollection) { + // clang-format off + return std::visit(meta::overloaded{ + [](const gr::DynamicPort& port) { + return property_map{ + {"name", port.name}, + {"type", port.defaultValue().type().name()} + }; + }, + [](const BlockModel::NamedPortCollection& namedCollection) { + return property_map{ + {"name", namedCollection.name}, + {"size", namedCollection.ports.size()}, + {"type", namedCollection.ports.empty() ? std::string() : std::string(namedCollection.ports[0].defaultValue().type().name()) } + }; + }}, + portOrCollection); + // clang-format on + }; + auto serializeEdge = [](const auto& edge) { + property_map result; + auto serializePortDefinition = [&](const std::string& key, const PortDefinition& portDefinition) { + std::visit(meta::overloaded( // + [&](const PortDefinition::IndexBased& definition) { + result[key + ".topLevel"] = definition.topLevel; + result[key + ".subIndex"] = definition.subIndex; + }, // + [&](const PortDefinition::StringBased& definition) { result[key] = definition.name; }), + portDefinition.definition); + }; + + result["sourceBlock"s] = std::string(edge.sourceBlock().uniqueName()); + serializePortDefinition("sourcePort"s, edge.sourcePortDefinition()); + result["destinationBlock"s] = std::string(edge.destinationBlock().uniqueName()); + serializePortDefinition("destinationPort"s, edge.destinationPortDefinition()); + + result["weight"s] = edge.weight(); + result["minBufferSize"s] = edge.minBufferSize(); + + return result; + }; + auto serializeBlock = [&serializeEdge, &serializePortOrCollection](auto _serializeBlock, const auto& block) -> property_map { + property_map result; + result["name"s] = std::string(block->name()); + result["uniqueName"s] = std::string(block->uniqueName()); + result["typeName"s] = std::string(block->typeName()); + result["isBlocking"s] = block->isBlocking(); + result["metaInformation"s] = block->metaInformation(); + result["blockCategory"s] = std::string(magic_enum::enum_name(block->blockCategory())); + result["uiCategory"s] = std::string(magic_enum::enum_name(block->uiCategory())); + result["settings"s] = block->settings().getStored(); + + property_map inputPorts; + for (auto& portOrCollection : block->dynamicInputPorts()) { + inputPorts[BlockModel::portName(portOrCollection)] = serializePortOrCollection(portOrCollection); + } + result["inputPorts"] = std::move(inputPorts); + + property_map outputPorts; + for (auto& portOrCollection : block->dynamicOutputPorts()) { + outputPorts[BlockModel::portName(portOrCollection)] = serializePortOrCollection(portOrCollection); + } + result["outputPorts"] = std::move(outputPorts); + + if (block->blockCategory() != block::Category::NormalBlock) { + property_map serializedChildren; + for (const auto& child : block->blocks()) { + serializedChildren[std::string(child->uniqueName())] = _serializeBlock(_serializeBlock, child); + } + result["children"] = std::move(serializedChildren); + } + + property_map serializedEdges; + std::size_t index = 0UZ; + for (const auto& edge : block->edges()) { + serializedEdges[std::to_string(index)] = serializeEdge(edge); + index++; + } + result["edges"] = std::move(serializedEdges); + + return result; + }; + + message.data = [&] { + property_map result; + result["name"s] = std::string(name); + result["uniqueName"s] = std::string(unique_name); + result["blockCategory"s] = std::string(magic_enum::enum_name(blockCategory)); + + property_map serializedChildren; + for (const auto& child : blocks()) { + serializedChildren[std::string(child->uniqueName())] = serializeBlock(serializeBlock, child); + } + result["children"] = std::move(serializedChildren); + + property_map serializedEdges; + std::size_t index = 0UZ; + for (const auto& edge : edges()) { + serializedEdges[std::to_string(index)] = serializeEdge(edge); + index++; + } + return result; + }(); + + message.endpoint = graph::property::kGraphInspected; + return message; + } + // connect using the port index template - [[nodiscard]] auto connect_internal(Source& source) { + [[nodiscard]] auto connectInternal(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) { - return connect_internal(source); + return connectInternal(source); } template @@ -438,7 +514,7 @@ class Graph : public gr::Block { 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{}; } - return connect_internal(source); + return connectInternal(source); } template @@ -450,22 +526,16 @@ class Graph : public gr::Block { 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)); - if (result == ConnectionResult::SUCCESS) { - auto* sourceBlock = findBlock(sourceBlockRaw).get(); - auto* destinationBlock = findBlock(destinationBlockRaw).get(); - _edges.emplace_back(sourceBlock, sourcePortDefinition, destinationBlock, destinationPortDefinition, minBufferSize, weight, edgeName); + ConnectionResult connect(Source& sourceBlockRaw, PortDefinition sourcePortDefinition, Destination& destinationBlockRaw, PortDefinition destinationPortDefinition, std::size_t minBufferSize = 65536, std::int32_t weight = 0, std::string edgeName = "unnamed edge") { + auto* sourceBlock = findBlock(sourceBlockRaw).get(); + auto* destinationBlock = findBlock(destinationBlockRaw).get(); + + if (sourceBlock == nullptr || destinationBlock == nullptr) { + return ConnectionResult::FAILED; } - 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") { - 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); + _edges.emplace_back(sourceBlock, sourcePortDefinition, destinationBlock, destinationPortDefinition, minBufferSize, weight, std::move(edgeName)); + return ConnectionResult::SUCCESS; } using Block::processMessages; @@ -475,22 +545,81 @@ class Graph : public gr::Block { 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; }); - if (result) { - _connectionDefinitions.clear(); + Edge::EdgeState applyEdgeConnection(Edge& edge) { + try { + auto& sourcePort = edge._sourceBlock->dynamicOutputPort(edge._sourcePortDefinition); + auto& destinationPort = edge._destinationBlock->dynamicInputPort(edge._destinationPortDefinition); + + if (sourcePort.defaultValue().type().name() != destinationPort.defaultValue().type().name()) { + edge._state = Edge::EdgeState::IncompatiblePorts; + } else { + auto connectionResult = sourcePort.connect(destinationPort) == ConnectionResult::SUCCESS; + edge._state = connectionResult ? Edge::EdgeState::Connected : Edge::EdgeState::ErrorConnecting; + } + } catch (...) { + edge._state = Edge::EdgeState::PortNotFound; } - return result; + + return edge._state; + } + + void disconnectAllEdges() { + for (auto& block : _blocks) { + block->initDynamicPorts(); + + auto disconnectAll = [](auto& ports) { + for (auto& port : ports) { + std::ignore = std::visit([](auto& portOrCollection) { return portOrCollection.disconnect(); }, port); + } + }; + + disconnectAll(block->dynamicInputPorts()); + disconnectAll(block->dynamicOutputPorts()); + } + + for (auto& edge : _edges) { + edge._state = Edge::EdgeState::WaitingToBeConnected; + } + } + + bool reconnectAllEdges() { + disconnectAllEdges(); + return connectPendingEdges(); + } + + bool connectPendingEdges() { + bool allConnected = true; + for (auto& edge : _edges) { + if (edge.state() == Edge::EdgeState::WaitingToBeConnected) { + applyEdgeConnection(edge); + const bool wasConnected = edge.state() == Edge::EdgeState::Connected; + if (!wasConnected) { + fmt::print("Edge could not be connected {}\n", edge); + } + allConnected = allConnected && wasConnected; + } + } + return allConnected; } - template // TODO: F must be constraint by a descriptive concept + template F> + void forEachBlockMutable(F&& f) { + std::ranges::for_each(_blocks, [f](auto& block_ptr) { std::invoke(f, *block_ptr.get()); }); + } + + template F> + void forEachEdgeMutable(F&& f) { + std::ranges::for_each(_edges, f); + } + + template F> void forEachBlock(F&& f) const { - std::ranges::for_each(_blocks, [f](const auto& block_ptr) { std::invoke(f, *block_ptr.get()); }); + std::ranges::for_each(_blocks, [f](auto& block_ptr) { std::invoke(f, std::as_const(*block_ptr.get())); }); } - template // TODO: F must be constraint by a descriptive concept + template F> void forEachEdge(F&& f) const { - std::ranges::for_each(_edges, [f](const auto& edge) { std::invoke(f, edge); }); + std::ranges::for_each(_edges, f); } }; @@ -780,32 +909,4 @@ inline std::ostream& operator<<(std::ostream& os, const T& value) { REFL_TYPE(gr::Graph) REFL_END -template<> -struct fmt::formatter { - char formatSpecifier = 's'; - - constexpr auto parse(fmt::format_parse_context& ctx) { - auto it = ctx.begin(); - if (it != ctx.end() && (*it == 's' || *it == 'l')) { - formatSpecifier = *it++; - } else if (it != ctx.end() && *it != '}') { - throw fmt::format_error("invalid format specifier"); - } - return it; - } - - template - auto format(const gr::Edge& e, FormatContext& ctx) { - using PortIndex = gr::PortIndexDefinition; - const auto& name = [this](const gr::BlockModel* block) { return (formatSpecifier == 'l') ? block->uniqueName() : block->name(); }; - const auto& portIdx = [](const PortIndex& port) { return port.topLevel; }; - const auto& subPort = [](const PortIndex& port) { return (port.subIndex == gr::meta::invalid_index) ? "" : fmt::format("[{}]", port.subIndex); }; - - return fmt::format_to(ctx.out(), "{}/{}{} ⟶ (name: '{}', size: {:2}, weight: {:2}, connected: {}) ⟶ {}/{}{}", // - name(e._sourceBlock), portIdx(e._sourcePortDefinition), subPort(e._sourcePortDefinition), // - e._name, e._minBufferSize, e._weight, e._connected, // - name(e._destinationBlock), portIdx(e._destinationPortDefinition), subPort(e._destinationPortDefinition)); - } -}; - #endif // include guard diff --git a/core/include/gnuradio-4.0/Graph_yaml_importer.hpp b/core/include/gnuradio-4.0/Graph_yaml_importer.hpp index bc0ff399..d7ff2962 100644 --- a/core/include/gnuradio-4.0/Graph_yaml_importer.hpp +++ b/core/include/gnuradio-4.0/Graph_yaml_importer.hpp @@ -239,8 +239,8 @@ inline gr::Graph loadGrc(PluginLoader& loader, const std::string& yamlSrc) { } struct result { - decltype(node) block_it; - PortIndexDefinition port_definition; + decltype(node) block_it; + PortDefinition port_definition; }; if (portField.IsSequence()) { @@ -304,27 +304,30 @@ inline std::string saveGrc(const gr::Graph& testGraph) { root.writeFn("connections", [&]() { detail::YamlSeq nodes(out); - auto writeEdge = [&](const auto& edge) { + + auto writePortDefinition = [&](const auto& definition) { // + std::visit(meta::overloaded( // + [&](const PortDefinition::IndexBased& _definition) { + if (_definition.subIndex != meta::invalid_index) { + detail::YamlSeq seqPort(out); + out << _definition.topLevel; + out << _definition.subIndex; + } else { + out << _definition.topLevel; + } + }, // + [&](const PortDefinition::StringBased& _definition) { out << _definition.name; }), + definition.definition); + }; + + auto writeEdge = [&](const auto& edge) { out << YAML::Flow; detail::YamlSeq seq(out); out << edge.sourceBlock().name().data(); - const auto sourcePort = edge.sourcePortDefinition(); - if (sourcePort.subIndex == meta::invalid_index) { - out << sourcePort.topLevel; - } else { - detail::YamlSeq seqPort(out); - out << std::to_string(sourcePort.topLevel); - out << std::to_string(sourcePort.subIndex); - } + writePortDefinition(edge.sourcePortDefinition()); + out << edge.destinationBlock().name().data(); - const auto destinationPort = edge.destinationPortDefinition(); - if (destinationPort.subIndex == meta::invalid_index) { - out << destinationPort.topLevel; - } else { - detail::YamlSeq seqPort(out); - out << std::to_string(destinationPort.topLevel); - out << std::to_string(destinationPort.subIndex); - } + writePortDefinition(edge.destinationPortDefinition()); }; testGraph.forEachEdge(writeEdge); diff --git a/core/include/gnuradio-4.0/Port.hpp b/core/include/gnuradio-4.0/Port.hpp index 43264a4e..3c760748 100644 --- a/core/include/gnuradio-4.0/Port.hpp +++ b/core/include/gnuradio-4.0/Port.hpp @@ -372,8 +372,8 @@ struct Port { static_assert(ConsumablePortSpan>); - std::span tags; // Range of tags for the currently processed stream range; only used in input ports - Tag::signed_index_type streamIndex; // Absolute offset of the first sample in the currently processed stream span; only used in input ports + std::span tags; // Range of tags for the currently processed stream range; only used in input ports + Tag::signed_index_type streamIndex{}; // Absolute offset of the first sample in the currently processed stream span; only used in input ports private: IoType _ioHandler = newIoHandler(); @@ -823,7 +823,7 @@ class DynamicPort { std::unique_ptr _accessor; template - class wrapper final : public model { + class PortWrapper final : public model { using TPortType = std::decay_t; std::conditional_t _value; @@ -839,15 +839,15 @@ class DynamicPort { } public: - wrapper() = delete; + PortWrapper() = delete; - wrapper(const wrapper&) = delete; + PortWrapper(const PortWrapper&) = delete; + PortWrapper(PortWrapper&&) = delete; - auto& operator=(const wrapper&) = delete; + auto& operator=(const PortWrapper&) = delete; + auto& operator=(PortWrapper&&) = delete; - auto& operator=(wrapper&&) = delete; - - explicit constexpr wrapper(T& arg) noexcept : _value{arg} { + explicit constexpr PortWrapper(T& arg) noexcept : _value{arg} { if constexpr (T::kIsInput) { static_assert(requires { arg.writerHandlerInternal(); }, "'private void* writerHandlerInternal()' not implemented"); } else { @@ -855,7 +855,7 @@ class DynamicPort { } } - explicit constexpr wrapper(T&& arg) noexcept : _value{std::move(arg)} { + explicit constexpr PortWrapper(T&& arg) noexcept : _value{std::move(arg)} { if constexpr (T::kIsInput) { static_assert(requires { arg.writerHandlerInternal(); }, "'private void* writerHandlerInternal()' not implemented"); } else { @@ -863,7 +863,7 @@ class DynamicPort { } } - ~wrapper() override = default; + ~PortWrapper() override = default; [[nodiscard]] std::any defaultValue() const noexcept override { return _value.defaultValue(); } @@ -918,10 +918,10 @@ class DynamicPort { // 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(); } diff --git a/core/include/gnuradio-4.0/Scheduler.hpp b/core/include/gnuradio-4.0/Scheduler.hpp index e4c4fd7d..3fec56b2 100644 --- a/core/include/gnuradio-4.0/Scheduler.hpp +++ b/core/include/gnuradio-4.0/Scheduler.hpp @@ -21,7 +21,6 @@ namespace gr::scheduler { using gr::thread_pool::BasicThreadPool; using namespace gr::message; -using namespace std::string_literals; enum class ExecutionPolicy { singleThreaded, /// @@ -55,6 +54,8 @@ class SchedulerBase : public Block { Annotated> timeout_inactivity_count = 20U; Annotated> process_stream_to_message_ratio = 16U; + constexpr static block::Category blockCategory = block::Category::ScheduledBlockGroup; + [[nodiscard]] static constexpr auto executionPolicy() { return execution; } explicit SchedulerBase(gr::Graph&& graph, // @@ -80,7 +81,7 @@ class SchedulerBase : public Block { void stateChanged(lifecycle::State newState) { this->notifyListeners(block::property::kLifeCycleState, {{"state", std::string(magic_enum::enum_name(newState))}}); } void connectBlockMessagePorts() { - _graph.forEachBlock([this](auto& block) { + _graph.forEachBlockMutable([this](auto& block) { if (ConnectionResult::SUCCESS != _toChildMessagePort.connect(*block.msgIn)) { this->emitErrorMessage("connectBlockMessagePorts()", fmt::format("Failed to connect scheduler input message port to child '{}'", block.uniqueName())); } @@ -119,7 +120,7 @@ class SchedulerBase : public Block { // Process messages in the graph _graph.processScheduledMessages(); if (_nRunningJobs.load(std::memory_order_acquire) == 0UZ) { - _graph.forEachBlock(&BlockModel::processScheduledMessages); + _graph.forEachBlockMutable(&BlockModel::processScheduledMessages); } const auto& messagesFromChildren = _fromChildMessagePort.streamReader().get(); @@ -216,28 +217,22 @@ class SchedulerBase : public Block { void init() { [[maybe_unused]] const auto pe = _profilerHandler.startCompleteEvent("scheduler_base.init"); base_t::processScheduledMessages(); // make sure initial subscriptions are processed - const auto result = _graph.performConnections(); - if (!result) { - this->emitErrorMessage("init()", "Failed to connect blocks in graph"); - } connectBlockMessagePorts(); } void reset() { - _graph.forEachBlock([this](auto& block) { this->emitErrorMessageIfAny("reset() -> LifecycleState", block.changeState(lifecycle::INITIALISED)); }); - - // since it is not possible to set up the graph connections a second time, this method leaves the graph in the initialized state with clear buffers. - // clear buffers - // std::for_each(_graph.edges().begin(), _graph.edges().end(), [](auto &edge) { - // - // }); + _graph.forEachBlockMutable([this](auto& block) { this->emitErrorMessageIfAny("reset() -> LifecycleState", block.changeState(lifecycle::INITIALISED)); }); + _graph.disconnectAllEdges(); } void start() { - // FIXME: runtime edge->connection API needs to add initialisation here. Implementation either here or in Graph. + const bool result = _graph.reconnectAllEdges(); + if (!result) { + this->emitErrorMessage("init()", "Failed to connect blocks in graph"); + } std::lock_guard lock(_jobListsMutex); - _graph.forEachBlock([this](auto& block) { + _graph.forEachBlockMutable([this](auto& block) { this->emitErrorMessageIfAny("LifecycleState -> RUNNING", block.changeState(lifecycle::RUNNING)); for_each_port([](auto& port) { port.publishPendingTags(); }, outputPorts(this)); }); @@ -337,7 +332,7 @@ class SchedulerBase : public Block { } void stop() { - _graph.forEachBlock([this](auto& block) { + _graph.forEachBlockMutable([this](auto& block) { this->emitErrorMessageIfAny("forEachBlock -> stop() -> LifecycleState", block.changeState(lifecycle::State::REQUESTED_STOP)); if (!block.isBlocking()) { // N.B. no other thread/constraint to consider before shutting down this->emitErrorMessageIfAny("forEachBlock -> stop() -> LifecycleState", block.changeState(lifecycle::State::STOPPED)); @@ -348,7 +343,7 @@ class SchedulerBase : public Block { } void pause() { - _graph.forEachBlock([this](auto& block) { + _graph.forEachBlockMutable([this](auto& block) { this->emitErrorMessageIfAny("pause() -> LifecycleState", block.changeState(lifecycle::State::REQUESTED_PAUSE)); if (!block.isBlocking()) { // N.B. no other thread/constraint to consider before shutting down this->emitErrorMessageIfAny("pause() -> LifecycleState", block.changeState(lifecycle::State::PAUSED)); @@ -358,7 +353,11 @@ class SchedulerBase : public Block { } void resume() { - _graph.forEachBlock([this](auto& block) { this->emitErrorMessageIfAny("resume() -> LifecycleState", block.changeState(lifecycle::RUNNING)); }); + const bool result = _graph.connectPendingEdges(); + if (!result) { + this->emitErrorMessage("init()", "Failed to connect blocks in graph"); + } + _graph.forEachBlockMutable([this](auto& block) { this->emitErrorMessageIfAny("resume() -> LifecycleState", block.changeState(lifecycle::RUNNING)); }); } }; diff --git a/core/include/gnuradio-4.0/Transactions.hpp b/core/include/gnuradio-4.0/Transactions.hpp index de455643..d6bef13a 100644 --- a/core/include/gnuradio-4.0/Transactions.hpp +++ b/core/include/gnuradio-4.0/Transactions.hpp @@ -331,7 +331,7 @@ class CtxSettings : public SettingsBase { [[nodiscard]] std::set>& autoForwardParameters() noexcept override { return _autoForwardParameters; } - [[nodiscard]] ApplyStagedParametersResult applyStagedParameters(std::uint64_t currentTime = 0ULL) noexcept override { + [[nodiscard]] ApplyStagedParametersResult applyStagedParameters(std::uint64_t currentTime = 0ULL) override { ApplyStagedParametersResult result; if constexpr (refl::is_reflectable()) { std::lock_guard lg(_lock); diff --git a/core/test/qa_Block.cpp b/core/test/qa_Block.cpp index 85e85680..bc5fb0c2 100644 --- a/core/test/qa_Block.cpp +++ b/core/test/qa_Block.cpp @@ -729,10 +729,10 @@ const boost::ut::suite _stride_tests = [] { expect(eq(gr::ConnectionResult::SUCCESS, graph.connect<"out">(*sources[3]).to<"inputs", 3UZ>(*testNode))); // test also different connect API - expect(eq(gr::ConnectionResult::SUCCESS, graph.connect(*testNode, {"outputs", 0}, *sinks[0], "in"s))); - expect(eq(gr::ConnectionResult::SUCCESS, graph.connect(*testNode, {"outputs", 1}, *sinks[1], "in"s))); - expect(eq(gr::ConnectionResult::SUCCESS, graph.connect(*testNode, {"outputs", 2}, *sinks[2], "in"s))); - expect(eq(gr::ConnectionResult::SUCCESS, graph.connect(*testNode, {"outputs", 3}, *sinks[3], "in"s))); + expect(eq(gr::ConnectionResult::SUCCESS, graph.connect(*testNode, "outputs#0"s, *sinks[0], "in"s))); + expect(eq(gr::ConnectionResult::SUCCESS, graph.connect(*testNode, "outputs#1"s, *sinks[1], "in"s))); + expect(eq(gr::ConnectionResult::SUCCESS, graph.connect(*testNode, "outputs#2"s, *sinks[2], "in"s))); + expect(eq(gr::ConnectionResult::SUCCESS, graph.connect(*testNode, "outputs#3"s, *sinks[3], "in"s))); gr::scheduler::Simple sched{std::move(graph)}; expect(sched.runAndWait().has_value()); diff --git a/core/test/qa_DynamicBlock.cpp b/core/test/qa_DynamicBlock.cpp index 3e159b30..46f1bbc0 100644 --- a/core/test/qa_DynamicBlock.cpp +++ b/core/test/qa_DynamicBlock.cpp @@ -8,6 +8,7 @@ #include const boost::ut::suite DynamicBlocktests = [] { + using namespace std::string_literals; using namespace boost::ut; using namespace gr::testing; "Change number of ports dynamically"_test = [] { @@ -25,7 +26,7 @@ const boost::ut::suite DynamicBlocktests = [] { for (std::size_t i = 0; i < nInputs; ++i) { sources.push_back(std::addressof(graph.emplaceBlock>({{"n_samples_max", nSamples}, {"mark_tag", false}}))); expect(sources.back()->settings().applyStagedParameters().forwardParameters.empty()); - expect(gr::ConnectionResult::SUCCESS == graph.connect(*sources.back(), {"out", gr::meta::invalid_index}, adder, {"inputs", sources.size() - 1})); + expect(gr::ConnectionResult::SUCCESS == graph.connect(*sources.back(), "out"s, adder, "inputs#"s + std::to_string(sources.size() - 1))); } expect(gr::ConnectionResult::SUCCESS == graph.connect<"out">(adder).to<"in">(sink)); diff --git a/core/test/qa_GraphMessages.cpp b/core/test/qa_GraphMessages.cpp index fcbb6a70..a321b24b 100644 --- a/core/test/qa_GraphMessages.cpp +++ b/core/test/qa_GraphMessages.cpp @@ -36,21 +36,21 @@ const boost::ut::suite<"Graph Formatter Tests"> graphFormatterTests = [] { std::string result = fmt::format("{:s}", edge); fmt::println("Edge formatter - default: {}", result); - expect(result.contains(" ⟶ (name: 'test_edge', size: 1024, weight: 1, connected: false) ⟶")) << result; + expect(result.contains(" ⟶ (name: 'test_edge', size: 1024, weight: 1, state: WaitingToBeConnected) ⟶")) << result; }; "short names"_test = [&edge] { std::string result = fmt::format("{:s}", edge); fmt::println("Edge formatter - short 's': {}", result); - expect(result.contains(" ⟶ (name: 'test_edge', size: 1024, weight: 1, connected: false) ⟶")) << result; + expect(result.contains(" ⟶ (name: 'test_edge', size: 1024, weight: 1, state: WaitingToBeConnected) ⟶")) << result; }; "long names"_test = [&edge] { std::string result = fmt::format("{:l}", edge); fmt::println("Edge formatter - long 'l': {}", result); - expect(result.contains(" ⟶ (name: 'test_edge', size: 1024, weight: 1, connected: false) ⟶")) << result; + expect(result.contains(" ⟶ (name: 'test_edge', size: 1024, weight: 1, state: WaitingToBeConnected) ⟶")) << result; }; }; }; @@ -64,7 +64,8 @@ auto returnReplyMsg(gr::MsgPortIn& port) { return msg; }; -bool awaitCondition(std::chrono::milliseconds timeout, std::function condition) { +template +bool awaitCondition(std::chrono::milliseconds timeout, Condition condition) { auto start = std::chrono::steady_clock::now(); while (std::chrono::steady_clock::now() - start < timeout) { if (condition()) { @@ -264,10 +265,10 @@ const boost::ut::suite RunningGraphTests = [] { 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))); + expect(eq(scheduler.graph().edges().size(), 1UZ)) << "edge registered with connect"; gr::MsgPortOut toGraph; gr::MsgPortIn fromGraph; @@ -317,7 +318,7 @@ const boost::ut::suite RunningGraphTests = [] { expect(awaitCondition(1s, [&scheduler] { return scheduler.state() == lifecycle::State::RUNNING; })) << "scheduler thread up and running w/ timeout"; expect(scheduler.state() == lifecycle::State::RUNNING) << "scheduler thread up and running"; - // FIXME: expect(eq(scheduler.graph().edges().size(), 1UZ)) << "added one new edges"; + expect(eq(scheduler.graph().edges().size(), 1UZ)) << "added one edge"; expect(awaitCondition(1s, [&sink] { return sink.count >= 10U; })) << "sink received enough data"; fmt::println("executed basic graph"); @@ -355,7 +356,7 @@ const boost::ut::suite RunningGraphTests = [] { for (const auto& edge : scheduler.graph().edges()) { fmt::println("edge in list({}): {}", scheduler.graph().edges().size(), edge); } - // FIXME: expect(eq(scheduler.graph().edges().size(), 3UZ)) << "added three new edges"; + expect(eq(scheduler.graph().edges().size(), 4UZ)) << "added three new edges, one previously registered with connect"; // FIXME: edge->connection is not performed // expect(awaitCondition(1s, [&sink] { diff --git a/core/test/qa_Scheduler.cpp b/core/test/qa_Scheduler.cpp index d3a09537..07ed4e6b 100644 --- a/core/test/qa_Scheduler.cpp +++ b/core/test/qa_Scheduler.cpp @@ -648,14 +648,17 @@ const boost::ut::suite<"SchedulerTests"> SchedulerTests = [] { scheduler.timeout_ms = 100U; // also dynamically settable via messages/block interface scheduler.timeout_inactivity_count = 10U; // also dynamically settable via messages/block interface + expect(scheduler.graph().reconnectAllEdges()); + expect(source.out.isConnected()) << "source.out is connected"; + expect(monitor.in.isConnected()) << "monitor.in is connected"; + expect(monitor.out.isConnected()) << "monitor.out is connected"; + expect(eq(0, scheduler.graph().progress().value())) << "initial progress definition (0)"; std::expected schedulerResult; auto schedulerThread = std::thread([&scheduler, &schedulerResult] { schedulerResult = scheduler.runAndWait(); }); - expect(awaitCondition(1s, [&scheduler] { return scheduler.state() == lifecycle::State::RUNNING; })) << "scheduler thread up and running w/ timeout"; + expect(awaitCondition(2s, [&scheduler] { return scheduler.state() == lifecycle::State::RUNNING; })) << "scheduler thread up and running w/ timeout"; + expect(scheduler.state() == lifecycle::State::RUNNING) << "scheduler thread up and running"; - expect(source.out.isConnected()) << "source out is connected"; - expect(monitor.in.isConnected()) << "monitor in is connected"; - expect(monitor.out.isConnected()) << "monitor out is connected"; auto oldProgress = scheduler.graph().progress().value(); expect(awaitCondition(1s, [&scheduler, &oldProgress] { // wait until there is no more progress (i.e. wait until all initial buffers are filled) diff --git a/core/test/qa_grc.cpp b/core/test/qa_grc.cpp index 91d31b33..39f56292 100644 --- a/core/test/qa_grc.cpp +++ b/core/test/qa_grc.cpp @@ -63,8 +63,15 @@ auto collectBlocks(const gr::Graph& graph) { auto collectEdges(const gr::Graph& graph) { std::set result; graph.forEachEdge([&](const auto& edge) { - result.insert(fmt::format("{}#{}#{} - {}#{}#{}", edge.sourceBlock().name(), edge.sourcePortDefinition().topLevel, edge.sourcePortDefinition().subIndex, // - edge.destinationBlock().name(), edge.destinationPortDefinition().topLevel, edge.destinationPortDefinition().subIndex)); + auto portDefinitionToString = [](const gr::PortDefinition& definition) { + return std::visit(gr::meta::overloaded( // + [](const gr::PortDefinition::IndexBased& _definition) { return fmt::format("{}#{}", _definition.topLevel, _definition.subIndex); }, // + [](const gr::PortDefinition::StringBased& _definition) { return _definition.name; }), // + definition.definition); + }; + result.insert(fmt::format("{}#{} - {}#{}", // + edge.sourceBlock().name(), portDefinitionToString(edge.sourcePortDefinition()), // + edge.destinationBlock().name(), portDefinitionToString(edge.destinationPortDefinition()))); }); return result; } @@ -227,7 +234,7 @@ const boost::ut::suite GrcTests = [] { expect(eq(ConnectionResult::SUCCESS, graph1.connect<"outB", 0>(arraySource0).to<"inA", 0>(arraySink))); expect(eq(ConnectionResult::SUCCESS, graph1.connect<"outB", 1>(arraySource1).to<"inA", 1>(arraySink))); - expect(graph1.performConnections()); + expect(graph1.reconnectAllEdges()); const auto graph1Saved = gr::saveGrc(graph1); const auto graph2 = gr::loadGrc(context->loader, graph1Saved); diff --git a/meta/include/gnuradio-4.0/meta/typelist.hpp b/meta/include/gnuradio-4.0/meta/typelist.hpp index d791733b..b47a1733 100644 --- a/meta/include/gnuradio-4.0/meta/typelist.hpp +++ b/meta/include/gnuradio-4.0/meta/typelist.hpp @@ -115,8 +115,7 @@ struct splitter<8> { template<> struct splitter<16> { - template + template using first = typelist; template @@ -219,9 +218,7 @@ struct transform_conditional_impl; template class Tpl1, template class Tpl2, typename... Ts> struct transform_conditional_impl> { - using type = decltype([](std::index_sequence) -> typelist::type...> { - return {}; - }(std::make_index_sequence())); + using type = decltype([](std::index_sequence) -> typelist::type...> { return {}; }(std::make_index_sequence())); }; } // namespace detail @@ -272,8 +269,7 @@ struct find_type_or_default_impl { }; template typename Predicate, typename DefaultType, typename Head, typename... Ts> -struct find_type_or_default_impl - : std::conditional_t::value, find_type_or_default_impl, find_type_or_default_impl> {}; +struct find_type_or_default_impl : std::conditional_t::value, find_type_or_default_impl, find_type_or_default_impl> {}; template struct at_impl; @@ -319,7 +315,7 @@ struct at_impl<7, T0, T1, T2, T3, T4, T5, T6, T7, Ts...> { }; template - requires(Index >= 8) +requires(Index >= 8) struct at_impl : at_impl {}; } // namespace detail @@ -339,14 +335,12 @@ struct typelist { using apply = Other; template - static constexpr void - for_each_impl(F &&f, std::index_sequence, LeadingArguments &&...args) { - (f(std::forward(args)..., std::integral_constant{}, Ts{}), ...); + static constexpr void for_each_impl(F&& f, std::index_sequence, LeadingArguments&&... args) { + (f(std::forward(args)..., std::integral_constant{}, static_cast(nullptr)), ...); } template - static constexpr void - for_each(F &&f, LeadingArguments &&...args) { + static constexpr void for_each(F&& f, LeadingArguments&&... args) { for_each_impl(std::forward(f), std::make_index_sequence{}, std::forward(args)...); } @@ -366,10 +360,9 @@ struct typelist { static constexpr inline bool are_convertible_from = (std::convertible_to && ...); template - requires(sizeof...(Ts) == std::tuple_size_v>) - static constexpr auto - construct(Tup &&args_tuple) { - return std::apply([](Args &&...args) { return std::make_tuple(F::template apply(std::forward(args))...); }, std::forward(args_tuple)); + requires(sizeof...(Ts) == std::tuple_size_v>) + static constexpr auto construct(Tup&& args_tuple) { + return std::apply([](Args&&... args) { return std::make_tuple(F::template apply(std::forward(args))...); }, std::forward(args_tuple)); } template typename Trafo> @@ -387,17 +380,17 @@ struct typelist { template using safe_head_default = std::remove_pointer_t 0) { - return static_cast *>(nullptr); + return static_cast*>(nullptr); } else { - return static_cast(nullptr); + return static_cast(nullptr); } }())>; using safe_head = std::remove_pointer_t 0) { - return static_cast *>(nullptr); + return static_cast*>(nullptr); } else { - return static_cast(nullptr); + return static_cast(nullptr); } }())>; @@ -414,11 +407,10 @@ struct typelist { using find_or_default = typename detail::find_type_or_default_impl::type; template - static constexpr std::size_t - index_of() { + static constexpr std::size_t index_of() { std::size_t result = static_cast(-1); - gr::meta::typelist::for_each([&](auto index, auto &&t) { - if constexpr (std::is_same_v>) { + gr::meta::typelist::for_each([&](auto index, auto* t) { + if constexpr (std::is_same_v>) { result = index; } }); @@ -431,11 +423,11 @@ struct typelist { using tuple_type = std::tuple; using tuple_or_type = std::remove_pointer_t(nullptr); + return static_cast(nullptr); } else if constexpr (sizeof...(Ts) == 1) { - return static_cast *>(nullptr); + return static_cast*>(nullptr); } else { - return static_cast(nullptr); + return static_cast(nullptr); } }())>; }; @@ -445,12 +437,11 @@ constexpr bool is_any_of_v = std::disjunction_v...>; namespace detail { template typename OtherTypelist, typename... Args> -meta::typelist -to_typelist_helper(OtherTypelist *); +meta::typelist to_typelist_helper(OtherTypelist*); } // namespace detail template -using to_typelist = decltype(detail::to_typelist_helper(static_cast(nullptr))); +using to_typelist = decltype(detail::to_typelist_helper(static_cast(nullptr))); } // namespace gr::meta