Skip to content

Commit

Permalink
Merge pull request #363 from JeffersonLab/nbrei_wiring
Browse files Browse the repository at this point in the history
Refine JWiringService
  • Loading branch information
nathanwbrei authored Sep 18, 2024
2 parents 5453e55 + 6ca8805 commit cd48b50
Show file tree
Hide file tree
Showing 3 changed files with 191 additions and 57 deletions.
129 changes: 93 additions & 36 deletions src/libraries/JANA/Services/JWiringService.cc
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@

#include "JWiringService.h"
#include "JANA/Utils/JEventLevel.h"
#include "toml.hpp"
#include <exception>
#include <memory>
#include <ostream>
#include <set>

namespace jana::services {

Expand All @@ -11,40 +12,54 @@ void JWiringService::Init() {
// User is _only_ allowed to specify wiring file via parameter
// This way, we can restrict calling JWiringService::Init until inside JApplication::Init
// Then we can load the wiring file exactly once. All WiredFactoryGenerators
// (Recursively) load files
// (recursively) load files

if (!m_wirings_input_file().empty()) {
AddWirings(*m_wirings_input_file);
}
}

std::unique_ptr<JWiringService::Wiring> JWiringService::overlay(std::unique_ptr<Wiring>&& above, std::unique_ptr<Wiring>&& below) {
void JWiringService::AddWirings(std::vector<std::unique_ptr<Wiring>>& wirings_bundle, const std::string& bundle_source) {

// In theory this should be handled by the caller, but let's check just in case
if (above->plugin_name != below->plugin_name) throw JException("Plugin name mismatch!");
if (above->type_name != below->type_name) throw JException("Type name mismatch!");
std::set<std::string> prefixes_in_bundle;
for (auto& wiring: wirings_bundle) {

if (above->input_names.empty() && !below->input_names.empty()) {
above->input_names = std::move(below->input_names);
}
if (above->input_levels.empty() && !below->input_levels.empty()) {
above->input_levels = std::move(below->input_levels);
}
if (above->output_names.empty() && !below->output_names.empty()) {
above->output_names = std::move(below->output_names);
}
for (const auto& [key, val] : below->configs) {
if (above->configs.find(key) == above->configs.end()) {
above->configs[key] = val;
// Assert that this wiring's prefix is unique _within_ this bundle.
auto bundle_it = prefixes_in_bundle.find(wiring->prefix);
if (bundle_it != prefixes_in_bundle.end()) {
throw JException("Duplicated prefix '%s' in wiring bundle '%s'", wiring->prefix.c_str(), bundle_source.c_str());
}
prefixes_in_bundle.insert(wiring->prefix);

// Check whether we have seen this prefix before
auto it = m_wirings_from_prefix.find(wiring->prefix);
if (it == m_wirings_from_prefix.end()) {
// This is a new wiring
m_wirings_from_prefix[wiring->prefix] = wiring.get();
m_wirings_from_type_and_plugin_names[{wiring->type_name, wiring->plugin_name}].push_back(wiring.get());
m_wirings.push_back(std::move(wiring));
}
else {
// Wiring is already defined; overlay this wiring _below_ the existing wiring
// First we do some sanity checks
if (wiring->type_name != it->second->type_name) {
throw JException("Wiring mismatch: type name '%s' vs '%s'", wiring->type_name.c_str(), it->second->type_name.c_str());
}
if (wiring->plugin_name != it->second->plugin_name) {
throw JException("Wiring mismatch: plugin name '%s' vs '%s'", wiring->plugin_name.c_str(), it->second->plugin_name.c_str());
}
Overlay(*(it->second), *wiring);
// Useful information from `wiring` has been copied into `it->second`.
// `wiring` will now be automatically destroyed
}
}
// below gets automatically deleted
return std::move(above);
// At this point all wirings have been moved out of wirings_bundle, so we clear it to avoid confusing callers
wirings_bundle.clear();
}


std::vector<std::unique_ptr<JWiringService::Wiring>> JWiringService::parse_table(const toml::table& table) {
void JWiringService::AddWirings(const toml::table& table, const std::string& source) {

std::vector<std::unique_ptr<Wiring>> wirings;
std::map<std::string, const Wiring*> prefix_lookup;

auto facs = table["factory"].as_array();
if (facs == nullptr) {
throw JException("No factories found!");
Expand All @@ -60,16 +75,6 @@ std::vector<std::unique_ptr<JWiringService::Wiring>> JWiringService::parse_table
wiring->plugin_name = f["plugin_name"].value<std::string>().value();
wiring->type_name = f["type_name"].value<std::string>().value();
wiring->prefix = f["prefix"].value<std::string>().value();
auto it = prefix_lookup.find(wiring->prefix);
if (it != prefix_lookup.end()) {
std::ostringstream oss;
oss << "Duplicated factory prefix in wiring file: " << std::endl;
oss << " Prefix: " << wiring->prefix << std::endl;
oss << " Type name: " << wiring->type_name << " vs " << it->second->type_name << std::endl;
oss << " Plugin name: " << wiring->plugin_name << " vs " << it->second->plugin_name << std::endl;
throw JException(oss.str());
}
prefix_lookup[wiring->prefix] = wiring.get();

wiring->level = parseEventLevel(f["level"].value_or<std::string>("None"));

Expand Down Expand Up @@ -106,9 +111,61 @@ std::vector<std::unique_ptr<JWiringService::Wiring>> JWiringService::parse_table

wirings.push_back(std::move(wiring));
}
return wirings;
AddWirings(wirings, source);
}

void JWiringService::AddWirings(const std::string& filename) {
try {
auto tbl = toml::parse_file(filename);
AddWirings(tbl, filename);
}
catch (const toml::parse_error& err) {
auto e = JException("Error parsing TOML file: '%s'", filename.c_str());
e.nested_exception = std::current_exception();
throw e;
}
}

const JWiringService::Wiring* JWiringService::GetWiring(const std::string& prefix) const {
auto it = m_wirings_from_prefix.find(prefix);
if (it == m_wirings_from_prefix.end()) {
return nullptr;
}
return it->second;
}

const std::vector<JWiringService::Wiring*>&
JWiringService::GetWirings(const std::string& plugin_name, const std::string& type_name) const {

auto it = m_wirings_from_type_and_plugin_names.find({type_name, plugin_name});
if (it == m_wirings_from_type_and_plugin_names.end()) {
return m_no_wirings;
}
return it->second;
}


void JWiringService::Overlay(Wiring& above, const Wiring& below) {

// In theory this should be handled by the caller, but let's check just in case
if (above.plugin_name != below.plugin_name) throw JException("Plugin name mismatch!");
if (above.type_name != below.type_name) throw JException("Type name mismatch!");

if (above.input_names.empty() && !below.input_names.empty()) {
above.input_names = std::move(below.input_names);
}
if (above.input_levels.empty() && !below.input_levels.empty()) {
above.input_levels = std::move(below.input_levels);
}
if (above.output_names.empty() && !below.output_names.empty()) {
above.output_names = std::move(below.output_names);
}
for (const auto& [key, val] : below.configs) {
if (above.configs.find(key) == above.configs.end()) {
above.configs[key] = val;
}
}
}



Expand Down
35 changes: 21 additions & 14 deletions src/libraries/JANA/Services/JWiringService.h
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,7 @@
#include <vector>
#include <memory>

namespace jana {
namespace services {
namespace jana::services {


class JWiringService : public JService {
Expand All @@ -25,12 +24,10 @@ class JWiringService : public JService {
std::vector<std::string> input_names;
std::vector<JEventLevel> input_levels;
std::vector<std::string> output_names;
std::vector<JEventLevel> output_levels;
std::map<std::string, std::string> configs;
};

using WiringIndex = std::map<std::pair<std::string,std::string>, std::map<std::string,Wiring*>>;
// { (plugin_name,typename) : {prefix : const Wiring*}}

private:
Parameter<std::string> m_wirings_input_file {this, "jana:wiring_file", "wiring.toml",
"Path to TOML file containing wiring definitions"};
Expand All @@ -39,18 +36,28 @@ class JWiringService : public JService {
"Allow multiple definitions inside wiring files"};

std::vector<std::unique_ptr<Wiring>> m_wirings;
WiringIndex m_wirings_index;
std::map<std::string, Wiring*> m_wirings_from_prefix;
std::map<std::pair<std::string,std::string>, std::vector<Wiring*>> m_wirings_from_type_and_plugin_names;
std::vector<Wiring*> m_no_wirings;

public:
void Init() override;
void parse_file(std::string filename);
void add_wiring(std::unique_ptr<Wiring> wiring);
std::unique_ptr<Wiring> overlay(std::unique_ptr<Wiring>&& above, std::unique_ptr<Wiring>&& below);
const Wiring* get_wiring(std::string plugin_name, std::string type_name, std::string prefix);
const std::vector<std::unique_ptr<Wiring>>& get_wirings();
std::vector<std::unique_ptr<Wiring>> parse_table(const toml::table& table);

void AddWirings(std::vector<std::unique_ptr<Wiring>>& wirings, const std::string& source);
void AddWirings(const toml::table& table, const std::string& source);
void AddWirings(const std::string& filename);

const Wiring*
GetWiring(const std::string& prefix) const;

const std::vector<Wiring*>&
GetWirings(const std::string& plugin_name, const std::string& type_name) const;

const std::vector<std::unique_ptr<Wiring>>&
GetWirings() const { return m_wirings; }

static void Overlay(Wiring& above, const Wiring& below);
};

} // namespace services
} // namespace jana
} // namespace jana::services

84 changes: 77 additions & 7 deletions src/programs/unit_tests/Services/JWiringServiceTests.cc
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,9 @@ TEST_CASE("WiringTests") {

jana::services::JWiringService sut;
toml::table table = toml::parse(some_wiring);
auto wirings = sut.parse_table(table);
sut.AddWirings(table, "testcase");

const auto& wirings = sut.GetWirings();
REQUIRE(wirings.size() == 2);
REQUIRE(wirings[0]->prefix == "myfac");
REQUIRE(wirings[1]->prefix == "myfac_modified");
Expand Down Expand Up @@ -74,7 +76,7 @@ TEST_CASE("WiringTests_DuplicatePrefixes") {
jana::services::JWiringService sut;
toml::table table = toml::parse(duplicate_prefixes);
try {
auto wirings = sut.parse_table(table);
sut.AddWirings(table,"testcase");
REQUIRE(1 == 0);
}
catch (const JException& e) {
Expand All @@ -101,11 +103,79 @@ TEST_CASE("WiringTests_Overlay") {
below->configs["y"] = "42";

auto sut = jana::services::JWiringService();
auto result = sut.overlay(std::move(above), std::move(below));
REQUIRE(result->input_names[2] == "make");
REQUIRE(result->input_levels[1] == JEventLevel::PhysicsEvent);
REQUIRE(result->configs["x"] == "6.18");
REQUIRE(result->configs["y"] == "42");
sut.Overlay(*above, *below);
REQUIRE(above->input_names[2] == "make");
REQUIRE(above->input_levels[1] == JEventLevel::PhysicsEvent);
REQUIRE(above->configs["x"] == "6.18");
REQUIRE(above->configs["y"] == "42");
}

static constexpr std::string_view fake_wiring_file = R"(
[[factory]]
plugin_name = "ECAL"
type_name = "ClusteringFac"
prefix = "myfac"
[factory.configs]
x = "22"
y = "verbose"
[[factory]]
plugin_name = "ECAL"
type_name = "ClusteringFac"
prefix = "variantfac"
[factory.configs]
x = "49"
y = "silent"
[[factory]]
plugin_name = "BCAL"
type_name = "ClusteringFac"
prefix = "sillyfac"
[factory.configs]
x = "618"
y = "mediocre"
)";


TEST_CASE("WiringTests_FakeFacGen") {
jana::services::JWiringService sut;
toml::table table = toml::parse(fake_wiring_file);
sut.AddWirings(table, "testcase");

using Wiring = jana::services::JWiringService::Wiring;
std::vector<std::unique_ptr<Wiring>> fake_facgen_wirings;

// One gets overlaid with an existing wiring
auto a = std::make_unique<Wiring>();
a->plugin_name = "ECAL";
a->type_name = "ClusteringFac";
a->prefix = "variantfac";
a->configs["x"] = "42";
fake_facgen_wirings.push_back(std::move(a));

// The other is brand new
auto b = std::make_unique<Wiring>();
b->plugin_name = "ECAL";
b->type_name = "ClusteringFac";
b->prefix = "exuberantfac";
b->configs["x"] = "27";
fake_facgen_wirings.push_back(std::move(b));

// We should end up with three in total
sut.AddWirings(fake_facgen_wirings, "fake_facgen");
auto final_wirings = sut.GetWirings("ECAL", "ClusteringFac");

REQUIRE(final_wirings.size() == 3);

REQUIRE(final_wirings[0]->prefix == "myfac");
REQUIRE(final_wirings[1]->prefix == "variantfac");
REQUIRE(final_wirings[2]->prefix == "exuberantfac");

REQUIRE(final_wirings[0]->configs["x"] == "22"); // from file only
REQUIRE(final_wirings[1]->configs["x"] == "49"); // file overrides facgen
REQUIRE(final_wirings[2]->configs["x"] == "27"); // from facgen only

}

0 comments on commit cd48b50

Please sign in to comment.