diff --git a/core/include/gnuradio-4.0/Graph_yaml_importer.hpp b/core/include/gnuradio-4.0/Graph_yaml_importer.hpp index e7779069..8d4c167f 100644 --- a/core/include/gnuradio-4.0/Graph_yaml_importer.hpp +++ b/core/include/gnuradio-4.0/Graph_yaml_importer.hpp @@ -15,6 +15,36 @@ namespace gr { namespace detail { + +template +inline auto +toYamlString(const T &value) { + if constexpr (std::is_same_v>) { + return value; + } else if constexpr (std::is_same_v>) { + return value ? "true" : "false"; + } else if constexpr (requires { std::to_string(value); }) { + return std::to_string(value); + } else { + return ""; + } +} + +struct YamlSeq { + YAML::Emitter &out; + + YamlSeq(YAML::Emitter &out_) : out(out_) { out << YAML::BeginSeq; } + + ~YamlSeq() { out << YAML::EndSeq; } + + template + requires std::is_invocable_v + void + write_fn(const char * /*key*/, F &&fun) { + fun(); + } +}; + struct YamlMap { YAML::Emitter &out; @@ -24,31 +54,24 @@ struct YamlMap { template void - write(const std::string_view &key, const T &value) { + write(const std::string_view &key, const std::vector &value) { out << YAML::Key << key.data(); - out << YAML::Value << value; + YamlSeq seq(out); + for (const auto &elem : value) out << YAML::Value << toYamlString(elem); } - template + template void - write_fn(const std::string_view &key, F &&fun) { + write(const std::string_view &key, const T &value) { out << YAML::Key << key.data(); - out << YAML::Value; - fun(); + out << YAML::Value << toYamlString(value); } -}; - -struct YamlSeq { - YAML::Emitter &out; - - YamlSeq(YAML::Emitter &out_) : out(out_) { out << YAML::BeginSeq; } - - ~YamlSeq() { out << YAML::EndSeq; } template - requires std::is_invocable_v void - write_fn(const char * /*key*/, F &&fun) { + write_fn(const std::string_view &key, F &&fun) { + out << YAML::Key << key.data(); + out << YAML::Value; fun(); } }; @@ -98,14 +121,19 @@ load_grc(plugin_loader &loader, const std::string &yaml_source) { // This is a known property of this node auto try_type = [&]() { - if (it->second.index() != variant_type_list::index_of()) { - return false; + if (it->second.index() == variant_type_list::index_of()) { + const auto &value = grc_value.template as(); + new_properties[key] = value; + return true; } - const auto &value = grc_value.template as(); - new_properties[key] = value; + if (it->second.index() == variant_type_list::index_of>()) { + const auto &value = grc_value.template as>(); + new_properties[key] = value; + return true; + } - return true; + return false; }; // clang-format off @@ -117,6 +145,9 @@ load_grc(plugin_loader &loader, const std::string &yaml_source) { try_type.operator()() || try_type.operator()() || try_type.operator()() || + try_type.operator()() || + try_type.operator()() || + try_type.operator()() || try_type.operator()() || [&] { // Fallback to string, and non-defined property @@ -203,18 +234,8 @@ save_grc(const gr::Graph &testGraph) { map.write_fn("parameters", [&]() { detail::YamlMap parameters(out); auto write_map = [&](const auto &local_map) { - for (const auto &settings_pair : local_map) { - std::visit( - [&](const T &value) { - if constexpr (std::is_same_v>) { - parameters.write(settings_pair.first, value); - } else if constexpr (requires { std::to_string(value); }) { - parameters.write(settings_pair.first, std::to_string(value)); - } else { - // not supported - } - }, - settings_pair.second); + for (const auto &[settingsKey, settingsValue] : local_map) { + std::visit([&](const T &value) { parameters.write(settingsKey, value); }, settingsValue); } }; diff --git a/core/test/app_grc.cpp b/core/test/app_grc.cpp index 328287aa..60be4f36 100644 --- a/core/test/app_grc.cpp +++ b/core/test/app_grc.cpp @@ -21,11 +21,17 @@ ENABLE_REFLECTION_FOR_TEMPLATE(ArraySource, outA, outB); template struct ArraySink : public gr::Block> { - std::array, 2> inA; - std::array, 2> inB; + std::array, 2> inA; + std::array, 2> inB; + gr::Annotated bool_setting = false; + gr::Annotated string_setting; + gr::Annotated, "Bool vector setting"> bool_vector; + gr::Annotated, "String vector setting"> string_vector; + gr::Annotated, "Double vector setting"> double_vector; + gr::Annotated, "int16_t vector setting"> int16_vector; }; -ENABLE_REFLECTION_FOR_TEMPLATE(ArraySink, inA, inB); +ENABLE_REFLECTION_FOR_TEMPLATE(ArraySink, inA, inB, bool_setting, string_setting, bool_vector, string_vector, double_vector, int16_vector); struct TestContext { TestContext(std::vector paths) : registry(), loader(®istry, std::move(paths)) {} @@ -35,20 +41,23 @@ struct TestContext { }; namespace { - auto collectBlocks(const gr::Graph &graph) { - std::set result; - graph.forEachBlock([&](const auto &node) { result.insert(fmt::format("{}-{}", node.name(), node.typeName())); }); - return result; - }; +auto +collectBlocks(const gr::Graph &graph) { + std::set result; + graph.forEachBlock([&](const auto &node) { result.insert(fmt::format("{}-{}", node.name(), node.typeName())); }); + return result; +}; - 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)); - }); - return result; - }; -} +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)); + }); + return result; +}; +} // namespace int main(int argc, char *argv[]) { @@ -94,12 +103,12 @@ main(int argc, char *argv[]) { using namespace gr; registerBuiltinBlocks(&context.registry); - auto graph_source = read_file(TESTS_SOURCE_PATH "/grc/test.grc"); + auto graph_source = read_file(TESTS_SOURCE_PATH "/grc/test.grc"); - auto graph_1 = gr::load_grc(context.loader, graph_source); - auto graph_saved_source = gr::save_grc(graph_1); + auto graph_1 = gr::load_grc(context.loader, graph_source); + auto graph_saved_source = gr::save_grc(graph_1); - auto graph_2 = gr::load_grc(context.loader, graph_saved_source); + auto graph_2 = gr::load_grc(context.loader, graph_saved_source); assert(collectBlocks(graph_1) == collectBlocks(graph_2)); assert(collectEdges(graph_1) == collectEdges(graph_2)); @@ -113,9 +122,9 @@ main(int argc, char *argv[]) { GP_REGISTER_NODE_RUNTIME(&context.registry, ArraySink, double); gr::Graph graph_1; - auto &arraySink = graph_1.emplaceBlock>(); - auto &arraySource0 = graph_1.emplaceBlock>(); - auto &arraySource1 = graph_1.emplaceBlock>(); + auto &arraySink = graph_1.emplaceBlock>(); + auto &arraySource0 = graph_1.emplaceBlock>(); + auto &arraySource1 = graph_1.emplaceBlock>(); graph_1.connect<"outA", 0>(arraySource0).to<"inB", 1>(arraySink); graph_1.connect<"outA", 1>(arraySource1).to<"inB", 0>(arraySink); @@ -130,4 +139,40 @@ main(int argc, char *argv[]) { assert(collectBlocks(graph_1) == collectBlocks(graph_2)); assert(collectEdges(graph_1) == collectEdges(graph_2)); } + + // Test settings serialization + { + using namespace gr; + registerBuiltinBlocks(&context.registry); + GP_REGISTER_NODE_RUNTIME(&context.registry, ArraySink, double); + + gr::Graph graph_1; + const auto expectedString = std::string("abc"); + const auto expectedBool = true; + const auto expectedStringVector = std::vector{ "a", "b", "c" }; + const auto expectedBoolVector = std::vector{ true, false, true }; + const auto expectedDoubleVector = std::vector{ 1., 2., 3. }; + const auto expectedInt16Vector = std::vector{ 1, 2, 3 }; + auto &arraySink = graph_1.emplaceBlock>({ { "bool_setting", expectedBool }, + { "string_setting", expectedString }, + { "bool_vector", expectedBoolVector }, + { "string_vector", expectedStringVector }, + { "double_vector", expectedDoubleVector }, + { "int16_vector", expectedInt16Vector } }); + + const auto graph_1_saved = gr::save_grc(graph_1); + const auto graph_2 = gr::load_grc(context.loader, graph_1_saved); + graph_2.forEachBlock([&](const auto &node) { + const auto settings = node.settings().get(); + assert(std::get(settings.at("bool_setting")) == expectedBool); + assert(std::get(settings.at("string_setting")) == expectedString); + assert(std::get>(settings.at("bool_vector")) == expectedBoolVector); + assert(std::get>(settings.at("string_vector")) == expectedStringVector); + assert(std::get>(settings.at("double_vector")) == expectedDoubleVector); + assert(std::get>(settings.at("int16_vector")) == expectedInt16Vector); + }); + + assert(collectBlocks(graph_1) == collectBlocks(graph_2)); + assert(collectEdges(graph_1) == collectEdges(graph_2)); + } } diff --git a/core/test/grc/test.grc.expected b/core/test/grc/test.grc.expected index 368f04cd..42a7ba5d 100644 --- a/core/test/grc/test.grc.expected +++ b/core/test/grc/test.grc.expected @@ -11,24 +11,24 @@ blocks: denominator::description: denominator denominator::documentation: "Bottom of resampling ratio (<1: Decimate, >1: Interpolate, =1: No change)" denominator::unit: "" - denominator::visible: 0 + denominator::visible: false description: "" meta_information::description: meta-information meta_information::documentation: store non-graph-processing information like UI block position etc. meta_information::unit: "" - meta_information::visible: 0 + meta_information::visible: false name::description: user-defined name name::documentation: N.B. may not be unique -> ::unique_name name::unit: "" - name::visible: 0 + name::visible: false numerator::description: numerator numerator::documentation: "Top of resampling ratio (<1: Decimate, >1: Interpolate, =1: No change)" numerator::unit: "" - numerator::visible: 0 + numerator::visible: false stride::description: stride stride::documentation: samples between data processing. N for skip, =0 for back-to-back. stride::unit: "" - stride::visible: 0 + stride::visible: false unknown_property: 42 - name: multiplier id: good::multiply @@ -41,24 +41,24 @@ blocks: denominator::description: denominator denominator::documentation: "Bottom of resampling ratio (<1: Decimate, >1: Interpolate, =1: No change)" denominator::unit: "" - denominator::visible: 0 + denominator::visible: false description: "" meta_information::description: meta-information meta_information::documentation: store non-graph-processing information like UI block position etc. meta_information::unit: "" - meta_information::visible: 0 + meta_information::visible: false name::description: user-defined name name::documentation: N.B. may not be unique -> ::unique_name name::unit: "" - name::visible: 0 + name::visible: false numerator::description: numerator numerator::documentation: "Top of resampling ratio (<1: Decimate, >1: Interpolate, =1: No change)" numerator::unit: "" - numerator::visible: 0 + numerator::visible: false stride::description: stride stride::documentation: samples between data processing. N for skip, =0 for back-to-back. stride::unit: "" - stride::visible: 0 + stride::visible: false - name: counter id: builtin_counter parameters: @@ -70,24 +70,24 @@ blocks: denominator::description: denominator denominator::documentation: "Bottom of resampling ratio (<1: Decimate, >1: Interpolate, =1: No change)" denominator::unit: "" - denominator::visible: 0 + denominator::visible: false description: "" meta_information::description: meta-information meta_information::documentation: store non-graph-processing information like UI block position etc. meta_information::unit: "" - meta_information::visible: 0 + meta_information::visible: false name::description: user-defined name name::documentation: N.B. may not be unique -> ::unique_name name::unit: "" - name::visible: 0 + name::visible: false numerator::description: numerator numerator::documentation: "Top of resampling ratio (<1: Decimate, >1: Interpolate, =1: No change)" numerator::unit: "" - numerator::visible: 0 + numerator::visible: false stride::description: stride stride::documentation: samples between data processing. N for skip, =0 for back-to-back. stride::unit: "" - stride::visible: 0 + stride::visible: false - name: sink id: good::cout_sink parameters: @@ -100,24 +100,24 @@ blocks: denominator::description: denominator denominator::documentation: "Bottom of resampling ratio (<1: Decimate, >1: Interpolate, =1: No change)" denominator::unit: "" - denominator::visible: 0 + denominator::visible: false description: "" meta_information::description: meta-information meta_information::documentation: store non-graph-processing information like UI block position etc. meta_information::unit: "" - meta_information::visible: 0 + meta_information::visible: false name::description: user-defined name name::documentation: N.B. may not be unique -> ::unique_name name::unit: "" - name::visible: 0 + name::visible: false numerator::description: numerator numerator::documentation: "Top of resampling ratio (<1: Decimate, >1: Interpolate, =1: No change)" numerator::unit: "" - numerator::visible: 0 + numerator::visible: false stride::description: stride stride::documentation: samples between data processing. N for skip, =0 for back-to-back. stride::unit: "" - stride::visible: 0 + stride::visible: false unknown_property: 42 connections: - [main_source, 0, multiplier, 0]