Skip to content

Commit

Permalink
Support of std::complex in Settings and YAML
Browse files Browse the repository at this point in the history
Signed-off-by: drslebedev <[email protected]>
  • Loading branch information
drslebedev authored and RalphSteinhagen committed Jun 11, 2024
1 parent 00147be commit 5d88ea6
Show file tree
Hide file tree
Showing 4 changed files with 202 additions and 169 deletions.
197 changes: 116 additions & 81 deletions core/include/gnuradio-4.0/Graph_yaml_importer.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,34 @@
#include "Graph.hpp"
#include "PluginLoader.hpp"

namespace YAML {
// YAML custom converter for complex numbers
template<typename T>
requires std::same_as<T, double> || std::same_as<T, float>
struct convert<std::complex<T>> {
static Node encode(const std::complex<T>& rhs) {
Node node;
node.push_back(rhs.real());
node.push_back(rhs.imag());
return node;
}

static bool decode(const Node& node, std::complex<T>& rhs) {
if (!node.IsSequence() || node.size() != 2) {
return false;
}
rhs = std::complex<T>(node[0].as<T>(), node[1].as<T>());
return true;
}
};
} // namespace YAML

namespace gr {

namespace detail {

template<typename T>
inline auto
toYamlString(const T &value) {
inline auto toYamlString(const T& value) {
if constexpr (std::is_same_v<std::string, std::remove_cvref_t<T>>) {
return value;
} else if constexpr (std::is_same_v<bool, std::remove_cvref_t<T>>) {
Expand All @@ -35,53 +56,67 @@ toYamlString(const T &value) {
}

struct YamlSeq {
YAML::Emitter &out;
YAML::Emitter& out;

YamlSeq(YAML::Emitter &out_) : out(out_) { out << YAML::BeginSeq; }
YamlSeq(YAML::Emitter& out_) : out(out_) { out << YAML::BeginSeq; }

~YamlSeq() { out << YAML::EndSeq; }

template<typename F>
requires std::is_invocable_v<F>
void
write_fn(const char * /*key*/, F &&fun) {
requires std::is_invocable_v<F>
void writeFn(const char* /*key*/, F&& fun) {
fun();
}
};

struct YamlMap {
YAML::Emitter &out;
YAML::Emitter& out;

YamlMap(YAML::Emitter &out_) : out(out_) { out << YAML::BeginMap; }
YamlMap(YAML::Emitter& out_) : out(out_) { out << YAML::BeginMap; }

~YamlMap() { out << YAML::EndMap; }

template<typename T>
void
write(const std::string_view &key, const std::vector<T> &value) {
void write(const std::string_view& key, const std::vector<T>& value) {
out << YAML::Key << key.data();
YamlSeq seq(out);
for (const auto &elem : value) out << YAML::Value << toYamlString(elem);
for (const auto& elem : value) {
if constexpr (std::same_as<T, std::complex<double>> || std::same_as<T, std::complex<float>>) {
writeComplexValue(out, elem);
} else {
out << YAML::Value << toYamlString(elem);
}
}
}

template<typename T>
void
write(const std::string_view &key, const T &value) {
void write(const std::string_view& key, const T& value) {
out << YAML::Key << key.data();
out << YAML::Value << toYamlString(value);
if constexpr (std::same_as<T, std::complex<double>> || std::same_as<T, std::complex<float>>) {
writeComplexValue(out, value);
} else {
out << YAML::Value << toYamlString(value);
}
}

template<typename F>
void
write_fn(const std::string_view &key, F &&fun) {
void writeFn(const std::string_view& key, F&& fun) {
out << YAML::Key << key.data();
out << YAML::Value;
fun();
}

private:
template<typename T>
requires std::same_as<T, std::complex<double>> || std::same_as<T, std::complex<float>>
void writeComplexValue(YAML::Emitter& outEmitter, const T& value) {
YamlSeq seq(outEmitter);
outEmitter << YAML::Value << toYamlString(value.real());
outEmitter << YAML::Value << toYamlString(value.imag());
}
};

inline std::size_t
parseIndex(std::string_view str) {
inline std::size_t parseIndex(std::string_view str) {
std::size_t index{};
auto [_, src_ec] = std::from_chars(str.begin(), str.end(), index);
if (src_ec != std::errc()) {
Expand All @@ -92,17 +127,16 @@ parseIndex(std::string_view str) {

} // namespace detail

inline gr::Graph
load_grc(PluginLoader &loader, const std::string &yaml_source) {
inline gr::Graph loadGrc(PluginLoader& loader, const std::string& yamlSrc) {
Graph testGraph;

std::map<std::string, BlockModel *> createdBlocks;
std::map<std::string, BlockModel*> createdBlocks;

YAML::Node tree = YAML::Load(yaml_source);
YAML::Node tree = YAML::Load(yamlSrc);
auto blocks = tree["blocks"];
for (const auto &grc_block : blocks) {
auto name = grc_block["name"].as<std::string>();
auto id = grc_block["id"].as<std::string>();
for (const auto& grcBlock : blocks) {
auto name = grcBlock["name"].as<std::string>();
auto id = grcBlock["id"].as<std::string>();

// TODO: Discuss how GRC should store the node types, how we should
// in general handle nodes that are parametrised by more than one type
Expand All @@ -112,27 +146,27 @@ load_grc(PluginLoader &loader, const std::string &yaml_source) {
}

currentBlock->setName(name);
property_map new_properties;
property_map newProperties;

auto parameters = grc_block["parameters"];
auto parameters = grcBlock["parameters"];
if (parameters && parameters.IsMap()) {
// TODO this applyStagedParameters is a workaround to make sure that currentBlock_settings is not empty
// but contains the default values of the block (needed to covert the parameter values to the right type)
// should this be based on metadata/reflection?
std::ignore = currentBlock->settings().applyStagedParameters();
auto currentBlock_settings = currentBlock->settings().get();
for (const auto &kv : parameters) {
const auto &key = kv.first.as<std::string>();
std::ignore = currentBlock->settings().applyStagedParameters();
auto currentBlockSettings = currentBlock->settings().get();
for (const auto& kv : parameters) {
const auto& key = kv.first.as<std::string>();

if (auto it = currentBlock_settings.find(key); it != currentBlock_settings.end()) {
using variant_type_list = meta::to_typelist<pmtv::pmt>;
const YAML::Node &grc_value = kv.second;
if (auto it = currentBlockSettings.find(key); it != currentBlockSettings.end()) {
using variant_type_list = meta::to_typelist<pmtv::pmt>;
const YAML::Node& grcValue = kv.second;

// This is a known property of this node
auto try_type = [&]<typename T>() {
auto tryType = [&]<typename T>() {
if (it->second.index() == variant_type_list::index_of<T>()) {
const auto &value = grc_value.template as<T>();
new_properties[key] = value;
const auto& value = grcValue.template as<T>();
newProperties[key] = value;
return true;
}

Expand All @@ -141,61 +175,63 @@ load_grc(PluginLoader &loader, const std::string &yaml_source) {
if constexpr (std::is_same_v<T, bool>) {
// gcc-stdlibc++/clang-libc++ have different implementations for std::vector<bool>
// see https://en.cppreference.com/w/cpp/container/vector_bool for details
const auto &value = grc_value.template as<std::vector<int>>(); // need intermediary vector
const auto& value = grcValue.template as<std::vector<int>>(); // need intermediary vector
std::vector<bool> boolVector;
for (int intValue : value) {
boolVector.push_back(intValue != 0);
}
new_properties[key] = boolVector;
newProperties[key] = boolVector;
return true;
}
#endif
const auto &value = grc_value.template as<std::vector<T>>();
new_properties[key] = value;
const auto& value = grcValue.template as<std::vector<T>>();
newProperties[key] = value;
return true;
}

return false;
};

// clang-format off
try_type.operator()<std::int8_t>() ||
try_type.operator()<std::int16_t>() ||
try_type.operator()<std::int32_t>() ||
try_type.operator()<std::int64_t>() ||
try_type.operator()<std::uint8_t>() ||
try_type.operator()<std::uint16_t>() ||
try_type.operator()<std::uint32_t>() ||
try_type.operator()<std::uint64_t>() ||
try_type.operator()<bool>() ||
try_type.operator()<float>() ||
try_type.operator()<double>() ||
try_type.operator()<std::string>() ||
tryType.operator()<std::int8_t>() ||
tryType.operator()<std::int16_t>() ||
tryType.operator()<std::int32_t>() ||
tryType.operator()<std::int64_t>() ||
tryType.operator()<std::uint8_t>() ||
tryType.operator()<std::uint16_t>() ||
tryType.operator()<std::uint32_t>() ||
tryType.operator()<std::uint64_t>() ||
tryType.operator()<bool>() ||
tryType.operator()<float>() ||
tryType.operator()<double>() ||
tryType.operator()<std::string>() ||
tryType.operator()<std::complex<float>>() ||
tryType.operator()<std::complex<double>>() ||
[&] {
// Fallback to string, and non-defined property
const auto& value = grc_value.template as<std::string>();
const auto& value = grcValue.template as<std::string>();
currentBlock->metaInformation()[key] = value;
return true;
}();
// clang-format on

} else {
const auto &value = kv.second.as<std::string>();
const auto& value = kv.second.as<std::string>();
currentBlock->metaInformation()[key] = value;
}
}
}

std::ignore = currentBlock->settings().set(new_properties);
std::ignore = currentBlock->settings().set(newProperties);
createdBlocks[name] = &testGraph.addBlock(std::move(currentBlock));
}

for (const auto &connection : tree["connections"]) {
for (const auto& connection : tree["connections"]) {
if (connection.size() != 4) {
throw fmt::format("Unable to parse connection ({} instead of 4 elements)", connection.size());
}

auto parseBlock_port = [&](const auto &blockField, const auto &portField) {
auto parseBlockPort = [&](const auto& blockField, const auto& portField) {
auto blockName = blockField.template as<std::string>();
auto node = createdBlocks.find(blockName);
if (node == createdBlocks.end()) {
Expand All @@ -213,16 +249,16 @@ load_grc(PluginLoader &loader, const std::string &yaml_source) {
}
const auto indexStr = portField[0].template as<std::string>();
const auto subIndexStr = portField[1].template as<std::string>();
return result{ node, { detail::parseIndex(indexStr), detail::parseIndex(subIndexStr) } };
return result{node, {detail::parseIndex(indexStr), detail::parseIndex(subIndexStr)}};
} else {
const auto indexStr = portField.template as<std::string>();
return result{ node, { detail::parseIndex(indexStr) } };
return result{node, {detail::parseIndex(indexStr)}};
}
};

if (connection.size() == 4) {
auto src = parseBlock_port(connection[0], connection[1]);
auto dst = parseBlock_port(connection[2], connection[3]);
auto src = parseBlockPort(connection[0], connection[1]);
auto dst = parseBlockPort(connection[2], connection[3]);
testGraph.connect(*src.block_it->second, src.port_definition, *dst.block_it->second, dst.port_definition);
} else {
}
Expand All @@ -231,45 +267,44 @@ load_grc(PluginLoader &loader, const std::string &yaml_source) {
return testGraph;
}

inline std::string
save_grc(const gr::Graph &testGraph) {
inline std::string saveGrc(const gr::Graph& testGraph) {
YAML::Emitter out;
{
detail::YamlMap root(out);

root.write_fn("blocks", [&]() {
root.writeFn("blocks", [&]() {
detail::YamlSeq nodes(out);

auto writeBlock = [&](const auto &node) {
auto writeBlock = [&](const auto& node) {
detail::YamlMap map(out);
map.write("name", std::string(node.name()));

const auto &full_type_name = node.typeName();
std::string type_name(full_type_name.cbegin(), std::find(full_type_name.cbegin(), full_type_name.cend(), '<'));
map.write("id", std::move(type_name));
const auto& fullTypeName = node.typeName();
std::string typeName(fullTypeName.cbegin(), std::find(fullTypeName.cbegin(), fullTypeName.cend(), '<'));
map.write("id", std::move(typeName));

const auto &settings_map = node.settings().get();
if (!node.metaInformation().empty() || !settings_map.empty()) {
map.write_fn("parameters", [&]() {
const auto& settingsMap = node.settings().get();
if (!node.metaInformation().empty() || !settingsMap.empty()) {
map.writeFn("parameters", [&]() {
detail::YamlMap parameters(out);
auto write_map = [&](const auto &local_map) {
for (const auto &[settingsKey, settingsValue] : local_map) {
std::visit([&]<typename T>(const T &value) { parameters.write(settingsKey, value); }, settingsValue);
auto writeMap = [&](const auto& localMap) {
for (const auto& [settingsKey, settingsValue] : localMap) {
std::visit([&]<typename T>(const T& value) { parameters.write(settingsKey, value); }, settingsValue);
}
};

write_map(settings_map);
write_map(node.metaInformation());
writeMap(settingsMap);
writeMap(node.metaInformation());
});
}
};

testGraph.forEachBlock(writeBlock);
});

root.write_fn("connections", [&]() {
root.writeFn("connections", [&]() {
detail::YamlSeq nodes(out);
auto write_edge = [&](const auto &edge) {
auto writeEdge = [&](const auto& edge) {
out << YAML::Flow;
detail::YamlSeq seq(out);
out << edge.sourceBlock().name().data();
Expand All @@ -292,7 +327,7 @@ save_grc(const gr::Graph &testGraph) {
}
};

testGraph.forEachEdge(write_edge);
testGraph.forEachEdge(writeEdge);
});
}

Expand Down
6 changes: 4 additions & 2 deletions core/include/gnuradio-4.0/Settings.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -25,15 +25,17 @@ namespace settings {
template<typename T>
inline constexpr static bool isSupportedVectorType() {
if constexpr (gr::meta::vector_type<T>) {
return std::is_arithmetic_v<typename T::value_type> || std::is_same_v<typename T::value_type, std::string>;
return std::is_arithmetic_v<typename T::value_type> || std::is_same_v<typename T::value_type, std::string> //
|| std::is_same_v<typename T::value_type, std::complex<double>> || std::is_same_v<typename T::value_type, std::complex<float>>;
} else {
return false;
}
}

template<typename T>
inline constexpr static bool isSupportedType() {
return std::is_arithmetic_v<T> || std::is_same_v<T, std::string> || isSupportedVectorType<T>() || std::is_same_v<T, property_map>;
return std::is_arithmetic_v<T> || std::is_same_v<T, std::string> || isSupportedVectorType<T>() || std::is_same_v<T, property_map> //
|| std::is_same_v<T, std::complex<double>> || std::is_same_v<T, std::complex<float>>;
}

template<typename T, typename TMember>
Expand Down
2 changes: 1 addition & 1 deletion core/test/qa_Settings.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -791,7 +791,7 @@ const boost::ut::suite TransactionTests = [] {
gr::registerBlock<Sink, double>(registry);
PluginLoader loader(registry, {});
try {
scheduler::Simple sched{load_grc(loader, std::string(grc))};
scheduler::Simple sched{loadGrc(loader, std::string(grc))};
expect(sched.runAndWait().has_value());
sched.graph().forEachBlock([](auto& block) { expect(eq(std::get<float>(*block.settings().get("sample_rate")), 123456.f)) << fmt::format("sample_rate forwarded to {}", block.name()); });
} catch (const std::string& e) {
Expand Down
Loading

0 comments on commit 5d88ea6

Please sign in to comment.